from qgis.PyQt.QtCore import (QAbstractTableModel, QDataStream, QFile, QFileInfo, QIODevice, QObject, QSize, Qt)
from qgis.PyQt.QtGui import (QColor, QIcon)
from qgis.PyQt.QtWidgets import (QApplication, QDesktopWidget, QFileDialog, 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)


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.0.0
from datetime import timedelta
from dateutil.tz import *
import subprocess

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

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: raise Exception(command, exitCode, output)


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


def seriesUpSeuils(dates, data, delta, valueSeuil, seuil, unitInfos):
    deltaScrutation = timedelta(minutes=delta)
    dictData = {k: v for k, v in zip(dates, data)}
    dataDebordement = {}
    for key in dictData.keys() :
        if dictData[key] > valueSeuil :
           dataDebordement[key] = dictData[key]
    new_dict={}
    for k,v in dataDebordement.items():
        dict_element={k:v}
        dict_element.update(new_dict)
        new_dict=dict_element

    dataDebordement = new_dict

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

    firstKey = True
    oldKey = None
    serieDebordement, seriesDebordement = {}, {}
    i = 0
    for key in dataDebordement.keys():
        if oldKey : 
           if abs(key - oldKey) == deltaScrutation :
              serieDebordement[key] = dataDebordement[key] 
              oldKey = key
           else :
               serieDebordement[oldKey] = dataDebordement[oldKey]
               seriesDebordement[i] = serieDebordement
               serieDebordement = {}
               serieDebordement[key] = dataDebordement[key]
               oldKey = key
               i+= 1    
        else :
               serieDebordement[key] = dataDebordement[key]
               oldKey = key

    if serieDebordement != {} : seriesDebordement[i] = serieDebordement

    nbDebordement = len(seriesDebordement.keys())

    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, seuil, 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"

        counterPeriode = 1
        for key in seriesDebordement.keys():
            infos = seriesDebordement[key]
            if infos != {} :
                counter = 0                
                fisrtDate = False
                html+= "<tr style='background-color: white; box-shadow: 0px 5px 14px 0px #0e1635; color: black;'>\n"
                for info in infos.keys():
                    if not fisrtDate : datedeb = info.strftime("%d/%m/%Y à %Hh%M"); fisrtDate = True; dDateDeb = info
                    counter+= 1
                datefin = info.strftime("%d/%m/%Y à %Hh%M")
                dDatefin = info
                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]
                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" % (counterPeriode, datedeb, datefin, counter, dureeObs)
                html+= "</tr>\n"
                counterPeriode+= 1
                
            else : pass

        html+= "</table></small></details></center>\n"
        
    else : html = "Aucun débordement [Premier seuil : %s %s %s] ...\n" % (seuil, 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

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

    for dir in dirs :
        myPath = cleanPath(dir)
        myIconPath = "%s%s" % (myPath, fileName)
        if QFile.exists(myIconPath): return myIconPath
        
    path = cleanPath(dirname(__file__))
    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


    
def cleanPath(path):
    path = "%s" % (path)
    a = len(path)
    letter = "/"
    b = path.rfind(letter, 0, a)
    return ("%s/" % (path)) if a != b 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)
