module Sound.Tidal.OscStream where

import qualified Data.Map as Map
import Data.Maybe
import Sound.Tidal.Tempo (Tempo, cps)
import Sound.Tidal.Stream
import Sound.Tidal.Utils
import GHC.Float (float2Double, double2Float)
import Sound.OSC.FD
import Sound.OSC.Datum
import Sound.Tidal.Params

data TimeStamp = BundleStamp | MessageStamp | NoStamp
 deriving Eq

data OscSlang = OscSlang {path :: String,
                          timestamp :: TimeStamp,
                          namedParams :: Bool,
                          preamble :: [Datum]
                         }

type OscMap = Map.Map Param (Maybe Datum)

toOscDatum :: Value -> Maybe Datum
toOscDatum (VF x) = Just $ float x
toOscDatum (VI x) = Just $ int32 x
toOscDatum (VS x) = Just $ string x

toOscMap :: ParamMap -> OscMap
toOscMap m = Map.map (toOscDatum) (Map.mapMaybe (id) m)

-- constructs and sends an Osc Message according to the given slang
-- and other params - this is essentially the same as the former
-- toMessage in Stream.hs
send s slang shape change tick (o, m) = osc
    where
      osc | timestamp slang == BundleStamp =
            sendOSC s $ Bundle (ut_to_ntpr logicalOnset) [Message (path slang) oscdata]
          | timestamp slang == MessageStamp =
            sendOSC s $ Message (path slang) ((int32 sec):(int32 usec):oscdata)
          | otherwise =
            doAt logicalOnset $ sendOSC s $ Message (path slang) oscdata
      oscPreamble = cpsPrefix ++ preamble slang
      oscdata | namedParams slang = oscPreamble ++ (concatMap (\(k, Just v) -> [string (name k), v] )
                                                    $ filter (isJust . snd) $ Map.assocs m)
              | otherwise = oscPreamble ++ (catMaybes $ mapMaybe (\x -> Map.lookup x m) (params shape))
      cpsPrefix | cpsStamp shape && namedParams slang = [string "cps", float (cps change)]
                | cpsStamp shape = [float (cps change)]
                | otherwise = []
      parameterise ds = mergelists (map (string . name) (params shape)) ds
      usec = floor $ 1000000 * (logicalOnset - (fromIntegral sec))
      sec = floor logicalOnset
      logicalOnset = logicalOnset' change tick o ((latency shape) + nudge)
      nudge = maybe 0 (toF) (Map.lookup nudge_p m)
      toF (Just (Float f)) = float2Double f
      toF _ = 0

-- type OscMap = Map.Map Param (Maybe Datum)
              
-- Returns a function that will convert a generic ParamMap into a specific Osc message and send it over UDP to the supplied server
-- messages will be built according to the given OscSlang
makeConnection :: String -> Int -> OscSlang -> IO (ToMessageFunc)
makeConnection address port slang = do
  s <- openUDP address port
  return (\ shape change tick (o,m) -> do
             let m' = if (namedParams slang) then (Just m) else (applyShape' shape m)
             -- this might result in Nothing, make sure we do this first
             m'' <- fmap (toOscMap) m'
             -- to allow us to simplify `send` (no `do`)
             return $ send s slang shape change tick (o,m'')
         )