module Cube where

import Graphics.GL
import Foreign
import Linear
import Data.Foldable
import Shader
import Control.Monad.Trans

newtype VertexArrayObject = VertexArrayObject   { unVertexArrayObject   :: GLuint }

data Cube = Cube
        { cubeVAO        :: VertexArrayObject
        , cubeShader     :: GLProgram
        , cubeIndexCount :: GLsizei
        , cubeUniformMVP :: UniformLocation
        }

----------------------------------------------------------
-- Make Cube
----------------------------------------------------------

renderCube :: MonadIO m => Cube -> M44 GLfloat -> m ()
renderCube cube mvp = do

    useProgram (cubeShader cube)

    let mvpUniformLoc = fromIntegral (unUniformLocation (cubeUniformMVP cube))

    liftIO $ withArray (concatMap toList (transpose mvp)) (\mvpPointer ->
        glUniformMatrix4fv mvpUniformLoc 1 GL_FALSE mvpPointer)

    glBindVertexArray (unVertexArrayObject (cubeVAO cube))

    glDrawElements GL_TRIANGLES (cubeIndexCount cube) GL_UNSIGNED_INT nullPtr

    glBindVertexArray 0


makeCube :: GLProgram -> IO Cube
makeCube program = do

    aVertex   <- getShaderAttribute program "aVertex"
    aColor    <- getShaderAttribute program "aColor"
    aID       <- getShaderAttribute program "aID"
    uMVP      <- getShaderUniform   program "uMVP"

    -- Setup a VAO
    vaoCube <- overPtr (glGenVertexArrays 1)

    glBindVertexArray vaoCube


    -----------------
    -- Cube Positions
    -----------------

    -- Buffer the cube vertices
    let cubeVertices =
            --- front
            [ -1.0 , -1.0 ,  1.0
            ,  1.0 , -1.0 ,  1.0
            ,  1.0 ,  1.0 ,  1.0
            , -1.0 ,  1.0 ,  1.0

            --- back
            , -1.0 , -1.0 , -1.0
            ,  1.0 , -1.0 , -1.0
            ,  1.0 ,  1.0 , -1.0
            , -1.0 ,  1.0 , -1.0 ] :: [GLfloat]



    vaoCubeVertices <- overPtr (glGenBuffers 1)

    glBindBuffer GL_ARRAY_BUFFER vaoCubeVertices

    let cubeVerticesSize = fromIntegral (sizeOf (undefined :: GLfloat) * length cubeVertices)

    withArray cubeVertices $
        \cubeVerticesPtr ->
            glBufferData GL_ARRAY_BUFFER cubeVerticesSize (castPtr cubeVerticesPtr) GL_STATIC_DRAW

    -- Describe our vertices array to OpenGL
    glEnableVertexAttribArray (fromIntegral (unAttributeLocation aVertex))

    glVertexAttribPointer
        (fromIntegral (unAttributeLocation aVertex)) -- attribute
        3                 -- number of elements per vertex, here (x,y,z)
        GL_FLOAT          -- the type of each element
        GL_FALSE          -- don't normalize
        0                 -- no extra data between each position
        nullPtr           -- offset of first element

    --------------
    -- Cube Colors
    --------------

    -- Buffer the cube colors
    let cubeColors =
            -- front colors
            [ 1.0, 0.0, 0.0
            , 0.0, 1.0, 0.0
            , 0.0, 0.0, 1.0
            , 1.0, 1.0, 1.0
              -- back colors
            , 1.0, 0.0, 0.0
            , 0.0, 1.0, 0.0
            , 0.0, 0.0, 1.0
            , 1.0, 1.0, 1.0 ] :: [GLfloat]

    vboCubeColors <- overPtr (glGenBuffers 1)

    glBindBuffer GL_ARRAY_BUFFER vboCubeColors


    let cubeColorsSize = fromIntegral (sizeOf (undefined :: GLfloat) * length cubeColors)
    withArray cubeColors $
        \cubeColorsPtr ->
            glBufferData GL_ARRAY_BUFFER cubeColorsSize (castPtr cubeColorsPtr) GL_STATIC_DRAW


    glEnableVertexAttribArray (fromIntegral (unAttributeLocation aColor))

    glVertexAttribPointer
        (fromIntegral (unAttributeLocation aColor)) -- attribute
        3                 -- number of elements per vertex, here (R,G,B)
        GL_FLOAT          -- the type of each element
        GL_FALSE          -- don't normalize
        0                 -- no extra data between each position
        nullPtr           -- offset of first element

    -----------
    -- Cube IDs
    -----------

    -- Buffer the cube ids
    let cubeIDs =
            [ 0
            , 1
            , 2
            , 3
            , 4
            , 5 ] :: [GLfloat]

    vboCubeIDs <- overPtr (glGenBuffers 1)

    glBindBuffer GL_ARRAY_BUFFER vboCubeIDs

    let cubeIDsSize = fromIntegral (sizeOf (undefined :: GLfloat) * length cubeIDs)

    withArray cubeIDs $
        \cubeIDsPtr ->
            glBufferData GL_ARRAY_BUFFER cubeIDsSize (castPtr cubeIDsPtr) GL_STATIC_DRAW


    glEnableVertexAttribArray (fromIntegral (unAttributeLocation aID))

    glVertexAttribPointer
        (fromIntegral (unAttributeLocation aID)) -- attribute
        1                 -- number of elements per vertex, here (R,G,B)
        GL_FLOAT          -- the type of each element
        GL_FALSE          -- don't normalize
        0                 -- no extra data between each position
        nullPtr           -- offset of first element



    ----------------
    -- Cube Indicies
    ----------------

    -- Buffer the cube indices
    let cubeIndices =
            -- front
            [ 0, 1, 2
            , 2, 3, 0
            -- top
            , 1, 5, 6
            , 6, 2, 1
            -- back
            , 7, 6, 5
            , 5, 4, 7
            -- bottom
            , 4, 0, 3
            , 3, 7, 4
            -- left
            , 4, 5, 1
            , 1, 0, 4
            -- right
            , 3, 2, 6
            , 6, 7, 3 ] :: [GLuint]

    iboCubeElements <- overPtr (glGenBuffers 1)

    glBindBuffer GL_ELEMENT_ARRAY_BUFFER iboCubeElements

    let cubeElementsSize = fromIntegral (sizeOf (undefined :: GLuint) * length cubeIndices)

    withArray cubeIndices $
        \cubeIndicesPtr ->
            glBufferData GL_ELEMENT_ARRAY_BUFFER cubeElementsSize (castPtr cubeIndicesPtr) GL_STATIC_DRAW

    glBindVertexArray 0

    return $ Cube
        { cubeVAO        = VertexArrayObject vaoCube
        , cubeShader     = program
        , cubeIndexCount = fromIntegral (length cubeIndices)
        , cubeUniformMVP = uMVP
        }