# -*- coding: utf-8 -*-
# noinspection PyPep8Naming
"""
***************************************************************************
    speclib/io/ecosis.py

    Input/Output of EcoSYS spectral library data
    ---------------------
    Beginning            : 2019-08-23
    Copyright            : (C) 2020 by Benjamin Jakimow
    Email                : benjamin.jakimow@geo.hu-berlin.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 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this software. If not, see <https://www.gnu.org/licenses/>.
***************************************************************************
"""
import csv
import json
import re
from pathlib import Path
from typing import List, Union, Optional

from qgis.PyQt.QtCore import QUrlQuery
from qgis.core import QgsFeature, QgsField, QgsFields, QgsGeometry, QgsProcessingFeedback, \
    QgsVectorLayer, QgsFileUtils
from ..core import create_profile_field, is_profile_field
from ..core.spectralprofile import encodeProfileValueDict, prepareProfileValueDict, ProfileEncoding, \
    SpectralProfileFileReader, SpectralProfileFileWriter, groupBySpectralProperties, decodeProfileValueDict
from ...unitmodel import UnitConverterFunctionModel


class EcoSISSpectralLibraryWriter(SpectralProfileFileWriter):
    def __init__(self, *args, **kwds):
        super().__init__(*args, **kwds)

    @classmethod
    def id(cls) -> str:
        return 'EcoSIS'

    @classmethod
    def filterString(cls) -> str:
        return 'EcoSIS text file (*.csv)'

    def writeFeatures(self, path: str,
                      features: List[QgsFeature],
                      feedback: Optional[QgsProcessingFeedback] = None) -> List[Path]:
        if feedback is None:
            feedback = QgsProcessingFeedback()

        field = self.mField

        grouped = groupBySpectralProperties(features, field=field, fwhm=False, bbl=False, mode='features')
        files = []

        converterModel = UnitConverterFunctionModel.instance()

        for i, (k, feat) in enumerate(grouped.items()):
            p = QgsFileUtils.uniquePath(path)
            feedback.pushInfo(f'Write {len(feat)} profiles to {p}')

            f = feat[0]
            f: QgsFeature
            with open(p, 'w', newline='') as csvfile:
                has_geom = f.hasGeometry()
                k = json.loads(k)
                wl = k.get('x')
                wlu = k.get('xUnit')

                func = converterModel.convertFunction(wlu, 'nm')
                wl = [int(v) for v in func(wl)]

                FIELDNAMES = dict()
                fields = f.fields()
                for fld in fields:
                    if fld.name() != field and not is_profile_field(fld):
                        FIELDNAMES[fld.name()] = fields.indexOf(fld.name())

                fieldnames = list(FIELDNAMES.keys())
                if has_geom:
                    fieldnames.append('latitude_y')
                    fieldnames.append('longitude_x')

                for v in wl:
                    fieldnames.append(v)

                writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
                writer.writeheader()
                for f in feat:

                    data = decodeProfileValueDict(f.attribute(field))
                    row = dict()

                    # define metadata
                    for name, idx in FIELDNAMES.items():
                        row[name] = f.attribute(idx)

                    # define geometry data
                    if has_geom:
                        pt = f.geometry().asPoint()
                        row['latitude_y'] = pt.y()
                        row['longitude_x'] = pt.x()

                    # define profile data
                    y = data['y']

                    for x, y in zip(wl, y):
                        row[x] = y

                    writer.writerow(row)

            files.append(path)

        return files


class EcoSISSpectralLibraryReader(SpectralProfileFileReader):

    def __init__(self, *args, **kwds):
        super().__init__(*args, **kwds)

        self.path()

    @classmethod
    def id(cls) -> str:
        return 'EcoSIS'

    @classmethod
    def shortHelp(cls) -> str:
        info = 'Ecological Spectral Information System (<a href="https://ecosis.org/">https://ecosis.org/</a>)'
        return info

    @classmethod
    def canReadFile(cls, path: Union[str, Path]) -> bool:
        path = Path(path)
        return path.suffix == '.csv'

    def asFeatures(self) -> List[QgsFeature]:

        csvLyr = self.loadCSVLayer()

        rxIsNum = re.compile(r'^\d+(\.\d+)?$')
        wlFields = QgsFields()
        mdFields = QgsFields()

        dstFields = QgsFields()
        profileField = create_profile_field(self.KEY_Reflectance, encoding=ProfileEncoding.Json)
        dstFields.append(profileField)

        wl = []

        csvFields = csvLyr.fields()
        csvWLIndices = []
        for i, field in enumerate(csvFields):
            field: QgsField
            if field.isNumeric() and rxIsNum.match(field.name()):
                wl.append(float(field.name()))
                wlFields.append(field)
                csvWLIndices.append(i)
            else:
                mdFields.append(field)
                dstFields.append(field)

        profiles: List[QgsFeature] = []

        csv2dst = {mdFields.lookupField(f.name()): dstFields.lookupField(f.name()) for f in mdFields}

        for i, f in enumerate(csvLyr.getFeatures()):

            f: QgsFeature
            f2 = QgsFeature(dstFields)

            # y = [f.attribute(field.name()) for field in wlFields]
            y = [f.attribute(j) for j in csvWLIndices]

            xUnit = 'nm'
            d = prepareProfileValueDict(x=wl, y=y, xUnit=xUnit)
            dump = encodeProfileValueDict(d, encoding=ProfileEncoding.Json)
            f2.setAttribute(profileField.name(), dump)

            if f.hasGeometry():
                g = f.geometry()
                f2.setGeometry(QgsGeometry(g))

            for iSrc, iDst in csv2dst.items():
                f2.setAttribute(iDst, f.attribute(iSrc))
            profiles.append(f2)

        del csvLyr
        return profiles

    def loadCSVLayer(self, **kwargs) -> QgsVectorLayer:
        cLat = cLon = None
        with open(self.path(), newline='') as csvfile:
            reader = csv.reader(csvfile)
            hdr_row = next(reader)

            for c in hdr_row:
                if re.match(r'^(lat|latitude)$', c, re.I):
                    cLat = c
                if re.match(r'^(lon|longitude)$', c, re.I):
                    cLon = c
        # see https://api.qgis.org/api/classQgsVectorLayer.html#details or
        # https://qgis.org/pyqgis/master/core/QgsVectorLayer.html#delimited-text-file-data-provider-delimitedtext
        # for details of the delimitedtext driver
        query = QUrlQuery()
        # query.addQueryItem('encoding', 'UTF-8')
        query.addQueryItem('detectTypes', 'yes')
        # query.addQueryItem('watchFile', 'no')
        # query.addQueryItem('type', 'csv')
        # query.addQueryItem('subsetIndex', 'no')
        # query.addQueryItem('useHeader', 'yes')
        query.addQueryItem('delimiter', kwargs.get('delimiter', ','))
        query.addQueryItem('quote', kwargs.get('quote', '"'))
        if 'xField' in kwargs and 'yField' in kwargs:
            query.addQueryItem('xField', kwargs['xField'])
            query.addQueryItem('yField', kwargs['yField'])
        elif cLat and cLon:
            query.addQueryItem('xField', cLon)
            query.addQueryItem('yField', cLat)
        query.addQueryItem('crs', kwargs.get('crs', 'EPSG:4326'))
        query.addQueryItem('geomType', kwargs.get('geomType', 'point'))
        uri = self.path().as_uri() + '?' + query.toString()
        # uri = path.as_posix()
        lyr = QgsVectorLayer(uri, self.path().name, 'delimitedtext')
        if not (lyr.isValid()):
            raise AssertionError(f'Unable to open {uri}')
        return lyr
