from xmlrpc.client import Boolean
from gui.visual.entity import Entity
import OpenGL.GL as gl
from pyopengltk import OpenGLFrame
from gui.visual.camera import Camera
from gui.visual.player import Player
from gui.visual.masterRenderer import MasterRenderer
from gui.visual.loader import Loader
from gui.visual.objLoader import OBJLoader
from gui.visual.player import Player
from gui.visual.mouse import Mouse
from gui.framsutils.creature import Creature
from typing import List, Callable
import threading
import time as timetime
from gui.utils import Swap
from typing import Dict
from gui.visual.modelData import ModelData

class AppOgl(OpenGLFrame):
    class GLContext(object):
        pass

    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self.REFRESH_RATE = 0.1
        self.player = Player(10, 10, 0, 0, 0, 0)
        self.camera = Camera(self.player)
        self.entities = []
        self.frams_readCreatures: Callable[[None], List[Creature]] = None
        self.frams_readCreatures_color: Boolean = False
        self.onDraw: Callable[[], None] = lambda: None
        self.commThread = threading.Thread(target=self.commThread, daemon=True)
        self.commThreadLock = threading.Lock()
        self.read_creature_semaphore = threading.Semaphore()
        self.creatures: List[Creature] = []
        self.swap_buffer = Swap(init=[])
        self.swap_buffer.update([])
        self.swap_buffer.update([])
        self.commThread.start()

    #show tri-colored axis instead of balls
    SHOW_AXIS = False

    NEURON_SCALE = 1.5
    OBJ_PATH = "gui/res/obj/"
    TEX_PATH = "gui/res/img/"

    NEURONS = ["|", "@", "G", "Gpart", "T", "S"]

    def neuronname_in_filename(self, neuronname):
        if neuronname == "|":
            return "bend"
        elif neuronname == "@":
            return "rot"
        return neuronname

    def initgl(self):
        gl.glClearColor(0, 0, 0.4, 0)
        
        self.loader = Loader()

        if self.SHOW_AXIS:
            self.bodyPartModel = OBJLoader.loadOBJ(self.OBJ_PATH+"axis.obj", self.TEX_PATH+"axis.png", self.loader)
        else:
            self.bodyPartModel = OBJLoader.loadOBJ(self.OBJ_PATH+"part.obj", self.TEX_PATH+"green2.png", self.loader)
        self.jointModel = OBJLoader.loadOBJ(self.OBJ_PATH+"stick5red3.obj", self.TEX_PATH+"green2.png", self.loader)
        self.neurons: Dict[str, ModelData] = dict()
        for n in self.NEURONS:
            self.neurons[n] = OBJLoader.loadOBJ(self.OBJ_PATH+"neuro-"+self.neuronname_in_filename(n)+".obj", self.TEX_PATH+"amber.jpg", self.loader)

        self.renderer = MasterRenderer(self.loader)

        gl.glEnable(gl.GL_DEPTH_TEST)
        gl.glClearDepth(1.0)
        gl.glEnable(gl.GL_TEXTURE_2D)

        self.initalized = True
        self._resize(self.width, self.height)

    def init_context(self):
        self.context = self.GLContext()

    def redraw(self):
        #call onDraw callback  only for frams library 
        self.onDraw()
        self.entities.clear()
        self.creatures: List[Creature] = self.swap_buffer.get()
        #for every creature, prepare entities for every parts, joints and neurons
        for creature in self.creatures:
            parts = creature.mechParts
            joints = creature.joints
            neurons = creature.neurons

            '''
            TODO: solid shapes should be handled here
            obj models should be loaded in initgl method and used depending on the shape type (probably need to add new lists to Creature class).
            Position should already be loaded in the part object, as well as rotation matrix, you need to add scale in FramsSocket and FramsLib.
            You can add scale in each axis in the Entity constructor or as independent properties of the Entity class.
            scaleX, scaleY, scaleZ have higher priority than scale only if all three (x, y, z) are set.
            '''
            for i, part in enumerate(parts):
                rotMatrix = creature.partRotationMatrix(i)
                partEntity = Entity(part[0], part[1], part[2], self.bodyPartModel, scale=0.7, rotMatrix=rotMatrix)
                if i < len(creature.colorsPart):
                    partEntity.color = creature.colorsPart[i]
                self.entities.append(partEntity)

            for i in range(len(joints)):
                translation = creature.jointTranslation(i)
                rotMatrix = creature.jointRotation(i)
                jointEntity = Entity(translation[0], translation[1], translation[2], self.jointModel, rotMatrix=rotMatrix)
                jointEntity.scaleX = creature.jointLength(i)
                jointEntity.scaleY = 1
                jointEntity.scaleZ = 1
                if i < len(creature.colorsJoint):
                    partEntity.color = creature.colorsJoint[i]
                self.entities.append(jointEntity)

            for i, neuron in enumerate(neurons):
                if creature.styleNeuron[i] in self.neurons:
                    style = creature.styleNeuron[i]
                    if neuron[0] >= 0:
                        translation = creature.partTranslation(neuron[0])
                        rotMatrix = creature.partRotationMatrix(neuron[0])
                        rotMatrix = rotMatrix * creature.neuronRelativeOrient[i]
                        neuronEntity = Entity(translation[0], translation[1], translation[2], self.neurons[style], scale=self.NEURON_SCALE, rotMatrix=rotMatrix)
                    else:
                        translation = creature.jointTranslation(neuron[1])
                        if style == 'G':
                            rotMatrix = creature.jointRotation(neuron[1])
                        else:
                            rotMatrix = creature.partRotationMatrix(joints[neuron[1]][0])
                        rotMatrix = rotMatrix * creature.neuronRelativeOrient[i]
                        neuronEntity = Entity(translation[0], translation[1], translation[2], self.neurons[style], scale=self.NEURON_SCALE, rotMatrix=rotMatrix)
                    self.entities.append(neuronEntity)

        self.camera.move()
        self.renderer._isTexturesOn = not self.frams_readCreatures_color
        self.renderer.renderScene(self.player, self.entities, self.camera)

    #reload all creatures from frams with self.REFRESH_RATE interval
    def commThread(self):
        while True:
            if self.frams_readCreatures:
                with self.read_creature_semaphore:
                    c = self.frams_readCreatures(self.frams_readCreatures_color)
                self.swap_buffer.update(c)
            timetime.sleep(self.REFRESH_RATE)

    def onResize(self, event):
        width, height = event.width, event.height
        self._resize(width, height)

    def _resize(self, width, height):
        self.width = width
        self.height = height
        try:
            self.initalized
            gl.glViewport(0, 0, width, height)
            self.renderer.resize(width, height)
        except AttributeError:
            pass

    def onMouseMotion(self, event):
        Mouse.setXY(event.x, event.y)

    def onScroll(self, event):
        if event.delta > 0:
            Mouse.incrementDWheel(1)
        else:
            Mouse.incrementDWheel(-1)

    def onMouseClick(self, event):
        if event.num == 1:
            Mouse.setButton(Mouse.MOUSE_LEFT)
        elif event.num == 3:
            Mouse.setButton(Mouse.MOUSE_RIGHT)

    def onMouseRelease(self, event):
        Mouse.setButton(Mouse.MOUSE_NONE)

    def onMouseEnter(self, event):
        Mouse.setXY(event.x, event.y)

    def reloadWorld(self, worldType: int, simType: int, worldSize: float, worldMap: str, worldBoundaries: int, worldWaterLevel: float) -> None:
        self.renderer.reloadWorld(self.loader, worldType, simType, worldSize, worldMap, worldBoundaries, worldWaterLevel)
        self.camera.setPosition(worldSize / 2.0, worldSize / 2.0)
        self.camera.setZoomScale(worldSize / 20)
        self.camera.setZoom(worldSize)