{-# LANGUAGE OverloadedStrings, FlexibleContexts #-}

module Network.NineP.Internal.File
        ( NineFile(..)
        , boringFile
        , boringDir
        ) where

import Control.Exception
import Control.Monad.EmbedIO
import qualified Data.ByteString.Lazy.Char8 as B
import qualified Data.Map as M
import Data.Word

import Data.NineP
import Network.NineP.Error

data NineFile m =
        RegularFile {
                read :: Word64  -- Offset
                        -> Word32 -- Length
                        -> m (B.ByteString),    -- Resulting data
                write :: Word64 -- Offset
                        -> B.ByteString -- The written data
                        -> m (Word32),  -- Number of bytes written successfully. Should return @0@ in case of an error.
                remove :: m (),
                stat :: m Stat,
                wstat :: Stat -> m (),
                version :: m Word32
        } | Directory {
                -- |A callback to get the list of the files this directory contains. Must not contain @.@ and @..@ entries.
                getFiles :: m [NineFile m],
                parent :: m (Maybe (NineFile m)),
                -- |A callback to address a specific file by its name. @.@ and @..@ are handled in the library.
                descend :: String -> m (NineFile m),
                -- |Create a file under this directory.
                create :: String -> Word32 -> m (NineFile m),
                remove :: m (),
                -- |The directory stat must return only stat for @.@.
                stat :: m Stat,
                wstat :: Stat -> m (),
                version :: m Word32
        }

boringStat :: Stat
boringStat = Stat 0 0 (Qid 0 0 0) 0o0777 0 0 0 "boring" "root" "root" "root"

-- |A dumb file that can't do anything.
boringFile :: (Monad m, EmbedIO m) => String -> NineFile m
boringFile name = RegularFile
        (\_ _ -> return "")
        (\_ _ -> return 0)
        (return ())
        (return $ boringStat {st_name = name})
        (const $ return ())
        (return 0)

-- |A dumb directory that can't do anything but provide the files it contains. An user can create files, but they won't show up in listing and will essentially be 'boringFile's.
boringDir :: (Monad m, EmbedIO m) => String -> [(String, NineFile m)] -> NineFile m
boringDir name contents = let m = M.fromList contents in Directory {
        getFiles = (return $ map snd $ contents),
        descend = (\x -> case M.lookup x m of
                Nothing -> throw $ ENoFile x
                Just f -> return f),
        create = (\name perms -> return $ boringFile name),
        remove = (return ()),
        stat = (return $ boringStat {st_name = name}),
        wstat = (const $ return ()),
        version = (return 0),
        parent = return Nothing }