{-# LANGUAGE TypeSynonymInstances, MultiParamTypeClasses, RankNTypes, FlexibleContexts #-}

-- | Convenience module for exporting usually useful stuff
-- ALL "unsafe*" functions must be deleted and turned into "primitives"
-- in this module.

module Halfs.FSState (StateHandle(..) -- FIX: might get rid of FSState or StateHandle.
                      -- writing stuff:
                     ,FSWrite, unsafeLiftIOWrite, runFSWrite
                     ,evalFSWriteIO, evalFSWriteIOMV
                     ,runFSWriteIO, readToWrite, readToWriteCont, writeToBuffer
                     ,unsafeLiftIOWriteWithRoot, unsafeWriteGet, putStrLnWriteRead
                     ,updateInodeCacheWrite
                     ,modifyFSWrite
                      -- reading stuff:
                     ,FSRead, unsafeLiftIORead, runFSRead
                     ,runFSReadIO, evalFSReadIOMV, unsafeReadGet
                     ,unsafeModifyFSRead
                     ,unsafeWriteToRead
                      -- general monad capabilities. move elsewhere?
                     ,newReadRef, readReadRef, modifyReadRef, writeReadRef
                     ,takeMVarRW, readMVarRW, putMVarRW, modifyMVarRW_
                     ,modifyMVarRW_'
                      -- other stuff:
                     ,module Control.Monad.Trans
                     ,module Control.Monad.Error
                     ,module Control.Monad.State
                      -- read-write
                     ,FSRW, unsafeLiftIORW
                      -- FIX: don't export put & get:
                     ,Control.Monad.State.put
                     ,Control.Monad.State.get
                     ,alreadyExistsEx
                     ,doesNotExistEx
                     ,eofEx
                     ,illegalOpEx
                     ,decLinksForInode
                     ,forkWithMVar
                     ,forkFSWrite
               ) where

import Binary (Binary, BinHandle, put_)
import Halfs.FSRoot (FSRoot(..), FSStatus(..)
                    ,fsRootUpdateInodeCache)
import Halfs.FSRW
import Halfs.Inode(Inode(..), InodeMetadata(..))
-- import System.RawDevice(RawDevice)

-- FIX: get rid of FSState data type; we only need it because it works
-- on the StateT monad.  We can rool it ourselves and get rid of
-- FSState, probably.

-- base

import Control.Concurrent(MVar, ThreadId, modifyMVar_, tryPutMVar, modifyMVar,
                          putMVar, newMVar, readMVar, forkIO, takeMVar,
                          withMVar)
import Control.Exception(assert)
import Control.Monad.Error(throwError, catchError, MonadError)
import Control.Monad.State(StateT(..), modify,
                           evalStateT, execStateT,
                           MonadState)
import qualified Control.Monad.State (get, put)
import Control.Monad.Trans(liftIO)
import Data.IORef (IORef, newIORef, readIORef, modifyIORef, writeIORef)
import System.IO.Error (alreadyExistsErrorType, doesNotExistErrorType,
                        illegalOperationErrorType, eofErrorType, mkIOError)

-- | Read-write state
data StateHandle
    = StateHandle { {- any function needing access to the FSRoot
                       needs to get this mvar: -}
                    stateFineMVar  :: MVar FSRoot

                    {- clients can use this MVar to block by calling
                       the evalFS{Read,Write}IOMV functions: -}
                  , stateCoarseMVar :: Maybe (MVar ())
                  } deriving Eq
type FSState a = StateT StateHandle IO a

alreadyExistsEx :: FilePath -> IOError
alreadyExistsEx path
    = mkIOError alreadyExistsErrorType "already exists" Nothing (Just path)

doesNotExistEx :: FilePath -> IOError
doesNotExistEx path
    = mkIOError doesNotExistErrorType "does not exist" Nothing (Just path)

illegalOpEx :: FilePath -> IOError
illegalOpEx path
    = mkIOError illegalOperationErrorType "illegal operation" Nothing (Just path)

eofEx :: FilePath -> IOError
eofEx path = mkIOError eofErrorType "EOF" Nothing (Just path)

-- ------------------------------------------------------------
-- * FSState Read Only
-- ------------------------------------------------------------

newtype FSRead a = FSR (FSState a)
instance Monad FSRead where
  return a = FSR (return a)
  (FSR m) >>= k  = FSR $ do a <- m
                            runFSRead $ k a

-- | Make this read-only monad into a read-write monad.
runFSRead :: FSRead a -> FSState a
runFSRead (FSR f) = f

-- | Returns the FSRoot since things like Inode cache may change.
runFSReadIO' :: FSRead a
             -> FSRoot
             -> IO (a, FSRoot)
runFSReadIO' (FSR f) s =
  do mv <- newMVar s
  -- same mvar, so ignore it on output:
     (r,_) <- runStateT f (StateHandle mv Nothing)
     fsroot <- readMVar mv
     return (r, fsroot)

runFSReadIO :: FSRead a
            -> FSRoot
            -> (IOError -> IO (a, FSRoot)) -- exception handler
            -> IO (a, FSRoot)
runFSReadIO f s handler = catch (runFSReadIO' f s) handler

-- |
evalFSReadIOMV :: FSRead a -> StateHandle -> IO a
evalFSReadIOMV (FSR f) mv@(StateHandle _ (Just blockOn))
    = do (r,_) <- withMVar blockOn $ \ _ -> runStateT f mv
         return r
evalFSReadIOMV (FSR f) mv@(StateHandle _ Nothing)
    = do (r,_) <- runStateT f mv
         return r

-- | Unsafe because it uses readMVar.
unsafeReadGet :: FSRead FSRoot
unsafeReadGet = FSR $ do (StateHandle mv _) <- Control.Monad.State.get
                         liftIO $ readMVar mv

-- | FIX: Unsafe?
unsafeModifyFSRead :: (FSRoot -> (FSRoot, a)) -> FSRead a
unsafeModifyFSRead f = FSR $ do
  (StateHandle mv _) <- Control.Monad.State.get
  retVal <- liftIO $ modifyMVar mv (\fsroot -> return (f fsroot))
  return retVal

-- | FIX: Delete this function; it's temporary during refactoring.  It
-- violates safety requirements.
unsafeLiftIORead :: IO a -> FSRead a
unsafeLiftIORead action = FSR $ liftIO action

-- | unsafe since this allows a reader to write.
unsafeWriteToRead :: FSWrite a -> FSRead a
unsafeWriteToRead (FSW a) = FSR a

-- These are mostly for handles.  The types aren't what we would like
-- since we can't import FileHandle.

-- This synonym is for documentation purposes:
type ReadRef a = IORef a

newReadRef :: a -> FSRead (ReadRef a)
newReadRef = unsafeLiftIORead . newIORef

readReadRef :: (ReadRef a) -> FSRead a
readReadRef = unsafeLiftIORead . readIORef

modifyReadRef :: (ReadRef a) -> (a -> a) -> FSRead ()
modifyReadRef f h = unsafeLiftIORead $ modifyIORef f h

writeReadRef :: (ReadRef a) -> a -> FSRead ()
writeReadRef f h = unsafeLiftIORead $ writeIORef f h

takeMVarRW :: (FSRW m) => MVar a -> m a
takeMVarRW = unsafeLiftIORW . takeMVar

putMVarRW :: (FSRW m) => MVar a -> a -> m ()
putMVarRW m a = unsafeLiftIORW (putMVar m a)

readMVarRW :: (FSRW m) => MVar a -> m a
readMVarRW = unsafeLiftIORW . readMVar

modifyMVarRW_ :: (FSRW m) => MVar a -> (a -> IO a) -> m ()
modifyMVarRW_ m f = unsafeLiftIORW (modifyMVar_ m f)

modifyMVarRW_' :: (FSRW m, MonadError IOError m) => MVar a -> (a -> m a) -> m ()
modifyMVarRW_' m f = do
  s <- takeMVarRW m
  catchError (f s >>= putMVarRW m)
             (\e -> putMVarRW m s >> unsafeLiftIORW (throwError e))

instance MonadError IOError FSRead where
    throwError = FSR . throwError
    catchError (FSR m) f
        = FSR $ catchError m (\e -> runFSRead $ f e)

-- ------------------------------------------------------------
-- * FSState Write Only
-- ------------------------------------------------------------

newtype FSWrite a = FSW (FSState a)
instance Monad FSWrite where
  return a = FSW (return a)
  (FSW m) >>= k  = FSW $ do a <- m
                            runFSWrite $ k a

-- | Make this write-only monad into a read-write monad.
runFSWrite :: FSWrite a -> FSState a
runFSWrite (FSW f) = f

-- Forks a new thread which uses the mvar from this state.  Whew.  Sorta doesn't make sense to be in FSWrite, maybe should just be in IO; we have two versions of the state available here, and that's no good... one is really just for syncronizeation.

forkWithMVar :: (StateHandle -> FSWrite ()) -> FSWrite ThreadId
forkWithMVar f = do
  mv <- FSW Control.Monad.State.get
  forkFSWrite (f mv)

forkFSWrite :: FSWrite () -> FSWrite ThreadId
forkFSWrite (FSW f) =
  FSW (do mv <- Control.Monad.State.get
          -- safe to ignore output since its held in the mvar
          liftIO $ forkIO $ (runStateT f mv >> return ()))

runFSWriteIO :: FSWrite a -> FSRoot -> IO (a, FSRoot)
runFSWriteIO (FSW f) s = do mv <- newMVar s
                            -- same mvar, so ignore it on output:
                            (r,_) <- runStateT f (StateHandle mv Nothing)
                            fsroot <- readMVar mv
                            return (r, fsroot)

evalFSWriteIOMV :: FSWrite a -> StateHandle -> IO a
evalFSWriteIOMV (FSW f) mv@(StateHandle _ (Just blockOn))
    = do (r,_) <- withMVar blockOn $ \ _ -> runStateT f mv
         return r
evalFSWriteIOMV (FSW f) mv@(StateHandle _ Nothing)
    = do (r,_) <- runStateT f mv
         return r

-- |
evalFSWriteIO :: FSWrite a -> FSRoot -> IO a
evalFSWriteIO (FSW f) s = do
  mv <- newMVar s
  evalStateT f (StateHandle mv Nothing)

putStrLnWriteRead :: String -> FSWrite ()
putStrLnWriteRead = unsafeLiftIOWrite . putStrLn

-- | FIX: Delete this function; it's temporary during refactoring.  It
-- violates safety requirements.
unsafeLiftIOWrite :: IO a -> FSWrite a
unsafeLiftIOWrite action = FSW $ liftIO action

unsafeLiftIOWriteWithRoot :: (FSRoot -> IO a) -> FSWrite a
unsafeLiftIOWriteWithRoot action = FSW $ do (StateHandle mv _) <- Control.Monad.State.get
                                            fsroot <- liftIO $ readMVar mv
                                            liftIO (action fsroot)

{- UNUSED:
-- | Blocks on the MVar while performing this action.
unsafeLiftIOWriteModifyRoot :: (FSRoot -> IO FSRoot) -> FSWrite ()
unsafeLiftIOWriteModifyRoot action
    = FSW $ do (StateHandle mv v)
                   <- Control.Monad.State.get
               _newRoot <- liftIO $ withMVar mv action
               -- unnecessary:
               Control.Monad.State.put (StateHandle mv v)
-}

writeToBuffer :: (Binary a) => BinHandle -> a -> FSWrite ()
writeToBuffer bh a = FSW $ liftIO (Binary.put_ bh a)

instance MonadError IOError FSWrite where
    throwError = FSW . throwError
    catchError (FSW m) f
        = FSW $ catchError m (\e -> runFSWrite $ f e)

instance MonadState FSRoot FSWrite where
    get = FSW (do (StateHandle mv _) <- Control.Monad.State.get
--                  liftIO $ readMVar mv)
--                  liftIO $ putStrLn "monadState get!"
                  fsroot <- liftIO $ takeMVar mv
                  return $ assert (fsStatus fsroot /= FsUnmounted) fsroot
{-
                  maybeRoot <- liftIO $ tryTakeMVar mv
                  case maybeRoot of
                   -- FIX: remove below when using multi-threads:
                   Nothing -> liftIO $ putStrLn "DEADLOCK GET" >> error "deadlock get"
                   Just a  -> return a
-}
                   )
    put s = FSW (do (StateHandle mv _) <- Control.Monad.State.get
                    maybeFoo <- liftIO $ tryPutMVar mv s
                    case maybeFoo of
                     True -> return ()
                     False -> liftIO $ putStrLn "DEADLOCK PUT" >> error "deadlock put"                )

-- | Unsafe since it uses readMVar.  For get-only situations, for
-- reading and not writing; no mvar block.
unsafeWriteGet :: FSWrite FSRoot
unsafeWriteGet = FSW $ do (StateHandle mv _) <- Control.Monad.State.get
                          liftIO $ readMVar mv

-- | FIX: This 'f' should probably be in the FSRW monad, since that doesn't have state.
modifyFSWrite :: (FSRoot -> FSWrite (FSRoot, a)) -> FSWrite a
modifyFSWrite f = do
  fsroot <- Control.Monad.State.get
  (newRoot, retVal) <- f fsroot
  Control.Monad.State.put newRoot
  return retVal

-- | Safe since writers can be readers.
readToWrite :: FSRead a -> FSWrite a
readToWrite (FSR a) = FSW a

-- | Safe since writers can be readers.
readToWriteCont :: ((forall s . m s -> FSRead a) -> FSRead a)
                -> (forall s . m s -> FSWrite a) -> FSWrite a
readToWriteCont f cont =
  case f (w2r cont) of
     FSR a -> FSW a
  where
   w2r :: (forall s . m s -> FSWrite a) -> (forall s . m s -> FSRead a)
   w2r g s = case g s of
               FSW a -> FSR a



updateInodeCacheWrite :: Inode -> FSWrite ()
updateInodeCacheWrite inode
    = modify (\f -> fsRootUpdateInodeCache f inode)

decLinksForInode :: Inode -> FSWrite ()
decLinksForInode inode@Inode{metaData=md@InodeMetadata{hard_links=hl}}
    = updateInodeCacheWrite inode{metaData=md{hard_links=hl-1}}

-- ------------------------------------------------------------
-- * FSReadWrite
-- ------------------------------------------------------------

-- FSRW functions don't carry state unless it's passed explicitly as
-- input and output.
instance FSRW FSRead where
    unsafeLiftIORW = unsafeLiftIORead

instance FSRW FSWrite where
    unsafeLiftIORW = unsafeLiftIOWrite