-- |
-- Module     : Simulation.Aivika.Distributed.Optimistic.Internal.UndoableLog
-- Copyright  : Copyright (c) 2015-2017, David Sorokin <david.sorokin@gmail.com>
-- License    : BSD3
-- Maintainer : David Sorokin <david.sorokin@gmail.com>
-- Stability  : experimental
-- Tested with: GHC 7.10.3
--
-- This module defines an output message queue.
--
module Simulation.Aivika.Distributed.Optimistic.Internal.UndoableLog
       (UndoableLog,
        newUndoableLog,
        writeLog,
        rollbackLog,
        reduceLog,
        logSize) where

import Data.IORef

import Control.Monad
import Control.Monad.Trans

import qualified Simulation.Aivika.DoubleLinkedList as DLL
import Simulation.Aivika.Trans.Comp
import Simulation.Aivika.Trans.Internal.Types

import Simulation.Aivika.Distributed.Optimistic.Internal.Priority
import Simulation.Aivika.Distributed.Optimistic.Internal.DIO
import Simulation.Aivika.Distributed.Optimistic.Internal.IO

-- | Specified an undoable log with ability to rollback the operations.
data UndoableLog =
  UndoableLog { logItems :: DLL.DoubleLinkedList UndoableItem
                -- ^ The items that can be undone.
              }

data UndoableItem =
  UndoableItem { itemTime :: Double,
                 -- ^ The time at which the operation had occured.
                 itemUndo :: DIO ()
                 -- ^ Undo the operation
               }

-- | Create an undoable log.
newUndoableLog :: DIO UndoableLog
newUndoableLog =
  do xs <- liftIOUnsafe DLL.newList
     return UndoableLog { logItems = xs }

-- | Write a new undoable operation.
writeLog :: UndoableLog -> DIO () -> Event DIO ()
{-# INLINE writeLog #-}
writeLog log h =
  Event $ \p ->
  do let x = UndoableItem { itemTime = pointTime p, itemUndo = h }
     ---
     --- logDIO DEBUG $ "Writing the log at t = " ++ show (itemTime x)
     ---
     liftIOUnsafe $
       do f <- DLL.listNull (logItems log)
          if f
            then DLL.listAddLast (logItems log) x
            else do x0 <- DLL.listLast (logItems log)
                    when (itemTime x < itemTime x0) $
                      error $
                      "The logging data are not sorted by time (" ++
                      (show $ itemTime x) ++ " < " ++
                      (show $ itemTime x0) ++ "): writeLog"
                    DLL.listAddLast (logItems log) x

-- | Rollback the log till the specified time either including that one or not.
rollbackLog :: UndoableLog -> Double -> Bool -> DIO ()
rollbackLog log t including =
  do ---
     --- logDIO DEBUG $ "Rolling the log back to t = " ++ show t
     ---
     loop
       where
         loop =
           do f <- liftIOUnsafe $ DLL.listNull (logItems log)
              unless f $
                do x <- liftIOUnsafe $ DLL.listLast (logItems log)
                   when ((t < itemTime x) || (including && t == itemTime x)) $
                     do liftIOUnsafe $ DLL.listRemoveLast (logItems log)
                        itemUndo x
                        loop

-- | Reduce the log removing all items older than the specified time.
reduceLog :: UndoableLog -> Double -> IO ()
reduceLog log t =
  do f <- DLL.listNull (logItems log)
     unless f $
       do x <- DLL.listFirst (logItems log)
          when (itemTime x < t) $
            do DLL.listRemoveFirst (logItems log)
               reduceLog log t

-- | Return the log size.
logSize :: UndoableLog -> IO Int
logSize log = DLL.listCount (logItems log)