{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ViewPatterns #-}

module System.Console.GetOpt.Generics (
  -- * Simple IO API
  withCli,
  WithCli(),
  HasArguments,
  WithCli.Argument(argumentType, parseArgument),
  -- * Customizing the CLI
  withCliModified,
  Modifier(..),
  -- * IO API
  getArguments,
  modifiedGetArguments,
  -- * Pure API
  parseArguments,
  Result(..),
  -- * Re-exports from "Generics.SOP"
  Generics.SOP.Generic,
  HasDatatypeInfo,
  Code,
  All2,
 ) where

import           Generics.SOP
import           System.Environment

import           WithCli
import           WithCli.Parser
import           WithCli.HasArguments
import           WithCli.Result
import           System.Console.GetOpt.Generics.Modifier

-- | Parses command line arguments (gotten from 'withArgs') and returns the
--   parsed value. This function should be enough for simple use-cases.
--
--   Throws the same exceptions as 'withCli'.
--
-- Here's an example:

-- ### Start "docs/RecordType.hs" "" Haddock ###

-- |
-- >  {-# LANGUAGE DeriveGeneric #-}
-- >
-- >  module RecordType where
-- >
-- >  import qualified GHC.Generics
-- >  import           System.Console.GetOpt.Generics
-- >
-- >  -- All you have to do is to define a type and derive some instances:
-- >
-- >  data Options
-- >    = Options {
-- >      port :: Int,
-- >      daemonize :: Bool,
-- >      config :: Maybe FilePath
-- >    }
-- >    deriving (Show, GHC.Generics.Generic)
-- >
-- >  instance Generic Options
-- >  instance HasDatatypeInfo Options
-- >
-- >  -- Then you can use `getArguments` to create a command-line argument parser:
-- >
-- >  main :: IO ()
-- >  main = do
-- >    options <- getArguments
-- >    print (options :: Options)

-- ### End ###

-- | And this is how the above program behaves:

-- ### Start "docs/RecordType.shell-protocol" "" Haddock ###

-- |
-- >  $ program --port 8080 --config some/path
-- >  Options {port = 8080, daemonize = False, config = Just "some/path"}
-- >  $ program  --port 8080 --daemonize
-- >  Options {port = 8080, daemonize = True, config = Nothing}
-- >  $ program --port foo
-- >  cannot parse as INTEGER: foo
-- >  # exit-code 1
-- >  $ program
-- >  missing option: --port=INTEGER
-- >  # exit-code 1
-- >  $ program --help
-- >  program [OPTIONS]
-- >        --port=INTEGER
-- >        --daemonize
-- >        --config=STRING (optional)
-- >    -h  --help                      show help and exit

-- ### End ###

getArguments :: forall a . (Generic a, HasDatatypeInfo a, All2 HasArguments (Code a)) =>
  IO a
getArguments = modifiedGetArguments []

-- | Like 'getArguments` but allows you to pass in 'Modifier's.
modifiedGetArguments :: forall a . (Generic a, HasDatatypeInfo a, All2 HasArguments (Code a)) =>
  [Modifier] -> IO a
modifiedGetArguments modifiers = do
  args <- getArgs
  progName <- getProgName
  handleResult $ parseArguments progName modifiers args

-- | Pure variant of 'modifiedGetArguments'.
--
--   Does not throw any exceptions.
parseArguments :: forall a . (Generic a, HasDatatypeInfo a, All2 HasArguments (Code a)) =>
     String -- ^ Name of the program (e.g. from 'getProgName').
  -> [Modifier] -- ^ List of 'Modifier's to manually tweak the command line interface.
  -> [String] -- ^ List of command line arguments to parse (e.g. from 'getArgs').
  -> Result a
parseArguments progName mods args = do
  modifiers <- mkModifiers mods
  parser <- genericParser modifiers
  runParser progName modifiers
    (normalizeParser (applyModifiers modifiers parser)) args