package com.example.cmpt362_group30_maze_game.game

import android.opengl.GLES20
import android.opengl.GLES32
import android.opengl.Matrix
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import java.nio.ShortBuffer

// Every mesh must be passed a parent GameObject to be attached to. It is needed to calculate
// the model matrix
class Mesh (vertices : FloatArray, colours : FloatArray, drawOrder : ShortArray, parentObject: GameObject){
    // Contains model space coordinates for model vertices
    private var vertexBuffer : FloatBuffer

    // Vertex draw order, stored as list of indices to vertex buffer
    private var drawListBuffer : ShortBuffer
    private var drawListSize = drawOrder.size

    // List of RGBA values for each vertex
    private var colourBuffer : FloatBuffer

    // Open GL identifier for this mesh (I think)
    private var program : Int = 0

    // 3 for triangles, 2 for lines, 1 for points
    private var verticesPerElement = 0
    private var renderMode : Int = 0

    // parent pos, rotate, and scale determines modelMatrix transformation
    private var parent : GameObject = parentObject
    private var modelMatrix : FloatArray = FloatArray(16)

    init{
        // Create ByteBuffer for vertex draw order list.
        drawListBuffer = ByteBuffer.allocateDirect(drawOrder.size * 2).order(ByteOrder.nativeOrder()).asShortBuffer()
        drawListBuffer.put(drawOrder).position(0)

        // Create ByteBuffer for vertex list
        vertexBuffer = ByteBuffer.allocateDirect(vertices.size * 4).order(ByteOrder.nativeOrder()).asFloatBuffer()
        vertexBuffer.put(vertices).position(0)

        // Create ByteBuffer for vertex colours
        colourBuffer = ByteBuffer.allocateDirect(colours.size * 4).order(ByteOrder.nativeOrder()).asFloatBuffer()
        colourBuffer.put(colours).position(0)

        // calculate initial model matrix
        Matrix.setIdentityM(modelMatrix,0)
        computeModelMatrix()
    }

    // This function must be called at least once before draw is called
    // vertShader and fragShader are references to already compiled shaders
    fun initDraw(vertShader : Int, fragShader : Int, renderMode : Int){
        setRenderMode(renderMode)

        program = GLES20.glCreateProgram()

        // Link shaders to mesh program, and
        GLES32.glAttachShader(program, vertShader)
        GLES32.glAttachShader(program, fragShader)

        // finalize program initialization
        GLES32.glLinkProgram(program)
    }

    // Note: add check to ensure initDraw was called at least once before this is called
    // To be called inside renderer onDrawFrame() method, draws this mesh in the next frame.
    fun draw(projectionMatrix : FloatArray, viewMatrix : FloatArray){
        // Update modelMatrix if parent GameObject has moved
        computeModelMatrix()

        // Add this mesh to the rendering environment
        GLES32.glUseProgram(program)

        // Pass vertex buffer to vertex shader
        val vertShaderPosition = GLES32.glGetAttribLocation(program, "atr_pos")
        GLES32.glVertexAttribPointer(vertShaderPosition, verticesPerElement, GLES32.GL_FLOAT, false, verticesPerElement*4, vertexBuffer)

        // Pass vertex colour to vertex shader
        val vertColour = GLES32.glGetAttribLocation(program, "atr_col")
        GLES32.glVertexAttribPointer(vertColour, 4, GLES32.GL_FLOAT, false, 16, colourBuffer)

        // Pass transformations to vertex shader
        val vertViewMatrix = GLES32.glGetUniformLocation(program, "view_Matrix")
        GLES32.glUniformMatrix4fv(vertViewMatrix, 1, false, viewMatrix, 0)
        val vertProjectionMatrix = GLES32.glGetUniformLocation(program, "projection_Matrix")
        GLES32.glUniformMatrix4fv(vertProjectionMatrix, 1, false, projectionMatrix, 0)
        val vertModelMatrix = GLES32.glGetUniformLocation(program, "model_Matrix")
        GLES32.glUniformMatrix4fv(vertModelMatrix, 1, false, modelMatrix, 0)

        GLES32.glEnableVertexAttribArray(vertShaderPosition)
        GLES32.glEnableVertexAttribArray(vertColour)

        // Use a draw list to repeat shared vertices while rendering. Draw the mesh.
        GLES20.glDrawElements(renderMode, drawListSize, GLES32.GL_UNSIGNED_SHORT, drawListBuffer)

        GLES32.glDisableVertexAttribArray(vertColour)
        GLES32.glDisableVertexAttribArray(vertShaderPosition)
    }

    // Note: Add check of vertices that ensure setting renderMode to mode would result in valid
    // rendering
    // Changes draw mode between triangle, lines, and points
    fun setRenderMode(mode : Int){
        require(mode == GLES32.GL_TRIANGLES || mode == GLES32.GL_LINES || mode == GLES32.GL_POINTS){
            "Mesh.setRenderMode(mode): mode must be one of GLES32.GL_TRIANGLES, GLES32.GL_LINES, " +
                    "GLES32.GL_POINTS" }

        renderMode = mode
        verticesPerElement = when (mode) {
            GLES32.GL_TRIANGLES -> {
                3
            }
            GLES32.GL_LINES -> {
                2
            }
            else -> {
                1
            }
        }
    }

    // Note: add checks to ensure transformation matrices are computed only if needed
    // Computes a model matrix for this mesh based on its parent GameObject's pos scale and rotation
    private fun computeModelMatrix(){
        Matrix.setIdentityM(modelMatrix,0)
        Matrix.translateM(modelMatrix, 0, parent.xPos, parent.yPos, parent.zPos)
        Matrix.scaleM(modelMatrix, 0, parent.xScale, parent.yScale, parent.zScale)

        // Not sure if this is the correct rotation order
        Matrix.rotateM(modelMatrix, 0, parent.xRotate, 1.0f, 0.0f, 0.0f)
        Matrix.rotateM(modelMatrix, 0, parent.yRotate, 0.0f, 1.0f, 0.0f)
        Matrix.rotateM(modelMatrix, 0, parent.zRotate, 0.0f, 0.0f, 1.0f)
    }

    companion object{
        // Defines a basic triangle's vertices
        val triangleVerts = floatArrayOf(-1.0f,-1.0f,0.0f, 1.0f,-1.0f,0.0f, 0.0f,1.0f,0.0f)
        val triangleDrawOrder = shortArrayOf(0,1,2)

        // Defines a basic square's vertices
        val squareVerts = floatArrayOf(-1.0f,1.0f,0.0f, -1.0f,-1.0f,0.0f, 1.0f,-1.0f,0.0f, 1.0f,1.0f,0.0f)
        val squareDrawOrder = shortArrayOf(0,1,2, 0,2,3)

        // Defines a basic cube's vertices (Note: this model does not 100% reuse overlapping vertices)
        val cubeVerts = floatArrayOf(
        -1.0f,-1.0f,1.0f, 1.0f,-1.0f,1.0f, -1.0f,1.0f,1.0f, 1.0f,1.0f,1.0f,
        -1.0f,-1.0f,-1.0f, -1.0f,-1.0f,1.0f, -1.0f,1.0f,-1.0f, -1.0f,1.0f,1.0f,
        -1.0f,1.0f,1.0f, 1.0f,1.0f,1.0f, -1.0f,1.0f,-1.0f, 1.0f,1.0f,-1.0f,
        1.0f,-1.0f,1.0f, -1.0f,-1.0f,1.0f, 1.0f,-1.0f,-1.0f, -1.0f,-1.0f,-1.0f,
        1.0f,-1.0f,1.0f, 1.0f,-1.0f,-1.0f, 1.0f,1.0f,.0f, 1.0f,1.0f,-1.0f,
        1.0f,-1.0f,-1.0f, -1.0f,-1.0f,-1.0f, 1.0f,1.0f,-1.0f, -1.0f,1.0f,-1.0f)
        val cubeDrawOrder = shortArrayOf(
        0,1,2, 1,3,2,
        4,5,6, 5,7,6,
        8,9,10, 9,11,10,
        12,13,14, 13,15,14,
        16,17,18, 17,19,18,
        20,21,22, 21,23,22)
    }
}
