# -*- 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
from PyQt5.QtCore import *
from qgis.core import (QgsProcessing,
                       QgsFeatureSink,
                       QgsProcessingException,
                       QgsProcessingAlgorithm,
                       QgsWkbTypes,
                       QgsCoordinateReferenceSystem,
                       QgsProcessingParameterFile,
                       QgsVectorLayer,
                       QgsFields,
                       QgsField,
                       QgsFeature,
                       QgsGeometry,
                       QgsLineString,
                       QgsMultiPolygon,
                       QgsPolygon,
                       QgsPoint,
                       QgsProcessingParameterFeatureSink)
from qgis import processing
from qgis.PyQt.QtGui import QIcon
from GeoINCRA.images.Imgs import *
from datetime import datetime
import os, re
import platform

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('Memorial do Sigef para Camadas')

    def group(self):

        return self.tr(self.groupId())

    def groupId(self):

        return ''

    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">
                      <img style="width: 100%; height: auto;" src="data:image/jpg;base64,'''+ INCRA_GeoOne +'''
                      </div>
                      <div align="right">
                      <p align="right">
                      <a href="https://geoone.com.br/pvgeoincra2/"><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/georreferenciamento-de-imveis-rurais-com-o-plugin-geoincra-1690158094835"><span style="font-weight: bold;">Acesse seu curso na GeoOne</span></a>
                      </p>
                      <a target="_blank" rel="noopener noreferrer" href="https://geoone.com.br/"><img height="80" 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 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))

        # Detectando o sistema operacional e instalando PyPDF2
        system_os = platform.system()
        if system_os == "Linux":
            import subprocess
            import sys
            try:
                from PyPDF2 import PdfReader
            except:
                feedback.pushInfo('PyPDF2 não está instalado. Tentando instalar "PyPDF2" utilizando "pip"...')
                try:
                    subprocess.check_call([sys.executable, "-m", "pip", "install", "PyPDF2"],
                                           stdout=subprocess.DEVNULL,
                                           stderr=subprocess.DEVNULL)
                    from PyPDF2 import PdfReader
                except subprocess.CalledProcessError as e:
                    feedback.reportError(f"Erro ao instalar o pacote pacote PyPDF2. Você pode tentar instalar manualmente via OSGeo4W Shell:\n"
                             f"python3 -m pip install PyPDF2")
                    raise QgsProcessingException(f"Falha ao instalar o pacote PyPDF2: {e}")
        else: # "Windows","Darwin" (MacOS)
            import pip
            try:
                from PyPDF2 import PdfReader
            except ImportError:
                feedback.pushInfo('PyPDF2 não está instalado. Tentando instalar "PyPDF2" utilizando "pip"...')
                try:
                    # Executa o pip usando subprocess
                    pip.main(["install","PyPDF2"])
                    from PyPDF2 import PdfReader
                except Exception as e:
                    feedback.reportError(f"Erro ao instalar o pacote pacote PyPDF2. Você pode tentar instalar manualmente via OSGeo4W Shell:\n"
                             f"pip install PyPDF2")
                    raise QgsProcessingException(f"Falha ao instalar o pacote PyPDF2: {e}")
        feedback.pushInfo('Biblioteca PyPDF2 importada com sucesso...')

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

        # Criar camada de Pontos
        feedback.pushInfo('Criando camada Vértice...')
        Fields1 = QgsFields()
        itens  = {   'indice': QVariant.Int,
                     'vertice': QVariant.String,
                     'tipo_verti': QVariant.String,
                     'metodo_pos' : QVariant.String,
                     'sigma_x' : QVariant.Double,
                     'sigma_y' : QVariant.Double,
                     'sigma_z' : QVariant.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': QVariant.String,
                     'confrontan': QVariant.String,
                     'cns': QVariant.String,
                     'matricula' : QVariant.String,
                     }
        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': QVariant.String,
                     'nat_serv': QVariant.Int,
                     'pessoa': QVariant.Int,
                     'cpf_cnpj' : QVariant.String,
                     'denominacao': QVariant.String,
                     'situacao': QVariant.Int,
                     'natureza': QVariant.Int,
                     'sncr': QVariant.String,
                     'matricula': QVariant.String,
                     'cod_cartorio': QVariant.String,
                     'municipio': QVariant.String,
                     'uf': QVariant.String,
                     'resp_tec': QVariant.String,
                     'reg_prof': QVariant.String,
                     'data': QVariant.Date,
                     }
        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))

        # Lendo arquivo PDF
        reader = PdfReader(pdf_path)
        text = ''
        # Iterar por cada página do PDF
        for page_num, page in enumerate(reader.pages):
            text += page.extract_text()

        dic = {
        'Denominação:': '',
        'Proprietário:': '',
        'Proprietário(a):': '',
        'Matrícula do imóvel:': '',
        'Transcrição do imóvel:': '',
        'Natureza da Área:': '',
        'CPF:': '',
        'CNJP:': '',
        'Município/UF:': '',
        'Código INCRA/SNCR:': '',
        'Responsável Técnico:': '',
        'Responsável Técnico(a):': '',
        'Formação:': '',
        'Conselho Profissional:': '',
        'Código de credenciamento:': '',
        'Documento de RT:': '',
        'Cartório (CNS):': '',
        'Área (Sistema Geodésico Local)': '',
        'Perímetro (m)': '',
        'Data Certificação:': '',
        'Data da Geração:': '',
        }

        chaves = list(dic.keys())

        lista_cod = []
        dic_cod = {}
        ind_encravado = []

        # Dividir o texto em linhas
        lines = text.splitlines()
        sentinela = False
        sentinela2 = False
        cont = 0
        pattern = r'\s*[A-Z0-9]{3,4}-[PMOV]-[A-Z0-9]{1,6}(?:,\s*[A-Z0-9]{3,4}-[PMOV]-[A-Z0-9]{1,6})*'

        for line in lines:

            if sentinela:
                dic[item] = line
                sentinela = False

            for item in dic:
                if item in line:
                    sentinela = True
                    break

            if cont == 8:
                cont = 0
                sentinela2 = False

            if bool(re.fullmatch(pattern, line)):
                if cont == 0:
                    codigo = line.strip()
                    lista_cod.append(codigo)
                    sentinela2 = True

            if 'Área encravada' in line:
                ind_encravado.append(len(lista_cod))

            elif sentinela2:
                cont += 1
                if cont == 1:
                    dic_cod[lista_cod[-1]] = {'lon':'', 'lat':'', 'h':'', 'cns':'', 'matr':'', 'confr': '', 'az': '', 'dist': '', 'texto_confr': ''}
                if cont == 2:
                    dic_cod[lista_cod[-1]]['lon'] = line.strip()
                if cont == 3:
                    dic_cod[lista_cod[-1]]['lat'] = line.strip()
                if cont == 4:
                    dic_cod[lista_cod[-1]]['h'] = line.strip()
                if cont == 6:
                    dic_cod[lista_cod[-1]]['az'] = line.strip()
                if cont == 7:
                    dic_cod[lista_cod[-1]]['dist'] = line.strip()
                if cont == 8:
                    try:
                        cns,mat,confr = line.strip().split('|')
                        cns = cns.split(':')[-1].strip()
                        matr = mat.strip().split()[-1]
                        confr = confr.strip()
                        dic_cod[lista_cod[-1]]['cns'] = cns
                        dic_cod[lista_cod[-1]]['matr'] = matr
                        dic_cod[lista_cod[-1]]['confr'] = confr
                        dic_cod[lista_cod[-1]]['texto_confr'] = line
                    except:
                        dic_cod[lista_cod[-1]]['confr'] = line.strip()
                        dic_cod[lista_cod[-1]]['texto_confr'] = line


        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)

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