"""
 wfk_task

 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.                                   *
 *                                                                         *
 ***************************************************************************/
"""
import os

from qgis.core import QgsTask
from PyQt5.QtCore import *
from weissflaechenkartierung.src.exceptions import RemoteFileDownloadError, InternetConnectionError, ExtractZipError
from weissflaechenkartierung.src.analysis.analysis_task import AnalysisTaskLayerInfo
from weissflaechenkartierung.src.xml.legend import Legend
from weissflaechenkartierung.src.xml.config import Config
from weissflaechenkartierung.src.logger import Logger
from weissflaechenkartierung.src.qgis import QGisInterface
from weissflaechenkartierung.src.xml.xml_config_ctrl import XMLConfigCtrl


class WfkResult:
    def __init__(self):
        self.result: bool = False
        self.result_txt: str = ""
        self.time_elapsed: float = 0.0
        self.time_started: float = 0.0
        self.layer_for_analysis: list = []
        self.layer_to_protocol: list = []


class WfkTaskRunInfo:
    def __init__(self):
        self.description: str = "WfkTask"
        self.xml_config_path: str = ""
        self.config_name: str = ""
        self.interface = None
        self.qgis_layer = None
        self.protocol_layer: bool = False
        self.run_analysis: bool = False
        self.run_with_errors: bool = False

    def __str__(self):
        return self.description + ": | cfg: " + self.config_name + " | pl: " + str(self.protocol_layer) \
               + " | ra: " + str(self.run_analysis)


class WfkTaskSignals(QObject):
    finished = pyqtSignal()  # signal to connect to
    result = pyqtSignal(WfkResult)  # signal to connect to


class WfkTask(QgsTask):
    def __init__(self, run_config: WfkTaskRunInfo):
        QgsTask.__init__(self, description=run_config.description, flags=QgsTask.CanCancel)
        self.signals = WfkTaskSignals()
        self._run_config: WfkTaskRunInfo = run_config
        self._wfk_result = WfkResult()

    def run(self):
        """
        the action happens here

        returns false only if its irreparable
        """
        Logger.info("Run WfkTask...")
        # initialize the xml controller
        config_ctrl = XMLConfigCtrl()
        try:
            if not os.path.exists(self._run_config.xml_config_path):
                err_txt = "The given XML-Document-path does not exist"
                self._wfk_result.result_txt = err_txt
                return False

            Logger.debug("Parse xml file: " + self._run_config.xml_config_path)
            config_ctrl.parse(self._run_config.xml_config_path)
        except (TypeError, AttributeError) as e:
            err_txt = "The given XML-Document can not be parsed well:\n" + Logger.get_current_system_exception()
            self._wfk_result.result_txt = err_txt
            return False

        try:
            # get the first xml configuration obj by name
            first_config = config_ctrl.get_first_config_by_name(name=self._run_config.config_name)
            Logger.info("Start config: " + self._run_config.config_name)

            # initialize the config object
            cfg = Config(parameters=config_ctrl.get_parameter_from_xml(xml=first_config),
                         name=config_ctrl.get_name_from_xml(element=first_config),
                         qgis_layer=self._run_config.qgis_layer,
                         config_section=first_config)
            Logger.info("Load modules...")
            cfg.add_modules(XMLConfigCtrl.get_modules_from_xml(element=first_config,
                                                               config=cfg))
            # start the task with the configuration and save the result layers
            self._wfk_result = self._run(cfg=cfg)
        except Exception as e:
            self._wfk_result.result_txt = "An error occurred while running wfk:\n" + \
                                          Logger.get_current_system_exception()
            return False
        return True

    def _run(self, cfg: Config) -> WfkResult:
        """
        run configuration
        """
        wfk_result = WfkResult()

        if self.has_to_stop():
            return wfk_result
        self.setProgress(5)

        # clone the wfk layer from qgis (from qgis interface obj)
        wfk_layer = QGisInterface.clone_layer(input_layer=cfg.qgis_layer)
        step = 0
        module_list_len = len(cfg.module_list)
        # iterate over the configuration modules
        modules_to_rem = []
        Logger.info("There are " + str(len(cfg.module_list)) + " modules to process")
        for module in cfg.module_list:
            try:
                Logger.info("Run module: " + module.name)

                # run the connector from the module
                if self.has_to_stop():
                    Logger.debug("The program has to stop " + module.name)
                    return wfk_result

                Logger.debug("Run connector")
                module.run_connector()

                self.setProgress(5 + (step * (75 / module_list_len)) + (10 / module_list_len))

                # run the layer from the module
                if self.has_to_stop():
                    Logger.debug("The program has to stop after running connectors")
                    return wfk_result

                Logger.debug("Running layer")
                module.run_layer()

                self.setProgress(5 + ((step * (75 / module_list_len)) + (30 / module_list_len)))

                # run differences
                if self.has_to_stop():
                    Logger.debug("The program has to stop after running layers")
                    return wfk_result

                Logger.debug("run difference")
                diff_run_result = module.run_difference(diff_layer=wfk_layer)
                wfk_layer = diff_run_result.run_result  # run result -> diffed layer

                Logger.debug("check if there is a blacklisted layer")
                # check if the user would like to see the black listed features separately
                if hasattr(module.difference, "save_blacklisted_layer"):
                    if module.difference.save_blacklisted_layer:
                        wfk_result.layer_to_protocol.extend(module.difference.diff_layer)

                # check if the difference class name is the blacklist class
                if module.difference.__class__.__name__ == "BlacklistingDifference":
                    if module.difference.diff_layer is not None:
                        print(self.__class__.__name__ + " - prepare for blacklisting analyzing")
                        # create run analyze information object for analyze step
                        analysis_layer = AnalysisTaskLayerInfo()
                        analysis_layer.layer_to_analyze = module.difference.diff_layer
                        analysis_layer.blacklisted = True
                        analysis_layer.last_cfg = cfg
                        wfk_result.layer_for_analysis.append(analysis_layer)

                self.setProgress(5 + (step * (75 / module_list_len)) + (75 / module_list_len))
                step += 1
                Logger.info("Module ran successful")

            except FileNotFoundError as e:
                if self._run_config.run_with_errors:
                    wfk_result.result_txt = Logger.get_current_system_exception()
                    modules_to_rem.append(module)
                    Logger.warning("Skipped module: " + module.name)
                    continue
                else:
                    raise FileNotFoundError(e)
            except RemoteFileDownloadError as e:
                if self._run_config.run_with_errors:
                    wfk_result.result_txt = Logger.get_current_system_exception()
                    modules_to_rem.append(module)
                    Logger.warning("Skipped module: " + module.name)
                    continue
                else:
                    raise RemoteFileDownloadError(e)
            except InternetConnectionError as e:
                if self._run_config.run_with_errors:
                    wfk_result.result_txt = Logger.get_current_system_exception()
                    modules_to_rem.append(module)
                    Logger.warning("Skipped module: " + module.name)
                    continue
                else:
                    raise InternetConnectionError(e)
            except ExtractZipError as e:
                if self._run_config.run_with_errors:
                    wfk_result.result_txt = Logger.get_current_system_exception()
                    modules_to_rem.append(module)
                    Logger.warning("Skipped module: " + module.name)
                    continue
                else:
                    raise ExtractZipError(e)

        # check if there are modules to remove
        if len(modules_to_rem) > 0:
            Logger.warning("There where " + str(len(modules_to_rem)) + " failed modules.")
            for m in modules_to_rem:
                cfg.module_list.remove(m)

        if self.has_to_stop():
            return wfk_result

        # check if there are modules left to process
        if len(cfg.module_list) == 0:
            Logger.info("There were no layers to process")
            return wfk_result

        # process remaining layers
        Logger.debug("Run multi2single")
        wfk_layer = QGisInterface.multi_2_single(base_layer=wfk_layer,
                                                 name_layer="tmp")

        if self.has_to_stop():
            Logger.debug("The program has to stop after multi two single operation")
            return wfk_result

        Logger.debug("Add module layer to result layer")
        for module in cfg.module_list:
            wfk_result.layer_to_protocol.extend(module.add_to_map())

        if self.has_to_stop():
            Logger.debug("The program has to stop after adding module to result list")
            return wfk_result

        Logger.debug("Get the names for the Legend")
        # legend
        legend = Legend(xml_section=XMLConfigCtrl.get_first_tag_in_child_nodes(
            xml=cfg.config_section,
            tag="legend"
        ),
            module=None)

        # set the wfk layer name
        wfk_layer = legend.add_wfk_layer_to_map_param(wfk_layer=wfk_layer)

        # add wfk layer to analysis list
        Logger.debug("Create analysis information")
        analysis_layer = AnalysisTaskLayerInfo()
        analysis_layer.layer_to_analyze = wfk_layer
        analysis_layer.last_cfg = cfg
        wfk_result.layer_for_analysis.append(analysis_layer)

        # add wfk layer to the protocol list
        Logger.debug("Add wfk layer to protocol list")
        wfk_result.layer_to_protocol.extend([wfk_layer])
        return wfk_result

    def finished(self, result):
        """
        called when the task has finished
        """
        Logger.info("wfk task finished: " + str(result))
        # set the run result
        self._wfk_result.result = result

        # emit the wfk result
        self.signals.result.emit(self._wfk_result)
        self.signals.finished.emit()

    def has_to_stop(self) -> bool:
        """
        checks if the task needs to stopped
        """
        return True if self.isCanceled() else False
