import os
import pickle as pk
import sys
import time

import pyqtgraph as pg
from PyQt5 import QtCore, QtWidgets, uic
from PyQt5.QtCore import QThread, QThreadPool, QTimer
from sklearn.decomposition import IncrementalPCA

from src.KNN.faissKNN import faissKNN
from src.Projections.Python.PCA.PCAProjector import PCAProjector
from src.Projections.Python.ProjectionTransformer import ProjectionTransformer
from src.UI.Renderer2D import Renderer2D
from src.UI.Renderer3D import Renderer3D
from src.UI.SideGrip import SideGrip

baseUIClass, baseUIWidget = uic.loadUiType("GUI_BASE.ui")

# use loaded ui file in the logic class
class App(baseUIWidget, baseUIClass):
    _gripSize = 8

    def __init__(self, parent=None):
        super(App, self).__init__(parent)
        #UI related
        self.setupUi(self)
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)

        #TODO, change these to lists, to allow multiple projectors, transformers
        self.PCAProjector = None
        self.transformer = None
        self.knn = None
        #All the models created / loaded in
        self.models = []
        self.selectModel.addItem("",0)

        #For updating the percentages in the GUI
        #self.percentageTimer = QTimer()
        #self.percentageTimer.start(100)

        #Resizing related
        self.oldPos = QtCore.QPoint(0,0)
        self.sideGrips = [
            SideGrip(self, QtCore.Qt.LeftEdge), 
            SideGrip(self, QtCore.Qt.TopEdge), 
            SideGrip(self, QtCore.Qt.RightEdge), 
            SideGrip(self, QtCore.Qt.BottomEdge), 
        ]
        # corner grips should be "on top" of everything, otherwise the side grips
        # will take precedence on mouse events, so we are adding them *after*;
        # alternatively, widget.raise_() can be used
        self.cornerGrips = [QtWidgets.QSizeGrip(self) for i in range(4)]


        self.startVisualisation()
        self.handleConnections()
        self.setupPercentageLoaders()
    
    def setupPercentageLoaders(self):
        self.setValue(self.labelPercentageFitting,self.circularProgressFitting," rgb(115, 185, 255)",0)
        self.setValue(self.labelPercentageTransforming,self.circularProgressTransforming," rgba(255, 0, 127, 255)",0)
        self.setValue(self.labelPercentageKNN,self.circularProgressKNN,"rgb(0, 159, 0)",0)

    #Resizing as shown in https://stackoverflow.com/questions/62807295/how-to-resize-a-window-from-the-edges-after-adding-the-property-qtcore-qt-framel
    #IGNORE THIS
    @property
    def gripSize(self):
        return self._gripSize
    def setGripSize(self, size):
        if size == self._gripSize:
            return
        self._gripSize = max(2, size)
        self.updateGrips()    
    def updateGrips(self):
        self.setContentsMargins(*[self.gripSize] * 4)
        outRect = self.rect()
        # an "inner" rect used for reference to set the geometries of size grips
        inRect = outRect.adjusted(self.gripSize, self.gripSize,
            -self.gripSize, -self.gripSize)
        # top left
        self.cornerGrips[0].setGeometry(
            QtCore.QRect(outRect.topLeft(), inRect.topLeft()))
        # top right
        self.cornerGrips[1].setGeometry(
            QtCore.QRect(outRect.topRight(), inRect.topRight()).normalized())
        # bottom right
        self.cornerGrips[2].setGeometry(
            QtCore.QRect(inRect.bottomRight(), outRect.bottomRight()))
        # bottom left
        self.cornerGrips[3].setGeometry(
            QtCore.QRect(outRect.bottomLeft(), inRect.bottomLeft()).normalized())
        # left edge
        self.sideGrips[0].setGeometry(
            0, inRect.top(), self.gripSize, inRect.height())
        # top edge
        self.sideGrips[1].setGeometry(
            inRect.left(), 0, inRect.width(), self.gripSize)
        # right edge
        self.sideGrips[2].setGeometry(
            inRect.left() + inRect.width(), 
            inRect.top(), self.gripSize, inRect.height())
        # bottom edge
        self.sideGrips[3].setGeometry(
            self.gripSize, inRect.top() + inRect.height(), 
            inRect.width(), self.gripSize)
    def resizeEvent(self, event):
        QtWidgets.QMainWindow.resizeEvent(self, event)
        self.updateGrips()


    #Connecting signals to functions, for example the buttons
    def handleConnections(self):
        self.uploadFileBTN.clicked.connect(self.uploadFile)
        self.getPrecisionPCA.clicked.connect(self.getPrecision)
        self.getProjectionBTN.clicked.connect(self.getProjection)
        self.stopProjectionBTN.clicked.connect(self.stopProjection)
        self.btn_close.clicked.connect(self.closeWindow)
        self.btn_minimize.clicked.connect(self.minimizeWindow)
        self.btn_maximize_restore.clicked.connect(self.maximizeWindow)
        self.removePointsBTN.clicked.connect(self.visualiser.removeAllPoints)
        self.btn_toggle_menu.clicked.connect(self.clickedMainButton)
        self.selectModel.currentIndexChanged.connect(self.modelChanged)
        self.loadModelBTN.clicked.connect(self.loadModel)
        self.saveModelBTN.clicked.connect(self.saveModel)
        self.knnRequestBTN.clicked.connect(self.getKNNRequest)

    def getKNNRequest(self):
        knnRequest = self.knnRequest.text()
        if not self.knn:
            return

        self.knn.getKNNSemantic(knnRequest)

    
    def saveModel(self):
        pk.dump(self.PCAProjector.getModel(), open("SavedModels//SavedModel.pkl","wb"))    

    def loadModel(self):
        filePath = QtWidgets.QFileDialog.getOpenFileName(self, 'Select File',".//SavedModels")[0]
        with open(filePath,"rb") as f:
            model = pk.load(f)
        self.models.append(model)
        fileName = filePath.split('/')[-1].split('.')[0]
        self.selectModel.addItem(fileName)

    def addToVisualiser(self, results):
        self.visualiser.addPoints(results)

    def setupTransformer(self,model, path):
        if(not self.addProjection.isChecked()):
            self.visualiser.removeAllPoints()
        if self.transformer and not self.addProjection.isChecked():
            self.transformer.stop()

        #Create new transformer
        self.transformerThread = QThread()
        self.transformer = ProjectionTransformer(model,path)
        self.transformer.newPointsSignal.connect(self.addToVisualiser)
        self.transformer.progressSignal.connect(self.updateTransformPercentage)
        self.transformer.moveToThread(self.transformerThread)
        self.transformerThread.started.connect(self.transformer.run)
        self.transformerThread.start()


    def modelChanged(self,value):
        if(value == 0):
            return
        selectedModel = self.models[value-1]
        #TODO own file for transformer
        self.setupTransformer(selectedModel,self.PCAProjector.filePath)
    
    def updateFittingPercentage(self,percentage):
        if self.PCAProjector:
            self.setValue(self.labelPercentageFitting,self.circularProgressFitting," rgba(115, 185, 255,255)",percentage)

    def updateTransformPercentage(self,percentage):
        if self.transformer:
            self.setValue(self.labelPercentageTransforming,self.circularProgressTransforming," rgb(161, 0, 241)",percentage)

    def updateKNNPercentage(self,percentage):
        if self.knn:
            self.setValue(self.labelPercentageKNN,self.circularProgressKNN,"rgb(0, 159, 0)",percentage)

    #For the custom top-row buttons
    def minimizeWindow(self):
        self.showMinimized()
        self.updateGrips()
    def clickedMainButton(self):
        print("clicked main")
        self.frame_left_menu.setVisible(not self.frame_left_menu.isVisible())
    def maximizeWindow(self):
        self.showMaximized()
        self.updateGrips()
    def closeWindow(self):
        self.close()

    #Setting the percentage values 
    def setValue(self, labelPercentage, progressBarName, color, value):
            # CONVERT VALUE TO INT
            value = round(value)

            htmlText = """<p align="center"><span style=" font-size:20pt;">{VALUE}</span><span style=" font-size:15pt; vertical-align:super;">%</span></p>"""
            # HTML TEXT PERCENTAGE
            labelPercentage.setText(htmlText.replace("{VALUE}", str(value)))

            # CALL DEF progressBarValue
            self.progressBarValue(value, progressBarName, color)
    def progressBarValue(self, value, widget, color):

        # PROGRESSBAR STYLESHEET BASE
        styleSheet = """
        QFrame{
            border-radius: 50px;
            background-color: qconicalgradient(cx:0.5, cy:0.5, angle:90, stop:{STOP_1} rgba(85, 85, 127, 100), stop:{STOP_2} {COLOR});
            }
        """

        # GET PROGRESS BAR VALUE, CONVERT TO FLOAT AND INVERT VALUES
        # stop works of 1.000 to 0.000
        progress = (100 - value) / 100.0

        # GET NEW VALUES
        stop_1 = str(progress - 0.001)
        stop_2 = str(progress)

        # FIX MAX VALUE
        if value == 100:
            stop_1 = "1.000"
            stop_2 = "1.000"

        # SET VALUES TO NEW STYLESHEET
        newStylesheet = styleSheet.replace("{STOP_1}", stop_1).replace("{STOP_2}", stop_2).replace("{COLOR}", color)

        # APPLY STYLESHEET WITH NEW VALUES
        widget.setStyleSheet(newStylesheet)

    #Default visualisation
    def startVisualisation(self):
        self.visualiser = Renderer2D(1)
        self.stackedWidget.insertWidget(0,self.visualiser.getWidget())

    #Custom dragging window        
    def mousePressEvent(self, event):
        print("test")
        self.oldPos = event.globalPos()
        print("Mousepress")
    def mouseMoveEvent(self, event):
        if self.frame_label_top_btns.underMouse():
            delta = QtCore.QPoint (event.globalPos() - self.oldPos)
            self.move(self.x() + delta.x(), self.y() + delta.y())
            self.oldPos = event.globalPos()

    #When stop Projection button clicked
    def stopProjection(self):
        if not self.transformer:
            print("No transformer yet")
            return
        self.transformer.stop()

    #Because they are running on separate threads, they can keep running even though main GUI is closed.
    #Overwrite closeEvent to stop them
    def closeEvent(self,event):
        print("Close")
        if self.transformer:
            self.transformer.stop()
        if self.PCAProjector:
            self.PCAProjector.stop()

    def getProjection(self):
        #Get current model from projector
        model = self.PCAProjector.getModel()
        path = self.PCAProjector.filePath
        
        self.models.append(model)
        modelName = "PCAModel_v" + str(len(self.models))
        self.selectModel.addItem(modelName)

        #TODO own file for transformer
        self.setupTransformer(model,path)        

    #TODO Unknown what to do with this, dont know if other projectors will suport this. Leave it for now since only using PCA
    def getPrecision(self):
        if not self.PCAProjector:
            print("No projector yet")
            return
        print(self.PCAProjector.getPrecision())



    def setupKNN(self,filePath):
        self.knnThread = QThread()
        self.knn = faissKNN(filePath[0])
        self.knn.moveToThread(self.knnThread)
        self.knnThread.started.connect(self.knn.run)
        self.knn.progressSignal.connect(self.updateKNNPercentage)
        self.knnThread.start()


    #Start the projection (PCA is only one for now)
    def uploadFile(self):
        filePath = QtWidgets.QFileDialog.getOpenFileName(self, 'Select File', os.path.expanduser('~\\Documents\\'))
        self.setupKNN(filePath)

        self.PCAProjectorThread = QThread()
        self.PCAProjector = PCAProjector(filePath[0])
        self.PCAProjector.progressSignal.connect(self.updateFittingPercentage)
        self.PCAProjector.moveToThread(self.PCAProjectorThread)
        self.PCAProjectorThread.started.connect(self.PCAProjector.run)
        self.PCAProjectorThread.start()

#QT related
def main():
    app = QtWidgets.QApplication(sys.argv)
    ui = App(None)
    ui.show()
    sys.exit(app.exec_())

main()