# -*- coding: utf-8 -*-
from qgis.PyQt.QtWidgets import QApplication
from qgis.core import QgsProject, QgsVectorLayer, QgsFillSymbol, QgsLineSymbol, QgsMarkerSymbol, QgsRasterLayer, QgsCoordinateReferenceSystem
from qgis.utils import iface

import geopandas as gpd
import pandas as pd
from urllib.parse import quote

from ..utils.utils import Utils

class MapCanvas():
    """
    Classe responsável por gerenciar o mostrador do QGIS. Utilizada somente para os dois botões presentes na tela de resultados do PRISMA.
    Botões para mostrar todas as camadas comparadas e mostrar somente camadas sobrepostas.
    """
    def __init__(self):
        """Método construtor da classe."""
        self.operation_config = None
        self.utils = Utils()
        self.basemap_name, self.basemap_link = self.utils.get_active_basemap()


    def print_all_layers_qgis(self, operation_config):
        """
        Função que printa no QGIS todas as camadas que estão sendo comparadas.

        @keyword operation_config: Dicionário que armazena configurações de operação, como por exemplo: dado de input, bases de dados selecionadas para comparação, busca por ponto, shapefile, etc...
        """
        self.operation_config = operation_config
        input = operation_config['input']
        input_standard = operation_config['input_standard']

        gdf_selected_shp = operation_config['gdf_selected_shp']
        gdf_selected_wfs = operation_config['gdf_selected_wfs']
        gdf_selected_db = operation_config['gdf_selected_db']

        if 'basemap' in operation_config['operation_config']:
            layer = QgsRasterLayer(self.basemap_link, self.basemap_name, 'wms')
        else:
            # Carrega camada mundial do OpenStreetMap
            tms = 'type=xyz&url=http://a.tile.openstreetmap.org/{z}/{x}/{y}.png'
            layer = QgsRasterLayer(tms, 'OpenStreetMap', 'wms')

        QgsProject.instance().addMapLayer(layer)
        QApplication.instance().processEvents()
        QgsProject.instance().setCrs(QgsCoordinateReferenceSystem("EPSG:4326"))
        QApplication.instance().processEvents()

        # Carrega camadas shapefiles
        index = -1
        index_show_overlay = 0
        input = input.to_crs(epsg='4326')
        for area in gdf_selected_shp:
            area = area.to_crs(epsg='4326')
            index += 1

            if len(area) > 0:
                show_qgis_areas = QgsVectorLayer(area.to_json(), operation_config['operation_config']['shp'][index]['nomeFantasiaCamada'])
                show_qgis_areas.loadSldStyle(operation_config['operation_config']['shp'][index]['estiloCamadas'][0]['stylePath'])
                QgsProject.instance().addMapLayer(show_qgis_areas)

        # Carrega camadas WFS
        index = -1
        index_show_overlay = 0
        print(operation_config['operation_config']['wfs'])
        input = input.to_crs(epsg='4326')
        for area in gdf_selected_wfs:
            area = area.to_crs(epsg='4326')
            index += 1

            if len(area) > 0:
                show_qgis_areas = QgsVectorLayer(area.to_json(),
                                                 operation_config['operation_config']['wfs'][index][
                                                     'nomeFantasiaTabelasCamadas'])
                show_qgis_areas.loadSldStyle(
                    operation_config['operation_config']['wfs'][index]['estiloTabelasCamadas'])
                QgsProject.instance().addMapLayer(show_qgis_areas)

        # Exibe de sobreposição entre input e Postgis
        index_db = 0
        for db in gdf_selected_db:
            index_layer = 0
            for area in db:
                if 'geom' in area:
                    area = area.drop(columns=['geom'])

                if len(area) > 0:
                    show_qgis_areas = QgsVectorLayer(area.to_json(),
                                                     operation_config['operation_config']['pg'][index_db][
                                                         'nomeFantasiaTabelasCamadas'][index_layer])
                    show_qgis_areas.loadSldStyle(
                        operation_config['operation_config']['pg'][index_db]['estiloTabelasCamadas'][index_layer]['stylePath'])
                    QgsProject.instance().addMapLayer(show_qgis_areas)
                index_layer += 1
            index_db += 1

        if 'aproximacao' in operation_config['operation_config']:
            show_qgis_input = QgsVectorLayer(input.to_json(), "Feição de Estudo/Sobreposição")

            show_qgis_input.loadSldStyle(operation_config['operation_config']['sld_default_layers']['buffer'])

            QgsProject.instance().addMapLayer(show_qgis_input)

            show_qgis_input_standard = QgsVectorLayer(input_standard.to_json(), "Feição de Estudo/Sobreposição (padrão)")

            self.get_input_standard_symbol(show_qgis_input_standard.geometryType(), show_qgis_input_standard)

            QgsProject.instance().addMapLayer(show_qgis_input_standard)
        else:
            show_qgis_input = QgsVectorLayer(input.to_json(), "Feição de Estudo/Sobreposição")

            self.get_input_standard_symbol(show_qgis_input.geometryType(), show_qgis_input)

            QgsProject.instance().addMapLayer(show_qgis_input)

        # Repaint the canvas map
        iface.mapCanvas().refresh()
        # Da zoom na camada de input
        iface.zoomToActiveLayer()

    def print_overlay_qgis(self, operation_config):
        """
        Função que printa no QGIS todas as camadas que apresentaram sobreposição entre camada de input e camadas selecionadas para comparação.

        @keyword operation_config: Dicionário que armazena configurações de operação, como por exemplo: dado de input, bases de dados selecionadas para comparação, busca por ponto, shapefile, etc...
        """
        self.operation_config = operation_config
        input = operation_config['input']
        input_standard = operation_config['input_standard']

        gdf_selected_shp = operation_config['gdf_selected_shp']
        gdf_selected_wfs = operation_config['gdf_selected_wfs']
        gdf_selected_db = operation_config['gdf_selected_db']

        if 'basemap' in operation_config['operation_config']:
            layer = QgsRasterLayer(self.basemap_link, self.basemap_name, 'wms')
        else:
            # Carrega camada mundial do OpenStreetMap
            tms = 'type=xyz&url=http://a.tile.openstreetmap.org/{z}/{x}/{y}.png'
            layer = QgsRasterLayer(tms, 'OpenStreetMap', 'wms')

        QgsProject.instance().addMapLayer(layer)
        QApplication.instance().processEvents()
        QgsProject.instance().setCrs(QgsCoordinateReferenceSystem("EPSG:4326"))
        QApplication.instance().processEvents()

        # Exibe de sobreposição entre input e Shapefiles
        index = -1
        index_show_overlay = 0
        gdf_input = gpd.GeoDataFrame(columns = input.columns)
        print_input = False
        input = input.to_crs(epsg='4326')
        for area in gdf_selected_shp:
            area = area.to_crs(epsg='4326')
            index += 1
            gdf_area = gpd.GeoDataFrame(columns = area.columns)
            for indexArea, rowArea in area.iterrows():
                for indexInput, rowInput in input.iterrows():
                    if (rowArea['geometry'].intersection(rowInput['geometry'])):
                        gdf_input.loc[index_show_overlay] = rowInput
                        gdf_area.loc[index_show_overlay] = rowArea
                        index_show_overlay += 1

            if len(gdf_area) > 0:
                print_input = True

                gdf_area = gdf_area.drop_duplicates()
                show_qgis_areas = QgsVectorLayer(gdf_area.to_json(), operation_config['operation_config']['shp'][index]['nomeFantasiaCamada'])
                show_qgis_areas.loadSldStyle(
                    operation_config['operation_config']['shp'][index]['estiloCamadas'][0]['stylePath'])
                QgsProject.instance().addMapLayer(show_qgis_areas)

        # Exibe de sobreposição entre input e WFS
        index = -1
        index_show_overlay = 0
        gdf_input = gpd.GeoDataFrame(columns=input.columns)
        print_input = False
        input = input.to_crs(epsg='4326')
        for area in gdf_selected_wfs:
            area = area.to_crs(epsg='4326')
            index += 1
            gdf_area = gpd.GeoDataFrame(columns=area.columns)
            for indexArea, rowArea in area.iterrows():
                for indexInput, rowInput in input.iterrows():
                    if (rowArea['geometry'].intersection(rowInput['geometry'])):
                        gdf_input.loc[index_show_overlay] = rowInput
                        gdf_area.loc[index_show_overlay] = rowArea
                        index_show_overlay += 1

            if len(gdf_area) > 0:
                print_input = True

                gdf_area = gdf_area.drop_duplicates()
                show_qgis_areas = QgsVectorLayer(gdf_area.to_json(),
                                                 operation_config['operation_config']['wfs'][index][
                                                     'nomeFantasiaTabelasCamadas'])
                show_qgis_areas.loadSldStyle(
                    operation_config['operation_config']['wfs'][index]['estiloTabelasCamadas'])
                QgsProject.instance().addMapLayer(show_qgis_areas)

        # Exibe de sobreposição entre input e Postgis
        index_db = 0
        for db in gdf_selected_db:
            index_layer = 0
            for area in db:
                gdf_area = gpd.GeoDataFrame(columns=area.columns)
                for indexArea, rowArea in area.iterrows():
                    for indexInput, rowInput in input.iterrows():
                        if (rowArea['geometry'].intersection(rowInput['geometry'])):
                            gdf_input.loc[index_show_overlay] = rowInput
                            gdf_area.loc[index_show_overlay] = rowArea
                            index_show_overlay += 1

                if len(gdf_area) > 0:
                    print_input = True

                    if 'geom' in gdf_area:
                        gdf_area = gdf_area.drop(columns=['geom'])

                    gdf_area = gdf_area.drop_duplicates()

                    show_qgis_areas = QgsVectorLayer(gdf_area.to_json(),
                                                     operation_config['operation_config']['pg'][index_db][
                                                         'nomeFantasiaTabelasCamadas'][index_layer])
                    show_qgis_areas.loadSldStyle(
                        operation_config['operation_config']['pg'][index_db]['estiloTabelasCamadas'][index_layer]['stylePath'])
                    QgsProject.instance().addMapLayer(show_qgis_areas)

                index_layer += 1
            index_db += 1

        if print_input:
            if 'aproximacao' in operation_config['operation_config']:
                gdf_input = gdf_input.drop_duplicates()

                show_qgis_input = QgsVectorLayer(gdf_input.to_json(), "Feição de Estudo/Sobreposição")

                show_qgis_input.loadSldStyle(operation_config['operation_config']['sld_default_layers']['buffer'])

                QgsProject.instance().addMapLayer(show_qgis_input)

                input_standard = input_standard.to_crs(4326)
                # gdf_input = gdf_input.to_crs(4326)
                get_overlay_standard = self.get_overlay_features(input, input_standard, gdf_selected_shp, gdf_selected_db)

                show_qgis_input_standard = QgsVectorLayer(get_overlay_standard.to_json(), "Feição de Estudo/Sobreposição (padrão)")

                self.get_input_standard_symbol(show_qgis_input_standard.geometryType(), show_qgis_input_standard)

                QgsProject.instance().addMapLayer(show_qgis_input_standard)

            else:
                input_standard = input_standard.to_crs(4326)
                # gdf_input = gdf_input.to_crs(4326)
                get_overlay_standard = self.get_overlay_features(input, input_standard, gdf_selected_shp, gdf_selected_db)

                show_qgis_input_standard = QgsVectorLayer(get_overlay_standard.to_json(),
                                                          "Feição de Estudo/Sobreposição")

                self.get_input_standard_symbol(show_qgis_input_standard.geometryType(), show_qgis_input_standard)

                QgsProject.instance().addMapLayer(show_qgis_input_standard)

            # Repaint the canvas map
            iface.mapCanvas().refresh()
            # Da zoom na camada de input
            iface.zoomToActiveLayer()

    def get_overlay_features(self, input, input_standard, gdf_selected_shp, gdf_selected_db):
        """
        Verifica, entre camada de input e camadas selecionadas para comparação, quais possuem sobreposição.

        @keyword input: Camada contendo feições de input.
        @keyword input_standard: Camada contendo feições de input, porém se o buffer de proximidade (caso necessário).
        @keyword gdf_selected_shp: Vetor de camadas shapefile selecionadas para comparação.
        @keyword gdf_selected_db: Vetor de camadas de banco de dados selecionados para comparação.
        """
        get_overlay_standard = gpd.GeoDataFrame(columns=input_standard.columns)

        # Teste com shapefile
        for area in gdf_selected_shp:
            for indexArea, rowArea in area.iterrows():
                for indexInput, rowInput in input.iterrows():
                    if rowInput['geometry'].intersection(rowArea['geometry']):
                        get_overlay_standard = gpd.GeoDataFrame(
                            pd.concat([get_overlay_standard, input_standard.iloc[[indexInput]]]))

        # Teste com banco de dados
        index_db = 0
        for db in gdf_selected_db:
            index_layer = 0
            for area in db:
                if 'geom' in area:
                    area = area.drop(columns=['geom'])

                for indexInput, rowInput in input.iterrows():
                    for indexArea, rowArea in area.iterrows():
                        if rowInput['geometry'].intersection(rowArea['geometry']):
                            get_overlay_standard = gpd.GeoDataFrame(
                                pd.concat([get_overlay_standard, input_standard.iloc[[indexInput]]]))
                index_layer += 1
            index_db += 1

        get_overlay_standard = get_overlay_standard.drop_duplicates()
        get_overlay_standard = get_overlay_standard.reset_index()

        return get_overlay_standard

    def get_input_symbol(self, geometry_type, show_qgis_input):
        """
        Estilização dinâmica para diferentes tipos de geometrias (Área de input).

        @keyword geometry_type: Tipo de geometria da área de input (com ou se buffer de área de aproximação).
        @return symbol: Retorna o objeto contendo a estilização de uma determinada camada.
        """
        symbol = None

        # Point
        if geometry_type == 0:
            show_qgis_input.loadSldStyle(
                self.operation_config['operation_config']['sld_default_layers']['default_input_point'])
        # Line String
        if geometry_type == 1:
            show_qgis_input.loadSldStyle(
                self.operation_config['operation_config']['sld_default_layers']['default_input_line'])
        # Polígono
        elif geometry_type == 2:
            show_qgis_input.loadSldStyle(
                self.operation_config['operation_config']['sld_default_layers']['default_input_polygon'])

    def get_input_standard_symbol(self, geometry_type, show_qgis_input):
        """
        Estilização dinâmica para diferentes tipos de geometrias (Área de input sem o buffer de aproximação).

        @keyword geometry_type: Tipo de geometria da área de input sem o buffer de aproximação.
        @return symbol: Retorna o objeto contendo a estilização de uma determinada camada.
        """
        symbol = None

        # Point
        if geometry_type == 0:
            show_qgis_input.loadSldStyle(
                self.operation_config['operation_config']['sld_default_layers']['default_input_point'])
        # Line String
        if geometry_type == 1:
            show_qgis_input.loadSldStyle(
                self.operation_config['operation_config']['sld_default_layers']['default_input_line'])
        # Polígono
        elif geometry_type == 2:
            show_qgis_input.loadSldStyle(
                self.operation_config['operation_config']['sld_default_layers']['default_input_polygon'])

    def get_feature_symbol(self, geometry_type, style):
        """
        Estilização dinâmica para diferentes tipos de geometrias (Áreas de comparação).

        @keyword geometry_type: Tipo de geometria da área de comparação.
        @keyword style: Variável armazena o estilo que será usado para a projeção de uma determinada camada. Este estilo é obtido através do arquivo JSON de configuração.
        @return symbol: Retorna o objeto contendo a estilização de uma determinada camada.
        """
        symbol = None

        # Point
        if geometry_type == 0 or geometry_type == 4:
            symbol = QgsMarkerSymbol.createSimple(style)
        # Line String
        elif geometry_type == 1 or geometry_type == 5:
            symbol = QgsLineSymbol.createSimple(style)
        # Polígono
        elif geometry_type == 2 or geometry_type == 6:
            symbol = QgsFillSymbol.createSimple(style)

        return symbol