{-# LANGUAGE ScopedTypeVariables #-}
-- | Replacement of Yampa's reactimate function with recod-and-replay
-- capabilities.
module FRP.Titan.Record.Yampa
    ( reactimateRecord
    , RecordMode(..)
    )
  where

import Control.Exception
import Control.Monad
import Data.IORef
import Data.Maybe
import FRP.Yampa
import System.IO

-- | How to treat the given trace: read (replay) but not write to it,
--   write (record) but not replay it, and readwrite (replay until the end
--   and the continue recording to it).
data RecordMode = RecordReadOnly
                | RecordWriteOnly
                | RecordReadWrite
  deriving (Eq, Show)

recordMustWrite RecordReadOnly = False
recordMustWrite _              = True

recordMustRead RecordWriteOnly = False
recordMustRead _               = True

reactimateRecord :: (Read a, Show a)
                 => Maybe (FilePath, RecordMode)    -- ^ Debug: File onto which the result should be recorded and recording mode
                 -> IO a                            -- ^ FRP: Initial sensing action
                 -> (Bool -> IO (DTime, Maybe a))   -- ^ FRP: Continued sensing action
                 -> (Bool -> b -> IO Bool)          -- ^ FRP: Rendering/consumption action
                 -> SF a b                          -- ^ FRP: Signal Function that defines the program
                 -> IO ()
reactimateRecord Nothing sense0 sense actuate sf = reactimate sense0 sense actuate sf
reactimateRecord (Just (fp, mode)) sense0 sense actuate sf = do
  samples   <- (maybeRead =<<) <$> (catch (Just <$> readFile fp) (\(e :: IOException) -> return Nothing))
  hPutStrLn stderr (show samples)

  -- Read from here
  sample0Ref <- newIORef (fmap fst samples)
  samplesRef <- newIORef (let ss = fromMaybe [] $ fmap snd samples in length ss `seq` ss)

  -- Write into here
  newSample0Ref <- newIORef Nothing
  newSamplesRef <- newIORef []

  let newSense0 = do
        -- Sense from IO
        a  <- sense0

        -- Choose which one to use
        a' <- if recordMustRead mode
                then do sample0' <- readIORef sample0Ref
                        return (fromMaybe a sample0')
                else return a

        -- Update record, if necessary
        when (recordMustWrite mode) $ writeIORef newSample0Ref (Just a')
        return a'

  let newSense b = do
        as  <- sense b

        as' <- if recordMustRead mode
                then do sample <- readIORef samplesRef
                        case sample of
                          []     -> return as
                          (x:xs) -> do writeIORef samplesRef xs
                                       return x
                else return as

        when (recordMustWrite mode) $ do
          curSamples <- readIORef newSamplesRef
          writeIORef newSamplesRef (curSamples ++ [as'])

        return as'

  let newActuate x y = do
        last <- actuate x y
        when (recordMustWrite mode) $ do
          curSample0 <- readIORef newSample0Ref
          curSamples <- readIORef newSamplesRef
          case curSample0 of
            Nothing -> return ()
            Just s0 -> length curSamples `seq` writeFile fp (show (s0, curSamples))
        return last

  (maybe 0 (length.snd) samples) `seq`
    reactimate newSense0 newSense newActuate sf

maybeRead :: Read a => String -> Maybe a
maybeRead = fmap fst . listToMaybe . reads