{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, DeriveFunctor, FlexibleContexts, TypeOperators, GeneralizedNewtypeDeriving, Trustworthy, ExistentialQuantification, EmptyDataDecls #-}
module System.Console.Wizard.Internal ( Wizard (..)
                                      , PromptString (..)
                                      , (:+:) (..)
                                      , (:<:)
                                      , inject
                                      , Run (..)
                                      , run
                                      -- $functors
                                      , Output (..)
                                      , OutputLn (..)
                                      , Line (..)
                                      , LinePrewritten (..)
                                      , Password (..)
                                      , Character (..)
                                      , ArbitraryIO (..)
                                      -- $backend
                                      ) where
import Control.Monad.Free
import Control.Monad.Trans.Maybe
import Control.Applicative

-- | A string for a prompt
type PromptString = String

-- | A @Wizard b a@ is a conversation with the user via back-end @b@ that will result in a data type @a@, or may fail.
--   A 'Wizard' is made up of one or more \"primitives\" (see below), composed using the 'Applicative',
--  'Monad' and 'Alternative' instances. The 'Alternative' instance is, as you might expect, a maybe-style cascade. 
--   If the first wizard fails, the next one is tried. `mzero` can be used to induce failure directly.
--  
--  The 'Wizard' constructor is exported here for use when developing backends,  but it is better for end-users to 
--  simply pretend that 'Wizard' is an opaque data type. Don't depend on this unless you have no other choice.
-- 
--  'Wizard's are, internally, just a maybe transformer over a free monad built from some coproduct of functors,
--  each of which is a primitive action.
newtype Wizard backend a = Wizard (MaybeT (Free backend) a)
      deriving (Monad, Functor, Applicative, Alternative, MonadPlus)

-- | Coproduct of two functors
data (f :+: g) w = Inl (f w) | Inr (g w) deriving Functor

-- | Subsumption of two functors. You shouldn't define any of your own instances of this when writing back-ends, rely only on GeneralizedNewtypeDeriving.
class (Functor sub, Functor sup) => sub :<: sup where
   inj :: sub a -> sup a

instance Functor f => f :<: f where inj = id
instance (Functor f, Functor g) => f :<: (f :+: g) where inj = Inl
instance (Functor f, Functor g, Functor h, f :<: g) => f :<: (h :+: g) where inj = Inr . inj

-- | Injection function for free monads, see \"Data Types a la Carte\" from Walter Swierstra, @http:\/\/www.cs.ru.nl\/~W.Swierstra\/Publications\/DataTypesALaCarte.pdf@
inject :: (g :<: f ) => g (Free f a) -> Free f a
inject = Impure . inj

-- | A class for implementing actions on a backend. E.g Run IO Output provides an interpreter for the Output action in the IO monad.
class Run a b where
   runAlgebra :: b (a v) -> a v 

instance (Run b f, Run b g) => Run b (f :+: g) where
   runAlgebra (Inl r) = runAlgebra r
   runAlgebra (Inr r) = runAlgebra r

infixr 9 :+:

-- $functors
--  Each of the following functors is a primitive action. A back-end provides interpreters for these actions using the 'Run' class,

data Output w = Output String w deriving Functor
data OutputLn w = OutputLn String w deriving Functor
data Line w = Line PromptString (String -> w) deriving Functor
data Character w = Character PromptString (Char -> w) deriving Functor
data LinePrewritten w = LinePrewritten PromptString String String (String -> w) deriving Functor
data Password w = Password PromptString (Maybe Char) (String -> w) deriving Functor
data ArbitraryIO w = forall a. ArbitraryIO (IO a) (a -> w) 
instance Functor (ArbitraryIO) where
    fmap f (ArbitraryIO iov f') = ArbitraryIO iov (fmap f f')



run' :: (Functor f, Monad b,  Run b f) => Free f a -> b a
run' = foldFree return runAlgebra

-- | Run a wizard using some back-end.
run :: (Functor f, Monad b,  Run b f) => Wizard f a -> b (Maybe a)
run (Wizard c) = run' (runMaybeT c)


-- $backend
--   A short tutorial on writing backends.
--
--   Backends consist of two main components:
--   
--      1. A monad, @M@, in which the primitive actions are interpreted. 'Run' instances specify an interpreter for each supported
--         action, e.g @Run M Output@ will specify an interpreter for the 'Output' primitive action in the monad M.
--
--      2. A newtype, e.g @Backend a@, which is a functor, usually implemented by wrapping a coproduct of all supported features.
--         '(:<:)' instances, the 'Functor' instance, and the 'Run' instance are provided by generalized newtype deriving.
-- 
--   As an example, suppose I am writing a back-end to @IO@, like "System.Console.Wizard.BasicIO". I want to support basic input and output,
--   and arbitrary IO, so I declare instances for 'Run' for the 'IO' monad: 
--
--  @
--  instance Run IO Output      where runAlgebra (Output s w)        = putStr s   >> w
--  instance Run IO OutputLn    where runAlgebra (OutputLn s w)      = putStrLn s >> w
--  instance Run IO Line        where runAlgebra (Line s w)          = getLine    >>= w
--  instance Run IO Character   where runAlgebra (Character s w)     = getChar    >>= w
--  instance Run IO ArbitraryIO where runAlgebra (ArbitraryIO iov f) = iov        >>= f
--  @
--  
--  And then I would define the newtype for the backend, which we can call @MyIOBackend@:
--  
--  @
--  newtype MyIOBackend a = MyIOBackend ((Output :+: OutputLn :+: Line :+: Character :+: ArbitraryIO) a)
--                        deriving ( Functor, Run IO
--                                 , (:<:) Output
--                                 , (:<:) OutputLn
--                                 , (:<:) Line
--                                 , (:<:) Character
--                                 , (:<:) ArbitraryIO
--                                 )
--  @
--
--  A useful convenience is to provide a simple identity function to serve as a type coercion:
--  
--  @
--  myIOBackend :: Wizard MyIOBackend a -> Wizard MyIOBackend a
--  myIOBackend = id
--  @
-- 
--  One additional primitive action that I might want to include is the ability to clear the screen at a certain point.
--  So, we define a new data type for the action:
--
--  @
--  data ClearScreen w = ClearScreen w deriving Functor -- via -XDeriveFunctor
--  @
-- 
--  And a \"smart\" constructor for use by the user:
--
--  @
--  clearScreen :: (ClearScreen :\<: b) => Wizard b ()
--  clearScreen = Wizard $ lift $ inject (ClearScreen (Pure ())) 
--  @
--
--  (These smart constructors all follow a similar pattern. See the source of "System.Console.Wizard" for more examples)
--
--  And then we define an interpreter for it:
-- 
--  @
--  instance Run IO ArbitraryIO where runAlgebra (ClearScreen f) = clearTheScreen >> f
--  @
--
--  Now, we can use this as-is simply by directly extending our back-end:
--
--  @
--  foo :: Wizard (ClearScreen :+: MyIOBackend)
--  foo = clearScreen >> output \"Hello World!\"
--  @
--
--  Or, we could modify @MyIOBackend@ to include the extension directly.
--
--
--  For custom actions that /return/ output, the definition looks slightly different. Here is the definition of Line:
--
--  @
--  data Line w = Line (PromptString) (String -> w) deriving Functor -- via -XDeriveFunctor
--  @
-- 
--  And the smart constructor looks like this:
--
--  @
--  line :: (Line :\<: b) => PromptString -> Wizard b String
--  line s = Wizard $ lift $ inject (Line s Pure) 
--  @