'''

  process(xmlpath) : Process principal de Pardit

* Parse un fichier XML
* Transforme les données selon les consignes issues des fichiers de configurations pardit.yaml et userdata.yaml
* Génère un fichier XFDF avec ces données
* Injecte ce fichier dans le formulaire PDF.

Retourne le(s) chemin(s) d'accès du/des fichier(s) généré(s), ainsi que la ou les adresses mails des contacts correspondants.

@author: olivier.massot, sept. 2017
'''
import datetime
import re
from shutil import SameFileError
import subprocess
import tempfile

from path import Path

from core import config
from core.constants import TEMPLATE_PATH
from core.outlook import Mail
from core.pdfform import fill_form, gen_xfdf
from core.xmlparser import parse


class XmlFileError(Exception):
    pass

class ProcessNotNeeded(Exception):
    pass

class MissingValue(ValueError):
    """ erreur levée par les fonctions additionnelles lorsque la valeur manque """
    pass


# *************** METHODES SPECIALES DU FICHIER DE CONFIG ***************

def _parsedate(datestring):
    """ parse a string to a datetime """
    if not datestring:
        raise MissingValue
    return datetime.datetime.strptime(datestring[:10], "%Y-%m-%d")

def _day(datestring):
    """ extrait le jour 'dd' d'une datestring ('dd/mm/yyyy') """
    return _parsedate(datestring).strftime("%d")

def _month(datestring):
    """ extrait le mois 'mm' d'une datestring ('dd/mm/yyyy') """
    return _parsedate(datestring).strftime("%m")

def _year(datestring):
    """ extrait l'année 'yyyy' d'une datestring ('dd/mm/yyyy') """
    return _parsedate(datestring).strftime("%Y")

def _now(*args):  # @UnusedVariable
    """ retourne la date du jour ('dd/mm/yyyy')  """
    # return datestring: dd/mm/yyyy
    return datetime.date.today().strftime("%Y-%m-%d")

_FUNCTIONS = {"JOUR": _day,
              "MOIS": _month,
              "ANNEE": _year,
              "MAINTENANT": _now}




# *************** FONCTIONS DE TRAITEMENT DES DONNEES ***************

def process(xmlpath):
    """ Traite les données XML au moyen du fichier de configuration
    pour générer le ou les Pdf de réponse """

    # Chemin d'accès au fichier de données
    xmlpath = Path(xmlpath)

    # Détermine et créé le répertoire de sortie
    output_dir = Path(config.get("repertoire_sortie")).abspath() / xmlpath.namebase
    output_dir.makedirs_p()

    # Parse une premiere fois les données du fichier XML
    xmldata = parse(xmlpath)

    # Determine le type de demande (dt, dict, conjointe)
    first_nodes = {node.split(".")[0] for node in xmldata}
    if "dtDictConjointes" in first_nodes:
        if xmldata.get("dtDictConjointes.partieDT.souhaitsPourLeRecepisse.souhaiteRecevoirLeRecepisse", "") == "true":
            a_traiter = {"dict": parse(xmlpath, "partieDICT"), "dt": parse(xmlpath, "partieDT")}
        else:
            a_traiter = {"dict": parse(xmlpath, "partieDICT")}
    elif "DT" in first_nodes:
        a_traiter = {"dt": parse(xmlpath, "DT")}
    elif "DICT" in first_nodes:
        a_traiter = {"dict": parse(xmlpath, "DICT")}
    else:
        raise XmlFileError("Le format du fichier XML n'est pas reconnu")

    # Traite la ou les demandes de réponse
    outputfiles = []
    mails = []

    for doctype, xmldata in a_traiter.items():

        # Construit le modele de reponse
        modele = {}
        modele.update(config.get("donnees", "commun"))  # Donnees communes à toutes les réponses
        modele.update(config.get("donnees", doctype))  # Données spécifique au type de réponse

        # inject_data remplace les <...> par les valeurs des champs correspondants
        # eval_ parse et évalue les éventuelles fonctions de l'expression
        reponse = {field: eval_(inject_data(str(value), xmldata)) for field, value in modele.items()}

        # Patch 1: on coche la case 'demande conjointe' lorsque la demande est conjointe
        if "dtDictConjointes" in first_nodes:
            reponse["Recepisse_DT"] = "Non"
            reponse["Recepisse_DICT"] = "Non"
            reponse["Recepisse_DC"] = "Oui"

        outputname = output_dir / "Recepisse_{}.pdf".format(doctype.upper())

        make_pdf(reponse, outputname, keep_xfdf=True)
        outputfiles.append(outputname)

        if doctype == "dict" or xmldata.get("souhaitsPourLeRecepisse.souhaiteRecevoirLeRecepisse") == "true":
            contact = eval_(inject_data(config.get("mail", doctype, "dest"), xmldata))
            subject = eval_(inject_data(config.get("mail", doctype, "objet"), xmldata))
            content = eval_(inject_data(config.get("mail", doctype, "texte"), xmldata))
            mail = Mail(contact, subject, content)

            if not contact in [mail.to for mail in mails]:
                mails.append(mail)

    with open(output_dir / "contact.txt", "w+") as f:
        f.write("\n".join([mail.to for mail in mails]))

    # Patch 2: en cas de demande conjointe, et si le demandeur DT est le même que le demandeur DICT, on ne joint qu'un seul document au mail.
    # On supprime le recepisse DT, et on renomme le recepisse DICT en recepisse DC
    if len(mails) == 1 and len(outputfiles) > 1:
        rec_dict, rec_dt = outputfiles
        rec_dc = rec_dict.parent / "Recepisse_DC.pdf"
        rec_dc.remove_p()
        rec_dict.rename(rec_dc)
        rec_dt.remove_p()
        outputfiles = [rec_dc]

    # Créé une svg du fichier XML traité
    try:
        xmlpath.copy(output_dir)
    except SameFileError:
        pass

    return outputfiles, mails


def inject_data(value, xmldata):
    """ injecte les données issues du fichier XML parsé
        dans la valeur du champ. """
    def _get(matchobj):
        key = matchobj.group(1)
        return xmldata.get(key, "")
    value = re.sub(r"<([^<>]*)>", _get, value)
    return value

def eval_(value):
    """ applique les éventuelles fonctions à la valeur du champ """
    value = str(value) if value != None else ""

    for name, fct in _FUNCTIONS.items():
        match = re.search(r"{}\((.*)\)".format(name), str(value))
        if match:
            value = match.group(1) if match.group(1) else ''
            value = eval_(value)

            try:
                value = fct(value)
            except MissingValue:
                value = ""

    return value

def make_pdf(data, outputname, keep_xfdf=False):
    """ injecte les données 'data' dans le Pdf modèle """
    with tempfile.TemporaryDirectory() as tmpdir:
        tmpdir = Path(tmpdir)

        tmpform = TEMPLATE_PATH.copy(tmpdir)  # @UndefinedVariable
        filledform = tmpdir / "filled.pdf"

        xfdf = gen_xfdf(tmpdir / "data.fdf", data)

        try:
            fill_form(tmpform, xfdf, filledform, flatten=False)
        except subprocess.CalledProcessError:
            print("Erreur lors de l'écriture du PDF, assurez-vous qu'il n'est pas ouvert")
            return

        if outputname.isfile():
            outputname.remove()
        filledform.move(outputname)

        if keep_xfdf:
            xfdf.copy(outputname.parent)
