{-# LANGUAGE RankNTypes #-}
-----------------------------------------------------------------------------
-- Copyright 2015, Ideas project team. This file is distributed under the
-- terms of the Apache License 2.0. For more information, see the files
-- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-----------------------------------------------------------------------------
--  $Id: Encoder.hs 8747 2015-10-15 14:54:48Z bastiaan $

module Ideas.Encoding.Encoder
   ( -- * Converter type class
     Converter(..)
   , getExercise, getStdGen, getScript, getRequest
   , withExercise, withOpenMath, (//)
     -- * Options
   , Options, simpleOptions, makeOptions
     -- * Encoder datatype
   , Encoder, TypedEncoder
   , makeEncoder, encoderFor, exerciseEncoder
   , (<?>), encodeTyped
     -- * Decoder datatype
   , Decoder, TypedDecoder
   , makeDecoder, decoderFor
   , split, symbol, setInput
     -- re-export
   , module Data.Monoid, module Control.Applicative
   , module Control.Arrow
   ) where

import Control.Applicative hiding (Const)
import Control.Arrow
import Control.Monad
import Data.Monoid
import Ideas.Common.Library hiding (exerciseId, symbol)
import Ideas.Common.Utils (Some(..))
import Ideas.Service.DomainReasoner
import Ideas.Service.FeedbackScript.Parser (parseScriptSafe, Script)
import Ideas.Service.Request
import Ideas.Service.Types
import Ideas.Text.XML
import System.Random (newStdGen, mkStdGen, StdGen)
import qualified Control.Category as C

-------------------------------------------------------------------
-- Converter type class

class Converter f where
   fromOptions :: (Options a -> t) -> f a s t
   run  :: Monad m => f a s t -> Options a -> s -> m t

getExercise :: Converter f => f a s (Exercise a)
getExercise = fromOptions exercise

getStdGen :: Converter f => f a s StdGen
getStdGen = fromOptions stdGen

getScript :: Converter f => f a s Script
getScript = fromOptions script

getRequest :: Converter f => f a s Request
getRequest = fromOptions request

withExercise :: (Converter f, Monad (f a s)) => (Exercise a -> f a s t) -> f a s t
withExercise = (getExercise >>=)

withOpenMath :: (Converter f, Monad (f a s)) => (Bool -> f a s t) -> f a s t
withOpenMath = (liftM useOpenMath getRequest >>=)

(//) :: (Converter f, Monad (f a s2)) => f a s t -> s -> f a s2 t
p // a = do
   xs <- fromOptions id
   run p xs a

-------------------------------------------------------------------
-- Options

data Options a = Options
   { exercise :: Exercise a -- the current exercise
   , request  :: Request    -- meta-information about the request
   , stdGen   :: StdGen     -- random number generator
   , script   :: Script     -- feedback script
   }

simpleOptions :: Exercise a -> Options a
simpleOptions ex =
   let req = emptyRequest {encoding = [EncHTML]}
   in Options ex req (mkStdGen 0) mempty

makeOptions :: DomainReasoner -> Request -> IO (Some Options)
makeOptions dr req = do
   Some ex  <-
      case exerciseId req of
         Just code -> findExercise dr code
         Nothing   -> return (Some emptyExercise)

   scr <- case feedbackScript req of
             Just s -> parseScriptSafe s
             Nothing
                | getId ex == mempty -> return mempty
                | otherwise          -> defaultScript dr (getId ex)
   stdgen <- newStdGen
   return $ Some Options
      { exercise = ex
      , request  = req
      , stdGen   = stdgen
      , script   = scr
      }

-------------------------------------------------------------------
-- Encoder datatype

newtype Encoder a s t = Enc { runEnc :: Options a -> s -> Error t }

type TypedEncoder a = Encoder a (TypedValue (Type a))

instance C.Category (Encoder a) where
   id    = arr id
   f . g = Enc $ \xs -> runEnc g xs >=> runEnc f xs

instance Arrow (Encoder a) where
   arr   f = Enc $ \_ -> return . f
   first f = Enc $ \xs (a, b) -> runEnc f xs a >>= \c -> return (c, b)

instance Alternative (Encoder a s) where
   empty = mzero
   (<|>) = mplus

instance Monad (Encoder a s) where
   return a = Enc $ \_ _ -> return a
   fail s   = Enc $ \_ _ -> fail s
   p >>= f  = Enc $ \xs s -> do
      (a) <- runEnc p xs s
      runEnc (f a) xs s

instance MonadPlus (Encoder a s) where
   mzero = fail "Decoder: mzero"
   mplus p q = Enc $ \xs s ->
      runEnc p xs s `mplus` runEnc q xs s

instance Functor (Encoder a s) where
   fmap = liftM

instance Applicative (Encoder a s) where
   pure  = return
   (<*>) = liftM2 ($)

instance Converter Encoder where
   fromOptions f = Enc $ \xs _ -> return (f xs)
   run f xs = runErrorM . runEnc f xs

instance Monoid t => Monoid (Encoder a s t) where
   mempty  = pure mempty
   mappend = liftA2 (<>)

instance BuildXML t => BuildXML (Encoder a s t) where
   n .=. s   = pure (n .=. s)
   unescaped = pure . unescaped
   builder   = pure . builder
   tag       = liftA . tag

makeEncoder :: (s -> t) -> Encoder a s t
makeEncoder = arr

encoderFor :: (s -> Encoder a s t) -> Encoder a s t
encoderFor f = C.id >>= f

exerciseEncoder :: (Exercise a -> s -> t) -> Encoder a s t
exerciseEncoder f = withExercise $ makeEncoder . f

infixr 5 <?>

(<?>) :: (Encoder a t b, Type a1 t) -> Encoder a (TypedValue (Type a1)) b
                                    -> Encoder a (TypedValue (Type a1)) b
(p, t) <?> q = do
   val ::: tp <- makeEncoder id
   case equal tp t of
      Just f -> p // f val
      Nothing -> q

encodeTyped :: Encoder st t b -> Type a t -> Encoder st (TypedValue (Type a)) b
encodeTyped p t = (p, t) <?> fail "Types do not match"

-------------------------------------------------------------------
-- Decoder datatype

newtype Decoder a s t = Dec { runDec :: Options a -> s -> Error (t, s) }

type TypedDecoder a s = forall t . Type a t -> Decoder a s t

instance Monad (Decoder a s) where
   return a = Dec $ \_ s -> return (a, s)
   fail s   = Dec $ \_ _ -> fail s
   p >>= f  = Dec $ \xs s1 -> do
      (a, s2) <- runDec p xs s1
      runDec (f a) xs s2

instance MonadPlus (Decoder a s) where
   mzero = fail "Decoder: mzero"
   mplus p q = Dec $ \xs s ->
      runDec p xs s `mplus` runDec q xs s

instance Functor (Decoder a s) where
   fmap = liftM

instance Applicative (Decoder a s) where
   pure  = return
   (<*>) = liftM2 ($)

instance Alternative (Decoder a s) where
   empty = fail "Decoder: empty"
   (<|>) = mplus

get :: Decoder a s s
get = Dec $ \_ s -> return (s, s)

put :: s -> Decoder a s ()
put s = Dec $ \_ _ -> return ((), s)

instance Converter Decoder where
   fromOptions f = Dec $ \xs s -> return (f xs, s)
   run f xs = liftM fst . runErrorM . runDec f xs

split :: (s -> Either String (t, s)) -> Decoder a s t
split f = get >>= either fail (\(a, s2) -> put s2 >> return a) . f

symbol :: Decoder a [s] s
symbol = split f
 where
   f []     = Left "Empty input"
   f (x:xs) = Right (x, xs)

setInput :: s -> Decoder a s ()
setInput inp = split (\_ -> Right ((), inp))

makeDecoder:: (s -> t) -> Decoder a s t
makeDecoder f = fmap f get

decoderFor :: (s -> Decoder a s t) -> Decoder a s t
decoderFor f = get >>= f

-------------------------------------------------------------------
-- Error monad (helper)

newtype Error a = Error { runError :: Either String a }

instance Functor Error where
   fmap = (<$>)

instance Applicative Error where
   pure  = return
   (<*>) = ap

instance Alternative Error where
   empty = mzero
   (<|>) = mplus

instance Monad Error where
   fail    = Error . Left
   return  = Error . Right
   m >>= f = Error $ either Left (runError . f) (runError m)

instance MonadPlus Error where
   mzero     = fail "mzero"
   mplus p q = Error $
      case (runError p, runError q) of
         (Right a, _) -> Right a
         (_, Right a) -> Right a
         (Left s, _)  -> Left s

runErrorM :: Monad m => Error a -> m a
runErrorM = either fail return . runError