-- | 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 Char8 {- bytestring -}
import qualified Data.ByteString.Lazy as Lazy {- bytestring -}
import qualified Data.ByteString.Lazy.UTF8 as UTF8 {- utf8-string -}

import qualified Sound.OSC as O {- hosc -}
import qualified Sound.OSC.Datum.Datem as D {- hosc -}
import qualified Sound.OSC.Type.JSON.Aeson as J {- hosc-json -}

-- * 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 = UTF8.toString . J.encode_json

-- | 'String' variant of 'decode_json'.
--
-- > import Sound.OSChar8.Type.JSON
-- > let j = decode_json_str "[\"/n_set\",-1,\"c1\",66]"
-- > fmap decode_message j
decode_json_str :: String -> Maybe Value
decode_json_str = J.decode_json . UTF8.fromString

-- * Encoding

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

-- | Encode 'Number'.
encode_number :: Number -> Value
encode_number = either J.encode_integer J.encode_double

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

encode_integral :: Integral n => n -> Value
encode_integral = J.encode_integer . fromIntegral

encode_floating :: Real n => n -> Value
encode_floating = J.encode_double . realToFrac

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

-- | Encode 'O.Midi' data ('Word8' tuple).
encode_midi :: O.MIDI -> A.Value
encode_midi (O.MIDI p q r s) =
    let a = J.encode_list (map encode_integral [p,q,r,s])
    in J.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 :: O.Datum -> Value
encode_datum d =
    case d of
      O.Int32 n -> encode_integral n
      O.Int64 n -> encode_integral n
      O.Float n -> encode_floating n
      O.Double n -> encode_floating n
      O.ASCII_String s -> J.encode_string (Char8.unpack s)
      O.Blob b -> encode_blob b
      O.TimeStamp n -> encode_timestamp n
      O.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 :: O.Message -> Value
encode_message (O.Message a d) =
    let a' = J.encode_string a
        d' = map encode_datum d
    in J.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 :: O.Bundle -> Value
encode_bundle (O.Bundle t m) =
    let b = J.encode_string "#bundle"
        t' = encode_timestamp t
        m' = map encode_message m
    in J.encode_list (b : t' : m')

-- | 'Packet' encoder.
encode_packet :: O.Packet -> Value
encode_packet p =
    case p of
      O.Packet_Message m -> encode_message m
      O.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 O.Message
decode_message j =
    case J.decode_list j of
      Just (m : d) ->
          case J.decode_datum m of
            Just (O.ASCII_String m') ->
                mapM J.decode_datum d >>=
                Just . O.message (Char8.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 O.Bundle
decode_bundle j =
    case J.decode_list j of
      Just (b : t : m) ->
          case (D.datum_string =<< J.decode_datum b,J.decode_datum t) of
            (Just "#bundle",Just (O.TimeStamp t')) ->
                mapM decode_message m >>= Just . O.Bundle t'
            _ -> Nothing
      _ -> Nothing

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