import re
from os import path
from urllib import parse
#from distutils.util import strtobool

from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QMessageBox

from qgis.core import (NULL,
                       Qgis,
                       #QgsSettings,
                       #QgsMapSettings,
                       #QgsSnappingUtils,
                       QgsCoordinateTransform,
                       QgsDataSourceUri,
                       QgsProject,
                       QgsGeometry,
                       QgsFeature,
                       QgsVectorLayerUtils,
                       QgsMapLayer,
                       QgsPoint,
                       QgsFeatureSource,
                       QgsVectorLayer,
                       QgsRasterLayer,
                       QgsVectorLayerEditUtils,
                       QgsCoordinateReferenceSystem,
                       QgsSpatialIndex,
                       QgsFieldConstraints,
                       QgsWkbTypes,
                       QgsProviderRegistry)

from qgis.utils import iface
from qgis.analysis import QgsGeometrySnapper

from qgis_agri import __QGIS_AGRI_LAYER_TAG__, agriConfig, tr
from qgis_agri.log.logger import QgisLogger as logger
from qgis_agri.gui.geometry_util import QGISAgriGeometry
from qgis_agri.gui.geometrysnapper import QGISAgriGeometrySnapper
from qgis_agri.qgis_agri_exceptions import QGISAgriException

#
#-----------------------------------------------------------
class QGISAgriLayers:
    """ Utiliy class for Agri layer manipulation. """

    __isLayerStyleUpdating = False

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def removeAllMapLayers():
        """Remove all map layers in project"""
        project = QgsProject.instance()
        project.removeAllMapLayers()

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def clear_toc(skipRasterLayer=False):
        """Remove layers in TOC"""
        # Get the project instance
        project = QgsProject.instance()
        # loop layers in TOC
        root_node = project.layerTreeRoot()
        for layer in project.mapLayers().values():
            # check if raster layer
            if skipRasterLayer and layer.type() == QgsMapLayer.RasterLayer:
                continue

            # find layer node in TOC
            node = root_node.findLayer( layer )
            if node is None:
                continue

            # remove layer\node in TOC
            grp_node = node.parent()
            grp_node.removeLayer( layer )

        # remove empty group nodes
        root_node.removeChildrenGroupWithoutLayers()

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def extract_geometry_vertices(geom):
        n = 0
        ver = geom.vertexAt(0)
        points = []

        # extract nodes
        while(ver != QgsPoint(0,0)):
            n +=1
            points.append(ver)
            ver=geom.vertexAt(n)

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def set_current_layer(layer):
        curlayer = iface.activeLayer()
        if curlayer == layer:
            return
        view = iface.layerTreeView()
        view.setCurrentLayer( None )
        view.setCurrentLayer( layer )

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def read_only_layers(vlay_list, read_only=True):
        # loop layers
        for vlayer in vlay_list:
            vlayer.setReadOnly(read_only)

    """
    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def update_layer_snap_locator(layer, enableSnappingForInvisibleFeature=False):
        #init
        #value = QgsSettings().value( "/qgis/digitizing/snap_invisible_feature", False )
        #enableSnappingForInvisibleFeature = bool( strtobool(value) )
        # get map canvas
        canvas = iface.mapCanvas()
        # update  map canvas util
        snapUtils = canvas.snappingUtils()
        map_settings = snapUtils.mapSettings()
        #
        snapUtils.setEnableSnappingForInvisibleFeature( enableSnappingForInvisibleFeature )
        snapUtils.setMapSettings( QgsMapSettings() )
        snapUtils.setMapSettings( map_settings )
        #
        canvas.setSnappingUtils( QgsSnappingUtils( snapUtils.parent() ) )
        canvas.setSnappingUtils( snapUtils )
    """

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def start_editing(vlay_list, force_edit=False):
        # loop layers
        for vlayer in vlay_list:
            if not vlayer.isEditable():
                if force_edit:
                    vlayer.setReadOnly( False )
                    #QGISAgriLayers.update_layer_snap_locator( vlayer, enableSnappingForInvisibleFeature=True )
                vlayer.startEditing()

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def end_editing(vlay_list, allowCancel=False):
        # loop layers
        res = True
        for vlayer in vlay_list:
            if vlayer.isEditable():
                res = res and iface.vectorLayerTools().stopEditing( vlayer, allowCancel )
        return res

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def end_editing_ext(vlay_list,
                        buttons=QMessageBox.Save|QMessageBox.Discard|QMessageBox.Cancel,
                        noMessage=False,
                        callback=None,
                        headerMessage=None,
                        msgLevel=Qgis.Info):
        # loop layers
        buttons = QMessageBox.Save|QMessageBox.Discard|QMessageBox.Cancel if buttons is None else buttons
        headerMessage = headerMessage or ''
        oper = QMessageBox.Save if noMessage else None

        # compose message
        lays = [l.name() for l in vlay_list if l.isEditable()]
        if len(lays) == 1:
            msg = tr( 'Voi salvare le modifiche apportate al vettore?' )
        else:
            msg = tr( 'Voi salvare le modifiche apportate ai vettori?' )

        msg_lays = ''.join( map(lambda x: f'
{x}
', lays) )
        msg = f"{msg}
{msg_lays}
"

        if headerMessage:
            msg = f"{headerMessage}{msg}"

        # close editing for each layer
        for vlayer in vlay_list:
            if not vlayer.isEditable():
                continue

            if not vlayer.isModified():
                if not vlayer.commitChanges():
                    raise Exception( vlayer.commitErrors() )
                continue

            if oper is None:
                oper = logger.htmlMsgbox(
                    msgLevel,
                    msg,
                    title= tr( 'Interrompi modifica' ),
                    standardButtons= buttons )
                """
                oper = QMessageBox.information(
                    iface.mainWindow(),
                    tr( 'Interrompi modifica' ),
                    msg,
                    btns_ext if allowCancel else btns_def )
                """
                if oper == QMessageBox.Cancel:
                    return False

                if callback:
                    callback( vlayer, oper, True )

            if oper == QMessageBox.Save:
                if not vlayer.commitChanges():
                    raise Exception( vlayer.commitErrors() )

            elif oper == QMessageBox.Discard:
                if not vlayer.rollBack():
                    raise Exception( vlayer.commitErrors() )

            if callback:
                callback( vlayer, oper, False )

        return True

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def end_all_editing(allowCancel=False):
        # loop layers in editing mode
        res = True
        vlay_list = iface.editableLayers()
        for vlayer in vlay_list:
            res = res and iface.vectorLayerTools().stopEditing( vlayer, allowCancel )
        return res

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def is_requested_vectorlayer(layer, data_source_uri_lst):
        # check if vector layer
        if layer is None:
            return False
        if layer.type() != QgsMapLayer.VectorLayer:
            return False
        # check data source uri
        lay_source = QGISAgriLayers.get_data_source_file_path( layer )
        data_provider = layer.dataProvider()
        ds_uri = data_provider.dataSourceUri()
        uri = QgsDataSourceUri( ds_uri )
        ds_lst = [ path.normpath( uri.database() ), uri.table() ]

        for data_source_uri in data_source_uri_lst:
            if isinstance( data_source_uri, QgsDataSourceUri ):
                params = [path.normpath(data_source_uri.database()), data_source_uri.table()]
                if all((a.lower() == b.lower()) for a, b in zip(params, ds_lst)):
                    return True
            else:
                data_source_uri = path.normpath( str( data_source_uri ) )
                if data_source_uri == lay_source:
                    return True

        return False

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def get_data_source_uri(layer_lst):
        res = []
        for layer in layer_lst:
            provider = layer.dataProvider()
            res.append( QgsDataSourceUri( provider.dataSourceUri() ) )
        return res

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def get_data_source_file_path(layer):
        # get source path
        provider = layer.dataProvider().name()
        source_parts = QgsProviderRegistry.instance().decodeUri( provider, layer.source() )
        source_path = source_parts.get( 'path', '' )
        if 'ogr' == provider.lower():
            source_path = source_path.split('|')[0]
        return path.normpath( source_path )

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def get_vectorlayer(data_source_uri_lst, source_other_than_rdbms=False):
        data_source_uri_lst = data_source_uri_lst or []
        layer_lst = []
        layers = QgsProject.instance().layerTreeRoot().findLayers()
        for data_source_uri in data_source_uri_lst:
            if not data_source_uri:
                continue

            elif source_other_than_rdbms and isinstance( data_source_uri, str ):
                data_source_uri = path.normpath( data_source_uri )
                for node_layer in layers:
                    layer = node_layer.layer()
                    layer_type = layer.type()
                    if layer_type != QgsMapLayer.VectorLayer:
                        continue

                    ds_uri = QGISAgriLayers.get_data_source_file_path( layer )
                    if data_source_uri == ds_uri:
                        layer_lst.append( layer )

            else:
                params = [path.normpath(data_source_uri.database()), data_source_uri.table()]
                for node_layer in layers:
                    layer = node_layer.layer()
                    layer_type = layer.type()
                    if layer_type != QgsMapLayer.VectorLayer:
                        continue

                    data_provider = layer.dataProvider()
                    uri = QgsDataSourceUri(data_provider.dataSourceUri())
                    ds_lst = [path.normpath(uri.database()), uri.table()]
                    if all((a.lower() == b.lower()) for a, b in zip(params, ds_lst)):
                        layer_lst.append(layer)
        return layer_lst

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def get_toc_vectorlayer(data_source_uri,
                           provider=None,
                           toc_grp_name=None):

        """Get Ari layer from Toc."""

        # check dat source uri
        if data_source_uri is None:
            return None

        # get group node from toc
        # pylint: disable=E1101
        root_node = grp_node = QgsProject.instance().layerTreeRoot()
        if toc_grp_name is not None:
            grp_node = root_node.findGroup(toc_grp_name)
        if grp_node is None:
            return None

        #
        params = [path.normpath(data_source_uri.database()), data_source_uri.table()]

        # get layer in group node
        layers = grp_node.findLayers()
        for node in layers:
            layer = node.layer()
            layer_type = layer.type()
            # pylint: disable=E1101
            if layer_type != QgsMapLayer.VectorLayer:
                continue

            # get layer provider data
            data_provider = layer.dataProvider()
            if (provider is not None and
                data_provider.name().lower() != provider.lower()):
                continue

            # pylint: disable=E1101
            uri = QgsDataSourceUri( data_provider.dataSourceUri() )
            ds_lst = [path.normpath(uri.database()), uri.table()]
            if all((a.lower() == b.lower()) for a, b in zip(params, ds_lst)):
                return layer

        return None

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def get_toc_vectorlayer_by_uri(uriDict: dict,
                                   provider: str,
                                   toc_grp_name: str =None) -> QgsVectorLayer:
        # get formatted uri
        uriDict = uriDict or {}

        # get group node from toc
        root_node = grp_node = QgsProject.instance().layerTreeRoot()
        if toc_grp_name is not None:
            grp_node = root_node.findGroup(toc_grp_name)
        if grp_node is None:
            return None

        # get layer in group node
        layers = grp_node.findLayers()
        for node in layers:
            layer = node.layer()
            layer_type = layer.type()
            if layer_type != QgsMapLayer.VectorLayer:
                continue

            # get layer provider data
            lay_provider = layer.dataProvider()
            if lay_provider.name().lower() != provider.lower():
                continue

            # get source path
            source_parts = QgsProviderRegistry.instance().decodeUri(
                layer.dataProvider().name(), layer.source() )
            source_path = source_parts.get( 'path', '' )
            if 'ogr' == provider.lower():
                source_path = source_path.split('|')[0]
            source_path = path.normpath( source_path )

            # get source layer name
            layer_name = source_parts.get( 'layerName', '' )
            if not layer_name or layer_name == NULL:
                layer_name = layer.name()

            # check if equal source
            if source_path == path.normpath( uriDict.get( 'path', '' ) ) and\
               layer_name == uriDict.get( 'layerName', '' ):
                return layer

        return None

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def get_layers_id_by_filesource(fileSource: str) -> QgsMapLayer:
        # init
        fileSource = path.normpath( fileSource )
        lstLayers = []

        # loop all layer in group node
        for k, layer in QgsProject.instance().mapLayers().items():
            laySource = QGISAgriLayers.get_data_source_file_path( layer )
            if laySource == fileSource:
                lstLayers.append( k )

        return lstLayers

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def create_toc_group_path(grp_path, parent_node, start=0):
        node = parent_node
        new_lst = []
        grp_lst = str(grp_path).split('/')
        grp_len = len( grp_lst )
        for grp_name in grp_lst:
            create_node = True
            if grp_len == 1 and grp_name.startswith( '*' ):
                create_node = False
                grp_name = grp_name[1:]
            node = parent_node.findGroup( grp_name )
            if node is None:
                if not create_node:
                    return '/'.join( new_lst ), node
                node = parent_node.insertGroup( start, grp_name )
            new_lst.append( grp_name )
            parent_node = node
            start = -1
        return '/'.join( new_lst ), node

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def create_toc_groups(lst_grp_name, start=0):
        index = start
        root_node = QgsProject.instance().layerTreeRoot()
        for toc_grp_name in lst_grp_name:
            # create group node
            new_path, _ = QGISAgriLayers.create_toc_group_path(
                toc_grp_name, root_node, index )
            if toc_grp_name.startswith( '*' ):
                toc_grp_name = toc_grp_name[1:]
            if new_path == toc_grp_name:
                index += 1

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def add_toc_vectorlayer(data_source_uri,
                             provider,
                             crs=None,
                             toc_lay_name=None,
                             toc_grp_name=None,
                             toc_grp_index=-1,
                             toc_lay_order=-1,
                             exclude_empty=False,
                             style_file=None,
                             show_total=True,
                             node_expanded=True,
                             toc_grp_sort_fn=None):
        """Add Agri vector layer to Toc."""

        # check dat source uri & provider
        if data_source_uri is None:
            return None

        uri = ''
        vlayer = None
        if isinstance( data_source_uri, dict ):
            uri = data_source_uri.get( 'uri', '' )
            vlayer = QGISAgriLayers.get_toc_vectorlayer_by_uri(
                data_source_uri, provider, toc_grp_name=toc_grp_name )
        else:
            # format params
            uri = data_source_uri.uri()
            toc_lay_name = toc_lay_name or data_source_uri.table()
            vlayer = QGISAgriLayers.get_toc_vectorlayer( data_source_uri )

        # check if layer already exists
        root_node = grp_node = QgsProject.instance().layerTreeRoot()
        if vlayer is not None:
            # show layer
            lay_node = root_node.findLayer( vlayer.id() )
            lay_node.setItemVisibilityCheckedRecursive( True )
            return vlayer

        # create vector layer
        opts = QgsVectorLayer.LayerOptions()
        opts.loadDefaultStyle = False
        vlayer = QgsVectorLayer( uri, toc_lay_name, provider, options=opts )
        if not vlayer.isValid():
            raise Exception( vlayer.error().message() )

        # mark as plugin layer
        vlayer.setProperty( __QGIS_AGRI_LAYER_TAG__, True )

        # set crs
        if crs is not None:
            vlayer.setCrs( QgsCoordinateReferenceSystem( crs ), False )

        # load layer style
        if style_file is not None:
            vlayer.loadNamedStyle( style_file, True )

        # load layer in TOC
        if exclude_empty and vlayer.hasFeatures() == QgsFeatureSource.NoFeaturesAvailable:
            return vlayer

        # add layer to QGIS layers registry
        QgsProject.instance().addMapLayer( vlayer, False )

        # create toc group node if not exists
        if toc_grp_name is not None:
            _, grp_node = QGISAgriLayers.create_toc_group_path( toc_grp_name, root_node, toc_grp_index )

        # add layer in TOC
        layNode = grp_node.insertLayer( toc_lay_order, vlayer )
        layNode.setExpanded(node_expanded)

        # sort layer group
        if toc_grp_sort_fn is not None:
            lyrList = [c.layer() for c in grp_node.children()]
            lyrSortList = sorted(lyrList, key=toc_grp_sort_fn)

            for idx, lyr in enumerate(lyrSortList):
                _ = grp_node.insertLayer(idx, lyr)

            grp_node.removeChildren(len(lyrList),len(lyrList))

        ## set custom property to show total features per layer
        if show_total:
            layNode.setCustomProperty( "showFeatureCount", True )
            # patch to update feature counts
            filterExpr = vlayer.subsetString()
            if not filterExpr:
                vlayer.setSubsetString( 'FALSE' )
                vlayer.setSubsetString( filterExpr )
                renderer = vlayer.renderer().clone()
                vlayer.setRenderer( renderer )

        return vlayer

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def reorder_layer(layer, index):
        root = QgsProject.instance().layerTreeRoot()
        nodeLayer = root.findLayer(layer.id())
        nodeClone = nodeLayer.clone()
        nodeParent = nodeLayer.parent()
        nodeParent.insertChildNode(index, nodeClone)
        nodeParent.removeChildNode(nodeLayer)

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def add_toc_owslayer(ows_url_params,
                          toc_lay_name=None,
                          toc_grp_name=None,
                          toc_grp_index=-1,
                          style_file=None,
                          allowInvalidSource=False,
                          recreateLayer=False):
        """Add Agri raster layer to Toc."""

        # check params
        if not isinstance( ows_url_params, dict ):
            return None

        lay_name = ows_url_params.get( 'layers', None )
        if lay_name is None:
            return None

        url = ows_url_params.get( 'url', None )
        if url is None:
            return None

        # correct url
        qUrl = QUrl( url )
        qUrl.setQuery( QUrl.toPercentEncoding( qUrl.query() ).data().decode() )
        ows_url_params[ 'url' ] = qUrl.toString()

#        # extract info from ows layer
#         try:
#             # get ows Capabilities
#             from owslib.wmts import WebMapTileService
#             wmts = WebMapTileService( ows_url_params.get('url') )
#             ows_layer = wmts.contents.get(lay_name)
#             if ows_layer is not None:
#                 if toc_lay_name is None:
#                     toc_lay_name = ows_layer.title
#         except Exception as e:
#             print( str(e) )

        # format parameter
        toc_lay_name = toc_lay_name or lay_name

        # check if group node exists
        grp_lay_index = 0
        root_node = grp_node = QgsProject.instance().layerTreeRoot()
        if toc_grp_name is not None:
            grp_node = root_node.findGroup( toc_grp_name )
            if grp_node is None:
                grp_node = root_node.insertGroup( toc_grp_index, toc_grp_name )

        # URL construction
        wms_url_final = parse.unquote( parse.urlencode( ows_url_params ) )
        uri = QgsDataSourceUri( wms_url_final )
        uri_match = re.sub( r"\/ogcproxy\/[^\/]*\/", "/ogcproxy/", uri.encodedUri().data().decode() )

        # get layer in group node
        recreateNodeVisible = None
        layers = grp_node.findLayers()
        for node in layers:
            layer = node.layer()
            layer_type = layer.type()
            if layer_type != QgsMapLayer.RasterLayer:
                continue

            # get layer provider data
            data_provider = layer.dataProvider()
            umatch = re.sub( r"\/ogcproxy\/[^\/]*\/", "/ogcproxy/", data_provider.uri().encodedUri().data().decode() )
            if umatch != uri_match:
                pass

            elif recreateLayer or not layer.isValid():
                try:
                    grp_lay_index = grp_node.children().index( layer )
                except:
                    grp_lay_index = 0

                recreateNodeVisible = node.isVisible()

                grp_node.removeChildNode( node )
                QgsProject.instance().removeMapLayer( layer.id() )
                break

            else:
                return layer

        # let's try to add it to the map canvas
        rlayer = QgsRasterLayer( wms_url_final, toc_lay_name, 'wms' )
        if not rlayer.isValid():
            if not allowInvalidSource:
                raise Exception( rlayer.error().message() )

        # mark as plugin layer
        rlayer.setProperty( __QGIS_AGRI_LAYER_TAG__, True )

        # add layer to QGIS layers registry
        QgsProject.instance().addMapLayer( rlayer, False )

        # load layer in toc
        node = grp_node.insertLayer( grp_lay_index, rlayer )

        if recreateNodeVisible is not None:
            node.setItemVisibilityChecked( recreateNodeVisible )

        return rlayer

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def layer_editFormFields_setReadOnly(vlayer, str_fld_list, read_only=True):
        # format list of fields to set read only in edit form
        if not isinstance( str_fld_list, str ):
            return

        fld_list = [ k.strip() for k in str_fld_list.split( ',' ) ]
        if not fld_list:
            return

        filter_fields = '*' not in fld_list

        # get layer edit form instance
        layEditFormCfg = vlayer.editFormConfig()
        # loop all field widgets in field form
        layFields = vlayer.fields()
        for idx in range( layFields.count() ):
            # filter field widget
            field = layFields.at( idx )
            if ( filter_fields and
                 field is not None and
                 field.name() not in fld_list ):
                continue
            # set field widget as read only
            layEditFormCfg.setReadOnly( idx, readOnly=read_only )
        # re apply edit format
        vlayer.setEditFormConfig( layEditFormCfg )

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def update_layers(lay_list):
        if type(lay_list) is not list:
            return
        for layer in lay_list:
            # pylint: disable=E1101
            if isinstance(layer, QgsMapLayer):
                layer.triggerRepaint()

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def update_layers_renderer(lay_list):
        try:
            if QGISAgriLayers.__isLayerStyleUpdating:
                return
            QGISAgriLayers.__isLayerStyleUpdating = True

            if type(lay_list) is not list:
                return

            for layer in lay_list:
                # pylint: disable=E1101
                if isinstance(layer, QgsMapLayer):
                    renderer = layer.renderer().clone()
                    layer.setRenderer( renderer )
        finally:
            QGISAgriLayers.__isLayerStyleUpdating = False

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def hide_layers(lay_list, hide=True):
        if type(lay_list) is not list:
            return
        # pylint: disable=E1101
        root_node = QgsProject.instance().layerTreeRoot()
        for layer in lay_list:
            # pylint: disable=E1101
            if isinstance(layer, QgsMapLayer):
                lay_node = root_node.findLayer( layer.id() )
                lay_node.setItemVisibilityCheckedRecursive( not hide )

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def show_layer_labels(lay_list):
        for layer in lay_list:
            layer.setLabelsEnabled( not layer.labelsEnabled() )
        QGISAgriLayers.update_layers( lay_list )

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def activate_layer_Labeling(lay_list, label_name, active=True, activeSwitch=False):
        from qgis.core import QgsRuleBasedLabeling

        # prepare label rule name
        hide_min_scale = False
        label_negate = False
        label_name = str(label_name).strip()
        if label_name.startswith('!'):
            label_name = label_name[1:]
            label_negate = True

        for layer in lay_list:
            # get type of labelling
            layer_settings = layer.labeling()
            if not isinstance( layer_settings, QgsRuleBasedLabeling ):
                # if not rule base
                if activeSwitch:
                    active = not layer.labelsEnabled()
                layer.setLabelsEnabled( active )
                continue

            # activate\deactivate rule
            for rule in layer_settings.rootRule().descendants():
                labe_eq = rule.description() == label_name
                if label_negate ^ labe_eq:
                    # activate\deactivate rule
                    if activeSwitch:
                        active = not rule.active()
                    rule.setActive( active )
                    # if activated rule, check scale visibility
                    if active and\
                       rule.dependsOnScale() and\
                       rule.minimumScale() < iface.mapCanvas().scale():
                        hide_min_scale = True

        # update layers
        QGISAgriLayers.update_layers( lay_list )

        # return
        return { 'hide_min_scale': hide_min_scale }

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def zoom_layers_ext(lay_list):
        ##xform = QgsCoordinateTransform(layer.crs(), canvas.mapSettings().destinationCrs()) for CRS
        ###canvas.setExtent(xform.transform(layer.extent()))

        p = QgsProject.instance()

        extent = None
        for layer in lay_list:
            if ( layer.type() == QgsMapLayer.VectorLayer and
                 layer.hasFeatures() == QgsFeatureSource.NoFeaturesAvailable ):
                continue

            xform = QgsCoordinateTransform( layer.crs(), p.crs(), p )
            lay_extent = xform.transform( layer.extent() )
            if extent is None:
                extent = lay_extent
            else:
                extent.combineExtentWith( lay_extent )

        if extent is None:
            return

        canvas = iface.mapCanvas()
        canvas.setExtent( extent )
        canvas.refresh()

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def zoom_full_extent():
        # pylint: disable=E1101
        canvas = iface.mapCanvas()
        canvas.zoomToFullExtent()
        canvas.refresh()

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def remove_layers(layers: list, noException: bool =True) -> None:
        try:
            lstIdLays = [l.id() for l in layers]
            QgsProject.instance().removeMapLayers( lstIdLays )
        except Exception as ex:
            if not noException:
                raise ex

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def change_map_crs(crs):
        QgsProject.instance().setCrs(QgsCoordinateReferenceSystem(crs))

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def repair_feature_single(layer,
                       feature,
                       attr_dict=None,
                       splitMultiParts=False,
                       autoSnap=False,
                       suoliMinArea=agriConfig.TOLERANCES.AREA_TOLERANCE_SUOLO,
                       suoliSnapTolerance=agriConfig.TOLERANCES.SNAP_TOLERANCE,
                       suoliSnapLayerUris=None,
                       addTopologicalPoints=False):

        """ Method to auto repair a feature geometry """

        # init
        res = True
        errMsg = ''
        geom = None

        # get auto repai attribute
        if not attr_dict:
            attr_dict = {}
            fld_lst = agriConfig.get_value( 'commands/repairsuolo/wrkFields', [] )
            for fld_def in fld_lst:
                if 'setValue' not in fld_def:
                    continue
                fld_name = fld_def.get( 'name', '' )
                fld_value = fld_def.get( 'setValue', None )
                attr_dict[fld_name] = fld_value

        # snap geometry (make valid)
        if autoSnap and suoliSnapLayerUris is not None and suoliSnapLayerUris:
            suoliSnapLayers = QGISAgriLayers.get_vectorlayer( suoliSnapLayerUris )
            geom = QGISAgriLayers.snapFeature( suoliSnapLayers, feature, snapTolerance=suoliSnapTolerance )

        else:
            geom = QgsGeometry( feature.geometry() )

        # repair geometry
        if geom.isGeosValid():
            newGeom = geom
        else:
            newGeom = geom.makeValid()
            if not newGeom.isGeosValid():
                res = False
                errMsg += newGeom.lastError()
                newGeom = QgsGeometry( feature.geometry() )

        # bufferize
        """
        newGeom = newGeom.buffer( -(agriConfig.TOLERANCES.CLEAN_VERTEX_TOLERANCE), 0 )\
                         .buffer( agriConfig.TOLERANCES.CLEAN_VERTEX_TOLERANCE*1.3, 0 )\
                         .intersection( newGeom )\
                         .simplify( agriConfig.TOLERANCES.CLEAN_VERTEX_TOLERANCE )
        """

        # split parts
        newFeatureLst = []
        if splitMultiParts and \
           newGeom.isMultipart() and \
           len( newGeom.asGeometryCollection() ) > 1:
            # get mapped attributes
            attribBaseMap = QGISAgriLayers.map_attribute_values( feature )

            # split feature for each geometry part
            for part in QGISAgriGeometry.get_geometry_single_parts( newGeom ):
                # skip small parts
                if part.area() < suoliMinArea:
                    continue

                #
                part.convertToMultiType()

                # add new feature
                attribMap = QGISAgriLayers.map_attribute_values( feature, attr_dict )
                attribMap = { **attribBaseMap, **attribMap }
                newFeature = QgsVectorLayerUtils.createFeature(
                    layer,
                    geometry= QgsGeometry( part ),
                    attributes= attribMap )
                layer.addFeature( newFeature )
                newFeatureLst.append( newFeature )

            # remove original feature
            layer.deleteFeature( feature.id() )

        else:
            # apply new feature geometry
            newGeom.convertToMultiType()
            feature.setGeometry( newGeom )
            layer.changeGeometry( feature.id(), newGeom )
            newFeatureLst.append( feature )

        try:
            snapper = QGISAgriGeometrySnapper()
            for feature in newFeatureLst:
                snapper.run( layer, feature, 0.005 )

        except Exception as e:
            res = False
            errMsg += str(e)

        # add topological points
        if addTopologicalPoints:
            layUtils = QgsVectorLayerEditUtils( layer )
            for feature in newFeatureLst:
                layUtils.addTopologicalPoints( feature.geometry() )

        return res, errMsg, newFeatureLst

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def repair_feature(layer,
                       feature,
                       attr_dict=None,
                       splitMultiParts=False,
                       autoSnap=False,
                       suoliMinArea=agriConfig.TOLERANCES.AREA_TOLERANCE_SUOLO,
                       suoliSnapTolerance=agriConfig.TOLERANCES.SNAP_TOLERANCE,
                       suoliSnapLayerUris=None,
                       addTopologicalPoints=False):

        res, errMsg, newFeatureLst = QGISAgriLayers.repair_feature_single(
                layer,
                feature,
                attr_dict=attr_dict,
                splitMultiParts=splitMultiParts,
                autoSnap=autoSnap,
                suoliMinArea=suoliMinArea,
                suoliSnapTolerance=suoliSnapTolerance,
                suoliSnapLayerUris=suoliSnapLayerUris,
                addTopologicalPoints=addTopologicalPoints
        )
        for feat in newFeatureLst:
            res, errMsg, _ = QGISAgriLayers.repair_feature_single(
                layer,
                feat,
                attr_dict=attr_dict,
                splitMultiParts=splitMultiParts,
                autoSnap=autoSnap,
                suoliMinArea=suoliMinArea,
                suoliSnapTolerance=suoliSnapTolerance,
                suoliSnapLayerUris=suoliSnapLayerUris,
                addTopologicalPoints=addTopologicalPoints
            )

        return True, ''

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def repair_feature_by_geom(layer,
                               feature,
                               attr_dict=None,
                               splitMultiParts=False,
                               autoSnap=False,
                               suoliMinArea=agriConfig.TOLERANCES.AREA_TOLERANCE_SUOLO,
                               suoliSnapTolerance=agriConfig.TOLERANCES.SNAP_TOLERANCE,
                               suoliSnapLayerUris=None,
                               addTopologicalPoints=False):
        # init
        attr_dict = attr_dict or {}
        inGeometry = QgsGeometry( feature.geometry() )
        delta = 10.0 / 100.0
        repaired = 0

        # get features that insersect the input feature
        fids = QGISAgriLayers.getFeaturesIdByGeom( layer, inGeometry )

        # loop features for intersection\difference
        for fid in fids:
            # get feature geometry
            feat = layer.getFeature( fid )
            featGeom = QgsGeometry( feat.geometry() )
            # check if there are intersections
            if not inGeometry.intersects( featGeom ):
                continue
            # get difference geometries
            intersGeom = featGeom.intersection( inGeometry )
            # check if valid
            if intersGeom.isEmpty():
                continue
            if intersGeom.isNull():
                continue

            # check area
            min_area = featGeom.area() * delta
            if intersGeom.area() < min_area:
                continue

            # repair feature
            QGISAgriLayers.repair_feature(
                layer,
                feat,
                attr_dict=attr_dict,
                splitMultiParts=splitMultiParts,
                autoSnap=autoSnap,
                suoliMinArea=suoliMinArea,
                suoliSnapTolerance=suoliSnapTolerance,
                suoliSnapLayerUris=suoliSnapLayerUris,
                addTopologicalPoints=addTopologicalPoints )

            # incr
            repaired += 1

        return repaired > 0

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def clone_features(layer, features, copyAttributesList, defaultAttributes=None):
        # init
        defaultAttributes = defaultAttributes or {}
        copyAttributesDict = {}
        defaultValues = {}
        lstFilterSrcAttribs = []
        newFeatures = []

        # prepare attributes
        for fld_def in copyAttributesList:
            field_name = fld_def.get( 'name', None )
            if not field_name:
                continue
            # popolate attribute dictionary
            copyAttributesDict[field_name] = fld_def
            # populate list of source attribute to copy
            lstFilterSrcAttribs.append( field_name )
            # get default values
            if 'default' in fld_def:
                destAttName = fld_def.get( 'destname', field_name )
                val = fld_def.get( 'default', '' )
                defaultValues[destAttName] = val

        defaultAttributes = {**defaultValues, **defaultAttributes}

        #create features to layer
        for srcFeat in features:
            # clone geometry
            geom = srcFeat.geometry()
            newFeat = QgsFeature( layer.fields() )
            newFeat.setGeometry( geom )

            # clone attributes
            srcAttribs = QGISAgriLayers.get_attribute_values( srcFeat, lstFilterSrcAttribs )
            attribs = {}
            for srcAttName,val in srcAttribs.items():
                attDef = copyAttributesDict.get( srcAttName, {} )
                destAttName = attDef.get( 'destname', srcAttName )
                if destAttName:
                    attribs[destAttName] = val

            attribs = {**defaultAttributes, **attribs}
            for k,v in attribs.items():
                newFeat.setAttribute( k, v )

            # add new feature to layer
            layer.addFeature( newFeat )
            newFeatures.append( newFeat )

        # return new features
        return newFeatures

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def change_attribute_values(layer,
                                feat_lst,
                                attr_dic,
                                add_fields_dic=None,
                                unvalorized_attr_dic=None):
        # init
        res = False

        # add new fields
        fields = layer.fields()
        if isinstance(add_fields_dic, dict):
            nadd = 0
            provider = layer.dataProvider()
            for fld_name, field in add_fields_dic.items():
                idx = provider.fieldNameIndex(fld_name)
                if (idx is None or idx == -1):
                    nadd += 0
                    provider.addAttributes( field )
            if nadd > 0:
                res = True
                layer.updateFields()

        # loop all feature
        for feat in feat_lst:
            fields = feat.fields()
            # loop dictionaty of attributes
            for attr_name, attr_val in attr_dic.items():
                # format attribute value setting
                if not isinstance( attr_val, dict ):
                    attr_val = { 'value': attr_val, 'skipValorized': False }

                # get attribute index
                idx = fields.indexFromName( attr_name )
                if idx == -1:
                    continue

                # check if attribute valorized
                curVal = feat.attribute( idx )
                if curVal != NULL and attr_val.get( 'skipValorized', False ):
                    continue

                # assign attribute to feature if changed
                newVal = attr_val.get( 'value' )
                if curVal != newVal:
                    #newVal = NULL if newVal is None else newVal
                    res = True
                    feat.setAttribute( idx, newVal )
                    layer.changeAttributeValue( feat.id(), idx, newVal, oldValue=curVal )

        # return if were changes
        return res

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def map_attribute_values(feature, attr_dict=None):
        res = {}
        # loop feature fields
        for k, field in enumerate( feature.fields() ):
            if field.constraints().constraints() & QgsFieldConstraints.ConstraintUnique:
                continue

            elif not attr_dict:
                res[k] = feature.attribute( k )

            elif field.name() in attr_dict:
                res[k] = attr_dict.get( field.name() )

        return res

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def get_attribute_values(feature, attr_list=None):
        attr_dic = {}
        fields = feature.fields()

        if attr_list is None:
            for fld in fields:
                attr = fld.name()
                attr_dic[attr] = feature[attr]
        else:
            for attr in attr_list:
                attr = str(attr)
                index = fields.indexFromName( attr )
                if index != -1:
                    attr_dic[attr] = feature.attribute( index )

        return attr_dic

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def get_attribute_values_ext(feature, attr_list=None):
        attr_dic = {}
        fields = feature.fields()

        if attr_list is None:
            for fld in fields:
                attr = fld.name()
                attr_dic[attr] = feature[attr]
        else:
            for attr in attr_list:
                if not isinstance( attr, dict ):
                    attr = { 'name': str(attr), 'from': str(attr) }

                attr_name = attr.get( 'name', '' )
                fld_name = attr.get( 'from', attr_name )
                index = fields.indexFromName( fld_name )
                if index != -1:
                    attr_dic[attr_name] = feature.attribute( index )

        return attr_dic

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def snapGeometry(geom,
                     otherGeoms,
                     snapTolerance=agriConfig.TOLERANCES.SNAP_TOLERANCE,
                     mode=QgsGeometrySnapper.PreferClosest):

        # init
        inGeom = QgsGeometry( geom )

        # get snapped geometry
        inGeom = QgsGeometrySnapper.snapGeometry( inGeom,
                                                  snapTolerance,
                                                  otherGeoms,
                                                  mode )

        # get snapped geometry
        return inGeom.makeValid()

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def snapFeature(vlay_list,
                    inFeature,
                    snapTolerance=agriConfig.TOLERANCES.SNAP_TOLERANCE,
                    mode=QgsGeometrySnapper.PreferClosest):

        # init
        vlay_list = vlay_list or []
        inGeom = QgsGeometry( inFeature.geometry() )

        # make valid geometry
        validGeom = inGeom
        if not validGeom.isGeosValid():
            validGeom = validGeom.makeValid()

        return validGeom

#         # get list of geometries
#         for layer in vlay_list:
#
#             # get features that insersect the input feature
#             refGeometries = []
#             for fid in QGISAgriLayers.getFeaturesIdByGeom( layer, validGeom, searchTolerance=snapTolerance ):
#                 feat = layer.getFeature( fid )
#                 if feat == inFeature:
#                     continue
#
#                 # collect valid geometry
#                 geom = QgsGeometry( feat.geometry() )
#                 geom = geom.makeValid()
#                 refGeometries.append( geom )
#
#             # get snapped geometry
#             for g in refGeometries:
#                 snapGeom = QgsGeometrySnapper.snapGeometry( validGeom,
#                                                             snapTolerance,
#                                                             [g],
#                                                             mode )
#                 if snapGeom.length() > snapTolerance:
#                     validGeom = snapGeom
#
#             """
#             validGeom = QgsGeometrySnapper.snapGeometry( validGeom,
#                                                          snapTolerance,
#                                                          refGeometries,
#                                                          mode )
#             """
#         # get snapped geometry
#         return validGeom.makeValid()

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def getFeaturesIdByGeom(vlayer,
                            geomSelector,
                            expression=None,
                            areaTolerance=agriConfig.TOLERANCES.AREA_TOLERANCE,
                            searchTolerance=agriConfig.TOLERANCES.SNAP_TOLERANCE,
                            skipInvaliGeom=False,
                            raiseExceptInvaliGeom=False,
                            tryMakeValid=False):
        """
        Function get layer features by a geometry.
        Do not evaluate if layer is hidden.
        """

        # init
        areaTolerance = areaTolerance or agriConfig.TOLERANCES.AREA_TOLERANCE
        searchTolerance = searchTolerance or agriConfig.TOLERANCES.SNAP_TOLERANCE
        if not geomSelector:
            return []

        if vlayer is None or not vlayer.isValid() or vlayer.type() != QgsMapLayer.VectorLayer:
            return []

        if not geomSelector.isGeosValid():
            # repair invalid geometry
            geomSelector = geomSelector.makeValid()

        # create spatial index
        index = QgsSpatialIndex( vlayer.getFeatures( expression ) ) \
                    if expression else QgsSpatialIndex( vlayer.getFeatures() )

        # Find all features that intersect the bounding box of the geometry selector
        bbox = geomSelector.boundingBox()
        bbox.grow( 2*searchTolerance )
        intersecting_ids = index.intersects( bbox )

        # loop all selected features
        res = []
        for fid in intersecting_ids:
            # check if feature with valid geometry
            feat = vlayer.getFeature( fid )
            geom = feat.geometry()
            if not geom:
                continue

            # check if valid geom
            if not geom.isGeosValid():
                if raiseExceptInvaliGeom:
                    # raise exceprion
                    raise QGISAgriException(
                        tr( "Presente feature non valida.\n\n"
                            "Correggere le geometrie errate nell'area di applicazione.\n"
                            "Utilizzare il comando 'Ripara suolo'." ) )

                elif tryMakeValid:
                    geom = geom.makeValid()

            # check if feature geometry intersects the selection rectangle
            if not geomSelector.intersects( geom ) and \
               not geomSelector.contains( geom ):
                continue

            # check tollerance and validity
            geomInt = geomSelector.intersection( geom )
            if (skipInvaliGeom and not geomInt.isGeosValid()) or \
                geomInt.isNull() or \
                geomInt.isEmpty() or \
                geom.area() < areaTolerance: # VERIFY CONDITION
                continue

            # collect feature
            res.append( fid )

        return res

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def cut_not_overlapping_feature(inFeature,
                                   overlapLayers,
                                   expression=None,
                                   areaTolerance=agriConfig.TOLERANCES.AREA_MIN_VALID_SUOLO):
        # init
        cuttedGeom = QgsGeometry( inFeature.geometry() )

        # loop overlap layers
        for overLayer in overlapLayers:
            # get features that overlap the input feature
            outFeats_ids = QGISAgriLayers.getFeaturesIdByGeom(
                overLayer, cuttedGeom, expression=expression, tryMakeValid=True )
            # loop features for intersection\difference
            for fid in outFeats_ids:
                # get feature geometry
                feat = overLayer.getFeature( fid )
                featGeom = QgsGeometry( feat.geometry() )

                if not featGeom.intersects( cuttedGeom ):
                    continue

                # create difference feature
                cuttedGeom = QgsGeometry( cuttedGeom.difference( featGeom ) )
                #if not diffGeom.isEmpty():
                #    cuttedGeom = diffGeom

        # check if valid cutted geometry
        if cuttedGeom.isNull() or \
           cuttedGeom.isEmpty() or \
           cuttedGeom.area() < agriConfig.TOLERANCES.AREA_TOLERANCE:
            return True

        # cut not overlap geometry
        inGeom = QgsGeometry( inFeature.geometry() )
        overGeom = inGeom.difference( cuttedGeom )

        # check if valid result geometry
        if overGeom.isNull() or \
           overGeom.isEmpty() or \
           overGeom.area() < areaTolerance:
            return False

        # re assign new cutted geometry
        inFeature.setGeometry( overGeom )

        return True

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def cut_interserct_feature(inFeature, cutLayerLst):
        # init
        inGeometry = QgsGeometry( inFeature.geometry() )

        # loop cutting layers
        for layer in cutLayerLst:
            # get features that insersect the input feature
            outFeats_ids = QGISAgriLayers.getFeaturesIdByGeom( layer, inGeometry )
            # loop features for intersection\difference
            for fid in outFeats_ids:
                # get feature geometry
                feat = layer.getFeature( fid )
                featGeom = QgsGeometry( feat.geometry() )

                if not featGeom.intersects( inGeometry ):
                    continue

                # create difference feature
                diffGeom = QgsGeometry( inGeometry.difference( featGeom ) )
                if not diffGeom.isEmpty():
                    inGeometry = diffGeom

        # re assign new cutted geometry
        inFeature.setGeometry( inGeometry )

    # --------------------------------------
    #
    # --------------------------------------
    @staticmethod
    def get_interserct_feature(inFeature,
                               layer,
                               getDiffGeom=True,
                               getIntersGeom=True,
                               getMaxIntersGeom=False,
                               layerFilterExpr=None,
                               areaTolerance=agriConfig.TOLERANCES.AREA_TOLERANCE,
                               suoliSnapLayers=None,
                               snapTolerance=agriConfig.TOLERANCES.SNAP_TOLERANCE):
        """
        Find difference geometries of a layer
        """

        # init
        interMaxArea = -1.0
        featMaxArea = None
        overlapFeatIdList = []
        delFeatIdList = []
        diffFeatList = []
        intersFeatList = []
        ##inGeometry = QgsGeometry( inFeature.geometry() )

        # get output fields
        ndx = 0
        outAttrIdLst = []
        outFields = layer.fields()
        for field in outFields:
            if field.constraints().constraints() & QgsFieldConstraints.ConstraintUnique:
                pass
            else:
                outAttrIdLst.append(ndx)
            ndx += 1

        inGeometry =  QGISAgriLayers.snapFeature( suoliSnapLayers,
                                                  inFeature,
                                                  snapTolerance=snapTolerance )

        # get features that insersect the input feature
        outFeats_ids = QGISAgriLayers.getFeaturesIdByGeom( layer,
                                                           inGeometry,
                                                           expression=layerFilterExpr,
                                                           raiseExceptInvaliGeom=True )

        # loop features for intersection\difference
        for fid in outFeats_ids:
            # get feature geometry
            feat = layer.getFeature( fid )
            featGeom = QgsGeometry( feat.geometry() )

            featGeom = QGISAgriLayers.snapFeature( suoliSnapLayers,
                                                   feat,
                                                   snapTolerance=snapTolerance )

            # check if vadil geometry
            #if not featGeom.isGeosValid():
            #    outLayer.selectByIds( [fid], QgsVectorLayer.SetSelection )
            #    msg = msgTag + self.tr( "Geometria invalida" )
            #    raise QGISAgriException( msg )

            # check if there are intersections
            if not featGeom.intersects( inGeometry ):
                continue

            # get difference geometries
            if getDiffGeom:
                # create difference feature
                diffGeom = QgsGeometry( featGeom.difference( inGeometry ) )
                if not diffGeom.isEmpty():
                    #if False and not diffGeom.isGeosValid():
                    #    msg = msgTag + self.tr( "Create delle geometrie invalide" )
                    #    raise QGISAgriException( msg )
                    if not(diffGeom.area() < agriConfig.TOLERANCES.AREA_TOLERANCE_SUOLO):
                        outFeat = QgsFeature()
                        outFeat.setGeometry( diffGeom )
                        outFeat.setFields( outFields )
                        for ndx in outAttrIdLst:
                            outFeat.setAttribute( ndx, feat.attribute(ndx) )
                        diffFeatList.append( outFeat )
                    else:
                        overlapFeatIdList.append( feat.id() )

                    delFeatIdList.append( feat.id() )

            # get difference geometries
            if getIntersGeom or getMaxIntersGeom:
                # create intersection feature
                interGeom = QgsGeometry( featGeom.intersection( inGeometry ) )

                # correct intersection
                if interGeom.wkbType() == QgsWkbTypes.Unknown or \
                   QgsWkbTypes.flatType( interGeom.wkbType() ) == QgsWkbTypes.GeometryCollection:
                    interGeom = QgsGeometry()
                    comGeom = featGeom.combine( inGeometry )
                    if comGeom is not None:
                        symGeom = featGeom.symDifference( inGeometry )
                        interGeom = QgsGeometry( comGeom.difference( symGeom ) )

                # check if valid intersection geometry
                if not interGeom.isEmpty():
                    interGeomArea = 0.0

                    if getMaxIntersGeom:
                        # get feature with max intersecting surface
                        interGeomArea = interGeom.area()
                        if interGeomArea > interMaxArea:
                            interMaxArea = interGeomArea
                            featMaxArea = feat

                    if interGeom.area() < areaTolerance:
                        continue

                    deltaArea = featGeom.area() - interGeomArea
                    if deltaArea < agriConfig.TOLERANCES.AREA_TOLERANCE:
                        delFeatIdList.append( feat.id() )

                    overlapFeatIdList.append( feat.id() )
                    """
                    if not getMaxIntersGeom or deltaArea < 0.000001:
                        # create new intersecting feature
                        outFeat = QgsFeature()
                        outFeat.setGeometry( interGeom )
                        outFeat.setFields( outFields )
                        for ndx in outAttrIdLst:
                            outFeat.setAttribute( ndx, feat.attribute(ndx) )
                        intersFeatList.append( outFeat )
                        delFeatIdList.append( feat.id() )
                    """

        # create new max intersecting surface feature
        if getMaxIntersGeom and featMaxArea is not None:
            outFeat = QgsFeature()
            outFeat.setGeometry( QgsGeometry( inGeometry ) )
            outFeat.setFields( outFields )
            for ndx in outAttrIdLst:
                outFeat.setAttribute( ndx, featMaxArea.attribute(ndx) )
            intersFeatList.append( outFeat )
            delFeatIdList.append( featMaxArea.id() )

        # return
        return (
            intersFeatList,
            diffFeatList,
            delFeatIdList,
            overlapFeatIdList,
            featMaxArea
        )


import logging
import re

from django import forms
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _

from metashare.settings import LOG_HANDLER
from metashare.repository.models import resourceInfoType_model
from metashare.repository.search_indexes import resourceInfoType_modelIndex
from metashare.recommendations.recommendations import get_more_from_same_creators, \
    get_more_from_same_projects

from haystack.forms import FacetedSearchForm
from haystack.query import SQ

# Setup logging support.
LOGGER = logging.getLogger(__name__)
LOGGER.addHandler(LOG_HANDLER)

# define special query prefixes
MORE_FROM_SAME_CREATORS = "mfsc"
MORE_FROM_SAME_PROJECTS = "mfsp"

class FacetedBrowseForm(FacetedSearchForm):
    """
    A custom `FacetedSearchForm` for faceted browsing and searching.
    """
    def search(self):
        """
        A blend of its super methods with only a different base
        `SearchQuerySet` in case of empty/invalid queries.
        """
        sqs = self.searchqueryset
        if self.is_valid() and self.cleaned_data.get('q'):
            # extract special queries
            special_queries, query = \
              _extract_special_queries(self.cleaned_data.get('q'))
            if query:
                sqs = sqs.auto_query(query)
            if (special_queries):
                # for each special query, get the Django internal resource ids
                # matching the query and filter the SearchQuerySet accordingly
                for _sq in special_queries:
                    _res_ids = _process_special_query(_sq)
                    if _res_ids:
                        _sq = SQ()
                        for _id in _res_ids:
                            _sq.add(SQ(django_id=_id), SQ.OR)
                        sqs = sqs.filter(_sq)
                    else:
                        # force empty search result if no ids are returned
                        # for a special query
                        sqs = sqs.none()
                        break
        if self.load_all:
            sqs = sqs.load_all()
        # we need to process each facet to ensure that the field name and the
        # value are quoted correctly and separately:
        for facet in [f for f in self.selected_facets if ":" in f]:
            field, value = facet.split(":", 1)
            # only add facets which are also in the search index
            # pylint: disable-msg=E1101
            if not field in resourceInfoType_modelIndex.fields:
                LOGGER.info('Ignoring unknown facet field "%s".', field)
                continue
            if value:
                sqs = sqs.narrow(u'%s:"%s"' % (field, sqs.query.clean(value)))
        return sqs

    def clean(self):
        # add validation errors for unknown facet fields in the request:
        _errors = []
        for facet in self.selected_facets:
            field = facet.split(":", 1)[0]
            # pylint: disable-msg=E1101
            if not field in resourceInfoType_modelIndex.fields:
                _errors.append(
                    _("Ignoring an unknown filter from your query: %s") % field)
        if _errors:
            raise forms.ValidationError(_errors)

        return super(FacetedBrowseForm, self).clean()

def _extract_special_queries(query):
    """
    Extracts from the given query string all special queries;
    returns the original query that is stripped from the special queries
    and the extracted special queries;
    currently , we have two special queries:
    more-from-creator-of:<resource-id>
    more-from-project-of:<resource-id>
    """
    # here we collect the special queries
    special_queries = []

    for _token in query.split():
        if _token.startswith(MORE_FROM_SAME_CREATORS)\
          or _token.startswith(MORE_FROM_SAME_PROJECTS):
            special_queries.append(_token)
    # remove special queries from original query
    if special_queries:
        for _sq in special_queries:
            query = query.replace(_sq, "")
        ws_pattern = re.compile(r'\s+')
        query = re.sub(ws_pattern, " ", query)
        query = query.strip()

    return special_queries, query

def _process_special_query(query):
    """
    Processes the given special query;
    returns a list of resource ids matching the query;
    ids are the INTERNAL Django ids, not the StorageObject identifiers!!!
    """
    query_type, resource_id = query.split(":")
    # get resource
    try:
        res = resourceInfoType_model.objects.get(
            storage_object__identifier=resource_id)
    except resourceInfoType_model.DoesNotExist:
        LOGGER.info('Ignoring unknown storage identifier "%s" in "%s" query.',
            resource_id, query_type)
        return []
    # get related resources
    if query_type == MORE_FROM_SAME_CREATORS:
        rel_res = get_more_from_same_creators(res)
    elif query_type == MORE_FROM_SAME_PROJECTS:
        rel_res = get_more_from_same_projects(res)
    else:
        LOGGER.info('Ignoring unknown special query type "%s".', query_type)
        return []
    # return internal ids from related resources
    return [x.id for x in rel_res]

class LicenseSelectionForm(forms.Form):
    """
    A `Form` for presenting download licenses and selecting exactly one of them.
    """
    def __init__(self, licences, *args, **kwargs):
        """
        Initializes the `LicenseSelectionForm` with the given licenses.
        """
        super(LicenseSelectionForm, self).__init__(*args, **kwargs)
        class _LicenseSelectionRenderer(forms.widgets.RadioFieldRenderer):
            """
            A custom `RadioSelectRenderer` for rendering license selections.

            This widget does not only contain radio buttons with license name
            labels but additionally short license information blocks for each
            license.
            """
            def __iter__(self):
                for i, choice in enumerate(self.choices):
                    yield (licences[choice[0]][0],
                           forms.widgets.RadioInput(self.name, self.value,
                                                self.attrs.copy(), choice, i))

            def render(self):
                return mark_safe(u'
{0}\n
'.format(
                    u'\n'.join([u'

{0}
\n{1}
' \
                                    .format(force_unicode(w),
                                            self._create_restrictions_block(l,
                                                                w.choice_value))
                                for (l, w) in self])))

            def _create_restrictions_block(self, licence_info, licence_name):
                """
                Creates an HTML block element string containing the restrictions
                of the given license information.
                """
                result = u'

{0}
\n
' \
                    .format(_('Restrictions of use:'))
                r_list = licence_info.get_restrictionsOfUse_display_list()
                if r_list:
                    for restr in r_list:
                        result += u'
{0}
'.format(restr)
                if licences[licence_name][1]:
                    direct_download_msg = _('direct download available')
                else:
                    direct_download_msg = _('no direct download available')
                result += u'
{0}

'.format(direct_download_msg)
                return result

        self.fields['licence'] = \
            forms.ChoiceField(choices=[(name, name) for name in licences],
                              widget=forms.widgets.RadioSelect(
                                        renderer=_LicenseSelectionRenderer))

class LicenseAgreementForm(forms.Form):
    """
    A `Form` for presenting a license to which the user must agree.
    """
    in_licence_agree_form = forms.BooleanField(initial=True,
                                               widget=forms.HiddenInput())
    licence_agree = forms.BooleanField(label=_('I agree to these licence ' \
                            'terms and would like to download the resource.'))

    def __init__(self, licence, *args, **kwargs):
        """
        Initializes the `LicenseAgreementForm` with the given licence.
        """
        super(LicenseAgreementForm, self).__init__(*args, **kwargs)
        self.fields['licence'] = forms.CharField(initial=licence,
                                                 widget=forms.HiddenInput())

class DownloadContactForm(forms.Form):
    """
    A `Form` for sending a contact request regarding the download of a resource
    """
    userEmail = forms.EmailField(label=_("Your e-mail"))
    message = forms.CharField(label=_("Your message"), widget=forms.Textarea())


#!/usr/bin/python3
"""
link the class with the database
"""

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String

Base = declarative_base()

class State(Base):
    """State class."""
    __tablename__ = 'states'

    id = Column(Integer,
                primary_key=True,
                nullable=False,
                autoincrement="auto")
    name = Column(String(128), nullable=False)


import datetime
import os
# import subprocess
from collections import namedtuple

import matplotlib.pyplot as plt
import numpy as np

def extract_value_1(string):
    path = os.path.abspath(string)
    file_object = open(path, "r")
    i = 0
    list1 = []
    storage = (0, 0)
    for line in file_object:
        i += 1
        list2 = line.split("/")
        if i % 2 == 0:
            time = int(list2[2])
            list1.append((storage[0], storage[1] / time))
        else:
            size = int(list2[0])
            time = int(list2[2])
            storage = (size, time)
    file_object.close()
    return list1

Data = namedtuple("Data", ["size", "time_seq", "time_par"])

def extract_value_2(path):
    path = os.path.abspath(path)
    with open(path, "r") as f:
        return [parse_line(line1, line2) for (line1, line2) in zip(f, f)]

def parse_line(line1, line2):
    [size, _, time_seq] = line1.split("/")
    [_, _, time_par] = line2.split("/")
    return Data(int(size), int(time_seq), int(time_par))

def parallel_plot():
    path = os.path.abspath("graph_generation/10to1000by10with101try.txt")
    file_object = open(path, "r")
    list1 = []
    for line in file_object:
        list2 = line.split('/')
        if list2[1] != 'Sequential':
            list1.append((int(list2[0]), int(list2[2])))
    list3 = median(list1)
    file_object.close()
    plt.close('all')
    x = [i[0] for i in list3]
    y = [i[1] for i in list3]
    plt.ylabel("Time")  # y label
    plt.xlabel("Size_Instance")  # x label
    plt.grid()
    plt.plot(x, y)

def sequential_time_100():
    path = os.path.abspath("graph_generation/10to1000by10with101try.txt")
    file_object = open(path, "r")
    list_holder = []
    for line in file_object:
        list2 = line.split('/')
        if list2[1] == 'Sequential':
            list_holder.append((int(list2[0]), int(list2[2])))
    list_moyenne = []
    holder = []
    for x in range(0, len(list_holder)):
        if x != len(list_holder) - 1 and (list_holder[x][0] // 100) == (list_holder[x + 1][0] // 100):
            holder.append(list_holder[x][1])
        else:
            holder.append(list_holder[x][1])
            list_moyenne.append((list_holder[x][0], sum(holder) / len(holder)))
            holder.clear()
    return list_moyenne

def sequential_plot():
    path = os.path.abspath("graph_generation/10to1000by10with101try.txt")
    file_object = open(path, "r")
    list1 = []
    for line in file_object:
        list2 = line.split('/')
        if list2[1] == 'Sequential':
            list1.append((int(list2[0]), int(list2[2])))
    list3 = median(list1)
    # plt.close('all')
    x = [i[0] for i in list3]
    y = [i[1] for i in list3]
    plt.ylabel("Time")  # y label
    plt.xlabel("Size_Instance")  # x label
    plt.grid()
    plt.plot(x, y)
    plt.legend(["parallèle", "séquentiel"], loc="lower right")
    # plt.show()
    plt.title("Temps d'exécution/Taille de l'instance 10-1000 pas=10")
    plt.savefig(f'graph_generation/Graphs/{datetime.datetime.now()}.png')

def moyenne(list_tuple):
    list_moyenne = []
    holder = []
    for x in range(0, len(list_tuple)):
        if x != len(list_tuple) - 1 and list_tuple[x][0] == list_tuple[x + 1][0]:
            holder.append(list_tuple[x][1])
        else:
            holder.append(list_tuple[x][1])
            list_moyenne.append((list_tuple[x][0], sum(holder) / len(holder)))
            holder.clear()
    return list_moyenne

def median(list_tuple):
    list_median = []
    holder = []
    for x in range(0, len(list_tuple)):
        if x != len(list_tuple) - 1 and list_tuple[x][0] == list_tuple[x + 1][0]:
            holder.append(list_tuple[x][1])
        else:
            holder.append(list_tuple[x][1])
            list_median.append((list_tuple[x][0], np.median(holder)))
            holder.clear()
    return list_median

def group_data(data_list):
    grouped_data = {}
    for data in data_list:
        if data.size not in grouped_data:
            grouped_data[data.size] = []
        grouped_data[data.size].append(data)
    return sorted(grouped_data.values(), key=lambda l: l[0].size)

def basic_plot(list1):
    plt.close('all')
    x = [i[0] for i in list1]
    y = [i[1] for i in list1]
    plt.ylabel("Speed_Up")  # y label
    plt.xlabel("Size_Instance")  # x label
    plt.grid()
    plt.plot(x,y)
    plt.title("Speed_up/Taille Instance 10-1000 pas=25/Worst-Case")
    plt.savefig(f'graph_generation/Graphs/{datetime.datetime.now()}.png')

def threads_iterations_plots(list1, list2, list3):
    plt.close('all')
    a = [i[0] for i in list1]
    b = [i[1] for i in list1]
    c = [i[0] for i in list2]
    d = [i[1] for i in list2]
    e = [i[0] for i in list3]
    f = [i[1] for i in list3]
    plt.ylabel("Speed_Up")  # y label
    plt.xlabel("Size_Instance")  # x label
    plt.grid()
    plt.plot(a, b)
    plt.plot(c, d)
    plt.plot(e, f)
    plt.legend(["2 thread", "3 thread", "4 thread"], loc="lower right")
    plt.title("Speed_up/Taille Instance 100-200(pas=10)/nombre_threads")
    plt.savefig(f'graph_generation/Graphs/{datetime.datetime.now()}.png')

if __name__ == '__main__':
    command = 'cargo run -- --instance-size-start 100 --instance-size-end 1000 -p 25 -t 4 -n 51 -w'
    # subprocess.run(command, shell=True)
    # list_speedup_2 = moyenne(extract_value("graph_generation/seed42threads2.txt"))
    # list_speedup_3 = moyenne(extract_value("graph_generation/seed42threads3.txt"))
    # list_speedup_4 = moyenne(extract_value("graph_generation/seed42threads4.txt"))
    # threads_iterations_plots(list_speedup_2, list_speedup_3, list_speedup_4)
    list_size_speedup = extract_value_1("graph_generation/100to1000by25with51tryWorstCase.txt")
    better_list = moyenne(list_size_speedup)
    basic_plot(better_list)
    # parallel_plot()
    # sequential_plot()
    # subprocess.run('rm graph_generation/log.txt', shell=True)
    # print(sequential_time_100())


#Initialization of array to store variable assignments
assignments = []

#Rows and cols strings
rows = 'ABCDEFGHI'
cols = '123456789'

#Switch to define if the sudoku to solve is a regular one or one with diagonal restrictions
isSudokuDiagonal = True

def cross(A, B):
    "Cross product of elements in A and elements in B."
    return [s + t for s in A for t in B]

boxes = cross(rows, cols)

row_units = [cross(r, cols) for r in rows]
column_units = [cross(rows, c) for c in cols]
square_units = [cross(rs, cs) for rs in ('ABC','DEF','GHI') for cs in ('123','456','789')]

if (isSudokuDiagonal):
    #Generates both diagonals of the 9x9 grid
    diagonal_units = [[s+t for s,t in zip(rows,cols)],[s+t for s,t in zip(rows,cols[::-1])]]
    unitlist = row_units + column_units + square_units + diagonal_units
else:
    unitlist = row_units + column_units + square_units

units = dict((s, [u for u in unitlist if s in u]) for s in boxes)
peers = dict((s, set(sum(units[s],[]))-set([s])) for s in boxes)

def assign_value(values, box, value):
    """
    Please use this function to update your values dictionary!
    Assigns a value to a given box. If it updates the board record it.
    """
    values[box] = value
    if len(value) == 1:
        assignments.append(values.copy())
    return values

def grid_values(grid):
    """
    Convert grid into a dict of {square: char} with '123456789' for empties.
    Args:
        grid(string) - A grid in string form.
    Returns:
        A grid in dictionary form
            Keys: The boxes, e.g., 'A1'
            Values: The value in each box, e.g., '8'. If the box has no value, then the value will be '123456789'.
    """
    assert len(grid) == 81, "Grid length must be 81 for a 9x9 sudoku"
    return dict((boxes[i], '123456789' if grid[i] == '.' else grid[i]) for i in range(len(grid)))

def display(values):
    """
    Display the values as a 2-D grid.
    Args:
        values(dict): The sudoku in dictionary form
    """
    width = 1 + max(len(values[s]) for s in boxes)
    line = '+'.join(['-' * (width * 3)] * 3)
    for r in rows:
        print(''.join(values[r + c].center(width) + ('|' if c in '36' else '')
                      for c in cols))
        if r in 'CF': print(line)
    return


import typing
import discord
import mimetypes

from .. import Cog
from ...core.utilities import api

class Imgur(Cog):
    """Another utility plugin which provides few commands to directly interact with imgur API."""

    def __init__(self, plugin):
        super().__init__()
        self.plugin = plugin

    @Cog.cooldown(1, 5, type=Cog.bucket_type.user)
    @Cog.command(name="imgur", aliases=["imgurfy", "imgurify"])
    async def imgur(self, ctx, url: typing.Union[discord.Member, discord.Emoji, discord.PartialEmoji, str] = None):
        """Uploads provided URL or attached image to imgur.com and returns the direct URL of the image.
        You can also specify either an emoji or mention someone to upload their avatar to imgur. If no URL is
        specified, returns the imgur URL of the user's avatar.

        """
        if not (url or ctx.message.attachments):
            url = str(ctx.author.avatar_url_as(static_format="png"))

        if isinstance(url, discord.Member):
            url = str(url.avatar_url_as(static_format="png"))
        if isinstance(url, (discord.Emoji, discord.PartialEmoji)):
            url = str(url.url)
        url = url or ctx.message.attachments[0].url

        type_ = mimetypes.guess_type(url)
        if type_[0] in ("video/mp4",):
            media = dict(video=url)
        else:
            media = dict(image=url)

        try:
            async with ctx.loading():
                image = await self.bot.utilities.imgur.upload(**media)
        except api.ImgurHTTPException:
            return await ctx.send_line("❌    Failed to upload provided image or URL to imgur.")
        return await ctx.send_line(
            "Click here to open the image in your browser.", content=f"<{image.url}>",
            icon_url=self.bot.theme.images.share, author_url=image.url)


"""JM"""
def main():
    """Doc"""
    keep1 = int(input())
    keep2 = int(input())
    kong = ""
    for i in range(0, keep1+1):
        kong += str(i) #เก็บเลขที่วนไว้ใน kong
    keep = kong.count(str(keep2))#หาจำนวนในตัวแปร kong โดย count จำนวนที่ต้องการจะหาว่ามีจำนวนกี่ตัว
    print(keep)
main()


#!/usr/bin/env python

""" General-purpose functions that do not fit anywhere else """
import itertools

def findall(substr: str, string: str):
    """ :return A list sorted in ascending order containing indices indicating where @substr is found in @string. """
    indices = []
    startindex = 0
    while True:
        startindex = string.find(substr, startindex)
        if startindex < 0:
            break
        indices.append(startindex)
        startindex += len(substr)
    return indices

def ascii_printables():
    """ :return A Unicode string consisting of every printable symbol between 0 and 255. """
    return ''.join(chr(i) for i in itertools.chain(range(33, 128), range(161, 256)))

def embed_strings(basestring: str, embeddables: dict):
    """ :return The string constructed by, for every entry (i, s) in @embeddables, embedding string s into @basestring
    at index i. """
    substrings = []
    seekidx = 0
    for embedidx, embedstr in iter(sorted(embeddables.items())):
        substrings.append(basestring[seekidx: embedidx])
        seekidx = embedidx
        if embedidx + len(embedstr) <= len(basestring):
            substrings.append(embedstr)
            seekidx += len(embedstr)
        elif embedidx < len(basestring) < embedidx + len(embedstr):
            substrings.append(embedstr[:len(basestring) - embedidx])
            seekidx += len(basestring) - embedidx
        else:
            seekidx = len(basestring)
            break
    substrings.append(basestring[seekidx:len(basestring)])
    return ''.join(substrings)


from .graph import Graph

def build_graph(sentences, frequent_phrases):
    graph = Graph()
    for item in sentences:
        weight = 1
        for phrase in frequent_phrases.keys():
            if((item.text.lower()).find(phrase) >= 0):
                weight += 1
        if not graph.has_node(item.token):
            graph.add_node(item.token, weight)

    return graph

def remove_unreachable_nodes(graph):
    for node in graph.nodes():
        if sum(graph.edge_weight((node, other)) for other in graph.neighbors(node)) == 0:
            graph.del_node(node)


from pattern.es import spelling
from pattern.es import lexicon
from pattern.es import verbs
from pattern.es import parse
import time
from random import choice
import random
import json
from JugadorGenerico import Padre

class Maquinola(Padre):

    def __init__(self,turno=False):
        super().__init__(turno)
        self._palabra = ""
        self._ID = 1
        self._cant_paso=0

    def get_cant_paso(self):
        return self._cant_paso

    def get_id(self):
        return self._ID
    def fin_turno(self):
        '''Finaliza el turno de la maquina '''
        self._palabra = ""
        super().set_turno(False)
        super().set_letra("")

    def get_palabra(self):
        return self._palabra
    def set_palabra(self,palabra):
        self._palabra = palabra
    def get_palabra(self):
        return self._palabra

    def busco_en_x(self,x,y,tablero,tab):
        '''Busca si en el eje x es posibe ingresar la letra'''
        ok=False
        cant=len(self._palabra)
        for i in range(cant):
            x=x+1
            if( x<15):
                if tablero[x][y] :
                        return False
            else :
                return False
        return True
    def busco_en_y(self,x,y,tablero,tab):
        '''Busca si en el eje y es posibe ingresar la letra'''
        ok=False
        cant=len(self._palabra)
        for i in range(cant):
            y=y+1
            if( y<15):
                if tablero[x][y] :
                        return False
            else :
                return False
        return True

    def poner_palabra_x(self,x,y,tab,g):
        '''Verifica si la palabra puede ser colocado en el eje x'''
        pal=self._palabra
        for i in pal:
            tab.chequeroDuplica(x,y,i)
            tab.EscribirEnTablero(x,y,g,i)
            x=x+1

    def poner_palabra_y(self,x,y,tab,g):
        '''Verifica si la palabra puede ser colocado en el eje y'''
        pal=self._palabra
        for i in pal:
            tab.chequeroDuplica(x,y,i)
            tab.EscribirEnTablero(x,y,g,i)
            y=y+1

    def _chequeo_espacio(self,tablero,tab,x,y,g):
        '''Chequeo que la proxima posicion sea valida'''
        ok=True
        if(tablero[x][y]==False):
            if self.busco_en_x(x,y,tablero,tab,):
                ok=False
                self.poner_palabra_x(x,y,tab,g)
                return True
            if self.busco_en_y(x,y,tablero,tab,) and ok:
                self.poner_palabra_y(x,y,tab,g)
                return True
        return False

    def lugar_random(self,tab,g):
        '''Eligue un lugar random al colocar la palabra'''
        ok=False
        x=random.randrange(15)
        y=random.randrange(15)
        tablero=[]
        tablero=tab.get_tablero_booleano()
        num=500
        while True:
            num=num-1
            puse_palabra=self._chequeo_espacio(tablero,tab,x,y,g)

            if puse_palabra:
                break
            else:
                x=random.randrange(15)
                y=random.randrange(15)
            if num<=0:
                break

    def PrimerLugar(self,tab,g):
        '''Coloca la primer palabra en el casilllero de inicio'''
        ok=False
        x=7
        y=7
        tablero=[]
        tablero=tab.get_tablero_booleano()
        num=500
        while True:
            num=num-1
            puse_palabra=self._chequeo_espacio(tablero,tab,x,y,g)
            if puse_palabra:
                break
            else:
                x=random.randrange(15)
                y=random.randrange(15)
            if num<=0:
                break

    def calculo_lugar_en_x_cruzada(self,tab,x,y,primerMitad,segundaMitad):
        '''Busca que haya lugar antes y despues de la letra que se quiere cruzar en el eje X'''
        pal=self._palabra
        cant=len(pal)
        x=x-len(primerMitad)

        if x>=0 and x<15:
            for i in range(len(primerMitad)):
                if tab.chequeo_selected(x,y):
                    return False
                if x<14:
                    x=x+1
                else : return False
            if x<14:
                x=x+1
                for i in range(len(segundaMitad)):
                    if tab.chequeo_selected(x,y):
                        return False
                    if x<14:
                        x=x+1
                    else :
                        return False
            else :
                return False
        return True

    def calculo_lugar_en_y_cruzada(self,tab,x,y,primerMitad,segundaMitad):
        '''Busca que haya lugar antes y despues de la letra que se quiere cruzar en el eje Y'''
        pal=self._palabra
        cant=len(pal)
        y=y-len(primerMitad)
        if y>=0 and y<15:
            for i in range(len(primerMitad)):
                if tab.chequeo_selected(x,y):
                    return False
                if y<14:
                    y=y+1
                else : return False
            if y<14:
                y=y+1
                for i in range(len(segundaMitad)):
                    if tab.chequeo_selected(x,y):
                        return False
                    if y<14:
                        y=y+1
                    else :
                        return False
            else :
                return False
        return True

    def poner_palabra_x_cruzada(self,tab_ejecucion,primerMitad,segundoMitad,x,y,g):
        '''Escribe una palabra en el eje X cruzando una letra del tablero'''
        pal=self._palabra
        cant=len(pal)
        x=x-len(primerMitad)
        if x>=0:
            for i in range(len(primerMitad)):
                tab_ejecucion.chequeroDuplica(x,y,i)
                tab_ejecucion.EscribirEnTablero(x,y,g,primerMitad[i])
                x=x+1
            x=x+1
            for i in range(len(segundoMitad)):
                tab_ejecucion.chequeroDuplica(x,y,i)
                tab_ejecucion.EscribirEnTablero(x,y,g,segundoMitad[i])
                x=x+1

    def poner_palabra_y_cruzada(self,tab_ejecucion,primerMitad,segundoMitad,x,y,g):
        '''Escribe una palabra en el eje Y cruzando una letra del tablero'''
        pal=self._palabra
        cant=len(pal)
        y=y-len(primerMitad)
        if y>=0:
            for i in range(len(primerMitad)):
                tab_ejecucion.chequeroDuplica(x,y,i)
                tab_ejecucion.EscribirEnTablero(x,y,g,primerMitad[i])
                y=y+1
            y=y+1
            for i in range(len(segundoMitad)):
                tab_ejecucion.chequeroDuplica(x,y,i)
                tab_ejecucion.EscribirEnTablero(x,y,g,segundoMitad[i])
                y=y+1

    def PuntosPalabra_Maquina(self,palabra,tab):
        '''Calculo los puntos de la palabra ingresada'''
        pts=0
        archivo= open("Almacenamiento/puntaje_letras.json","r")
        dicc1=json.load(archivo)
        archivo.close()
        lista=tab.get_duplica()
        lista1=tab.get_triplica()
        for i in palabra:
            num=dicc1[i]
            num=num[0]
            if i in lista:
                num=num*2
            if i in lista1:
                num=num*3
            pts+=num
        return pts

    def Actualizar_Puntaje(self,AV,palabra,tab):
        '''Actuliza el puntaje total de la maquina'''
        puntos_maquina = self.PuntosPalabra_Maquina(palabra,tab)
        if puntos_maquina==0:
            self._cant_paso+=1
        palabra_y_puntaje_maquina = palabra + ' ' +str(puntos_maquina)
        self._puntaje_total = self._puntaje_total + puntos_maquina
        return palabra_y_puntaje_maquina

    def evaluar_donde(self,tab_ejecucion,g,l):
        '''Evalua donde es posible colocar la palabra'''
        box_x = -1
        box_y = -1
        okk=True
        ok = False
        id_usados = []
        booleanito=True
        pos=-1
        pal=self._palabra
        if l != "":
            for x in range(15):
                for y in range(15):
                    if l.upper()== tab_ejecucion.get_coordenadas_en_tablero(x,y).upper():
                        box_x = x
                        box_y = y
            for i in range(len(pal)):
                if l == pal[i]:
                    pos = i
            if box_x!=-1:
                primerMitad=pal[0:pos]
                segundoMitad=pal[pos+1:]
                if self.calculo_lugar_en_x_cruzada(tab_ejecucion,box_x,box_y,primerMitad,segundoMitad):
                    self.poner_palabra_x_cruzada(tab_ejecucion,primerMitad,segundoMitad,box_x,box_y,g)
                    booleanito=False
                    okk=False
                if self.calculo_lugar_en_y_cruzada(tab_ejecucion,box_x,box_y,primerMitad,segundoMitad) and okk :
                    self.poner_palabra_y_cruzada(tab_ejecucion,primerMitad,segundoMitad,box_x,box_y,g)
                    booleanito=False
                if booleanito:
                    self.lugar_random(tab_ejecucion,g)
            else :
                self.lugar_random(tab_ejecucion,g)
        else:
             self.lugar_random(tab_ejecucion,g)

    def buscarEn(self,nombre):
        '''Busca  en el conjunto dependiendo del nivel elegido'''
        for palabra in nombre:
            ya_usadas=[]
            atril_a_usar=self.get_atril().copy()
            sirve=True
            palabra=palabra.upper()
            for i in palabra:
                if i in atril_a_usar:
                    ya_usadas.append(i)
                    atril_a_usar.remove(i)
                else:
                    sirve=False
            if sirve and len(palabra)>1:
                nuevo = self.BuscarEnLaBolsa(len(ya_usadas))
                for letra in self.get_atril():
                    for i in ya_usadas:
                        if letra == i:
                            ya_usadas.remove(i)
                            self.set_remove_atril(letra)
                for i in nuevo:
                    self.set_agregar_atril(i)
                break
        if sirve:
            return (palabra,True,ya_usadas)
        else:
            return("",False,[])

    def EncontrarPalabra(self,nivel,TE):
        '''Manda a buscar una palabra en el conjunto seleccionado dependiendo del nivel'''
        listaDeLetrasEnElTablero=TE.get_lista_de_letras_en_tablero()
        tupla=("",False,[])
        letra=""
        if nivel=="Facil":
            tupla=self.buscarEn(lexicon)
            if not tupla[1]:
                tupla=self.buscarEn(spelling)
        if nivel=="Medio":
            tupla=self.buscarEn(verbs)
            if not tupla[1]:
                archivo=open ("Almacenamiento/adjetivos.json","r")
                adjetivos=json.load(archivo)
                archivo.close()
                tupla=self.buscarEn(adjetivos)
        if nivel=="VB":
            tupla=self.buscarEn(verbs)
        if nivel=="JJ":
            archivo=open ("Almacenamiento/adjetivos.json","r")
            adjetivos=json.load(archivo)
            archivo.close()
            tupla=self.buscarEn(adjetivos)
        if tupla[1]:
            for k in tupla[0]:
                if k in listaDeLetrasEnElTablero:
                    letra=k
                    break
        self._palabra=tupla[0]
        super().set_letra(letra)
        palabra=tupla[0]
        return letra,palabra


import requests
from icecream import ic
import datetime

# response = requests.request("GET", "http://api.open-notify.org/iss-now.json")
response = requests.get("http://api.open-notify.org/iss-now.json")
response.raise_for_status()

result = response.json()["iss_position"]
print(f'https://www.latlong.net/c/?lat={result["latitude"]}&long={result["longitude"]}')

parameters = {
    "lat": result['latitude'],
    "lng": result['longitude'],
    "formatted": 0
}

response = requests.get(url="https://api.sunrise-sunset.org/json", params=parameters)
response.raise_for_status()
result = response.json()["results"]
ic(result["sunset"])


from button import *
import pyperclip

class Menu:
    def __init__(self):
        self.run = 1
        self.user_text = ""
        self.text_box_active = False
        self.submit = False
        self.movie = []
        self.alert = False

    def draw(self, font, label_font):

        submit = Button(((WIDTH - 120)/2, (HEIGHT*5/8 -60/2),120,60),black,"Find movies",24,screen,border_color=red,border_width=4)

        label = label_font.render("Movie recommender", 1, black)
        screen.blit(label, (WIDTH / 2 - label.get_width() / 2, 250))

        # Name display

        name_lst = ["สร้างสรรค์โดย \"หนังไก่ไม่มีหนังนี้ดีกว่า\"",
                    "   65010322 ณัฐริกา เจ็กสูงเนิน",
                    "   65010329 ณัฐวุฒิ  ฉายอ่วม",
                    "   65010373 ทักษ์ดนัย  ดีเผือก",
                    "   65010492 ธีรวัชร์ แสงอุไร"]
        for pos,name in enumerate(name_lst):
            name_label = font.render(name, 1, black)
            screen.blit(name_label, (10, HEIGHT-200 + (pos*(40))))

        instruction_font = pygame.font.Font("font/THSarabunNew.ttf",28)
        instruction = instruction_font.render("Enter movie(s) name (if movies >= 2 , enter \',\' between 2 movies)", 1, (6, 97, 0))
        screen.blit(instruction, (140, 500))

        # input
        inp_box = pygame.Rect(120, (HEIGHT - 120)/2, 560, 40 )

        # color_passive store color(chartreuse4) which is
        # color of input box.
        color_active = pygame.Color('lightskyblue3')

        color_passive = pygame.Color('chartreuse4')
        pygame.scrap.init()
        pygame.scrap.set_mode(pygame.SCRAP_CLIPBOARD)
        submit.run()

        # Time delay for repeat when a key is held down
        REPEAT_DELAY = 500  # milliseconds
        REPEAT_RATE = 50    # milliseconds
        # Initialize variables for handling key repetition
        key_repeat_time = 0
        key_repeat_key = None

        #event polling
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
            if event.type == pygame.MOUSEBUTTONDOWN:
                if inp_box.collidepoint(event.pos):
                    self.text_box_active = True
                else:
                    self.text_box_active = False

            if event.type == pygame.KEYDOWN:
                if (event.key == pygame.K_v) and (event.mod & pygame.KMOD_CTRL):
                    self.user_text += pyperclip.paste()
                if event.key == pygame.K_RETURN:
                    submit.clicked = True
                # Check for backspace
                if event.key == pygame.K_ESCAPE:
                    self.user_text = ''
                elif event.key == pygame.K_BACKSPACE:
                    if pygame.time.get_ticks() - key_repeat_time >= REPEAT_DELAY:
                        key_repeat_key = pygame.K_BACKSPACE
                        key_repeat_time = pygame.time.get_ticks()
                        self.user_text = self.user_text[:-1]
                    else:
                        self.user_text = self.user_text[:-1]
                else:
                    self.user_text += event.unicode

            if event.type == pygame.KEYUP:
                if event.key == pygame.K_BACKSPACE:
                    key_repeat_key = None

        # Check for key repetition
        if key_repeat_key == pygame.K_BACKSPACE and pygame.time.get_ticks() - key_repeat_time >= REPEAT_RATE:
            if self.user_text:  # Check if there's text to delete
                self.user_text = self.user_text[:-1]
            key_repeat_time = pygame.time.get_ticks()

        if self.text_box_active:
            color = color_active
        else:
            color = color_passive
        pygame.draw.rect(screen, color, inp_box)
        #text input render
        text_inp_font = pygame.font.Font("font/THSarabunNew.ttf",26)
        text_inp = text_inp_font.render(self.user_text, True, black)
        screen.blit(text_inp, (inp_box.x+5, inp_box.y+8))

        # set width of textfield so that text cannot get
        # outside of user's text input
        inp_box.w = max(100, text_inp.get_width()+10)

        if submit.clicked:
            if any(c.isalnum() for c in self.user_text) and not all(c.isspace() for c in self.user_text):
                print("SUBMIT!!")
                self.movie = [i.strip().lower() for i in self.user_text.split(",") ]
                print(self.movie)
                self.submit = True
            else:
                self.alert =True

        if self.alert:
            alert = font.render("Please Enter the name of movie(s)", 1, red)
            screen.blit(alert, (300, 700))
        # two_p.run()
        # three_p.run()
        # if one_p.clicked:
        #     # click_sound.play()
        #     # self.game_state = 1
        #     pass
        # if two_p.clicked :
        #     # self.score_state = 1
        #     # click_sound.play()
        #     pass
        # if three_p.clicked:
        #     # click_sound.play()
        #     # self.quit = 1
        #     pass