module Graphics.LambdaCube.RenderSystem.GL.Utils where

import Data.Bits
import Data.Char
import qualified Data.Set as Set
import Foreign
import Foreign.C.String

import Graphics.Rendering.OpenGL.Raw.Core31
import qualified Graphics.Rendering.OpenGL.Raw.ARB.Compatibility as Compat
import qualified Graphics.Rendering.OpenGL.Raw.ARB as ARB
import qualified Graphics.Rendering.OpenGL.Raw.EXT as EXT

import Graphics.LambdaCube.BlendMode
import Graphics.LambdaCube.Common
import Graphics.LambdaCube.HardwareBuffer
import Graphics.LambdaCube.HardwareVertexBuffer
import Graphics.LambdaCube.PixelFormat
import Graphics.LambdaCube.RenderSystemCapabilities
import Graphics.LambdaCube.Texture
import Graphics.LambdaCube.TextureUnitState

{-# INLINE peek4 #-}
peek4 :: Storable a => (a -> a -> a -> a -> b) -> Ptr a -> IO b
peek4 f = peek4M $ \x y z w -> return (f x y z w)

{-# INLINE peek4M #-}
peek4M :: Storable a => (a -> a -> a -> a -> IO b) -> Ptr a -> IO b
peek4M f ptr = do
   x <- peekElemOff ptr 0
   y <- peekElemOff ptr 1
   z <- peekElemOff ptr 2
   w <- peekElemOff ptr 3
   f x y z w

getFloat :: GLenum -> IO GLfloat
getFloat n = alloca $ \i -> do
    glGetFloatv n i
    peek i

getInteger :: GLenum -> IO GLint
getInteger n = alloca $ \i -> do
    glGetIntegerv n i
    peek i

getBoolean :: GLenum -> IO GLboolean
getBoolean n = alloca $ \i -> do
    glGetBooleanv n i
    peek i

getBoolean4 :: GLenum -> IO (GLboolean, GLboolean, GLboolean, GLboolean)
getBoolean4 n = allocaArray 4 $ \buf -> do
   glGetBooleanv n buf
   peek4 (,,,) buf

getInteger4 :: GLenum -> IO (GLint, GLint, GLint, GLint)
getInteger4 n = allocaArray 4 $ \buf -> do
   glGetIntegerv n buf
   peek4 (,,,) buf

getString :: GLenum -> IO String
getString n = glGetString n >>= maybeNullPtr (return "") (peekCString . castPtr)
  where
    maybeNullPtr :: b -> (Ptr a -> b) -> Ptr a -> b
    maybeNullPtr n f ptr | ptr == nullPtr = n
                         | otherwise      = f ptr

getGLExtensions :: IO [String]
getGLExtensions = fmap words $ getString gl_EXTENSIONS

getGLVersion :: IO (Int, Int)
getGLVersion = fmap parse $ getString gl_VERSION
  where
    defaultVersion = (-1, -1)
    parse str = case span isDigit str of
                    (major@(_:_), '.':rest) ->
                        case span isDigit rest of
                            (minor@(_:_), _) -> (read major, read minor)
                            _ -> defaultVersion
                    _ -> defaultVersion

getGLUsage :: Usage -> GLenum
getGLUsage usage = case usage of
    HBU_STATIC                            -> gl_STATIC_DRAW
    HBU_STATIC_WRITE_ONLY                 -> gl_STATIC_DRAW
    HBU_DYNAMIC                           -> gl_DYNAMIC_DRAW
    HBU_DYNAMIC_WRITE_ONLY                -> gl_DYNAMIC_DRAW
    HBU_DYNAMIC_WRITE_ONLY_DISCARDABLE    -> gl_STREAM_DRAW
    _                                     -> gl_DYNAMIC_DRAW

getGLType :: VertexElementType -> GLenum
getGLType t = case t of
    VET_FLOAT1        -> gl_FLOAT
    VET_FLOAT2        -> gl_FLOAT
    VET_FLOAT3        -> gl_FLOAT
    VET_FLOAT4        -> gl_FLOAT
    VET_SHORT1        -> gl_SHORT
    VET_SHORT2        -> gl_SHORT
    VET_SHORT3        -> gl_SHORT
    VET_SHORT4        -> gl_SHORT
    VET_COLOUR_ABGR   -> gl_UNSIGNED_BYTE
    VET_COLOUR_ARGB   -> gl_UNSIGNED_BYTE
    VET_UBYTE4        -> gl_UNSIGNED_BYTE

getGLTextureTarget :: TextureType -> GLenum
getGLTextureTarget textureType = case textureType of
    TEX_TYPE_1D       -> gl_TEXTURE_1D
    TEX_TYPE_2D       -> gl_TEXTURE_2D
    TEX_TYPE_3D       -> gl_TEXTURE_3D
    TEX_TYPE_CUBE_MAP -> gl_TEXTURE_CUBE_MAP

optionalPO2 :: RenderSystemCapabilities -> Int -> Int
optionalPO2 rcaps value = case Set.member RSC_NON_POWER_OF_2_TEXTURES (rscCapabilities rcaps) of
    True  -> value
    False -> 2 ^ (1 + log2 (value-1))
  where
    log2 :: Int -> Int
    log2 n = case n of
      0 -> -1
      _ -> 1 + log2 (shiftR n 1)

getMaxMipmaps :: Int -> Int -> Int -> PixelFormat -> GLint
getMaxMipmaps width height depth _format = maximum [f width, f height, f depth]
  where
    bits :: Double -> Double
    bits = logBase 2
    f x = floor $ bits $ fromIntegral x

-- TODO
getNativeFormat :: TextureType -> PixelFormat -> Bool -> PixelFormat
getNativeFormat _ttype _format _isTarget = PF_R8G8B8

getClosestGLInternalFormat :: PixelFormat -> Bool -> GLint
getClosestGLInternalFormat mFormat hwGamma = fromIntegral $ case mFormat of
    PF_L8             -> Compat.gl_LUMINANCE8
    PF_L16            -> Compat.gl_LUMINANCE16
    PF_A8             -> Compat.gl_ALPHA8
    PF_A4L4           -> Compat.gl_LUMINANCE4_ALPHA4
    PF_BYTE_LA        -> Compat.gl_LUMINANCE8_ALPHA8
    PF_R3G3B2         -> gl_R3_G3_B2
    PF_A1R5G5B5       -> gl_RGB5_A1
    PF_R5G6B5         -> gl_RGB5
    PF_B5G6R5         -> gl_RGB5
    PF_A4R4G4B4       -> gl_RGBA4
    PF_R8G8B8         -> if hwGamma then gl_SRGB8 else gl_RGB8
    PF_B8G8R8         -> if hwGamma then gl_SRGB8 else gl_RGB8
    PF_X8B8G8R8       -> if hwGamma then gl_SRGB8 else gl_RGB8
    PF_X8R8G8B8       -> if hwGamma then gl_SRGB8 else gl_RGB8
    PF_A8R8G8B8       -> if hwGamma then gl_SRGB8_ALPHA8 else gl_RGBA8
    PF_B8G8R8A8       -> if hwGamma then gl_SRGB8_ALPHA8 else gl_RGBA8
    PF_A2R10G10B10    -> gl_RGB10_A2
    PF_A2B10G10R10    -> gl_RGB10_A2
    PF_FLOAT16_R      -> ARB.gl_LUMINANCE16F
    PF_FLOAT16_RGB    -> ARB.gl_RGB16F
    PF_FLOAT16_GR     -> ARB.gl_LUMINANCE_ALPHA16F
    PF_FLOAT16_RGBA   -> ARB.gl_RGBA16F
    PF_FLOAT32_R      -> ARB.gl_LUMINANCE32F
    PF_FLOAT32_GR     -> ARB.gl_LUMINANCE_ALPHA32F
    PF_FLOAT32_RGB    -> ARB.gl_RGB32F
    PF_FLOAT32_RGBA   -> ARB.gl_RGBA32F
    PF_SHORT_RGBA     -> gl_RGBA16
    PF_SHORT_RGB      -> gl_RGB16
    PF_SHORT_GR       -> Compat.gl_LUMINANCE16_ALPHA16
    PF_DXT1           -> if hwGamma then EXT.gl_COMPRESSED_SRGB_ALPHA_S3TC_DXT1 else EXT.gl_COMPRESSED_RGBA_S3TC_DXT1
    PF_DXT3           -> if hwGamma then EXT.gl_COMPRESSED_SRGB_ALPHA_S3TC_DXT3 else EXT.gl_COMPRESSED_RGBA_S3TC_DXT3
    PF_DXT5           -> if hwGamma then EXT.gl_COMPRESSED_SRGB_ALPHA_S3TC_DXT5 else EXT.gl_COMPRESSED_RGBA_S3TC_DXT5
    _                 -> if hwGamma then gl_SRGB8 else gl_RGBA8

{-
getClosestPixelFormat :: GLenum -> PixelFormat
getClosestPixelFormat fmt = case fmt of
    gl_LUMINANCE8                           -> PF_L8
    gl_LUMINANCE16                          -> PF_L16
    gl_ALPHA8                               -> PF_A8
    gl_LUMINANCE4_ALPHA4                    -> PF_BYTE_LA
    gl_LUMINANCE8_ALPHA8                    -> PF_BYTE_LA
    gl_R3_G3_B2                             -> PF_R3G3B2
    gl_RGB5_A1                              -> PF_A1R5G5B5
    gl_RGB5                                 -> PF_R5G6B5
    gl_RGBA4                                -> PF_A4R4G4B4
    gl_RGB8                                 -> PF_X8R8G8B8
    gl_SRGB8                                -> PF_X8R8G8B8
    gl_RGBA8                                -> PF_A8R8G8B8
    gl_SRGB8_ALPHA8                         -> PF_A8R8G8B8
    gl_RGB10_A2                             -> PF_A2R10G10B10
    gl_RGBA16                               -> PF_SHORT_RGBA
    gl_RGB16                                -> PF_SHORT_RGB
    gl_LUMINANCE16_ALPHA16                  -> PF_SHORT_GR
    gl_LUMINANCE_FLOAT16_ATI                -> PF_FLOAT16_R
    gl_LUMINANCE_ALPHA_FLOAT16_ATI          -> PF_FLOAT16_GR
    gl_LUMINANCE_ALPHA_FLOAT32_ATI          -> PF_FLOAT32_GR
    gl_LUMINANCE_FLOAT32_ATI                -> PF_FLOAT32_R
    gl_RGB_FLOAT16_ATI                      -> PF_FLOAT16_RGB
    gl_RGBA_FLOAT16_ATI                     -> PF_FLOAT16_RGBA
    gl_RGB_FLOAT32_ATI                      -> PF_FLOAT32_RGB
    gl_RGBA_FLOAT32_ATI                     -> PF_FLOAT32_RGBA
    gl_COMPRESSED_RGB_S3TC_DXT1_EXT         -> PF_DXT1
    gl_COMPRESSED_RGBA_S3TC_DXT1_EXT        -> PF_DXT1
    gl_COMPRESSED_SRGB_S3TC_DXT1_EXT        -> PF_DXT1
    gl_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT  -> PF_DXT1
    gl_COMPRESSED_RGBA_S3TC_DXT3_EXT        -> PF_DXT3
    gl_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT  -> PF_DXT3
    gl_COMPRESSED_RGBA_S3TC_DXT5_EXT        -> PF_DXT5
    gl_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT  -> PF_DXT5
    _                                       -> PF_A8R8G8B8
-}

getBlendMode :: SceneBlendFactor -> GLenum
getBlendMode blend = case blend of
    SBF_ONE                       -> gl_ONE
    SBF_ZERO                      -> gl_ZERO
    SBF_DEST_COLOUR               -> gl_DST_COLOR
    SBF_SOURCE_COLOUR             -> gl_SRC_COLOR
    SBF_ONE_MINUS_DEST_COLOUR     -> gl_ONE_MINUS_DST_COLOR
    SBF_ONE_MINUS_SOURCE_COLOUR   -> gl_ONE_MINUS_SRC_COLOR
    SBF_DEST_ALPHA                -> gl_DST_ALPHA
    SBF_SOURCE_ALPHA              -> gl_SRC_ALPHA
    SBF_ONE_MINUS_DEST_ALPHA      -> gl_ONE_MINUS_DST_ALPHA
    SBF_ONE_MINUS_SOURCE_ALPHA    -> gl_ONE_MINUS_SRC_ALPHA

getBlendEquation :: SceneBlendOperation -> GLenum
getBlendEquation op = case op of
    SBO_ADD               -> gl_FUNC_ADD
    SBO_SUBTRACT          -> gl_FUNC_SUBTRACT
    SBO_REVERSE_SUBTRACT  -> gl_FUNC_REVERSE_SUBTRACT
    SBO_MIN               -> gl_MIN
    SBO_MAX               -> gl_MAX

getTextureAddressingMode :: TextureAddressingMode -> GLenum
getTextureAddressingMode tam = case tam of
    TAM_WRAP      -> gl_REPEAT
    TAM_MIRROR    -> gl_MIRRORED_REPEAT
    TAM_CLAMP     -> gl_CLAMP_TO_EDGE
    TAM_BORDER    -> gl_CLAMP_TO_BORDER

getLayerBlendSource :: LayerBlendSource -> GLenum
getLayerBlendSource src = case src of
    LBS_CURRENT   -> Compat.gl_PREVIOUS
    LBS_TEXTURE   -> gl_TEXTURE
    LBS_MANUAL    -> Compat.gl_CONSTANT
    LBS_DIFFUSE   -> Compat.gl_PRIMARY_COLOR
    LBS_SPECULAR  -> Compat.gl_PRIMARY_COLOR

getTextureCombineFunction :: Bool -> LayerBlendOperationEx -> GLenum
getTextureCombineFunction hasDot3 op = case op of
    LBX_SOURCE1               -> gl_REPLACE
    LBX_SOURCE2               -> gl_REPLACE
    LBX_MODULATE              -> Compat.gl_MODULATE
    LBX_MODULATE_X2           -> Compat.gl_MODULATE
    LBX_MODULATE_X4           -> Compat.gl_MODULATE
    LBX_ADD                   -> Compat.gl_ADD
    LBX_ADD_SIGNED            -> Compat.gl_ADD_SIGNED
    LBX_ADD_SMOOTH            -> Compat.gl_INTERPOLATE
    LBX_SUBTRACT              -> Compat.gl_SUBTRACT
    LBX_BLEND_DIFFUSE_COLOUR  -> Compat.gl_INTERPOLATE
    LBX_BLEND_DIFFUSE_ALPHA   -> Compat.gl_INTERPOLATE
    LBX_BLEND_TEXTURE_ALPHA   -> Compat.gl_INTERPOLATE
    LBX_BLEND_CURRENT_ALPHA   -> Compat.gl_INTERPOLATE
    LBX_BLEND_MANUAL          -> Compat.gl_INTERPOLATE
    LBX_DOTPRODUCT            -> if hasDot3 then Compat.gl_DOT3_RGB else Compat.gl_MODULATE

convertCompareFunction :: CompareFunction -> GLenum
convertCompareFunction f = case f of
    CMPF_ALWAYS_FAIL      -> gl_NEVER
    CMPF_ALWAYS_PASS      -> gl_ALWAYS
    CMPF_LESS             -> gl_LESS
    CMPF_LESS_EQUAL       -> gl_LEQUAL
    CMPF_EQUAL            -> gl_EQUAL
    CMPF_NOT_EQUAL        -> gl_NOTEQUAL
    CMPF_GREATER_EQUAL    -> gl_GEQUAL
    CMPF_GREATER          -> gl_GREATER