#!/usr/bin/python3
# -*- coding: utf-8 -*-

from xml.dom.minidom import parseString, parse
import numpy as np
import re

licence = """
    file acidebase.py: part of the package pyacidobasic version %s:

    Copyright (C) 2010-2024 Georges Khaznadar <georgesk@debian.org>
    Copyright (C) 2024 philippe DALET <philippe.dalet@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

class abHTMLparser:
    """
    un analyseur de code HTML capable de construire une liste d'acides
    et de bases
    """
    def __init__(self, infile):
        self.infile = infile
        try:
            self.document = parse(open(infile))
        except Exception as e:
            print(f"Erreur dans {infile} : {e}")
        return

    def parseTableToList(self):
        t = self.document.getElementsByTagName("table")[0]
        lignes = t.getElementsByTagName("tr")[1:]
        ab = [acideBase.fromTableRow(ln) for ln in lignes]
        return ab

noTD = re.compile(r'<td[^>]*>\s*(.*)\s*</td>', re.MULTILINE)
def untagTD(s):
    m = noTD.match(s)
    if m:
        return m.group(1)
    else:
        return s

class acideBase:
    def __init__(self, formes=["AH", "A<sup>-</sup>"], formeIndex=0,
                 nom="acide générique", pK=[3.5], c=1.0, charge=0, v=0.0, other=None):
        """
        objet représentant un couple acido-basique
        @param formes la liste des formes acido-basiques conjuguées
        @param formeIndex la forme de base
        @param nom désignation de la forme de base
        @param pK liste des constantes d'acidité
        @param c concentration initiale en mol/L
        @param charge charge électrique de la forme acide en unité élémentaire
        @param v volume en mL
        @param other une instance d'acideBase à recopier
        """
        if other is not None:
            self.formes = other.formes
            self.formeIndex = other.formeIndex
            self.nom = other.nom
            self.pK = other.pK
            self.c = other.c
            self.charge = other.charge
            self.v = other.v
        else:
            self.formes = formes
            self.formeIndex = formeIndex
            self.nom = nom
            self.pK = pK
            self.c = c
            self.charge = charge
            self.v = v

    @staticmethod
    def fromTableRow(tr):
        data = tr.getElementsByTagName("td")
        formes = re.split(r",\s*", untagTD(data[0].toxml()))
        formeIndex = int(untagTD(data[1].toxml()))
        nom = untagTD(data[2].toxml())
        pK = re.split(r",\s*", untagTD(data[3].toxml()))
        pK = [float(i) for i in pK]
        c = float(untagTD(data[4].toxml()))
        charge = int(untagTD(data[5].toxml()))
        result = acideBase(formes, formeIndex, nom, pK, c, charge)
        return result

    def copie(self):
        return acideBase(self.formes, self.formeIndex, self.nom, self.pK,  self.c,
                         self.charge, self.v)

    def __str__(self):
        return f"{self.formes};{self.formeIndex};{self.nom};{self.pK};{self.c};\
                {self.charge};{self.v}"

    def concentrationParPH(self, espece, pHData, vTotal, vBurette=None):
        """
        @param espece un index dans self.formes
        @param pHData un array (numpy) de données de pH
        @param vTotal un vecteur de volume total de la solution en unité mL
        @param vBurette un vecteur de volumes versés en unité mL, ou None si on
        s'intéresse au bécher
        @return un array de données pour la concentration de l'espèce en fonction du pH
        """
        result = np.ones(len(pHData))
        for i in range(len(pHData)):
            if vBurette is not None:
                verse = vBurette[i]
            else:
                verse = None
            result[i] = self.ni(pHData[i],
                                vBurette=verse)[espece]*1e3/vTotal[i]
        return result

    def chargeNette(self, pH):
        """
        @param pH : le pH
        @return la charge électrique en Faraday en fonction du pH
        """
        ni = self.ni(pH)
        ch = self.charge  # on considère la charge de l'espèce acide
        result = 0.0
        # on calcule d'abord la concentration de charges
        for n in ni:
            result += n*ch  # on cumule la charge de l'espèce acide
            ch -= 1        # et on passe à la base conjuguée
        return result

    def ni(self, pH, vBurette=None):
        """
        renvoie la liste des quantités des formes acido-basiques
        @param pH : le pH
        @param vBurette volume versé, ou None si ce n'est pas de la burette que vient
        le produit
        @return la liste des quantités de formes acido-basiques
        """
        n = np.ones(1+len(self.pK))  # liste des quantités des espèces
        total = 1.0                 # concentration totale
        for i in range(len(self.pK)):
            c = n[i]*10**(pH-self.pK[i])
            n[1+i] = c
            total += c
        # On normaliste pour que la quantité totale soit self.c*self.v*1e-3
        if vBurette is not None:
            n *= self.c*vBurette*1e-3/total
        else:
            n *= self.c*self.v*1e-3/total
        return n

def fromString(s):
    s = s.split(";")
    ab = acideBase()
    formes = s[0].split(',')
    ab.formes = []
    for f in formes:
        f = (f, "utf-8")
        ab.formes.append(str(f))
    ab.formeIndex = int(s[1])
    ab.nom = str(s[2].encode("utf-8"))
    pKs = s[3].split(",")
    ab.pK = []
    for pK in pKs:
        ab.pK.append(float(pK))
    ab.c = float(s[4])
    ab.charge = int(s[5])
    return ab

def listFromString(s):
    liste = []
    for ligne in s.split("\n"):
        try:
            acideOuBase = fromString(ligne)
            liste.append(acideOuBase)
        except ValueError:
            pass
    return liste

def listFromHTML(s):
    """
    remplit une liste d'acides/bases à partir de code HTML
    """
    liste = []
    p = abHTMLparser(liste)
    p.feed(s)
    return liste

class abDOZparser:
    """
    classe pour un analyseur de fichier .equ de dozzaqueux
    """
    def __init__(self, liste):
        """
        Le constructeur
        @ param liste référence d'une liste que l'analyseur va allonger
        """
        self.liste = liste
        self.doc = None     # racine du document
        self.el = []        # liste d'éléments de la base de donnée

    def feed(self, s):
        """
        digère le contenu d'un fichier .qu de dozzaqueux
        """
        if s[:5] != '<Doz>':
            s = xmlizeDoz(s)
        self.doc = parseString(s)
        self.el = self.doc.getElementsByTagName("Element")
        self.lesAcides()

    def lesAcides(self):
        """
        extrait les composés de la base qui ont un comportement acido-basique
        """
        for e in self.el:
            lk = e.getElementsByTagName("Log_K")
            if lk:
                lk = lk[0].firstChild.nodeValue.strip().split("\n")
                # i = e.getElementsByTagName("Identifiant")[0]
                # nom = i.firstChild.nodeValue.strip().encode("utf-8")
                # attributs = e.getElementsByTagName("Composition")[0].
                # firstChild.nodeValue.strip().split("\n")


def xmlizeDoz(s):
    """
    change un fichier dozzaqueux de type .equ pour en faire un fichier
    xml valide. Testé avec la version 119 du fichier base.equ
    """
    s = "<Doz>\n"+s+"</Doz>\n"  # établit une racine unique
    s = s.replace("Representant de l'element", "Representant")
    s = s.replace("Atomes constitutifs", "Atomes_constitutifsq")
    s = s.replace("Log K", "Log_K")
    return s

def listFromDozzaqueux(s):
    """
    remplit une liste d'acides/bases à partir de code Dozzaqueux
    """
    liste = []
    p = abDOZparser(liste)
    p.feed(s)
    return liste

def listFromFile(fname):
    p = fname.find("html")
    if p > 0:
        return listFromHTML(open(fname, "r").read(2000000))
    p = fname.find("equ")
    if p > 0:
        return listFromDozzaqueux(open(fname, "r").read(2000000))
    return listFromString(open(fname, "r").read(2000000))

if __name__ == "__main__":
    p = abHTMLparser("lang/db-fr_FR.html")
    for a in p.parseTableToList():
        print(str(a))

