-----------------------------------------------------------------------------
-- |
-- Module      :  Control.FRPNow.Gloss
-- Copyright   :  (c) Atze van der Ploeg 2015
-- License     :  BSD-style
-- Maintainer  :  atzeus@gmail.org
-- Stability   :  provisional
-- Portability :  portable
-- 
-- This module provides interoperability of FRPNow and the gloss system.

module Control.FRPNow.Gloss(GEvent,Time,runNowGloss, runNowGlossPure, toMouseMoves, toMousePos, toKeysDown, filterMouseButtons) where

import Graphics.Gloss.Interface.IO.Game hiding (Event)
import Control.FRPNow
import Data.Sequence
import Control.Applicative
import qualified Data.Sequence as Seq
import Data.Maybe
import Data.IORef
import Debug.Trace
import GHC.Float
import qualified Data.Foldable as Fold
import Data.Set
import qualified Data.Set as Set
import qualified Graphics.Gloss.Interface.IO.Game as Gloss
import Debug.Trace

-- | Alias for 'Gloss.Event' to prevent name clash with 'Event'.
type GEvent = Gloss.Event
-- | The gloss type for time.
type Time = Float 

-- | Run a Now computation which produced a behavior of type Picture, and draw that on screen.
runNowGloss :: 
            Display  -- ^ Display mode.
         -> Color    -- ^ Background color.
         -> Int      -- ^ Maximum number of frames per second 
         -> (Behavior Time -> EvStream GEvent -> Now (Behavior Picture))  -- ^ A now computation giving the picture to be displayed on the screen, taking the behavior of time and the eventstream of gloss events.
         -> IO ()
runNowGloss disp bg fps m = 
  do scheduleRef <- newIORef Seq.empty
     callbackRef <- newIORef undefined
     pictureRef <- newIORef Blank
     initNow (schedule scheduleRef) (initM callbackRef pictureRef)
     (cbTime, cbgEv) <- readIORef callbackRef
     playIO disp bg fps ()
         (\_ -> readIORef pictureRef)
         (\ev _ -> cbgEv ev)
         (\deltaTime _ -> do cbTime deltaTime
                             rounds <- readIORef scheduleRef
                             writeIORef scheduleRef Seq.empty
                             mapM_ id (Fold.toList rounds)
                             return ()
         )
          
                 
    where 
  initM callbackRef pictureRef = 
     do (timeEvs,cbtime) <- callbackStream
        (gevEvs,cbgEv)   <- callbackStream
        sync $ writeIORef callbackRef (cbtime,cbgEv)
        clock <- sample $ foldEs (+) 0 timeEvs
        pict <- m clock gevEvs
        curPict <- sample pict
        sync $ writeIORef pictureRef curPict
        callIOStream (writeIORef pictureRef) (toChanges pict)
        return never

  schedule ref m = atomicModifyIORef ref (\s -> (s |> m, ())) 

-- | Like 'runNowGloss', but does not allow IO.
runNowGlossPure ::   
            Display  -- ^ Display mode.
         -> Color    -- ^ Background color.
         -> Int      -- ^ Maximum number of frames per second 
         -> (Behavior Time -> EvStream GEvent -> Behavior (Behavior Picture))  -- ^ A now computation giving the picture to be displayed on the screen, taking the behavior of time and the eventstream of gloss events.
         -> IO ()
runNowGlossPure disp bg fps b = runNowGloss disp bg fps (\t e -> sample $ b t e)

-- | Filter the mouse moves from an event stream of gloss events
toMouseMoves :: EvStream GEvent -> EvStream (Float,Float) 
toMouseMoves evs = filterMapEs getMouseMove evs
  where getMouseMove (EventMotion p) = Just p
        getMouseMove _               = Nothing

-- | Get a behavior of the mouse position from an event stream of gloss events
toMousePos :: EvStream GEvent -> Behavior (Behavior (Float, Float))
toMousePos evs = fromChanges (0,0) (toMouseMoves evs) 

-- | Get a behavior of the set of currently pressed keys from an event stream of gloss events
toKeysDown :: EvStream GEvent -> Behavior (Behavior (Set Key))
toKeysDown evs = foldEs updateSet Set.empty evs where
  updateSet :: Set Key -> GEvent -> Set Key
  updateSet s (EventKey k i _ _) = action i k s
      where action Up    = delete
            action Down  = insert
  updateSet s _ = s

filterMouseButtons :: Behavior (Set Key) -> Behavior (Set MouseButton)
filterMouseButtons b = 
     let isMouseButton (MouseButton _) = True
         isMouseButton _               = False
     in Set.map (\(MouseButton x) -> x) . Set.filter isMouseButton <$> b