# 0
from qgis.core import (
    QgsProcessingAlgorithm, QgsProcessingParameterFile,
    QgsProcessingParameterEnum, QgsProcessingParameterFolderDestination,
    QgsProcessingContext, QgsProcessingFeedback
)
from .core import ocpft


# 1
class OCPFTProcessingAlgorithm(QgsProcessingAlgorithm):
    P_FILE = 'innc'
    P_SENSOR = 'sensor'
    P_MODEL = 'model'
    P_AC = 'ac'
    P_OSIZE = 'osize'

    P_OUTPUT_FOLDER = 'outfolder'

    SENSORS_ALGO = ['EnMAP', 'OLCI', 'MSI', 'DESIS']
    SENSORS_USER = ['EnMAP (Environmental Mapping and Analysis Program, Germany)',
                    'OLCI (Ocean and Land Colour Instrument, Europe)',
                    'MSI (Medium Resolution Imaging Spectrometer, Europe)',
                    'DESIS (DLR Earth Sensing Imaging Spectrometer, Germany)']
    MODEL = ['LAKE CONSTANCE', 'GLOBAL']
    AC = ['ENPT-ACWATER Polymer', 'POLYMER']  # ONNS and other options will be added
    OSIZE = ['Standard output']  # define better

    # 2
    def group(self):
        return 'Water'

    def groupId(self):
        return 'water'  # internal id

    def displayName(self):
        return 'OC-PFT'

    def name(self):
        return 'ocpft'  # internal id

    # 3
    def initAlgorithm(self, configuration=None):

        # Define required user inputs.
        self.addParameter(QgsProcessingParameterFile(
            name=self.P_FILE, description='Input'))
        self.addParameter(QgsProcessingParameterEnum(
            name=self.P_SENSOR,
            description='Sensor',
            # options=self.SENSORS_USER,
            options=self.SENSORS_ALGO,
            defaultValue=self.SENSORS_ALGO[0]))
        self.addParameter(QgsProcessingParameterEnum(
            name=self.P_MODEL,
            description='Model',
            options=self.MODEL,
            defaultValue=self.MODEL[0]))
        self.addParameter(QgsProcessingParameterEnum(
            name=self.P_AC,
            description='Atmospheric correction',
            options=self.AC,
            defaultValue=self.AC[0]))
        self.addParameter(QgsProcessingParameterEnum(
            name=self.P_OSIZE,
            description='Processor output size',
            options=self.OSIZE,
            defaultValue=self.OSIZE[0]))

        self.addParameter(QgsProcessingParameterFolderDestination(
            name=self.P_OUTPUT_FOLDER, description='Output Folder'))

    # 4
    def processAlgorithm(
        self,
        parameters,
        context: QgsProcessingContext,
        feedback: QgsProcessingFeedback
    ):

        inputfile = self.parameterAsFile(parameters, self.P_FILE, context)
        outputDirectory = self.parameterAsFileOutput(parameters, self.P_OUTPUT_FOLDER, context)

        sensor: str = self.parameterAsEnumString(parameters, self.P_SENSOR, context)
        # the following line always returned 0 -> 'ENAMP', no matter what sensor was selected!
        # sensor = self.SENSORS_ALGO[self.parameterAsEnum(parameters, self.P_SENSOR, context)]

        if sensor not in self.SENSORS_ALGO:
            feedback.pushError(
                f'Sensor {sensor} not supported. Please choose one of the following: {self.SENSORS_ALGO}')
            return {}

        model: str = self.parameterAsEnumString(parameters, self.P_MODEL, context)
        ac: str = self.parameterAsEnumString(parameters, self.P_AC, context)
        osize: str = self.parameterAsEnumString(parameters, self.P_OSIZE, context)

        # try to execute the core algorithm
        try:
            cmd = ocpft(
                inputfile=inputfile,
                outputDirectory=outputDirectory,
                sensor=sensor,
                model=self.MODEL.index(model),
                ac=self.AC.index(ac),
                # note only polymer is implemented!!!
                osize=self.OSIZE.index(osize)
            )

            feedback.pushCommandInfo(cmd)
            # feedback.pushDebugInfo(odebugInfo)

            # return all output parameters
            return {self.P_OUTPUT_FOLDER: self.parameterAsFileOutput(parameters, self.P_OUTPUT_FOLDER, context)}

        # handle any uncatched exceptions
        except Exception as ex:
            # print traceback to console and pass it to the processing feedback object
            import traceback
            traceback.print_exc()
            msg = f'Error occurred during processing\n{ex}'
            for line in traceback.format_exc().split('\n'):
                msg += f'\n{line}'
            feedback.reportError(msg)
            return {}

    # 5
    def shortHelpString(self):

        html = (
            ''
            '<p> OC-PFT is an abundance based approach for the retrieval of Phytoplankton Functional Types (PFTs) '
            'from satellite or in situ chlorophyll-a (Chl-a) measurements using functions describing the relationship '
            'between PFT specific Chl-a to total Chl-as obtained from a large HPLC '
            'based phytoplankton pigment data set. '
            'Prior to this specific pigments serving as marker pigment for specific PFT have been converted to '
            'specific PFT-Chl-a applying the diagnostic pigment analysis '
            '[<a href="https://www.frontiersin.org/articles/10.3389/fmars.2017.00203/full">'
            'using Losa et al.,2017</a>; '
            '<a href="https://www.awi.de/en/science/climate-sciences/physical-oceanography/'
            'main-research-focus/ocean-optics/publications/non-peer-reviewed-articles.html#c33874"> '
            'updated as in Alvarado et al., 2022</a>]. </p>'
            ''
            '<h3>Input</h3>'
            '<p>The level-2 Chl-a data in NETCDF4 (Standard Polymer output) '
            'and GeoTiff formats (EnPT-ACWater output). </p>'
            ''
            '<h3>Sensor</h3>'
            '<p>Level-2 Chl-a data from the Environmental Mapping and Analysis Program (EnMAP) or '
            'the Ocean and Land Colour Instrument (OLCI) onboard Sentinel-3. '
            'Data input from other historical, current and future ocean colour sensors '
            'is possible as well (e.g. DESIS, Sentinel 2-MSI). </p>'
            ''
            '<h3>Model</h3>'
            '<p> Two models can be applied to the Level-2 Chl-a, global and Lake Constance. '
            'The first one is developed using for model development global in-situ data '
            '[<a href="https://doi.pangaea.de/10.1594/PANGAEA.954738">Xi et al., 2023</a>]) '
            'to derive PFT functions to relate the PFT specific Chl-a to the the total Chl-a, '
            'while the second corresponds to an in-situ data set specific Lake Constance (from LUBW-ISF).  </p>'
            ''
            '<h3>Atmospheric correction</h3>'
            '<p>'
            'The EnMAP Level-2 Chl-a is generated by EnPT and uses the Polymer algorithm for atmospheric correction '
            'over water. Polymer is a spectral matching algorithm in which atmospheric and '
            'oceanic signals are obtained '
            'simultaneously using the fully available visible spectrum. The algorithm was developed by '
            '<a href="www.hygeos.com"> Hygeos</a>; it is available as a python package and it has been largely '
            'applied to ocean colour sensors. The Polymer algorithm was integrated into EnPT using the wrapper module '
            'ACwater developed by AWI in cooperation with GFZ. In addition, Polymer was further adapted to process '
            'EnMAP L1B satellite data. For details on the underlying Polymer algorithm, please refer to '
            '<a href="https://doi.org/10.1364/OE.19.009783">Steinmetz et al., 2011</a> '
            '<a href="https://www.mdpi.com/1424-8220/21/12/4125"> and Soppa et al. 2021</a>. '
            ''
            '<h3>Processor output size</h3>'
            '<p>The output contains ocean colour products (PFT concentrations:  1) Chlorophyll-a; '
            '2) Diatoms; 3) Dinoflagellates; 4) Prokaryotes; 5) Prochlorococcus sp; 6) Haptophytes;  '
            '7) Green Algae; 8) Cryptophytes. </p>'
            ''
            '<h3>Output Folder</h3>'
            '<p>Specify where to save the output.  </p>'
        )
        return html

    # 7
    def createInstance(self):
        return type(self)()
