{-
  $Id: conc050.hs,v 1.1 2005/03/21 13:59:36 simonmar Exp $

  Implements a simple directory service that handles
  insert and delete commands using STMs.
-}

module Main
where
import Control.Concurrent
import Control.Concurrent.STM
import System.Environment
import Control.Monad

type Key = Int

type Value = Int

type DirectoryEntry = (Key, Value)

type DirectoryEntryList = [DirectoryEntry]

-- The service handles add and remove commands
data DirectoryCommand = DirectoryAdd Key Value | DirectoryRemove Key

type DirectoryChannel = TChan DirectoryCommand

type DirectoryTable = TVar DirectoryEntryList

type DirectoryCommandCount = TVar Int

-- The service's state
data DirectoryState = DirectoryState {
  chan :: DirectoryChannel,
  table :: DirectoryTable,
  count :: DirectoryCommandCount }

{-
  Return True if a DirectoryEntry's key equals
  the specified key.
-}
keyEquals :: Key -> DirectoryEntry -> Bool
keyEquals k e = (fst e) == k

{-
  Return True if a DirectoryEntry's key does not equal
  the specified key.
-}
keyNotEquals :: Key -> DirectoryEntry -> Bool
keyNotEquals k e = (fst e) /= k

{-
  Print a DirectoryEntryList to stdout.
-}
dumpDirectoryEntryList :: DirectoryEntryList -> IO ()
dumpDirectoryEntryList [] = return ()
dumpDirectoryEntryList (x:xs)
 = do putStrLn ((show (fst x)) ++ " " ++ (show (snd x)))
      dumpDirectoryEntryList xs

{-
  Print a DirectoryTable to stdout.
-}
dumpDirectoryTable :: DirectoryTable -> IO ()
dumpDirectoryTable t
 = do l <- atomically (do {l <- readTVar t; writeTVar t l; return l})
      putStrLn ("table length = " ++ (show (length l)))
      -- dumpDirectoryEntryList l

{-
  Add a DirectoryEntry to a DirectoryTable verifying
  that the key does not already exist in the table.
-}
addDirectoryTable :: DirectoryTable -> DirectoryEntry -> IO ()
addDirectoryTable t e@(key,value)
 = do atomically (do l <- readTVar t
                     if filter (keyEquals key) l == []
                       then writeTVar t (e:l)
                       else writeTVar t l)
      -- putStrLn ("added (" ++ (show (fst e)) ++ "," ++ (show (snd e)) ++ ")")

{-
  Insert a DirectoryCommand into a DirectoryChannel.
-}
postCommand :: DirectoryChannel -> DirectoryCommand -> IO ()
postCommand c cmd = atomically (writeTChan c cmd)

{-
  Remove a DirectoryEntry from a DirectoryTable.
-}
removeDirectoryTable :: DirectoryTable -> Key -> IO ()
removeDirectoryTable t k
 = atomically (do l <- readTVar t
                  let newl = filter (keyNotEquals k) l
                  writeTVar t newl)
      -- putStrLn ("removed " ++ (show k))

{-
  Find a DirectoryEntry in a DirectoryTable.
-}
findDirectoryTable :: DirectoryTable -> Key -> IO DirectoryEntryList
findDirectoryTable t k
 = do l <- atomically (do l <- readTVar t
                          writeTVar t l
                          return l)
      let fl = filter (keyEquals k) l
      return fl

{-
  Increment the DirectoryCommandCount.
-}
incDirectoryCommandCount :: DirectoryCommandCount -> IO ()
incDirectoryCommandCount cnt
 = atomically (do i <- readTVar cnt; writeTVar cnt (i+1))

{-
  Read the DirectoryCommandCount.
-}
readDirectoryCommandCount :: DirectoryCommandCount -> IO Int
readDirectoryCommandCount cnt
 = do i <- atomically (do i <- readTVar cnt
                          writeTVar cnt i
                          return i)
      return i

{-
  Process that constantly searches the DirectoryTable
  for a DirectoryKey of 1 and prints whether it found it.
-}
directoryFinder :: DirectoryState -> TVar Bool -> IO ()
directoryFinder state done
 = do cc <- readDirectoryCommandCount cnt
      l <- findDirectoryTable t 1
{-
      if l /= [] then
         putStr "found"
       else
         putStr "not found"
      putStrLn (" " ++ (show cc))
-}

      b <- atomically (readTVar done)
      if b then return ()
           else directoryFinder state done
   where
     t = table state
     cnt = count state

{-
  Process that constantly prints the contents of
  the DirectoryTable.
-}
directoryDumper :: DirectoryState -> IO ()
directoryDumper state
 = do let t = table state
      dumpDirectoryTable t
      directoryDumper state

{-
  Process that reads commands from the DirectoryChannel
  and executes them.
-}
directoryListener :: Int -> DirectoryState -> TVar Bool -> IO ()
directoryListener 0 state done = atomically (writeTVar done True)
directoryListener n state done
 = do cmd <- atomically (do {cmd <- readTChan c; return cmd})
      case cmd of
        (DirectoryAdd k v) -> addDirectoryTable t (k,v)
        (DirectoryRemove k) -> removeDirectoryTable t k
      incDirectoryCommandCount cnt
      directoryListener (n-1) state done
   where
     c = chan state
     t = table state
     cnt = count state

{-
  Process that constantly posts DirectoryAdd
  commands to the DirectoryChannel.
-}
directoryPoster1 :: Int -> DirectoryState -> IO ()
directoryPoster1 0 state = return ()
directoryPoster1 n state
 = do let c = chan state
      postCommand c (DirectoryAdd 1 2)
      directoryPoster1 (n-1) state

{-
  Process that constantly posts DirectoryRemove
  commands to the DirectoryChannel.
-}
directoryPoster2 :: Int -> DirectoryState -> IO ()
directoryPoster2 0 state = return ()
directoryPoster2 n state
 = do let c = chan state
      postCommand c (DirectoryRemove 1)
      directoryPoster2 (n-1) state

{-
  The DirectoryService main process.
-}
directoryService
 = do args <- getArgs
      n <- case args of
        []  -> return 10000
        [s] -> return (read s :: Int)

      c <- atomically (newTChan)
      t <- atomically (newTVar [])
      cnt <- atomically (newTVar 0)
      let state = DirectoryState c t cnt

      done <- atomically (newTVar False)
      forkIO (directoryListener (n*2) state done)
      forkIO (directoryPoster1 n state)
      forkIO (directoryPoster2 n state)
      directoryFinder state done
      -- directoryDumper state

main
  = directoryService
