{-# LANGUAGE TupleSections #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE Arrows #-}

module LiveCoding.Gloss
  ( module X
  , module LiveCoding.Gloss
  ) where

-- base
import Control.Concurrent
import Control.Monad (when)
import Data.IORef
import System.Exit (exitSuccess)

-- transformers
import Control.Arrow (returnA)
import Control.Monad.Trans.Writer
import Control.Monad.Trans.State.Strict (StateT)

-- gloss
import Graphics.Gloss as X
import Graphics.Gloss.Interface.IO.Game as X

-- essence-of-live-coding
import LiveCoding

-- essence-of-live-coding-gloss
import LiveCoding.Gloss.Debugger as X
import LiveCoding.Gloss.PictureM as X

-- | In a 'Handle', store a separate thread where the gloss main loop is executed,
--   and several concurrent variables to communicate with it.
data GlossHandle = GlossHandle
  { glossThread :: ThreadId
  , glossVars   :: GlossVars
  }

-- | The concurrent variables needed to communicate with the gloss thread.
data GlossVars = GlossVars
  { glossEventsRef :: IORef [Event]
    -- ^ Stores all 'Event's that arrived since the last tick
  , glossPicRef    :: IORef Picture
    -- ^ Stores the next 'Picture' to be painted
  , glossDTimeVar  :: MVar Float
    -- ^ Stores the time passed since the last tick
  , glossExitRef   :: IORef Bool
    -- ^ Write 'True' here to stop the gloss thread
  }

-- | Collect all settings that the @gloss@ backend requires.
--   Taken from @rhine-gloss@.
data GlossSettings = GlossSettings
  { displaySetting  :: Display      -- ^ Display mode (e.g. 'InWindow' or 'FullScreen').
  , backgroundColor :: Color        -- ^ Background color.
  , stepsPerSecond  :: Int          -- ^ Number of simulation steps per second of real time.
  }

defaultSettings :: GlossSettings
defaultSettings = GlossSettings
  { displaySetting  = InWindow "Essence of live coding" (600, 800) (20, 20)
  , backgroundColor = black
  , stepsPerSecond  = 30
  }

-- | Will create a handle for communication with the gloss thread,
--   and start gloss.
glossHandle :: GlossSettings -> Handle IO GlossHandle
glossHandle GlossSettings { .. } = Handle
  { create = do
      glossEventsRef <- newIORef []
      glossDTimeVar <- newEmptyMVar
      glossPicRef <- newIORef blank
      glossExitRef <- newIORef False
      let glossVars = GlossVars { .. }
      glossThread <- forkIO
        $ playIO displaySetting backgroundColor stepsPerSecond glossVars getPicture handleEvent stepGloss
      return GlossHandle { .. }
  , destroy = \GlossHandle { glossVars = GlossVars { .. }, .. } -> writeIORef glossExitRef True
  }

getPicture :: GlossVars -> IO Picture
getPicture GlossVars { .. } = readIORef glossPicRef

handleEvent :: Event -> GlossVars -> IO GlossVars
handleEvent event vars@GlossVars { .. } = do
  modifyIORef glossEventsRef (event :)
  return vars

stepGloss :: Float -> GlossVars -> IO GlossVars
stepGloss dTime vars@GlossVars { .. } = do
  threadDelay $ round $ dTime * 1000
  putMVar glossDTimeVar dTime
  exitNow <- readIORef glossExitRef
  when exitNow exitSuccess
  return vars

-- | Given a cell in the gloss monad 'PictureM',
--   start the gloss backend and connect the cell to it.
--   This introduces 'Handle's, which need to be taken care of by calling 'runHandlingState'
--   or a similar function.
glossWrapC :: GlossSettings -> Cell PictureM a b -> Cell (StateT (HandlingState IO) IO) a b
glossWrapC glossSettings cell = proc a -> do
  GlossHandle { .. } <- handling $ glossHandle glossSettings -< ()
  liftCell pump -< (glossVars, a)
  where
    pump = proc (GlossVars { .. }, a) -> do
      _      <- arrM takeMVar                        -< glossDTimeVar
      events <- arrM $ flip atomicModifyIORef ([], ) -< glossEventsRef
      (picture, b) <- runPictureT cell               -< (events, a)
      arrM (uncurry writeIORef)                      -< (glossPicRef, picture)
      arrM threadDelay                               -< 10000 -- TODO Tweak for better performance
      returnA                                        -< b