module Sound.OSC.Type where
import qualified Data.ByteString.Lazy as B 
import qualified Data.ByteString.Char8 as C 
import Data.Int 
import Data.List 
import Data.Word 
import Numeric 
type Time = Double
immediately :: Time
immediately = 1 / 2^(32::Int)
type Datum_Type = Char
type ASCII = C.ByteString
ascii :: String -> ASCII
ascii = C.pack
ascii_to_string :: ASCII -> String
ascii_to_string = C.unpack
data MIDI = MIDI Word8 Word8 Word8 Word8
    deriving (Eq,Show,Read)
data Datum = Int32 {d_int32 :: Int32}
           | Int64 {d_int64 :: Int64}
           | Float {d_float :: Float}
           | Double {d_double :: Double}
           | ASCII_String {d_ascii_string :: ASCII}
           | Blob {d_blob :: B.ByteString}
           | TimeStamp {d_timestamp :: Time}
           | Midi {d_midi :: MIDI}
             deriving (Eq,Read,Show)
datum_tag :: Datum -> Datum_Type
datum_tag dt =
    case dt of
      Int32 _ -> 'i'
      Int64 _ -> 'h'
      Float _ -> 'f'
      Double _ -> 'd'
      ASCII_String _ -> 's'
      Blob _ -> 'b'
      TimeStamp _ -> 't'
      Midi _ -> 'm'
datum_integral :: Integral i => Datum -> Maybe i
datum_integral d =
    case d of
      Int32 x -> Just (fromIntegral x)
      Int64 x -> Just (fromIntegral x)
      _ -> Nothing
datum_floating :: Floating n => Datum -> Maybe n
datum_floating d =
    case d of
      Int32 n -> Just (fromIntegral n)
      Int64 n -> Just (fromIntegral n)
      Float n -> Just (realToFrac n)
      Double n -> Just (realToFrac n)
      TimeStamp n -> Just (realToFrac n)
      _ -> Nothing
class Datem a where
    d_put :: a -> Datum
    d_get :: Datum -> Maybe a
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 C.ByteString where
    d_put = ASCII_String
    d_get d = case d of {ASCII_String x -> Just x;_ -> Nothing}
instance Datem B.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}
int32 :: Integral n => n -> Datum
int32 = Int32 . fromIntegral
int64 :: Integral n => n -> Datum
int64 = Int64 . fromIntegral
float :: Real n => n -> Datum
float = Float . realToFrac
double :: Real n => n -> Datum
double = Double . realToFrac
string :: String -> Datum
string = ASCII_String . C.pack
midi :: (Word8,Word8,Word8,Word8) -> Datum
midi (p,q,r,s) = Midi (MIDI p q r s)
type Address_Pattern = String
data Message = Message {messageAddress :: Address_Pattern
                       ,messageDatum :: [Datum]}
               deriving (Eq,Read,Show)
message :: Address_Pattern -> [Datum] -> Message
message a xs =
    case a of
      '/':_ -> Message a xs
      _ -> error "message: ill-formed address pattern"
descriptor :: [Datum] -> ASCII
descriptor l = C.pack (',' : map datum_tag l)
descriptor_tags :: ASCII -> ASCII
descriptor_tags = C.drop 1
data Bundle = Bundle {bundleTime :: Time
                     ,bundleMessages :: [Message]}
              deriving (Eq,Read,Show)
instance Ord Bundle where
    compare (Bundle a _) (Bundle b _) = compare a b
bundle :: Time -> [Message] -> Bundle
bundle t xs =
    case xs of
      [] -> error "bundle: empty?"
      _ -> Bundle t xs
data Packet = Packet_Message {packetMessage :: Message}
            | Packet_Bundle {packetBundle :: Bundle}
              deriving (Eq,Read,Show)
p_bundle :: Time -> [Message] -> Packet
p_bundle t = Packet_Bundle . bundle t
p_message :: Address_Pattern -> [Datum] -> Packet
p_message a = Packet_Message . message a
packetTime :: Packet -> Time
packetTime = at_packet (const immediately) bundleTime
packetMessages :: Packet -> [Message]
packetMessages = at_packet return bundleMessages
packet_to_bundle :: Packet -> Bundle
packet_to_bundle = at_packet (\m -> Bundle immediately [m]) id
packet_to_message :: Packet -> Maybe Message
packet_to_message p =
    case p of
      Packet_Bundle b ->
          case b of
            Bundle t [m] -> if t == immediately then Just m else Nothing
            _ -> Nothing
      Packet_Message m -> Just m
packet_is_immediate :: Packet -> Bool
packet_is_immediate = (== immediately) . packetTime
at_packet :: (Message -> a) -> (Bundle -> a) -> Packet -> a
at_packet f g p =
    case p of
      Packet_Message m -> f m
      Packet_Bundle b -> g b
message_has_address :: Address_Pattern -> Message -> Bool
message_has_address x = (== x) . messageAddress
bundle_has_address :: Address_Pattern -> Bundle -> Bool
bundle_has_address x = any (message_has_address x) . bundleMessages
packet_has_address :: Address_Pattern -> Packet -> Bool
packet_has_address x =
    at_packet (message_has_address x)
              (bundle_has_address x)
type FP_Precision = Maybe Int
floatPP :: RealFloat n => Maybe Int -> n -> String
floatPP p n =
    let s = showFFloat p n ""
        s' = dropWhile (== '0') (reverse s)
    in case s' of
         '.':_ -> reverse ('0' : s')
         _ -> reverse s'
timePP :: FP_Precision -> Time -> String
timePP = floatPP
vecPP :: Show a => [a] -> String
vecPP v = '<' : intercalate "," (map show v) ++ ">"
datumPP :: FP_Precision -> Datum -> String
datumPP p d =
    case d of
      Int32 n -> show n
      Int64 n -> show n
      Float n -> floatPP p n
      Double n -> floatPP p n
      ASCII_String s -> show (C.unpack s)
      Blob s -> show s
      TimeStamp t -> timePP p t
      Midi (MIDI b1 b2 b3 b4) -> vecPP [b1,b2,b3,b4]
messagePP :: FP_Precision -> Message -> String
messagePP p (Message a d) =
    let d' = map (datumPP p) d
    in unwords ("#message" : a : d')
bundlePP :: FP_Precision -> Bundle -> String
bundlePP p (Bundle t m) =
    let m' = intersperse ";" (map (messagePP p) m)
    in unwords ("#bundle" : timePP p t : m')
packetPP :: FP_Precision -> Packet -> String
packetPP p pkt =
    case pkt of
      Packet_Message m -> messagePP p m
      Packet_Bundle b -> bundlePP p b
readMaybe :: (Read a) => String -> Maybe a
readMaybe s =
    case reads s of
      [(x, "")] -> Just x
      _ -> Nothing
parse_datum :: Datum_Type -> String -> Maybe Datum
parse_datum ty =
    case ty of
      'i' -> fmap Int32 . readMaybe
      'h' -> fmap Int64 . readMaybe
      'f' -> fmap Float . readMaybe
      'd' -> fmap Double . readMaybe
      's' -> fmap (ASCII_String . C.pack) . readMaybe
      'b' -> fmap (Blob . B.pack) . readMaybe
      't' -> error "parse_datum: timestamp"
      'm' -> fmap midi . readMaybe
      _ -> error "parse_datum: type"