{-# LANGUAGE GADTs, DataKinds, KindSignatures, TypeFamilies,
             ScopedTypeVariables, UndecidableInstances #-}

module Graphics.Rendering.Ombra.OutBuffer.Types where

import Data.Proxy
import Graphics.Rendering.Ombra.Shader.Types
import Graphics.Rendering.Ombra.Shader.Language.Types
import Graphics.Rendering.Ombra.Texture.Types

-- | A GPU object that can be used to retrieve data from a 'GBuffer'.
data GBufferSampler o = FragmentShaderOutput o => GBufferSampler [GSampler2D]

-- | A GPU object that can be used to retrieve data from a 'DepthBuffer'.
newtype DepthBufferSampler = DepthBufferSampler GSampler2D

data OutDepthBuffer

data OutBufferInfo o where
        EmptyFloatGBuffer               :: FragmentShaderOutput o
                                        => TextureParameters
                                        -> OutBufferInfo o

        EmptyByteGBuffer                :: FragmentShaderOutput o
                                        => TextureParameters
                                        -> OutBufferInfo o

        EmptyDepthBuffer                :: TextureParameters
                                        -> OutBufferInfo OutDepthBuffer

        EmptyDepthStencilBuffer         :: TextureParameters
                                        -> OutBufferInfo OutDepthBuffer

data OutBuffer o where
        TextureFloatGBuffer             :: FragmentShaderOutput o
                                        => Int
                                        -> Int
                                        -> [LoadedTexture]
                                        -> OutBuffer o

        TextureByteGBuffer              :: FragmentShaderOutput o
                                        => Int
                                        -> Int
                                        -> [LoadedTexture]
                                        -> OutBuffer o

        TextureDepthBuffer              :: Int
                                        -> Int
                                        -> LoadedTexture
                                        -> OutBuffer OutDepthBuffer

        TextureDepthStencilBuffer       :: Int
                                        -> Int
                                        -> LoadedTexture
                                        -> OutBuffer OutDepthBuffer

-- | A 'GBuffer' and a 'DepthBuffer' of the same size.
data BufferPair o = BufferPair (GBuffer o) DepthBuffer

instance FragmentShaderOutput o => MultiShaderType (GBufferSampler o) where
        type ExprMST (GBufferSampler o) = [ExprMST GSampler2D]
        mapMST f (GBufferSampler x) = GBufferSampler $ map f x
        toExprMST (GBufferSampler x) = map toExprMST x
        fromExprMST = GBufferSampler . map fromExprMST

instance FragmentShaderOutput o => ShaderInput (GBufferSampler o) where
        buildMST f i = (GBufferSampler $ take n infiniteSamplers, i + n)
                where n = textureCount (Proxy :: Proxy o)
                      infiniteSamplers = map f [i ..]
        foldrMST f s (GBufferSampler x) = foldr f s x

instance FragmentShaderOutput o => Uniform (GBufferSampler o) where
        type CPUUniform (GBufferSampler o) = GBuffer o
        foldrUniform _ f s buf =
                foldr (\u s -> f (UniformTexture $ TextureLoaded u) s)
                      s (textures buf)

instance MultiShaderType DepthBufferSampler where
        type ExprMST DepthBufferSampler = ExprMST GSampler2D
        mapMST f (DepthBufferSampler x) = DepthBufferSampler $ f x
        toExprMST (DepthBufferSampler x) = toExprMST x
        fromExprMST = DepthBufferSampler . fromExprMST

instance ShaderInput DepthBufferSampler where
        buildMST f i = (DepthBufferSampler $ f i, i + 1)
        foldrMST f s (DepthBufferSampler x) = foldrMST f s x

instance Uniform DepthBufferSampler where
        type CPUUniform DepthBufferSampler = DepthBuffer
        foldrUniform _ f s buf =
                f (UniformTexture . TextureLoaded . head . textures $ buf) s

-- | A container that can be used to store the output of some drawing operation.
type GBuffer = OutBuffer
-- | A container for depth/stencil values.
type DepthBuffer = OutBuffer OutDepthBuffer

type GBufferInfo = OutBufferInfo
type DepthBufferInfo = OutBufferInfo OutDepthBuffer

textures :: OutBuffer o -> [LoadedTexture]
textures (TextureFloatGBuffer _ _ ts) = ts
textures (TextureByteGBuffer _ _ ts) = ts
textures (TextureDepthBuffer _ _ t) = [t]
textures (TextureDepthStencilBuffer _ _ t) = [t]

bufferSize :: OutBuffer o -> (Int, Int)
bufferSize (TextureFloatGBuffer w h _) = (w, h)
bufferSize (TextureByteGBuffer w h _) = (w, h)
bufferSize (TextureDepthBuffer w h _) = (w, h)
bufferSize (TextureDepthStencilBuffer w h _) = (w, h)