{-|
  This module provides everything relating to input in the scope of spice.
-}
module FRP.Spice.Input ( module Rexport
                       , Sinks (..)
                       , Input (..)
                       , InputContainer (..)
                       , makeInputContainer
                       , makeMousePositionCallback
                       , makeKeyboardCallback
                       , makeMouseCallback
                       ) where

--------------------
-- Global Imports --
import Data.Map.Strict hiding (keys, map)
import Graphics.Rendering.OpenGL
import Graphics.UI.GLFW as GLFW
import FRP.Elerea.Param

-------------------
-- Local Imports --
import qualified FRP.Spice.Input.MousePosition as MousePosition
import qualified FRP.Spice.Input.Keyboard as Keyboard
import qualified FRP.Spice.Input.Mouse as Mouse
import FRP.Spice.Math.Vector
import FRP.Spice.Config

-----------------------
-- Rexported Imports --
import Data.Map.Strict  as Rexport ((!))
import Graphics.UI.GLFW as Rexport (Key (..), SpecialKey (..), MouseButton (..))

----------
-- Code --

{-|
  A wrapper around the sinks for the mouse position, the key states, and the
  mouse button states.
-}
data Sinks = Sinks { mousePositionSinks :: Vector Float -> IO ()
                   , keyboardSinks      :: Map Key (Bool -> IO ())
                   , mouseSinks         :: Map MouseButton (Bool -> IO ())
                   }

{-|
  A container for all of the states themselves. It is used as a @'Signal'@
  @'Input'@ in the @'InputContainer'@ (which is necessary to use it within
  Elerea's FRP network).
-}
data Input = Input { mousePosition :: Vector Float
                   , keyboard      :: Map Key Bool
                   , mouse         :: Map MouseButton Bool
                   }

{-|
  A container around @'Sinks'@ and @'Signal'@ @'Input'@ so that one needn't
  pass around a tuple.
-}
data InputContainer = InputContainer { getSinks :: Sinks
                                     , getInput :: Signal Input
                                     }

{-|
  Making an @'InputContainer'@ filled with all necessary externals.
-}
makeInputContainer :: IO InputContainer
makeInputContainer = do
  mousePositionExternals <- MousePosition.externals
  keyboardExternals      <- Keyboard.externals
  mouseExternals         <- Mouse.externals

  let sinks = Sinks { mousePositionSinks = MousePosition.sinks mousePositionExternals
                    , keyboardSinks      = Keyboard.sinks keyboardExternals
                    , mouseSinks         = Mouse.sinks mouseExternals
                    }

  let input = do mps <- fst mousePositionExternals
                 kbs <- Keyboard.signals keyboardExternals
                 ms  <- Mouse.signals mouseExternals

                 return Input { mousePosition = mps
                              , keyboard      = kbs
                              , mouse         = ms
                              }

  return InputContainer { getSinks = sinks
                        , getInput = input
                        }

{-|
  Creating a callback to update the mouse position's state.
-}
makeMousePositionCallback :: WindowConfig -> InputContainer -> MousePosCallback
makeMousePositionCallback wc ic (Position x y) =
  mousePositionSinks (getSinks ic) $
    Vector (  fromIntegral x  / ((fromIntegral $ getWindowWidth  wc) / 2) - 1)
           ((-fromIntegral y) / ((fromIntegral $ getWindowHeight wc) / 2) + 1)

{-|
  Creating a callback to update the keyboard's states.
-}
makeKeyboardCallback :: InputContainer -> KeyCallback
makeKeyboardCallback ic key state =
  keyboardSinks (getSinks ic) ! key $ case state of
    Press   -> True
    Release -> False

{-|
  Creating a callback to update the mouse buttons' states.
-}
makeMouseCallback :: InputContainer -> MouseButtonCallback
makeMouseCallback ic button state =
  mouseSinks (getSinks ic) ! button $ case state of
    Press   -> True
    Release -> False