module Options.Applicative.Builder.Internal (
  -- * Internals
  Mod(..),
  HasName(..),
  HasCompleter(..),
  HasValue(..),
  HasMetavar(..),
  OptionFields(..),
  FlagFields(..),
  CommandFields(..),
  ArgumentFields(..),
  DefaultProp(..),

  optionMod,
  fieldMod,

  baseProps,
  mkCommand,
  mkParser,
  mkOption,
  mkProps,

  internal,
  noGlobal
  ) where

import Control.Applicative
import Control.Monad (mplus)
import Data.Semigroup hiding (Option)
import Prelude

import Options.Applicative.Common
import Options.Applicative.Types

data OptionFields a = OptionFields
  { optNames :: [OptName]
  , optCompleter :: Completer
  , optNoArgError :: String -> ParseError }

data FlagFields a = FlagFields
  { flagNames :: [OptName]
  , flagActive :: a }

data CommandFields a = CommandFields
  { cmdCommands :: [(String, ParserInfo a)]
  , cmdGroup :: Maybe String }

data ArgumentFields a = ArgumentFields
  { argCompleter :: Completer }

class HasName f where
  name :: OptName -> f a -> f a

instance HasName OptionFields where
  name n fields = fields { optNames = n : optNames fields }

instance HasName FlagFields where
  name n fields = fields { flagNames = n : flagNames fields }

class HasCompleter f where
  modCompleter :: (Completer -> Completer) -> f a -> f a

instance HasCompleter OptionFields where
  modCompleter f p = p { optCompleter = f (optCompleter p) }

instance HasCompleter ArgumentFields where
  modCompleter f p = p { argCompleter = f (argCompleter p) }

class HasValue f where
  -- this is just so that it is not necessary to specify the kind of f
  hasValueDummy :: f a -> ()
instance HasValue OptionFields where
  hasValueDummy _ = ()
instance HasValue ArgumentFields where
  hasValueDummy _ = ()

class HasMetavar f where
  hasMetavarDummy :: f a -> ()
instance HasMetavar OptionFields where
  hasMetavarDummy _ = ()
instance HasMetavar ArgumentFields where
  hasMetavarDummy _ = ()
instance HasMetavar CommandFields where
  hasMetavarDummy _ = ()

-- mod --

data DefaultProp a = DefaultProp
  (Maybe a)
  (Maybe (a -> String))

instance Monoid (DefaultProp a) where
  mempty = DefaultProp Nothing Nothing
  mappend = (<>)

instance Semigroup (DefaultProp a) where
  (DefaultProp d1 s1) <> (DefaultProp d2 s2) =
    DefaultProp (d1 `mplus` d2) (s1 `mplus` s2)

-- | An option modifier.
--
-- Option modifiers are values that represent a modification of the properties
-- of an option.
--
-- The type parameter @a@ is the return type of the option, while @f@ is a
-- record containing its properties (e.g. 'OptionFields' for regular options,
-- 'FlagFields' for flags, etc...).
--
-- An option modifier consists of 3 elements:
--
--  - A field modifier, of the form @f a -> f a@. These are essentially
--  (compositions of) setters for some of the properties supported by @f@.
--
--  - An optional default value and function to display it.
--
--  - A property modifier, of the form @OptProperties -> OptProperties@. This
--  is just like the field modifier, but for properties applicable to any
--  option.
--
-- Modifiers are instances of 'Monoid', and can be composed as such.
--
-- One rarely needs to deal with modifiers directly, as most of the times it is
-- sufficient to pass them to builders (such as 'strOption' or 'flag') to
-- create options (see 'Options.Applicative.Builder').
data Mod f a = Mod (f a -> f a)
                   (DefaultProp a)
                   (OptProperties -> OptProperties)

optionMod :: (OptProperties -> OptProperties) -> Mod f a
optionMod = Mod id mempty

fieldMod :: (f a -> f a) -> Mod f a
fieldMod f = Mod f mempty id

instance Monoid (Mod f a) where
  mempty = Mod id mempty id
  mappend = (<>)

-- | @since 0.13.0.0
instance Semigroup (Mod f a) where
  Mod f1 d1 g1 <> Mod f2 d2 g2
    = Mod (f2 . f1) (d2 <> d1) (g2 . g1)

-- | Base default properties.
baseProps :: OptProperties
baseProps = OptProperties
  { propMetaVar = ""
  , propVisibility = Visible
  , propHelp = mempty
  , propShowDefault = Nothing
  , propDescMod = Nothing
  , propShowGlobal = True
  }

mkCommand :: Mod CommandFields a -> (Maybe String, [String], String -> Maybe (ParserInfo a))
mkCommand m = (group, map fst cmds, (`lookup` cmds))
  where
    Mod f _ _ = m
    CommandFields cmds group = f (CommandFields [] Nothing)

mkParser :: DefaultProp a
         -> (OptProperties -> OptProperties)
         -> OptReader a
         -> Parser a
mkParser d@(DefaultProp def _) g rdr =
  let
    o = liftOpt $ mkOption d g rdr
  in
    maybe o (\a -> o <|> pure a) def

mkOption :: DefaultProp a
         -> (OptProperties -> OptProperties)
         -> OptReader a
         -> Option a
mkOption d g rdr = Option rdr (mkProps d g)

mkProps :: DefaultProp a
        -> (OptProperties -> OptProperties)
        -> OptProperties
mkProps (DefaultProp def sdef) g = props
  where
    props = (g baseProps)
      { propShowDefault = sdef <*> def }

-- | Hide this option completely from the help text
--
-- Use 'hidden' if the option should remain visible in the full description.
internal :: Mod f a
internal = optionMod $ \p -> p { propVisibility = Internal }


-- | Suppress this option from appearing in global options
noGlobal :: Mod f a
noGlobal = optionMod $ \pp -> pp { propShowGlobal = False }