{-# LANGUAGE ScopedTypeVariables, TypeFamilies, FlexibleContexts, UndecidableInstances #-}
module System.Console.CmdLib.ADTs where

import System.Console.CmdLib.Attribute
import System.Console.CmdLib.Flag
import System.Console.GetOpt
import Data.Data
import Data.Generics.Aliases( extB )
import Data.Maybe( fromMaybe )

-- | The ADT wrapper type allows use of classic ADTs (algebraic data types) for
-- flag representation. The flags are then passed to the command as a list of
-- values of this type. However, you need to make the type an instance of the
-- Attributes first (if you do not wish to attach any attributes, you may keep
-- the instance body empty). E.g.:
--
-- > data Flag = Simplify | Wibblify Int
-- > instance Attributes where
-- >     attributes _ = Wibblify %> Help "Add a wibblification pass." %+ ArgHelp "intensity" %%
-- >                    Simplify %> Help "Enable a two-pass simplifier."
--
-- The "Command" instances should then use @(ADT Flag)@ for their second type
-- parameter (the flag type).
newtype (Attributes adt) => ADT adt = ADT adt deriving Eq

instance (Eq (ADT adt), Attributes adt, Data adt) => FlagType (ADT adt) where
  type Folded (ADT adt) = [adt]

  flag_attrkey (ADT x) = KeyC $ toConstr x
  flag_empty _ = []
  flag_type (ADT flag) _ = optionType (gmapQ dataTypeOf flag) (gmapQ typeOf flag)

  flag_list = [ ADT $ fromConstr x | x <- dataTypeConstrs $ dataTypeOf (undefined :: adt) ]
  flag_defaults attr = [ (setdef f:) | ADT f <- flag_list, length (gmapQ typeOf f) == 1,
                                       enabled (attr $ ADT f) ]
    where setdef :: adt -> adt
          setdef f = fromConstrB (defvalue $ attrs (ADT f) attr) (toConstr f)

  flag_attrs (ADT flag) = (attrFun $ attributes flag) (KeyC $ toConstr flag) %+
                          long (nameFromConstr $ toConstr flag) %+
                          defaults

  flag_set (ADT flag) v = (fromConstrB ((error "flag_set" `extB` v) ()) (toConstr flag) :)
  flag_parse (ADT flag) str = (fromConstrB (readFlag (undefined :: adt) str) (toConstr flag) :)

  flag_args (ADT flag) attr = case flag_type (ADT flag) attr of
    BooleanOption -> OptArg addoptional ""
    OptionalArgument -> OptArg addoptional ""
    RequiredArgument -> ReqArg add ""
    SimpleOption -> NoArg $ (flag:)

    where reify :: Data a => String -> a
          reify y = fromConstrB (readFlag (undefined :: adt) y) (toConstr flag)
          add x = (reify x :)
          addoptional x = (reify (fromMaybe "" x) :)