{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Haskus.Binary.Serialize.File
   ( FileGetState (..)
   , FileGetT (..)
   , runFileGet
   , runFilePathGet
   )
where

import Haskus.Binary.Serialize.Get
import Haskus.Binary.Storable
import Haskus.Memory.Buffer
import Haskus.Utils.Monad
import Haskus.Utils.Maybe

import GHC.Exts (Ptr (..))
import System.IO
import Control.Monad.Trans.State.Strict as S
import Control.Monad.Fail as F
import Control.Monad.Fix

-- | FileGetT state
data FileGetState = FileGetState
   { fileGetHandle :: !Handle
   }

-- | A Get monad over a File
newtype FileGetT m a
   = FileGetT (StateT FileGetState m a)
   deriving newtype (Functor, Applicative, Monad, MonadFail, MonadFix, MonadIO)

-- | Get file handle
getHandle :: Monad m => FileGetT m (Handle)
getHandle = FileGetT (gets fileGetHandle)

-- | Helper to get some things
getSomething :: forall a m.
   ( MonadIO m
   ) => Word -> (Ptr a -> IO a) -> FileGetT m a
getSomething sz act = do
   hdl <- getHandle
   liftIO $ allocaBytes sz \p -> do
      -- FIXME: handle EOF
      _n <- hGetBuf hdl p (fromIntegral sz)
      act p


instance (MonadIO m) => GetMonad (FileGetT m) where
      getSkipBytes n = do
         hdl <- getHandle
         liftIO $ hSeek hdl RelativeSeek (fromIntegral n)

      getWord8       = getSomething 1 peek
      getWord16      = getSomething 2 peek
      getWord32      = getSomething 4 peek
      getWord64      = getSomething 8 peek

      getBufferInto sz dest mdoff = getSomething sz \(Ptr addr) -> do
         let b = BufferE addr sz
         copyBuffer b 0 dest (fromMaybe 0 mdoff) sz


-- | Run a getter on a file
runFileGet :: Handle -> FileGetT IO a -> IO a
runFileGet hdl (FileGetT s) = do
   (a,_s') <- runStateT s (FileGetState hdl)
   return a

-- | Run a getter on a file
runFilePathGet :: FilePath -> FileGetT IO a -> IO a
runFilePathGet path s = withBinaryFile path ReadMode (\hdl -> runFileGet hdl s)