###########################################################################
# ThreeDR.py
#
# Monterey Bay Aquarium Research Institute   2019
# All rights reserved.
#
# GUI for managing 3DR tasks and visualizing data.
#
#
# Created:  Sept 2019      Paul Roberts
#
###########################################################################

import time
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
import os
#import lcm
import glob
import cv2
import imageio
from ThreadedProcessor import VideoManager


pg.mkQApp()

## Stylesheets
styleNormalProg = 'QProgressBar {border: 2px solid grey;border-radius: 5px;text-align: center;font: 75 12pt "MS Shell Dlg 2"; } QProgressBar::chunk { background-color: #adcdff;}"'
styleWarnProg = 'QProgressBar {border: 2px solid grey;border-radius: 5px;text-align: center;font: 75 12pt "MS Shell Dlg 2"; } QProgressBar::chunk { background-color: #f5c842;}"'
styleErrProg = 'QProgressBar {border: 2px solid grey;border-radius: 5px;text-align: center;font: 75 12pt "MS Shell Dlg 2"; } QProgressBar::chunk { background-color: #f54242;}"'

## Define main window class from template
path = os.path.dirname(os.path.abspath(__file__))
uiFile = os.path.join(path, 'ThreeDR.ui')
WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile)


class BackgroundManager(QtCore.QThread):

    # signals
    #rawFrame = QtCore.Signal(object)
    imagesLoaded = QtCore.Signal(float)
    loadingDone = QtCore.Signal(bool)
    processingDone = QtCore.Signal(bool)
    setRawFrames = QtCore.Signal(object)
    setProcFrames = QtCore.Signal(object)
    set3DRData = QtCore.Signal(object)

    def __init__(self, video_path):
        QtCore.QThread.__init__(self)
        self.video_loaded = False
        self.playing = False
        self.process_frames = False
        self.frame_rate = 30
        self.frame_index = 0
        self.last_displayed_index = 0
        self.mgr = VideoManager(video_path, ncpus=32)

    def __del__(self):
        self.wait()

    def set_process_frames(self):
        self.process_frames = True

    def run(self):

        while self.isRunning:

            if not self.video_loaded:
                self.mgr.load_FVS()
                self.mgr.chunk_video()
                self.video_loaded = True
                self.loadingDone.emit(True)
                self.setRawFrames.emit(self.mgr.frames)

            if self.process_frames:
                self.mgr.do_process()
                self.processingDone.emit(True)
                self.setProcFrames.emit(self.mgr.get_proc_frames())
                self.set3DRData.emit(self.mgr.get_3dr_data())
                self.process_frames = False

            time.sleep(0.1)


class MainWindow(TemplateBaseClass):

    previousFrameSignal = QtCore.Signal(int)
    nextFrameSignal = QtCore.Signal(int)
    setFrameSignal = QtCore.Signal(int)

    def __init__(self, argv):

        # simple error check, add getopt later
        if len(sys.argv) < 2:
            print('Please supply video file path as first argument.')
            exit()

        self.data_path = argv[1]
        self.playing = False
        self.video_scale = 2

        self.frame_index = 0

        TemplateBaseClass.__init__(self)
        self.setWindowTitle('ThreeDR - Python - Qt')

        self.rawImageDisplayScale = 255
        self.lastImage = []
        self.rawFrames = []
        self.procFrames = []
        self.three_dr_data = []

        # Create the main window
        self.ui = WindowTemplate()
        self.ui.setupUi(self)

        # Video input and processing thread
        self.background_manager = BackgroundManager(self.data_path)
        self.background_manager.setRawFrames.connect(self.setRawFrames)
        self.background_manager.setProcFrames.connect(self.setProcFrames)
        self.background_manager.set3DRData.connect(self.set3DRData)
        self.background_manager.mgr.frames_loaded.connect(self.updateFramesInFile)
        self.background_manager.mgr.frames_processed.connect(self.updateFramesProcessed)
        self.background_manager.loadingDone.connect(self.loadingDone)
        self.ui.processFramesButton.clicked.connect(self.background_manager.set_process_frames)
        self.background_manager.start()

        # UI handlers
        self.ui.frameSelector.valueChanged.connect(self.ui.frameNumberSpinBox.setValue)
        self.ui.playButton.clicked.connect(self.togglePlay)
        self.ui.prevButton.clicked.connect(self.prevFrame)
        self.ui.nextButton.clicked.connect(self.nextFrame)
        self.ui.rawDisplayScale.valueChanged.connect(self.setScale)
        self.ui.frameSelector.valueChanged.connect(self.setFrame)
        self.ui.playbackRate.editingFinished.connect(self.updatePlaybackRate)

        self.ui.playbackRate.setText('30.0')

        # timer used to play video, call playback in a separate thread
        self.playback_timer = QtCore.QTimer()
        self.playback_timer.timeout.connect(self.playback)

        # setup opengl window
        ## Simple surface plot example
        ## x, y values are not specified, so assumed to be 0:50
        self.scatterPlot1 =None
        self.three_d_points = None

        vb = pg.ViewBox()
        self.ui.rawVideoView.setCentralItem(vb)
        vb.setAspectLocked()
        self.rawImage = pg.ImageItem()
        vb.addItem( self.rawImage)
        vb.setRange(QtCore.QRectF(0, 0, 960, 540))
        #self.tfMouseMoved = pg.SignalProxy(vb.scene().sigMouseMoved, rateLimit=60, slot=self.tfMouseMoved)
        #self.tfMouseMoved = pg.SignalProxy(vb.scene().sigMouseClicked, rateLimit=60, slot=self.tfMouseClicked)
        #self.tfvb = vb

        vb = pg.ViewBox()
        self.ui.procVideoView.setCentralItem(vb)
        vb.setAspectLocked()
        self.procImage = pg.ImageItem()
        vb.addItem(self.procImage)
        vb.setRange(QtCore.QRectF(0, 0, 960, 540))

        # Show the main window
        self.show()



    def playback(self):
        self.setFrameIndex(self.frame_index+1)

    def setFrameIndex(self, index):

        self.frame_index = index % self.background_manager.mgr.frames_in_file
        if self.frame_index < 0:
            self.frame_index = 0
        # keep spingbox up to date with window
        self.ui.frameNumberSpinBox.setValue(self.frame_index)
        self.drawRawFrame()
        self.drawProcFrame()

    def setRawFrames(self, frames):
        self.rawFrames = frames
        self.drawRawFrame()

    def setProcFrames(self,frames):
        self.procFrames = frames
        self.drawProcFrame()

    def set3DRData(self,three_dr_data):
        self.three_dr_data = three_dr_data
        self.draw3DRData()

    def loadingDone(self, done):
        self.ui.frameSelector.setMaximum(self.background_manager.mgr.frames_in_file)
        self.ui.frameNumberSpinBox.setMaximum(self.background_manager.mgr.frames_in_file)
        self.ui.frameSelector.setValue(0)
        self.ui.frameNumberSpinBox.setValue(0)

    def updateFramesProcessed(self,processed_frames):
        self.ui.framesProcessedLabel.setText(str(processed_frames))

    def updateFramesInFile(self, loaded_frames):
        self.ui.framesLoadedLabel.setText(str(loaded_frames))

    def tfMouseClicked(self,evt):
        pos = evt[0].pos()
        if self.tfvb.sceneBoundingRect().contains(pos):
            mousePoint = self.tfvb.mapSceneToView(pos)
            print([mousePoint.x(),mousePoint.y()])

    def tfMouseMoved(self,evt):
        pos = evt[0]
        if self.tfvb.sceneBoundingRect().contains(pos):
            mousePoint = self.tfvb.mapSceneToView(pos)
            print([mousePoint.x(),mousePoint.y()])

    def updatePlaybackRate(self):
        try:
            self.background_manager.frame_rate = float(self.ui.playbackRate.text())
        except TypeError:
            pass

    def togglePlay(self):
        if self.playing:
            self.playing = False
            self.background_manager.playing = False
            self.ui.playButton.setStyleSheet("")
            self.playback_timer.stop()
        else:
            self.playing = True
            self.background_manager.playing = True
            self.ui.playButton.setStyleSheet("background-color: #999;")
            self.playback_timer.start(1000/float(self.ui.playbackRate.text()))

    def setScale(self):
        self.rawImageDisplayScale = self.ui.rawDisplayScale.value()
        scale = [0, self.rawImageDisplayScale]
        if len(self.lastImage):
            self.totalFocusImage.setImage(self.lastImage, autoLevels=False, levels=scale, autoDownsample=False)
        # print(self.rawImageDisplayScale)

    def setFrame(self):
        self.setFrameIndex(self.ui.frameSelector.value())

    def prevFrame(self):
        self.setFrameIndex(self.frame_index - 1)

    def nextFrame(self):
        self.setFrameIndex(self.frame_index + 1)


    def drawRawFrame(self):
        scale = [0, self.rawImageDisplayScale]
        output = self.rawFrames[self.frame_index]
        output = cv2.resize(output, (int(output.shape[1]/self.video_scale), int(output.shape[0]/self.video_scale)))
        output = cv2.transpose(output)
        self.rawImage.setImage(output, autoLevels=False, levels=scale, autoDownsample=False)

    def drawProcFrame(self):
        scale = [0, self.rawImageDisplayScale]
        output = self.procFrames[self.frame_index]
        output = cv2.resize(output, (int(output.shape[1]/self.video_scale), int(output.shape[0]/self.video_scale)))
        output = cv2.transpose(output)
        self.procImage.setImage(output, autoLevels=False, levels=scale, autoDownsample=False)

    def draw3DRData(self):

        self.three_d_points = np.hstack(
            (self.three_dr_data[0].points/1000,
             np.ones((self.three_dr_data[0].points.shape[0], 1))))
        for i in range(1, len(self.three_dr_data)):
            tmp = np.hstack((self.three_dr_data[i].points/1000,
                                       i/600*np.ones((self.three_dr_data[i].points.shape[0], 1))))
            self.three_d_points = np.concatenate((self.three_d_points,tmp))

        if self.scatterPlot1 is None:
            self.scatterPlot1 = gl.GLScatterPlotItem(pos=self.three_d_points, color=(1, 1, 1, 1),size=0.01,pxMode=True)
            self.ui.pointCloudView.addItem(self.scatterPlot1)
        else:
            self.scatterPlot1.setData(pos=self.three_d_points, color=(1, 1, 1, 1))


###########################################################################
#  Main program
###########################################################################
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
    import sys

    win = MainWindow(sys.argv)

    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()