"""
/***************************************************************************
 MainWindow

 wide area mapping
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                             -------------------
        begin                : 2022-01-01
        git sha              : $Format:%H$
        copyright            : (C) 2022 by forschung@ciss.de
        email                : forschung@ciss.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""

__author__ = 'forschung@ciss.de'
__date__ = 'January 2022'

import os

from PyQt5 import QtGui
from PyQt5.QtCore import QUrl, QTimer
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import QFileDialog
from qgis.gui import QgisInterface


from weissflaechenkartierung.src.tools import get_class_from_frame, replace_ampersands
from weissflaechenkartierung.ui.MainWindowInterface import Ui_MainWindow
from weissflaechenkartierung.ui.base.WndBase import BaseMainWindow
from weissflaechenkartierung.ui.MessageBoxWnd import *
from weissflaechenkartierung.src.logger import LogLvl
from weissflaechenkartierung.src.analysis_ctrl import *
from weissflaechenkartierung.src.wfk_ctrl import *
from weissflaechenkartierung.src.xml.xml_config_ctrl import XMLConfigCtrl

print(os.path.dirname(os.path.realpath(__file__)))

class MainWindow(BaseMainWindow):
    web_urls = {
        "ciss_limbo": "https://www.ciss.de/datenforschung/potentialflaechen/",
        "ciss_website": "https://www.ciss.de/",
        "ciss_converter": "https://shop.ciss.de/d-xml-upload-formular.html",
        "ciss_webshop": "https://shop.ciss.de/",
        "ciss_xing": "https://www.xing.com/pages/ciss-tdi-gmbh",
        "ciss_github": "https://github.com/cisstdi"
    }

    def __init__(self, qgis_interface: QgisInterface):
        self.ui = Ui_MainWindow()
        super(MainWindow, self).__init__(dlg=self,
                                         save_last_pos=False,
                                         shadow=True,
                                         resize=True,
                                         move_window=True)
        # wfk controller
        self._wfk_ctrl = WfkCtrl()
        self._is_wfk_running: bool = False
        self._error_wfk_processed: bool = False
        self._run_with_errors: bool = False

        # analysis controller
        self._analysis_ctrl = AnalysisCtrl()
        self._is_analysis_running: bool = False
        self._error_analysis_processed: bool = False

        # msg box
        self._msg_box = MessageBoxWindow(self)

        # window functions
        self._WindowFunctions = WindowFunctions(self)

        # qgis interface
        self._QGisInterface = QGisInterface(qgis_interface)
        # bind signal -> if the map canvas changes refresh the layer list
        self._QGisInterface.get_inter_face().mapCanvas().mapCanvasRefreshed.connect(self._refresh_current_layer_list)

        # load settings
        self._settings = QSettings('wfk_tool', 'wfk_tool')
        self._last_cfg_path: str = self._settings.value("LastCfg", "")
        self.ui.checkBox_wfk_one_run.setChecked(self._settings.value("WfkOneRun", type=bool))
        self.ui.checkBox_wfk_protocol_layers.setChecked(self._settings.value("ProtocolLayers", type=bool))

        # TODO ###############################################################
        drive, path_and_file = os.path.splitdrive(__file__)
        f_dir, f_name = os.path.split(path_and_file)
        m = f_dir.replace(r'/', os.sep).split(os.sep)
        c = 0
        demo_cfg_path = ""
        for a in m:
            if c == len(m) - 1:
                break
            c += 1
            demo_cfg_path += a + os.sep

        demo_cfg_path += "wfk_demo.cfg"
        if os.path.exists(demo_cfg_path):
            self._last_cfg_path = demo_cfg_path


        # TODO  ###############################################################

        # check the last config path
        if os.path.exists(self._last_cfg_path):
            self.ui.le_wfk_config_path.setText(os.path.split(self._last_cfg_path)[1])
            self.ui.le_wfk_config_path.setToolTip(self._last_cfg_path)
            QTimer.singleShot(0, lambda: self._load_xml_sequence())
        else:
            self._last_cfg_path = ""  # if the last config path does not exists

        # create your own menu:
        # 1 - open QT Designer with limbo_wfk_gui.ui
        # 2 - add a new page to page widget
        # 3 - rebuild the ui with pyuic5
        # 4 - remove the import of the resource_rc file at the end of the build .py file
        # 5 - add a new dict entry to menu_pages in the WndFunctions.py
        # 6 - call [add_new_btn_to_menu] method with corresponding parameters
        self._WindowFunctions.add_new_btn_to_menu(obj_name="btn_menu_wfk")
        self._WindowFunctions.add_new_btn_to_menu(obj_name="btn_menu_analyze")
        self._WindowFunctions.add_new_btn_to_menu(obj_name="btn_menu_settings")

        # select the right menu
        self._WindowFunctions.change_menu("btn_menu_wfk")
        self._WindowFunctions.last_menus.append("btn_menu_wfk")     # add the current menu to the last menu list

        ################################
        # ui user interactions

        self.ui.btn_minimize.clicked.connect(lambda: self.setWindowState(QtCore.Qt.WindowMinimized))
        self.ui.btn_exit.clicked.connect(self.close)

        # menu actions
        self.ui.btn_toggle.clicked.connect(lambda: self._WindowFunctions.toggle_menu(180, True))
        self.ui.btn_menu_ciss.clicked.connect(lambda: self._WindowFunctions.menu_button_clicked())

        # wfk
        self.ui.btn_wfk_refresh.clicked.connect(self._refresh_current_layer_list)
        self.ui.btn_wfk_start.clicked.connect(self._start_wfk)
        self.ui.btn_wfk_load_config.clicked.connect(self._load_config_clicked)

        # analysis
        self.ui.btn_analyze_start.clicked.connect(self._start_analysis)

        # back arrow
        self.ui.btn_about_change_menu.clicked.connect(self._WindowFunctions.go_to_last_menu_point)
        self.ui.btn_wfk_change_menu.clicked.connect(self._WindowFunctions.go_to_last_menu_point)
        self.ui.btn_settings_change_menu.clicked.connect(self._WindowFunctions.go_to_last_menu_point)
        self.ui.btn_analyze_change_menu.clicked.connect(self._WindowFunctions.go_to_last_menu_point)

        # settings connections
        self.ui.checkBox_settings_wnd_first.toggled.connect(self._window_first_toggled)
        self.ui.checkBox_settings_allow_errors.toggled.connect(self._allow_errors_toggled)
        self.ui.cb_settings_logger_level.currentIndexChanged.connect(self._logger_level_changed)

        # we need the toggled functions here, before we can set the new data
        self.ui.checkBox_settings_wnd_first.setChecked(self._settings.value("WindowFirst", type=bool))
        self.ui.checkBox_settings_allow_errors.setChecked(self._settings.value("AllowErrors", type=bool))

        # about connections
        self.ui.btn_about_web.clicked.connect(
            lambda: QTimer.singleShot(0,
                                      lambda: QDesktopServices.openUrl(QUrl(MainWindow.web_urls["ciss_website"]))
                                      )
        )
        self.ui.btn_about_xing.clicked.connect(
            lambda: QTimer.singleShot(0,
                                      lambda: QDesktopServices.openUrl(QUrl(MainWindow.web_urls["ciss_xing"]))
                                      )
        )
        self.ui.btn_about_github.clicked.connect(
            lambda: QTimer.singleShot(0,
                                      lambda: QDesktopServices.openUrl(QUrl(MainWindow.web_urls["ciss_github"]))
                                      )
        )
        self.ui.btn_about_alkis_data.clicked.connect(
            lambda: QTimer.singleShot(0,
                                      lambda: QDesktopServices.openUrl(QUrl(MainWindow.web_urls["ciss_webshop"]))
                                      )
        )

        # wfk connections
        self.ui.btn_wfk_limbo.clicked.connect(
            lambda: QTimer.singleShot(0,
                                      lambda: QDesktopServices.openUrl(QUrl(MainWindow.web_urls["ciss_limbo"]))
                                      )
        )
        #########################
        # ui initializing

        # settings
        for lvl in LogLvl:
            self.ui.cb_settings_logger_level.addItem(lvl.name.lower(), lvl)
        self.ui.cb_settings_logger_level.setCurrentIndex(LogLvl.DEBUG.value)

        # wfk
        self.ui.pb_wfk_status.setVisible(False)
        self.ui.pb_wfk_status.setValue(0)

        # analyze
        self.ui.pb_analyze_status.setVisible(False)
        self.ui.pb_analyze_status.setValue(0)
        self._WindowFunctions.enable_widget(enable=False, widget=self.ui.btn_analyze_start)

        # loader
        movie = QtGui.QMovie(":/other/spinning_load.gif")
        self.ui.label_movie.setMovie(movie)
        movie.setScaledSize(self.ui.label_movie.size())
        movie.start()
        self.ui.label_movie.hide()

        # load possible layers
        self._refresh_current_layer_list()

        # TODO ###############################################
        self.ui.checkBox_settings_allow_errors.setChecked(True)
        self.ui.checkBox_wfk_protocol_layers.setChecked(True)
        # TODO ###############################################

        self.ui.label_version.setText("V2.2.3")

    def closeEvent(self, event):
        """
        safe last config path before exit
        """
        if self._is_wfk_running:
            self._wfk_ctrl.stop()

        if self._is_analysis_running:
            self._analysis_ctrl.stop()

        self._settings.setValue("LastCfg",  self._last_cfg_path)
        self._settings.setValue("WindowFirst",  str(self.ui.checkBox_settings_wnd_first.isChecked()))
        self._settings.setValue("AllowErrors",  str(self.ui.checkBox_settings_allow_errors.isChecked()))
        self._settings.setValue("WfkOneRun", str(self.ui.checkBox_wfk_one_run.isChecked()))
        self._settings.setValue("ProtocolLayers", str(self.ui.checkBox_wfk_protocol_layers.isChecked()))
        super(MainWindow, self).closeEvent(event)

    def _load_config_clicked(self):
        """
        triggered if the user clicks on the open config browser button
        """
        # look in the plugin path
        current_working_dir = os.path.dirname(os.path.realpath('__file__'))
        if len(self.ui.le_wfk_config_path.text()) > 0:
            b_dir, f_name = os.path.split(self.ui.le_wfk_config_path.toolTip())
            current_working_dir = b_dir

        # open the directory
        result = QFileDialog.getOpenFileName(self,
                                             'Open file',
                                             current_working_dir,
                                             "Config files (*.cfg)")
        if not result[0] == "" and os.path.exists(result[0]):
            b_dir, f_name = os.path.split(result[0])
            self.ui.le_wfk_config_path.setText(f_name)
            self.ui.le_wfk_config_path.setToolTip(result[0])
            Logger.debug("Changed cfg path from: " + self._last_cfg_path)
            self._last_cfg_path = result[0]
            Logger.debug("Changed cfg path to: " + self._last_cfg_path)
            QTimer.singleShot(0, lambda: self._refresh_configs())
        else:
            if os.path.exists(self._last_cfg_path):
                self.ui.le_wfk_config_path.setToolTip(self.ui.le_wfk_config_path.toolTip())
                self.ui.le_wfk_config_path.setText(os.path.split(self._last_cfg_path)[1])

    def _load_xml_sequence(self, show_msg_box: bool = False):
        """
        loads the xml file and handles th eui
        """
        if os.path.exists(self._last_cfg_path):
            # open the file and translate
            with open(self._last_cfg_path, "r") as f:
                cont = f.read()

            cont = replace_ampersands(p_base=cont)
            with open(self._last_cfg_path, "r+") as f:
                f.truncate(0)
                f.write(cont)

            try:
                configs: list = XMLConfigCtrl(xml_path=self._last_cfg_path).get_configuration_sections()
                self.ui.cb_wfk_config_selection.clear()
                self.ui.cb_wfk_config_selection.addItems(configs)
                if show_msg_box:
                    self._show_msg_box(self.ui.le_wfk_config_path.text() + " loaded successful", MESSAGE_BOX_STATE.INFO)
            except Exception:
                Logger.error("Can not load: " + self._last_cfg_path, exc_info=True)
                err_text = "The Config file can not be loaded:\n" + self._last_cfg_path + \
                           "\n\nError Type: " + Logger.ex_type + "\nError Msg: " + Logger.ex_value

                self._show_msg_box(err_text, MESSAGE_BOX_STATE.ERROR)

    def _logger_level_changed(self, index):
        """
        triggered if the user changes the logger level trough the ui
        """
        data = self.ui.cb_settings_logger_level.itemData(index)
        if data != Logger.level:
            Logger.debug("Changed Logger-Level from: " + str(Logger.level) + " to " + str(data))
            Logger.level = data

    def _refresh_configs(self):
        """
        refreshes the loaded configurations
        """
        QTimer.singleShot(0, lambda: self._load_xml_sequence(True))

    def _refresh_current_layer_list(self):
        """
        refreshes the combobox with the current available layers
        """
        self.ui.cb_wfk_layer_selection.clear()
        self.ui.cb_wfk_layer_selection.addItems(self._QGisInterface.get_current_available_layers())

    def _set_ui_to_std(self):
        """
        sets the ui to the status before running
        """
        self.ui.label_movie.hide()
        self._WindowFunctions.enable_widget(enable=True, widget=self.ui.btn_wfk_start)
        self.ui.btn_wfk_start.setText("Start")
        self._WindowFunctions.enable_widget(enable=True, widget=self.ui.btn_wfk_refresh)
        self._WindowFunctions.enable_widget(enable=True, widget=self.ui.btn_wfk_change_menu)
        self._WindowFunctions.enable_widget(enable=True, widget=self.ui.btn_wfk_load_config)
        self.ui.pb_wfk_status.setVisible(False)
        self.ui.pb_wfk_status.setValue(0)
        self._WindowFunctions.enable_widget(enable=True, widget=self.ui.btn_analyze_start)
        self.ui.pb_analyze_status.setVisible(False)
        self.ui.pb_analyze_status.setValue(0)

    def _set_ui_to_loading_wfk(self):
        """
        set the ui to loading status
        """
        self._WindowFunctions.enable_widget(enable=True, widget=self.ui.btn_wfk_start)
        self.ui.btn_wfk_start.setText("Stop")
        self._WindowFunctions.enable_widget(enable=False, widget=self.ui.btn_wfk_refresh)
        self._WindowFunctions.enable_widget(enable=False, widget=self.ui.btn_wfk_change_menu)
        self._WindowFunctions.enable_widget(enable=False, widget=self.ui.btn_wfk_load_config)
        self.ui.pb_wfk_status.setVisible(True)
        self.ui.pb_wfk_status.setValue(0)
        self.ui.label_movie.show()

    def _set_ui_to_loading_analysis(self):
        """
        set the ui to loading status
        """
        self._WindowFunctions.enable_widget(enable=False, widget=self.ui.btn_wfk_start)
        self._WindowFunctions.enable_widget(enable=False, widget=self.ui.btn_wfk_refresh)
        self._WindowFunctions.enable_widget(enable=False, widget=self.ui.btn_wfk_change_menu)
        self._WindowFunctions.enable_widget(enable=False, widget=self.ui.btn_wfk_load_config)
        self.ui.pb_wfk_status.setVisible(False)
        self.ui.pb_wfk_status.setValue(0)
        self._WindowFunctions.enable_widget(enable=False, widget=self.ui.btn_analyze_start)
        self._WindowFunctions.enable_widget(enable=False, widget=self.ui.btn_analyze_change_menu)
        self.ui.label_movie.show()

    def _show_msg_box(self, p_text: str, p_state: MESSAGE_BOX_STATE):
        """
        shows the msg box
        """
        QTimer.singleShot(0, lambda: Logger.print_errors())
        if not self._msg_box.isVisible():
            self._msg_box.set_txt(p_text, p_state)
            self._msg_box.show()
        else:
            self._msg_box.set_txt(self._msg_box.txt + "\n" + p_text, p_state)

    def _window_first_toggled(self, is_checked):
        """
        triggered if the user clicked the checkbox window first
        """
        if is_checked:
            self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
            self.show()
            Logger.debug("WindowFirstToggled changed to: True")
        else:
            self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowStaysOnTopHint)
            self.show()
            Logger.debug("WindowFirstToggled changed to: False")

    def _allow_errors_toggled(self, is_checked):
        """
        triggered if the user clicked the checkbox allow errors while running (experimental)
        """
        self._run_with_errors = is_checked
        Logger.debug("AllowErrorsToggled changed to: " + str(self._run_with_errors))

    # ##################################################################################################
    # wfk run result

    @pyqtSlot(float)
    def _wfk_progress_changed(self, progress: float):
        """
        change progress
        """
        self.ui.pb_wfk_status.setValue(progress)

    @pyqtSlot(WfkResult)
    def _wfk_finished(self, wfk_result: WfkResult):
        """
        called if the wfk process has finished
        """
        if self._is_wfk_running:
            if isinstance(wfk_result.result, bool):
                if wfk_result.result:
                    Logger.debug("wfk process finished successful")
                    Logger.info("Process results...")
                    Logger.debug("Process needed: " + str(wfk_result.time_elapsed))
                    if self._wfk_ctrl.last_run_info.protocol_layer:
                        Logger.debug("There are " + str(len(wfk_result.layer_to_protocol)) + " layer to add to map")
                        try:
                            for layer in wfk_result.layer_to_protocol:
                                self._QGisInterface.add_layer_to_map(layer)
                        except Exception as e:
                            txt = "Can not add layer to map!\n" + str(e)
                            self._show_msg_box(txt, MESSAGE_BOX_STATE.ERROR)
                            Logger.error(txt)

                    # check if there are layer to analyze
                    if len(wfk_result.layer_for_analysis) > 0:
                        # change menu to analysis menu
                        self._WindowFunctions.change_menu("btn_menu_analyze")
                        # check if the user want to automatically analyze
                        if self._wfk_ctrl.last_run_info.run_analysis:
                            Logger.debug("WfkOneRun: True", )
                            self._start_analysis()
                        else:
                            self._show_msg_box("wfk process finished successful: "
                                               + str(round(wfk_result.time_elapsed, 3)) + " s" + "\n" +
                                               wfk_result.result_txt
                                               , MESSAGE_BOX_STATE.INFO)
                    else:
                        self._show_msg_box("wfk process finished successful: "
                                           + str(round(wfk_result.time_elapsed, 3)) + " s" + "\n" +
                                           wfk_result.result_txt
                                           , MESSAGE_BOX_STATE.INFO)
                else:
                    if wfk_result.result_txt:
                        Logger.error("wfk process finished not successful: " + wfk_result.result_txt)
                        self._show_msg_box(wfk_result.result_txt, MESSAGE_BOX_STATE.ERROR)
            self._is_wfk_running = False
            QTimer.singleShot(0, lambda: self._set_ui_to_std())

    @pyqtSlot(str)
    def _wfk_error(self, result: str):
        """
        called if an error happens while running the wfk process
        """
        if self._is_wfk_running and not self._error_wfk_processed:
            if isinstance(result, str):
                Logger.error(result.replace("\n", " "))
                self._show_msg_box(result, MESSAGE_BOX_STATE.ERROR)
            self._error_wfk_processed = True

    def _start_wfk(self):
        """
        starts the wfk process
        """
        self._error_wfk_processed = False
        if not self._wfk_ctrl.running and not self._is_wfk_running:
            self._is_wfk_running = True
            Logger.info("Collect needed data for wfk process")
            run_info = WfkTaskRunInfo()
            run_info.run_analysis = self.ui.checkBox_wfk_one_run.isChecked()
            run_info.protocol_layer = self.ui.checkBox_wfk_protocol_layers.isChecked()
            run_info.qgis_layer = self._QGisInterface.get_current_layer(self.ui.cb_wfk_layer_selection.currentIndex())
            run_info.config_name = self.ui.cb_wfk_config_selection.currentText()
            run_info.xml_config_path = self._last_cfg_path
            run_info.interface = self._QGisInterface.get_inter_face()
            run_info.run_with_errors = self._run_with_errors
            Logger.data(run_info.__str__())

            Logger.info("start wfk process")
            self._wfk_ctrl.signals.finished.connect(self._wfk_finished)
            self._wfk_ctrl.signals.progressChanged.connect(self._wfk_progress_changed)
            self._wfk_ctrl.signals.error.connect(self._wfk_error)
            self._set_ui_to_loading_wfk()
            QTimer.singleShot(0, lambda: self._wfk_ctrl.start(wfk_data=run_info))
        else:
            self._wfk_ctrl.stop()

    # ##################################################################################################
    # analysis run result

    @pyqtSlot(float)
    def _analysis_progress_changed(self, progress: float):
        """
         change progress
        """
        self.ui.pb_analyze_status.setValue(progress)

    @pyqtSlot(AnalysisResult)
    def _analysis_finished(self, analysis_result: AnalysisResult):
        """
        called if the analysis process has finished
        """
        if self._is_analysis_running:
            if isinstance(analysis_result.result, bool):
                if analysis_result.result:
                    try:
                        for layer in analysis_result.layer_to_protocol:
                            self._QGisInterface.add_layer_to_map(layer)
                        Logger.debug("analysis process finished successful")
                        Logger.debug("Process needed: " + str(analysis_result.time_elapsed))
                        # change menu to ciss menu
                        self._WindowFunctions.change_menu("btn_menu_ciss")
                        # display finished information
                        self._show_msg_box("analysis process finished successful: "
                                           + str(round(analysis_result.time_elapsed, 3)) + " s" + "\n" +
                                           analysis_result.result_txt
                                           , MESSAGE_BOX_STATE.INFO)
                    except Exception as e:
                        txt = "Can not add layer to map!\n" + str(e)
                        self._show_msg_box(txt, MESSAGE_BOX_STATE.ERROR)
                        Logger.error(txt)
                else:
                    if analysis_result.result_txt:
                        Logger.error("Analysis process finished not successful: " + analysis_result.result_txt)
                        self._show_msg_box(analysis_result.result_txt, MESSAGE_BOX_STATE.ERROR)
            self._is_analysis_running = False
            QTimer.singleShot(0, lambda: self._set_ui_to_std())

    @pyqtSlot(str)
    def _analysis_error(self, result: str):
        """
        called if an error happens
        """
        if self._is_analysis_running and not self._error_analysis_processed:
            if isinstance(result, str):
                Logger.error(result.replace("\n", " "))
                self._show_msg_box(result, MESSAGE_BOX_STATE.ERROR)
            self._error_analysis_processed = True

    def _start_analysis(self):
        """
        creates and starts the analysis process
        """
        self._error_analysis_processed = False
        if not self._analysis_ctrl.running:
            self._is_analysis_running = True
            run_info = AnalysisTaskRunInfo()
            run_info.layer_list = self._wfk_ctrl.last_result_info.layer_for_analysis
            run_info.run_with_errors = self._run_with_errors

            Logger.debug("start analysis process")
            self._analysis_ctrl.signals.finished.connect(self._analysis_finished)
            self._analysis_ctrl.signals.progressChanged.connect(self._analysis_progress_changed)
            self._analysis_ctrl.signals.error.connect(self._analysis_error)
            QTimer.singleShot(0, lambda: self._set_ui_to_loading_analysis())
            self._analysis_ctrl.start(analysis_data=run_info)
        else:
            self._analysis_ctrl.stop()
