-- | Encoding and decoding of OSC types as JSON values.
module Sound.OSC.Type.JSON where

import qualified Data.Aeson as A {- aeson -}
import qualified Data.ByteString.Char8 as C {- bytestring -}
import qualified Data.ByteString.Lazy as B.L {- bytestring -}
import qualified Data.ByteString.Lazy.UTF8 as U {- utf8-string -}
import Sound.OSC as O {- hosc -}
import Sound.OSC.Type.JSON.Aeson {- hosc -}

-- * Library variant

-- | The JSON value type.
type Value = A.Value

-- * String translation

-- | 'String' variant of 'encode_json'.
encode_json_str :: Value -> String
encode_json_str = U.toString . encode_json

-- | 'String' variant of 'decode_json'.
decode_json_str :: String -> Maybe Value
decode_json_str = decode_json . U.fromString

-- * Encoding

-- | JSON numbers are 'Either' 'Integer' or 'Double'.
type Number = Either Integer Double

-- | Encode 'Number'.
encode_number :: Number -> Value
encode_number = either encode_integral encode_floating

-- | Encode 'O.TimeStamp' data ('O.Time'), ie. the @hosc@ real-valued
-- @NRT@ representation.
encode_timestamp :: Time -> Value
encode_timestamp n = encode_assoc ("timestamp",encode_floating n)

-- | Encode 'O.Blob' data ('B.L.ByteString').
encode_blob :: B.L.ByteString -> Value
encode_blob b =
    let a = encode_list (map encode_integral (B.L.unpack b))
    in encode_assoc ("blob",a)

-- | Encode 'O.Midi' data ('Word8' tuple).
encode_midi :: MIDI -> A.Value
encode_midi (MIDI p q r s) =
    let a = encode_list (map encode_integral [p,q,r,s])
    in encode_assoc ("midi",a)

-- | 'Datum' encoder.  The encoding is shallow, 'O.Int', 'O.Float' and
-- 'O.Double' are all sent to 'A.Number'.  'O.Blob', 'O.TimeStamp' and
-- 'O.Midi' are tagged.
--
-- > let {t = [(int32 0,"0")
-- >          ,(int64 0,"0")
-- >          ,(float 0.0,"0.0")
-- >          ,(double 0.1,"0.1")
-- >          ,(string "s","\"s\"")
-- >          ,(Blob (Data.ByteString.Lazy.pack [0,1]),"{\"blob\":[0,1]}")
-- >          ,(TimeStamp 0.0,"{\"timestamp\":0.0}")
-- >          ,(midi (0,1,2,3),"{\"midi\":[0,1,2,3]}")]
-- >     ;r = map (\(d,s) -> encode_json_str (encode_datum d) == s) t}
-- > in all id r == True
encode_datum :: Datum -> Value
encode_datum d =
    case d of
      Int32 n -> encode_integral n
      Int64 n -> encode_integral n
      Float n -> encode_floating n
      Double n -> encode_floating n
      ASCII_String s -> encode_string (C.unpack s)
      Blob b -> encode_blob b
      TimeStamp n -> encode_timestamp n
      Midi m -> encode_midi m

-- | 'Message' encoder, the representation is a flat array of
-- @address@ and then arguments.
--
-- > let m = message "/m" [Int32 0,Float 1,string "s"]
-- > in encode_json_str (encode_message m)
--
-- > import Sound.SC3
-- > encode_json_str (encode_message (n_free [0])) == "[\"/n_free\",0]"
encode_message :: Message -> Value
encode_message (Message a d) =
    let a' = encode_string a
        d' = map encode_datum d
    in encode_list (a' : d')

-- | 'O.Bundle' encoder, the representation is a flat array of @#bundle@
-- tag, 'O.TimeStamp' and then message arrays.
--
-- > let b = bundle 0 [message "/m" []]
-- > in encode_json_str (encode_bundle b)
--
-- > let {b = bundle 0 [c_set1 3 4,n_free [0]]
-- >     ;r = "[\"#bundle\",{\"timestamp\":0.0}" ++
-- >          ",[\"/c_set\",3,4.0],[\"/n_free\",0]]"}
-- > in encode_json_str (encode_bundle b) == r
encode_bundle :: Bundle -> Value
encode_bundle (Bundle t m) =
    let b = encode_string "#bundle"
        t' = encode_timestamp t
        m' = map encode_message m
    in encode_list (b : t' : m')

-- | 'Packet' encoder.
encode_packet :: Packet -> Value
encode_packet p =
    case p of
      Packet_Message m -> encode_message m
      Packet_Bundle b -> encode_bundle b

-- * Decoder

-- | Decode 'Message'.
--
-- > let m = message "/m" [Int32 1,Float 1]
-- > in decode_message (encode_message m) == Just m
decode_message :: Value -> Maybe Message
decode_message j =
    case decode_list j of
      Just (m : d) ->
          case decode_datum m of
            Just (ASCII_String m') -> mapM decode_datum d >>=
                                      Just . message (C.unpack m')
            _ -> Nothing
      _ -> Nothing

-- | Decode 'Bundle'.
--
-- > let b = bundle 0.0 [message "/m" [Int32 1,Float 1]]
-- > in decode_bundle (encode_bundle b) == Just b
--
-- > let {b = bundle 0 [c_set1 3 4,n_free [0]]
-- >     ;j = encode_bundle b}
-- > in (b,decode_bundle j)
decode_bundle :: Value -> Maybe Bundle
decode_bundle j =
    case decode_list j of
      Just (b : t : m) ->
          case (datum_string =<< decode_datum b,decode_datum t) of
            (Just "#bundle",Just (TimeStamp t')) ->
                mapM decode_message m >>= Just . Bundle t'
            _ -> Nothing
      _ -> Nothing

-- | Decode 'Packet'.
decode_packet :: Value -> Maybe Packet
decode_packet v =
    case decode_bundle v of
      Just b -> Just (Packet_Bundle b)
      Nothing -> case decode_message v of
                   Just m -> Just (Packet_Message m)
                   Nothing -> Nothing