-- | These are the objects of the TPFS system: namely, files and
-- tags. This module provides the low-level functions for managing
-- file/tag info and their respective tables, as well as somewhat
-- higher-level functions for more sane access.
module System.TPFS.Objects (
    -- * File objects
    -- ** The file table
    FileID,
    isFileIDInUse,
    lookupByFileID,
    addToFileTable,
    removeFromFileTable,
    -- ** File information blocks
    FileInfo(..),
    getFileInfo,
    putFileInfo,
    createFileInfo,
    removeFileInfo,
    -- ** High-level access
    -- * Tag objects
    -- ** The tag table
    TagHash,
    lookupByTagHash,
    addToTagTable,
    removeFromTagTable,
    -- ** Tag information blocks
    TagInfo(..),
    getTagInfo,
    putTagInfo,
    createTagInfo,
    removeTagInfo
    -- ** High-level access
  ) where

import qualified Data.ByteString.Lazy as B
import qualified Data.ByteString as BS
import           Data.Word
import           System.Random
import           System.TPFS.Device
import           System.TPFS.Filesystem

-- | File identification numbers are 128 bits long and are usually
-- randomly generated. As such, there is a 'Random' instance for
-- 'FileID's. Be careful when using this, however. It is possible
-- (though unlikely) for a collision to occur, so it is strongly
-- recommended to use 'isFileIDInUse' to ensure the 'FileID' is not
-- already in use when generating a 'FileID' in this manner.
data FileID = FileID !Word64 !Word64
            deriving Eq

instance Random FileID where
  randomR  = undefined
  random g = (FileID a b, g'') where (a, g' ) = random g
                                     (b, g'') = random g'

-- | Searches the file table to check if the given 'FileID' is
-- currently in use. This is useful when generating random 'FileID's.
isFileIDInUse :: Device m h
              => Filesystem m h
              -> FileID         -- ^ The 'FileID' to check.
              -> m Bool         -- ^ 'True' if the 'FileID' is
                                -- currently in use; 'False' if not.

isFileIDInUse = undefined

-- | Searches the file table for a given 'FileID' and returns the
-- address at which its 'FileInfo' can be found.
lookupByFileID :: Device m h
               => Filesystem m h
               -> FileID            -- ^ The ID of the file to look up.
               -> m (Maybe Address) -- ^ 'Nothing' if the 'FileID' does not exist;
                                    -- @'Just' 'Address'@ otherwise.

lookupByFileID = undefined

-- | Links a file information record to the file table so that it may
-- be discovered.
-- 
-- Note that a file information record (represented by 'FileInfo')
-- /must/ be linked to the file table in order for it to be found.
addToFileTable :: Device m h
               => Filesystem m h
               -> (FileID, Address) -- ^ The ID of the file in
                                    -- question, and the address of
                                    -- the first block of its record.
               -> m ()

addToFileTable = undefined

-- | Unlinks a file information table from the file table. This
-- prevents the file from being discovered.
-- 
-- It is possible for a file to be \'lost\' if it is not linked, so
-- take care when using this function manually.
removeFromFileTable :: Device m h
                    => Filesystem m h
                    -> FileID         -- ^ The file to be unlinked.
                    -> m ()

removeFromFileTable = undefined

-- | The 'FileInfo' structure attaches the blocks of a file to its
-- identification ('FileID' and tags) and other information vital for
-- reading the file (offset, length, lock).
data FileInfo = FileInfo { fileID     :: FileID    -- ^ The identification number of the file.
                         , firstBlock :: Address   -- ^ The first block in the file.
                         , lastBlock  :: Address   -- ^ The last block in the file. Makes appending faster.
                         , fileOffset :: Word64    -- ^ Describes the offset of the content within the blocks'
                                                   -- data. This could potentially allow for quick prepending of
                                                   -- data to the file.
                         , fileLength :: Word64    -- ^ The apparent (not necessarily actual block-wise) byte
                                                   -- length of the file.
                         , isLocked   :: Bool      -- ^ Whether the file is currently locked for writing. If the
                                                   -- file is locked for writing, the implementation must raise
                                                   -- an error when attempting to open the file for writing.
                         , tagHashes  :: [TagHash] -- ^ The hashes of each of the tags the file is attached to.
                         }

-- | Reads the file information from a file information record.
getFileInfo :: Device m h
            => Filesystem m h
            -> Address        -- ^ The first block of the file information record.
            -> m FileInfo

getFileInfo = undefined

-- | Modifies the file information in a file information record. Does
-- not affect tag structures linked via 'tagHashes'.
putFileInfo :: Device m h
            => Filesystem m h
            -> Address        -- ^ The first block of the file information record to be modified.
            -> FileInfo       -- ^ The file information to place in the record.
            -> m ()

putFileInfo = undefined

-- | Creates a new file information record. Won't link the file to the
-- file table, nor affect tag structures linked via 'tagHashes'.
createFileInfo :: Device m h
               => Filesystem m h
               -> FileInfo       -- ^ The initial contents of the file information record.
               -> m Address      -- ^ A pointer to the first block of the newly created record.

createFileInfo = undefined

-- | Frees the blocks used by a file information record. Does not free
-- blocks used by the file itself.
-- 
-- Note: 'removeFileInfo' won't automatically unlink the file from the
-- file table nor any tags linked to it. These operations should both
-- be done before calling 'removeFileInfo'.
removeFileInfo :: Device m h
               => Filesystem m h
               -> Address        -- ^ The address of the first block
                                 -- in the file information record to
                                 -- be removed.
               -> m ()

removeFileInfo = undefined

-- | Identifies tags with a SHA-256 hash of their data.
data TagHash = TagHash !Word64 !Word64 !Word64 !Word64
             deriving Eq

-- | Looks for a tag information record by its hash.
lookupByTagHash :: Device m h
                => Filesystem m h
                -> TagHash           -- ^ The hash of the tag to look up.
                -> m (Maybe Address) -- ^ 'Just' the address of the first block of the tag
                                     -- information record if found, or 'Nothing' if not found.

lookupByTagHash = undefined

-- | Links a tag information record to the tag table.
addToTagTable :: Device m h
              => Filesystem m h
              -> (TagHash, Address) -- ^ The hash of the tag in question, and the
                                    -- address of its information record.
              -> m ()

addToTagTable = undefined

-- | Removes a tag information record from the tag table.
-- 
-- Note: There is really no good reason to do this, as tags are
-- usually managed automatically.
removeFromTagTable :: Device m h
                   => Filesystem m h
                   -> TagHash
                   -> m ()

removeFromTagTable = undefined

-- | Describes a tag information record.
data TagInfo = TagInfo { tagHash :: TagHash       -- ^ The hash of the 'tagData'.
                       , tagData :: BS.ByteString -- ^ The data (contents) of the tag. The length/contents
                                                  -- of a tag's data are not restricted. Note: This is a strict
                                                  -- ByteString, unlike the lazy ByteStrings that are often
                                                  -- used throughout this package.
                       , fileIDs :: [FileID]      -- ^ The IDs of the files attached to the tag.
                       }

-- | Reads a 'TagInfo' structure from a tag information record on
-- disk.
getTagInfo :: Device m h
           => Filesystem m h
           -> Address        -- ^ The first block of the tag information record.
           -> m TagInfo

getTagInfo = undefined

-- | Writes a 'TagInfo' structure to a tag information record on disk.
putTagInfo :: Device m h
           => Filesystem m h
           -> Address        -- ^ The first block of the tag information record to be modified.
           -> TagInfo        -- ^ The tag information to place in the record.
           -> m ()

putTagInfo = undefined

-- | Creates a new tag information record. Won't link the tag to the
-- tag table, nor affect files linked via 'fileIDs'.
-- 
-- Note: A tag information record is fairly useless if not linked.
createTagInfo :: Device m h
              => Filesystem m h
              -> TagInfo        -- ^ The initial contents of the tag information record.
              -> m Address      -- ^ A pointer to the first block of the newly created record.

createTagInfo = undefined

-- | Frees the blocks used by a tag information record. This function
-- is mostly for internal use, as tag information records are usually
-- managed automatically.
-- 
-- Note: 'removeTagInfo' won't automatically unlink the tag from the
-- tag table nor any files linked to it. These operations should both
-- be done before calling 'removeTagInfo'.
removeTagInfo :: Device m h
              => Filesystem m h
              -> Address        -- ^ The address of the first block in
                                -- the tag information record to be
                                -- removed.
              -> m ()

removeTagInfo = undefined