content: {-# LANGUAGE TemplateHaskell   #-}
  {-# LANGUAGE RankNTypes #-}
  
  module Gfx.Engine where
  
  import           Control.DeepSeq
  import           Control.Monad.State.Strict
  import qualified Data.Map                      as M
  import           Graphics.Rendering.OpenGL      ( BlendingFactor
                                                    ( One
                                                    , OneMinusSrcAlpha
                                                    , SrcAlpha
                                                    )
                                                  , GLfloat
                                                  )
  import           Lens.Simple                    ( (^.)
                                                  , makeLenses
                                                  , over
                                                  , set
                                                  )
  import           Linear.Matrix                  ( (!*!)
                                                  , M44
                                                  , identity
                                                  )
  import           Linear.V3                      ( V3(..) )
  
  import           Gfx.Geometries                 ( Geometries
                                                  , createAllGeometries
                                                  )
  import           Gfx.Materials                  ( MaterialLibrary
                                                  , MaterialsConfig(..)
                                                  )
  import           Gfx.Matrices                   ( projectionMat
                                                  , viewMat
                                                  )
  import           Gfx.PostProcessing             ( AnimationStyle(NormalStyle)
                                                  , PostProcessingConfig
                                                  , filterVars
                                                  )
  import           Gfx.TextRendering              ( TextRenderer )
  import           Gfx.Textures                   ( TextureLibrary )
  import           Gfx.Types                      ( Colour(..) )
  import           Language.Ast                   ( Value )
  import qualified Util.Setting                  as S
  import qualified Util.SettingMap               as SM
  
  import           Configuration                  ( ImprovizConfig )
  import qualified Configuration                 as C
  import qualified Configuration.Screen          as CS
  
  import           Util                           ( (/.) )
  
  data GFXFillStyling
    = GFXFillColour !Colour
    | GFXNoFill
    deriving (Eq, Show)
  instance NFData GFXFillStyling where rnf = rwhnf
  
  data GFXTextureStyling = GFXTextureStyling String !Int
    deriving (Eq, Show)
  instance NFData GFXTextureStyling where rnf = rwhnf
  
  data GFXStrokeStyling
    = GFXStrokeColour !Colour
    | GFXNoStroke
    deriving (Eq, Show)
  instance NFData GFXStrokeStyling where rnf = rwhnf
  
  data SavableState = SavableState
    { _savedMatrixStack   :: [M44 GLfloat]
    , _savedFillStyles    :: !GFXFillStyling
    , _savedStrokeStyles  :: !GFXStrokeStyling
    , _savedTextureStyles :: !GFXTextureStyling
    , _savedMaterials     :: String
    , _savedMaterialVars  :: !(M.Map String Value)
    }
    deriving Show
  instance NFData SavableState where rnf s = rnf (rwhnf s, _savedMatrixStack s)
  
  makeLenses ''SavableState
  
  data GfxEngine = GfxEngine
    { _fillStyle        :: S.Setting GFXFillStyling
    , _strokeStyle      :: S.Setting GFXStrokeStyling
    , _textureStyle     :: S.Setting GFXTextureStyling
    , _material         :: S.Setting String
    , _geometryBuffers  :: Geometries
    , _textureLibrary   :: TextureLibrary
    , _materialLibrary  :: MaterialLibrary
    , _materialVars     :: SM.SettingMap String Value
    , _viewMatrix       :: !(M44 GLfloat)
    , _projectionMatrix :: !(M44 GLfloat)
    , _aspectRatio      :: Float
    , _postFX           :: PostProcessingConfig
    , _postFXVars       :: SM.SettingMap String Value
    , _textRenderer     :: TextRenderer
    , _matrixStack      :: [M44 GLfloat]
    , _scopeStack       :: [SavableState]
    , _animationStyle   :: S.Setting AnimationStyle
    , _backgroundColor  :: S.Setting Colour
    , _depthChecking    :: S.Setting Bool
    , _blendFunction
        :: S.Setting
          ((BlendingFactor, BlendingFactor), (BlendingFactor, BlendingFactor))
    }
    deriving (Show)
  instance NFData GfxEngine where
    rnf g = rnf ((_fillStyle g, _strokeStyle g, _textureStyle g, _material g), (_matrixStack g, _scopeStack g))
  
  makeLenses '' GfxEngine
  
  type GraphicsEngine v = StateT GfxEngine IO v
  
  createGfxEngine
    :: ImprovizConfig
    -> Int
    -> Int
    -> PostProcessingConfig
    -> TextRenderer
    -> TextureLibrary
    -> MaterialsConfig
    -> IO GfxEngine
  createGfxEngine config width height pprocess trender textLib matCfg =
    let
      ratio      = width /. height
      front      = config ^. C.screen . CS.front
      back       = config ^. C.screen . CS.back
      projection = projectionMat front back (pi / 4) ratio
      view       = viewMat (V3 0 0 10) (V3 0 0 0) (V3 0 1 0)
    in
      do
        gbos <- createAllGeometries (config ^. C.geometryDirectories)
        return GfxEngine
          { _fillStyle        = S.create $ GFXFillColour $ Colour 1 1 1 1
          , _strokeStyle      = S.create $ GFXStrokeColour $ Colour 0 0 0 1
          , _textureStyle     = S.create $ GFXTextureStyling "" 0
          , _material         = S.create "basic"
          , _geometryBuffers  = gbos
          , _textureLibrary   = textLib
          , _materialLibrary  = materialsLibrary matCfg
          , _materialVars     = SM.create $ varDefaults matCfg
          , _viewMatrix       = view
          , _projectionMatrix = projection
          , _aspectRatio      = ratio
          , _postFX           = pprocess
          , _postFXVars       = pprocess ^. filterVars
          , _textRenderer     = trender
          , _matrixStack      = [identity]
          , _scopeStack       = []
          , _animationStyle   = S.create NormalStyle
          , _backgroundColor  = S.create (Colour 1 1 1 1)
          , _depthChecking    = S.create True
          , _blendFunction = S.create ((SrcAlpha, OneMinusSrcAlpha), (One, One))
          }
  
  resizeGfxEngine
    :: ImprovizConfig
    -> Int
    -> Int
    -> PostProcessingConfig
    -> TextRenderer
    -> GfxEngine
    -> GfxEngine
  resizeGfxEngine config newWidth newHeight newPP newTR =
    let front    = config ^. C.screen . CS.front
        back     = config ^. C.screen . CS.back
        newRatio = newWidth /. newHeight
        newProj  = projectionMat front back (pi / 4) newRatio
    in  set projectionMatrix newProj
          . set postFX       newPP
          . set textRenderer newTR
          . set aspectRatio  newRatio
  
  resetGfxEngine :: GfxEngine -> GfxEngine
  resetGfxEngine ge = ge
    { _fillStyle       = S.reset (_fillStyle ge)
    , _strokeStyle     = S.reset (_strokeStyle ge)
    , _material        = S.reset (_material ge)
    , _materialVars    = SM.reset (_materialVars ge)
    , _postFXVars      = SM.reset (_postFXVars ge)
    , _matrixStack     = [identity]
    , _scopeStack      = []
    , _animationStyle  = S.resetIfUnused (_animationStyle ge)
    , _backgroundColor = S.resetIfUnused (_backgroundColor ge)
    , _depthChecking   = S.resetIfUnused (_depthChecking ge)
    , _blendFunction   = S.resetIfUnused (_blendFunction ge)
    }
  
  pushMatrix :: M44 Float -> GfxEngine -> GfxEngine
  pushMatrix mat = over matrixStack (\stack -> (head stack !*! mat) : stack)
  
  popMatrix :: GfxEngine -> GfxEngine
  popMatrix = over matrixStack tail
  
  multMatrix :: M44 Float -> GfxEngine -> GfxEngine
  multMatrix mat = over matrixStack (\stack -> (head stack !*! mat) : tail stack)
  