module Sound.Tidal.EspGrid where

import Control.Concurrent.MVar
import Control.Concurrent
import Control.Monad (forever)
import Control.Monad.Loops (iterateM_)
import Sound.OSC.FD
import Data.Time.Clock
import Data.Time.Clock.POSIX
import Data.Time.Calendar (fromGregorian)

import Sound.Tidal.Tempo
import Sound.Tidal.Time as T
import Sound.Tidal.Stream
import Sound.Tidal.Dirt
import Sound.Tidal.Transition (transition)
import Sound.Tidal.Pattern (silence)

parseEspTempo :: [Datum] -> Maybe Tempo
parseEspTempo d = do
  on <- datum_integral (d!!0)
  bpm <- datum_floating (d!!1)
  t1 <- datum_integral (d!!2)
  t2 <- datum_integral (d!!3)
  n <- datum_integral (d!!4)
  let nanos = (t1*1000000000) + t2
  let utc = posixSecondsToUTCTime ((realToFrac nanos)/1000000000)
  return (Tempo utc (fromIntegral n) (bpm/60) (on==0) 0.04)

changeTempo :: MVar Tempo -> Packet -> IO ()
changeTempo mvar (Packet_Message msg) = do
    case parseEspTempo (messageDatum msg) of
      Just t -> tryTakeMVar mvar >> putMVar mvar t
      Nothing -> putStrLn "Unable to parse message as Tempo"
changeTempo _ _ = putStrLn "Can only process Packet_Message"

getTempo :: MVar Tempo -> IO Tempo
getTempo = readMVar

runClientEsp :: IO (MVar Tempo,MVar Double)
runClientEsp = do
  mTempo <- newEmptyMVar
  mCps <- newEmptyMVar
  socket <- openUDP "127.0.0.1" 5510
  forkIO $ forever $ do
    sendOSC socket $ Message "/esp/tempo/q" []
    response <- waitAddress socket "/esp/tempo/r"
    changeTempo mTempo response
    threadDelay 100000
  return (mTempo, mCps)

sendEspTempo :: Real t => t -> IO ()
sendEspTempo t = do
  socket <- openUDP "127.0.0.1" 5510
  sendOSC socket $ Message "/esp/beat/tempo" [float (t*60)]

cpsUtilsEsp :: IO (Double -> IO (), IO Rational, IO Tempo)
cpsUtilsEsp = do
  (mTempo,mCps) <- runClientEsp
  return (sendEspTempo,getCurrentBeat mTempo,getTempo mTempo)

clockedTickEsp :: Int -> (Tempo -> Int -> IO ()) -> IO ()
clockedTickEsp tpb callback = do
  (mTempo, _) <- runClientEsp
  nowBeat <- getCurrentBeat mTempo
  let nextTick = ceiling (nowBeat * (fromIntegral tpb))
  iterateM_ (clockedTickLoopEsp tpb callback mTempo) nextTick

clockedTickLoopEsp :: Int -> (Tempo -> Int -> IO ()) -> MVar Tempo -> Int -> IO Int
clockedTickLoopEsp tpb callback mTempo tick = do
  tempo <- readMVar mTempo
  if (paused tempo)
    then do  -- TODO - do this via blocking read on the mvar somehow rather than polling

      let pause = 0.01
      threadDelay $ floor (pause * 1000000)
      return $ if cps tempo < 0 then 0 else tick  -- reset tick to 0 if cps is negative

    else do
      now <- getCurrentTime
      let beatsFromAtToTick = fromIntegral tick / fromIntegral tpb - beat tempo
          delayUntilTick = beatsFromAtToTick / cps tempo - realToFrac (diffUTCTime now (at tempo))
      threadDelay $ floor (delayUntilTick * 1000000)
      callback tempo tick
      return $ tick + 1

streamEsp :: Backend a -> Shape -> IO (ParamPattern -> IO ())
streamEsp backend shape = do
  patternM <- newMVar silence
  forkIO $ clockedTickEsp ticksPerCycle (onTick backend shape patternM)
  return $ \p -> do swapMVar patternM p
                    return ()

dirtStreamEsp :: IO (ParamPattern -> IO ())
dirtStreamEsp = do
  backend <- dirtBackend
  streamEsp backend dirt

stateEsp :: Backend a -> Shape -> IO (MVar (ParamPattern, [ParamPattern]))
stateEsp backend shape = do
  patternsM <- newMVar (silence, [])
  let ot = (onTick' backend shape patternsM) :: Tempo -> Int -> IO ()
  forkIO $ clockedTickEsp ticksPerCycle ot
  return patternsM

dirtSettersEsp :: IO T.Time -> IO (ParamPattern -> IO (), (T.Time -> [ParamPattern] -> ParamPattern) -> ParamPattern -> IO ())
dirtSettersEsp getNow = do
  backend <- dirtBackend
  ds <- stateEsp backend dirt
  return (setter ds, transition getNow ds)

superDirtSettersEsp :: IO T.Time -> IO (ParamPattern -> IO (), (T.Time -> [ParamPattern] -> ParamPattern) -> ParamPattern -> IO ())
superDirtSettersEsp getNow = do
  backend <- superDirtBackend 57120
  ds <- stateEsp backend dirt
  return (setter ds, transition getNow ds)