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

"""
Drone_PhotosHistogramMatch.py
***************************************************************************
*                                                                         *
*   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__ = 'Leandro França'
__date__ = '2023-06-17'
__copyright__ = '(C) 2023, Leandro França'

from qgis.core import (Qgis,
                       QgsProcessingException,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFile,
                       QgsProcessingParameterFeatureSource,
                       QgsApplication
                       )

import numpy as np
from lftools.geocapt.imgs import Imgs
from lftools.translations.translate import translate
import os
from qgis.PyQt.QtGui import QIcon

class PhotosHistogramMatch(QgsProcessingAlgorithm):

    LOC = QgsApplication.locale()[:2]

    def tr(self, *string):
        return translate(string, self.LOC)

    def createInstance(self):
        return PhotosHistogramMatch()

    def name(self):
        return 'photoshistogrammatch'

    def displayName(self):
        return self.tr('Photos Histogram Matching', 'Casar histogramas de fotos')

    def group(self):
        return self.tr('Drones')

    def groupId(self):
        return 'drones'

    def tags(self):
        return 'GeoOne,drone,match,mosaic,casar,ajuste,casamento,ajustamento,equalização,equalization,adjustment,histograma,fotos'.split(',')

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

    txt_en = '''This tool performs histogram matching of the JPEG photo files of one input photo layer relative to another reference photo layer.'''
    txt_pt = '''Esta ferramenta realiza o casamento do histograma dos arquivos de fotos JPEG de uma camada de fotografias de entrada em relação a outra camada de fotografias de referência.'''
    figure = 'images/tutorial/drone_histogram.jpg'

    def shortHelpString(self):
        social_BW = Imgs().social_BW
        footer = '''<div align="center">
                      <img src="'''+ os.path.join(os.path.dirname(os.path.dirname(__file__)), self.figure) +'''">
                      </div>
                      <div align="right">
                      <p align="right">
                      <b>'''+self.tr('Author: Leandro Franca', 'Autor: Leandro França')+'''</b>
                      </p>'''+ social_BW + '''</div>
                    </div>'''
        return self.tr(self.txt_en, self.txt_pt) + footer

    INPUT = 'INPUT'
    REFERENCE = 'REFERENCE'
    FOLDER = 'FOLDER'

    def initAlgorithm(self, config=None):
        # INPUT
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.INPUT,
                self.tr('Input photo layer', 'Camada de fotos a serem ajustadas'),
                [Qgis.ProcessingSourceType.TypeVectorPoint]
            )
        )

        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.REFERENCE,
                self.tr('Layer of reference photos', 'Fotos de Referência'),
                [Qgis.ProcessingSourceType.TypeVectorPoint]
            )
        )

        # OUTPUT
        self.addParameter(
            QgsProcessingParameterFile(
                self.FOLDER,
                self.tr('Destination folder', 'Pasta de destino'),
                behavior = Qgis.ProcessingFileParameterBehavior.Folder
            )
        )

    def processAlgorithm(self, parameters, context, feedback):
        
        from lftools.dependencies import ensure_pillow
        Image = ensure_pillow(feedback)
        if Image is None:
            raise QgsProcessingException(
                "The Pillow library (PIL) is required for this tool and could not be loaded automatically."
            )
        
        # camada de fotos a serem ajustadas
        ajust = self.parameterAsSource(
            parameters,
            self.INPUT,
            context
        )
        if ajust is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))

        # camada de fotos de referência
        ref = self.parameterAsSource(
            parameters,
            self.REFERENCE,
            context
        )
        if ref is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.REFERENCE))

        # pasta de destino
        pasta = self.parameterAsFile(
            parameters,
            self.FOLDER,
            context
        )
        if not pasta:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.FOLDER))

        # distancia
        def distancia1(geom1, geom2):
            p1 = geom1.asPoint()
            x1,y1 =  p1.x(),  p1.y()
            p2 = geom2.asPoint()
            x2,y2 =  p2.x(),  p2.y()
            return np.sqrt((x1-x2)**2 + (y1-y2)**2)

        # Para cada imagem a ser ajustada encontrar a foto mais próxima
        feedback.pushInfo(self.tr('Histogram matching...', 'Casamento de histograma...'))
        Percent = 100.0/ajust.featureCount() if ajust.featureCount() else 0

        for current, feat1 in enumerate(ajust.getFeatures()):
            geom1 = feat1.geometry()
            caminho = feat1[self.tr('path','caminho')]
            nome = feat1[self.tr('name','nome')]
            dist = 1e10
            for feat2 in ref.getFeatures():
                geom2 = feat2.geometry()
                distancia = distancia1(geom1,geom2)
                if distancia < dist:
                    dist = distancia
                    caminho_ref = feat2[self.tr('path','caminho')]
                    nome_ref = feat2[self.tr('name','nome')]

            # Abrir imagem de referência
            img = Image.open(caminho_ref)
            rgb = np.asarray(img)
            img = None

            # Calcular média e desvio padrão por banda
            R = rgb[..., 0]
            G = rgb[..., 1]
            B = rgb[..., 2]
            meanR_ref, stdR_ref = R.mean(), R.std()
            meanG_ref, stdG_ref = G.mean(), G.std()
            meanB_ref, stdB_ref = B.mean(), B.std()

            del rgb

            # Abrir imagem a ser ajustada
            img = Image.open(caminho)
            exif = img.info['exif']
            rgb = np.asarray(img)
            R = rgb[..., 0]
            G = rgb[..., 1]
            B = rgb[..., 2]
            meanR, stdR = R.mean(), R.std()
            meanG, stdG = G.mean(), G.std()
            meanB, stdB = B.mean(), B.std()

            # Normalizar imagem a ser ajustada
            R_norm = (R - meanR)/stdR
            G_norm = (G - meanG)/stdG
            B_norm = (B - meanB)/stdB

            # Calibrar parâmetros de acordo com a imagem de referência
            R = np.round(R_norm*stdR_ref + meanR_ref)
            G = np.round(G_norm*stdG_ref + meanG_ref)
            B = np.round(B_norm*stdB_ref + meanB_ref)

            R = R*((R>0)*(R<256)) + 255*(R>255)
            G = G*((G>0)*(G<256)) + 255*(G>255)
            B = B*((B>0)*(B<256)) + 255*(B>255)

            RGB = np.dstack((R,G,B))

            # Salvar imagem ajustada
            nova_img = Image.fromarray(np.uint8(RGB))
            nova_img.save(os.path.join(pasta,nome), quality=90, subsampling=0, exif=exif)

            if feedback.isCanceled():
                break
            feedback.setProgress(int((current + 1) * Percent))


        feedback.pushInfo(self.tr('Operation completed successfully!', 'Operação finalizada com sucesso!'))
        feedback.pushInfo(self.tr('Leandro Franca - Cartographic Engineer', 'Leandro França - Eng Cart'))

        return {self.FOLDER: pasta}
