content: {-|
  Module      : KMonad.Args
  Description : How to parse arguments and config files into an AppCfg
  Copyright   : (c) David Janssen, 2019
  License     : MIT
  
  Maintainer  : janssen.dhj@gmail.com
  Stability   : experimental
  Portability : non-portable (MPTC with FD, FFI to Linux-only c-code)
  
  -}
  module KMonad.Args
    ( getCmd, loadConfig, Cmd, HasCmd(..))
  where
  
  import KMonad.Prelude
  import KMonad.App.Types
  import KMonad.Args.Cmd
  import KMonad.Args.Joiner
  import KMonad.Args.Parser
  import KMonad.Args.Types
  
  --------------------------------------------------------------------------------
  --
  
  -- | Parse a configuration file into a 'AppCfg' record
  loadConfig :: HasLogFunc e => Cmd -> RIO e AppCfg
  loadConfig cmd = do
  
    tks <- loadTokens (cmd^.cfgFile)      -- This can throw a ParseError
    cgt <- joinConfigIO (joinCLI cmd tks) -- This can throw a JoinError
  
    -- Try loading the sink and src
    lf  <- view logFuncL
    snk <- liftIO . _snk cgt $ lf
    src <- liftIO . _src cgt $ lf
  
    -- Emit the release of <Enter> if requested
  
    -- Assemble the AppCfg record
    pure $ AppCfg
      { _keySinkDev   = snk
      , _keySourceDev = src
      , _keymapCfg    = _km      cgt
      , _firstLayer   = _fstL    cgt
      , _fallThrough  = _flt     cgt
      , _allowCmd     = _allow   cgt
      , _startDelay   = _strtDel cmd
      , _keyOutDelay  = fromIntegral <$> _ksd cgt
      }
  
  
  -- | Join the options given from the command line with the one read from the
  -- configuration file.
  -- This does not yet throw any kind of exception, as we are simply inserting the
  -- given options into every 'KDefCfg' block that we see.
  joinCLI :: Cmd -> [KExpr] -> [KExpr]
  joinCLI cmd = traverse._KDefCfg %~ insertCliOption cliList
   where
    -- | All options and flags that were given on the command line.
    cliList :: DefSettings
    cliList = catMaybes $
         map flagToMaybe [cmd^.cmdAllow, cmd^.fallThrgh]
      <> [cmd^.iToken, cmd^.oToken, cmd^.cmpSeq, cmd^.cmpSeqDelay, cmd^.keySeqDelay, cmd^.implArnd]
  
    -- | Convert command line flags to a 'Maybe' type, where the non-presence, as
    -- well as the default value of a flag will be interpreted as @Nothing@
    flagToMaybe :: DefSetting -> Maybe DefSetting
    flagToMaybe = \case
      SAllowCmd    b -> if b then Just (SAllowCmd    b) else Nothing
      SFallThrough b -> if b then Just (SFallThrough b) else Nothing
      _              -> Nothing
  
    -- | Insert all command line options, potentially overwriting already existing
    -- options that were given in the configuration file. This is a paramorphism
    insertCliOption :: DefSettings -> DefSettings -> DefSettings
    insertCliOption cliSettings cfgSettings =
      foldr (\s cfgs ->
               if   s `elem` cfgs
               then map (\x -> if s == x then s else x) cfgs
               else s : cfgs)
            cfgSettings
            cliSettings
  