-- © 2001, 2002, 2003 Peter Thiemann
{-|This is the preferred, type-indexed implementation of server-side state.

Glossary

A persistent entity (PE) is a time-indexed, named global value.
The current value of a PE is the value that the PE has now.
A handle gives access to a snapshot of a persistent entity at a particular time, potentially in the past.
A handle is /current/ if the value of the PE has not been @set@ or @add@ed to after the creation of the handle.
-}

module WASH.CGI.Persistent2 (T, init, get, set, add, current) where

import WASH.CGI.CGIConfig

import System
import Prelude hiding (init)
import qualified Prelude (init)
import List hiding (init)
import qualified List (init)
import Maybe
import IO
import Directory
import Monad
import Random

import WASH.Utility.Auxiliary
import WASH.CGI.CGI hiding (head, div, span, map)
import WASH.CGI.BaseCombinators (unsafe_io)
import WASH.CGI.Types
import qualified WASH.Utility.Locking as L

import WASH.CGI.StateItem
import WASH.CGI.MakeTypedName


-- |@init name initialValue@ creates a new PE with name @name@ with initial
-- value @initialValue@ and returns the handle to the initial value. If the PE
-- already exists, then @init@ returns the handle to the current value.
init    :: (Read a, Show a, Types a) => String -> a -> CGI (T a)
-- |@get handle@ retrieves the value of @handle@. This value may not be current
-- because the handle may point to a snapshot from the past.
get     :: (Read a, Types a) => T a -> CGI a
-- |@set handle newValue@ tries to overwrite the value of the pe pointed to by
-- @handle@ with @newValue@. Succeeds @Just handle1@ if @handle@ is current, in
-- this case it returns a handle to the new value. Fails @Nothing@ if the handle
-- is not current. 
set     :: (Read a, Show a, Types a) => T a -> a -> CGI (Maybe (T a))
-- |@add handle addValue@ conceptually, this operation adds @addValue@ to the
-- set of values stored in @handle@. Since this set is represented as a list,
-- @handle@ must point to a value of type @[a]@. Since the PE is assumed to
-- contain a set, it does not matter if @handle@ is current. However, the
-- returned handle is guaranteed to be current with a value that contains
-- @addValue@.
add     :: (Read a, Show a, Types a) => T [a] -> a -> CGI (T [a])
-- |@current handle@ returns a handle to the PE pointed to by
-- @handle@. It returns @Nothing@ if @handle@ is still /current/. Otherwise, it
-- returns @Just newHandle@ where @newHandle@ is /current/ in the sense defined
-- above. Using the @newHandle@ obtained from @current@ explicitly discards the
-- value pointed to by @handle@ in favor of a value that may have been stored
-- by a concurrently executing script. Use with caution!
current :: (Read a, Types a) => T a -> CGI (Maybe (T a))
-- 


data P a = P { nr :: Int, vl :: a }
  deriving (Read, Show)

t :: String -> P a -> T a
t name (P i a) = T name i

traceInit =
  -- writeDebugFile (persistent2Dir ++ "TRACE") "" >>
  return ()
trace s = 
  -- appendFile (persistent2Dir ++ "TRACE") (s ++ "\n") >>
  return ()

init name val = do
  unsafe_io $
     assertDirectoryExists (List.init persistent2Dir) (return ())
  io $ catch (
     do traceInit
	trace ("P2: init " ++  name ++ " " ++ show val)
	trace ("P2: ty = " ++ show myTyspec)
	trace ("P2: tid ty = " ++ tid myTyspec "")
	trace ("P2: typedName = " ++ typedName)
	trace ("P2: fileName = " ++ show fileName)
	L.obtainLock fileName
	trace ("P2.init: after obtainLock ")
	contents <- readFileStrictly fileName
	trace ("P2.init: after readFileStrictly " ++ contents)
	L.releaseLock fileName
	trace ("P2.init: after releaseLock[1] " ++ contents)
	pairs <- return $ read contents
	return $ t name $ head $ pairs
     )
     $ \ ioError ->
     do trace ("P2.init: ioError caught")
	nonce <- randomIO
	let initialP = P nonce val
	writeFile fileName (show [initialP])
	trace ("P2.init: writing " ++ show [initialP])
	L.releaseLock fileName
	return (t name initialP)
  where
    fileName = persistent2Dir ++ typedName
    myTyspec = ty val
    typedName = makeTypedName name myTyspec

get tn@(T name i) =
  unsafe_io $
  do trace ("P2: get " ++ name ++ " " ++ show i)
     L.obtainLock fileName
     contents <- readFileStrictly fileName
     L.releaseLock fileName
     trace ("P2.get: fileName = " ++ show fileName)
     pairs <- return $ read contents
     return $ vl $ fromJust $ find ((== i) . nr) pairs
  where
    typedName = makeTypedNameFromVal name (tvirtual tn)
    fileName = persistent2Dir ++ typedName

set (T name i) val =
  io $
  do trace ("P2: set " ++ name ++ " " ++ show i ++ " " ++ show val)
     L.obtainLock fileName
     contents <- readFileStrictly fileName
     pairs <- return $ read contents
     i' <- randomIO
     if nr (head pairs) == i
       then do writeFile fileName (show (P i' val : take 15 pairs))
	       L.releaseLock fileName
	       return (Just (T name i'))
       else do L.releaseLock fileName
	       return Nothing
  where
    typedName = makeTypedNameFromVal name val
    fileName = persistent2Dir ++ typedName

add (T name _) val =
  io $
  do trace ("P2: add " ++ name ++ " " ++ show val)
     L.obtainLock fileName
     contents <- readFileStrictly fileName
     pairs <- return $ read contents
     let P i vals = head pairs
     i' <- randomIO
     writeFile fileName (show (P i' (val : vals) : take 15 pairs))
     L.releaseLock fileName
     return (T name i')
  where
    typedName = makeTypedNameFromVal name [val]
    fileName = persistent2Dir ++ typedName

current tn@(T name i) =
  io $
  do trace ("P2: current " ++ name ++ " " ++ show i)
     L.obtainLock fileName
     contents <- readFileStrictly fileName
     L.releaseLock fileName
     pairs <- return $ read contents
     let P i' vals = head pairs
     if i==i'
       then return Nothing
       else return (Just (t name (head pairs)))
  where
    typedName = makeTypedNameFromVal name (tvirtual tn)
    fileName = persistent2Dir ++ typedName