from qgis.PyQt.QtCore import (QAbstractTableModel, QDataStream, QFile, QFileInfo, QIODevice, QObject, QSize, Qt)
from qgis.PyQt.QtGui import (QColor, QIcon, QKeySequence)
from qgis.PyQt.QtWidgets import (QAction, QApplication, QDesktopWidget, QFileDialog, QMenu, QMessageBox, QProgressBar, QPushButton, QTextEdit, QTreeWidgetItem)
from qgis.core import (Qgis,QgsApplication, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsGeometry, QgsPointXY, QgsProject)
from qgis.gui import (QgsMapCanvasItem, QgsProjectionSelectionDialog, QgsVertexMarker)

from math import (cos, floor, isinf, log, pi, pow, sin, sqrt)

try : from matplotlib import dateutil
except : from matplotlib.dates import dateutil

from os.path import (basename, dirname, exists, isfile, join, splitext)
from os import (startfile, walk)
from sys import path as sysPath
from xml.dom.minidom import parseString
from xml.etree import ElementTree as etree


try:
    from osgeo.gdal import Open as gdalopen
    from osgeo.gdal import GetDriver as gdal_GetDriver
    from osgeo.gdal import GetDriverCount as gdal_GetDriverCount
    from osgeo.gdal import GetDriverByName as gdal_GetDriverByName
    from osgeo.gdal import (DMD_LONGNAME, DMD_MIMETYPE, DMD_EXTENSION)
    from osgeo.ogr import GetDriver as ogr_GetDriver
    from osgeo.ogr import GetDriverCount as ogr_GetDriverCount
except:
    from gdal import Open as gdalopen
    from gdal import GetDriver as gdal_GetDriver
    from gdal import GetDriverCount as gdal_GetDriverCount
    from gdal import GetDriverByName as gdal_GetDriverByName
    from gdal import (DMD_LONGNAME, DMD_MIMETYPE, DMD_EXTENSION)
    from ogr import GetDriver as ogr_GetDriver
    from ogr import GetDriverCount as ogr_GetDriverCount


import pytz
import subprocess

from datetime import datetime as thedatetime
from functools import reduce
from threading import Thread, enumerate    
from time import sleep
from urllib.parse import unquote
from urllib.request import (Request, build_opener, urlopen, URLError, HTTPError)
from webbrowser import open as webbrowseropen

UPDATE_INTERVAL = 0.01

import sys
import numpy as np
import statistics as stats
import processing
from qgis.core import (QgsProcessingFeedback, QgsProcessingFeatureSourceDefinition)
#NEW 4.0.1
from PIL import ImageColor

def period(delta, pattern):
    d = {'d': delta.days}
    d['h'], rem = divmod(delta.seconds, 3600)
    d['m'], d['s'] = divmod(rem, 60)
    return pattern.format(**d)

#NEW 4.15
def extrudeDiff(baseValue, value, meanValue):
    ratioValue = value / baseValue
    ratioMeanValue = meanValue / baseValue
    coeff = 100 / meanValue
    
    if ratioValue < ratioMeanValue : value = (baseValue - (30 *coeff * (value / meanValue)))
    else : value = (baseValue - (100 *coeff * (value / meanValue)))
    
    return round(value, 2)


#NEW 4.0.1
def getRGB(hexaColor): return ImageColor.getcolor(hexaColor, "RGB")

#NEW 4.0.0
from datetime import timedelta
from dateutil.tz import *
import subprocess


#NEW 4.1.5
def getLayerByName(layerName):
    layers = QgsProject.instance().mapLayersByName(layerName)
    layer = layers[0] if layers != [] else None
    return layer

def getContextMenuSelection(widget, ctrl, self, integral):
    if widget != None :
        contextMenu = QMenu(widget)  

        actions = {"selectAll" : ("selectall.png", "Sélectionner tous les membres", "" , widget.selAllItems, True, "Ctrl+A"),
                   "unSelectAll" : ("unselectall.png", "Aucune membre sélectionné", "" , widget.unSelAllItems, True, "Ctrl+Z"),
                   "invertSelected" : ("invertselection.png", "Inverser la sélection", "" , widget.invertSelItems, True, "Ctrl+I"),
                   }
    else :
        contextMenu = QMenu()
        if integral :
            actions = {"selectAll" : ("selectall.png", "Sélectionner toutes les stations", "" , self.selAllItems, True, "Ctrl+A"),
                       "unSelectAll" : ("unselectall.png", "Aucune station sélectionnée", "" , self.unSelAllItems, True, "Ctrl+Z"),
                       "invertSelected" : ("invertselection.png", "Inverser la sélection", "" , self.invertSelItems, True, "Ctrl+I"),
                       "~0" : (),
                       "reloadContext" : ("reload.png", "Recharger la liste des stations", "" , self.updateListStations, True, "Ctrl+R")
                       }             
        else :
           actions = {"selectAll" : ("selectall.png", "Sélectionner tous les membres", "" , self.selAllItems, True, "Ctrl+A"),
                      "unSelectAll" : ("unselectall.png", "Aucune membre sélectionné", "" , self.unSelAllItems, True, "Ctrl+Z"),
                      "invertSelected" : ("invertselection.png", "Inverser la sélection", "" , self.invertSelItems, True, "Ctrl+I"),
                      }    

    for key in actions :
      if key.startswith("~") : contextMenu.addSeparator()
      else :
          icon = actions[key][0]
          icon = getThemeIcon(icon)

          action = QAction(QIcon(icon), actions[key][1], widget) if widget != None else QAction(QIcon(icon), actions[key][1], ctrl)
          
          if actions[key][2] != "" : action.setShortcut(QKeySequence(actions[key][2]))
          action.setEnabled(actions[key][4])
          action.setObjectName(key)
          action.setShortcut(QKeySequence(actions[key][5]))
          contextMenu.addAction(action)
          action.triggered.connect(actions[key][3])

          setattr(widget, key, action) if widget != None else setattr(self, key, action)

    return contextMenu

def selAllItems(comBo):
    model = comBo.model()
    for index in range(0, model.rowCount()):
        item = model.item(index)
        item.setData(Qt.Checked, Qt.CheckStateRole) 
      
def unSelAllItems(comBo):
    model = comBo.model()
    for index in range(0, model.rowCount()):
        item = model.item(index)
        item.setData(Qt.Unchecked, Qt.CheckStateRole) 

def invertSelItems(comBo):
    model = comBo.model()
    for index in range(0, model.rowCount()):
        item = model.item(index)
        if  item.checkState() == Qt.Unchecked : item.setData(Qt.Checked, Qt.CheckStateRole)
        else : item.setData(Qt.Unchecked, Qt.CheckStateRole)

#NEW 4.1.5 - END BLOC
    

def rbcircle(rb, center, distance, N, getPoints, getCircle):
    r = distance 
    g = None
    if getCircle : rb.reset()    
    coords = {}
        
    for itheta in range(N+1):
        theta = itheta*(2.0 * pi/N)
        point = QgsPointXY(center.x()+r*cos(theta),center.y()+r*sin(theta))
        
        if getCircle : rb.addPoint(point)
        if getPoints : coords[theta] = point
        
    if getCircle :
        coordsCircle = []
        [coordsCircle.append(rb.getPoint(0,ki)) for ki in range(rb.numberOfVertices())]
        g = QgsGeometry().fromPolygonXY([coordsCircle])
        rb.reset()
    
    return coords, g

def getResolutionScreen() :
    sizeObject = QDesktopWidget().screenGeometry(-1)
    return sizeObject.width(), sizeObject.height()

def getFactorsWH():
    width, height = getResolutionScreen()
    refWidth, refHeight = 1920, 1200
    factorW, factorH = (refWidth / width), (refHeight / height)
    return factorW, factorH 

def setImgPath(pattern, img):
    img = pattern % img
    img = img.replace("\\", "/")
    return img

#FUNCTION TO SET VIGILANCE
def getNivVigilance(dicoSeuils, lastMesure, typeInfos, unitInfos):
    bgColor, color, iAlarm, keySeuil = "#DEDEDF", "#000000", -1, -1  #GRIS - PAS DE SEUIL / PAS DE NIVEAU VIGILANCE
    racPath = "file:///%s/data/imgs/%s"
    commentStatut = "Pas de seuils pour la station."
    sufixImg = ""

    i = 1
    
    for key in dicoSeuils.keys() :        
        sKey = None
        if (key == "S%s" % i) :
            sKey = "S%s" % i
            valueSeuil = dicoSeuils[sKey]["valueSeuil"]
        if (key == "S%s%s" % (typeInfos, i)) :
            sKey = "S%s%s" % (typeInfos, i)
            valueSeuil = dicoSeuils[sKey]
            
        if sKey :    
           if valueSeuil != -1 :
              if lastMesure > valueSeuil :
                 if i == 1 : bgColor, commentStatut, iAlarm, keySeuil, = "#D0FB05", "Seuil de vigilance RDI [%s %s] dépassé." % (valueSeuil, unitInfos), i, sKey #VIGILANCE JAUNE
                 elif i == 2 : bgColor, color, commentStatut, iAlarm, keySeuil = "#C9001F", "#FFFFFF", "1er seuil de débordement [%s %s] dépassé." % (valueSeuil, unitInfos), i , sKey #VIGILANCE ROUGE FONCE
                 else : bgColor, color, commentStatut, iAlarm, keySeuil = "#000000", "#FFFFFF", "Seuil critique [%s %s] dépassé." % (valueSeuil, unitInfos), i , sKey #VIGILANCE NOIRE
              else :
                 if keySeuil == -1 : bgColor, color, commentStatut, iAlarm = "#27D961", "#FFFFFF", "Pas de dépassement en cours pour la station.", 0 #VIGILANCE VERTE
                 else :
                    if lastMesure * 1.05 >= valueSeuil :
                       if iAlarm == 1 : bgColor, commentStatut, iAlarm, keySeuil, sufixImg = "#FFA500 ", "Proche du 1er seuil de débordement [%s %s]." % (valueSeuil, unitInfos), iAlarm, sKey, ".1" #VIGILANCE ORANGE
                       elif iAlarm == 2 : bgColor, color, commentStatut, iAlarm, keySeuil, sufixImg = "#DF4E00", "#FFFFFF", "Proche du seuil critique [%s %s]." % (valueSeuil, unitInfos), iAlarm , sKey, ".1" #VIGILANCE ROUGE MOYEN
                       else : bgColor, color, commentStatut, iAlarm, keySeuil = "#000000", "#FFFFFF", "Seuil de débordement [%s %s] dépassé." % (valueSeuil, unitInfos), iAlarm , sKey #VIGILANCE NOIRE                        
                       break
           i+= 1
           
    imgStatut = setImgPath(racPath, (dirname(__file__), "puce_%s%s.png" % (iAlarm, sufixImg)))
    
    return bgColor, color, commentStatut, int(keySeuil[-1]) if keySeuil != -1 else keySeuil, imgStatut, keySeuil        

#EUROPEAN AQI
def getQualityAir(aqi):
    if aqi < 10 : return "La qualité de l'air est très bonne."
    elif aqi >= 10 and aqi < 20 : return "La qualité de l'air est bonne."
    elif aqi >= 20 and aqi < 25 : return "La qualité de l'air est moyenne."
    elif aqi >= 25 and aqi < 50 : return "La qualité de l'air est mauvaise."
    elif aqi >= 50 and aqi < 75 : return "La qualité de l'air est très mauvaise."
    else : return "La qualité de l'air est extrêmement mauvaise."

#SOURCE WMO Weather Codes
def getMeteoIcon(weather_code, isDay):
    
    dictMeteo = {0 :  {"context" : "Ciel ensoleillé"},
                 1 :  {"context" : "Partiellement nuageux"},
                 2 :  {"context" : "localement nuageux"}, #"Ciel ensoleillé avec quelques nuages"
                 3 :  {"context" : "Couvert"},
                 45 : {"context" : "Brouillard"},
                 48 : {"context" : "Brouillard givrand"},
                 51 : {"context" : "Bruine légère"},
                 53 : {"context" : "Bruine modérée"},
                 55 : {"context" : "Bruine forte"},
                 56 : {"context" : "Bruine verglaçante légère à modérée"},
                 57 : {"context" : "Bruine verglaçante forte"},
                 61 : {"context" : "Pluie légère"},
                 63 : {"context" : "Pluie modérée"},
                 65 : {"context" : "Pluie forte"},
                 66 : {"context" : "Pluie verglaçante légère"},
                 67 : {"context" : "Pluie verglaçante forte"},
                 71 : {"context" : "Neige légère"},
                 73 : {"context" : "Neige modérée"},
                 75 : {"context" : "Neige forte"},
                 77 : {"context" : "Neige"}, #grêle
                 80 : {"context" : "Averses légères"},
                 81 : {"context" : "Averses modérées"},
                 82 : {"context" : "Averses fortes"},
                 85 : {"context" : "Averses de neige lègères à modérées"},
                 86 : {"context" : "Averses de neige fortes"},
                 95 : {"context" : "Orages, faibles à modérés, sans grêle"},
                 96 : {"context" : "Orages d'intensité faible à modérée, avec grêle"},
                 99 : {"context" : "Orages violents avec grêle"},
                 }

    
    imgMeteo = "%s.png" % (weather_code if isDay else 100+weather_code)
    lblMeteo = dictMeteo[weather_code]["context"] if weather_code in dictMeteo.keys() else "Statut inconnu"
    
    imgMeteo = getThemeIcon(imgMeteo)
    if not exists(imgMeteo): imgMeteo = getThemeIcon("unknowmeteo.png")

    return imgMeteo, lblMeteo
    

def getCloudCover(value):
    #source : https://aerozach.fr/octas-mesure-couverture-nuageuse/
    #Adaptation des octas mesures
    if value <= 10 : return "Ciel dégagé"
    elif value > 10 and value <= 37 : return "Quelques nuages éparses"
    elif value > 37 and value < 50 : return "Quelques nuages"
    elif value > 50 and value < 87 : return "Ciel nuageux"
    else : return "Ciel couvert"

def getRainForce(value) :
    #source : https://meteo45.com/pluie.html
    if value == 0 : return "Pas de pluie"
    elif value > 0 and value < 0.5 : return "Bruine"
    elif value >= 0.5 and value < 1.5 : return "Pluie légère"
    elif value >= 1.5 and value < 3.5 : return "Pluie modérée"
    else : return "Fortes averses ou orages"
    

def getWindForce(value):
    #source : https://www.sharemysea.fr/a-decouvrir/beaufort-lechelle-de-mesure-vent/
    if value < 1 : return "Calme"
    elif value >= 1 and value < 6 : return "Très légère brise"
    elif value >= 6 and value < 12 : return "Légère brise"
    elif value >= 12 and value < 20 : return "Petite brise"
    elif value >= 20 and value < 31 : return "Jolie brise"
    elif value >= 31 and value < 40 : return "Bonne brise"
    elif value >= 40 and value < 51 : return "Vent frais"
    elif value >= 51 and value < 62 : return "Grand frais"
    elif value >= 62 and value < 75 : return "Coup de vent"
    elif value >= 75 and value < 89 : return "Fort coup de vent"
    elif value >= 89 and value < 103 : return "Tempête"
    elif value >= 103 and value < 118 : return "Violente tempête"
    else : return "Ouragan"


def getWindOrientation(value):
    direction = ""
    dicOrientation = { 0 : "Nord", 22.5 : "Nord Nord-Est", 45 : "Nord-Est", 67.5 : "Est Nord-Est",
                       90 : "Est", 112.5 : "Est Sud-Est", 135 : "Sud-Est", 157.5 : "Sud Sud-Est",
                       180 : "Sud", 202.5 : "Sud Sud-Ouest", 225 : "Sud-Ouest", 247.5 : "Ouest Sud-Ouest",
                       270 : "Ouest", 292.5 : "Ouest Nord-Ouest", 315 : "Nord-Ouest", 337.5 : "Nord Nord-Ouest"
                       }

    sKey, eKey = 0, None
    for key in dicOrientation.keys():
        if value > key : sKey = key
        else : eKey = key ; break
    if not eKey : eKey = 337.5

    diffEKey = abs(value - eKey)
    diffSKey = abs(value - sKey)
    direction = dicOrientation[eKey] if diffEKey <= diffSKey else dicOrientation[sKey]    
    
    return direction


def getTransformer(sourceEpsgCode, targetEpsgCode):
    transformer = QgsCoordinateTransform()
    transformer.setSourceCrs(QgsCoordinateReferenceSystem(sourceEpsgCode))
    transformer.setDestinationCrs(QgsCoordinateReferenceSystem(targetEpsgCode))
    return transformer

####Fonction de lissage
def lissage(Lx,Ly,p):
    '''Fonction qui débruite une courbe par une moyenne glissante
    sur 2P+1 points'''
    Lxout=[]
    Lyout=[]
    Lxout = Lx[p: -p]
    for index in range(p, len(Ly)-p):
        average = np.mean(Ly[index-p : index+p+1])
        Lyout.append(round(average, 2))
    return Lxout,Lyout
  

def getAltiPoint(terrainLayer, point, condCrs, transformCrs):
    if not condCrs : point = transformCrs.transform(point, QgsCoordinateTransform.ForwardTransform)
    val, res = terrainLayer.dataProvider().sample(point, 1)
    if res : return val 
    else : return 0

#NEW 4.1.0
def playAlarm(command, startTimeChrono, deltaChrono):
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    output = "Fin du signal d'alarme."
    #while ((thedatetime.now()-startTimeChrono).total_seconds() * 1000) < deltaChrono : process.wait()
    exitCode = process.returncode
    if (exitCode == 0): return output
    else:
        try : raise Exception(command, exitCode, output)
        except : return


def findExec(root, execFile):
    pathExec = None
    for folder, dirs, files in walk(root):
        for fileName in files:
            if fileName.find("vlc.exe") != -1 : 
               name = "%s" % (basename(fileName))
               extension = getExtension(fileName)
               pathExec = "%s\\%s" % (folder, fileName)
               break
    return r"%s" % pathExec.replace("\\", "/")


def testSeuilVisibility(idStation, valueSeuil, maxMesures):
    if maxMesures > valueSeuil :  return "false"
    elif (maxMesures * 100 / valueSeuil) >= 70 : return "false"
    else : return "true"      
    

def getQuartiles(resultat_obs):
    data = list(resultat_obs)
    data.sort()
    half_list = int(len(data)//2)
    q3 = round(stats.median(data[-half_list:]), 3)
    q1 = round(stats.median(data[:half_list]), 3)
    median = round(stats.median(data), 3)
    return q1, median, q3

#NEW 4.0.1
def getDureeObs(dDatefin, dDateDeb):
    dureeObs = period(dDatefin-dDateDeb, " {d} jour(s), {h} heure(s), {m} minute(s) et {s} seconde(s)")
    if dureeObs == " 0 jour(s), 0 heure(s), 0 minute(s) et 0 seconde(s)" : dureeObs = "Observation ponctuelle."
    dureeObs = dureeObs.replace(" 0 jour(s),", "").replace(" 0 heure(s),", "").replace(" 0 minute(s) et", "").replace(" et 0 seconde(s)", "").replace(" 0 seconde(s)", "")
    if dureeObs[-1] == "," : dureeObs = dureeObs[0:len(dureeObs)-1]
    return dureeObs
    
#NEW 4.0.1
def seriesUpSeuils(dates, data, delta, valueSeuil, seuil, commentSeuil, unitInfos):
    deltaScrutation = timedelta(minutes=delta)
    dictData = {k: v for k, v in zip(dates, data)}

    nbDebordement = 0
    html = ""
    firstDate = False
    counterDeb = 0
    periodes = {}

    lblDebordement = "débordement" if commentSeuil != "Seuil RDI" else "dépassement"

    for key in dictData.keys() :
        if dictData[key] > valueSeuil :
           if not firstDate : 
              datefin = key.strftime("%d/%m/%Y à %Hh%M"); firstDate = True; dDateFin = key; counterDeb = 1
           else : counterDeb+= 1   
        else :
           if firstDate :
              dDateDeb = key 
              dureeObs = getDureeObs(dDateFin, dDateDeb)
              datedeb = key.strftime("%d/%m/%Y à %Hh%M")

              periodes["P%s" % nbDebordement] = {"datefin" : datefin, "datedeb" : datedeb, "counterDeb" : counterDeb, "dureeObs" : dureeObs} 

              nbDebordement+= 1
              counterDeb = 0
              firstDate = False

    if firstDate :
       dDateDeb = key
       counterDeb+= 1 
       dureeObs = getDureeObs(dDateFin, dDateDeb)
       datedeb = key.strftime("%d/%m/%Y à %Hh%M")
       periodes["P%s" % nbDebordement] = {"datefin" : datefin, "datedeb" : datedeb, "counterDeb" : counterDeb, "dureeObs" : dureeObs}
       nbDebordement+= 1
        
    if nbDebordement > 0 :

        html = "<center><details class='normal'><summary class='normal'><small>%s période%s de %s [%s : %s %s] ...</summary><br>\n" % (nbDebordement, "s" if nbDebordement > 1 else "", lblDebordement, commentSeuil, valueSeuil, unitInfos)
        html+= "<table style='width: 100%;font-size: 12px;'>\n"
        html+= "<tr style='background-color: #1C6EA4; box-shadow: 0px 5px 14px 0px #0e1635; color: white;'><td style='text-align: right; margin-right: 2px;'>Période :</td><td>Date du début</td><td>Date de fin</td><td>Nombre d'observation(s)</td><td>Durée</td></tr>\n"

        borne = len(periodes.keys())-1
        index = 0
        for i in range(borne, -1, -1):
            key = "P%s" % i
            datedeb = periodes[key]["datedeb"]
            datefin = periodes[key]["datefin"]
            counterDeb = periodes[key]["counterDeb"]
            dureeObs = periodes[key]["dureeObs"]

            html+= "<tr style='background-color: white; box-shadow: 0px 5px 14px 0px #0e1635; color: black;'>\n"
            html+= "<td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>P%s :</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td>\n" % (index, datedeb, datefin, counterDeb, dureeObs)
            html+= "</tr>\n"
            index+= 1

        html+= "</table></small></details></center>\n"

    else : html = "Aucun débordement [Premier seuil : %s %s %s] ...\n" % (commentSeuil, valueSeuil, unitInfos)

    return html, nbDebordement 


def getExtension(fileName):
    textension = splitext(fileName)
    extension = textension[len(textension)-1].lower()
    return extension

def convertUnits(value, mesurement, unite, precision):

    units = { "volume" :
              { "coeff" : [10**-3, 10**-2, 10**-1, 10**0, 10**1, 10**2, 10**3, 10**-3, 10**0, 10**3, 10**6, 10**9],
                "symbol" : ["mL", "cL", "dL", "L", "daL", "hL", "kL", "cm3", "dm3", "m3", "dam3", "hm3", "km3"] },
              "volumeV" :
              { "coeff" : [10**-3, 10**-2, 10**-1, 10**0, 10**1, 10**2, 10**3, 10**-3, 10**0, 10**3, 10**6, 10**9],
                "symbol" : ["mL/h", "cL/h", "dL/h", "L/h", "daL/h", "hL/h", "kL/h", "cm3/h", "dm3/h", "m3/h", "dam3/h", "hm3/h", "km3/h"] },              
              "masse" : 
              { "coeff" : [10**-3, 10**-2, 10**-1, 10**0, 10**1, 10**2, 10**3, 10**5, 10**6],
                "symbol" : ["mg", "cg", "dg", "g", "dag", "hg", "kg", "q", "t"] },
              "hauteur" : 
              { "coeff" : [10**-9,10**-6,10**-3,10**-2,10**-1,10**0,10**1,10**2,10**3],
                "symbol" : ["nm","micrometre","mm","cm","dm","m","dam","hm","km"] },
              "surface" : 
              { "coeff" : [10**-6, 10**-4, 10**-2, 10**0, 10**2, 10**4, 10**6],
                "symbol" : ["mm2", "cm2", "dm2", "m2", "dam2", "hm2", "km2"] }
              }

    signe = "-" if value < 0.0 else "+"

    value = abs(value)
    
    if dicoHasKey(units, mesurement):
       mesures = units[mesurement] 
       if dicoHasKey(mesures["symbol"], unite):
           index = mesures["symbol"].index(unite)
           base = mesures["coeff"][index]
           if not (value // mesures["coeff"][index]) > base :
              start, stop, step =  index, 0, -1
           else :  
              start, stop, step =  index, len(mesures["coeff"]), 1

           for i in range(start, stop, step):
                  newValue = value / mesures["coeff"][i]
                  if step == -1 :
                      if newValue // mesures["coeff"][index] > base :
                          index, value = i, newValue
                          break
                  else :    
                      if newValue // mesures["coeff"][index] in [0,1,2,3,4,5,6,7,8,9]: 
                          index, value = i, newValue
                          break
                          break

    return "%s %s" % (signe, round(value, precision)), mesures["symbol"][index]


def bufferPlus(input, distance, output, before_processing_reproject_to_epsg_number=None, cond=True):
    """
    try : data = QgsProcessingFeatureSourceDefinition('%s' % input, cond)
    except : data = input
    """
    params = {'INPUT': QgsProcessingFeatureSourceDefinition('%s' % input, cond),
              'DISTANCE': distance,
              'END_CAP_STYLE': 2 , # - 0: Round - 1: Flat - 2: Square
              'JOIN_STYLE' : 0, # - 0: Round - 1: Angle droit - 2: Oblique
              "SEGMENTS": 10, 
              'MITER_LIMIT' : 6, 
              'DISSOLVE': True,
              'OUTPUT': output}
    feedback = QgsProcessingFeedback()
    alg_name = 'native:buffer'
    result = processing.run(alg_name, params, feedback=feedback)
    return  result



def getMean(data, precision): return round(np.mean(data), precision)
def getHarmonicMean(data, precision): return round(stats.harmonic_mean(data), precision)
def getStDev(data, precision): return round(stats.pstdev(data), precision)

def openFile(filename: str) -> None:
    if not exists(filename) : QMessageBox.information(None, "RDI Gestion de Crises", "Fichier : %s\nAbsent du répertoire : %s." % (basename(filename), dirname(filename))); return
    if sys.platform == "win32" : startfile(filename)
    else:
        opener = "open" if sys.platform == "darwin" else "xdg-open"
        subprocess.call([opener, filename])

def isInfNumber(number): return isinf(number)


##### ======= FUNCTIONS DATE ======== ####
#NEW 4.0.0
def getLitteralDate(date, rac, shortFormat=False, withHours=False):

      lblDate = date

      if type(date) == str :  
          date_format = '%Y-%m-%dT%H:%M:%S%z' if date.find("T") > -1 else '%Y-%m-%d %H:%M:%S%z'
      else : date_format = '%Y-%m-%d %H:%M:%S%z'

      if type(date) == str : date_obj = thedatetime.strptime(date, date_format)
      else : date_obj = date  

      if shortFormat :
          lblDate = date_obj.strftime("%d/%m/%Y")
      else :  
          date = date_obj.strftime("%A %d-%B-%Y").split("-")
          lblDate = "%s%s%s %s %s" % (rac, " " if rac != "" else "", date[0], date[1], date[2])

      if withHours : lblDate+= date_obj.strftime(" à %Hh%M")

      return lblDate
    

def timeZones():
    return pytz.all_timezones

def utc_to_local(utc_dt, tz):
    local_tz = pytz.timezone(tz)
    local_dt = utc_dt.replace(tzinfo=pytz.utc).astimezone(local_tz)
    return local_tz.normalize(local_dt) 

##### ======= FUNCTIONS DATE GRAPH MATPLOTLIB ======== ####
def jsonToMatplotlibDate(jsonDate, tz):
    jsonDate = thedatetime.fromisoformat(jsonDate[:-1])
    jsonDate = str(utc_to_local(jsonDate, tz))
    jsonDate.replace("-","").replace(".","").replace("Z", "")
    return dateutil.parser.parse(jsonDate)

"""
#REMEMBER : ORDINAL POSITIONS
year = jsonDate[:4]
month = jsonDate[5:7]
day = jsonDate[8:10]
hours = jsonDate[11:13]
minutes = jsonDate[14:16]
seconds = jsonDate[17:19]
microseconds = jsonDate[20:23]
"""

   
def jsonDateToStrDate(jsonDate, prefixe, retLigne, tz):
    jsonDate = thedatetime.fromisoformat(jsonDate[:-1])
    jsonDate = str(utc_to_local(jsonDate, tz)) if tz else str(jsonDate)
    pos = jsonDate.find("+")
    jsonDate = jsonDate[:pos]
    jsonDate = jsonDate.replace("T", " ").replace("Z", "")
    
    try : jsonDate = thedatetime.strptime(jsonDate, '%Y-%m-%d %H:%M:%S.%f')
    except : jsonDate = thedatetime.strptime(jsonDate, '%Y-%m-%d %H:%M:%S')

    return "%s%s%s%s" % (prefixe, jsonDate.strftime("%d/%m/%Y"), "%sà%s" % (retLigne, retLigne), jsonDate.strftime("%Hh%M")) #-%Ss"))

##### ======= END FUNCTIONS DATE GRAPH MATPLOTLIB ======== ####

def sortDic(dic):
    keys = dic.keys()
    try : keys.sort()
    except :
        try : keys = sorted(keys)
        except : pass
    return keys

def getComboLayer(self, comboBox, inLegend):
   targetLayer = None
   if comboBox.currentIndex() != -1 :
       modelIndex = comboBox.model().index(comboBox.currentIndex(),0)
       layerId = comboBox.model().data(modelIndex, Qt.UserRole)
       root = QgsProject.instance().layerTreeRoot()
       if root != None :
               for layer in root.findLayers() :
                   if layer.layerId() == layerId: targetLayer = layer.layer(); break;
   return targetLayer

#NEW 4.0.0
def getGroupServices(iface, nameGroup):
    root = QgsProject(iface).instance().layerTreeRoot()
    group = root.findGroup(nameGroup)
    index = len(root.children())
    if not group :
       group = root.insertGroup(index, nameGroup); expanded = True; 
    return group 



##### ======= FUNCTIONS QTREEWIDGET ======== ####
def visitTreeElt(listItems, item, level, maxlevel):
  listItems.append([level, item.text(0), item.whatsThis(0), item.text(1)])
  for i in range(item.childCount()):
      if maxlevel < level+1 : maxlevel = level+1  
      maxlevel = visitTreeElt(listItems, item.child(i), level+1, maxlevel)
  return maxlevel    

def visitTreeList(tree) :
  #NEW 4.4.0  
  if not tree : return [], 0
  listItems = []
  maxlevel = 0
  for i in range(tree.topLevelItemCount()) :
      maxlevel = visitTreeElt(listItems, tree.topLevelItem(i), 0, maxlevel) 
  return listItems, maxlevel
    
def legendSymbolItemChecked(node, ruleKey):
    isCheckedElt = False
    if Qgis.QGIS_VERSION_INT <= 31299 : isCheckedElt = node.layer().renderer().legendSymbolItemChecked(ruleKey)
    else :
          try : isCheckedElt = node.layer().featureRenderer().legendSymbolItemChecked(ruleKey)
          except :
              try : isCheckedElt = node.layer().renderer().legendSymbolItemChecked(ruleKey) 
              except : pass
    return isCheckedElt

def conditionnalAddChild(treeWidget, parent, legendItem) :    
    if not parent : treeWidget.addTopLevelItem(legendItem) 
    else : parent.addChild(legendItem)

def createTreeWidgetItem(iconPath, icon, text):
    legendItem = QTreeWidgetItem()
    legendItem.setIcon(0, icon)
    legendItem.setText(0, text)
    legendItem.setWhatsThis(0, iconPath)
    return legendItem    
##### ======= END FUNCTIONS QTREEWIDGET ======== ####

def multiGetRequest(anim, ctrlanim, uris, timeout, useCase): 
    def alive_count(lst, i):
        try : anim.jumpToFrame(i)
        except : pass
        ctrlanim.repaint()
        alive = map(lambda x : 1 if x.isAlive() else 0, lst)
        return reduce(lambda a,b : a + b, alive)

    if useCase == 0 : threads = [ urlThread(uri[0], uri[1], uri[2], uri[3]) for uri in uris ]
    elif useCase == 1 : threads = [ urlThreadRequests(uri[0]) for uri in uris ]
   
    
    for thread in threads:
        thread.start()
        i = 0
    while alive_count(threads, i) > 0 and timeout > 0.0:
        try :
            i = i+1 if i < (anim.frameCount()-1) else 0
        except : ctrlanim.repaint()     
        timeout = timeout - UPDATE_INTERVAL
        sleep(UPDATE_INTERVAL)
    return [ (x.url, x.response) for x in threads ]


class urlThreadRequests(Thread):
    def __init__(self, url):
        super(urlThreadRequests, self).__init__()
        self.response = None
        self.timeOut = False
        self.url = url

    def run(self):
        hdr = {'User-Agent': 'OpenAnything/1.0 +http://somepage.org/', 'Connection': 'keep-alive'}
        try : 
            request = Request(self.url, headers=hdr)
            opener = build_opener()  
            self.response = opener.open(request).read()
        except : self.response = None
        

class urlThread(Thread):
    def __init__(self, url, method, headers, data=None):
        super(urlThread, self).__init__()
        self.response = None
        self.timeOut = False
        self.url = url
        self.method = method
        self.headers = headers
        self.data = data.encode("utf-8")
        self.ftpMode = url.startswith('ftp://')

    def run(self):
        if self.ftpMode :
            try:
                self.response = urlopen(url)
            except IOError as e: self.response, reason = None, e
            else:
                if self.response.read(): self.response, reason = self.response.status_code, self.response.reason
                else: self.response, reason = None, 'Empty Page'
        else :    
            try:
                self.response = requests.get(self.url, timeout=6, auth=None, headers=self.headers)
            except (requests.ConnectionError, requests.HTTPError, requests.Timeout) as e: self.response, reason = None, e
            else:
                if self.response.text: self.response, reason = self.response.status_code, self.response.reason
                else: self.response, reason = None, 'Empty Page'

               

def makeListFormatsExtensions(self, vector, raster, includeJoker):
    listFormatsExtensions, listFormatsExtensionsAll, listMime = [], [], []

    for iDriver in range(gdal_GetDriverCount()):
        poDriver = gdal_GetDriver(iDriver)
        poValue = poDriver.ShortName
        driver = gdal_GetDriverByName("%s" % (poValue))
        listFormatsExtensionsAll, listMime = setEltListFormatsExtensions(self, listFormatsExtensionsAll, listMime, driver, poValue)

   
    listFormatsExtensions = [value for value in listFormatsExtensionsAll ]
    listFormatsExtensions = sorted(listFormatsExtensions)
    filterListMime = 'Tous les types raster %s' % listMime
    filterListMime = filterListMime.replace("[", "(").replace("]", ")").replace(",", "").replace("'", "")
    
    if includeJoker : listFormatsExtensions.insert(0, filterListMime)
    return listFormatsExtensions

def setEltListFormatsExtensions(self, listFormatsExtensions, listMime, driver, poValue):
       if driver :
            poDriverMetadata = driver.GetMetadata()
            if dicoHasKey(poDriverMetadata, DMD_EXTENSION):
                if poDriverMetadata[DMD_EXTENSION]!= '' and not poDriverMetadata[DMD_EXTENSION] in  listFormatsExtensions :
                   listFormatsExtensions.append("%s (*.%s)" % (poDriverMetadata[DMD_LONGNAME], poDriverMetadata[DMD_EXTENSION]))
                   listMime.append("*.%s" % poDriverMetadata[DMD_EXTENSION])
                   
       else :
           if poValue != '' and not poValue in  listFormatsExtensions : listFormatsExtensions.append["*.%s" % poValue] 
       return listFormatsExtensions, listMime


def getListFormats(self):
    listFormats = {}

    for iDriver in range(gdal_GetDriverCount()):
        poDriver = gdal_GetDriver(iDriver)
        poValue = poDriver.ShortName
        driver = gdal_GetDriverByName("%s" % (poValue))
        listFormats = setEltListFormats(self, listFormats, driver, poValue)

    listitems = sortDic(listFormats)

    return listitems

def setEltListFormats(self, listFormats, driver, poValue):
       if driver :
            poDriverMetadata = driver.GetMetadata()
            if dicoHasKey(poDriverMetadata, DMD_LONGNAME):    
               poFormat = poDriverMetadata[DMD_LONGNAME]
               listFormats[poFormat]=poFormat
            elif dicoHasKey(poDriverMetadata, DMD_MIMETYPE):    
               poFormat = poDriverMetadata[DMD_MIMETYPE]
               listFormats[poFormat]= poFormat
            elif dicoHasKey(poDriverMetadata, DMD_EXTENSION):    
               poFormat = poDriverMetadata[DMD_EXTENSION] 
               listFormats[poFormat]= poFormat
       else : listFormats[poValue]=poValue
       return listFormats
   
def sortDic(dic):
    keys = dic.keys()
    try : keys.sort()
    except :
        try : keys = sorted(keys)
        except : pass
    return keys


def iff(a, b, c):
    if a: return b
    else: return c

#-------------------------------------------------
# FUNCTION TO FORCE / VERIFY EXTENSION
#-------------------------------------------------
def fileNameWithExtension(self, fileName, selectedFilter):
    extension = ""
    file = QFileInfo(fileName)
    extensionfind = ".%s" % (file.suffix())

    if selectedFilter.find("(")!=-1 :
        s = selectedFilter.find("*")+1
        e = selectedFilter.find(" ", s)
        e2 = selectedFilter.find(")", s)
        if (e == -1 and e2 < e) : e = e2 
        extension = selectedFilter[s:e].replace("*","")
    else :
        if selectedFilter.find("*.")!=-1 : extension = selectedFilter.replace("*","")
        
    if(file.suffix()=="") or (extensionfind.find(extension)==-1) : fileName+= "%s" % (extension)

    return fileName


def convertSize(size):
   size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
   i = 0
   if size > 0 :
      i = int(floor(log(size,1024)))
      p = pow(1024,i)
      s = round(size/p,2)
   else :
      s = 0
   return '%s %s' % (s,size_name[i])
#-------------------------------------------------
#FUNCTION TEST IF NUMBER TYPE INT OR SUPPORT FLOAT
#-------------------------------------------------     
def isFloatNumber(s):
    result = False
    try :
        if s.count(".") == 1 :
            if s.replace(".", "").isdigit(): result = True
    except : pass        
    return result    

#-----------------------------------------------
#FUNCTION TEST IF NUMBER TYPE INT OR SUPPORT INT
#-----------------------------------------------    
def isIntNumber(s):
    try : return not(set(s)-set(digits))
    except :
        try : return int(s)
        except : return False
    
def bissextile(an):
    if (an % 4)==0:
        if (an % 100)==0 and (an % 400) != 0 : return False
        else : return True
    else:
        return False

def verifdate(j,m,a):
    if a < 100:
        if a < 0: return False  
        if a < 50: a += 2000  
        else: a += 1900  
    if m < 1 or m > 12 or j < 1: return False
    if j > (31,29,31,30,31,30,31,31,30,31,30,31)[m-1]: return False
    if (not bissextile(a)) and m==2 and j>28: return False
    return True
 
def verifdatechaine(d, sep="/"):
    l = d.split(sep)
    if len(l) != 3: return False, ""
    for i in range(len(l)):
        if not isIntNumber(l[i]) : return False, ""
    a, m, j =  int(l[2]), int(l[1]), int(l[0])
    if verifdate(j, m, a) : return True, "%s/%s/%s" % (l[0], l[1], l[2])
    elif verifdate(a, m, j): return True, "%s/%s/%s" % (l[2], l[1], l[0])
    else : return False, ""

#================================================
#   FUNCTION FOR WINDOWS AND BUTTONS
#================================================
def windowIcon(qtWindow, icon):
    if not qtWindow : return
    icon = getThemeIcon(icon)
    if icon != "" : qtWindow.setWindowIcon(QIcon(icon)) 

def changeButtonIcon(self, obj, icon):
    icon = getThemeIcon(icon)
    obj.setIcon(QIcon(icon))
    
def getLogoFile(fileName):
    if QFile.exists(fileName): return fileName
    else : fileName = getThemeIcon(basename(fileName))
    return fileName

#NEW 4.1.5
def getThemeIcon(fileName):
    dirs = list(sysPath)
    dirs.insert(0, "%s%s" % (cleanPath(QgsApplication.pluginPath(), False),"rdi/icons/"))
    dirs.insert(0, "%s%s" % (cleanPath(QgsApplication.pluginPath(), False),"rdi/data/logos/"))
    dirs.insert(0, "%s%s" % (cleanPath(QgsApplication.pluginPath(), False),"rdi/data/boussoles/"))
    dirs.insert(0, "%s%s" % (cleanPath(QgsApplication.pluginPath(), False),"rdi/data/sounds/"))
    dirs.insert(0, "%s%s" % (cleanPath(dirname(__file__), False),"icons/"))
    dirs.insert(0, "%s%s" % (cleanPath(dirname(__file__), False),"icons/mime/"))

    for dir in dirs :
        myPath = cleanPath(dir, False)
        myIconPath = "%s%s" % (myPath, fileName)
        if QFile.exists(myIconPath): return myIconPath
        
    path = cleanPath(dirname(__file__), False)
    for nameDir, dirnames, filenames in walk(path):
        for nameFile in filenames:
            if basename(nameFile) == fileName : return join(nameDir, nameFile) 
        
    return ""

def setObjSizes(obj, w, h):
    if obj :
        obj.setMinimumSize(w, h)
        obj.setMaximumSize(w, h)

def cleanUri(uri):
    pos = uri.find("http")
    startUri = uri[:pos]      

    uri = uri.replace(startUri, "")
    uri = uri.replace("{", "%7B")
    uri = uri.replace("}", "%7D")
    uri = uri.replace("=", "%3D")
    uri = uri.replace("&", "%26")

    uri = startUri+uri
        
    return uri


#NEW 4.1.5    
def cleanPath(path, isFile):
    path = "%s" % (path)
    a = len(path)
    letter = "/"
    b = path.rfind(letter, 0, a)
    return ("%s/" % (path)) if a != b and not isFile else path 
    
def fixeLabelsFileDialog(self, dlgFile, context, hasName):
    dlgFile.setLabelText(QFileDialog.Accept, QApplication.translate("rdi","Open")) if context == 0 else dlgFile.setLabelText(QFileDialog.Accept, QApplication.translate("rdi","Save"))
    dlgFile.setLabelText(QFileDialog.Reject, QApplication.translate("rdi","Cancel"))        
    dlgFile.setLabelText(QFileDialog.FileName, "%s : " % (QApplication.translate("rdi","Name"))) if hasName else dlgFile.setLabelText(QFileDialog.FileName, QApplication.translate("rdi","Folder : "))
    dlgFile.setLabelText(QFileDialog.FileType, "%s : " % QApplication.translate("rdi","Filters"))
    dlgFile.setLabelText(QFileDialog.LookIn,   "%s : " % QApplication.translate("rdi","Folder"))

#================================================
#   FUNCTION FOR DICT AND LIST, XML
#================================================
def dicoHasKey(dico, key):
    cond = False
    try : cond = dico.has_key(key)
    except : cond = (key in dico)
    return cond

def prettifyXml(xml):
    return parseString(xml).toprettyxml()


#================================================
#   OTHER CLASSES
#================================================

class funnyPushButton(QPushButton):
     def __init__(self, *args): super(funnyPushButton, self).__init__(*args)
            
     def initPushButton(self, sizeWidth, sizeHeight, coordX, coordY, name, text, toolTip, isGeom, icon, iconWidth, iconHeight, isStyleSheeted):
            self.setMinimumSize(sizeWidth, sizeHeight)
            self.setMaximumSize(sizeWidth, sizeHeight)
            self.iconWidth = iconWidth
            self.iconHeight = iconHeight
            self.selfFocused = False
            self.subMenuVisble = False
            if isGeom : self.setGeometry(coordX, coordY, sizeWidth, sizeHeight)
            if icon != "" :
               self.setIcon(QIcon(icon))
               self.setIconSize(QSize(self.iconWidth , self.iconHeight))
            self.setToolTip(toolTip)
            if isStyleSheeted : self.setStyleSheet(" QPushButton {border-width: 0px; border-radius: 10px;  border-color: beige;}")
            self.setObjectName(name)
            if text != "" : self.setText(text)

     def enterEvent(self, event):
          if not self.selfFocused :
             #NEW 3.6.0 
             self.setIconSize(QSize(int(self.iconWidth *0.8), int(self.iconHeight *0.8)))
             self.selfFocused = True
          
     def leaveEvent(self, event):
        if self.subMenuVisble == False : self.restoreButton()
     
     def restoreButton(self):
           self.selfFocused = False    
           self.setIconSize(QSize(self.iconWidth, self.iconHeight))



class myProgressBar(QProgressBar):
     def __init__(self, *args):  super(myProgressBar, self).__init__(*args)

     def initTextEdit(self, sizeWidth, sizeHeight, coordX, coordY, name, isGeom, defaultValue, text, align, isStyleSheeted):
            if isGeom : self.setGeometry(coordX, coordY, sizeWidth, sizeHeight)
            self.setProperty("value", defaultValue)
            self.setAlignment(align)
            self.setTextVisible(text)
            self.setObjectName(name)
            if isStyleSheeted == 0 :
                 self.setStyleSheet(
                      "QProgressBar {border: 2px solid grey; border-radius: 5px; text-align: center;}"
                      "QProgressBar::chunk {background-color: #6C96C6; width: 10px; margin: 0.5px;}"
                  )
                   

class basicLocationMarker(QgsVertexMarker):
	def __init__(self, canvas, x, y, color):
		super(basicLocationMarker, self).__init__(canvas)
		self.canvas = canvas
		self.color = color
		self.map_pos = QgsPointXY(x, y)
		self.setCenter(self.map_pos)
		self.setColor(self.color)
		self.setIconSize(10)
		self.setIconType(QgsVertexMarker.ICON_X) # ICON_BOX ICON_CROSS, ICON_X
		self.setPenWidth(2)

class myTextEdit(QTextEdit):           
     def __init__(self, *args): super(myTextEdit, self).__init__(*args)
     
     def initTextEdit(self, sizeWidth, sizeHeight, coordX, coordY, name, isGeom, isUndoRedo, isReadOnly, isRichText, isResizable):
            self.setMinimumSize(sizeWidth, sizeHeight)
            if not isResizable : self.setMaximumSize(sizeWidth, sizeHeight)
            if isGeom : self.setGeometry(coordX, coordY, sizeWidth, sizeHeight)
            self.setUndoRedoEnabled(isUndoRedo)
            self.setReadOnly(isReadOnly)
            self.setAcceptRichText(isRichText)
            self.setObjectName(name)

class textEdit(QTextEdit):
    def __init__(self, * args):
        QTextEdit.__init__(self, * args)

    def initTextEdit(self, SizeW, SizeH, posX, posY, name, isGeom, undoRedo, isReadOnly, isRichText, isSizable):
        if not isSizable:  self.setMaximumSize(SizeW, SizeH)
        if isGeom: self.setGeometry(posX, posY, SizeW, SizeH)
        self.setUndoRedoEnabled(undoRedo)
        self.setReadOnly(isReadOnly)
        self.setAcceptRichText(isRichText)
        self.setObjectName(name)
        self.setAccessibleName(name)
        self.setAcceptDrops(True)           

class myPushButton(QPushButton):
     def __init__(self, *args): super(myPushButton, self).__init__(*args)
            
     def initPushButton(self, sizeWidth, sizeHeight, coordX, coordY, name, text, toolTip, isGeom, icon, iconWidth, iconHeight, isStyleSheeted):
            self.setMinimumSize(sizeWidth, sizeHeight)
            self.setMaximumSize(sizeWidth, sizeHeight)
            if isGeom : self.setGeometry(coordX, coordY, sizeWidth, sizeHeight)
            if icon != "" :
               self.setIcon(QIcon(icon))
               self.setIconSize(QSize(iconWidth, iconHeight))
            self.setToolTip(toolTip)
            if isStyleSheeted : self.setStyleSheet("QPushButton {border: none;}")
            self.setObjectName(name)
            if text != "" : self.setText(text)
