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

"""
/***************************************************************************
 GeoCAR
                                 A QGIS plugin
Cadastro Ambiental Rural (CAR)
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2026-02-27
        copyright            : (C) 2026 by Prof Cazaroli e Leandro França
        email                : contato@geoone.com.br
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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__ = 'Prof Cazaroli e Leandro França'
__date__ = '2026-02-27'
__copyright__ = '(C) 2026 by Prof Cazaroli e Leandro França'

from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import *
from qgis import processing
from qgis.PyQt.QtGui import QIcon, QColor
from geocar.images.Imgs import *
import os

class altitude_1800_Declividade_45(QgsProcessingAlgorithm):
    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterRasterLayer('mdt', 'MDT', defaultValue=None))
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                'rea_do_imvel',
                'Área do imóvel',
                types=[QgsProcessing.TypeVectorPolygon],
                defaultValue=None
            )
        )

        self.addParameter(QgsProcessingParameterCrs('src', 'SRC (projetado)', defaultValue='EPSG:31983'))

        self.addParameter(
            QgsProcessingParameterFeatureSink(
                'ReaAcimaDe1800m',
                'Área acima de 1800 m',
                type=QgsProcessing.TypeVectorAnyGeometry,
                createByDefault=True,
                defaultValue=None
            )
        )
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                'DeclividadeAcimaDe45',
                'Declividade acima de 45°',
                type=QgsProcessing.TypeVectorAnyGeometry,
                createByDefault=True,
                defaultValue=None
            )
        )

    # Validações
    def _check_lftools_installed(self):
        """Verifica se o algoritmo do LFTools está disponível no Processing."""
        try:
            processing.algorithmHelp('lftools:binarythresholding')
        except Exception:
            raise QgsProcessingException(
                "O plugin LFTools não foi encontrado no Processing.\n"
                "Instale/ative o LFTools e reinicie o QGIS. (Algoritmo esperado: lftools:binarythresholding)"
            )

    def _check_projected_crs(self, crs: QgsCoordinateReferenceSystem):
        """Verifica se CRS é projetado (unidade em metros)."""
        if not crs or not crs.isValid():
            raise QgsProcessingException("SRC inválido. Selecione um SRC projetado (ex.: UTM).")

        # Preferir validação por tipo + unidade
        if crs.isGeographic():
            raise QgsProcessingException(
                f"O SRC escolhido ({crs.authid()}) NÃO é projetado.\n"
                "Para declividade (slope) e recortes por altitude, use um SRC projetado (ex.: UTM/SIRGAS 2000).\n"
                "Ex.: EPSG:31983 / EPSG:31984 / EPSG:32724 etc."
            )

    def processAlgorithm(self, parameters, context, model_feedback):
        feedback = QgsProcessingMultiStepFeedback(10, model_feedback)
        results = {}
        outputs = {}

        # --- Validação 1: LFTools instalado ---
        self._check_lftools_installed()

        # --- Validação 2: CRS projetado ---
        target_crs = self.parameterAsCrs(parameters, 'src', context)
        self._check_projected_crs(target_crs)

        # -------------------------
        # 1) Reprojetar coordenadas (raster MDT)
        # -------------------------
        alg_params = {
            'DATA_TYPE': 0,
            'EXTRA': None,
            'INPUT': parameters['mdt'],
            'MULTITHREADING': False,
            'NODATA': None,
            'OPTIONS': None,
            'RESAMPLING': 2,  # Cúbico
            'SOURCE_CRS': None,
            'TARGET_CRS': target_crs,
            'TARGET_EXTENT': None,
            'TARGET_EXTENT_CRS': None,
            'TARGET_RESOLUTION': None,
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ReprojetarCoordenadas'] = processing.run(
            'gdal:warpreproject',
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True
        )

        feedback.setCurrentStep(1)
        if feedback.isCanceled():
            return {}

        # -------------------------
        # 2) Limiarização Binária (ALTITUDE >= 1800)
        # IMPORTANTE: usar o raster reprojetado
        # -------------------------
        alg_params = {
            'METHOD': 3,
            'OPEN': False,
            'RasterIN': outputs['ReprojetarCoordenadas']['OUTPUT'],
            'SAMPLES': None,
            'VALUES': '1800,6000',
            'RasterOUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['LimiarizacaoBinaria'] = processing.run(
            'lftools:binarythresholding',
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True
        )

        feedback.setCurrentStep(2)
        if feedback.isCanceled():
            return {}

        # -------------------------
        # 3) Declividade (slope)
        # -------------------------
        alg_params = {
            'INPUT': outputs['ReprojetarCoordenadas']['OUTPUT'],
            'Z_FACTOR': 1,
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['Declividade'] = processing.run(
            'native:slope',
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True
        )

        feedback.setCurrentStep(3)
        if feedback.isCanceled():
            return {}

        # -------------------------
        # 4) Raster para vetor (poligonizar) - ALTITUDE
        # -------------------------
        alg_params = {
            'BAND': 1,
            'EIGHT_CONNECTEDNESS': False,
            'EXTRA': None,
            'FIELD': 'DN',
            'INPUT': outputs['LimiarizacaoBinaria']['RasterOUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['PolygonizeAlt'] = processing.run(
            'gdal:polygonize',
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True
        )

        feedback.setCurrentStep(4)
        if feedback.isCanceled():
            return {}

        # -------------------------
        # 5) Extrair por atributo (DN = 1)
        # -------------------------
        alg_params = {
            'FIELD': 'DN',
            'INPUT': outputs['PolygonizeAlt']['OUTPUT'],
            'OPERATOR': 0,
            'VALUE': '1',
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ExtractAlt'] = processing.run(
            'native:extractbyattribute',
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True
        )

        feedback.setCurrentStep(5)
        if feedback.isCanceled():
            return {}

        # -------------------------
        # 6) Recortar pela área do imóvel - ALTITUDE
        # -------------------------
        out_param_alt = parameters['ReaAcimaDe1800m']
        
        # Verifica se já é uma definição de camada, se não, cria uma
        if not isinstance(out_param_alt, QgsProcessingOutputLayerDefinition):
            output_alt = QgsProcessingOutputLayerDefinition(out_param_alt, context.project())
        else:
            output_alt = out_param_alt
            
        # Define o nome que aparecerá na legenda
        output_alt.destinationName = 'Area_1800m'

        alg_params = {
            'INPUT': outputs['ExtractAlt']['OUTPUT'],
            'OVERLAY': parameters['rea_do_imvel'],
            'OUTPUT': output_alt
        }
        outputs['ClipAlt'] = processing.run(
            'native:clip',
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True
        )
        results['ReaAcimaDe1800m'] = outputs['ClipAlt']['OUTPUT']

        feedback.setCurrentStep(6)
        if feedback.isCanceled():
            return {}

        # -------------------------
        # 7) Limiarização Binária (DECLIVIDADE >= 45)
        # -------------------------
        alg_params = {
            'METHOD': 3,
            'OPEN': False,
            'RasterIN': outputs['Declividade']['OUTPUT'],
            'SAMPLES': None,
            'VALUES': '45,90',
            'RasterOUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['LimiarizacaoBinaria_2'] = processing.run(
            'lftools:binarythresholding',
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True
        )

        feedback.setCurrentStep(7)
        if feedback.isCanceled():
            return {}

        # -------------------------
        # 8) Polygonize - DECLIVIDADE
        # -------------------------
        alg_params = {
            'BAND': 1,
            'EIGHT_CONNECTEDNESS': False,
            'EXTRA': None,
            'FIELD': 'DN',
            'INPUT': outputs['LimiarizacaoBinaria_2']['RasterOUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['PolygonizeSlope'] = processing.run(
            'gdal:polygonize',
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True
        )

        feedback.setCurrentStep(8)
        if feedback.isCanceled():
            return {}

        # -------------------------
        # 9) Extrair por atributo (DN = 1)
        # -------------------------
        alg_params = {
            'FIELD': 'DN',
            'INPUT': outputs['PolygonizeSlope']['OUTPUT'],
            'OPERATOR': 0,
            'VALUE': '1',
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ExtractSlope'] = processing.run(
            'native:extractbyattribute',
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True
        )

        feedback.setCurrentStep(9)
        if feedback.isCanceled():
            return {}

        # -------------------------
        # 10) Recortar pela área do imóvel - DECLIVIDADE
        # -------------------------
        out_param_slope = parameters['DeclividadeAcimaDe45']
        
        if not isinstance(out_param_slope, QgsProcessingOutputLayerDefinition):
            output_slope = QgsProcessingOutputLayerDefinition(out_param_slope, context.project())
        else:
            output_slope = out_param_slope
            
        output_slope.destinationName = 'Declividade_45'

        alg_params = {
            'INPUT': outputs['ExtractSlope']['OUTPUT'],
            'OVERLAY': parameters['rea_do_imvel'],
            'OUTPUT': output_slope
        }
        outputs['ClipSlope'] = processing.run(
            'native:clip',
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True
        )
        results['DeclividadeAcimaDe45'] = outputs['ClipSlope']['OUTPUT']

        # Guardamos os IDs para o post-process
        self.output_alt_id = outputs['ClipAlt']['OUTPUT']
        self.output_slope_id = outputs['ClipSlope']['OUTPUT']

        return {
            'ReaAcimaDe1800m': self.output_alt_id,
            'DeclividadeAcimaDe45': self.output_slope_id
        }

    def name(self):
        return 'altitude_1800_Declividade_45'

    def displayName(self):
        return '2. Altitude > 1800 m e Declividade > 45°'

    def group(self):
        return self.tr(self.groupId())

    def groupId(self):
        return ''

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

    def createInstance(self):
        return altitude_1800_Declividade_45()
    
    def icon(self):
        return QIcon(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images/geocar.png'))

    def shortHelpString(self):
        txt = """Identifica áreas com <b>altitude superior a 1800 m</b> e/ou <b>declividade maior que 45°</b> a partir de um Modelo Digital do Terreno (MDT).

A ferramenta calcula automaticamente a declividade do MDT, classifica os valores acima dos limites definidos e gera polígonos recortados pela <b>área do imóvel</b>, permitindo delimitar regiões que podem corresponder a <b>Áreas de Preservação Permanente (APP)</b>.

<b>Parâmetros principais:</b>
• MDT – raster do Modelo Digital do Terreno  
• Área do imóvel – limite utilizado para recorte da análise  
• SRC (projetado) – sistema de coordenadas em metros (ex.: UTM)

<b>Resultado:</b>
São geradas duas camadas vetoriais:
• <b>Área acima de 1800 m</b>  
• <b>Declividade acima de 45°</b>

<b>Observação:</b>
O processamento requer um <b>SRC projetado</b> (ex.: UTM) e o plugin <b>LFTools</b> instalado no QGIS.
"""

        footer = '''<div>
                      <div align="center">
                      <img style="width: 100%; height: auto;" src="data:image/jpg;base64,'''+ CAR_GeoOne +'''
                      </div>
                      <div align="right">
                      <p align="right">
                      <a href="https://geoone.com.br/pvcar/"><span style="font-weight: bold;">Conheça o curso de Cadastro Ambiental Rural (CAR)</span></a>
                      </p>
                      <p align="right">
                      <a href="https://portal.geoone.com.br/m/lessons/car?classId=6080"><span style="font-weight: bold;">Acesse a aula sobre esta ferramenta no curso de CAR da GeoOne</span></a>
                      </p>
                      <a target="_blank" rel="noopener noreferrer" href="https://geoone.com.br/"><img title="GeoOne" src="data:image/png;base64,'''+ GeoOne +'''"></a>
                      <p><i>"Mapeamento automatizado, fácil e direto ao ponto é na GeoOne!"</i></p>
                      </div>
                    </div>'''
        return txt + footer

    def postProcessAlgorithm(self, context, feedback):
        def aplicar_hachura(layer_id, cor_hex):
            # Obtém a camada a partir do ID/String
            layer = QgsProcessingUtils.mapLayerFromString(layer_id, context)
            if not layer:
                return

            # 1. Criar a camada de hachura (linhas)
            hachura = QgsLinePatternFillSymbolLayer()
            hachura.setColor(QColor(cor_hex))
            hachura.setLineAngle(45)
            hachura.setDistance(2.0)   # Espaçamento entre linhas
            hachura.setLineWidth(0.5)  # Espessura da linha

            # 2. Criar símbolo de preenchimento com contorno
            symbol = QgsFillSymbol.createSimple({
                'color': '0,0,0,0',          # transparente
                'outline_color': cor_hex,    # cor da borda
                'outline_width': '0.4'       # espessura da borda
            })

            # 3. Adicionar a hachura como camada extra
            symbol.appendSymbolLayer(hachura)

            # 4. Aplicar o renderizador
            layer.setRenderer(QgsSingleSymbolRenderer(symbol))
            layer.triggerRepaint()

        # Cores solicitadas:
        aplicar_hachura(self.output_alt_id, "#00ffeb")   # Altitude
        aplicar_hachura(self.output_slope_id, "#44a0e6") # Declividade

        return {}