{- | Base-level decode function for Osc packets.
  For ordinary use see 'Sound.Osc.Coding.Decode.Binary'.
-}
module Sound.Osc.Coding.Decode.Base (
  decodeMessage,
  decodeBundle,
  decodePacket,
) where

import Data.Binary {- base -}
import qualified Data.ByteString.Char8 as C {- bytestring -}
import qualified Data.ByteString.Lazy as B {- bytestring -}
import Data.List {- base -}
import Data.Maybe {- base -}

import Sound.Osc.Coding.Byte {- hosc -}
import Sound.Osc.Coding.Convert {- hosc -}
import Sound.Osc.Datum {- hosc -}
import Sound.Osc.Packet {- hosc -}
import Sound.Osc.Time {- hosc -}

-- | The plain byte count of an Osc value.
size :: DatumType -> B.ByteString -> Int
size ty b =
  case ty of
    'i' -> 4 -- Int32
    'f' -> 4 -- Float
    'd' -> 8 -- Double
    't' -> 8 -- Time (NTP)
    'm' -> 4 -- MIDI
    's' ->
      int64_to_int
        ( fromMaybe
            (error ("size: no terminating zero: " ++ show b))
            (B.elemIndex 0 b)
        )
    'b' -> decode_i32 (B.take 4 b)
    _ -> error "size: illegal type"

-- | The storage byte count (aligned) of an Osc value.
storage :: DatumType -> B.ByteString -> Int
storage ty b =
  case ty of
    's' -> let n = size 's' b + 1 in n + align n
    'b' -> let n = size 'b' b in n + align n + 4
    _ -> size ty B.empty

-- | Decode an Osc datum
decode_datum :: DatumType -> B.ByteString -> Datum
decode_datum ty b =
  case ty of
    'i' -> Int32 (decode b)
    'h' -> Int64 (decode b)
    'f' -> Float (decode_f32 b)
    'd' -> Double (decode_f64 b)
    's' -> AsciiString (decode_ascii (b_take (size 's' b) b))
    'b' -> Blob (b_take (size 'b' b) (B.drop 4 b))
    't' -> TimeStamp (ntpi_to_ntpr (decode_word64 b))
    'm' ->
      case B.unpack (B.take 4 b) of
        [b0, b1, b2, b3] -> midi (b0, b1, b2, b3)
        _ -> error "decode_datum: illegal midi data"
    _ -> error ("decode_datum: illegal type (" ++ [ty] ++ ")")

-- | Decode a sequence of Osc datum given a type descriptor string.
decode_datum_seq :: Ascii -> B.ByteString -> [Datum]
decode_datum_seq cs b =
  let swap (x, y) = (y, x)
      cs' = C.unpack cs
      f b' c = swap (B.splitAt (int_to_int64 (storage c b')) b')
  in zipWith decode_datum cs' (snd (mapAccumL f b cs'))

-- | Decode an Osc 'Message'.
decodeMessage :: B.ByteString -> Message
decodeMessage b =
  let n = storage 's' b
      cmd = decode_datum 's' b
      m = storage 's' (b_drop n b)
  in case (cmd, decode_datum 's' (b_drop n b)) of
      (AsciiString cmd', AsciiString dsc) ->
        let arg = decode_datum_seq (descriptor_tags dsc) (b_drop (n + m) b)
        in Message (C.unpack cmd') arg
      _ -> error "decodeMessage"

-- | Decode a sequence of length prefixed (Int32) Osc messages.
decode_message_seq :: B.ByteString -> [Message]
decode_message_seq b =
  let s = decode_i32 b
      m = decodeMessage (b_drop 4 b)
      nxt = decode_message_seq (b_drop (4 + s) b)
  in if B.length b == 0 then [] else m : nxt

-- | Decode an Osc 'Bundle'.
decodeBundle :: B.ByteString -> BundleOf Message
decodeBundle b =
  let h = storage 's' b -- header (should be '#bundle')
      t = storage 't' (b_drop h b) -- time
  in case decode_datum 't' (b_drop h b) of
      TimeStamp timeStamp -> Bundle timeStamp (decode_message_seq (b_drop (h + t) b))
      _ -> error "decodeBundle"

{- | Decode an Osc 'Packet'.

>>> let b = B.pack [47,103,95,102,114,101,101,0,44,105,0,0,0,0,0,0]
>>> decodePacket b == Packet_Message (Message "/g_free" [Int32 0])
True
-}
decodePacket :: B.ByteString -> PacketOf Message
decodePacket b =
  if bundleHeader `B.isPrefixOf` b
    then Packet_Bundle (decodeBundle b)
    else Packet_Message (decodeMessage b)

-- * Util

-- | 'B.take' with 'Int' count.
b_take :: Int -> B.ByteString -> B.ByteString
b_take = B.take . int_to_int64

-- | 'B.drop' with 'Int' count.
b_drop :: Int -> B.ByteString -> B.ByteString
b_drop = B.drop . int_to_int64
