    {- |

    Handling for the command-line options that can be used to configure
    Dyre. As of the last count, there are four of them, and more are
    unlikely to be needed. The only one that a user should ever need to
    use is the @--force-reconf@ option, so the others all begin with
    @--dyre-<option-name>@.

    At the start of the program, before anything else occurs, the
    'withDyreOptions' function is used to hide Dyre's command-line
    options. They are loaded into the @IO@ monad using the module
    "System.IO.Storage". This keeps them safely out of the way of
    the user code and our own.

    Later, when Dyre needs to access the options, it does so through
    the accessor functions defined here. When it comes time to pass
    control over to a new binary, it gets an argument list which
    preserves the important flags with a call to 'customOptions'.

    -}
    module Config.Dyre.Options
      ( removeDyreOptions
      , withDyreOptions
      , customOptions
      , getDenyReconf
      , getForceReconf
      , getDebug
      , getMasterBinary
      , getStatePersist
      ) where

    import Data.List                     (isPrefixOf)
    import Data.Maybe                    (fromMaybe)
    import System.IO.Storage             (withStore, putValue, getValue, getDefaultValue)
    import System.Environment            (getArgs, getProgName, withArgs)
    import System.Environment.Executable (getExecutablePath)

    import Config.Dyre.Params

    -- | Remove all Dyre's options from the given commandline arguments.
    removeDyreOptions :: [String] -> [String]
    removeDyreOptions :: [[Char]] -> [[Char]]
    removeDyreOptions = ([Char] -> Bool) -> [[Char]] -> [[Char]]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (([Char] -> Bool) -> [[Char]] -> [[Char]])
    -> ([Char] -> Bool) -> [[Char]] -> [[Char]]
    forall a b. (a -> b) -> a -> b
    $ Bool -> Bool
    not (Bool -> Bool) -> ([Char] -> Bool) -> [Char] -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [[Char]] -> [Char] -> Bool
    forall {a}. Eq a => [[a]] -> [a] -> Bool
    prefixElem [[Char]]
    dyreArgs
      where prefixElem :: [[a]] -> [a] -> Bool
    prefixElem [[a]]
    xs = [Bool] -> Bool
    forall (t :: * -> *). Foldable t => t Bool -> Bool
    or ([Bool] -> Bool) -> ([a] -> [Bool]) -> [a] -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (([a] -> Bool) -> [a] -> Bool) -> [[a] -> Bool] -> [[a]] -> [Bool]
    forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
    zipWith ([a] -> Bool) -> [a] -> Bool
    forall a b. (a -> b) -> a -> b
    ($) (([a] -> [a] -> Bool) -> [[a]] -> [[a] -> Bool]
    forall a b. (a -> b) -> [a] -> [b]
    map [a] -> [a] -> Bool
    forall a. Eq a => [a] -> [a] -> Bool
    isPrefixOf [[a]]
    xs) ([[a]] -> [Bool]) -> ([a] -> [[a]]) -> [a] -> [Bool]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [a] -> [[a]]
    forall a. a -> [a]
    repeat

    -- | Store Dyre's command-line options to the IO-Store "dyre",
    --   and then execute the provided IO action with all Dyre's
    --   options removed from the command-line arguments.
    withDyreOptions :: Params c r -> IO a -> IO a
    withDyreOptions :: forall c r a. Params c r -> IO a -> IO a
    withDyreOptions Params{configCheck :: forall cfgType a. Params cfgType a -> Bool
    configCheck = Bool
    check} IO a
    action = [Char] -> IO a -> IO a
    forall a. [Char] -> IO a -> IO a
    withStore [Char]
    "dyre" (IO a -> IO a) -> IO a -> IO a
    forall a b. (a -> b) -> a -> b
    $ do
        -- Pretty important
        args <- IO [[Char]]
    getArgs

        -- If the flag exists, it overrides the current file. Likewise,
        --   if it doesn't exist, we end up with the path to our current
        --   file. This seems like a sensible way to do it.
        -- Don't use 'getExecutablePath' if we're byassing the rest of Dyre.
        this <- if check then getExecutablePath else getProgName
        putValue "dyre" "masterBinary" this
        storeFlag args "--dyre-master-binary=" "masterBinary"

        -- Load the other important arguments into IO storage.
        storeFlag args "--dyre-state-persist=" "persistState"
        putValue "dyre" "forceReconf"  $ "--force-reconf" `elem` args
        putValue "dyre" "denyReconf"   $ "--deny-reconf"  `elem` args
        putValue "dyre" "debugMode"    $ "--dyre-debug"   `elem` args

        -- We filter the arguments, so now Dyre's arguments 'vanish'
        withArgs (removeDyreOptions args) action

    -- | Get the value of the @--force-reconf@ flag, which is used
    --   to force a recompile of the custom configuration.
    getForceReconf :: IO Bool
    getForceReconf :: IO Bool
    getForceReconf = [Char] -> [Char] -> Bool -> IO Bool
    forall a. Typeable a => [Char] -> [Char] -> a -> IO a
    getDefaultValue [Char]
    "dyre" [Char]
    "forceReconf" Bool
    False

    -- | Get the value of the @--deny-reconf@ flag, which disables
    --   recompilation. This overrides "--force-reconf", too.
    getDenyReconf :: IO Bool
    getDenyReconf :: IO Bool
    getDenyReconf = [Char] -> [Char] -> Bool -> IO Bool
    forall a. Typeable a => [Char] -> [Char] -> a -> IO a
    getDefaultValue [Char]
    "dyre" [Char]
    "denyReconf" Bool
    False

    -- | Get the value of the @--dyre-debug@ flag, which is used
    --   to debug a program without installation. Specifically,
    --   it forces the application to use @./cache/@ as the cache
    --   directory, and @./@ as the configuration directory.
    getDebug  :: IO Bool
    getDebug :: IO Bool
    getDebug = [Char] -> [Char] -> Bool -> IO Bool
    forall a. Typeable a => [Char] -> [Char] -> a -> IO a
    getDefaultValue [Char]
    "dyre" [Char]
    "debugMode" Bool
    False

    -- | Get the path to the master binary. This is set to the path of
    --   the /current/ binary unless the @--dyre-master-binary=@ flag
    --   is set. Obviously, we pass the @--dyre-master-binary=@ flag to
    --   the custom configured application from the master binary.
    getMasterBinary :: IO (Maybe String)
    getMasterBinary :: IO (Maybe [Char])
    getMasterBinary = [Char] -> [Char] -> IO (Maybe [Char])
    forall a. Typeable a => [Char] -> [Char] -> IO (Maybe a)
    getValue [Char]
    "dyre" [Char]
    "masterBinary"

    -- | Get the path to a persistent state file. This is set only when
    --   the @--dyre-state-persist=@ flag is passed to the program. It
    --   is used internally by "Config.Dyre.Relaunch" to save and restore
    --   state when relaunching the program.
    getStatePersist :: IO (Maybe String)
    getStatePersist :: IO (Maybe [Char])
    getStatePersist = [Char] -> [Char] -> IO (Maybe [Char])
    forall a. Typeable a => [Char] -> [Char] -> IO (Maybe a)
    getValue [Char]
    "dyre" [Char]
    "persistState"

    -- | Return the set of options which will be passed to another instance
    --   of Dyre. Preserves the master binary, state file, and debug mode
    --   flags, but doesn't pass along the forced-recompile flag. Can be
    --   passed a set of other arguments to use, or it defaults to using
    --   the current arguments when passed 'Nothing'.
    customOptions :: Maybe [String] -> IO [String]
    customOptions :: Maybe [[Char]] -> IO [[Char]]
    customOptions Maybe [[Char]]
    otherArgs = do
        masterPath <- IO (Maybe [Char])
    getMasterBinary
        stateFile  <- getStatePersist
        debugMode  <- getDebug
        mainArgs <- maybe getArgs pure otherArgs

        -- Combine the other arguments with the Dyre-specific ones
        pure $ mainArgs ++ concat
            [ ["--dyre-debug" | debugMode]
            , ["--dyre-state-persist=" ++ sf | Just sf <- [stateFile]]
            , [ "--dyre-master-binary="
                ++ fromMaybe (error "'dyre' data-store doesn't exist (in Config.Dyre.Options.customOptions)") masterPath]
            ]

    -- | Look for the given flag in the argument array, and store
    --   its value under the given name if it exists.
    storeFlag :: [String] -> String -> String -> IO ()
    storeFlag :: [[Char]] -> [Char] -> [Char] -> IO ()
    storeFlag [[Char]]
    args [Char]
    flag [Char]
    name
        | [[Char]] -> Bool
    forall a. [a] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null [[Char]]
    match  = () -> IO ()
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return ()
        | Bool
    otherwise   = [Char] -> [Char] -> [Char] -> IO ()
    forall a. Typeable a => [Char] -> [Char] -> a -> IO ()
    putValue [Char]
    "dyre" [Char]
    name ([Char] -> IO ()) -> [Char] -> IO ()
    forall a b. (a -> b) -> a -> b
    $ Int -> [Char] -> [Char]
    forall a. Int -> [a] -> [a]
    drop ([Char] -> Int
    forall a. [a] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length [Char]
    flag) ([[Char]] -> [Char]
    forall a. HasCallStack => [a] -> a
    head [[Char]]
    match)
      where match :: [[Char]]
    match = ([Char] -> Bool) -> [[Char]] -> [[Char]]
    forall a. (a -> Bool) -> [a] -> [a]
    filter ([Char] -> [Char] -> Bool
    forall a. Eq a => [a] -> [a] -> Bool
    isPrefixOf [Char]
    flag) [[Char]]
    args

    -- | The array of all arguments that Dyre recognizes. Used to
    --   make sure none of them are visible past 'withDyreOptions'
    dyreArgs :: [String]
    dyreArgs :: [[Char]]
    dyreArgs = [ [Char]
    "--force-reconf", [Char]
    "--deny-reconf"
               , [Char]
    "--dyre-state-persist", [Char]
    "--dyre-debug"
               , [Char]
    "--dyre-master-binary" ]