module Simulation.Aivika.Trans.Circuit
       (
        Circuit(..),
        iterateCircuitInIntegTimes,
        iterateCircuitInIntegTimes_,
        iterateCircuitInTimes,
        iterateCircuitInTimes_,
        
        arrCircuit,
        accumCircuit,
        
        arrivalCircuit,
        
        delayCircuit,
        
        timeCircuit,
        
        (<?<),
        (>?>),
        filterCircuit,
        filterCircuitM,
        neverCircuit,
        
        circuitSignaling,
        circuitProcessor,
        
        integCircuit,
        integCircuitEither,
        sumCircuit,
        sumCircuitEither,
        
        circuitTransform) where
import qualified Control.Category as C
import Control.Arrow
import Control.Monad.Fix
import Simulation.Aivika.Trans.Session
import Simulation.Aivika.Trans.ProtoRef
import Simulation.Aivika.Trans.Comp
import Simulation.Aivika.Trans.Internal.Specs
import Simulation.Aivika.Trans.Internal.Simulation
import Simulation.Aivika.Trans.Internal.Dynamics
import Simulation.Aivika.Trans.Internal.Event
import Simulation.Aivika.Trans.Dynamics.Memo
import Simulation.Aivika.Trans.Transform
import Simulation.Aivika.Trans.SystemDynamics
import Simulation.Aivika.Trans.Signal
import Simulation.Aivika.Trans.Stream
import Simulation.Aivika.Trans.Process
import Simulation.Aivika.Trans.Processor
import Simulation.Aivika.Trans.Task
import Simulation.Aivika.Arrival (Arrival(..))
newtype Circuit m a b =
  Circuit { runCircuit :: a -> Event m (b, Circuit m a b)
            
          }
instance MonadComp m => C.Category (Circuit m) where
  id = Circuit $ \a -> return (a, C.id)
  (.) = dot
    where 
      (Circuit g) `dot` (Circuit f) =
        Circuit $ \a ->
        Event $ \p ->
        do (b, cir1) <- invokeEvent p (f a)
           (c, cir2) <- invokeEvent p (g b)
           return (c, cir2 `dot` cir1)
instance MonadComp m => Arrow (Circuit m) where
  arr f = Circuit $ \a -> return (f a, arr f)
  first (Circuit f) =
    Circuit $ \(b, d) ->
    Event $ \p ->
    do (c, cir) <- invokeEvent p (f b)
       return ((c, d), first cir)
  second (Circuit f) =
    Circuit $ \(d, b) ->
    Event $ \p ->
    do (c, cir) <- invokeEvent p (f b)
       return ((d, c), second cir)
  (Circuit f) *** (Circuit g) =
    Circuit $ \(b, b') ->
    Event $ \p ->
    do (c, cir1) <- invokeEvent p (f b)
       (c', cir2) <- invokeEvent p (g b')
       return ((c, c'), cir1 *** cir2)
       
  (Circuit f) &&& (Circuit g) =
    Circuit $ \b ->
    Event $ \p ->
    do (c, cir1) <- invokeEvent p (f b)
       (c', cir2) <- invokeEvent p (g b)
       return ((c, c'), cir1 &&& cir2)
instance (MonadComp m, MonadFix m) => ArrowLoop (Circuit m) where
  loop (Circuit f) =
    Circuit $ \b ->
    Event $ \p ->
    do rec ((c, d), cir) <- invokeEvent p (f (b, d))
       return (c, loop cir)
instance MonadComp m => ArrowChoice (Circuit m) where
  left x@(Circuit f) =
    Circuit $ \ebd ->
    Event $ \p ->
    case ebd of
      Left b ->
        do (c, cir) <- invokeEvent p (f b)
           return (Left c, left cir)
      Right d ->
        return (Right d, left x)
  right x@(Circuit f) =
    Circuit $ \edb ->
    Event $ \p ->
    case edb of
      Right b ->
        do (c, cir) <- invokeEvent p (f b)
           return (Right c, right cir)
      Left d ->
        return (Left d, right x)
  x@(Circuit f) +++ y@(Circuit g) =
    Circuit $ \ebb' ->
    Event $ \p ->
    case ebb' of
      Left b ->
        do (c, cir1) <- invokeEvent p (f b)
           return (Left c, cir1 +++ y)
      Right b' ->
        do (c', cir2) <- invokeEvent p (g b')
           return (Right c', x +++ cir2)
  x@(Circuit f) ||| y@(Circuit g) =
    Circuit $ \ebc ->
    Event $ \p ->
    case ebc of
      Left b ->
        do (d, cir1) <- invokeEvent p (f b)
           return (d, cir1 ||| y)
      Right b' ->
        do (d, cir2) <- invokeEvent p (g b')
           return (d, x ||| cir2)
circuitSignaling :: MonadComp m => Circuit m a b -> Signal m a -> Signal m b
circuitSignaling (Circuit cir) sa =
  Signal { handleSignal = \f ->
            Event $ \p ->
            do let s = runSession (pointRun p)
               r <- newProtoRef s cir
               invokeEvent p $
                 handleSignal sa $ \a ->
                 Event $ \p ->
                 do cir <- readProtoRef r
                    (b, Circuit cir') <- invokeEvent p (cir a)
                    writeProtoRef r cir'
                    invokeEvent p (f b) }
circuitProcessor :: MonadComp m => Circuit m a b -> Processor m a b
circuitProcessor (Circuit cir) = Processor $ \sa ->
  Cons $
  do (a, xs) <- runStream sa
     (b, cir') <- liftEvent (cir a)
     let f = runProcessor (circuitProcessor cir')
     return (b, f xs)
arrCircuit :: MonadComp m => (a -> Event m b) -> Circuit m a b
arrCircuit f =
  let x =
        Circuit $ \a ->
        Event $ \p ->
        do b <- invokeEvent p (f a)
           return (b, x)
  in x
accumCircuit :: MonadComp m => (acc -> a -> Event m (acc, b)) -> acc -> Circuit m a b
accumCircuit f acc =
  Circuit $ \a ->
  Event $ \p ->
  do (acc', b) <- invokeEvent p (f acc a)
     return (b, accumCircuit f acc') 
arrivalCircuit :: MonadComp m => Circuit m a (Arrival a)
arrivalCircuit =
  let loop t0 =
        Circuit $ \a ->
        Event $ \p ->
        let t = pointTime p
            b = Arrival { arrivalValue = a,
                          arrivalTime  = t,
                          arrivalDelay = 
                            case t0 of
                              Nothing -> Nothing
                              Just t0 -> Just (t  t0) }
        in return (b, loop $ Just t)
  in loop Nothing
delayCircuit :: MonadComp m => a -> Circuit m a a
delayCircuit a0 =
  Circuit $ \a ->
  return (a0, delayCircuit a)
timeCircuit :: MonadComp m => Circuit m a Double
timeCircuit =
  Circuit $ \a ->
  Event $ \p ->
  return (pointTime p, timeCircuit)
(>?>) :: MonadComp m
         => Circuit m a (Maybe b)
         
         -> Circuit m b c
         
         -> Circuit m a (Maybe c)
         
whether >?> process =
  Circuit $ \a ->
  Event $ \p ->
  do (b, whether') <- invokeEvent p (runCircuit whether a)
     case b of
       Nothing ->
         return (Nothing, whether' >?> process)
       Just b  ->
         do (c, process') <- invokeEvent p (runCircuit process b)
            return (Just c, whether' >?> process')
(<?<) :: MonadComp m
         => Circuit m b c
         
         -> Circuit m a (Maybe b)
         
         -> Circuit m a (Maybe c)
         
(<?<) = flip (>?>)
filterCircuit :: MonadComp m => (a -> Bool) -> Circuit m a b -> Circuit m a (Maybe b)
filterCircuit pred = filterCircuitM (return . pred)
filterCircuitM :: MonadComp m => (a -> Event m Bool) -> Circuit m a b -> Circuit m a (Maybe b)
filterCircuitM pred cir =
  Circuit $ \a ->
  Event $ \p ->
  do x <- invokeEvent p (pred a)
     if x
       then do (b, cir') <- invokeEvent p (runCircuit cir a)
               return (Just b, filterCircuitM pred cir')
       else return (Nothing, filterCircuitM pred cir)
neverCircuit :: MonadComp m => Circuit m a (Maybe b)
neverCircuit =
  Circuit $ \a -> return (Nothing, neverCircuit)
integCircuit :: MonadComp m
                => Double
                
                -> Circuit m Double Double
                
integCircuit init = start
  where
    start = 
      Circuit $ \a ->
      Event $ \p ->
      do let t = pointTime p
         return (init, next t init a)
    next t0 v0 a0 =
      Circuit $ \a ->
      Event $ \p ->
      do let t  = pointTime p
             dt = t  t0
             v  = v0 + a0 * dt
         v `seq` return (v, next t v a)
integCircuitEither :: MonadComp m
                      => Double
                      
                      -> Circuit m (Either Double Double) Double
                      
                      
integCircuitEither init = start
  where
    start = 
      Circuit $ \a ->
      Event $ \p ->
      do let t = pointTime p
         return (init, next t init a)
    next t0 v0 a0 =
      Circuit $ \a ->
      Event $ \p ->
      do let t = pointTime p
         case a0 of
           Left v ->
             v `seq` return (v, next t v a)
           Right a0 -> do
             let dt = t  t0
                 v  = v0 + a0 * dt
             v `seq` return (v, next t v a)
sumCircuit :: (MonadComp m, Num a)
              => a
              
              -> Circuit m a a
              
sumCircuit init = start
  where
    start = 
      Circuit $ \a ->
      Event $ \p ->
      return (init, next init a)
    next v0 a0 =
      Circuit $ \a ->
      Event $ \p ->
      do let v = v0 + a0
         v `seq` return (v, next v a)
sumCircuitEither :: (MonadComp m, Num a)
                    => a
                    
                    -> Circuit m (Either a a) a
                    
                    
sumCircuitEither init = start
  where
    start = 
      Circuit $ \a ->
      Event $ \p ->
      return (init, next init a)
    next v0 a0 =
      Circuit $ \a ->
      Event $ \p ->
      case a0 of
        Left v ->
          v `seq` return (v, next v a)
        Right a0 -> do
          let v = v0 + a0
          v `seq` return (v, next v a)
circuitTransform :: MonadComp m => Circuit m a b -> Transform m a b
circuitTransform cir = Transform start
  where
    start m =
      Simulation $ \r ->
      do let s = runSession r
         ref <- newProtoRef s cir
         invokeSimulation r $
           memo0Dynamics (next ref m)
    next ref m =
      Dynamics $ \p ->
      do a <- invokeDynamics p m
         cir <- readProtoRef ref
         (b, cir') <-
           invokeDynamics p $
           runEvent (runCircuit cir a)
         writeProtoRef ref cir'
         return b
iterateCircuitInPoints_ :: MonadComp m => [Point m] -> Circuit m a a -> a -> Event m ()
iterateCircuitInPoints_ [] cir a = return ()
iterateCircuitInPoints_ (p : ps) cir a =
  enqueueEvent (pointTime p) $
  Event $ \p' ->
  do (a', cir') <- invokeEvent p $ runCircuit cir a
     invokeEvent p $ iterateCircuitInPoints_ ps cir' a'
iterateCircuitInPoints :: MonadComp m => [Point m] -> Circuit m a a -> a -> Event m (Task m a)
iterateCircuitInPoints ps cir a =
  do let loop [] cir a source = triggerSignal source a
         loop (p : ps) cir a source =
           enqueueEvent (pointTime p) $
           Event $ \p' ->
           do (a', cir') <- invokeEvent p $ runCircuit cir a
              invokeEvent p $ loop ps cir' a' source
     source <- liftSimulation newSignalSource
     loop ps cir a source
     runTask $ processAwait $ publishSignal source
iterateCircuitInIntegTimes_ :: MonadComp m => Circuit m a a -> a -> Event m ()
iterateCircuitInIntegTimes_ cir a =
  Event $ \p ->
  do let ps = integPoints $ pointRun p
     invokeEvent p $ 
       iterateCircuitInPoints_ ps cir a
iterateCircuitInTimes_ :: MonadComp m => [Double] -> Circuit m a a -> a -> Event m ()
iterateCircuitInTimes_ ts cir a =
  Event $ \p ->
  do let ps = map (pointAt $ pointRun p) ts
     invokeEvent p $ 
       iterateCircuitInPoints_ ps cir a 
iterateCircuitInIntegTimes :: MonadComp m => Circuit m a a -> a -> Event m (Task m a)
iterateCircuitInIntegTimes cir a =
  Event $ \p ->
  do let ps = integPoints $ pointRun p
     invokeEvent p $ 
       iterateCircuitInPoints ps cir a
iterateCircuitInTimes :: MonadComp m => [Double] -> Circuit m a a -> a -> Event m (Task m a)
iterateCircuitInTimes ts cir a =
  Event $ \p ->
  do let ps = map (pointAt $ pointRun p) ts
     invokeEvent p $ 
       iterateCircuitInPoints ps cir a