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

"""
/***************************************************************************
 GreenViewIndex
                                 A QGIS plugin
 A plugin for Green View Index (GVI) operations
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2023-04-21
        copyright            : (C) 2023 by Alexandros Voukenas
        email                : avoukenas@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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__ = 'Alexandros Voukenas'
__date__ = '2023-04-21'
__copyright__ = '(C) 2023 by Alexandros Voukenas'

# This will get replaced with a git SHA1 when you do a git archive

__revision__ = '$Format:%H$'
import os
import inspect
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing,
                       QgsFeatureSink,
                       QgsProcessingException,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterFeatureSink,
                       QgsProcessingParameterField,
                       QgsProcessingParameterEnum,
                       QgsProcessingParameterNumber,
                       QgsField,
                       QgsFields,
                       QgsFeature,
                       QgsCoordinateReferenceSystem)
from qgis import processing


class GenerateSamplePoints(QgsProcessingAlgorithm):
    
    #Set the input types and the input as parameters
    
    INPUT_AOI = 'INPUT AOI'
    INPUT_ROAD_NETWORK = 'INPUT ROAD NETWORK'
    
    GEOM_INPUT_TYPE= 'GEOMETRY INPUT TYPE'
    
    NUM_POINTS='NUMBER OF POINTS'
    MIN_DISTANCE='MINIMUM DISTANCE'
    
    OUTPUT = 'OUTPUT'
       
    def tr(self, string):
        """
        Returns a translatable string with the self.tr() function.
        """
        return QCoreApplication.translate('Processing', string)
        
    def icon(self):
        cmd_folder = os.path.split(inspect.getfile(inspect.currentframe()))[0]
        icon = QIcon(os.path.join(os.path.join(cmd_folder, 'rand_points_logo.png')))
        return icon
        
    def createInstance(self):
        return GenerateSamplePoints()

    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 'generate_sample_points'

    def displayName(self):
        """
        Returns the translated algorithm name, which should be used for any
        user-visible display of the algorithm name.
        """
        return self.tr('Generate Sample Points')

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

    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 ''

    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(r"""Given a geometry input (AOI, road network or both), this script will generate sample points within that geometry. 
        If only AOI is given, random points will be generated inside that AOI, regardless of the road network. If only road network is given, points will be generated along that network, snapped to the fieatures of it. If both are given, the road network will be clipped to the boundaries of the AOI and points will be generated along the network, snapped to the features of it. 
        The output layer will be in WGS84 Geographic Coordinate Reference System (EPSG:4326). The input geometry layers can be in any CRS, but if both AOI and road network are given, they must be in the same CRS.
        The minimum distance must be in meters (regardless of the CRS of the input geometry). Also, as a parameter, it prevails over the number of points to generate. This means that if you set both minimum distance and number of points, less points might be generated in order to satisfy the minimum distance.""")

    def initAlgorithm(self, config=None):
        """
        Here we define the inputs and output of the algorithm, along
        with some other properties.
        """
        #add the input parameters to the algorithm
        
        self.addParameter(
        QgsProcessingParameterEnum(
            self.GEOM_INPUT_TYPE,
            self.tr('Select Geometry Input type'),
            options=[self.tr('Area of Interest (AOI) only'),self.tr('Road network only'),self.tr('Area of Interest and Road network (clip)')],
            defaultValue=0,
            optional=False)
        )
        
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.INPUT_AOI,
                self.tr('Input AOI layer'),
                [QgsProcessing.TypeVectorPolygon],
                optional=True
            )
        )
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.INPUT_ROAD_NETWORK,
                self.tr('Input Road Network layer'),
                [QgsProcessing.TypeVectorLine],
                optional=True
            )
        )
        self.addParameter(
            QgsProcessingParameterNumber(
            self.MIN_DISTANCE,
            self.tr('Minimum distance between sample points'),
            defaultValue=0
            )
        )
        
        self.addParameter(
            QgsProcessingParameterNumber(
            self.NUM_POINTS,
            self.tr('Number of points to generate'),
            defaultValue=100
            )
        )
        # We add a feature sink in which to store our processed features (this
        # usually takes the form of a newly created vector layer when the
        # algorithm is run in QGIS).
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT,
                self.tr('Output sample point layer')
            )
        )

    def processAlgorithm(self, parameters, context, feedback):

        #define the source type and the other parameters for the processing
        source_geom_type=self.parameterAsEnum(
            parameters,
            self.GEOM_INPUT_TYPE,
            context
        )
        num_points=self.parameterAsDouble(
            parameters,
            self.NUM_POINTS,
            context
        )
        min_distance=self.parameterAsDouble(
            parameters,
            self.MIN_DISTANCE,
            context
        )
        
        #the fields for the ouptut layer. It will only contain a unique ID field, of integer type
        
        fields = QgsFields()
        fields.append(QgsField("random_point_id", QVariant.Int))
        
        
        if source_geom_type==0: #if user selected "AOI only" input
            
            source_AOI = self.parameterAsSource(
            parameters,
            self.INPUT_AOI,
            context
            )
            
            if source_AOI is None:
                raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT_AOI))
            
            #If the source CRS is not projected (unit not in meters), reproject it to a global projected CRS (EPSG 3395). 
            #This is so that the minimum distance parameter can be interpreted in meters during random point generation
            #Otherwise, if the CRS is projected, perform the random point generation
            if source_AOI.sourceCrs().mapUnits()!=0:
                source_AOI=processing.run('qgis:reprojectlayer',
                {'INPUT': parameters[self.INPUT_AOI],
                'TARGET_CRS': 'EPSG:3395',
                'OUTPUT': 'TEMPORARY_OUTPUT'},
                context=context, feedback=None)['OUTPUT']
                
                #create the random points
                random_points=processing.run("qgis:randompointsinsidepolygons", 
                {'INPUT':source_AOI,
                'STRATEGY':0,
                'VALUE':num_points,
                'MIN_DISTANCE':min_distance,
                'OUTPUT':'TEMPORARY_OUTPUT'},
                context=context, feedback=None)['OUTPUT']
                
            else:
                random_points=processing.run("qgis:randompointsinsidepolygons", 
                {'INPUT':parameters[self.INPUT_AOI],
                'STRATEGY':0,
                'VALUE':num_points,
                'MIN_DISTANCE':min_distance,
                'OUTPUT':'TEMPORARY_OUTPUT'},
                context=context, feedback=None)['OUTPUT']
            
        elif source_geom_type==1: #if user defined "Road network" as input type
        
            source_RN=self.parameterAsSource(
            parameters,
            self.INPUT_ROAD_NETWORK,
            context
            )
            
            if source_RN is None:
                raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT_ROAD_NETWORK))
            
            #Again, if the source is not projected CRS, reproject it to one, and then generate the random points
            if source_RN.sourceCrs().mapUnits()!=0:
                source_RN=processing.run('qgis:reprojectlayer',
                {'INPUT': parameters[self.INPUT_ROAD_NETWORK],
                'TARGET_CRS': 'EPSG:3395',
                'OUTPUT': 'TEMPORARY_OUTPUT'},
                context=context, feedback=None)['OUTPUT']
                
                random_points=processing.run('qgis:randompointsalongline',
                {'INPUT' :source_RN,
                'MIN_DISTANCE' :min_distance,
                'OUTPUT' : 'TEMPORARY_OUTPUT',
                'POINTS_NUMBER' : num_points},
                context=context, feedback=None)['OUTPUT']
            else:
                random_points=processing.run('qgis:randompointsalongline',
                {'INPUT' :parameters[self.INPUT_ROAD_NETWORK],
                'MIN_DISTANCE' :min_distance,
                'OUTPUT' : 'TEMPORARY_OUTPUT',
                'POINTS_NUMBER' : num_points},
                context=context, feedback=None)['OUTPUT']                
            
        else: #if the user defined "AOI and Road Network" as input type
            source_AOI = self.parameterAsSource(
            parameters,
            self.INPUT_AOI,
            context
            )
            source_RN=self.parameterAsSource(
            parameters,
            self.INPUT_ROAD_NETWORK,
            context
            )
            if source_RN is None:
                raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT_ROAD_NETWORK))
            if source_AOI is None:
                raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT_AOI))
            
            #clip the road network to the AOI
            clipped_rn=processing.run('qgis:clip',
            {'INPUT' :parameters[self.INPUT_ROAD_NETWORK] , 
            'OUTPUT' : 'TEMPORARY_OUTPUT', 
            'OVERLAY' : parameters[self.INPUT_AOI] },
            context=context, feedback=None)['OUTPUT']
            
            #if the output of clip is not projected CRS, reproject it to one, and then generate the random points
            if clipped_rn.sourceCrs().mapUnits()!=0:
                clipped_rn=processing.run('qgis:reprojectlayer',
                {'INPUT': clipped_rn,
                'TARGET_CRS': 'EPSG:3395',
                'OUTPUT': 'TEMPORARY_OUTPUT'},
                context=context, feedback=None)['OUTPUT']
            
            random_points=processing.run('qgis:randompointsalongline',
            {'INPUT' :clipped_rn,
            'MIN_DISTANCE' : min_distance,
            'OUTPUT' : 'TEMPORARY_OUTPUT',
            'POINTS_NUMBER' : num_points},
            context=context, feedback=None)['OUTPUT']

        #regardless of the input type, the randomm points have been generated in a projected CRS. Reproject them to WGS84 (EPSG:4326)
        random_points=processing.run('qgis:reprojectlayer',
                {'INPUT': random_points,
                'TARGET_CRS': 'EPSG:4326',
                'OUTPUT': 'TEMPORARY_OUTPUT'},
                context=context, feedback=None)['OUTPUT']
                
        #by default, the result is created as Multipart, so convert to singlepart:
        random_points=processing.run("native:multiparttosingleparts", 
                {'INPUT':random_points,
                'OUTPUT':'TEMPORARY_OUTPUT'},
                context=context, feedback=None)['OUTPUT']
        
        #set the sink parameter, where the output will be written
        (sink, dest_id) = self.parameterAsSink(
        parameters,
        self.OUTPUT,
        context, fields, random_points.wkbType(), QgsCoordinateReferenceSystem("EPSG:4326")
        )
        
        #write the output in the sink (feature, geometry, and id attribute)
        for i,f in enumerate(random_points.getFeatures()):
            new_feature =  QgsFeature()
            new_feature.setGeometry(f.geometry())
            new_feature.setAttributes([f.id()])
            sink.addFeature(new_feature, QgsFeatureSink.FastInsert)
            
        return {self.OUTPUT: dest_id}
