module Main where 

import Control.DeepSeq 
import Control.Exception
import Control.Monad.IO.Class
import Data.IORef 
import Data.Proxy 
import Network.BSD (getHostByName, hostAddress)
import Network.Socket (SockAddr(..))
import System.Environment
import System.Exit
import Text.Read 

import Game.Core
import Game.GoreAndAsh
import Game.GoreAndAsh.Actor
import Game.GoreAndAsh.Network
import Game.GoreAndAsh.Sync

import FPS 
import Game

simulationFPS :: Int 
simulationFPS = 60 

parseArgs :: IO (String, Int)
parseArgs = do 
  args <- getArgs 
  case args of 
    [h, p] -> case readMaybe p of 
      Nothing -> fail "Failed to parse port"
      Just pint -> return (h, pint)
    _ -> fail "Misuse of arguments: gore-and-ash-server HOST PORT"

main :: IO ()
main = withModule (Proxy :: Proxy AppMonad) $ do
  gs <- newGameState $ runActor' mainWire
  gsRef <- newIORef gs
  fps <- makeFPSBounder simulationFPS
  (host, port) <- liftIO parseArgs
  firstStep fps host port gs gsRef `onCtrlC` exitHandler gsRef
  where
    -- | What to do on emergency exit
    exitHandler gsRef = do 
      gs <- readIORef gsRef 
      cleanupGameState gs
      exitSuccess

    -- | Resolve given hostname and port
    getAddr s p = do
      he <- getHostByName s
      return $ SockAddrInet p $ hostAddress he

    -- | Initialization step
    firstStep fps host port gs gsRef = do 
      (_, gs') <- stepGame gs $ do 
        networkSetDetailedLoggingM False
        syncSetLoggingM True
        syncSetRoleM SyncMaster
        addr <- liftIO $ getAddr host (fromIntegral port)
        networkBind (Just addr) 100 2 0 0
      writeIORef gsRef gs'
      gameLoop fps gs' gsRef

    -- | Normal game loop
    gameLoop fps gs gsRef = do 
      waitFPSBound fps
      (a, gs') <- stepGame gs (return ())
      writeIORef gsRef gs'
      a `deepseq` gameLoop fps gs' gsRef

-- | Executes given handler on Ctrl-C pressing
onCtrlC :: IO a -> IO () -> IO a
p `onCtrlC` q = catchJust isUserInterrupt p (const $ q >> p `onCtrlC` q)
  where
    isUserInterrupt :: AsyncException -> Maybe ()
    isUserInterrupt UserInterrupt = Just ()
    isUserInterrupt _             = Nothing