{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
module Bindings.HDF5.File
    ( AccFlags(..)
    , ObjType(..)
    , Scope(..)

    , isHDF5

    , File
    , createFile
    , openFile
    , reopenFile
    , flushFile
    , closeFile

    , mountFile
    , unmountFile

    , getFileSize
    , getFileCreatePlist
    , getFileAccessPlist

    , FileInfo(..)
    , getFileInfo
    , getFileIntent
    , getFileName
    , getFileObjCount

    , getOpenObjects
    , getFileFreespace
--    , get_mdc_config
--    , get_mdc_hit_rate
--    , get_mdc_size
--    , clear_elink_file_cache
--    , reset_mdc_hit_rate_stats
--    , set_mdc_config
    ) where

import Bindings.HDF5.Core
import Bindings.HDF5.Error
import Bindings.HDF5.Object
import Bindings.HDF5.PropertyList.FAPL
import Bindings.HDF5.PropertyList.FCPL
import Bindings.HDF5.PropertyList.FMPL
import Bindings.HDF5.Raw.H5
import Bindings.HDF5.Raw.H5F
import Bindings.HDF5.Raw.H5I
import Bindings.HDF5.Raw.H5P
import Data.Bits
import Data.Maybe
import qualified Data.ByteString as BS
import qualified Data.Vector.Storable as SV
import Foreign.C
import Foreign.Ptr
import Foreign.Ptr.Conventions
import Foreign.Storable

-- TODO: determine whether all of these are valid for _both_ create and open.
-- any that are not should be converted to Bool inputs to the corresponding function.
-- It very well may be best to separate several of them out like that anyway.
data AccFlags
    = ReadOnly
    | ReadWrite
    | Truncate
    | FailIfExists
    | Debug
    | Create
    deriving (Eq, Ord, Enum, Bounded, Read, Show)

accFlagToInt :: AccFlags -> CUInt
accFlagToInt ReadOnly       = h5f_ACC_RDONLY
accFlagToInt ReadWrite      = h5f_ACC_RDWR
accFlagToInt Truncate       = h5f_ACC_TRUNC
accFlagToInt FailIfExists   = h5f_ACC_EXCL
accFlagToInt Debug          = h5f_ACC_DEBUG
accFlagToInt Create         = h5f_ACC_CREAT

accFlagsToInt :: [AccFlags] -> CUInt
accFlagsToInt = foldl (.|.) 0 . map accFlagToInt

intToAccFlags :: CUInt -> [AccFlags]
intToAccFlags x =
    [ f
    | f <- [minBound .. maxBound]
    , accFlagToInt f .&. x /= 0
    ]

instance Storable [AccFlags] where
    sizeOf    _ = sizeOf (0 :: CUInt)
    alignment _ = alignment (0 :: CUInt)
    peek        = fmap intToAccFlags . peek . castPtr
    poke p      = poke (castPtr p) . accFlagsToInt

data ObjType
    = Files
    | Datasets
    | Groups
    | Datatypes
    | Attrs
    | All
    deriving (Eq, Ord, Enum, Bounded, Read, Show)

objTypeToInt :: ObjType -> CUInt
objTypeToInt Files      = h5f_OBJ_FILE
objTypeToInt Datasets   = h5f_OBJ_DATASET
objTypeToInt Groups     = h5f_OBJ_GROUP
objTypeToInt Datatypes  = h5f_OBJ_DATATYPE
objTypeToInt Attrs      = h5f_OBJ_ATTR
objTypeToInt All        = h5f_OBJ_ALL

objTypesToInt :: [ObjType] -> CUInt
objTypesToInt = foldl (.|.) 0 . map objTypeToInt

intToObjTypes :: CUInt -> [ObjType]
intToObjTypes x =
    [ f
    | f <- [minBound .. maxBound]
    , objTypeToInt f .&. x /= 0
    ]

instance Storable [ObjType] where
    sizeOf    _ = sizeOf (0 :: CUInt)
    alignment _ = alignment (0 :: CUInt)
    peek        = fmap intToObjTypes . peek . castPtr
    poke p      = poke (castPtr p) . objTypesToInt

data Scope
    = Local
    | Global
    deriving (Eq, Ord, Enum, Bounded, Read, Show)

scopeCode :: Scope -> H5F_scope_t
scopeCode Local  = h5f_SCOPE_LOCAL
scopeCode Global = h5f_SCOPE_GLOBAL

data CloseDegree
    = Weak
    | Semi
    | Strong
    deriving (Eq, Ord, Enum, Bounded, Read, Show)

rawCloseDegreesInv :: [(H5F_close_degree_t, Maybe CloseDegree)]
rawCloseDegreesInv = [(a,b) | (b,a) <- rawCloseDegrees]
rawCloseDegrees :: [(Maybe CloseDegree, H5F_close_degree_t)]
rawCloseDegrees =
    [ (Nothing,     h5f_CLOSE_DEFAULT)
    , (Just Weak,   h5f_CLOSE_WEAK)
    , (Just Semi,   h5f_CLOSE_SEMI)
    , (Just Strong, h5f_CLOSE_STRONG)
    ]

closeDegreeFromCode :: H5F_close_degree_t -> Maybe CloseDegree
closeDegreeFromCode c = fromMaybe Nothing (lookup c rawCloseDegreesInv)

closeDegreeToCode :: Maybe CloseDegree -> H5F_close_degree_t
closeDegreeToCode c =
    fromMaybe (error ("closeDegreeToCode: unrecognized H5F_close_degree_t: " ++ show c))
                  (lookup c rawCloseDegrees)

instance Storable (Maybe CloseDegree) where
    sizeOf _    = sizeOf    (undefined :: H5F_close_degree_t)
    alignment _ = alignment (undefined :: H5F_close_degree_t)
    peek        = fmap closeDegreeFromCode . peek . castPtr
    poke p      = poke (castPtr p) . closeDegreeToCode

isHDF5 :: BS.ByteString -> IO Bool
isHDF5 filename = htriToBool (BS.useAsCString filename h5f_is_hdf5)

newtype File = File HId_t
    deriving (Eq, HId, FromHId, HDFResultType)

instance Location File
instance Object File where
    staticObjectType = Tagged (Just FileObj)

createFile :: BS.ByteString -> [AccFlags] -> Maybe FCPL -> Maybe FAPL -> IO File
createFile filename flags create_plist access_plist =
    fmap File $
        withErrorCheck $
            BS.useAsCString filename $ \cfilename ->
                h5f_create cfilename (accFlagsToInt flags) (maybe h5p_DEFAULT hid create_plist) (maybe h5p_DEFAULT hid access_plist)

openFile :: BS.ByteString -> [AccFlags] -> Maybe FAPL -> IO File
openFile filename flags access_plist =
    fmap File $
        withErrorCheck $
            BS.useAsCString filename $ \cfilename ->
                h5f_open cfilename (accFlagsToInt flags) (maybe h5p_DEFAULT hid access_plist)

reopenFile :: File -> IO File
reopenFile (File file_id) =
    fmap File $
        withErrorCheck $
            h5f_reopen file_id

flushFile :: File -> Scope -> IO ()
flushFile (File file_id) scope =
    withErrorCheck_ $
        h5f_flush file_id (scopeCode scope)

closeFile :: File -> IO ()
closeFile (File file_id) =
    withErrorCheck_ (h5f_close file_id)

mountFile :: Location loc => loc -> BS.ByteString -> File -> Maybe FMPL -> IO ()
mountFile loc groupname (File file_id) mount_plist =
    withErrorCheck_ $
        BS.useAsCString groupname $ \cgroupname ->
            h5f_mount (hid loc) cgroupname file_id (maybe h5p_DEFAULT hid mount_plist)

unmountFile :: Location loc => loc -> BS.ByteString -> IO ()
unmountFile loc groupname =
    withErrorCheck_ $
        BS.useAsCString groupname $ \cgroupname ->
            h5f_unmount (hid loc) cgroupname

getFileSize :: File -> IO HSize
getFileSize (File file_id) =
    fmap HSize $
        withOut_ $ \sz ->
            withErrorCheck $
                h5f_get_filesize file_id sz

getFileCreatePlist :: File -> IO FCPL
getFileCreatePlist (File file_id) =
    fmap uncheckedFromHId $
        withErrorCheck $
            h5f_get_create_plist file_id

getFileAccessPlist :: File -> IO FAPL
getFileAccessPlist (File file_id) =
    fmap uncheckedFromHId $
        withErrorCheck $
            h5f_get_access_plist file_id

data FileInfo = FileInfo
    { superExtSize  :: !HSize
    , sohmHdrSize   :: !HSize
    , sohmMsgsInfo  :: !IH_Info
    } deriving (Eq, Ord, Read, Show)

readFileInfo :: H5F_info_t -> FileInfo
readFileInfo (H5F_info_t a b (H5_ih_info_t c d)) = FileInfo (HSize a) (HSize b) (IH_Info (HSize c) (HSize d))

getFileInfo :: Object obj => obj -> IO FileInfo
getFileInfo obj =
    fmap readFileInfo $
        withOut_ $ \info ->
            withErrorCheck $
                h5f_get_info (hid obj) info

getFileIntent :: File -> IO [AccFlags]
getFileIntent (File file_id) =
    fmap intToAccFlags $
        withOut_ $ \intent ->
            withErrorCheck_ $
                h5f_get_intent file_id intent

getFileName :: File -> IO BS.ByteString
getFileName (File file_id) =
    withOutByteString' $ \buf bufSz ->
        withErrorWhen (< 0) $
            h5f_get_name file_id buf bufSz

getFileObjCount :: Maybe File -> Bool -> [ObjType] -> IO CSize
getFileObjCount mbFile local objTypes =
    fmap fromIntegral $
        withErrorWhen (< 0) $
            h5f_get_obj_count (maybe (HId_t h5f_OBJ_ALL) hid mbFile) (objTypesToInt objTypes .|. if local then 0 else h5f_OBJ_LOCAL)

getOpenObjects :: Maybe File -> Bool -> [ObjType] -> IO (SV.Vector ObjectId)
getOpenObjects mbFile local objTypes = do
    n <- getFileObjCount mbFile local objTypes

    withOutVector' (fromIntegral n) $ \objects ->
        withErrorWhen (< 0) $
            h5f_get_obj_ids (maybe (HId_t h5f_OBJ_ALL) hid mbFile) (objTypesToInt objTypes .|. if local then 0 else h5f_OBJ_LOCAL) n (castWrappedPtr objects)

getFileFreespace :: File -> IO HSize
getFileFreespace (File file_id) =
    fmap fromIntegral $
        withErrorWhen (< 0) $
            h5f_get_freespace file_id
