{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ExistentialQuantification #-}

-- |Types used by other modules in the package.
--
--  The module contains the following exception hierarchy:
--
--  * 'SomeREPLError'
--
--      * 'SomeAskerError'
--
--          * 'AskerTypeError'
--          * 'AskerPredicateError'
--          * 'AskerInputAbortedError'
--
--      * 'SomeCommandError'
--
--          * 'MalformedParamsError'
--          * 'TooFewParamsError'
--          * 'TooManyParamsError'
--
--  * 'NoConfigFileParseError'
--
module System.REPL.Types where

import Control.Exception (SomeException(..), Exception(..))
import qualified Data.Functor.Apply as Ap
import qualified Data.Functor.Bind as Bi
import qualified Data.Text as T
import Data.Typeable

-- Asker types
-------------------------------------------------------------------------------

-- |An error message indicating that a value wasn't able to be parsed.
type TypeError = SomeException
-- |An error message indicating that a value failed a predicate.
type PredicateError = SomeException
-- |A prompt.
type PromptMsg = T.Text

-- |A predicate which a value has to fulfil.
type Predicate m a b = a -> m (Either PredicateError b)

-- |A predicate which does not change the type of its input.
type Predicate' m a = Predicate m a a

-- |A parser which either returns a parsed value or an error message.
type Parser a = T.Text -> Either TypeError a

-- |The description of an \'ask for user input\'-action.
--  The type parameters are the used monad (typically 'IO' or 'ExceptT'),
--  the type of the read value and the type of the error that is thrown
--  in case of failures.
--
--  The components are a prompt, a parser, and a predicate that
--  the parsed value must fulfil. The predicate
--
--  * is monadic and
--  * can change the returned type (useful for adjoining additional information)
data Asker m a b = Asker{ -- |The prompt to be displayed to the user.
                          askerPrompt::T.Text,
                          -- |The parser for the input value.
                          askerParser::Parser a,
                          -- |The predicate which the input, once read,
                          --  must fulfill. The Left side is an error message.
                          askerPredicate::Predicate m a b}

-- |An Asker which does not convert its argument into a different type after parsing.
type Asker' m a = Asker m a a

-- |Root of the exception hierarchy.
data SomeREPLError = forall e.Exception e => SomeREPLError e deriving (Typeable)
instance Show SomeREPLError where show (SomeREPLError e) = show e
instance Exception SomeREPLError

replErrorUpcast :: (Exception a) => a -> SomeException
replErrorUpcast = toException . SomeREPLError
replErrorDowncast :: (Exception a) => SomeException -> Maybe a
replErrorDowncast x = do {SomeREPLError y <- fromException x; cast y}

-- |Generic error related to 'Asker's. Either the input was incorrect
--  in some way, or the process was aborted by the user.
data SomeAskerError = forall e.Exception e => SomeAskerError e deriving (Typeable)
instance Show SomeAskerError where show (SomeAskerError e) = show e
instance Exception SomeAskerError where
   toException = replErrorUpcast
   fromException = replErrorDowncast

askerErrorUpcast :: (Exception a) => a -> SomeException
askerErrorUpcast = toException . SomeAskerError
askerErrorDowncast :: (Exception a) => SomeException -> Maybe a
askerErrorDowncast x = do {SomeAskerError y <- fromException x; cast y}

-- |The input could not be parsed.
data AskerTypeError = AskerTypeError SomeException deriving (Show, Typeable)
instance Exception AskerTypeError where
   toException = askerErrorUpcast
   fromException = askerErrorDowncast

-- |The parsed value failed a predicate.
data AskerPredicateError = AskerPredicateError SomeException deriving (Show, Typeable)
instance Exception AskerPredicateError where
   toException = askerErrorUpcast
   fromException = askerErrorDowncast

-- |The input for an Asker was aborted by the user.
data AskerInputAbortedError = AskerInputAbortedError deriving (Show, Typeable)
instance Exception AskerInputAbortedError where
   toException = askerErrorUpcast
   fromException = askerErrorDowncast

-- |A generic type failure for use with Askers.
data GenericTypeError = GenericTypeError T.Text deriving (Show, Typeable, Eq)
instance Exception GenericTypeError

-- |Constructor for 'GenericTypeError' which wraps the value into a 'SomeException'.
genericTypeError :: T.Text -> SomeException
genericTypeError = SomeException . GenericTypeError

-- |A generic predicate failure for use with Askers.
data GenericPredicateError = GenericPredicateError T.Text deriving (Show, Typeable, Eq)
instance Exception GenericPredicateError

-- |Constructor for 'GenericTypeError' which wraps the value into a 'SomeException'.
genericPredicateError :: T.Text -> SomeException
genericPredicateError = SomeException . GenericPredicateError

-- |A verbatim Text whose Read instance simply returns the read
--  string, as-is.
--  This is useful for askers which ask for strings without quotes.
newtype Verbatim = Verbatim{fromVerbatim::T.Text}

-- |Read-instance for 'Verbatim'. Wraps the given value into quotes and
--  reads it a a 'T.Text'.
instance Read Verbatim where
   readsPrec _ s = [(Verbatim $ T.pack s,"")]

-- Types for example askers
-------------------------------------------------------------------------------

-- |Indicates whether the target of a path exists and what form it has.
data PathExistenceType = IsDirectory | IsFile | DoesNotExist deriving (Eq, Show, Ord, Read, Enum, Bounded)

-- |Indicates that no part of a path exists.
data PathRootDoesNotExist = PathRootDoesNotExist FilePath deriving (Typeable, Eq, Show)
instance Exception PathRootDoesNotExist

-- |Indicates that the last existing portion of a path is not writable.
data PathIsNotWritable = PathIsNotWritable FilePath deriving (Typeable, Eq, Show)
instance Exception PathIsNotWritable

-- Command types
-------------------------------------------------------------------------------

-- Exceptions
-------------------------------------------------------------------------------

-- |Generic error related to command execution.
data SomeCommandError = forall e.Exception e => SomeCommandError e deriving (Typeable)
instance Show SomeCommandError where show (SomeCommandError e) = show e
instance Exception SomeCommandError where
   toException = replErrorUpcast
   fromException = replErrorDowncast

commandErrorUpcast :: (Exception a) => a -> SomeException
commandErrorUpcast = toException . SomeCommandError
commandErrorDowncast :: (Exception a) => SomeException -> Maybe a
commandErrorDowncast x = do {SomeCommandError y <- fromException x; cast y}

-- |The input of a command was malformed and could not be interpreted. I.e.
--  the input contained inadmissible characters, or quotes were mismatched.
--  The 'Text' argument contains the parser error.
data MalformedParamsError = MalformedParamsError T.Text deriving (Show, Eq, Typeable, Ord)
instance Exception MalformedParamsError where
   toException = commandErrorUpcast
   fromException = commandErrorDowncast

-- |Too many parameters were given to a command. The first value is the maximum,
--  the second the actual number.
data TooManyParamsError = TooManyParamsError Int Int deriving (Show, Eq, Typeable, Ord)
instance Exception TooManyParamsError where
   toException = commandErrorUpcast
   fromException = commandErrorDowncast

-- |Too few parameters were given to a command. The first value is the minium,
--  the second the actual number.
data TooFewParamsError = TooFewParamsError Int Int deriving (Show, Eq, Typeable, Ord)
instance Exception TooFewParamsError where
   toException = commandErrorUpcast
   fromException = commandErrorDowncast

-- Command type
-------------------------------------------------------------------------------

-- |A REPL command, possibly with parameters.
data Command m i a = Command{
                     -- |The short name of the command. Purely informative.
                     commandName :: T.Text,
                     -- |Returns whether the first part of an input
                     --  (the command name) matches
                     --  a the command. 'defCommandTest' is appropriate for most cases.
                     commandTest :: i -> Bool,
                     -- |A description of the command.
                     commandDesc :: T.Text,
                     -- |Runs the command with the input text as parameter,
                     --  returning the unconsumed input.
                     runPartialCommand :: [i] -> m (a, [i])}

instance Functor m => Functor (Command m i) where
   fmap f c@Command{runPartialCommand=run} = c{runPartialCommand=(fmap (\(x,y) -> (f x, y))  . run)}

instance (Monad m) => Ap.Apply (Command m i) where
   -- |Runs the first command, then the second with the left-over input.
   --  The result of the first command is applied to that of the second.
   --
   --  All other fields (name, description,...) of the second command are
   --  ignored.
   f <.> g = f{runPartialCommand = h}
      where
         h input = do (func, output) <- runPartialCommand f input
                      (arg, output') <- runPartialCommand g output
                      return (func arg, output')


instance (Monad m) => Bi.Bind (Command m i) where
   -- |The same as 'Ap.<.>', but the second argument can read the result of the
   --  first.
   f >>- g = f{runPartialCommand = h}
      where
         h input = do (res, output)   <- runPartialCommand f input
                      (res', output') <- runPartialCommand (g res) output
                      return (res', output')


-- Config file types
-------------------------------------------------------------------------------

-- |Indicates that some string was not able to be parsed.
data NoConfigFileParseError = NoConfigFileParseError T.Text deriving (Show, Eq, Read, Typeable)

instance Exception NoConfigFileParseError