-- | Binary encoders and decoders.
module Sound.Sc3.Server.Graphdef.Binary where

import System.FilePath {- filepath -}

import qualified Data.Binary.Get as Get {- binary -}
import qualified Data.Binary.IEEE754 as IEEE754 {- data-binary-ieee754 -}
import qualified Data.ByteString.Lazy as ByteString {- bytestring -}

import qualified Sound.Osc.Coding.Byte as Byte {- hosc -}
import qualified Sound.Osc.Coding.Cast as Cast {- hosc -}
import qualified Sound.Osc.Datum as Datum {- hosc -}

import Sound.Sc3.Server.Graphdef {- hsc3 -}

-- * Binary Get (version 0|1 or 2)

-- | Get a 'Name' (Pascal string).
get_pstr :: Get.Get Name
get_pstr = do
  n <- fmap fromIntegral Get.getWord8
  fmap Byte.decode_ascii (Get.getLazyByteString n)

-- | Get_Functions for binary .scsyndef files.
binary_get_f :: Get_Functions Get.Get
binary_get_f =
  ( get_pstr
  , fmap fromIntegral Get.getInt8
  , fmap fromIntegral Get.getInt16be
  , fmap fromIntegral Get.getInt32be
  , fmap realToFrac IEEE754.getFloat32be
  )

-- * Read

{- | Read Graphdef from .scsyndef file.

> dir = "/home/rohan/sw/rsc3-disassembler/scsyndef/"
> pp nm = read_graphdef_file (dir ++ nm) >>= putStrLn . graphdef_stat
> pp "simple.scsyndef"
> pp "with-ctl.scsyndef"
> pp "mce.scsyndef"
> pp "mrg.scsyndef"
-}
read_graphdef_file :: FilePath -> IO Graphdef
read_graphdef_file nm = do
  b <- ByteString.readFile nm
  return (Get.runGet (get_graphdef binary_get_f) b)

-- * Stat

-- | 'read_graphdef_file' and run 'graphdef_stat'.
scsyndef_stat :: FilePath -> IO String
scsyndef_stat fn = do
  g <- read_graphdef_file fn
  return (graphdef_stat g)

-- * Encode (version zero)

-- | 'Encode_Functions' for 'ByteString.ByteString'
enc_bytestring :: Encode_Functions ByteString.ByteString
enc_bytestring =
  ( ByteString.concat
  , encode_pstr
  , Byte.encode_i8
  , Byte.encode_i16
  , Byte.encode_i32
  , encode_sample
  , const ByteString.empty
  )

{- | Pascal (length prefixed) encoding of 'Name'.

> ByteString.unpack (encode_pstr (ascii "string")) ==  [6, 115, 116, 114, 105, 110, 103]
-}
encode_pstr :: Name -> ByteString.ByteString
encode_pstr = ByteString.pack . Cast.str_pstr . Datum.ascii_to_string

-- | Byte-encode 'Input'.
encode_input :: Input -> ByteString.ByteString
encode_input = encode_input_f enc_bytestring

-- | Byte-encode 'Control'.
encode_control :: Control -> ByteString.ByteString
encode_control = encode_control_f enc_bytestring

-- | Byte-encode 'Ugen'.
encode_ugen :: Ugen -> ByteString.ByteString
encode_ugen = encode_ugen_f enc_bytestring

-- | Encode 'Sample' as 32-bit IEEE float.
encode_sample :: Sample -> ByteString.ByteString
encode_sample = Byte.encode_f32 . realToFrac

-- | Encode 'Graphdef'.
encode_graphdef :: Graphdef -> ByteString.ByteString
encode_graphdef = encode_graphdef_f enc_bytestring

-- * IO

-- | Write 'Graphdef' to indicated file.
graphdefWrite :: FilePath -> Graphdef -> IO ()
graphdefWrite fn = ByteString.writeFile fn . encode_graphdef

{- | Write 'Graphdef' to indicated directory.  The filename is the
'graphdef_name' with the appropriate extension (@scsyndef@).
-}
graphdefWrite_dir :: FilePath -> Graphdef -> IO ()
graphdefWrite_dir dir s =
  let fn = dir </> Datum.ascii_to_string (graphdef_name s) <.> "scsyndef"
  in graphdefWrite fn s
