module FRP.Peakachu
  ( runProgram
  ) where

import FRP.Peakachu.Backend (Backend(..), Sink(..))
import FRP.Peakachu.Program (Program(..))
import Control.Concurrent.MVar.YC (writeMVar)

import Control.Concurrent (forkIO, threadDelay)
import Control.Concurrent.MVar (newMVar, putMVar, readMVar, takeMVar)
import Control.Monad (when)
import Data.Function (fix)
import Data.Maybe (isNothing)

doWhile :: Monad m => m Bool -> m ()
doWhile x = fix $ (x >>=) . flip when

runProgram :: Backend o i -> Program i o -> IO ()
runProgram backend program = do
  progVar <- newMVar program
  resumeVar <- newMVar True
  sinkVar <- newMVar Nothing
  let
    consumeOutput =
      doWhile $ do
        Just sink <- readMVar sinkVar
        prog@(Program vals more) <- takeMVar progVar
        case vals of
          [] -> do
            putMVar progVar prog
            when (isNothing (progMore prog)) $ do
              sinkQuitLoop sink
              writeMVar resumeVar False
            return False
          (x : xs) -> do
            putMVar progVar $ Program xs more
            sinkConsume sink x
            return True
    handleInput val = do
      prog@(Program vals maybeMore) <- takeMVar progVar
      case maybeMore of
        Nothing ->
          putMVar progVar prog
        Just more -> do
          let Program mVals mMore = more val
          putMVar progVar $ Program (vals ++ mVals) mMore
          consumeOutput
  sink <- runBackend backend handleInput
  writeMVar sinkVar (Just sink)
  sinkInit sink
  forkIO $ do
    threadDelay 300000
    consumeOutput
  case sinkMainLoop sink of
    Nothing ->
      doWhile $ do
        threadDelay 200000 -- 0.2 sec
        readMVar resumeVar
    Just mainloop -> mainloop