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

"""
***************************************************************************
    MorpheoAlgorithm.py
    ---------------------
    Date                 : August 2016
    Copyright            : (C) 2016 3Liz
    Email                : rldhont at 3liz dot com
***************************************************************************
*                                                                         *
*   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.                                   *
*                                                                         *
***************************************************************************
"""
from __future__ import print_function

import logging
from builtins import str
from .userFolder import getTempDirInTempFolder

__author__ = 'René-Luc DHONT'
__date__ = 'August 2016'
__copyright__ = '(C) 2016, 3Liz'

# This will get replaced with a git SHA1 when you do a git archive

__revision__ = '$Format:%H$'

import os
import time
import sys

from qgis.core import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from qgis.PyQt.QtXml import QDomDocument
from qgis.gui import QgsGui

#TODO: remettre les ..(core) partout et faire en sorte que celà fonctionne quand même avec unittest 
#from plugins.processing.core.ProcessingConfig import ProcessingConfig
#from plugins.processing.tools import dataobjects


from math import pi
from ..core.errors import BuilderError
from ..core.graph_builder import SpatialiteBuilder
from ..core.structdiff import structural_diff
from ..core import horizon as hrz
from ..core.ways import read_ways_graph
from ..core.sql import connect_database
from ..core.layers import export_as_geopakage
from ..core import mesh

Builder = SpatialiteBuilder


def log_info(info):
    # fix_print_with_import
    print("info: ", info)
    
    # the following line replaces the old ProcessingLog. Log DB path can be found using QgsGui.historyProviderRegistry().userHistoryDbPath()
    QgsGui.historyProviderRegistry().addEntry('ALGORITHM', {'INFO': info, 'algorithm_id': 'morpheo'})
    


def log_error(error):
    # fix_print_with_import
    print("error: ", error)

    #the following line replaces the old ProcessingLog. Log DB path can be found using QgsGui.historyProviderRegistry().userHistoryDbPath()
    QgsGui.historyProviderRegistry().addEntry('ALGORITHM', {'ERROR': error, 'algorithm_id': 'morpheo'})
    


def add_vector_layer(dbname, table_name, layer_name, clause=''):
    # Build URI
    uri = QgsDataSourceUri()
    uri.setDatabase(dbname)
    uri.setDataSource('', table_name, 'GEOMETRY', clause, '')
    # Find already loaded layer
    layersByName = QgsProject.instance().mapLayersByName(layer_name)
    if layersByName:
        vlayer = layersByName[0]
        XMLDocument = QDomDocument("style")
        XMLMapLayers = XMLDocument.createElement("maplayers")
        XMLMapLayer = XMLDocument.createElement("maplayer")
        vlayer.writeLayerXml(XMLMapLayer,XMLDocument, QgsReadWriteContext())
        XMLMapLayer.firstChildElement("datasource").firstChild().setNodeValue(uri.uri())
        XMLMapLayers.appendChild(XMLMapLayer)
        XMLDocument.appendChild(XMLMapLayers)
        vlayer.readLayerXml(XMLMapLayer, QgsReadWriteContext())
        vlayer.reload()
    else:
        vlayer = QgsVectorLayer(uri.uri(), layer_name, 'spatialite')
        QgsProject.instance().addMapLayer(vlayer)


def remove_vector_layer( layer_name ):
    layers = QgsProject.instance().mapLayersByName(layer_name)
    if layers:
        QgsProject.instance().removeMapLayer( layers[0].id() )


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


class MorpheoBuildAlgorithm(QgsProcessingAlgorithm):

    INPUT_LAYER = 'INPUT_LAYER'
    DIRECTORY = 'DIRECTORY'
    DBNAME = 'DBNAME'

    COMPUTE = 'COMPUTE'

    # Options controlling graph
    SNAP_DISTANCE = 'SNAP_DISTANCE'
    MIN_EDGE_LENGTH = 'MIN_EDGE_LENGTH'

    # Options controlling places
    BUFFER = 'BUFFER'
    INPUT_PLACES = 'INPUT_PLACES'

    # Options controlling ways
    WAY_ATTRIBUTE = 'WAY_ATTRIBUTE'
    THRESHOLD = 'THRESHOLD'
    RTOPO = 'RTOPO'
    ATTRIBUTES = 'ATTRIBUTES'
    ORTHOGONALITY = 'ORTHOGONALITY'
    BETWEENNESS = 'BETWEENNESS'
    CLOSENESS = 'CLOSENESS'
    STRESS = 'STRESS'
    CLASSES = 'CLASSES'

    # Ouput
    OUTPUT_DBPATH = 'OUTPUT_DBPATH'

    def __init__(self):
        QgsProcessingAlgorithm.__init__(self)
        # fix_print_with_import
        print("loading morpheo build algo at ", time.strftime("%H:%M:%S"))

    def getIcon(self):
        return QIcon(os.path.join(os.path.dirname(__file__),'..','morpheo.png'))

    def name(self):
        return 'build'

    def displayName(self):
        return tr(self.name())

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

    def groupId(self):
        return 'morpheo'
    
    def createInstance(self):
        return MorpheoBuildAlgorithm()

    def commandLineName(self):
        return 'morpheo:build'

    def initAlgorithm(self, config):
#        self.name = 'Build all : graph, places and ways'
#        self.group = 'Build'

        self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT_LAYER, 'Input layer',
                          [QgsProcessing.TypeVectorLine]))

        self.addParameter(QgsProcessingParameterFolderDestination(self.DIRECTORY, 'Output directory to store database and data'))

        self.addParameter(QgsProcessingParameterString(self.DBNAME, 'Database and data directory name', optional=True))

        # Options controlling graph
        self.addParameter(
            QgsProcessingParameterNumber(self.SNAP_DISTANCE, 'Snap distance (no cleanup if zero)', QgsProcessingParameterNumber.Type.Double, 0.2, False, 0., 99.))
        self.addParameter(
            QgsProcessingParameterNumber(self.MIN_EDGE_LENGTH, 'Min edge length', QgsProcessingParameterNumber.Type.Double, 4., False, 0., 99.))

        # Options controlling places
        self.addParameter(
            QgsProcessingParameterNumber(self.BUFFER, 'Place Buffer size', QgsProcessingParameterNumber.Type.Double, 4., False, 0., 99.99))
        self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT_PLACES, 'Default input polygons for places',
                          [QgsProcessing.TypeVectorPolygon], optional=True))

        # Options controlling ways
        self.addParameter(QgsProcessingParameterField(self.WAY_ATTRIBUTE,
            'Attribute for building street ways', '', self.INPUT_LAYER, QgsProcessingParameterField.DataType.String, False, True))
        self.addParameter(
            QgsProcessingParameterNumber(self.THRESHOLD, 'Threshold angle (in degree)', QgsProcessingParameterNumber.Type.Double, 60., False, 0., 180.0))
        self.addParameter(QgsProcessingParameterBoolean(self.RTOPO, 'Compute topological radius', False))
        self.addParameter(QgsProcessingParameterBoolean(self.ATTRIBUTES, 'Compute attributes', False))
        self.addParameter(QgsProcessingParameterBoolean(self.ORTHOGONALITY, 'Compute orthogonality (require attributes)', False))
        self.addParameter(QgsProcessingParameterBoolean(self.BETWEENNESS, 'Compute betweenness centrality (require attributes)', False))
        self.addParameter(QgsProcessingParameterBoolean(self.CLOSENESS, 'Compute closeness centrality (require attributes)', False))
        self.addParameter(QgsProcessingParameterBoolean(self.STRESS, 'Compute stress centrality (require attributes)', False))
        self.addParameter(
            QgsProcessingParameterNumber(self.CLASSES, 'Number of classes', QgsProcessingParameterNumber.Type.Integer, 10, False, 2, 10))

        outputDBPath = QgsProcessingOutputString(self.OUTPUT_DBPATH, 'Database path')
        outputDBPath.hidden = True
        self.addOutput(outputDBPath)


class MorpheoWayAttributesAlgorithm(QgsProcessingAlgorithm):

    DBPATH = 'DBPATH'

    # Options controlling ways
    RTOPO = 'RTOPO'
    ORTHOGONALITY = 'ORTHOGONALITY'
    BETWEENNESS = 'BETWEENNESS'
    CLOSENESS = 'CLOSENESS'
    STRESS = 'STRESS'
    CLASSES = 'CLASSES'

    # Ouput
    OUTPUT_DBPATH = 'OUTPUT_DBPATH'

    def __init__(self):
        QgsProcessingAlgorithm.__init__(self)
        # fix_print_with_import
        print("loading morpheo way_attributes algo at ", time.strftime("%H:%M:%S"))

    def getIcon(self):
        return QIcon(os.path.join(os.path.dirname(__file__), '..', 'morpheo.png'))

    def name(self):
        return 'way_attributes'

    def displayName(self):
        return tr(self.name())

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

    def groupId(self):
        return 'morpheo'
    
    def createInstance(self):
        return MorpheoWayAttributesAlgorithm()

    def commandLineName(self):
        return 'morpheo:way_attributes'

    def initAlgorithm(self, config):
#        self.name = 'Compute attributes on ways'
#        self.group = 'Compute'

        self.addParameter(QgsProcessingParameterFile(self.DBPATH, 'Morpheo database path', extension='sqlite'))

        # Options controlling ways
        self.addParameter(QgsProcessingParameterBoolean(self.RTOPO, 'Compute topological radius', False))
        self.addParameter(QgsProcessingParameterBoolean(self.ORTHOGONALITY, 'Compute orthogonality', False))
        self.addParameter(QgsProcessingParameterBoolean(self.BETWEENNESS, 'Compute betweenness centrality', False))
        self.addParameter(QgsProcessingParameterBoolean(self.CLOSENESS, 'Compute closeness centrality', False))
        self.addParameter(QgsProcessingParameterBoolean(self.STRESS, 'Compute stress centrality', False))
        self.addParameter(QgsProcessingParameterNumber(self.CLASSES, 'Number of classes', QgsProcessingParameterNumber.Type.Integer, 10, False, 2, 99))

        outputDBPath = QgsProcessingOutputString(self.OUTPUT_DBPATH, 'Database path')
        outputDBPath.hidden = True
        self.addOutput(outputDBPath)

    def processAlgorithm(self, progress, is_test_case: bool = False, config: dict[str, any] = None, context: QgsProcessingContext = None) -> dict[str, str]:
        """ Compute way attributes
        """

        dbpath = self.parameterAsString(config, name=self.DBPATH, context=context) if is_test_case else self.getParameterValue(self.DBPATH)
        if not os.path.isfile( dbpath ):
            log_error('Morpheo database path not found')

        output = os.path.dirname(dbpath)
        dbname = os.path.basename(dbpath).replace('.sqlite', '')

        builder = Builder.from_database(os.path.join(output, dbname))
        builder.compute_way_attributes(
                orthogonality= self.parameterAsBool(config, self.ORTHOGONALITY, context) if is_test_case else self.getParameterValue(self.ORTHOGONALITY),
                betweenness=self.parameterAsBool(config, self.BETWEENNESS, context) if is_test_case else  self.getParameterValue(self.BETWEENNESS),
                closeness=self.parameterAsBool(config, self.CLOSENESS, context) if is_test_case else self.getParameterValue(self.CLOSENESS),
                stress=self.parameterAsBool(config, self.STRESS, context) if is_test_case else  self.getParameterValue(self.STRESS),
                rtopo=self.parameterAsBool(config, self.RTOPO, context) if is_test_case else  self.getParameterValue(self.RTOPO),
                classes=self.parameterAsInt(config, self.CLASSES, context) if is_test_case else self.getParameterValue(self.CLASSES),
                output=os.path.join(output, dbname))

        # Visualize data
        add_vector_layer(os.path.join(output, dbname)+'.sqlite', 'places', "%s_%s" % ('places',dbname))
        add_vector_layer(os.path.join(output, dbname)+'.sqlite', 'place_edges', "%s_%s" % ('place_edges',dbname))
        add_vector_layer(os.path.join(output, dbname)+'.sqlite', 'ways', "%s_%s" % ('ways',dbname))

        if not is_test_case:
            self.setOutputValue(self.OUTPUT_DBPATH, os.path.join(output, dbname)+'.sqlite')
        
        return {self.OUTPUT_DBPATH: os.path.join(output, dbname) + '.sqlite'}



class MorpheoEdgeAttributesAlgorithm(QgsProcessingAlgorithm):

    DBPATH = 'DBPATH'

    # Options controlling edges
    ORTHOGONALITY = 'ORTHOGONALITY'
    BETWEENNESS = 'BETWEENNESS'
    CLOSENESS = 'CLOSENESS'
    STRESS = 'STRESS'
    CLASSES = 'CLASSES'

    # Ouput
    OUTPUT_DBPATH = 'OUTPUT_DBPATH'

    def __init__(self):
        QgsProcessingAlgorithm.__init__(self)
        # fix_print_with_import
        print("loading morpheo edge_attributes algo at ", time.strftime("%H:%M:%S"))

    def getIcon(self):
        return QIcon(os.path.join(os.path.dirname(__file__),'..','morpheo.png'))

    def name(self):
        return 'edge_attributes'

    def displayName(self):
        return tr(self.name())

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

    def groupId(self):
        return 'morpheo'
    
    def createInstance(self):
        return MorpheoEdgeAttributesAlgorithm()

    def commandLineName(self):
        return 'morpheo:edge_attributes'

    def initAlgorithm(self, config):
#        self.name = 'Compute attributes on edges'
#        self.group = 'Compute'

        self.addParameter(QgsProcessingParameterFile(self.DBPATH, 'Morpheo database path',
                          extension='sqlite'))

        # Options controlling ways
        self.addParameter(QgsProcessingParameterBoolean(self.ORTHOGONALITY, 'Compute orthogonality', False))
        self.addParameter(QgsProcessingParameterBoolean(self.BETWEENNESS, 'Compute betweenness centrality', False))
        self.addParameter(QgsProcessingParameterBoolean(self.CLOSENESS, 'Compute closeness centrality', False))
        self.addParameter(QgsProcessingParameterBoolean(self.STRESS, 'Compute stress centrality', False))
        self.addParameter(
            QgsProcessingParameterNumber(self.CLASSES, 'Number of classes', QgsProcessingParameterNumber.Type.Integer, 10, False, 2, 99))

        outputDBPath = QgsProcessingOutputString(self.OUTPUT_DBPATH, 'Database path')
        outputDBPath.hidden = True
        self.addOutput(outputDBPath)

    def processAlgorithm(self, progress, is_test_case: bool = False, config: dict[str, any] = None, context: QgsProcessingContext = None) -> dict[str, str]:
        """ Compute way attributes
        """
        dbpath    = self.parameterAsString(config, name=self.DBPATH, context=context) if is_test_case else  self.getParameterValue(self.DBPATH)
        if not os.path.isfile( dbpath ):
            log_error('Morpheo database path not found')

        output    = os.path.dirname(dbpath)
        dbname    = os.path.basename(dbpath).replace('.sqlite','')

        builder = Builder.from_database( os.path.join(output, dbname) )
        builder.compute_edge_attributes( os.path.join(output, dbname),
                orthogonality= self.parameterAsBool(config, self.ORTHOGONALITY, context) if is_test_case else self.getParameterValue(self.ORTHOGONALITY),
                betweenness=self.parameterAsBool(config, self.BETWEENNESS, context) if is_test_case else  self.getParameterValue(self.BETWEENNESS),
                closeness=self.parameterAsBool(config, self.CLOSENESS, context) if is_test_case else self.getParameterValue(self.CLOSENESS),
                stress=self.parameterAsBool(config, self.STRESS, context) if is_test_case else  self.getParameterValue(self.STRESS),
                classes       = self.parameterAsInt(config, self.CLASSES, context) if is_test_case else self.getParameterValue(self.CLASSES),
                output        = os.path.join(output, dbname))

        # Visualize data
        add_vector_layer( os.path.join(output, dbname)+'.sqlite', 'place_edges', "%s_%s" % ('place_edges',dbname))

        if not is_test_case:
            self.setOutputValue(self.OUTPUT_DBPATH, os.path.join(output, dbname)+'.sqlite')
        return {self.OUTPUT_DBPATH: os.path.join(output, dbname) + '.sqlite'}

class MorpheoEdgesGraphAlgorithm(QgsProcessingAlgorithm):

    DBPATH = 'DBPATH'

    # Ouput
    OUTPUT_DBPATH = 'OUTPUT_DBPATH'

    def __init__(self):
        QgsProcessingAlgorithm.__init__(self)
        # fix_print_with_import
        print("loading morpheo edges_graph algo at ", time.strftime("%H:%M:%S"))

    def getIcon(self):
        return QIcon(os.path.join(os.path.dirname(__file__),'..','morpheo.png'))

    def name(self):
        return 'edges_graph'

    def displayName(self):
        return tr(self.name())

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

    def groupId(self):
        return 'morpheo'
    
    def createInstance(self):
        return MorpheoEdgesGraphAlgorithm()

    def commandLineName(self):
        return 'morpheo:edges_graph'

    def initAlgorithm(self, config: dict[str, any]):
#        self.name = 'Build edges graph'
#        self.group = 'Build'

        self.addParameter(QgsProcessingParameterFile(self.DBPATH, 'Morpheo database path',
                          extension='sqlite'))

        outputDBPath = QgsProcessingOutputString(self.OUTPUT_DBPATH, 'Database path')
        outputDBPath.hidden = True
        self.addOutput(outputDBPath)

    def processAlgorithm(self, progress, is_test_case: bool = False, config: dict[str, any] = None,context: QgsProcessingContext = None) -> dict[str, str]:
        """ Build edges graph
        """
        dbpath    = self.parameterAsString(config, self.DBPATH, context) if is_test_case else self.getParameterValue(self.DBPATH)
        if not os.path.isfile( dbpath ):
            log_error('Morpheo database path not found')

        output    = os.path.dirname(dbpath)
        dbname    = os.path.basename(dbpath).replace('.sqlite','')

        builder = Builder.from_database( os.path.join(output, dbname) )
        builder.build_edges_graph(os.path.join(output, dbname))

        if not is_test_case:
            self.setOutputValue(self.OUTPUT_DBPATH, os.path.join(output, dbname)+'.sqlite')

        return {self.OUTPUT_DBPATH, os.path.join(output, dbname) + '.sqlite'}

class MorpheoWaysGraphAlgorithm(QgsProcessingAlgorithm):

    DBPATH = 'DBPATH'

    # Ouput
    OUTPUT_DBPATH = 'OUTPUT_DBPATH'

    def __init__(self):
        QgsProcessingAlgorithm.__init__(self)
        # fix_print_with_import
        print("loading morpheo ways_graph algo at ", time.strftime("%H:%M:%S"))

    def getIcon(self):
        return QIcon(os.path.join(os.path.dirname(__file__),'..','morpheo.png'))

    def name(self):
        return 'ways_graph'

    def displayName(self):
        return tr(self.name())

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

    def groupId(self):
        return 'morpheo'
    
    def createInstance(self):
        return MorpheoWaysGraphAlgorithm()

    def commandLineName(self):
        return 'morpheo:ways_graph'

    def initAlgorithm(self, config):
#        self.name = 'Build ways graph'
#        self.group = 'Build'

        self.addParameter(QgsProcessingParameterFile(self.DBPATH, 'Morpheo database path',
                          extension='sqlite'))

        outputDBPath = QgsProcessingOutputString(self.OUTPUT_DBPATH, 'Database path')
        outputDBPath.hidden = True
        self.addOutput(outputDBPath)

    def processAlgorithm(self, progress, is_test_case: bool = False, config: dict[str, any] = None, context: QgsProcessingContext = None):
        """ Build edges graph
        """
        dbpath    = self.parameterAsString(config, self.DBPATH, context)  if is_test_case else self.getParameterValue(self.DBPATH)
        if not os.path.isfile( dbpath ):
            log_error('Morpheo database path not found')

        output    = os.path.dirname(dbpath)
        dbname    = os.path.basename(dbpath).replace('.sqlite','')

        builder = Builder.from_database( os.path.join(output, dbname) )
        builder.build_ways_graph(os.path.join(output, dbname))

        if not is_test_case:
            self.setOutputValue(self.OUTPUT_DBPATH, os.path.join(output, dbname)+'.sqlite')
        return {self.OUTPUT_DBPATH: os.path.join(output, dbname)+'.sqlite'}

class MorpheoStructuralDiffAlgorithm(QgsProcessingAlgorithm):

    DBPATH1 = 'DBPATH1'
    DBPATH2 = 'DBPATH2'

    DIRECTORY = 'DIRECTORY'
    DBNAME = 'DBNAME'

    TOLERANCE = 'TOLERANCE'

    # Ouput
    OUTPUT_DBPATH = 'OUTPUT_DBPATH'

    def __init__(self):
        QgsProcessingAlgorithm.__init__(self)
        # fix_print_with_import
        print("loading morpheo structural_diff algo at ", time.strftime("%H:%M:%S"))

    def getIcon(self):
        return QIcon(os.path.join(os.path.dirname(__file__),'..','morpheo.png'))

    def name(self):
        return 'structural_diff'

    def displayName(self):
        return tr(self.name())

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

    def groupId(self):
        return 'morpheo'
    
    def createInstance(self):
        return MorpheoStructuralDiffAlgorithm()

    def commandLineName(self):
        return 'morpheo:structural_diff'

    def initAlgorithm(self, config):
#        self.name = 'Compute structural difference'
#        self.group = 'Compute'

        self.addParameter(QgsProcessingParameterFile(self.DBPATH1, 'Initial Morpheo directory',
                          extension='sqlite'))

        self.addParameter(QgsProcessingParameterFile(self.DBPATH2, 'Final Morpheo directory',
                          extension='sqlite'))

        self.addParameter(QgsProcessingParameterFolderDestination(self.DIRECTORY, 'Output directory to store database and data'))

        self.addParameter(QgsProcessingParameterString(self.DBNAME, 'Database and data directory name',
                          optional=True))

        self.addParameter(
            QgsProcessingParameterNumber(self.TOLERANCE, 'Tolerance value', QgsProcessingParameterNumber.Type.Double, 1., False, 0., 99.99))

        outputDBPath = QgsProcessingOutputString(self.OUTPUT_DBPATH, 'Structural difference database path')
        outputDBPath.hidden = True
        self.addOutput(outputDBPath)

    def processAlgorithm(self, progress, is_test_case: bool = False, config: dict[str, any] = None, context: QgsProcessingContext = None):
        """ Compute structural difference
        """

        def check_dbpath(path):
            print(path)
            basename = os.path.basename(path)
            shp = os.path.join(path,'place_edges_%s.shp' % basename) if not is_test_case else os.path.join(path, 'place_edges_%s.gpkg' % basename)
            gpickle = os.path.join(path,'way_graph_%s.gpickle' % basename)
            return os.path.isfile(shp) and os.path.isfile(gpickle)

        dbpath1   =  self.parameterAsString(config, self.DBPATH1, context) if is_test_case else self.getParameterValue(self.DBPATH1)
        dirname1  = os.path.dirname(dbpath1)
        dbname1   = os.path.basename(dbpath1).replace('.sqlite','')
        if not check_dbpath(os.path.join(dirname1, dbname1)):
            log_error('Initial Morpheo directory is incomplete')

        dbpath2    = self.parameterAsString(config, self.DBPATH2, context) if is_test_case else self.getParameterValue(self.DBPATH2)
        dirname2  = os.path.dirname(dbpath2)
        dbname2   = os.path.basename(dbpath2).replace('.sqlite','')
        if not check_dbpath(os.path.join(dirname2, dbname2)):
            log_error('Final Morpheo directory is incomplete')

       
        output = self.parameterAsString(config, self.DIRECTORY, context)  if is_test_case else self.getParameterValue(self.DIRECTORY)
        output = output or getTempDirInTempFolder()
        
        dbname = self.parameterAsString(config, self.DBNAME, context) if is_test_case else self.getParameterValue(self.DBNAME) 
        dbname = dbname or 'morpheo_%s_%s' % (dbname1, dbname2)

        structural_diff( os.path.join(dirname1, dbname1), os.path.join(dirname2, dbname2),
                         output=os.path.join(output, dbname),
                         buffersize= self.parameterAsDouble(config, self.TOLERANCE, context) if is_test_case else self.getParameterValue(self.TOLERANCE)
                         )

        # Visualize data
        add_vector_layer( os.path.join(output, dbname)+'.sqlite', 'paired_edges', "%s_%s" % ('paired_edges',dbname))

        if not is_test_case:
            self.setOutputValue(self.OUTPUT_DBPATH, os.path.join(output, dbname)+'.sqlite')
        return {self.OUTPUT_DBPATH: os.path.join(output, dbname) + '.sqlite'}


class MorpheoMeshAlgorithm(QgsProcessingAlgorithm):

    DBPATH = 'DBPATH'
    WAY_LAYER = 'WAY_LAYER'
    WAY_ATTRIBUTE = 'WAY_ATTRIBUTE'
    PERCENTILE = 'PERCENTILE'
    USE_WAY = 'USE_WAY'

    def __init__(self):
        QgsProcessingAlgorithm.__init__(self)
        # fix_print_with_import
        print("loading morpheo mesh algo at ", time.strftime("%H:%M:%S"))

    def getIcon(self):
        return QIcon(os.path.join(os.path.dirname(__file__),'..','morpheo.png'))

    def name(self):
        return 'mesh'

    def displayName(self):
        return tr(self.name())

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

    def groupId(self):
        return 'morpheo'
    
    def createInstance(self):
        return MorpheoMeshAlgorithm()

    def commandLineName(self):
        return 'morpheo:mesh'

    def initAlgorithm(self, config):
#        self.name = 'Compute mesh'
#        self.group = 'Compute'

        self.addParameter(QgsProcessingParameterFile(self.DBPATH, 'Morpheo database path',
                          extension='sqlite'))

        self.addParameter(QgsProcessingParameterVectorLayer(self.WAY_LAYER, 'Ways layer',
                          [QgsProcessing.TypeVectorLine]))

        self.addParameter(QgsProcessingParameterField(self.WAY_ATTRIBUTE,
            'Attribute for mesh structure', '', self.WAY_LAYER, QgsProcessingParameterField.DataType.Any, False, True))

        self.addParameter(
            QgsProcessingParameterNumber(self.PERCENTILE, 'The percentile for computing the mesh structure', QgsProcessingParameterNumber.Type.Integer, 5, False, 1, 99))

        self.addParameter(QgsProcessingParameterBoolean(self.USE_WAY, 'Use ways for computing mesh components', False))

    def processAlgorithm(self, progress, is_test_case: bool = False, config: dict[str, any] = None, context: QgsProcessingContext = None):
        """ Compute mesh
        """
        dbpath    = self.parameterAsString(config, self.DBPATH, context) if is_test_case else self.getParameterValue(self.DBPATH)
        if not os.path.isfile( dbpath ):
            log_error('Morpheo database path not found')

        output    = os.path.dirname(dbpath)
        dbname    = os.path.basename(dbpath).replace('.sqlite','')

        attribute = self.parameterAsString(config, self.WAY_ATTRIBUTE, context) if is_test_case else self.getParameterValue(self.WAY_ATTRIBUTE)
        percentile = self.parameterAsInt(config, self.PERCENTILE, context) if is_test_case else self.getParameterValue(self.PERCENTILE)

        use_way = self.parameterAsBool(config, self.USE_WAY, context) if is_test_case else self.getParameterValue(self.USE_WAY)
        table = use_way and 'ways' or 'edges'

        conn = connect_database(dbpath)
        name = 'mesh_%s_%s_%s' % (table, attribute, percentile)
        ids = mesh.features_from_attribute(conn.cursor(), table, attribute, percentile)

        # Visualize data
        add_vector_layer( os.path.join(output, dbname)+'.sqlite', table, "%s_%s" % (name,dbname), 'OGC_FID IN ('+','.join(str(i) for i in ids)+')')


class MorpheoHorizonAlgorithm(QgsProcessingAlgorithm):

    DBPATH = 'DBPATH'
    WAY_LAYER = 'WAY_LAYER'
    WAY_ATTRIBUTE = 'WAY_ATTRIBUTE'
    PERCENTILE = 'PERCENTILE'

    #output param
    PLOT_BINS = 'PLOT_BINS'
    PLOT_COLOR = 'PLOT_COLOR'
    PLOT_WIDTH = 'PLOT_WIDTH'
    PLOT_HEIGHT = 'PLOT_HEIGHT'

    #output
    PLOT = 'PLOT'

    def __init__(self):
        QgsProcessingAlgorithm.__init__(self)
        # fix_print_with_import
        print("loading morpheo horizon algo at ", time.strftime("%H:%M:%S"))

    def getIcon(self):
        return QIcon(os.path.join(os.path.dirname(__file__),'..','morpheo.png'))

    def name(self):
        return 'horizon'

    def displayName(self):
        return tr(self.name())

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

    def groupId(self):
        return 'morpheo'
    
    def createInstance(self):
        return MorpheoHorizonAlgorithm()

    def commandLineName(self):
        return 'morpheo:horizon'

    def initAlgorithm(self, config):
#        self.name = 'Compute horizon'
#        self.group = 'Compute'

        self.addParameter(QgsProcessingParameterFile(self.DBPATH, 'Morpheo database path',
                          extension='sqlite'))

        self.addParameter(QgsProcessingParameterVectorLayer(self.WAY_LAYER, 'Ways layer',
                          [QgsProcessing.TypeVectorLine]))

        self.addParameter(QgsProcessingParameterField(self.WAY_ATTRIBUTE,
            'Attribute for building horizon', '', self.WAY_LAYER, QgsProcessingParameterField.DataType.Any, False, True))

        self.addParameter(
            QgsProcessingParameterNumber(self.PERCENTILE, 'Percentile of features', QgsProcessingParameterNumber.Type.Integer, 5, False, 1, 99))

    def processAlgorithm(self, progress, is_test_case: bool = False, config: dict[str, any] = None, context: QgsProcessingContext = None):
        """ Compute horizon
        """
        dbpath = self.parameterAsString(config, self.DBPATH, context) if is_test_case else self.getParameterValue(self.DBPATH)
        if not os.path.isfile(dbpath):
            log_error('Morpheo database path not found')

        output = os.path.dirname(dbpath)
        dbname = os.path.basename(dbpath).replace('.sqlite','')

        attribute = self.parameterAsString(config, self.WAY_ATTRIBUTE, context) if is_test_case else self.getParameterValue(self.WAY_ATTRIBUTE)        
        percentile =self.parameterAsInt(config, self.PERCENTILE, context) if is_test_case else  self.getParameterValue(self.PERCENTILE)

        conn = connect_database(dbpath)
        G = read_ways_graph(os.path.join(output, dbname))

        table = 'horizon_%s_%s' % (attribute, percentile)
        hrz.horizon_from_attribute(conn, G, table, attribute, percentile)

