from gui.visual.loader import Loader
from gui.visual.worldShader import WorldShader
from gui.visual.staticShader import StaticShader
from gui.visual.camera import Camera
from gui.visual.objLoader import OBJLoader
import OpenGL.GL as gl
from typing import Tuple, List
import numpy as np
import re
import glm

class WorldRenderer:
    WORLD_Z_OFFSET = -0.2

    def __init__(self, loader: Loader, projectionMatrix, staticShader: StaticShader) -> None:
        self.shader = WorldShader()
        self._createWater(loader)
        self.staticShader: StaticShader = staticShader

        self.fence = OBJLoader.loadOBJ("gui/res/obj/fence-element.obj", "gui/res/img/wood.png", loader)
        self.teleport = OBJLoader.loadOBJ("gui/res/obj/teleport.obj", "gui/res/img/teleportglow.png", loader)

        self.boundary: List[Tuple[glm.vec3, float]] = []
        self.worldType = 0
        self.worldSize = 20
        self.worldBoundaries = 0
        self.z_offset = 0

        self.shader.start()
        self.loadProjectionMatrix(projectionMatrix)
        self.shader.stop()

    def render(self, camera: Camera):
        if hasattr(self, "plane"):
            self.shader.start()
            self.shader.loadViewMatrix(camera)
            self.shader.loadMode(0)
            gl.glEnable(gl.GL_BLEND)
            gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)

            #render world plane
            gl.glBindVertexArray(self.plane.vaoID)
            gl.glEnableVertexAttribArray(0)
            gl.glDrawArrays(gl.GL_TRIANGLES, 0, int(self.plane.vertexCount))
            gl.glDisableVertexAttribArray(0)
            gl.glBindVertexArray(0)

            #render water plane
            gl.glBindVertexArray(self.water.vaoID)
            gl.glEnableVertexAttribArray(0)
            gl.glEnableVertexAttribArray(1)
            self.shader.loadMode(1)
            gl.glDrawArrays(gl.GL_TRIANGLES, 0, int(self.water.vertexCount))
            gl.glDisableVertexAttribArray(0)
            gl.glBindVertexArray(0)
            gl.glBindVertexArray(1)

            gl.glDisable(gl.GL_BLEND)

            #render bounds
            self.renderBounds()
            
            self.shader.stop()

    def loadProjectionMatrix(self, projectionMatrix):
        self.shader.loadProjectionMatrix(projectionMatrix)

    def reloadWorld(self, loader: Loader, worldType: int, simType: int, worldSize: float, worldMap: str, worldBoundaries: int, worldWaterLevel: float):
        self.worldType = worldType
        self.worldSize = worldSize
        self.worldBoundaries = worldBoundaries

        if simType == 0:
            self.z_offset = self.WORLD_Z_OFFSET
        else:
            self.z_offset = 0

        v = self._loadQuads(worldMap)
        self.plane = loader.loadToVao1(v, len(v) / 3)
        self.shader.start()
        self.shader.loadWorldSize(worldSize)
        self.shader.loadWaterLevel(worldWaterLevel + self.z_offset)
        self.shader.stop()

        if worldBoundaries == 1:
            self._addFence()
        elif worldBoundaries == 2:
            self._addTeleport()

    def _createBlock(self, p1: Tuple[float, float, float], p2: Tuple[float, float, float], 
                            p3: Tuple[float, float, float], p4: Tuple[float, float, float]) -> List[float]:
        vertices = []
        vertices.append(p1[0])
        vertices.append(p1[1])
        vertices.append(p1[2])

        vertices.append(p2[0])
        vertices.append(p2[1])
        vertices.append(p2[2])

        vertices.append(p3[0])
        vertices.append(p3[1])
        vertices.append(p3[2])

        vertices.append(p1[0])
        vertices.append(p1[1])
        vertices.append(p1[2])

        vertices.append(p3[0])
        vertices.append(p3[1])
        vertices.append(p3[2])

        vertices.append(p4[0])
        vertices.append(p4[1])
        vertices.append(p4[2])

        return vertices

    def _createColorsForBlock(self, color: Tuple[float, float, float]) -> List[float]:
        return [color[0], color[1], color[2]] * 6

    def _createWater(self, loader: Loader):
        vertices = self._createBlock((0,0,-0.01), (1,0,-0.01), (1,1,-0.01), (0,1,-0.01))
        colors = self._createColorsForBlock((0,0,1))

        self.water = loader.loadToVao2(vertices, colors, len(vertices) / 3)

    def _loadQuads(self, a: str):
        points = []
        indices = []
        lines = a.splitlines()
        self.primitive = None

        for line in lines:
            if line[0] == 'v':
                v = line.split()
                points.append((float(v[1]), float(v[2]), float(v[3]) + self.z_offset))
            elif line[0] == 'f':
                f = line.split()
                if len(f) == 4:
                    indices.append(int(f[1]))
                    indices.append(int(f[2]))
                    indices.append(int(f[3]))
                elif len(f) == 5:
                    indices.append(int(f[1]))
                    indices.append(int(f[2]))
                    indices.append(int(f[3]))

                    indices.append(int(f[1]))
                    indices.append(int(f[3]))
                    indices.append(int(f[4]))

        vertices = []
        for i in indices:
            vertices.append(points[i-1][0])
            vertices.append(points[i-1][1])
            vertices.append(points[i-1][2])

        return vertices

    def renderBounds(self):
        if self.worldBoundaries == 1:
            self.staticShader.start()
            gl.glBindVertexArray(self.fence.rawModel.vaoID)
            gl.glEnableVertexAttribArray(0)
            gl.glEnableVertexAttribArray(1)
            gl.glActiveTexture(gl.GL_TEXTURE0)
            gl.glBindTexture(gl.GL_TEXTURE_2D, self.fence.texture.id)
            self.staticShader.loadTextureOn(True)

            for b in self.boundary:
                matrix = glm.mat4(1)
                matrix = glm.translate(matrix, b[0])
                matrix = glm.rotate(matrix, 3.14/2, glm.vec3(1,0,0))
                matrix = glm.rotate(matrix, b[1], glm.vec3(0,1,0))
                self.staticShader.loadTransformationMatrix(matrix)
                gl.glDrawArrays(gl.GL_TRIANGLES, 0, int(self.fence.rawModel.vertexCount))

            self.staticShader.stop()
        elif self.worldBoundaries == 2:
            self.staticShader.start()
            gl.glBindVertexArray(self.teleport.rawModel.vaoID)
            gl.glEnableVertexAttribArray(0)
            gl.glEnableVertexAttribArray(1)
            gl.glActiveTexture(gl.GL_TEXTURE0)
            gl.glBindTexture(gl.GL_TEXTURE_2D, self.teleport.texture.id)

            for b in self.boundary:
                matrix = glm.mat4(1)
                matrix = glm.translate(matrix, b[0])
                matrix = glm.rotate(matrix, 3.14/2, glm.vec3(1,0,0))
                matrix = glm.rotate(matrix, b[1], glm.vec3(0,1,0))
                self.staticShader.loadTransformationMatrix(matrix)
                gl.glDrawArrays(gl.GL_TRIANGLES, 0, int(self.teleport.rawModel.vertexCount))

            self.staticShader.stop()

        gl.glDisableVertexAttribArray(0)
        gl.glDisableVertexAttribArray(1)
        gl.glBindVertexArray(0)

    def _addFence(self):
        i = 0
        self.boundary.clear()
        while i < self.worldSize / 2:
            self.boundary.append((glm.vec3(i * 2, 0, self.z_offset), 0))
            self.boundary.append((glm.vec3(i * 2, self.worldSize, self.z_offset), 0))
            self.boundary.append((glm.vec3(0, i * 2, self.z_offset), glm.radians(90)))
            self.boundary.append((glm.vec3(self.worldSize, i * 2, self.z_offset), glm.radians(90)))
            i += 1

    def _addTeleport(self):
        i = 0
        self.boundary.clear()
        while i < self.worldSize / 3:
            self.boundary.append((glm.vec3(i * 3, 0, self.z_offset), 0))
            self.boundary.append((glm.vec3(i * 3, self.worldSize, self.z_offset), 0))
            self.boundary.append((glm.vec3(0, i * 3, self.z_offset), glm.radians(-90)))
            self.boundary.append((glm.vec3(self.worldSize, i * 3, self.z_offset), glm.radians(-90)))
            i += 1
