{-|
Module      : Binary.Neko.Globals
Description : Global values for a Binary.Neko module
Copyright   : (c) Petr Penzin, 2015
License     : BSD2
Maintainer  : penzin.dev@gmail.com
Stability   : experimental
Portability : cross-platform

Emit and parse global values for a Binary.Neko module.

-}
module Binary.Neko.Globals where

import Data.ByteString.Lazy as BS
import Data.ByteString.Lazy.Char8 as BSChar
import Data.Binary.Get
import Data.Binary.Put
import Data.Either
import Data.Maybe
import Data.Word
import Data.Int

-- | Global value type
data Global =
        GlobalVar String -- ^ Global variable (name)
      | GlobalFunction (Int, Int) -- ^ Function
      | GlobalString String -- ^ String literal
      | GlobalFloat String -- ^ Floating point constant
      | GlobalDebug ([String], [(Int, Int)]) -- ^ Debug information
      | GlobalVersion Int -- ^ Version record
      deriving (Show, Eq)

-- | Read globals from a bytestring
readGlobals :: Word32 -- ^ Number of global values to read
            -> ByteString -- ^ Bytestring to read from
            -> Maybe ([Global], ByteString) -- ^ On success: list of global values read and unconsumed bytestring
readGlobals n bs = if (isRight res) then (Just (gs, rest)) else Nothing 
    where res = runGetOrFail (getGlobals n) bs
          Right (rest, _, gs) = res

-- | Read a single global value, if succesfull return a single global value and remaining bytestring
readGlobal :: ByteString -- ^ Bytes to read from
           -> Maybe (Global, ByteString) -- ^ Read value and the rest of bytes (if successfull)
readGlobal bs = if (isRight res) then (Just (g, rest)) else Nothing 
    where res = runGetOrFail getGlobal bs
          Right (rest, _, g) = res

-- | Get a list of globals from a bytestring
getGlobals :: Word32 -- ^ number of globals
           -> Get [Global] -- ^ decode a list
getGlobals 0 = return []
getGlobals n = getGlobal >>= \g -> getGlobals (n - 1) >>= \gs -> return (g:gs)

-- | Read a global from a bytestring
getGlobal :: Get Global
getGlobal = getWord8
        >>= \b -> if (b == 1) then getGlobalVar else
                  if (b == 2) then error "TODO getGlobal: implement GlobalFunction" else
                  if (b == 3) then getGlobalString else
                  if (b == 4) then error "TODO getGlobal: implement GlobalFloat" else
                  if (b == 5) then error "TODO getGlobal: implement GlobalDebug" else
                  if (b == 6) then error "TODO getGlobal: implement GlobalVersion" else
                  fail "getGlobal: urecognized global"

-- | Decode a global variable
getGlobalVar :: Get Global
getGlobalVar = getLazyByteStringNul >>= \b -> return ( GlobalVar $ BSChar.unpack b)

-- | Decode a global string
getGlobalString :: Get Global
getGlobalString = getWord16le
              >>= \length -> getLazyByteString (fromIntegral length)
              >>= \s -> return (GlobalString $ BSChar.unpack s)

-- | Write a global to a bytestring
putGlobal :: Global -> Put
putGlobal (GlobalVar s) = putWord8 1 >> putLazyByteString (BSChar.pack s) >> putWord8 0
putGlobal (GlobalString s) = putWord8 3
                          >> putWord16le (fromIntegral $ Prelude.length s) >> putLazyByteString (BSChar.pack s)
putGlobal g = error ("Unimplemented: " ++ (show g)) -- Remove after all implemented

-- | Write a list of globals to a bytestring
putGlobals :: [Global] -> Put
putGlobals [] = return ()
putGlobals (g:gs) = putGlobal g >> putGlobals gs