#! /usr/bin/env python3

# -*- coding: utf-8 -*-

__author__ = "Saulius Lukse"
__copyright__ = "Copyright 2016-2017, kurokesu.com"
__version__ = "0.5"
__license__ = "GPL"


'''
Changelog
=========

v0.2
----
* initlal up and running version

v0.3
----
* Read realtime lens position
* Save value to json on exit
* Remove init lens and power save buttons. Not needed any more
* Load settings on boot, set dials to correct position
* add camera view / 640x480 @ 30fps? / some issues setting to MJPG mode

v0.4
----
* basic software autofocus implementation

v0.5
----
* Converted to PyQt5 by Nick Zanobini

'''

import sys
import os
from PyQt5 import QtCore, QtGui, QtWidgets, uic
import threading
import queue
import utils
import cv2

print(sys.version)

if os.name == 'nt':
    from serial.tools.list_ports_windows import *
elif sys.platform == 'darwin':
    from serial.tools.list_ports_osx import *
    from serial.tools.list_ports_vid_pid_osx_posix import *
elif os.name == 'posix':
    from serial.tools.list_ports_posix import *
    from serial.tools.list_ports_vid_pid_osx_posix import *
else:
    raise ImportError("Serial error: no implementation for your platform ('%s') available" % (os.name,))

form_class = uic.loadUiType("gui.ui")[0]
ser = serial.Serial()
q = queue.Queue()
q_labels = queue.Queue()
max_1 = 55000
max_2 = 21000

autofocus_step = 200
autofocus_best = 0

running = True
json_file = 'settings.json'
config = {}
boot_sequence = True
video_running = False
capture_thread = None
q_video = queue.Queue()

autofocus_state = 0
focus_data = []


config = utils.json_boot_routine(json_file)


def get_blur(frame, scale):
    frame = cv2.resize(frame, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    fm = cv2.Laplacian(gray, cv2.CV_64F).var()
    return fm


def grab(cam, queue, width, height, fps):
    global running
    capture = cv2.VideoCapture(cam)
    capture.set(cv2.CAP_PROP_FRAME_WIDTH, width)
    capture.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
    capture.set(cv2.CAP_PROP_FPS, fps)

    while(running):
        frame = {}
        capture.grab()
        retval, img = capture.retrieve(0)
        frame["img"] = img
        frame["1"] = config["1"]
        frame["2"] = config["2"]

        blur = get_blur(img, 0.05)
        frame["blur"] = blur

        if queue.qsize() < 10:
            queue.put(frame)
        else:
            print(queue.qsize())


class OwnImageWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(OwnImageWidget, self).__init__(parent)
        self.image = None

    def setImage(self, image):
        self.image = image
        sz = image.size()
        self.setMinimumSize(sz)
        self.update()

    def paintEvent(self, event):
        qp = QtGui.QPainter()
        qp.begin(self)
        if self.image:
            qp.drawImage(QtCore.QPoint(0, 0), self.image)
        qp.end()


class MyWindowClass(QtWidgets.QMainWindow, form_class):
    def __init__(self, parent=None):
        QtWidgets.QMainWindow.__init__(self, parent)
        self.setupUi(self)

        self.btn_video.clicked.connect(self.start_video_clicked)
        self.btn_autofocus.setEnabled(False)

        self.push_zero.clicked.connect(self.zero_clicked)
        self.push_zero.setEnabled(False)

        self.btn_connect.clicked.connect(self.btn_connect_clicked)
        self.btn_autofocus.clicked.connect(self.btn_autofocus_clicked)

        self.dial_1.valueChanged.connect(self.adjust_1)
        self.dial_2.valueChanged.connect(self.adjust_2)

        self.group_controls.setEnabled(False)

        self.dial_1.setMaximum(max_1)
        self.dial_2.setMaximum(max_2)

        # setup video timer and widget
        w = self.widget_video.width()
        h = self.widget_video.height()
        self.widget_video = OwnImageWidget(self.widget_video)
        self.widget_video.resize(w, h)

        # setup update frame thread
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.update_frame)
        self.timer.start(1)

        # setup com port comunication
        self.combo_ports.clear()
        com_ports = sorted(comports())
        for port, desc, hwid in com_ports:
            self.combo_ports.addItem(port)

        self.timer1 = QtCore.QTimer(self)
        self.timer1.timeout.connect(self.update_pos)
        self.timer1.start(1)

    def zero_clicked(self):
        cmd = 'G92 X0 Y0\n'
        ser.write(bytes(cmd, 'utf8'))

    def update_frame(self):
        global autofocus_state
        global autofocus_best
        global focus_data

        if not q_video.empty():
            self.btn_video.setText('Camera is live')
            # self.btn_autofocus.setEnabled(True)
            frame = q_video.get()
            img = frame["img"]
            # print frame["1"], frame["2"]

            img_height, img_width, img_colors = img.shape

            scale_w = float(self.widget_video.width()) / float(img_width)
            scale_h = float(self.widget_video.height()) / float(img_height)
            scale = min([scale_w, scale_h])

            if scale == 0:
                scale = 1

            img = cv2.resize(img, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            height, width, bpc = img.shape
            bpl = bpc * width
            image = QtGui.QImage(img.data, width, height, bpl, QtGui.QImage.Format_RGB888)
            self.widget_video.setImage(image)

            blur = frame["blur"]
            self.label_blur.setText('%d' % blur)
            self.progress_focus.setValue(blur)

            # here starts autofocus code
            # --------------------------

            # just wait and do nothing
            if (autofocus_state == 1) and (int(frame["1"]) != 0):
                self.btn_autofocus.setText('Going to 0 focus point...')
                focus_data = []
                pass

            # swith to next state
            if (autofocus_state == 1) and (int(frame["1"]) == 0):
                autofocus_state = 2

            # collect data and move lens
            if (autofocus_state == 2) and (int(frame["1"]) < 55000):
                self.btn_autofocus.setText('Analyzing frames...')

                # save data
                metadata = frame
                metadata["img"] = None  # don't collect whole frame
                focus_data.append(metadata)

                # move lens
                value = self.dial_1.value()
                value += autofocus_step
                self.dial_1.setValue(value)

            if (autofocus_state == 2) and (int(frame["1"]) == 55000):
                autofocus_state = 3
                self.btn_autofocus.setText('Saving data...')
                # save collected data

            if (autofocus_state == 3):
                autofocus_state = 4
                max_focus_val = 0.0
                max_focus_pos = 0
                for frame in focus_data:
                    if max_focus_val < float(frame["blur"]):
                        max_focus_val = float(frame["blur"])
                        max_focus_pos = int(frame["1"])

                print(max_focus_val, max_focus_pos)
                autofocus_best = max_focus_pos + 800  # backlash in a lens

                self.btn_autofocus.setText('Analyzing data...')

            if (autofocus_state == 4):
                autofocus_state = 5
                self.dial_1.setValue(autofocus_best)
                self.btn_autofocus.setText('Moving to best spot...')

            if (autofocus_state == 5) and (int(frame["1"]) == autofocus_best):
                autofocus_state = 0
                self.btn_autofocus.setText('Autofocus')
                self.btn_autofocus.setEnabled(True)
                self.dial_1.setEnabled(True)
                self.dial_2.setEnabled(True)

            # self.label_temp.setText(str(len(focus_data)))
            # self.label_temp.setText(str(frame["1"]))

    def btn_autofocus_clicked(self):
        global autofocus_state

        if autofocus_state == 0:
            self.btn_autofocus.setEnabled(False)
            self.dial_1.setEnabled(False)
            self.dial_2.setEnabled(False)
            autofocus_state = 1
            # self.btn_autofocus.setText('Going to 0 focus point...')
            self.dial_1.setValue(0)  # change focus to 0

        '''
        start autofocus thread + state machine

        1 - set dial focus = 0
        2 - wait until feedback == 0
        3 - change focus slowly until feedback == max, collecta/analyze frames and save
        4 - analyze where is the best focus
        5 - goto 0
        6 - goto best focus position

        '''

    def start_video_clicked(self):
        global video_running
        video_running = True
        capture_thread.start()
        self.btn_video.setEnabled(False)
        self.btn_autofocus.setEnabled(True)
        self.btn_video.setText('Starting...')

    def update_pos(self):
        global boot_sequence
        if not q_labels.empty():
            f = q_labels.get()
            if not boot_sequence:
                self.label_1_real.setText(f["X"])
                self.label_2_real.setText(f["Y"])
            else:
                # Send command to adjust offset to controller
                cmd = 'G92 X' + str(config["1"]) + ' Y' + str(config["2"]) + '\n'
                ser.write(bytes(cmd, 'utf8'))

                # update dials
                self.dial_1.setValue(int(config["1"]))
                self.dial_2.setValue(int(config["2"]))

                # enable dials
                self.dial_2.setEnabled(True)
                self.dial_1.setEnabled(True)
                self.push_zero.setEnabled(True)
                boot_sequence = False

    def btn_connect_clicked(self):
        global ser
        try:
            ser.port = str(self.combo_ports.currentText())
            ser.baudrate = 115200
            ser.timeout = 2
            ser.open()
            ser.flushInput()
            ser.flushOutput()
            config["port"] = str(self.combo_ports.currentText())

        except Exception as e:
            self.reply = QtWidgets.QMessageBox.warning(self, 'Serial port error', str(e))
            return 0

        self.group_controls.setEnabled(True)
        self.btn_connect.setEnabled(False)
        self.combo_ports.setEnabled(False)
        self.dial_2.setEnabled(False)
        self.dial_1.setEnabled(False)
        # self.push_zero.setEnabled(False)

    def adjust_2(self):
        value = self.dial_2.value()
        self.label_2.setText(str(value))
        cmd = 'G0 Y' + str(value) + '\n'
        ser.write(bytes(cmd, 'utf8'))

    def adjust_1(self):
        value = self.dial_1.value()
        self.label_1.setText(str(value))
        cmd = 'G0 X' + str(value) + '\n'
        ser.write(bytes(cmd, 'utf8'))

    def closeEvent(self, event):
        global config
        global running
        if not boot_sequence:
            utils.json_exit_routine(json_file, config)
        running = False


def serial_sender():
    global ser

    line_old = None
    while running:
        if ser.isOpen():
            try:
                rd = str(ser.readline())
                line = rd.rstrip()
                if line != line_old:
                    feedback = {}
                    line_2 = line.split(',')
                    for l in line_2:
                        l_s = l.split("=")
                        if len(l_s) == 2:
                            feedback[str(l_s[0])] = l_s[int(1)]

                    q_labels.put(feedback)

                    if not boot_sequence:
                        config["1"] = feedback["X"]
                        config["2"] = feedback["Y"]

                line_old = line
            except:
                pass


capture_thread = threading.Thread(target=grab, args=(0, q_video, 640, 360, 30))
threading.Thread(target=serial_sender).start()
app = QtWidgets.QApplication(sys.argv)
myWindow = MyWindowClass(None)
myWindow.show()
app.exec_()