module Simulation.Aivika.Trans.Var
       (Var,
        varChanged,
        varChanged_,
        newVar,
        readVar,
        varMemo,
        writeVar,
        modifyVar,
        freezeVar) where
import Data.Array
import Simulation.Aivika.Trans.Session
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.Internal.Signal
import Simulation.Aivika.Trans.Ref
import Simulation.Aivika.Trans.Signal
import qualified Simulation.Aivika.Trans.Vector as V
import qualified Simulation.Aivika.Trans.Vector.Unboxed as UV
data Var m a = 
  Var { varXS    :: UV.Vector m Double,
        varMS    :: V.Vector m a,
        varYS    :: V.Vector m a,
        varChangedSource :: SignalSource m a }
     
newVar :: MonadComp m => a -> Simulation m (Var m a)
newVar a =
  Simulation $ \r ->
  do let sn = runSession r
     xs <- UV.newVector sn
     ms <- V.newVector sn
     ys <- V.newVector sn
     UV.appendVector xs $ spcStartTime $ runSpecs r
     V.appendVector ms a
     V.appendVector ys a
     s  <- invokeSimulation r newSignalSource
     return Var { varXS = xs,
                  varMS = ms,
                  varYS = ms,
                  varChangedSource = s }
varMemo :: MonadComp m => Var m a -> Dynamics m a
varMemo v =
  runEventWith CurrentEventsOrFromPast $
  Event $ \p ->
  do let xs = varXS v
         ms = varMS v
         ys = varYS v
         t  = pointTime p
     count <- UV.vectorCount xs
     let i = count  1
     x <- UV.readVector xs i
     if x < t
       then do a <- V.readVector ys i
               UV.appendVector xs t
               V.appendVector ms a
               V.appendVector ys a
               return a
       else if x == t
            then V.readVector ms i
            else do i <- UV.vectorBinarySearch xs t
                    if i >= 0
                      then V.readVector ms i
                      else V.readVector ms $  (i + 1)  1
readVar :: MonadComp m => Var m a -> Event m a
readVar v = 
  Event $ \p ->
  do let xs = varXS v
         ys = varYS v
         t  = pointTime p
     count <- UV.vectorCount xs
     let i = count  1
     x <- UV.readVector xs i
     if x <= t 
       then V.readVector ys i
       else do i <- UV.vectorBinarySearch xs t
               if i >= 0
                 then V.readVector ys i
                 else V.readVector ys $  (i + 1)  1
writeVar :: MonadComp m => Var m a -> a -> Event m ()
writeVar v a =
  Event $ \p ->
  do let xs = varXS v
         ms = varMS v
         ys = varYS v
         t  = pointTime p
         s  = varChangedSource v
     count <- UV.vectorCount xs
     let i = count  1
     x <- UV.readVector xs i
     if t < x 
       then error "Cannot update the past data: writeVar."
       else if t == x
            then V.writeVector ys i $! a
            else do UV.appendVector xs t
                    V.appendVector ms $! a
                    V.appendVector ys $! a
     invokeEvent p $ triggerSignal s a
modifyVar :: MonadComp m => Var m a -> (a -> a) -> Event m ()
modifyVar v f =
  Event $ \p ->
  do let xs = varXS v
         ms = varMS v
         ys = varYS v
         t  = pointTime p
         s  = varChangedSource v
     count <- UV.vectorCount xs
     let i = count  1
     x <- UV.readVector xs i
     if t < x
       then error "Cannot update the past data: modifyVar."
       else if t == x
            then do a <- V.readVector ys i
                    let b = f a
                    V.writeVector ys i $! b
                    invokeEvent p $ triggerSignal s b
            else do a <- V.readVector ys i
                    let b = f a
                    UV.appendVector xs t
                    V.appendVector ms $! b
                    V.appendVector ys $! b
                    invokeEvent p $ triggerSignal s b
freezeVar :: MonadComp m => Var m a -> Event m (Array Int Double, Array Int a, Array Int a)
freezeVar v =
  Event $ \p ->
  do xs <- UV.freezeVector (varXS v)
     ms <- V.freezeVector (varMS v)
     ys <- V.freezeVector (varYS v)
     return (xs, ms, ys)
     
varChanged :: Var m a -> Signal m a
varChanged v = publishSignal (varChangedSource v)
varChanged_ :: MonadComp m => Var m a -> Signal m ()
varChanged_ v = mapSignal (const ()) $ varChanged v