{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE PolyKinds #-}

-----------------------------------------------------------------------------
-- |
-- Copyright   : (C) 2015, 2016 Dimitri Sabadie
-- License     : BSD3
--
-- Maintainer  : Dimitri Sabadie <dimitri.sabadie@gmail.com>
-- Stability   : experimental
-- Portability : portable
-----------------------------------------------------------------------------

module Graphics.Luminance.Core.Region where

import Control.Monad.IO.Class ( MonadIO(..) )
import Control.Monad.Trans ( MonadTrans(..) )
import Control.Some ( Some(..) )
import Data.Bits
import Foreign.Ptr ( nullPtr )
import Graphics.GL
import Graphics.Luminance.Core.Blending ( setBlending )
import Graphics.Luminance.Core.Debug
import Graphics.Luminance.Core.Framebuffer ( Framebuffer(..) )
import Graphics.Luminance.Core.Geometry ( Geometry(..), VertexArray(..) )
import Graphics.Luminance.Core.Shader.Program ( Program(..), )
import Graphics.Luminance.Core.RenderCmd ( RenderCmd(..) )

-- |A 'Region' is a monad transformer used to create relationships between two monadic layers
-- and ensure GPU safety.
newtype Region r m a = Region { runRegion :: m a } deriving (Applicative,Functor,Monad,MonadIO)

instance MonadTrans (Region r) where
  lift = Region

-- |The /GPU/ main 'Region'. This 'Region' is the highest and more general you can find. You’ll need
-- to enter it if you want to enter any /GPU/ specific regions.
gpuRegion :: Region () m a -> m a
gpuRegion = runRegion

-- |The 'Framebuffer' 'Region'. This 'Region' binds a 'Framebuffer' for all children regions.
newFrame :: (MonadIO m) => Framebuffer rw c d -> Region Framebuffer m a -> Region () m a
newFrame fb fbRegion = do
  liftIO . debugGL $ glBindFramebuffer GL_DRAW_FRAMEBUFFER (fromIntegral $ framebufferID fb)
  liftIO . debugGL $ glClear $ GL_DEPTH_BUFFER_BIT .|. GL_COLOR_BUFFER_BIT
  lift (runRegion fbRegion)

-- |The 'Program' 'Region'. This 'Region' binds a 'Program' for all children regions.
newShading :: (MonadIO m) => Some Program -> Region Program m a -> Region Framebuffer m a
newShading (Some prog) progRegion = do
  liftIO . debugGL $ glUseProgram (programID prog)
  lift (runRegion progRegion)

-- |Draw the 'Geometry' held by a 'RenderCmd'.
drawGeometry :: (MonadIO m) => RenderCmd rw c d Geometry -> Region Program m ()
drawGeometry (RenderCmd blending depthTest geometry) = do
  setBlending blending
  (if depthTest then glEnable else glDisable) GL_DEPTH_TEST
  case geometry of
    DirectGeometry (VertexArray vid mode vbNb) -> do
      debugGL $ glBindVertexArray vid
      debugGL $ glDrawArrays mode 0 vbNb
    IndexedGeometry (VertexArray vid mode ixNb) -> do
      debugGL $ glBindVertexArray vid
      debugGL $ glDrawElements mode ixNb GL_UNSIGNED_INT nullPtr