# Copyright 3D Control Systems, Inc. All Rights Reserved 2017-2019.
# Built in San Francisco.

# This software is distributed under a commercial license for personal,
# educational, corporate or any other use.
# The software as a whole or any parts of it is prohibited for distribution or
# use without obtaining a license from 3D Control Systems, Inc.

# All software licenses are subject to the 3DPrinterOS terms of use
# (available at https://www.3dprinteros.com/terms-and-conditions/),
# and privacy policy (available at https://www.3dprinteros.com/privacy-policy/)

import os
import logging
import time
import threading

import log
import config
from awaitable import Awaitable

try:
    from PySide6.QtWidgets import QApplication, QMainWindow
    from PySide6.QtCore import QCoreApplication, QUrl, QMetaObject, Qt, QSize, QtMsgType, qInstallMessageHandler
    from PySide6.QtQuick import QQuickView
    from PySide6.QtGui import QIcon
except ImportError as e:
    logging.getLogger(__name__).exception("Qt interface disabled - Import error. No libs?")
    qt_found = False
else:
    qt_found = True
    from .core_model import CoreModel
    from .network_model import NetworkModel
    from .printer_model import PrinterModel
    from .wizard_model import WizardModel
    from .files_model import FilesModel


class QtInterface(threading.Thread, Awaitable):
    PACKAGE_PATH = os.path.dirname(__file__)
    QML_PATH = 'qml'
    MAIN_QML_PATH = 'main.qml'
    NAME = "QtInterface"

    def __init__(self, app):
        self.logger = logging.getLogger(__name__)
        self.app = app
        self.qt_app = None
        self.verbose_qml_log = config.get_settings()['logging'].get('qml', False)
        threading.Thread.__init__(self, name=self.NAME, daemon=True)
        Awaitable.__init__(self, app)

    def qt_message_handler(self, mode, context, message):
        if self.verbose_qml_log:
            if mode == QtMsgType.QtInfoMsg:
                self.logger.info(message)
            elif mode == QtMsgType.QtWarningMsg:
                self.logger.warning(message)
            elif mode == QtMsgType.QtCriticalMsg or mode == QtMsgType.QtFatalMsg:
                self.logger.critical(message)
            else:
                self.logger.debug(message)
        else:
            print(message)

    def detect_multihead(self):
        self.desktop = self.qt_app.desktop()
        self.screens_number = self.desktop.numScreens()
        # self.screens_number = QDesktopWidget.numScreens(self.desktop)
        self.logger.info(f"Screens number: {self.screens_number}")
        self.logger.info(f"Geometry:{self.desktop.geometry()}")
        self.primary_screen = self.desktop.primaryScreen()
        for screen in range(0, self.screens_number):
            if screen != self.primary_screen:
                self.secondary_screen = screen
                break
        else:
            self.secondary_screen = None
        self.logger.info("Virtual desktop:" + str(self.desktop.isVirtualDesktop()))
        self.logger.info(f"Screens: {self.primary_screen} {self.secondary_screen}")

    @log.log_exception
    def run(self):
        if not qt_found:
            self.logger.warning("Can't import PySide2. Skipping Qt interface.")
            return
        os.environ["QML_XHR_ALLOW_FILE_READ"] = "1"
        os.environ["QML_XHR_ALLOW_FILE_WRITE"] = "1"
        qInstallMessageHandler(self.qt_message_handler)
        self.qt_app = QApplication([])
       # self.detect_multihead() # TODO: fix for qt6

        self.qt_app.setQuitOnLastWindowClosed(False)
        self.view = QQuickView()
        self.view.setResizeMode(QQuickView.SizeRootObjectToView)
        self.view.closing.connect(self.app.raise_stop_flag)
        self.view.engine().addImportPath(os.path.join(self.PACKAGE_PATH, self.QML_PATH))
        #TODO automate models loading and initialization
        self.core_model = CoreModel(self.app)
        self.network_model = NetworkModel(self.core_model)
        self.printer_model = PrinterModel(self.core_model)
        #self.wizard_model = WizardModel(self.core_model)
        self.files_model = FilesModel(self.core_model)
        self.view.setMinimumSize(QSize(800, 480))
        self.view.setTitle("3dprinteros client")
        icon_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '3dprinteros-client.ico'))

        self.view.rootContext().setContextProperty('coreModel', self.core_model)
        self.view.rootContext().setContextProperty('networkModel', self.network_model)
        self.view.rootContext().setContextProperty('printerModel', self.printer_model)
        # self.view.rootContext().setContextProperty('wizardModel', self.wizard_model)
        self.view.rootContext().setContextProperty('filesModel', self.files_model)
        self.view.setSource(QUrl(os.path.join(self.PACKAGE_PATH, self.QML_PATH, self.MAIN_QML_PATH)))

        if os.path.exists(icon_path):
            self.view.setIcon(QIcon(icon_path))

        if 0: # a hack to fix a crash of wayland
            screen_rect = self.qt_app.desktop().screenGeometry()
            width, height = screen_rect.width(), screen_rect.height()
            self.view.setWidth(width)
            self.view.setHeight(height)
            self.view.showFullScreen()
        else:
            if self.core_model.resolution is None:
                self.view.setWidth(self.core_model.DEFAULT_RESOLUTION[0])
                self.view.setHeight(self.core_model.DEFAULT_RESOLUTION[1])
            else:
                self.view.setWidth(self.core_model.resolution[0])
                self.view.setHeight(self.core_model.resolution[1])
            self.view.show()

        self.logger.info("Starting Qt loop...")
        self.qt_app.exec_()
        self.qt_app.deleteLater()
        self.logger.info("Qt loop finished")

    def close(self):
        if threading.current_thread().name == "QtInterface":
            self.logger.warning("Close called from QtInterface thread. Skipping close Qt interface.")
            return
        if self.qt_app:
            try:
                self.logger.info("Invoking QApplication close")
                QMetaObject.invokeMethod(QApplication.instance(), "closeAllWindows", Qt.BlockingQueuedConnection)
                QMetaObject.invokeMethod(self.view, "deleteLater", Qt.BlockingQueuedConnection)
                QMetaObject.invokeMethod(self.core_model, "deleteLater", Qt.BlockingQueuedConnection)
                QMetaObject.invokeMethod(self.network_model, "deleteLater", Qt.BlockingQueuedConnection)
                QMetaObject.invokeMethod(self.printer_model, "deleteLater", Qt.BlockingQueuedConnection)
                QMetaObject.invokeMethod(self.files_model, "deleteLater", Qt.BlockingQueuedConnection)
                self.logger.info("QApplication close was invoked")
                #self.logger.debug('Active threads:' + str(threading.enumerate()))
                #self.logger.debug('Current thread' + str(threading.currentThread()))
                QCoreApplication.instance().processEvents()
                QMetaObject.invokeMethod(QApplication.instance(), "quit", Qt.BlockingQueuedConnection)
            except RuntimeError as e:
                self.logger.warning('Runtime race at closing Qt application detected:' + str(e))
            time.sleep(0.1)
            self.logger.info("QApplication had quit")

    def _check_function(self):
        return self.qt_app or not qt_found
