module WASH.CGI.CGIHistory where

import System.IO.Unsafe (unsafePerformIO)

import IO
import Maybe
import Monad
import Control.Concurrent
import Control.Concurrent.MVar
import qualified Data.Map as Map
-- 
import WASH.Utility.SHA1
import WASH.CGI.RawCGITypes
import WASH.CGI.CGIMonad
import WASH.CGI.LogEntry

-- | standard value for timeout of interaction threads: one hundred seconds
historyTimeout :: Int
historyTimeout = 100000000

-- | comprises the name of the application and a hash value.
type StateID = (String, String)

{-# NOINLINE history #-}
history :: MVar (Map.Map StateID TableEntry)
history = unsafePerformIO $ newMVar Map.empty

data TableEntry =
    TableEntry 
      { myID :: StateID		    		    -- ^ script name and hash key
      , parentID :: Maybe StateID		    -- ^ root has no parent
      , nextparm :: PARAMETER
      , timeout :: Int				    -- ^ timeout for this stage of interaction
      , threads :: MVar [MVar (CGIParameters, Handle)]-- ^ next round of parameters and output handle
      }

-- | Takes the id of the current node, the id of the father node (if any), the
-- current parameter, and (perhaps) a timeout value for the current interaction
-- and enters a corresponding record into the history table.
createEntry :: StateID -> Maybe StateID -> PARAMETER -> Maybe Int -> IO ()
createEntry myID parentID nextparm mTimeout = do
  -- putStrLn ("createEntry "++ show myID ++ " "++ show parentID ++ " " ++ show nextparm)
  threadsvar <- newMVar []
  let protoEntry = TableEntry { myID = myID
			      , parentID = parentID
			      , nextparm = nextparm
			      , timeout = fromMaybe historyTimeout mTimeout
			      , threads = threadsvar
			      }
  modifyMVar_ history (return . Map.insertWith (\ new old -> old) myID protoEntry)
  -- putStrLn ("createEntry finished")

-- | Suspends the current thread by waiting on an entry with the current node's
-- id. Returns the parameters passed to this node.
readParameters :: StateID -> IO (CGIParameters, Handle)
readParameters stateID = do
  -- putStrLn ("readParameters "++ show stateID)
  (myVar, myTimeout) <- modifyMVar history update
  myID <- myThreadId
  killerID <- forkIO (killmeOnTimeout myID myVar myTimeout)
  ch <- takeMVar myVar			    -- suspend until notify puts s.t.
  killThread killerID
  -- putStrLn ("readParameters "++ show stateID ++ " returns " ++ show ch)
  return ch
  where
    update t = do 
      let Just entry = Map.lookup stateID t
      myVar <- newEmptyMVar
      modifyMVar_ (threads entry) (\ ms -> return (myVar : ms))
      return (t, (myVar, timeout entry))
    remove v t = do
      let Just entry = Map.lookup stateID t
      modifyMVar (threads entry) (\ ms -> return (filter (/= v) ms, v `elem` ms))
    killmeOnTimeout tid var timeout = do
      threadDelay timeout
      wasPresent <- withMVar history (remove var)
      when wasPresent (killThread tid)
      -- putStrLn ("killmeOnTimeout: had to kill = "++show wasPresent)
      -- otherwise thread tid is on its way to kill me

-- | Attempts to pass parameters and a handle to a thread waiting for the given
-- stateID. Returns True if successful and False if no such thread was found.
notify :: StateID -> CGIParameters -> Handle -> IO Bool
notify stateID parms hout = do
  -- putStrLn ("notify "++ show stateID ++ " "++ show parms ++ " " ++ show hout)
  b <- withMVar history update
  -- putStrLn ("notify returns "++ show b)
  return b
  where
    update t = 
      case Map.lookup stateID t of
	Just entry ->
	  do maybeVar <- modifyMVar (threads entry) (return . uncons)
	     case maybeVar of
	       Just myVar -> do
		 putMVar myVar (parms, hout)
		 return True
	       Nothing ->
		 return False
	Nothing ->		   -- may happen after server restart
	  return False

uncons (x : xs) = (xs, Just x)
uncons []       = ([], Nothing)