-- Copyright: (c) 2013 GREE, Inc.
-- License: MIT-style

module System.Prefork.Main.Internal (
    masterMain
  , workerMain
  ) where

import Data.Maybe (listToMaybe, catMaybes)
import Control.Monad (unless, forM_, void)
import Control.Concurrent.STM
import System.Posix
import System.Environment (getArgs)
import System.Exit (exitSuccess)

import System.Prefork.Class
import System.Prefork.Types
import System.Prefork.Worker

data ControlMessage = 
    TerminateCM
  | InterruptCM
  | HungupCM
  | QuitCM
  | ChildCM
  deriving (Eq, Show, Read)

data Prefork sc = Prefork {
    pServerConfig :: !(TVar (Maybe sc))
  , pCtrlChan     :: !(TChan ControlMessage)
  , pProcs        :: !(TVar [ProcessID])
  , pSettings     :: !(PreforkSettings sc)
  }

workerMain :: (WorkerContext so) => (so -> IO ()) -> IO ()
workerMain act = do
  rawOpt <- getLine
  act $ decodeFromString rawOpt
  exitSuccess

masterMain :: PreforkSettings sc -> IO ()
masterMain settings = do
  ctrlChan <- newTChanIO
  procs    <- newTVarIO []
  mConfig  <- psUpdateConfig settings
  soptVar  <- newTVarIO mConfig
  atomically $ writeTChan ctrlChan HungupCM  
  setupServer ctrlChan
  (psOnStart settings) mConfig
  masterMainLoop (Prefork soptVar ctrlChan procs settings)
  mConfig' <- readTVarIO soptVar
  (psOnFinish settings) mConfig'
  return ()

masterMainLoop :: Prefork sc -> IO ()
masterMainLoop prefork@Prefork { pSettings = settings } = loop False
  where
    loop :: Bool -> IO ()
    loop finishing = do
      (msg, cids) <- atomically $ do
        msg <- readTChan $ pCtrlChan prefork
        procs <- readTVar $ pProcs prefork
        return (msg, procs)
      finRequested <- dispatch msg cids finishing
      childIds <- readTVarIO (pProcs prefork)
      unless ((finishing || finRequested) && null childIds) $ loop (finishing || finRequested)

    dispatch :: ControlMessage -> [CPid] -> Bool -> IO (Bool)
    dispatch msg cids finishing = case msg of
      TerminateCM -> do
        m <- readTVarIO $ pServerConfig prefork
        whenJust m $ flip (psOnTerminate settings) cids
        return (True)
      InterruptCM -> do
        m <- readTVarIO $ pServerConfig prefork
        whenJust m $ flip (psOnInterrupt settings) cids
        return (True)
      HungupCM -> do
        mConfig <- psUpdateConfig (pSettings prefork)
        whenJust mConfig $ \config -> do
          newProcs <- (psUpdateServer (pSettings prefork)) config
          atomically $ do
            writeTVar (pServerConfig prefork) (Just config)
            modifyTVar' (pProcs prefork) $ (++) newProcs
        return (False)
      QuitCM -> do
        m <- readTVarIO $ pServerConfig prefork
        whenJust m $ psOnQuit settings
        return (False)
      ChildCM -> do
        finished <- cleanupChildren cids (pProcs prefork)
        mConfig <- readTVarIO $ pServerConfig prefork
        case mConfig of
          Just config -> do
            forM_ finished $ (psCleanupChild settings) config
            unless finishing $ do
              newProcs <- (psOnChildFinished settings) config
              atomically $ modifyTVar' (pProcs prefork) $ (++) newProcs
          Nothing -> return ()
        return (False)

whenJust :: Monad m => Maybe a -> (a -> m ()) -> m ()
whenJust mg f = maybe (return ()) f mg

cleanupChildren :: [ProcessID] -> TVar [ProcessID] -> IO ([ProcessID])
cleanupChildren cids procs = do
  r <- mapM (getProcessStatus False False) cids -- WNOHANG is true, WUNTRACED is false
  let finished = catMaybes $ flip map (zip cids r) $ checkFinished
  atomically $ modifyTVar' procs $ filter (flip notElem finished)
  return (finished)
  where
    checkFinished (pid, x) = case x of
      Just (Exited {}) -> Just pid
      Just (Terminated {}) -> Just pid
      Just (Stopped {}) -> Nothing
      _ -> Nothing

setupServer :: TChan ControlMessage -> IO ()
setupServer chan = do
  let delegate = \sig msg -> setSignalHandler sig $ Catch $ atomically $ writeTChan chan msg
  delegate sigCHLD ChildCM
  delegate sigTERM TerminateCM
  delegate sigINT  InterruptCM
  delegate sigQUIT QuitCM
  delegate sigHUP  HungupCM
  setSignalHandler sigPIPE $ Ignore
  return ()
  where
    setSignalHandler :: Signal -> System.Posix.Handler -> IO ()
    setSignalHandler sig func = void $ installHandler sig func Nothing