module Quoridor.Cmdline.Options
  ( Options(..)
  , ExecMode(..)
  , getOptions
  ) where

import           Control.Monad         (unless)
import           System.Environment    (getProgName)
import           System.Exit           (exitFailure, exitSuccess)

import           System.Console.GetOpt (ArgDescr (NoArg, OptArg),
                                        ArgDescr (ReqArg),
                                        ArgOrder (RequireOrder), OptDescr,
                                        OptDescr (Option), getOpt, usageInfo)

import           Quoridor.Helpers      (andP)

-- | Represents possible options from the cmdline
data Options = Options
  { opBoardSize      :: Int
  , opNumOfPlayers   :: Int
  , opGatesPerPlayer :: Int
  , opHostListenPort :: Int
  , opHttpListenPort :: Int
  , opExecMode       :: ExecMode
  }

-- | Represents an execution mode for the program.
-- One can run quoridor at local play, server host or client join modes
data ExecMode = ExLocal | ExHost | ExJoin | ExProxy
  deriving Eq

defaultOptions :: Options
defaultOptions = Options
  { opBoardSize      = 9
  , opNumOfPlayers   = 2
  , opGatesPerPlayer = 10
  , opHostListenPort = 33996
  , opHttpListenPort = 33997
  , opExecMode       = ExLocal
  }

-- exported functions

-- | Given the args from the cmdline,
-- returns them parsed into an Options data value.
-- It runs in the IO monad to allow the ability to exit the program
-- if something fails in the parsing (upon which, a usageInfo will be displayed)
getOptions :: [String] -> IO Options
getOptions args = do
  let (actions, _, _) = getOpt RequireOrder options args
  foldl (>>=) (return defaultOptions) actions



--helpers

isInRange :: Ord a => a -> a -> a -> Bool
isInRange a b c = ((>= b) `andP` (<= c)) a

putUsageInfoLn :: IO ()
putUsageInfoLn = do
  prg <- getProgName
  putStrLn (usageInfo prg options)

options :: [ OptDescr (Options -> IO Options) ]
options =
  [ Option "b" ["board-size"]
      (ReqArg
          (\arg opts -> do
            argNum <- rangedOption 2 9 arg
            return opts { opBoardSize = argNum })
          "INTEGER")
      "Board size (2-9 rows/columns). default 9"

  , Option "n" ["number-of-players"]
      (ReqArg
          (\arg opts -> do
            argNum <- rangedOption 2 4 arg
            return opts { opNumOfPlayers = argNum })
          "INTEGER")
      "Number of players (2-4 players). default 2"

  , Option "g" ["gates-per-player"]
      (ReqArg
          (\arg opts -> do
            argNum <- rangedOption 0 100 arg
            return opts { opGatesPerPlayer = argNum })
          "INTEGER")
      "Gates per player (1-100 gates per player). default 10"

  , Option "l" ["local"]
      (NoArg $
          \opts -> return opts { opExecMode = ExLocal })
      "Start a local game"

  , Option "h" ["host"]
      (portOptionArg ExHost)
      "Host a game server. default port 33997"

  , Option "j" ["join"]
      (portOptionArg ExJoin)
      "Join a game server"

  , Option "p" ["client-proxy"]
      (portOptionArg ExProxy)
      "Client acts as proxy for a browser (used by the http server)"

  , Option "t" ["http-port"]
      (ReqArg
          (\arg opts -> do
            argNum <- rangedOption 1025 65535 arg
            return opts { opHttpListenPort = argNum })
          "PORT")
      "A port for the http-server (port 1025-65535), relevant only if --host flag is used. default 33997"

  , Option "?" ["help"]
      (NoArg $
          \_ -> do
            putUsageInfoLn
            exitSuccess)
      "Show help"
  ]
 where portOptionArg execMode =
         OptArg
             (\arg opts -> do
                argNum <- maybe (return $ opHostListenPort opts)
                  (rangedOption 1025 65535) arg
                return opts
                  { opExecMode = execMode
                  , opHostListenPort = argNum
                  })
              "PORT"
       rangedOption x y arg = do
         let argNum = read arg
         unless (isInRange argNum x y) $ do
           putUsageInfoLn
           exitFailure
         return argNum