# -*- coding: utf-8 -*-
"""
/***************************************************************************
 Categorization
                                 A QGIS plugin
 wide are mapping
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                             -------------------
        begin                : 2020-11-13
        git sha              : $Format:%H$
        copyright            : (C) 2020 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 json
import os
from abc import abstractmethod

import requests
from qgis.PyQt.QtCore import QVariant
from qgis.core import QgsField, QgsMessageLog, Qgis
from weissflaechenkartierung.src.exceptions import USUDistanceCategorizationError, SPARQLCategorizationError, ByCentroidCategorizationError

from weissflaechenkartierung.src.qgis import QGisInterface
from weissflaechenkartierung.src.xml.owner import Owner
from weissflaechenkartierung.src.xml.SPARQLWrapper import *
from weissflaechenkartierung.src.logger import Logger
import weissflaechenkartierung.src.xml.xml_config_ctrl as xml


class Categorization:
    def __init__(self, xml_section, module):
        """
        :param xml_section: section of an xml dom
        :param module: an module instance
        """
        self._xml_section = xml_section
        self._module = module
        self._parameters = xml.XMLConfigCtrl.get_parameter_from_xml(xml=self._xml_section)

    def log(self, txt):
        """
        logs content
        """
        Logger.debug(txt)

    @property
    def xml_section(self):
        """
        returns the xml section
        """
        return self._xml_section

    @property
    def parameters(self):
        """
        returns the parameters
        """
        return self._parameters

    @abstractmethod
    def run(self, cat_layer):
        """
        method for inheritance
        """
        Logger.debug("Run the base categorization")
        return cat_layer


class ByDistanceCategorization(Categorization):
    def run(self, cat_layer):
        selector = []
        Logger.debug("Run ByDistanceCategorization")
        if "inherited_fields" in self._parameters:
            selector = self._parameters["inherited_fields"]
        return QGisInterface.att_from_nearest(input_layer=cat_layer,
                                              ref_layer=self._module.layer.layer,
                                              fields=selector)


class ByPositionCategorization(Categorization):
    def run(self, cat_layer):
        selector = []
        Logger.debug("Run ByPositionCategorization")
        if "position_selector" in self._parameters:
            selector.append(self._parameters["position_selector"])
        return QGisInterface.att_from_position(input_layer=cat_layer,
                                               ref_layer=self._module.layer.layer,
                                               fields=selector)


class ByCentroidCategorization(Categorization):
    def run(self, cat_layer):
        out_layer = QGisInterface.clone_layer(input_layer=cat_layer)
        Logger.debug("Run ByCentroidCategorization")
        try:
            out_layer.startEditing()
            pr = out_layer.dataProvider()
            pr.addAttributes([QgsField("CentroidCoordX", QVariant.Double),
                              QgsField("CentroidCoordY", QVariant.Double)
                              ])
            out_layer.updateFields()
            out_layer.commitChanges()
            out_layer.startEditing()

            for f in out_layer.getFeatures():
                if f.hasGeometry():
                    center = f.geometry().centroid().asPoint()
                else:
                    continue
                f = QGisInterface.try_set_attribute_value(f, 'CentroidCoordX', center.y())
                f = QGisInterface.try_set_attribute_value(f, 'CentroidCoordY', center.x())
                out_layer.updateFeature(f)

            out_layer.commitChanges()
        except Exception as e:
            raise ByCentroidCategorizationError(e)
        return out_layer


class USUDistanceCategorization(ByCentroidCategorization):
    def run(self, cat_layer):
        out_layer = super().run(cat_layer)

        Logger.debug("Run USUDistanceCategorization")
        timeout = 15
        if "timeout" in self._parameters:
            timeout = int(self._parameters["timeout"])

        sx = 54.144703
        if "startX" in self._parameters:
            sx = float(self._parameters["startX"])

        sy = 12.099037
        if "startY" in self._parameters:
            sy = float(self._parameters["startY"])

        limit = 100
        if "featureLimit" in self._parameters:
            limit = int(self._parameters["featureLimit"])

        url = "http://limbo.usu-research.ml:8080/limbo/routing/"
        if "serviceUrl" in self._parameters:
            url = self._parameters["serviceUrl"]
        count = 0
        try:
            out_layer.startEditing()
            pr = out_layer.dataProvider()
            pr.addAttributes([QgsField("USUCost", QVariant.String)])
            out_layer.updateFields()
            out_layer.commitChanges()
            out_layer.startEditing()

            for f in out_layer.getFeatures():
                if count >= limit:
                    Logger.debug("USUDistanceCategorization: Feature-Limit von: " + str(
                        limit) + " wurde erreicht. Es werden keine weiteren Kosten berechnet.", )
                    break
                x = f['CentroidCoordX']
                y = f['CentroidCoordY']

                myobj = "{\n\t\"start\": {\n\t\t\"x\": " + str(sx) + ",\n\t\t\"y\": " + str(
                    sy) + "\n\t},\n\t\"end\": {\n\t\t\"x\": " + str(x) + ",\n\t\t\"y\": " + str(
                    y) + "\n\t},\n\t\"height\": 2,\n\t\"width\": 3,\n\t\"cost\": \"cost\"\n}"

                headers = {'Content-type': 'application/json'}

                req = requests.post(url, data=myobj, timeout=timeout, headers=headers)
                # print(req.status_code)
                jval = json.loads(req.text)

                f = QGisInterface.try_set_attribute_value(f, 'USUCost',
                                                          jval["features"][0]["properties"]["duration"])
                out_layer.updateFeature(f)
                count += 1
            out_layer.commitChanges()
        except Exception as e:
            raise USUDistanceCategorizationError(e)
        return out_layer


class SPARQLCategorization(ByPositionCategorization):
    def run(self, cat_layer):
        out_layer = super().run(cat_layer)
        selector = "id"
        Logger.error("Run SPARQLCategorization")
        if "attribute_selector" in self._parameters:
            selector = self._parameters["attribute_selector"]

        fields_to_add = ["Name", "dbpedia", "Homepage", "Adresse", "Buergermeister", "Partei", "Einwohnerzahl"]
        try:
            try:
                out_layer.startEditing()

                pr = out_layer.dataProvider()
                for field in fields_to_add:
                    pr.addAttributes([QgsField(field, QVariant.String)])

                out_layer.updateFields()
                out_layer.commitChanges()
            except Exception as e:
                raise SPARQLCategorizationError(e)

            Logger.debug("SPARQLCategorization attributes added")
            q_dict = {}

            out_layer.startEditing()
            for f in out_layer.getFeatures():
                key = f.attribute(selector)
                sparql = SPARQLWrapper("http://dbpedia.org/sparql")
                try:
                    x = int(key)
                except:
                    continue
                if int(key) in q_dict:
                    res = q_dict[int(key)]
                else:
                    sparql.setQuery("""
                    PREFIX foaf: <http://xmlns.com/foaf/0.1/>
                    PREFIX dbp: <http://dbpedia.org/property/>
                    select ?url ?partei ?name ?buergermeister ?homepage (GROUP_CONCAT(DISTINCT ?add; SEPARATOR=" , ") AS ?adresse) ?pop
                    where {
                        ?url dbp:gemeindeschlüssel """ + str(key) + """ .
                        optional{
                            ?url rdfs:label ?title .
                            BIND (STR(?title)  AS ?name) 
                            FILTER ( LANG(?title) = "de" )
                        }
                        optional{
                            ?url dbp:party ?partei .
                        }
                        optional{
                            ?url dbp:mayor ?buergermeister .
                        }
                        optional{
                            ?url foaf:homepage ?homepage .
                        }
                        optional{
                            ?url dbp:adresse ?add .
                        }optional
                        {
                        ?url dbo:populationTotal ?pop .
                        }
                    } 
                    LIMIT 1
                    """)
                    sparql.setReturnFormat(JSON)
                    sparql.setMethod(POST)
                    results = sparql.query().convert()
                    if len(results["results"]["bindings"]) == 0:
                        continue
                    res = results["results"]["bindings"][0]
                    q_dict[int(key)] = res

                f = QGisInterface.try_set_attribute(f, res, 'Name', 'name')
                f = QGisInterface.try_set_attribute(f, res, 'dbpedia', 'url')
                f = QGisInterface.try_set_attribute(f, res, 'Homepage', 'homepage')
                f = QGisInterface.try_set_attribute(f, res, 'Adresse', 'adresse')
                f = QGisInterface.try_set_attribute(f, res, 'Buergermeister', 'buergermeister')
                f = QGisInterface.try_set_attribute(f, res, 'Partei', 'partei')
                f = QGisInterface.try_set_attribute(f, res, 'Einwohnerzahl', 'pop')

                out_layer.updateFeature(f)

            out_layer.commitChanges()
        except Exception as e:
            Logger.error("SPARQLCategorization unexpected error, delete field names which can not be added", )
            Logger.error(str(e), )
            del_attributes_pos = []
            count = 0
            for field_united in out_layer.dataProvider().fields():
                if field_united.name() != selector and field_united.name() in fields_to_add:
                    del_attributes_pos.append(count)
                count += 1

            try:
                Logger.debug("SPARQLCategorization try to delete:  " + str(len(del_attributes_pos)) + " items",
                             )
                out_layer.startEditing()
                out_layer.dataProvider().deleteAttributes(del_attributes_pos)
                out_layer.updateFields()
                out_layer.commitChanges()
            except Exception as e:
               raise SPARQLCategorizationError(e)

            Logger.debug("SPARQLCategorization deleted " + str(len(del_attributes_pos)) + " items", )
        return out_layer


class ByPositionCategorizationWithClipping(Categorization):
    def run(self, cat_layer):
        selector = ""
        if "position_selector" in self._parameters:
            selector: str = self._parameters["position_selector"]

        # 1) clip layer
        clipped_layer_name = "Clipped"
        if "clipped_layer_name" in self._parameters:
            clipped_layer_name: str = self._parameters["clipped_layer_name"]
        self.log("clip layer")
        clipped_layer = QGisInterface.clip_by_layer(input_layer=self._module.layer.layer,
                                                    overlay_layer=cat_layer,
                                                    name_layer=clipped_layer_name)

        # 2) unite attributes from cat_layer and clipped layer
        self.log("unite layer")
        union_layer_name = "Tailored"
        if "tailored_layer_name" in self._parameters:
            union_layer_name: str = self._parameters["tailored_layer_name"]
        united_layer = QGisInterface.union_by_layer(input_layer=clipped_layer,
                                                    overlay_layer=cat_layer,
                                                    name=union_layer_name)

        # get names which will not be deleted
        not_del_attributes = []
        for field_cat in cat_layer.dataProvider().fields():
            not_del_attributes.append(field_cat.name())

        # 3) delete attributes except selector
        del_attributes_pos = []
        count = 0

        for field_united in united_layer.dataProvider().fields():
            if field_united.name() != selector and field_united.name() not in not_del_attributes:
                del_attributes_pos.append(count)
            count += 1

        self.log("delete attributes")
        united_layer.dataProvider().deleteAttributes(del_attributes_pos)
        united_layer.updateFields()

        return united_layer


class AdditionalInformationCategorization(ByPositionCategorizationWithClipping):
    def run(self, cat_layer):
        # run parent run()
        out_layer = super().run(cat_layer)

        # get path parameter
        additional_info_csv_file = ""
        if "path" in self._parameters:
            additional_info_csv_file: str = self._parameters["path"]

        selector = ""
        if "position_selector" in self._parameters:
            selector: str = self._parameters["position_selector"]

        if additional_info_csv_file == "" or not os.path.exists(additional_info_csv_file):
            self.log("error:path for 'file' is not given or does not exist")
            return out_layer

        if not additional_info_csv_file.endswith('.csv'):
            self.log("error:the file is not a *.csv file")
            return out_layer

        # get additional information
        add_info_list = []
        if "AdditionalInformationItems" in self._parameters:
            add_info_list = list(self._parameters["AdditionalInformationItems"])

        # get csv-column selector
        csv_column_selector = ""
        if "csv_selector" in self.parameters:
            csv_column_selector = self.parameters["csv_selector"]

        # get owner objects
        obj_cts = Owner.from_csv_to_dict(file_name=additional_info_csv_file,
                                         column_name=csv_column_selector)

        self.log("loaded " + str(len(obj_cts)) + " additional information objects")
        # try to add attributes to out_layer
        self.log("add attributes from xml")
        for attr in add_info_list:
            if type(attr) is str:
                self.log("attribute: " + attr)
                try:
                    out_layer.startEditing()
                    pr = out_layer.dataProvider()
                    pr.addAttributes([QgsField(attr, QVariant.String)])
                    out_layer.updateFields()
                    out_layer.commitChanges()
                except Exception as e:
                    self.log("error: " + str(repr(e)))

        def check_feature(feat, o, a):
            if hasattr(o, a):
                # determine feature still has add_info
                try:
                    feat.attribute(a)
                    return True
                except KeyError:
                    QgsMessageLog.logMessage(
                        message="AdditionalInformationCategorization:run:error: a feature does "
                                "not have '" + a,
                        level=Qgis.Critical)
            return False

        try:
            out_layer.startEditing()
            self.log("iterate over features")
            for f in out_layer.getFeatures():
                atr_key = str(f.attribute(selector))  # KNZ example: '07256600000007______'

                # iterate over the owner dict
                for key, owner_list in obj_cts.items():
                    # check the selector
                    if key == atr_key:
                        # iterate over the additional info list
                        for atr_name in add_info_list:
                            # iterate over the owner list

                            #
                            if len(owner_list) == 1:
                                for owner in owner_list:
                                    # check if the owner object has the member from the AdditionalInformationItems list
                                    if check_feature(feat=f, o=owner, a=atr_name):

                                        try:
                                            attr_value: str = str(getattr(owner, atr_name))
                                        except AttributeError:
                                            self.log("AttributeError::207987546")
                                            return out_layer

                                        # if there is nothing in the string
                                        if attr_value == "":
                                            attr_value = "No information"

                                        # set the attribute value
                                        QGisInterface.try_set_attribute_value(feature=f,
                                                                              attr_name=atr_name,
                                                                              attr_new_value=attr_value)
                            elif len(owner_list) > 1:
                                add_information = ""
                                count = 0
                                while count < len(owner_list):
                                    owner = owner_list[count]

                                    if check_feature(feat=f, o=owner, a=atr_name):
                                        try:
                                            attr_value: str = str(getattr(owner, atr_name))
                                        except AttributeError:
                                            self.log("AttributeError::207959546")
                                            return out_layer
                                        if attr_value != "":
                                            add_information += attr_value
                                        else:
                                            add_information += "No information"

                                        if count != len(owner_list) - 1:
                                            add_information += ", "

                                    count += 1
                                QGisInterface.try_set_attribute_value(feature=f,
                                                                      attr_name=atr_name,
                                                                      attr_new_value=add_information)
                out_layer.updateFeature(f)
            out_layer.commitChanges()
            self.log("added information to feature")
        except Exception as e:
            QgsMessageLog.logMessage(
                message="AdditionalInformationCategorization:run:error: not handled error:'" + str(repr(e)),
                level=Qgis.Critical)
        return out_layer
