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

"""
/***************************************************************************
 OpenCageProcessing
                                 A QGIS plugin
 Geocoding using the OpenCage API
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2023-01-11
        copyright            : (C) 2023 by ByteRoad
        email                : info@byteroad.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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__ = 'doublebyte'
__date__ = '2023-01-11'
__copyright__ = '(C) 2023 by opencage'

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

__revision__ = '$Format:%H$'

from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing,
                       QgsFeatureSink,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterField,
                       QgsProcessingParameterBoolean,
                       QgsProcessingParameterEnum,
                       QgsSettings,
                       QgsField,
                       QgsFields,
                       QgsWkbTypes,
                       QgsCoordinateReferenceSystem,
                       QgsFeature,
                       QgsPoint,
                       QgsProcessingParameterDefinition,
                       QgsProcessingException,
                       QgsProcessingParameterExtent,
                       QgsProcessingParameterFeatureSink)

from .QgsOpenCageGeocoder import QgsOpenCageGeocoder
from .locale_helper import LocaleHelper

import csv

# import logging
# logging.basicConfig(filename='/tmp/opencage.log', encoding='utf-8', level=logging.DEBUG)

class ForwardGeocode(QgsProcessingAlgorithm):
    """
    This algorithm takes a text file with addresses and
    creates a geospatial layer with geocoded geometries.

    In addition, it appends some structured information as attributes.

    As with all Processing algorithms, it extends the QgsProcessingAlgorithm
    class.
    """

    # 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.

    OUTPUT = 'Geocoded'
    INPUT = 'Address Layer'
    FIELD = 'Addresses'
    ABBRV = 'Abbreviated?'
    NO_ANNOTATIONS = 'No annotations'
    NO_RECORD = 'No record'
    LANGUAGE = 'Language'
    BOUNDS = 'Bounds'
    COUNTRY = 'Country'

    def initAlgorithm(self, config):
        """
        Here we define the inputs and output of the algorithm, along
        with some other properties.
        """

        # Setup country and language codes, using the helper class.
        self.localhelper = LocaleHelper()

        # We add the input source.
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.INPUT,
                self.tr('Input address file'),
                [QgsProcessing.TypeFile]
            )
        )

        self.addParameter(
            QgsProcessingParameterField(
                self.FIELD,
                self.tr("Address field"),
                '',
                self.INPUT
            )
        )

        # Set advanced parameters
        abbrvPar = QgsProcessingParameterBoolean(
            self.ABBRV, self.tr('Attempt to abbreviate and shorten the returned address (on the "formatted" field)'), defaultValue=False)
        
        noAnnotationsPar = QgsProcessingParameterBoolean(
            self.NO_ANNOTATIONS, self.tr('Additional information about the result location (e.g.: extra fields). Switch off for faster response!'), defaultValue=False)
    
        noRecordPar = QgsProcessingParameterBoolean(
            self.NO_RECORD, self.tr('Privacy mode: do not log query contents. It may limit customer support.'), defaultValue=False)

        extentPar = QgsProcessingParameterExtent(
                self.BOUNDS, 
                self.tr('Bounds: restrict the possible results to a defined bounding box'),
                optional=True)

        # Country names from here: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
        # (ISO 3166-1 alpha-2)
        countryPar = QgsProcessingParameterEnum(
                self.COUNTRY,
                self.tr('Restricts results to the specified country/territory or countries'),
                options=self.localhelper.getCountryStrings(),
                allowMultiple = True,
                defaultValue=None,
                optional=True)
        
        # Codes/names from here: https://en.wikipedia.org/wiki/IETF_language_tag
        # (List of common primary language subtags)
        langPar = QgsProcessingParameterEnum(
                self.LANGUAGE,
                self.tr('Format results in this language, if possible'),
                options=self.localhelper.getLanguageStrings(),
                defaultValue=0,
                optional=False)
        
        abbrvPar.setFlags(abbrvPar.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
        self.addParameter(abbrvPar)

        noAnnotationsPar.setFlags(noAnnotationsPar.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
        self.addParameter(noAnnotationsPar)
    
        noRecordPar.setFlags(noRecordPar.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
        self.addParameter(noRecordPar)

        extentPar.setFlags(extentPar.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
        self.addParameter(extentPar)

        countryPar.setFlags(countryPar.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
        self.addParameter(countryPar)

        langPar.setFlags(langPar.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
        self.addParameter(langPar)

        # 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('Geocoded addresses layer')
            )
        )

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

        # Grab address field
        address = self.parameterAsFields(
            parameters,
            self.FIELD,
            context
        )[0]

        abbreviation = 1 if self.parameterAsBool(parameters, self.ABBRV, context) == True else 0
        no_annotations = 0 if self.parameterAsBool(parameters, self.NO_ANNOTATIONS, context) == True else 1
        no_record = 1 if self.parameterAsBool(parameters, self.NO_RECORD, context) == True else 0

        lang_idx= self.parameterAsInt(parameters, self.LANGUAGE, context)
        language = self.localhelper.parseLanguage(lang_idx)

        country_ids= self.parameterAsEnums(parameters, self.COUNTRY, context)
        # logging.debug('ids: {}'.format(country_ids))

        countries=[]
        for i in country_ids:
            countries.append(self.localhelper.countries[i][0])

        countries_str = ','.join(countries)

        crs = QgsCoordinateReferenceSystem("EPSG:4326")
        extent = self.parameterAsExtent(parameters, self.BOUNDS, context, crs)
        # logging.debug(extent)

        settings = QgsSettings()
        self.api_key = settings.value('/plugins/opencage/api_key', '', str)

        geocoder = QgsOpenCageGeocoder(self.api_key, True, no_annotations)

        # Retrieve the feature source and sink. The 'dest_id' variable is used
        # to uniquely identify the feature sink, and must be included in the
        # dictionary returned by the processAlgorithm function.
        source = self.parameterAsSource(parameters, self.INPUT, context)

        crs = QgsCoordinateReferenceSystem("EPSG:4326")
        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                context, geocoder.appendedFields(), QgsWkbTypes.Point , crs)

        # Compute the number of steps to display within the progress bar and
        # get features from source
        total = 100.0 / source.featureCount() if source.featureCount() else 0
        features = source.getFeatures()

        try:

            for current, feature in enumerate(features):
                # Stop the algorithm if cancel button has been clicked
                if feedback.isCanceled():
                    break

                # Retrieve the geometry and address (later we can let user decide which fields to include)
                d = feature.attribute(feature.fieldNameIndex(address))
                new_feature = geocoder.forward(d, abbreviation, no_annotations, 
                                               no_record, language, extent, 
                                               countries_str, context, feedback)
                if new_feature:
                    sink.addFeature(new_feature, QgsFeatureSink.FastInsert)

                # Update the progress bar
                feedback.setProgress(int(current * total))

            return {self.OUTPUT: dest_id}
    
        except Exception as e:
            feedback.reportError("Error: {}".format(e), True)
            raise QgsProcessingException

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

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

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

    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 None

    def tr(self, string):
        return QCoreApplication.translate('Processing', string)

    def createInstance(self):
        return ForwardGeocode()

    def shortHelpString(self):
        """
        Returns a localised short help string for the algorithm.
        """
        return self.tr('<p>Convert addresses (e.g.: city names, place names, countries, postcodes or other form of location text in human language) to point geometries. This process is also known as forward geocoding.</p> <p>The original address used for geocoding is appended as an attribute in the output file.</p><p>For information about the other attributes, please check the help and <a href="https://opencagedata.com/tutorials/geocode-in-qgis">tutorial</a></p>.')
    
    def helpString(self):
        """
        Returns a localised help string for the algorithm.
        """
        return self.tr('Geocoding addresses')
    
    def helpUrl(self):
        """
        Returns the help url.
        """
        return "https://opencagedata.com/api"
    
