-- | A class for translating to and from 'Datum'.
module Sound.OSC.Datum.Datem where

import qualified Data.ByteString.Lazy as Lazy {- bytestring -}
import qualified Data.ByteString.Char8 as Char8 {- bytestring -}
import Data.Int {- base -}
import Data.Maybe {- base -}
import Data.Word {- base -}

import Sound.OSC.Datum {- hosc -}
import Sound.OSC.Time {- hosc -}

{- | Class for translating to and from 'Datum'.
     There are instances for the direct 'Datum' field types.

> d_put (1::Int32) == Int32 1
> d_put (1::Int64) == Int64 1
> d_put (1::Float) == Float 1
> d_put (1::Double) == Double 1
> d_put (Char8.pack "str") == ASCII_String (Char8.pack "str")
> d_put (Lazy.pack [37,37]) == Blob (blob_pack [37,37])
> d_put (MIDI 0 0 0 0) == Midi (MIDI 0 0 0 0)

There are also instances for standard Haskell types.

> d_put (1::Int) == Int64 1
> d_put (1::Integer) == Int64 1

-}
class Datem a where
    d_put :: a -> Datum -- ^ Function to wrap value in 'Datum'.
    d_get :: Datum -> Maybe a -- ^ Function to extract value from 'Datum'.

instance Datem Int32 where
    d_put = Int32
    d_get d = case d of {Int32 x -> Just x;_ -> Nothing}

instance Datem Int64 where
    d_put = Int64
    d_get d = case d of {Int64 x -> Just x;_ -> Nothing}

instance Datem Int where
    d_put = Int64 . fromIntegral
    d_get = datum_integral

instance Datem Integer where
    d_put = Int64 . fromIntegral
    d_get = datum_integral

instance Datem Float where
    d_put = Float
    d_get d = case d of {Float x -> Just x;_ -> Nothing}

instance Datem Double where
    d_put = Double
    d_get d = case d of {Double x -> Just x;_ -> Nothing}

instance Datem Char8.ByteString where
    d_put = ASCII_String
    d_get d = case d of {ASCII_String x -> Just x;_ -> Nothing}

instance Datem Lazy.ByteString where
    d_put = Blob
    d_get d = case d of {Blob x -> Just x;_ -> Nothing}

instance Datem MIDI where
    d_put = Midi
    d_get d = case d of {Midi x -> Just x;_ -> Nothing}

-- | Error variant of 'd_get'.
d_get_err :: Datem a => Datum -> a
d_get_err = fromMaybe (error "d_get") . d_get

-- * Type specialised

-- | Type specialised 'd_get'.
--
-- > map datum_int32 [Int32 1,Float 1] == [Just 1,Nothing]
datum_int32 :: Datum -> Maybe Int32
datum_int32 = d_get

-- | Type specialised 'd_get'.
datum_int64 :: Datum -> Maybe Int64
datum_int64 = d_get

-- | Type specialised 'd_get'.
datum_float :: Datum -> Maybe Float
datum_float = d_get

-- | Type specialised 'd_get'.
datum_double :: Datum -> Maybe Double
datum_double = d_get

-- | Type specialised 'd_get'.
--
-- > datum_ascii (d_put (Char8.pack "string")) == Just (Char8.pack "string")
datum_ascii :: Datum -> Maybe ASCII
datum_ascii = d_get

-- | 'Char8.unpack' of 'd_get'.
--
-- > datum_string (d_put (Char8.pack "string")) == Just "string"
-- > map datum_string [string "string",Int32 5] == [Just "string",Nothing]
datum_string :: Datum -> Maybe String
datum_string = fmap Char8.unpack . datum_ascii

-- | Type specialised 'd_get'.
datum_blob :: Datum -> Maybe Lazy.ByteString
datum_blob = d_get

-- | 'Maybe' variant of 'd_timestamp'.
datum_timestamp :: Datum -> Maybe Time
datum_timestamp d = case d of {TimeStamp x -> Just x;_ -> Nothing}

-- | Type specialised 'd_get'.
datum_midi :: Datum -> Maybe MIDI
datum_midi = d_get

-- | 'Datum' as sequence of 'Word8' if 'ASCII_String', 'Blob' or 'Midi'.
--
-- > let d = [string "5",Blob (Lazy.pack [53]),midi (0x00,0x90,0x40,0x60)]
-- > in Data.Maybe.mapMaybe datum_sequence d == [[53],[53],[0,144,64,96]]
datum_sequence :: Datum -> Maybe [Word8]
datum_sequence d =
    case d of
      ASCII_String s -> Just (map (fromIntegral . fromEnum) (Char8.unpack s))
      Blob s -> Just (Lazy.unpack s)
      Midi (MIDI p q r s) -> Just [p,q,r,s]
      _ -> Nothing