package com.example.cmpt362_group30_maze_game.game

import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.lifecycle.ViewModel
import java.io.FileNotFoundException
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.min
import kotlin.math.sqrt

class PlayerViewModel() : ViewModel() {
    var playerObject : GameObject? = null
    private var xOffset = 0.0f
    private var yOffset = 0.0f

    // Note: This must be called at least once before rendering starts
    // Initializes player mesh and  based on passed URI to png file
    // returns 0 on success or 1 on failure
    @RequiresApi(Build.VERSION_CODES.Q)
    fun loadData(context : Context, uri : Uri) : Int{
        // Load png information into bitmap
        try {
            val meshData = BitmapFactory.decodeStream(context.contentResolver.openInputStream(uri))

            // Find center of png and calculate vertex offsets
            xOffset = meshData!!.width/2.toFloat()
            yOffset = meshData.height/2.toFloat()

            // Extract mesh data from bitMap
            val verts : MutableList<Float> = mutableListOf()
            val drawOrder : MutableList<Short> = mutableListOf()
            val colours : MutableList<Float> = mutableListOf()

            // Note: Does not 100% reuse adjacent vertices
            for (i in 0.. meshData.width-1){
                for (j in 0..meshData.height-1){
                    val pixelCol = meshData.getColor(i,j)
                    if (pixelCol.red() != 1.0f || pixelCol.blue() != 1.0f || pixelCol.green() != 1.0f ) {
                        val numVerts = (verts.size)/3

                        // add square mesh for pixel
                        // Offsets are negative so that the mesh center is at (0,0,0)
                        verts.add(-xOffset+1.0f*i) // x
                        verts.add(-yOffset+1.0f*j) // y
                        verts.add(0.0f) // z

                        verts.add((-xOffset+1.0f*i)+1.0f)
                        verts.add(-yOffset+1.0f*j)
                        verts.add(0.0f)

                        verts.add((-xOffset+1.0f*i)+1.0f)
                        verts.add((-yOffset+1.0f*j)+1.0f)
                        verts.add(0.0f)

                        verts.add(-xOffset+1.0f*i)
                        verts.add((-yOffset+1.0f*j)+1.0f)
                        verts.add(0.0f)

                        // add drawOrder of pixel
                        drawOrder.add(numVerts.toShort())
                        drawOrder.add((numVerts+1).toShort())
                        drawOrder.add((numVerts+2).toShort())

                        drawOrder.add(numVerts.toShort())
                        drawOrder.add((numVerts+3).toShort())
                        drawOrder.add((numVerts+2).toShort())

                        // add colour of pixel
                        for (k in 0 ..3) {
                            colours.add(pixelCol.red())
                            colours.add(pixelCol.green())
                            colours.add(pixelCol.blue())
                            colours.add(1.0f)
                        }
                    }
                }
            }

            playerObject = GameObject(0.0f,0.0f,0.0f, 0.0f,0.0f,0.0f, 1.0f,1.0f,1.0f)
            playerObject!!.mesh = Mesh(verts.toFloatArray(), colours.toFloatArray(), drawOrder.toShortArray(), playerObject!!)

            return 0
        }
        catch(error: FileNotFoundException){
            // Note: add exception handling if time permits
            return 1
        }
    }

    // Note: This is very basic collision detection, if time update
    // Note: loadData must have been called at least once successfully before this can be called
    // Tests collision with map data
    // Returns 0, if no collision
    // Returns 1 if collided with maze
    // Returns 2 if collided with end of maze
    // Returns 3 on undefined collision data
    @RequiresApi(Build.VERSION_CODES.Q)
    fun isCollide(mapData : MapDataViewModel) : Int{
        val scaledXOffset = xOffset * playerObject!!.xScale
        val scaledYOffset = yOffset * playerObject!!.yScale

        val c1 = mapData.isCollision(playerObject!!.xPos - scaledXOffset, playerObject!!.yPos - scaledYOffset)
        val c2 = mapData.isCollision(playerObject!!.xPos - scaledXOffset, playerObject!!.yPos + scaledYOffset)
        val c3 = mapData.isCollision(playerObject!!.xPos + scaledXOffset, playerObject!!.yPos - scaledYOffset)
        val c4 = mapData.isCollision(playerObject!!.xPos + scaledXOffset, playerObject!!.yPos + scaledYOffset)

        // If any of the corners collide with maze
        if(c1 == 1 || c2 == 1 || c3 == 1 || c4 == 1){
            return 1
        }

        // if all corners are within the end of the maze
        if(c1 == 2 && c2 == 2 && c3 == 2 && c4 == 2){
            return 2
        }

        // undefined collision
        if(c1 == 3 || c2 == 3 || c3 == 3 || c4 == 3){
            return 3
        }

        // no collision occurred
        return 0
    }

    // Helper function for maze sound player frequency calculation
    // Returns the distance from the player character to closest maze wall
    @RequiresApi(Build.VERSION_CODES.Q)
    fun distToNearestMazeWall(mapData : MapDataViewModel) : Float{
        // Initialize distances to 1.0f since mazes are designed such that the closest wall is always
        // at most sqrt(0.5^2+0.5^2) from the player character
        var minDist = 1.0f

        val currTileX = floor(playerObject!!.xPos)
        val currTileY = floor(playerObject!!.yPos)

        val scaledXOffset = xOffset * playerObject!!.xScale
        val scaledYOffset = yOffset * playerObject!!.yScale

        // Find minimum distance among all wall tiles around player character
        for (i in -1..1){
            for (j in -1..1){
                if (mapData.isCollision(currTileX+i, currTileY+j) == 1){
                    var yDist = 0.0f
                    var xDist = 0.0f

                    if (i == -1){
                        xDist = playerObject!!.xPos - scaledXOffset - currTileX
                    }
                    else if (i == 1){
                        xDist = playerObject!!.xPos + scaledXOffset - ceil(playerObject!!.xPos)
                    }

                    if (j == -1){
                        yDist = playerObject!!.yPos - scaledYOffset - currTileY
                    }
                    else if (j == 1){
                        yDist = playerObject!!.yPos + scaledYOffset - ceil(playerObject!!.yPos)
                    }

                    // calculate the L2 norm of the difference between x and y coordinates
                    minDist = min(minDist,sqrt(xDist * xDist + yDist * yDist))

                }
            }
        }
        return minDist
    }

    // Helper function for maze vibration frequency calculation
    // returns the distance between the player character and the maze exit
    fun distToPointMazeEnd(mapData : MapDataViewModel) : Float{
        // calculate the L2 norm of the difference between x and y coordinates
        val xDiffSquare = (playerObject!!.xPos - mapData.mazeEndX) * (playerObject!!.xPos - mapData.mazeEndX)
        val yDiffSquare = (playerObject!!.yPos - mapData.mazeEndY) * (playerObject!!.yPos - mapData.mazeEndY)
        return sqrt(xDiffSquare + yDiffSquare)
    }
}