{- | Internals for 'FRP.Rhine.Gloss'.
You probably won't need this module.
-}
{-# LANGUAGE Arrows #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}

module FRP.Rhine.Gloss.Internals where

-- base
import qualified Control.Category as Category
import Data.Functor.Identity (Identity)

-- dunai
import Control.Monad.Trans.MSF.Reader (readerS, runReaderS)

-- gloss
import Graphics.Gloss.Interface.Pure.Game

-- rhine
import FRP.Rhine hiding (readerS, runReaderS)


-- * Clocks

-- | The error message that gets thrown when you try to start a pure @rhine-gloss@ app with 'flow'.
errMsg :: String
errMsg = unwords
  [ "You cannot start pure rhine-gloss apps with FRP.Rhine.flow,"
  , "since gloss has its own main loop."
  , "Use FRP.Rhine.Gloss.flowGloss instead."
  ]

-- | The clock that ticks whenever a @gloss@ event occurs.
data GlossEventClock = GlossEventClock

instance Clock m GlossEventClock where
  type Time GlossEventClock = ()
  type Tag  GlossEventClock = Event
  initClock _ = error errMsg

-- | The clock that ticks for every @gloss@ simulation step,
--   but only shows the time /differences/ in the tag.
--   Usually, you don't need this clock, but rather 'GlossSimulationClock'.
data GlossSimulationClock_ = GlossSimulationClock_

instance Clock m GlossSimulationClock_ where
  type Time GlossSimulationClock_ = ()
  type Tag  GlossSimulationClock_ = Float
  initClock _ = error errMsg

-- | The clock that ticks for every @gloss@ simulation step.
--   Use 'withProperSimClock' to transform to 'GlossSimulationClock_'.
data GlossSimulationClock = GlossSimulationClock

instance Clock m GlossSimulationClock where
  type Time GlossSimulationClock = Float
  type Tag  GlossSimulationClock = ()
  initClock _ = error errMsg

-- | To use all features of the 'ClSF' framework,
--   write your synchronous stream function on the 'GlossSimulationClock'
--   and then use this function to transform it.
withProperSimClock
  :: Monad m
  => ClSF m GlossSimulationClock  a b
  -> ClSF m GlossSimulationClock_ a b
withProperSimClock clsf = readerS
  $ (intermingle *** Category.id) >>> runReaderS clsf
  where
    intermingle :: Monad m => MSF m (TimeInfo GlossSimulationClock_) (TimeInfo GlossSimulationClock)
    intermingle = proc TimeInfo {tag} -> do
      let sinceLast = tag
      absolute <- sumS -< sinceLast
      let sinceInit = absolute
      returnA          -< TimeInfo { tag = (), .. }

-- | The clock that ticks for every @gloss@ graphics output.
data GlossGraphicsClock = GlossGraphicsClock

instance Clock m GlossGraphicsClock where
  type Time GlossGraphicsClock = ()
  type Tag  GlossGraphicsClock = ()
  initClock _ = error errMsg

-- | A schedule you can't actually use, for internal purposes.
glossSchedule :: Schedule Identity (SelectClock GlossEventClock a) GlossSimulationClock_
glossSchedule = error errMsg