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

"""
***************************************************************************
*                                                                         *
*   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.                                   *
*                                                                         *
***************************************************************************
"""
from PyQt5.QtCore import QVariant
from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import (
    QgsSnappingConfig,
    QgsVectorLayer,
    QgsProcessingException,
    QgsProcessingParameterFileDestination,
    QgsProcessingParameterVectorDestination,
    QgsProcessingLayerPostProcessorInterface,
    QgsDefaultValue,
    QgsTolerance,
    QgsFieldConstraints,
    QgsField,
    QgsProject,
    QgsProcessingAlgorithm,
    QgsProcessingParameterCrs,
    QgsProcessingUtils
)

from qgis import processing
import os, inspect

def set_snapping_options():
    proj = QgsProject.instance()        
    config = proj.snappingConfig()
    proj.setAvoidIntersectionsMode(QgsProject.AvoidIntersectionsMode(1))
    proj.setTopologicalEditing(True)
    config.setMode(QgsSnappingConfig.ActiveLayer)
    config.setType(QgsSnappingConfig.Vertex)
    config.setTolerance(10)
    config.setUnits(QgsTolerance.Pixels)
    config.setEnabled(True)
    proj.setSnappingConfig(config)
    return proj


class Renamer (QgsProcessingLayerPostProcessorInterface):
    def __init__(self, layer_name):
        self.name = layer_name
        super().__init__()
        
    def postProcessLayer(self, layer, context, feedback):
        layer.setName(self.name)

class CreateHandDigitizedMap(QgsProcessingAlgorithm):
    """
    This script creates a template for hand-digitizing polygons in the appropriate WAsP format
    """

    # Constants used to refer to parameters and outputs. They will be
    # used when calling the algorithm from another algorithm, or when
    # calling from the QGIS console.
    dest_id = None  # Save a reference to the output layer id
    OUTPUT_CRS = "OUTPUT_CRS"
    OUTPUT = "OUTPUT"
    
    def tr(self, string):
        """
        Returns a translatable string with the self.tr() function.
        """
        return QCoreApplication.translate("Processing", string)

    def createInstance(self):
        return CreateHandDigitizedMap()

    def name(self):
        """
        Returns the algorithm name, used for identifying the algorithm. This
        string should be fixed for the algorithm, and must not be localised.
        The name should be unique within each provider. Names should contain
        lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        """
        return "create_hand_digitized_map"

    def displayName(self):
        """
        Returns the translated algorithm name, which should be used for any
        user-visible display of the algorithm name.
        """
        return self.tr("Create hand-digitized landcover polygons")

    def group(self):
        """
        Returns the name of the group this algorithm belongs to. This string
        should be localised.
        """
        return self.tr("WAsP scripts")

    def groupId(self):
        """
        Returns the unique ID of the group this algorithm belongs to. This
        string should be fixed for the algorithm, and must not be localised.
        The group id should be unique within each provider. Group id should
        contain lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        """
        return "wasp_scripts"

    def shortHelpString(self):
        """
        Returns a localised short helper string for the algorithm. This string
        should provide a basic description about what the algorithm does and the
        parameters and outputs associated with it..
        """
        return self.tr(
            """Creates a template for hand-digitizing landcover information

            This create a vector layer with the appropriate attributes ('id','z0','d') that can be used for creating polygons by hand after clicking the 'Toggle editing' button. The snapping settings of the project are set to avoid overlap in the active layer, so you won't be able to draw polygons that overlap. Furthermore, it will snap the nearest vertices while digitizing in the map.
            
            The only input to the script is output coordinate reference system (CRS). When selecting a CRS that is not available in the WAsP GUI it will throw an error. The minimum extent of your map can be determined roughly by multiplying the height of your turbines or met mast by 100 and adding a buffer of about 50%.
            """
        )

    def initAlgorithm(self, config=None):
        """
        Here we define the inputs and output of the algorithm, along
        with some other properties.
        """
       
        self.addParameter(
            QgsProcessingParameterCrs(
            self.OUTPUT_CRS,
            'Output coordinate reference system', 
            defaultValue='EPSG:32633'
            )
        )

        # self.addOutput(
        #     QgsProcessingOutputVectorLayer(
        #         self.OUTPUT, self.tr("Output layer"),
        #         type = QgsProcessing.TypeVectorPolygon
        #     )
        # )

        self.addParameter(
            QgsProcessingParameterVectorDestination(
                self.OUTPUT, self.tr("Output layer")
            )
        )

    def checkParameterValues(self, parameters, context):
        out_crs = self.parameterAsCrs(parameters, self.OUTPUT_CRS, context)

        # open file from map editor to find usuable projections
        dir = os.path.split(inspect.getfile(inspect.currentframe()))[0]
        #dir = "/home/rofl/OneDrive/WAsP/QGIS_plugin/wasp_scripts/processing_provider/"
        with open(os.path.join(dir,"EPSG-WAsP-LookUpTable.dat"), "r") as f:
            lines = f.read().splitlines()        
        valid_epsg = ["EPSG:" + l.split("|")[1].strip() for l in lines] # get the epsg code from the file
        str_valid_epsg = ", ".join(valid_epsg[4:]) # first four items are lat/lon
        if out_crs.authid() not in valid_epsg:
            return False, self.tr(f"You have chosen a projection that is not supported in the WAsP GUI! You can choose from the following codes: {str_valid_epsg}")

        return super().checkParameterValues(parameters, context)
            

    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """

        out_crs = self.parameterAsCrs(parameters, self.OUTPUT_CRS, context)

        polygon_layer = QgsVectorLayer("MultiPolygon", "hand-digitized polygons", "memory")
        polygon_layer.setCrs(out_crs)
        prov = polygon_layer.dataProvider()
        # f1 = QgsField("d", QVariant.Double) # in the future we could add a displacement height, but currently it is not possible to set a default value and save it to the vector layer as constraint
        # f1.setDefaultValueDefinition(QgsDefaultValue('0.0'))
        attributes = [
            QgsField("fid", QVariant.Int),
            QgsField("id", QVariant.Int),
            QgsField("z0", QVariant.Double),
            QgsField("d", QVariant.Double),
            QgsField("desc", QVariant.String,'character', 100)
        ]
        prov.addAttributes(attributes)
        prov.addFeatures([])
        polygon_layer.updateFields()

        feedback.pushInfo(f"Creating layer with CRS {polygon_layer.crs().authid()}")

        proj = set_snapping_options()
       
        (sink, self.dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context,
            polygon_layer.fields(),
            polygon_layer.wkbType(),
            polygon_layer.sourceCrs(),
        )
        
        if sink is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))

        global renamer
        renamer = Renamer('hand-digitized polygons')
        context.layerToLoadOnCompletionDetails(self.dest_id).setPostProcessor(renamer)

        # Return the results of the algorithm. In this case our only result is
        # the feature sink which contains the processed features, but some


        # this is not the way you are supposed to add layers, but adding 
        # the output layer as feature sink (as in the other scripts) causes
        # the unique constraints to dissappear (see above). If you add like this
        # below it will still have unique constraints
        # proj.addMapLayer(polygon_layer)
     
     
        # Return the results of the algorithm. In this case our only result is
        # the feature sink which contains the processed features, but some
        # algorithms may return multiple feature sinks, calculated numeric
        # statistics, etc. These should all be included in the returned
        # dictionary, with keys matching the feature corresponding parameter
        # or output names.
        return {self.OUTPUT: self.dest_id}
    
    def postProcessAlgorithm(self, context, feedback):
        global renamer
        renamer = Renamer('hand-digitized landcover polygons')
        context.layerToLoadOnCompletionDetails(self.dest_id).setPostProcessor(renamer)

        # give meaningful colors based on z0
        cmd_folder = os.path.split(inspect.getfile(inspect.currentframe()))[0]
        layer = QgsProcessingUtils.mapLayerFromString(self.dest_id, context)
        layer.loadNamedStyle(os.path.join(cmd_folder,"styles","z0_polygons.qml"))
        layer.triggerRepaint()
        
        # set not null constraints to avoid users adding null values
        layer.setFieldConstraint(layer.fields().indexFromName('z0'), QgsFieldConstraints.ConstraintNotNull)
        layer.setFieldConstraint(layer.fields().indexFromName('z0'), QgsFieldConstraints.ConstraintNotNull)
        layer.setFieldConstraint(layer.fields().indexFromName('fid'), QgsFieldConstraints.ConstraintNotNull)
        layer.setFieldConstraint(layer.fields().indexFromName('id'), QgsFieldConstraints.ConstraintNotNull)
        layer.setDefaultValueDefinition(layer.fields().indexFromName('d'), QgsDefaultValue("0.0"))
        default_val = QgsDefaultValue(
                        """
                        CASE
                        WHEN maximum("fid") is NULL THEN 1
                        WHEN "fid" is NULL THEN maximum("fid")+1
                        ELSE "fid"
                        END
                        """,
                        applyOnUpdate=True)
        layer.setDefaultValueDefinition(layer.fields().lookupField('fid'), default_val)
        layer.setDefaultValueDefinition(layer.fields().lookupField('id'), default_val)
        layer.updateFields()

        return {self.OUTPUT: self.dest_id}