{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StrictData #-}

module Codec.GLB where

import Prelude hiding (length)

import Data.Binary (Binary(..), decodeFileOrFail)
import Data.Binary.Get (ByteOffset, getWord32le, getByteString, isEmpty)
import Data.Binary.Put (putByteString, putWord32le)
import Data.ByteString (ByteString)
import Data.Vector (Vector, unfoldrM)
import Data.Word (Word32)
import GHC.Generics (Generic)

fromFile :: FilePath -> IO (Either (ByteOffset, String) GLB)
fromFile = decodeFileOrFail

data GLB = GLB
  { header :: Header
  , chunks :: Vector Chunk
  } deriving (Eq, Show, Generic)

instance Binary GLB where
  get = GLB <$> get <*> unfoldrM getChunks ()
    where
      getChunks () = do
        done <- isEmpty
        if done then
          pure Nothing
        else do
          chunk <- get
          pure $ Just (chunk, ())

  put GLB{..} = do
    put header
    mapM_ put chunks

data Header = Header
  { magic   :: Word32
  , version :: Word32
  , length  :: Word32
  } deriving (Eq, Show, Generic)

instance Binary Header where
  get = Header
    <$> getWord32le
    <*> getWord32le
    <*> getWord32le

  put Header{..} = do
    putWord32le magic
    putWord32le version
    putWord32le length

data Chunk = Chunk
  { chunkLength :: Word32
  , chunkType   :: Word32
  , chunkData   :: ByteString
  }
  deriving (Eq, Show, Generic)

instance Binary Chunk where
  get = do
    chunkLength <- getWord32le
    chunkType   <- getWord32le
    chunkData   <- getByteString (fromIntegral chunkLength)
    pure Chunk{..}

  put Chunk{..} = do
    putWord32le   chunkLength
    putWord32le   chunkType
    putByteString chunkData