-- | Internal workings of 'Betsy', the Cartel flag maker.  Use of this
-- module may break 'Betsy' invariants.
module Cartel.Betsy.Internal where

import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Cartel.Types
import Control.Applicative

-- | Errors that may result from running a 'Betsy' computation.
data Error
  = DuplicateFlag FlagName
  -- ^ The user requested creation of a duplicate flag.
  | Failed String
  -- ^ 'fail' was invoked.
  | EmptyFlagName
  -- ^ The user requested creation of a flag with an empty name.
  deriving (Eq, Ord, Show)

renderError :: Error -> String
renderError e = unlines $
  "Error while attempting to generate Cabal file from Cartel source."
  : case e of
      DuplicateFlag (FlagName c cs) ->
        ["Duplicated flag: " ++ (c:cs)]
      Failed s -> ["The \"fail\" function was invoked: " ++ s]
      EmptyFlagName -> ["Empty flag name"]

-- | Internal state maintained in the 'Betsy' type.
data State = State [(FlagName, FlagOpts)]
  deriving (Eq, Ord, Show)

-- | Computations that can create and use Cabal flags.  Use of this
-- type, along with the 'Cartel.defaultMain' function ensures that any
-- 'FlagName' you use has been properly set up by using 'makeFlag'.
-- That way, you don't use flags in a 'Cartel.flag' without actually
-- declaring the flag.  When 'Cartel.defaultMain' creates your Cabal
-- file, it will print the necessary @Flag@ sections.
-- 'Betsy' is parameterized on a type, @m@.  When this type is a
-- monad, 'Betsy' is also a monad, allowing you to use use the usual
-- monad combinators and @do@ notation.  'Betsy' is also a monad transformer.

newtype Betsy m a = Betsy (State -> m (Either Error (a, State)))

instance Monad m => Monad (Betsy m) where
  return a = Betsy $ \s -> return (Right (a, s))
  (Betsy l) >>= f = Betsy $ \s -> do
    ei <- l s
    case ei of
      Left e -> return (Left e)
      Right (g, s') -> do
        let Betsy r = f g
        r s'
  fail s = Betsy $ \_ -> return (Left (Failed s))

instance Functor m => Functor (Betsy m) where
  fmap f (Betsy k) = Betsy $ fmap (fmap f') k
      f' ei = fmap (\(a, s) -> (f a, s)) ei

instance (Monad m, Functor m) => Applicative (Betsy m) where
  pure = return
  (<*>) = ap

instance MonadTrans Betsy where
  lift k = Betsy $ \st -> do
    a <- k
    return $ Right (a, st)

instance MonadIO m => MonadIO (Betsy m) where
  liftIO = lift . liftIO

-- ** Flags

-- | Options for flags, except for the flag's name.
data FlagOpts = FlagOpts
  { flagDescription :: String
  -- ^ A one-line description of what the flag does; this is optional.
  , flagDefault :: Bool
  -- ^ Is this flag on or off by default?
  , flagManual :: Bool
  -- ^ If a flag is manual, Cabal will not change its value.  If a
  -- flag is not manual, Cabal will change its value automatically to
  -- attempt to satisfy the package's dependencies.
  } deriving (Eq, Ord, Show)

-- | The name of a flag.  Only 'makeFlag' creates flags; it will
-- return a 'FlagName' to you.  You can then use that 'FlagName' in a
-- conditional using 'Cartel.flag'.
data FlagName = FlagName
  { flagNameHead :: Char
  , flagNameTail :: String
  } deriving (Eq, Ord, Show)

-- | Creates new flags.
  :: Applicative m
  => NonEmptyString
  -- ^ Name of flag
  -> FlagOpts
  -- ^ Options for the flag
  -> Betsy m FlagName
  -- ^ This operation will fail if there is already a flag with the
  -- name you gave.
makeFlag nes opts = Betsy $ \(State fls) -> pure $
  case nes of
    [] -> Left EmptyFlagName
    x:xs ->
      let nm = FlagName x xs
      in case lookup nm fls of
          Nothing ->
            let st' = State ((nm, opts) : fls)
            in Right (nm, st')
          Just _ -> Left (DuplicateFlag nm)

  :: Functor m
  => Betsy m a
  -> m (Either Error (a, [(FlagName, FlagOpts)]))
  -- ^ Returns 'Left' if the making of a flag failed.  Otherwise,
  -- returns the result of the computation, along with a list of all
  -- flags made.
runBetsy (Betsy f) = fmap (fmap g) $ f (State [])
    g (a, State ls) = (a, ls)

-- | Returns a list of all flags made so far.
currentFlags :: Applicative f => Betsy f [(FlagName, FlagOpts)]
currentFlags = Betsy $ \(State ls) -> pure (Right (ls, State ls))