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

"""
/***************************************************************************
 GeoINCRA
                                 A QGIS plugin
 Georreferenciamento de Imóveis Rurais
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2022-02-13
        copyright            : (C) 2022 by Tiago Prudencio 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__ = 'Tiago Prudencio e Leandro França'
__date__ = '2025-01-09'
__copyright__ = '(C) 2025 by Tiago Prudencio e Leandro França'

from qgis.PyQt.QtCore import QCoreApplication, QMetaType
from qgis.core import *
from qgis.PyQt.QtGui import QIcon, QFont, QColor
from .Funcs import LerPDF
from datetime import datetime
import os

class LayersFromPDF(QgsProcessingAlgorithm):

    PDF = 'PDF'
    VERTICE = 'VERTICE'
    LIMITE = 'LIMITE'
    PARCELA = 'PARCELA'

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

    def createInstance(self):
        return LayersFromPDF()

    def name(self):
        return 'LayersFromPDF'.lower()

    def displayName(self):
        return self.tr('b. Memorial do Sigef para Camadas (.pdf)')

    def group(self):
        return self.tr('4. 📥 Importação como camadas no QGIS')

    def groupId(self):
        return 'importacao_camadas_qgis'

    def icon(self):
        return QIcon(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images/geoincra_pb.png'))
    
    def tags(self):
        return 'GeoOne,GeoRural,INCRA,Sigef,memorial,pdf,conversão,tranformar,descritivo,documento,cartório,matrícula,regularização,fundiária,layer,geopackage'.split(',')

    
    def shortHelpString(self):
        txt = 'Esta ferramenta faz a leitura do arquivo <b>PDF do Memorial Descritivo Tabular</b> do SIGEF/INCRA, convertendo nas camadas vétice (ponto), limite (linha) e parcela (polígono) no padrão GeoRural.'

        footer = '''<div>
                      <div align="center">
                      <a target="_blank" rel="noopener noreferrer" href="https://geoone.com.br/pvgeoincra/"><img title="Inscreva-se no curso de GeoINCRA" style="width: 100%; height: auto;" src="'''+ os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images/INCRA_GeoOne.jpg') +'''"></a>
                      </div>
                      <div align="right">
                      <p align="right">
                      <a href="https://geoone.com.br/pvgeoincra/"><span style="font-weight: bold;">Conheça o curso de GeoINCRA no QGIS</span></a>
                      </p>
                      <p align="right">
                      <a href="https://portal.geoone.com.br/m/lessons/geoincra?classId=5820"><span style="font-weight: bold;">Acesse a aula sobre esta ferramenta no curso de GeoINCRA no GeoOne</span></a>
                      </p>
                      <a target="_blank" rel="noopener noreferrer" href="https://geoone.com.br/"><img title="GeoOne" width="280"  src="'''+ os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images/GeoOne.png') +'''"></a>
                      <p><i>"Mapeamento automatizado, fácil e direto ao ponto é na GeoOne!"</i></p>
                      </div>
                    </div>'''
        return txt + footer

    def initAlgorithm(self, config=None):

        self.addParameter(
        QgsProcessingParameterFile(
            self.PDF,
            self.tr('Memorial Descritivo Sigef (PDF)'),
            fileFilter= 'Arquivo PDF (*.pdf)'
            )
        )

        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.VERTICE,
                self.tr('Vértices do Memorial')
            )
        )

        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.LIMITE,
                self.tr('Limites do Memorial')
            )
        )

        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.PARCELA,
                self.tr('Parcela do Memorial')
            )
        )

    def processAlgorithm(self, parameters, context, feedback):

        pdf_path = self.parameterAsString(
            parameters,
            self.PDF,
            context
        )

        if pdf_path is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.PDF))


        # Sistema de Referência de Coordenadas
        SRC = QgsCoordinateReferenceSystem('EPSG:4674')

        # Criar camada de Pontos
        feedback.pushInfo('Criando camada Vértice...')
        Fields1 = QgsFields()
        itens  = {   'indice': QMetaType.Type.Int,
                     'vertice': QMetaType.Type.QString,
                     'tipo_verti': QMetaType.Type.QString,
                     'metodo_pos' : QMetaType.Type.QString,
                     'sigma_x' : QMetaType.Type.Double,
                     'sigma_y' : QMetaType.Type.Double,
                     'sigma_z' : QMetaType.Type.Double,
                     }
        for item in itens:
            Fields1.append(QgsField(item, itens[item]))

        (sink1, dest_id1) = self.parameterAsSink(
            parameters,
            self.VERTICE,
            context,
            Fields1,
            QgsWkbTypes.PointZ,
            SRC
        )
        if sink1 is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.VERTICE))

        # Criar camada de Linhas
        feedback.pushInfo('Criando camada Limite...')
        Fields2 = QgsFields()
        itens  = {   'tipo': QMetaType.Type.QString,
                     'confrontan': QMetaType.Type.QString,
                     'cns': QMetaType.Type.QString,
                     'matricula' : QMetaType.Type.QString,
                     }
        for item in itens:
            Fields2.append(QgsField(item, itens[item]))

        (sink2, dest_id2) = self.parameterAsSink(
            parameters,
            self.LIMITE,
            context,
            Fields2,
            QgsWkbTypes.LineStringZ,
            SRC
        )
        if sink2 is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.LIMITE))

        # Criar camada de Polígono
        feedback.pushInfo('Criando camada Parcela...')
        Fields3 = QgsFields()
        itens  = {   'nome': QMetaType.Type.QString,
                     'nat_serv': QMetaType.Type.Int,
                     'pessoa': QMetaType.Type.Int,
                     'cpf_cnpj' : QMetaType.Type.QString,
                     'denominacao': QMetaType.Type.QString,
                     'situacao': QMetaType.Type.Int,
                     'natureza': QMetaType.Type.Int,
                     'sncr': QMetaType.Type.QString,
                     'matricula': QMetaType.Type.QString,
                     'cod_cartorio': QMetaType.Type.QString,
                     'municipio': QMetaType.Type.QString,
                     'uf': QMetaType.Type.QString,
                     'resp_tec': QMetaType.Type.QString,
                     'reg_prof': QMetaType.Type.QString,
                     'data': QMetaType.Type.QDate,
                     }
        for item in itens:
            Fields3.append(QgsField(item, itens[item]))

        (sink3, dest_id3) = self.parameterAsSink(
            parameters,
            self.PARCELA,
            context,
            Fields3,
            QgsWkbTypes.MultiPolygonZ,
            SRC
        )
        if sink3 is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.PARCELA))
        
        # Ler PDF
        dic, lista_cod, dic_cod, ind_encravado = LerPDF(pdf_path, feedback = feedback)

        if len(lista_cod) == 0 or dic['Denominação:'] == '':
            raise QgsProcessingException('PDF de entrada não é um Memorial do Sigef!')

        feedback.pushInfo('Alimentando camada Vértice (pontos)...')
        cont = 0
        pnts = {}
        for codigo in lista_cod:
            lon = dic_cod[codigo]['lon'].replace('°', ' ').replace('"', ' ').replace("'", ' ').replace(',','.').split(' ')
            X = (abs(float(lon[0])) + float(lon[1])/60 + float(lon[2])/3600)*(-1 if dic_cod[lista_cod[-1]]['lon'][0] == '-' else 1)
            lat = dic_cod[codigo]['lat'].replace('°', ' ').replace('"', ' ').replace("'", ' ').replace(',','.').split(' ')
            Y = (abs(float(lat[0])) + float(lat[1])/60 + float(lat[2])/3600)*(-1 if dic_cod[lista_cod[-1]]['lat'][0] == '-' else 1)
            Z = float(dic_cod[codigo]['h'].replace(',','.'))
            feat = QgsFeature(Fields1)
            pnts[codigo] = QgsPoint(X,Y,Z)
            feat.setGeometry(QgsGeometry(QgsPoint(X,Y,Z)))
            cont += 1
            feat['indice'] = cont
            feat['vertice'] = codigo
            feat['tipo_verti'] = codigo.split('-')[1]
            feat['metodo_pos'] = ''
            feat['sigma_x'] = None
            feat['sigma_y'] = None
            feat['sigma_z'] = None
            sink1.addFeature(feat, QgsFeatureSink.FastInsert)
            if feedback.isCanceled():
                break

        feedback.pushInfo('Alimentando camada Limite (linhas)...')
        # Se encravado, fatiar lista_cod
        def fatiar_lista(a, ind):
            ind = [0] + ind + [len(a)]
            return [a[ind[i]:ind[i+1]] for i in range(len(ind)-1)]

        if len(ind_encravado) > 0:
            listas_fat = fatiar_lista(lista_cod, ind_encravado)
        else:
            listas_fat = [lista_cod]

        for lista_cod_fat in listas_fat:
            linha = []
            anterior_cns = dic_cod[lista_cod_fat[0]]['cns'].replace('NULL','')
            anterior_mat = dic_cod[lista_cod_fat[0]]['matr'].replace('NULL','')
            anterior_confr = dic_cod[lista_cod_fat[0]]['confr'].replace('NULL','')

            for k, codigo in enumerate(lista_cod_fat):
                linha += [pnts[codigo]]
                cns = dic_cod[codigo]['cns'].replace('NULL','')
                matricula = dic_cod[codigo]['matr'].replace('NULL','')
                confrontante = dic_cod[codigo]['confr'].replace('NULL','')
                if ((cns+matricula+confrontante) != (anterior_cns+anterior_mat+anterior_confr)):
                    feat = QgsFeature(Fields2)
                    feat.setGeometry(QgsGeometry(QgsLineString(linha)))
                    feat['tipo'] = ''
                    feat['confrontan'] = anterior_confr
                    feat['cns'] = anterior_cns
                    feat['matricula'] = anterior_mat
                    sink2.addFeature(feat, QgsFeatureSink.FastInsert)
                    anterior_mat = matricula
                    anterior_cns = cns
                    anterior_confr = confrontante
                    linha = [pnts[codigo]]
                if feedback.isCanceled():
                    break

            # Último segmento
            feat = QgsFeature(Fields2)
            if (cns+matricula+confrontante) == (anterior_cns+anterior_mat+anterior_confr):
                linha += [pnts[lista_cod_fat[0]]]
                feat['tipo'] = ''
                feat['confrontan'] = anterior_confr
                feat['cns'] = anterior_cns
                feat['matricula'] = anterior_mat
            else:
                linha = [pnts[lista_cod_fat[k]], pnts[lista_cod_fat[0]]]
                feat['tipo'] = ''
                feat['confrontan'] = confrontante
                feat['cns'] = cns
                feat['matricula'] = matricula
            feat.setGeometry(QgsGeometry(QgsLineString(linha)))
            sink2.addFeature(feat, QgsFeatureSink.FastInsert)

        # Conversões
        dic_nat_ser = {'Particular':1, 'Contrato com Administração Pública':2}
        dic_pessoa, dic_situacao  = {'Física':1, 'Jurídica':2}, {'Imóvel Registrado':1, 'Área Titulada não Registrada':2, 'Área não Titulada':3}
        dic_natureza = {'Assentamento':1,'Assentamento Parcela':2,'Estrada':3,'Ferrovia':4,'Floresta Pública':5,'Gleba Pública':6,'Particular':7,'Perímetro Urbano':8,'Terra Indígena':9,'Terreno de Marinha':10,'Terreno Marginal':11,'Território Quilombola':12,'Unidade de Conservação':13}

        # Data da certificação
        try:
            dataAss = datetime.strptime(dic['Data Certificação:'], '%d/%m/%Y %H:%M')
        except:
            try:
                dataAss = datetime.strptime(dic['Data Certificação:'], '%d/%m/%y %H:%M')
            except:
                dataAss = None
        if dataAss:
            dataCert = dataAss.isoformat()
        else:
            try:
                dataAss = datetime.strptime(dic['Data da Geração:'], '%d/%m/%Y %H:%M')
            except:
                try:
                    dataAss = datetime.strptime(dic['Data da Geração:'], '%d/%m/%y %H:%M')
                except:
                    dataAss = None
            if dataAss:
                dataCert = dataAss.isoformat()
            else:
                dataCert = datetime.now().date().isoformat()

        feedback.pushInfo('Alimentando camada Parcela (polígono)...')
        feat = QgsFeature(Fields3)
        feat['nome'] = dic['Proprietário(a):'] if dic['Proprietário(a):'] else dic['Proprietário:']
        feat['pessoa'] =  1 if dic['CPF:'] != '' else 2
        feat['cpf_cnpj'] = dic['CPF:'] if dic['CPF:'] != '' else dic['CNJP:']
        feat['denominacao'] = dic['Denominação:']
        feat['natureza'] = dic_natureza[dic['Natureza da Área:']]
        feat['sncr'] = dic['Código INCRA/SNCR:']
        feat['matricula'] = dic['Matrícula do imóvel:'] if dic['Matrícula do imóvel:'] else dic['Transcrição do imóvel:']
        feat['cod_cartorio'] = dic['Cartório (CNS):']
        feat['municipio'] = dic['Município/UF:'].split('-')[0]
        feat['uf'] = dic['Município/UF:'].split('-')[-1]
        feat['resp_tec'] = dic['Responsável Técnico(a):'] if dic['Responsável Técnico(a):'] else dic['Responsável Técnico:']
        feat['reg_prof'] = dic['Conselho Profissional:']
        feat['data'] = dataCert


        # Lista de pontos
        for k, lista_cod_fat in enumerate(listas_fat):
            lista_pontos = []
            for codigo in lista_cod_fat:
                lista_pontos += [pnts[codigo]]
            lista_pontos+[lista_pontos[0]]

            # Anel externo
            if k == 0:
                anel_ext = QgsLineString(lista_pontos)
                pol = QgsPolygon(anel_ext)
            else: # interno
                anel_int = QgsLineString(lista_pontos)
                pol.addInteriorRing(anel_int)

        mPol = QgsMultiPolygon()
        mPol.addGeometry(pol)
        newGeom = QgsGeometry(mPol)
        feat.setGeometry(newGeom)
        sink3.addFeature(feat, QgsFeatureSink.FastInsert)

        self.SAIDA_VERTICE = dest_id1
        self.SAIDA_LIMITE = dest_id2
        self.SAIDA_PARCELA = dest_id3

        return {self.VERTICE: dest_id1,
                self.LIMITE: dest_id2,
                self.PARCELA: dest_id3}
    

    def postProcessAlgorithm(self, context, feedback):
        # Recupera camadas (saídas do processamento)
        camada_vertice = QgsProcessingUtils.mapLayerFromString(self.SAIDA_VERTICE, context)
        camada_limite  = QgsProcessingUtils.mapLayerFromString(self.SAIDA_LIMITE, context)
        camada_parcela = QgsProcessingUtils.mapLayerFromString(self.SAIDA_PARCELA, context)

        # try:

        # -------------------------------------------------------
        # 1) Criar/posicionar grupo no TOPO e inserir as 3 camadas
        # -------------------------------------------------------
        proj = QgsProject.instance()
        root = proj.layerTreeRoot()

        group_name = "Camadas do Memorial do Sigef"
        group = root.findGroup(group_name)

        # cria no topo, ou recria no topo se já existir em outro lugar
        if group is None:
            group = root.insertGroup(0, group_name)  # topo do painel
        else:
            idx = root.children().index(group)
            if idx != 0:
                root.removeChildNode(group)
                group = root.insertGroup(0, group_name)

        # opcional: limpar conteúdo antigo do grupo a cada execução
        # group.removeAllChildren()

        # garante que as layers estão no projeto (registry)
        for lyr in (camada_vertice, camada_limite, camada_parcela):
            if proj.mapLayer(lyr.id()) is None:
                proj.addMapLayer(lyr, False)

        # remove nodes existentes dessas layers (de onde estiverem), evitando duplicação
        def _detach_layer_node(layer):
            node = root.findLayer(layer.id())
            if node is not None and node.parent() is not None:
                node.parent().removeChildNode(node)

        _detach_layer_node(camada_vertice)
        _detach_layer_node(camada_limite)
        _detach_layer_node(camada_parcela)

        # insere no grupo na ordem desejada (topo -> baixo)
        group.insertLayer(0, camada_vertice)
        group.insertLayer(1, camada_limite)
        group.insertLayer(2, camada_parcela)
        group.setExpanded(True)

        # -----------------------------
        # Helper rotulação (sem placement)
        # -----------------------------
        def _apply_labeling(layer, expr, font_size=10, color="white",
                            buffer_color="black", buffer_size=0.8, bold=True):
            label_settings = QgsPalLayerSettings()
            label_settings.fieldName = expr
            label_settings.isExpression = True
            label_settings.enabled = True

            text_format = QgsTextFormat()
            font = QFont("Arial", int(font_size))
            font.setBold(bool(bold))
            text_format.setFont(font)
            text_format.setSize(float(font_size))
            text_format.setColor(QColor(color))

            buffer_settings = QgsTextBufferSettings()
            buffer_settings.setEnabled(True)
            buffer_settings.setSize(float(buffer_size))
            buffer_settings.setColor(QColor(buffer_color))
            text_format.setBuffer(buffer_settings)

            label_settings.setFormat(text_format)

            labeling = QgsVectorLayerSimpleLabeling(label_settings)
            layer.setLabeling(labeling)
            layer.setLabelsEnabled(True)

        # -----------------------------
        # 2) VÉRTICE: simbologia + rotulação
        # -----------------------------
        symbol_vert = QgsMarkerSymbol.createSimple({
            'name': 'circle',
            'color': '255,0,0',
            'outline_color': '0,0,0',
            'outline_style': 'solid',
            'size': '2',
            'size_unit': 'MM'
        })
        camada_vertice.setRenderer(QgsSingleSymbolRenderer(symbol_vert))
        _apply_labeling(
            camada_vertice,
            expr='"vertice"',
            font_size=9,
            color="white",
            buffer_color="black",
            buffer_size=0.9,
            bold=True
        )
        camada_vertice.triggerRepaint()

        # -----------------------------
        # 3) LIMITE: categorizado por (confrontan-tipo) + rótulo com \n
        # -----------------------------
        expr_cat = "\"confrontan\" || '-' || \"tipo\""
        expr = QgsExpression(expr_cat)

        ctx = QgsExpressionContext()
        ctx.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(camada_limite))

        values = set()
        for f in camada_limite.getFeatures():
            ctx.setFeature(f)
            v = expr.evaluate(ctx)
            values.add("" if v is None else str(v))

        categories = []
        for v in sorted(values):
            sym = QgsLineSymbol.createSimple({'width': '0.6', 'width_unit': 'MM'})
            # cor determinística por hash
            h = abs(hash(v)) % 360
            sym.setColor(QColor.fromHsv(h, 200, 230))
            categories.append(QgsRendererCategory(v, sym, v))

        camada_limite.setRenderer(QgsCategorizedSymbolRenderer(expr_cat, categories))

        _apply_labeling(
            camada_limite,
            expr="\"confrontan\" || '\\n' || \"tipo\"",
            font_size=9,
            color="white",
            buffer_color="black",
            buffer_size=0.9,
            bold=True
        )
        camada_limite.triggerRepaint()

        # -----------------------------
        # 4) PARCELA: rosa claro 50% + rotulação com "nome"
        # -----------------------------
        sym_parc = QgsFillSymbol.createSimple({
            'color': '255,182,193,128',      # rosa claro com alpha
            'outline_color': '255,105,180,200',
            'outline_width': '0.6',
            'outline_width_unit': 'MM'
        })
        sym_parc.setOpacity(0.5)

        camada_parcela.setRenderer(QgsSingleSymbolRenderer(sym_parc))
        _apply_labeling(
            camada_parcela,
            expr="\"nome\"",
            font_size=10,
            color="black",
            buffer_color="white",
            buffer_size=1.0,
            bold=True
        )
        camada_parcela.triggerRepaint()

        return {}
        
       # except:
        #    feedback.pushWarning("Não foi possível aplicar estilos na camada!")
            # return {}
