-----------------------------------------------------------------------------
--
-- Module      :  Control.Monad.Supervisor
-- Copyright   :
-- License     :  BSD3
--
-- Maintainer  :  agocorona@gmail.com
-- Stability   :
-- Portability :
--
-- | A supervisor monad that explore the execution tree of an internal monad and define extra behaviours thanks to flexible instance definitions for each particular purpose.
-- It can inject new behaviours for backtracking, trace generation, testing, transaction rollbacks etc
-- The supervisor monad is used in the package MFlow to control the routing, tracing, state management, back button management and navigation in general

--
-----------------------------------------------------------------------------
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances,
             UndecidableInstances, DeriveDataTypeable,
             FunctionalDependencies #-}

module Control.Monad.Supervisor where

import Control.Monad.Trans
import Control.Monad.State
import Data.Typeable

import Debug.Trace
(!>)= flip trace

--class  Monad m => MonadState1 s m where
--   get :: m s
--   put :: s -> m ()

-- | The internal computation can be reexecuted, proceed forward or backward
data Control a = Control a | Forward a | Backward deriving (Typeable, Read, Show)

-- | The supervisor add a Control wrapper that is interpreted by the monad instance
newtype Sup s m a = Sup { runSup :: m (Control a ) }

-- | The supervise class add two general modifiers that can be applied:
class  MonadState s m => Supervise s m  where
   supBack :: s -> m ()          -- ^ Called before initiating backtracking in a control point
                                -- When the computation goes back, by default
                                -- the  state is kepth. This procedure can change
                                -- that behaviour. The state passed is the one before the
                                -- computation was executed.
   supBack = const $ return ()
   
   supervise :: s ->  m (Control a) -> m (Control a)  -- ^ When the conputation has been executed
                                                    -- this method is an opportunity for modifying the result
                                                    -- By default: supervise _= id
   supervise= const $ id


-- | Flag the computation that executes @breturn@ as a control point.
--
-- When the computation is going back, it will be re-executed (see the monad definition)
breturn :: Monad m => a -> Sup s m a
breturn = Sup . return . Control 

--instance MonadState () IO where
--  get= return()
--  put= const $ return ()
  
--instance MonadState s m => Supervise s m



-- | The Supervisor Monad is in essence an Identity monad transformer when executing Forward.
instance  Supervise s m => Monad (Sup s m) where
    fail   _ = Sup . return $ Backward
    return x = Sup . return $ Forward x
    x >>= f    = Sup $ loop 
     where
     loop = do
        s <- get
        -- execution as usual if supervise== id
        v <-  supervise s $ runSup x                        
        case v of
            --  a normal execution if supervise== id
            Forward y  -> supervise s $ runSup (f y)

            --   Backward was returned, stop the branch of execution and propagate it back
            Backward  ->  return  Backward

            -- the computaton x was a control point. if the branch of execution goes Backward
            -- then x will be reexecuted. supBack will control the state backtracking, how much of
            -- the current state we want to keep and how much we want to backtrack  
            Control y  -> do
                 z <- supervise s $ runSup (f y)            
                 case z of
                  Backward  -> supBack s >> loop           -- re-execute x   
                  other   -> return other


instance MonadTrans (Sup s) where
  lift f = Sup $  f >>= return . Forward

instance (MonadIO m,Supervise s m)=> MonadIO (Sup s m) where
  liftIO iof= Sup $ liftIO iof  >>= return . Forward

instance Supervise s m => MonadState s (Sup s m) where
   get= lift get
   put = lift . put