from numpy.core.numeric import cross
from numpy.oldnumeric.ma import dot
import math
from PyQt4.QtCore import *
from PyQt4.QtGui import *

from pandac.PandaModules import *

from Ui_MainWindow import Ui_MainWindow
from Ui_MainWidget import Ui_MainFTCommandPanel
from Ui_sourceDialog import Ui_Dialog
from Ui_inputDoubleErrorDialog import Ui_inputDoubleErrorDialog
import core
from core.scene_object import SceneObject
from core.scene_object_frustum_source import SceneObjectFrustumSource as FrustumSource
import numpy as np
import os, sys

class frustumInputDoubleErrorDialog(QDialog, Ui_inputDoubleErrorDialog):
    def __init__(self, parent=None):        
        super(QDialog, self).__init__(parent)        
        self.setupUi(self)   
        self.setModal(True)
        self.show()

#TODO: this is unused at the moment, remove it if you don't need it anymore
class frustumSourceInputDialog(QDialog,Ui_Dialog):    
    
    closed = pyqtSignal(FrustumSource)
    
    def __init__(self, parent=None):        
        super(QDialog, self).__init__(parent)        
        self.setupUi(self)       
        QObject.connect(self.buttonBox, SIGNAL("accepted()"), self.getData)
        self.dataValid = True
        self.show()     
        
    def validateDataRealNumber(self, toFloatResult):
        if(toFloatResult[1] is True):
            return toFloatResult[0]
        else:
            self.msgBox = frustumInputDoubleErrorDialog()
            self.dataValid = False
            return 0
        
    def getData(self):
        #TODO: add some data verification here
        obsPx = self.validateDataRealNumber(self.obsPx_line.text().toFloat())
        obsPy = self.validateDataRealNumber(self.obsPy_line.text().toFloat())
        obsPz = self.validateDataRealNumber(self.obsPz_line.text().toFloat())
        obs_point = Vertex(obsPx, obsPy, obsPz)
        
        obsPl1x = self.validateDataRealNumber(self.obsPl1x_line.text().toFloat())
        obsPl1y = self.validateDataRealNumber(self.obsPl1y_line.text().toFloat())
        obsPl1z = self.validateDataRealNumber(self.obsPl1z_line.text().toFloat())
        obs_plane_v1 = Vertex(obsPl1x, obsPl1y, obsPl1z)
        
        obsPl2x = self.validateDataRealNumber(self.obsPl2x_line.text().toFloat())
        obsPl2y = self.validateDataRealNumber(self.obsPl2y_line.text().toFloat())
        obsPl2z = self.validateDataRealNumber(self.obsPl2z_line.text().toFloat())
        obs_plane_v2 = Vertex(obsPl2x, obsPl2y, obsPl2z)
        
        obsPl3x = self.validateDataRealNumber(self.obsPl3x_line.text().toFloat())
        obsPl3y = self.validateDataRealNumber(self.obsPl3y_line.text().toFloat())
        obsPl3z = self.validateDataRealNumber(self.obsPl3z_line.text().toFloat())
        obs_plane_v3 = Vertex(obsPl3x, obsPl3y, obsPl3z)
        
        obsPl4x = self.obsPl4x_line.text().toFloat()[0]
        obsPl4y = self.obsPl4y_line.text().toFloat()[0]
        obsPl4z = self.obsPl4z_line.text().toFloat()[0]
        obs_plane_v4 = Vertex(obsPl4x, obsPl4y, obsPl4z)
        if (self.dataValid is True):
            self.closed.emit(FrustumSource("get_a_name", obs_point, obs_plane_v1, obs_plane_v2, obs_plane_v3, obs_plane_v4))
        
class FTCommandPanel(QWidget, Ui_MainFTCommandPanel):
    def __init__(self, parent=None): 
        super(QWidget, self).__init__(parent)
        # Set up the user interface from Designer.
        self.setupUi(self)
        self.show()

class FTMainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, pandaWorld, pandaCallback, parent=None): 
        super(QMainWindow, self).__init__(parent)
        #self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
        # Set up the user interface from Designer.
        self.setupUi(self)

        #self.pandaContainer = self.renderWidget
        self.pandaWorld = pandaWorld
        # this basically creates an idle task 
        timer = QTimer(self) 
        self.connect( timer, SIGNAL("timeout()"), pandaCallback )
       
        timer.start(0)
        screen =  core.app.desktop()
        screenX = screen.screenGeometry().width()
        screenY = screen.screenGeometry().height()
        """
        TODO: Main Window is the anchor for all other windows. If you move this,
         the other windows will move to. We need now to set the Main Window
         size and position accordingly to desktop resolution. The correct methods
         are the ones above. They take into consideration also multiple screens etc
        """
        self.move(50, 50)
        self.commandPanel = FTCommandPanel()
        self.commandPanel.move(self.x()+self.width()+20, self.y())
        QObject.connect(self.commandPanel.loadModel, SIGNAL("clicked()"), self.on_actionOpen_activated)
        QObject.connect(self.commandPanel.loadDebugModel, SIGNAL("clicked()"), self.on_generaScena_clicked)
        QObject.connect(self.commandPanel.addDebugSource, SIGNAL("clicked()"), self.addTestFrustumSource)
        QObject.connect(self.commandPanel.addSource, SIGNAL("clicked()"), self.on_addSource_clicked)
        QObject.connect(self.commandPanel.traceSource, SIGNAL("clicked()"), self.traceSources)

    #TODO: remove this since is unused
    """
    def on_addSource_clicked(self):
        #TODO: remove this if unused
        #This version use a modal dialog box to get a frustum source
        self.dialog = frustumSourceInputDialog()
        self.dialog.closed.connect(self.addFrustumSource)
        #TODO: GIM fix return value of dialog box
    """

    def validateDataRealNumber(self, toFloatResult):
        if(toFloatResult[1] is True):
            return toFloatResult[0]
        else:
            self.msgBox = frustumInputDoubleErrorDialog()
            self.dataValid = False
            return 0

    def on_addSource_clicked(self):
        #This version uses "position" and "direction" user inputs and
        # assumes solid angle is the same as a cubic source.
        #Notice that this will create one frustum only, the one going in the
        # specified direction. It will not create one frustum per cube face.
        self.dataValid = True
        srcName = self.commandPanel.lineEdit_sourceName.text()

        srcPosX = self.validateDataRealNumber(self.commandPanel.lineEdit_sourcePositionX.text().toFloat())
        srcPosY = self.validateDataRealNumber(self.commandPanel.lineEdit_sourcePositionY.text().toFloat())
        srcPosZ = self.validateDataRealNumber(self.commandPanel.lineEdit_sourcePositionZ.text().toFloat())
        srcPos = np.array([srcPosX, srcPosY, srcPosZ])

        srcDirX = self.validateDataRealNumber(self.commandPanel.lineEdit_sourceDirectionX.text().toFloat())
        srcDirY = self.validateDataRealNumber(self.commandPanel.lineEdit_sourceDirectionY.text().toFloat())
        srcDirZ = self.validateDataRealNumber(self.commandPanel.lineEdit_sourceDirectionZ.text().toFloat())
        srcDir = np.array([srcDirX, srcDirY, srcDirZ])
        
        if self.dataValid == True:
            self.computeAndAddFrustumSource(srcPos, srcDir, srcName)
        else:
            #TODO: handle input error
            pass

    def computeAndAddFrustumSource(self, srcPos, srcDir, srcName):
        #Start from arbitrary z+ oriented face of unitary cube centered
        # in (0,0,0)

        coord = math.sqrt(2.)/2.
        obsPlaneV1 = ([-coord, -coord, 1])
        obsPlaneV2 = ([coord, -coord, 1])
        obsPlaneV3 = ([coord, coord, 1])
        obsPlaneV4 = ([-coord, coord, 1])
        
        obsPlaneNormal = ([0,0,1])

        #Ma va bene calcolare asse a angolo di rotazione con la normale al
        # piano? E usare stesso angolo e asse per ogni punto?
        rotation_angle = np.arccos(dot(obsPlaneNormal, srcDir))
        rotation_axis = cross(obsPlaneNormal, srcDir)
        
        #Calculate rotation matrix
        rot_mtx = self.make_axis_rotation_matrix ( rotation_axis, rotation_angle )
        #Do rotation
        obsPlaneV1 = np.dot (obsPlaneV1, rot_mtx)
        obsPlaneV2 = np.dot (obsPlaneV2, rot_mtx)
        obsPlaneV3 = np.dot (obsPlaneV3, rot_mtx)
        obsPlaneV4 = np.dot (obsPlaneV4, rot_mtx)
        #Do translation
        obsPlaneV1 = obsPlaneV1 + srcPos
        obsPlaneV2 = obsPlaneV2 + srcPos
        obsPlaneV3 = obsPlaneV3 + srcPos
        obsPlaneV4 = obsPlaneV4 + srcPos

        core.env.source_dict[srcName]= FrustumSource( srcName, srcPos, obsPlaneV1, obsPlaneV2, obsPlaneV3, obsPlaneV4)
        self.pandaWorld.renderSources(core.env)
        self.commandPanel.tabCalculation.setEnabled(True)

    def make_axis_rotation_matrix(self, direction, angle):
        """
        Create a rotation matrix corresponding to the rotation around a general
        axis by a specified angle.

        R = dd^T + cos(a) (I - dd^T) + sin(a) skew(d)

        Parameters:

         angle : float a
         direction : array d
        """
        d = np.array(direction, dtype=np.float64)
        d /= np.linalg.norm(d)

        eye = np.eye(3, dtype=np.float64)
        ddt = np.outer(d, d)
        skew = np.array([[    0,  d[2],  -d[1]],
                      [-d[2],     0,  d[0]],
                      [d[1], -d[0],    0]], dtype=np.float64)

        mtx = ddt + np.cos(angle) * (eye - ddt) + np.sin(angle) * skew
        return mtx

    def prova(self):
        print("prova")
    
    def minimumSizeHint(self):
        return QSize(400,300)
        
    @pyqtSignature("")
    def on_actionQuit_activated(self):
        """
        Slot documentation goes here.
        """
        sys.exit(core.app.exec_())
            
    @pyqtSignature("")
    def on_actionOpen_activated(self):
        """
        Slot documentation goes here.
        """
        #protect from absence of panda world instance
        if self.pandaWorld is None:
            return
        
        fileName = QFileDialog.getOpenFileName(self, self.tr("Open File"), "/");
        
        if fileName == "":
            fileName = None
        
        if fileName is not None:
            #Get filename extension and invoke either panda pre.defined loaders or custom ones
            self.commandPanel.tabSource.setEnabled(True)
            self.loadFile( QString(fileName) )
            
    @pyqtSignature("")
    def on_generaScena_clicked(self):
        """
        Slot documentation goes here.
        """
        #self.pandaWorld.generaCubo()
        #fileName = r"D:\Files di lavoro\gui\models\CUBO\cubo.edg"
        fileName = r"gui/models/420Faces/testall2.edg" #Linux version
        #fileName = r"gui\models\420Faces\testall2.edg"
        
        # DEBUG SOURCE
        #self.addTestFrustumSource()
        
        self.loadFile( QString(fileName) )
    
    @pyqtSignature("")
    def on_generaOctree_clicked(self):
        """
        Slot documentation goes here.
        """
        
        #self.traceFrustum(core.env.source_list["FrustumSource1"], core.env.object_list["testall2-edge_data"])
        
        for source in core.env.source_list:
            core.env.source_list[source].traceSource(core.env.object_list)

        #core.cmd.exeCommand("CreateOctree", env=core.env)

    def loadFile (self, fileName):
        #TODO: control somehow that file extension are relative 
        #      to winged edge files
        file_name = fileName.split(".")[0]

        core.cmd.exeCommand("LoadEdge", env=core.env, file_name=file_name)

        self.pandaWorld.renderModels(core.env)
        self.commandPanel.tabSource.setEnabled(True)
        
    def addTestFrustumSource(self):
        # DA RIPRISTINARE !!!
        self.commandPanel.tabSource.setEnabled(True)
        
        # build data to create a frustum source
        obs_point    = np.array([0, 20, 0])
        obs_plane_v1 = np.array([-2, 14, -2])
        obs_plane_v2 = np.array([-2, 14, +2])
        obs_plane_v3 = np.array([+2, 14, +2])
        obs_plane_v4 = np.array([+2, 14, -2])
        src_name = "Debug_FrustumSource"
        
        core.env.source_dict[src_name]= FrustumSource( src_name, obs_point, [obs_plane_v1, obs_plane_v2, obs_plane_v3, obs_plane_v4 ] )
        self.pandaWorld.renderSources(core.env)
        self.commandPanel.tabCalculation.setEnabled(True)

    def traceSources(self):
        for name, source in core.env.source_dict.iteritems():
            
            source.traceSource(source.obs_point, source.obs_poly_vertex, core.env.object_dict, 1)
            #source.traceSourceOLD(core.env.object_dict)
            source.renderIllumination(source.source_tracing_results)
            