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

from PyQt5.QtCore import QVariant
from qgis.gui import QgisInterface
from qgis.core import QgsProcessingContext, QgsProject, QgsFeatureRequest, QgsVectorLayer, \
    QgsCoordinateReferenceSystem, \
    QgsMessageLog, Qgis
import processing


class QGisInterface:
    """
    interface with with functionality
    """

    def __init__(self, interface):
        self._interface = interface

    def get_inter_face(self) -> QgisInterface:
        """
        returns the qgis interface

        Abstract base class defining interfaces exposed by QgisApp and
        made available to plugins.
        """
        return self._interface

    def get_current_layer(self, _id: int):
        """
        returns the current layer
        """
        try:
            layer = self._interface.mapCanvas().layers()[_id]
        except Exception as e:
            return None
        return layer

    def get_current_available_layers(self) -> list:
        """
        :return: a list with all available layers
        """
        return [layer.name() for layer in self._interface.mapCanvas().layers()]

    @staticmethod
    def get_context():
        def_context = QgsProcessingContext()
        def_context.setProject(QgsProject.instance())
        def_context.setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid)
        return def_context

    @staticmethod
    def delete_layers_by_name(name: str):
        rem = QgsProject.instance().mapLayersByName(name)
        if rem is not None:
            for layer in rem:
                QgsProject.instance().removeMapLayer(layer)

    @staticmethod
    def diff_from_layer(base_layer, input_layer):
        """
        call: native:difference
        """
        try:
            result = processing.run("native:difference",
                                    {'INPUT': base_layer,
                                     'OVERLAY': input_layer, 'OUTPUT': 'TEMPORARY_OUTPUT'},
                                    context=QGisInterface.get_context())
            return result['OUTPUT']
        except Exception as e:
            QgsMessageLog.logMessage(message="QGisInterface:diff_from_layer:error: unexpected " + str(e),
                                     level=Qgis.Critical)
            return None

    @staticmethod
    def union_by_layer(input_layer, overlay_layer, name):
        try:
            result = processing.run("native:union",
                                    {'INPUT': input_layer,
                                     'OVERLAY': overlay_layer,
                                     'OUTPUT': 'TEMPORARY_OUTPUT'},
                                    context=QGisInterface.get_context())
            vl = result['OUTPUT']
            vl.setName(name)
            return vl
        except Exception as e:
            QgsMessageLog.logMessage(message="QGisInterface:union_by_layer:error: name " + name,
                                     level=Qgis.Critical)
            return None

    @staticmethod
    def clip_by_layer(input_layer, overlay_layer, name_layer):
        try:
            result = processing.run("native:clip",
                                    {'INPUT': input_layer,
                                     'OVERLAY': overlay_layer, 'OUTPUT': 'TEMPORARY_OUTPUT'},
                                    context=QGisInterface.get_context())
            vl = result['OUTPUT']
            vl.setName(name_layer)
            return vl
        except Exception as e:
            QgsMessageLog.logMessage(message="QGisInterface:clip_by_layer:error: name " + name_layer,
                                     level=Qgis.Critical)
            return None

    @staticmethod
    def buffer_layer(input_layer, distance, name_layer):
        if distance == 0.0:
            return input_layer
        try:
            result = processing.run("native:buffer",
                                    {'INPUT': input_layer,
                                     'DISTANCE': distance, 'OUTPUT': 'TEMPORARY_OUTPUT'},
                                    context=QGisInterface.get_context())
            vl = result['OUTPUT']
            vl.setName(name_layer + " (buffered)")
            return vl
        except Exception as e:
            QgsMessageLog.logMessage(message="QGisInterface:buffer_layer:error: cloning layer failed " + name_layer,
                                     level=Qgis.Critical)
            return None

    @staticmethod
    def clone_selected_layer(input_layer):
        try:
            result = processing.run("native:saveselectedfeatures",
                                    {'INPUT': input_layer, 'OUTPUT': 'TEMPORARY_OUTPUT'},
                                    context=QGisInterface.get_context())
            input_layer.removeSelection()
            output_layer = result['OUTPUT']
            output_layer.setName(input_layer.name())
            return output_layer
        except Exception as e:
            QgsMessageLog.logMessage(
                message="QGisInterface:clone_selected_layer:error: cloning selected layer failed " + str(e),
                level=Qgis.Critical)
            return None

    @staticmethod
    def clone_layer(input_layer):
        try:
            input_layer.selectAll()
            result = processing.run("native:saveselectedfeatures",
                                    {'INPUT': input_layer, 'OUTPUT': 'TEMPORARY_OUTPUT'},
                                    context=QGisInterface.get_context())
            input_layer.removeSelection()
            output_layer = result['OUTPUT']
            output_layer.setName(input_layer.name())
            return output_layer
        except Exception as e:
            QgsMessageLog.logMessage(message="QGisInterface:clone_layer:error: cloning layer failed " + str(e),
                                     level=Qgis.Critical)
            return None

    @staticmethod
    def multi_2_single(base_layer, name_layer):
        try:
            result = processing.run("native:multiparttosingleparts",
                                    {'INPUT': base_layer, 'OUTPUT': 'TEMPORARY_OUTPUT'},
                                    context=QGisInterface.get_context())
            vl = result['OUTPUT']
            vl.setName(name_layer)
            return vl
        except Exception as e:
            print(e)
            QgsMessageLog.logMessage(message="QGisInterface:multi_2_single:error: multi to single failed " + str(e),
                                     level=Qgis.Critical)
            return None

    @staticmethod
    def att_from_nearest(input_layer, ref_layer, fields):
        try:
            result = processing.run("qgis:joinbynearest",
                                    {'MAX_DISTANCE': None, 'NEIGHBORS': 1, 'DISCARD_NONMATCHING': False,
                                     'INPUT': input_layer, 'FIELDS_TO_COPY': fields, 'INPUT_2': ref_layer,
                                     'OUTPUT': 'TEMPORARY_OUTPUT'}, context=QGisInterface.get_context())
            vl = result['OUTPUT']
            vl.setName(input_layer.name())
            return vl
        except Exception as e:
            QgsMessageLog.logMessage(message="QGisInterface:att_from_nearest:error: " + str(e),
                                     level=Qgis.Critical)
            return None

    # ###################################################################
    # qgis interface actions
    @staticmethod
    def show_message(caption: str = "caption", txt: str = "text", lvl: int = Qgis.Info, timer: bool = False,
                     duration: int = 3, qgis_interface=None):
        """
        shows a message in the 'horizontalLayout_message' - layout
        :param caption: message caption
        :param txt: message text
        :param lvl: message level
        :param timer: timer activate or not
        :param duration: how long the message stays
        :param qgis_interface: interface to qgis
        :return: none
        """
        msg_lvl_list = [Qgis.Critical, Qgis.Info, Qgis.Warning, Qgis.Success]
        if lvl not in msg_lvl_list:
            QgsMessageLog.logMessage(message="WfkDialog:show_message:The given log level does not exist",
                                     level=Qgis.Critical
                                     )
        else:
            if timer:
                qgis_interface.messageBar().pushMessage(caption, txt, lvl, duration)
            else:
                qgis_interface.messageBar().pushMessage(caption, txt, lvl)

    @staticmethod
    def att_from_position(input_layer, ref_layer, fields):
        try:
            result = processing.run("qgis:joinattributesbylocation",
                                    {'PREDICATE': [0], 'DISCARD_NONMATCHING': False, 'INPUT': input_layer,
                                     'JOIN_FIELDS': fields, 'JOIN': ref_layer, 'METHOD': 1,
                                     'OUTPUT': 'TEMPORARY_OUTPUT'},
                                    context=QGisInterface.get_context())
            vl = result['OUTPUT']
            vl.setName(input_layer.name())
            return vl
        except Exception as e:
            QgsMessageLog.logMessage(message="QGisInterface:att_from_position:error: " + str(e),
                                     level=Qgis.Critical)
            return None

    @staticmethod
    def load_shape(url, name, crs):
        try:
            vl1 = QgsVectorLayer(url, name, "ogr")
            vl1.setCrs(QgsCoordinateReferenceSystem(crs))
            return vl1
        except Exception as e:
            QgsMessageLog.logMessage(message="QGisInterface:att_from_position:error: " + name + str(e),
                                     level=Qgis.Critical)
            return None

    @staticmethod
    def add_layer_to_map(layer):
        """
        adds the layer to map
        deletes all layer where the name is the same as 'layer'
        """
        try:
            QGisInterface.delete_layers_by_name(layer.name())
            QgsProject.instance().addMapLayer(layer)
        except Exception as e:
            QgsMessageLog.logMessage(message="QGisInterface:add_layer_to_map:error: " + layer.name() + str(e),
                                     level=Qgis.Critical)
            return None

    @staticmethod
    def selective_buffer(input_layer, crs, type_list, buffer_list, attribute, name):
        try:
            layer_list = []
            for i in range(len(type_list)):
                result = processing.run("native:extractbyattribute",
                                        {'INPUT': input_layer, 'VALUE': type_list[i], 'OPERATOR': 0,
                                         'FIELD': attribute,
                                         'OUTPUT': 'TEMPORARY_OUTPUT'}, context=QGisInterface.get_context())
                layer_list.append(QGisInterface.buffer_layer(result['OUTPUT'], buffer_list[i], "tmp"))
                if layer_list[i] is None:
                    QgsMessageLog.logMessage(message="QGisInterface:selective_buffer:error: buffer is non ",
                                             level=Qgis.Info)
                    return None
            result = processing.run("native:mergevectorlayers",
                                    {'CRS': crs, 'LAYERS': layer_list,
                                     'OUTPUT': 'TEMPORARY_OUTPUT'},
                                    context=QGisInterface.get_context())
            vl2 = result['OUTPUT']
            vl2.setName(name + " (buffered)")
            return vl2
        except Exception as e:
            QgsMessageLog.logMessage(message="QGisInterface:selective_buffer:error: unexpected " + str(e),
                                     level=Qgis.Info)
            return None

    @staticmethod
    def try_set_attribute(f, res, key1, key2):
        try:
            f.setAttribute(key1, QVariant(res[key2]["value"]))
        except Exception as e:
            QgsMessageLog.logMessage(message="QGisInterface:try_set_attribute:error: unexpected " + str(e),
                                     level=Qgis.Critical)
            return None
        return f

    @staticmethod
    def try_set_attribute_value(feature, attr_name, attr_new_value):
        try:
            feature.setAttribute(attr_name, QVariant(attr_new_value))
        except Exception as e:
            QgsMessageLog.logMessage(message="QGisInterface:try_set_attribute_value:error: unexpected " + str(e),
                                     level=Qgis.Critical)
            print(e)
            return None
        return feature

    @staticmethod
    def extract_by_expression(layer, expression: str):
        try:
            new_layer = processing.run("qgis:extractbyexpression",
                                       {'INPUT': layer,
                                        'EXPRESSION': expression,
                                        'OUTPUT': 'memory:'})['OUTPUT']
        except Exception as e:
            QgsMessageLog.logMessage(message="QGisInterface:extract_by_expression:error: " + str(e),
                                     level=Qgis.Critical)
            print(e)
            return None
        return new_layer
