package com.example.cmpt362_group30_maze_game.game

import android.opengl.GLES20
import android.opengl.GLES32
import android.opengl.GLSurfaceView
import android.opengl.Matrix
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10

// This class is a custom implementation of GLSurfaceView.Renderer.
// This defines what is drawn to the GameSurfaceView and how.
class GameRenderer : GLSurfaceView.Renderer {
    var playerViewModel: PlayerViewModel? = null
    var mapDataViewModel: MapDataViewModel? = null

    // transformation that maps 3d space to 2d screenspace (camera space -> screenspace)
    var projectionMatrix = FloatArray(16)

    // transformation that maps world coordinates to camera space (world space -> camera space)
    var viewMatrix = FloatArray(16)

    // Extra rotation around the player (in degrees).
    // This is what we drive from TiltController.
    var mapRotation: Float = 0f

    private val rotMatrix = FloatArray(16)

    // Acts similar to onViewCreated
    override fun onSurfaceCreated(graphicsLibrary: GL10?, config: EGLConfig?) {
        // Sets background to white
        GLES32.glClearColor(1.0f, 1.0f, 1.0f, 1.0f)

        // Initialize the camera view matrix (placeholder, real values set in onDrawFrame)
        Matrix.setLookAtM(
            viewMatrix,
            0,
            0.0f, 0.0f, 1.0f,   // eye
            0.0f, 0.0f, 0.0f,   // center
            0.0f, 1.0f, 0.0f    // up
        )

        // Define shaders
        val vertShaderCode =
            "attribute vec4 atr_pos;" +
                    "attribute vec4 atr_col;" +
                    "uniform mat4 view_Matrix;" +
                    "uniform mat4 projection_Matrix;" +
                    "uniform mat4 model_Matrix;" +
                    "varying vec4 v_col;" +
                    "void main() {" +
                    "  v_col = atr_col;" +
                    "  gl_Position = (projection_Matrix * view_Matrix * model_Matrix) * atr_pos;" +
                    "}"

        val fragShaderCode =
            "precision mediump float;" +
                    "varying vec4 v_col;" +
                    "void main() {" +
                    "  gl_FragColor = v_col;" +
                    "}"

        // Compile Shaders
        val vertShader = loadShader(GLES32.GL_VERTEX_SHADER, vertShaderCode)
        val fragShader = loadShader(GLES32.GL_FRAGMENT_SHADER, fragShaderCode)

        playerViewModel!!.playerObject!!.mesh!!.initDraw(vertShader, fragShader, GLES32.GL_TRIANGLES)
        mapDataViewModel!!.mapObject!!.mesh!!.initDraw(vertShader, fragShader, GLES32.GL_TRIANGLES)
        mapDataViewModel!!.mapBackground.mesh!!.initDraw(vertShader, fragShader, GLES32.GL_TRIANGLES)
    }

    // Called once to render every frame
    override fun onDrawFrame(graphicsLibrary: GL10?) {
        // Clear frame
        GLES20.glClear(GLES32.GL_COLOR_BUFFER_BIT)

        val player = playerViewModel?.playerObject ?: return

        // Base camera: top-down, centered on the player
        Matrix.setLookAtM(
            viewMatrix,
            0,
            player.xPos, player.yPos, 1.0f,   // eye just above the player
            player.xPos, player.yPos, player.zPos, // look down at player
            0.0f, 1.0f, 0.0f                  // "up" is +Y
        )

        // Apply extra rotation around Z at the camera (so the map spins around the sub)
        Matrix.setRotateM(rotMatrix, 0, mapRotation, 0f, 0f, 1f)
        val rotatedView = FloatArray(16)
        Matrix.multiplyMM(rotatedView, 0, rotMatrix, 0, viewMatrix, 0)
        viewMatrix = rotatedView

        // Draw objects with the rotated view
        mapDataViewModel!!.mapBackground.mesh!!.draw(projectionMatrix, viewMatrix)
        mapDataViewModel!!.mapObject!!.mesh!!.draw(projectionMatrix, viewMatrix)
        playerViewModel!!.playerObject!!.mesh!!.draw(projectionMatrix, viewMatrix)
    }

    // Called whenever the surface changes size or if orientation changes, and after onSurfaceCreated
    override fun onSurfaceChanged(graphicsLibrary: GL10?, width: Int, height: Int) {
        GLES20.glViewport(0, 0, width, height)

        // Scales fov
        val scale = 0.5f

        // Since using frustumM, we are rendering in perspective, not orthographic
        // Recalculate the Projection Matrix (transformation that maps 3d space to 2d screenspace)
        if (height > width) {
            // portrait
            val ratio: Float = width.toFloat() / height.toFloat()
            Matrix.frustumM(
                projectionMatrix,
                0,
                -ratio * scale,
                ratio * scale,
                -1.0f * scale,
                1.0f * scale,
                0.5f,
                15.0f
            )
        } else {
            // landscape
            val ratio: Float = height.toFloat() / width.toFloat()
            Matrix.frustumM(
                projectionMatrix,
                0,
                -1.0f * scale,
                1.0f * scale,
                -ratio * scale,
                ratio * scale,
                0.5f,
                15.0f
            )
        }
    }

    // This method compiles and returns an identifier for the passed shader source code
    fun loadShader(type: Int, shaderSource: String): Int {
        val shader = GLES20.glCreateShader(type)
        GLES20.glShaderSource(shader, shaderSource)
        GLES20.glCompileShader(shader)
        return shader
    }
}
