{-# LANGUAGE GADTs, RankNTypes #-}
-----------------------------------------------------------------------------
-- Copyright 2013, Open Universiteit Nederland. This file is distributed
-- under the terms of the GNU General Public License. For more information,
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-----------------------------------------------------------------------------
module Ideas.Encoding.Evaluator
   ( EncoderState, simpleEncoder, maybeEncoder, eitherEncoder
   , encoderFor, encoderStateFor, encodeTyped
   , runEncoderState, runEncoderStateM, (//)
   , getState, withState
     -- re-export
   , pure, (<$>), (<**>)
   , module Data.Monoid, liftA2
     -- old
   , Evaluator(..), evalService
   ) where

import Control.Applicative hiding (Const)
import Control.Arrow
import Control.Monad
import Data.List
import Data.Monoid
import Ideas.Common.Classes
import Ideas.Service.Types
import Ideas.Text.XML
import qualified Control.Category as C

newtype EncoderState st a b = Enc (st -> a -> Either [String] b)

instance C.Category (EncoderState st) where
   id = Enc $ const Right
   Enc f . Enc g = Enc $ \st -> either Left (f st) . g st

instance Arrow (EncoderState st) where
   arr f = Enc $ \_ -> Right . f
   first  (Enc f) = Enc $ \st (a, c) -> fmap (\b -> (b, c)) (f st a)
   second (Enc f) = Enc $ \st (a, b) -> fmap (\c -> (a, c)) (f st b)
   Enc f *** Enc g = Enc $ \st (a, b) ->
      case (f st a, g st b) of
         (Right c, Right d) -> Right (c, d)
         (Left err, _)      -> Left err
         (_, Left err)      -> Left err

instance ArrowZero (EncoderState st) where
   zeroArrow = Enc $ \_ _ -> Left []

instance ArrowPlus (EncoderState st) where
   Enc f <+> Enc g = Enc $ \st a ->
      case (f st a, g st a) of
         (Right b, _      ) -> Right b
         (_,       Right b) -> Right b
         (Left e1, Left e2) -> Left (e1 ++ e2)

instance ArrowChoice (EncoderState st) where
   left  (Enc f) = Enc $ \st -> either (fmap Left . f st) (Right . Right)
   right (Enc f) = Enc $ \st -> either (Right . Left) (fmap Right . f st)
   Enc f +++ Enc g = Enc $ \st -> either (fmap Left . f st) (fmap Right . g st)

instance ArrowApply (EncoderState st) where
   app = Enc $ \st (Enc f, a) -> f st a

instance Functor (EncoderState st a) where
   fmap = liftA

instance Applicative (EncoderState st a) where
   pure    = arr . const
   f <*> g = f &&& g >>> arr (uncurry ($))

instance Monoid b => Monoid (EncoderState st a b) where
   mempty  = pure mempty
   mappend = liftA2 (<>)

instance Monad (EncoderState st a) where
   return = pure
   fail s = Enc $ \_ _ -> Left [ s | not (null s) ]
   Enc f >>= g = Enc $ \st a ->
      case f st a of
         Left err -> Left err
         Right b  -> let Enc h = g b in h st a

instance MonadPlus (EncoderState st a) where
   mzero = zeroArrow
   mplus = (<+>)

instance BuildXML b => BuildXML (EncoderState st a b) where
   n .=. s   = return (n .=. s)
   unescaped = return . unescaped
   builder   = return . builder
   tag       = liftM . tag

getState :: EncoderState st a st
getState = Enc $ const . Right

withState :: (st -> b) -> EncoderState st a b
withState f = liftM f getState

runEncoderState :: EncoderState st a b -> st -> a -> Either String b
runEncoderState (Enc f) st = mapFirst (intercalate ", ") . f st

---

simpleEncoder :: (a -> b) -> EncoderState st a b
simpleEncoder = arr

maybeEncoder :: (a -> Maybe b) -> EncoderState st a b
maybeEncoder f = C.id >>= maybe mzero return . f

eitherEncoder :: (a -> Either String b) -> EncoderState st a b
eitherEncoder f = C.id >>= either fail return . f

encoderFor :: (a -> EncoderState st a b) -> EncoderState st a b
encoderFor = encoderStateFor . const

encoderStateFor :: (st -> a -> EncoderState st a b) -> EncoderState st a b
encoderStateFor f = do
   st <- getState
   a  <- C.id
   f st a

runEncoderStateM :: Monad m => EncoderState st a b -> st -> a -> m b
runEncoderStateM f st = either fail return . runEncoderState f st

encodeTyped :: Typed a t => EncoderState st t b -> EncoderState st (TypedValue (Type a)) b
encodeTyped enc = fromTyped >>> enc

infixl 8 //

(//) :: EncoderState st a c -> a -> EncoderState st b c
f // a = arr (const a) >>> f

----

fromTyped :: Typed a t => EncoderState st (TypedValue (Type a)) t
fromTyped = maybeEncoder $ \(val ::: tp) -> fmap ($ val) (equal tp typed)

-------------------------------------------------------------------

evalService :: Monad m => Evaluator a m b -> Service -> m b
evalService f = eval f . serviceFunction

data Evaluator a m b where
   Evaluator :: (TypedValue (Type a) -> m b)  -- encoder
             -> (forall t . Type a t -> m t)  -- decoder
             -> Evaluator a m b

{-
type Fix a = a -> a

encodeTypeRep :: Monoid a => (TypedValue f -> a) -> TypedValue (TypeRep f) -> a
encodeTypeRep = fix . encodeTypeRepFix

encodeTypeRepFix :: Monoid a => (TypedValue f -> a) -> Fix (TypedValue (TypeRep f) -> a)
encodeTypeRepFix enc rec (val ::: tp) =
   case tp of
      _ :-> _    -> mempty
      t1 :|: t2  -> case val of
                       Left a  -> rec (a ::: t1)
                       Right a -> rec (a ::: t2)
      Pair t1 t2 -> rec (fst val ::: t1) <> rec (snd val ::: t2)
      List t     -> mconcat (map (rec . (::: t)) val)
      Tree t     -> F.fold (fmap (rec . (::: t)) val)
      Unit       -> mempty
      Tag _ t    -> rec (val ::: t)
      Iso v t    -> rec (to v val ::: t)
      Const ctp  -> enc (val ::: ctp)

encodeWith :: (Monad m, Typed a t) => (t -> m b) -> TypedValue (Type a) -> m b
encodeWith enc (val ::: tp) =
   case equal tp typed of
      Just f  -> enc (f val)
      Nothing -> fail "encoding failed" -}

eval :: Monad m => Evaluator a m b -> TypedValue (Type a) -> m b
eval f@(Evaluator enc dec) tv@(val ::: tp) =
   case tp of
      -- handle exceptions
      Const String :|: t ->
         either fail (\a -> eval f (a ::: t)) val
      -- uncurry function if possible
      t1 :-> t2 :-> t3 ->
         eval f (uncurry val ::: Pair t1 t2 :-> t3)
      t1 :-> t2 -> do
         a <- dec t1
         eval f (val a ::: t2)
      _ ->
         enc tv