# -*- coding: utf-8 -*-
"""
/***************************************************************************
 FragScape
                                 A QGIS plugin
 This plugin computes mesh effective size
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                             -------------------
        begin                : 2018-11-05
        git sha              : $Format:%H$
        copyright            : (C) 2018 by Mathieu Chailloux
        email                : mathieu@chailloux.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
"""

import os.path
import time

from qgis.core import (Qgis,
                       QgsMapLayerProxyModel,
                       QgsWkbTypes)
import processing
from processing import QgsProcessingUtils

from ..qgis_lib_mc import utils, abstract_model, qgsUtils, qgsTreatments, feedbacks
from ..algs import FragScape_algs
from . import params, landuse


class FragmItem(abstract_model.DictItem):

    INPUT = FragScape_algs.PrepareFragmentationAlgorithm.INPUT
    SELECT_EXPR = FragScape_algs.PrepareFragmentationAlgorithm.SELECT_EXPR
    BUFFER = FragScape_algs.PrepareFragmentationAlgorithm.BUFFER
    NAME = "NAME"
    FRAGM = "FRAGM"

    FIELDS = [INPUT,SELECT_EXPR,BUFFER,NAME,FRAGM]


    def __init__(self,dict):
        if self.FRAGM not in dict:
            dict[self.FRAGM] = True
        super().__init__(dict,fields=self.FIELDS)
        self.selectionLayer = None
        self.bufferLayer = None
        
    def equals(self,other):
        return (self.dict[self.NAME] == other.dict[self.NAME])
        
    def getOutputVLayer(self):
        name = self.dict[self.NAME]
        self.outputLayer = params.mkTmpLayerPath(name + ".gpkg")
        return self.outputLayer
        
    def getOutputRLayer(self):
        name = self.dict[self.NAME]
        self.outputLayer = params.mkTmpLayerPath(name + ".tif")
        return self.outputLayer
        
    def getSelectionLayer(self):
        if not self.selectionLayer:
            name = self.dict[self.NAME]
            self.selectionLayer = params.mkTmpLayerPath(name + ".gpkg")
        return self.selectionLayer
       
    def getBufferLayer(self):
        if not self.bufferLayer:
            name = self.dict[self.NAME]
            self.bufferLayer = params.mkTmpLayerPath(name + "_buf.gpkg")
        return self.bufferLayer
        
        
class FragmModel(abstract_model.DictModel):

    PREPARE_INPUT = FragmItem.INPUT
    PREPARE_SELECT_EXPR = FragmItem.SELECT_EXPR
    PREPARE_BUFFER = FragmItem.BUFFER
    PREPARE_NAME = FragmItem.NAME
    PREPARE_OUTPUT = FragScape_algs.PrepareFragmentationAlgorithm.OUTPUT
    
    FIELDS = FragmItem.FIELDS
    
    APPLY_LANDUSE = FragScape_algs.ApplyFragmentationAlgorithm.LANDUSE
    APPLY_FRAGMENTATION = FragScape_algs.ApplyFragmentationAlgorithm.FRAGMENTATION
    APPLY_CRS = FragScape_algs.ApplyFragmentationAlgorithm.CRS
    APPLY_OUTPUT = FragScape_algs.ApplyFragmentationAlgorithm.OUTPUT
    
    def __init__(self,fsModel):
        self.parser_name = "FragmModel"
        self.fsModel = fsModel
        super().__init__(self,self.FIELDS)
        
    def mkItemFromDict(self,dict):
        if "in_layer" in dict:
            new_dict = { FragmItem.INPUT : dict["in_layer"],
                         FragmItem.SELECT_EXPR : dict["expr"],
                         FragmItem.BUFFER : dict["buffer"],
                         FragmItem.NAME : dict["name"],
                         FragmItem.FRAGM : True
                         }
            return FragmItem(new_dict)
        else:
            if FragmItem.FRAGM in dict:
                dict[FragmItem.FRAGM]  = dict[FragmItem.FRAGM] == "True"
                utils.info("HEHO " + str(dict[FragmItem.FRAGM]))
            else:
                dict[FragmItem.FRAGM]  = True
            return FragmItem(dict)
        
    def getSingleGeomLayer(self):
        return self.fsModel.mkOutputFile("landuseFragmSingleGeom.gpkg")
    def getSingleGeomLayerTmp(self):
        return QgsProcessingUtils.generateTempFilename("landuseFragmSingleGeom_tmp.gpkg")
        
    def getMergedLayer(self):
        return self.fsModel.mkOutputFile("landuseFragm.tif")
    def getmergeLayerTmp(self):
        return QgsProcessingUtils.generateTempFilename("landuseFragm_tmp.tif")
            
    def getFinalLayers(self):
        extentLayer = self.fsModel.paramsModel.getExtentLayer()
        if self.fsModel.modeIsVector():
            final_path = self.getSingleGeomLayer()
            if extentLayer:
                tmp_path = self.getSingleGeomLayerTmp()
            else:
                tmp_path = final_path
        else:
            final_path = self.getMergedLayer()
            if extentLayer:
                tmp_path = self.getmergeLayerTmp()
            else:
                tmp_path = final_path
        return (tmp_path, final_path)
        
    def getFinalLayer(self):
        layers = self.getFinalLayers()
        return layers[1]
            
    def prepareItem(self,item,context,feedback):
        self.fsModel.checkWorkspaceInit()
        # self.fsModel.checkExtentInit()
        input_rel = item.dict[FragmItem.INPUT]
        feedback.pushDebugInfo("input_rel = " + str(input_rel))
        input = self.fsModel.getOrigPath(input_rel)
        feedback.pushDebugInfo("input = " + str(input))
        input_layer, input_type = qgsUtils.loadLayerGetType(input)
        input_vector = input_type == 'Vector'
        select_expr = item.dict[FragmItem.SELECT_EXPR]
        buffer_expr = item.dict[FragmItem.BUFFER]
        name = item.dict[FragmItem.NAME]
        is_fragm = item.dict[FragmItem.FRAGM]
        burn_val = 0 if is_fragm else 1
        vector_mode = self.fsModel.modeIsVector()
        outVPath = item.getOutputVLayer()
        feedback.pushDebugInfo("outVPath = " + str(outVPath))
        outRPath = item.getOutputRLayer()
        feedback.pushDebugInfo("outRPath = " + str(outRPath))
        # Processing
        step_feedback = feedbacks.ProgressMultiStepFeedback(3,feedback)
        clipped = self.fsModel.paramsModel.clipByExtent(input,
            name=name,clip_raster=False,context=context,feedback=feedback)
        # clipped = input
        step_feedback.pushDebugInfo("clipped = " + str(clipped))
        step_feedback.setCurrentStep(1)
        if input_vector:
            parameters = { self.PREPARE_INPUT : clipped,
                           self.PREPARE_SELECT_EXPR : select_expr,
                           self.PREPARE_BUFFER : buffer_expr,
                           self.PREPARE_NAME : name,
                           self.PREPARE_OUTPUT : outVPath }
            prepared = qgsTreatments.applyProcessingAlg(
                "FragScape","prepareFragm",parameters,
                context=context,feedback=step_feedback)
            step_feedback.setCurrentStep(2)
            if vector_mode:
                clipped_layer = qgsUtils.loadVectorLayer(clipped)
                if not buffer_expr and clipped_layer.geometryType() != QgsWkbTypes.PolygonGeometry:
                    utils.user_error("Empty buffer with non-polygon layer " + str(clipped))
                res = prepared
            else:
                self.fsModel.checkResolutionInit()
                self.fsModel.checkExtentInit()
                crs, extent, resolution = self.fsModel.getRasterParams()
                burn_val = 0 if is_fragm else 1
                res = FragScape_algs.applyRasterizationFixAllTouch(prepared,outRPath,
                    extent,resolution,out_type=Qgis.Byte,nodata_val=255,burn_val=burn_val,
                    all_touch=True,context=context,feedback=step_feedback)
        elif vector_mode:
            utils.internal_error("Not implemented yet : Raster to Vector")
        else:
            self.fsModel.checkResolutionInit()
            landuseLayer = self.fsModel.landuseModel.getOutputLayer()
            calc_path = QgsProcessingUtils.generateTempFilename(name + '_calc.tif')
            # normalized = self.fsModel.paramsModel.normalizeRaster(input,
                # out_path=normalized_path,context=context,feedback=feedback)
            expr = '(A / A) * ' + str(burn_val)
            calc = qgsTreatments.applyRasterCalc(clipped,calc_path,expr,
                out_type=Qgis.Byte,nodata_val=255,context=context,feedback=step_feedback)
            step_feedback.setCurrentStep(2)
            crs, extent, resolution = self.fsModel.getRasterParams()
            res = qgsTreatments.applyWarpReproject(calc_path,outRPath,
                dst_crs=crs,extent=extent,extent_crs=crs,
                resolution=resolution,nodata_val=255,
                context=context,feedback=step_feedback)
        step_feedback.setCurrentStep(3)
        return res
            
    def applyItemsWithContext(self,context,feedback,indexes=None):
        fragmMsg = "Additional data"
        feedbacks.beginSection(fragmMsg)
        # Out layer
        vector_mode = self.fsModel.modeIsVector()
        res_path = self.getFinalLayer()
        feedback.pushDebugInfo("res_path = " + str(res_path))
        if vector_mode:
            qgsUtils.removeVectorLayer(res_path)
        else:
            qgsUtils.removeRaster(res_path)
        # Prepare
        prepared_layers = []
        nb_items = len(self.items)
        nb_steps = nb_items + 1
        if vector_mode:
            nb_steps = nb_steps * 2
        curr_step = 0
        step_feedback = feedbacks.ProgressMultiStepFeedback(nb_steps,feedback)
        reversed_items = [i for i in reversed(self.items)]
        for item in reversed_items:
            step_feedback.pushDebugInfo("item = " + str(item))
            prepared = self.prepareItem(item,context,step_feedback)
            curr_step += 1
            step_feedback.setCurrentStep(curr_step)
            prepared_layers.append(prepared)
            item_fragm = item.dict[FragmItem.FRAGM]
        step_feedback.pushDebugInfo("prepared_layers = " + str(prepared_layers))
        # MERGE
        landuseLayer = self.fsModel.landuseModel.getOutputLayer()
        if not utils.fileExists(landuseLayer):
            utils.user_error(self.tr("File " + str(landuseLayer)
                + " does not exist, please launch step 2 before"))
        if vector_mode:
            crs = self.fsModel.paramsModel.crs
            curr_layer = landuseLayer
            for cpt, prepared_layer in enumerate(prepared_layers):
                item = reversed_items[cpt]
                item_fragm = item.dict[FragmItem.FRAGM]
                item_name = item.dict[FragmItem.NAME]
                step_feedback.pushDebugInfo("item name = " + str(item_name))
                tmp_path = params.mkTmpLayerPath("fragm_" + item_name + ".gpkg")
                if item_fragm:
                    curr_layer = qgsTreatments.applyDifference(
                        curr_layer,prepared_layer,tmp_path,
                        context=context,feedback=step_feedback)
                else:
                    curr_layer = qgsTreatments.mergeVectorLayers(
                        [curr_layer,prepared_layer],crs,tmp_path,
                        context=context,feedback=step_feedback)
                curr_step += 1
                step_feedback.setCurrentStep(curr_step)
            # clipped = self.fsModel.paramsModel.clipByExtent(curr_layer,
                # name='fragm',clip_raster=False,context=context,feedback=feedback)
            # res = clipped
            dissolved_path = params.mkTmpLayerPath("fragm_dissolved.gpkg")
            dissolved = qgsTreatments.dissolveLayer(curr_layer,dissolved_path,
                context=context,feedback=step_feedback)
            res = qgsTreatments.multiToSingleGeom(dissolved,res_path,
                context=context,feedback=step_feedback)
                
            # parameters = { self.APPLY_LANDUSE : landuseLayer,
                           # self.APPLY_FRAGMENTATION : prepared_layers,
                           # self.APPLY_CRS : crs,
                           # self.APPLY_OUTPUT : res_path }
            # res = qgsTreatments.applyProcessingAlg(
                # "FragScape","applyFragm",parameters,
                # context=context,feedback=step_feedback)
        else:
            prepared_layers.insert(0,landuseLayer)
            res = qgsTreatments.applyMergeRaster(prepared_layers,res_path,
                nodata_val=255,out_type=Qgis.Byte,nodata_input=255,
                context=context,feedback=step_feedback)
        step_feedback.setCurrentStep(nb_steps)
        # res = self.fsModel.paramsModel.clipByExtent(tmp_path,
            # out_path=res_path,context=context,feedback=step_feedback)
        qgsUtils.loadLayer(res,loadProject=True)
        feedbacks.endSection()
        return res
            
        
        
class FragmConnector(abstract_model.AbstractConnector):

    def __init__(self,dlg,fragmModel):
        self.parser_name = "FragmConnector"
        self.dlg = dlg
        self.onlySelection = False
        # self.fragmFlag = True
        self.fragmStatus = None
        self.clip_layer = None
        super().__init__(fragmModel,self.dlg.fragmView,
                        self.dlg.fragmAdd,self.dlg.fragmRemove,
                        self.dlg.fragmRun)

    def initGui(self):
        self.dlg.fragmInputLayerCombo.setFilters(QgsMapLayerProxyModel.VectorLayer)
        
    def connectComponents(self):
        super().connectComponents()
        #self.dlg.fragmInLayer.fileChanged.connect(self.setInLayer)
        self.dlg.fragmInputLayerCombo.layerChanged.connect(self.setInLayerFromCombo)
        self.layerComboDlg = qgsUtils.LayerComboDialog(self.dlg,
                                                       self.dlg.fragmInputLayerCombo,
                                                       self.dlg.fragmInputLayer)
        self.dlg.fragmStatus.currentIndexChanged.connect(self.switchFragmStatus)
        self.dlg.selectionUp.clicked.connect(self.upgradeItem)
        self.dlg.selectionDown.clicked.connect(self.downgradeItem)
        
    def applyItems(self):
        self.dlg.resultsInputLayer.setLayer(None)
        super().applyItems()
        tmp_path, res_path = self.model.getFinalLayers()
        res_layer = qgsUtils.loadLayer(res_path)
        self.dlg.resultsInputLayer.setLayer(res_layer)
        
    def setInLayerFromCombo(self,layer):
        self.dlg.fragmExpr.setLayer(layer)
        self.dlg.fragmBuffer.setLayer(layer)
        
    def switchFragmStatus(self,index):
        if index == 0:
            self.fragmStatus = None
        elif index == 1:
            self.fragmStatus = True
        elif index == 2:
            self.fragmStatus = False
        else:
            utils.debug("Unexpected fragmentation status : " + str(index))
        
    def mkItem(self):
        in_layer = self.dlg.fragmInputLayerCombo.currentLayer()
        if not in_layer:
            utils.user_error("No layer selected")
        in_layer_path = self.model.fsModel.normalizePath(qgsUtils.pathOfLayer(in_layer))
        expr = self.dlg.fragmExpr.expression()
        buffer = self.dlg.fragmBuffer.expression()
        #if not buffer:
        #    utils.user_error("Empty buffer")
        name = self.dlg.fragmName.text()
        # is_fragm = self.fragmFlag
        is_fragm = self.fragmStatus
        if not name:
            utils.user_error("Empty name (no identifier specified)")
        if is_fragm is None:
            utils.user_error("No fragmentation status selected")
        dict = { FragmItem.INPUT : in_layer_path,
                 FragmItem.SELECT_EXPR : expr,
                 FragmItem.BUFFER : buffer,
                 FragmItem.NAME : name,
                 FragmItem.FRAGM : is_fragm}
        item = FragmItem(dict)
        return item
        
        
