/*
 * Copyright
 *   2007 axYus - www.axyus.com
 *   2007 N.Le Corre - nicolas.lecorre@axyus.com
 *   2008 L. Meckert - laurent.meckert@axyus.com
 *
 * This file is part of XEMELIOS.
 *
 * XEMELIOS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * XEMELIOS 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with XEMELIOS; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package fr.gouv.finances.cp.xemelios.export;

import java.io.File;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;


import java.util.Enumeration;

import javax.swing.JDialog;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;


import org.apache.log4j.Logger;
import org.jdesktop.swingworker.SwingWorker;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import fr.gouv.finances.cp.utils.xml.dompath.DomPath;
import fr.gouv.finances.cp.xemelios.common.Constants;
import fr.gouv.finances.cp.xemelios.common.config.ChampModel;
import fr.gouv.finances.cp.xemelios.common.config.DocumentModel;
import fr.gouv.finances.cp.xemelios.common.config.ElementModel;
import fr.gouv.finances.cp.xemelios.common.config.ListeResultatModel;
import fr.gouv.finances.cp.xemelios.common.config.SimpleElement;
import fr.gouv.finances.cp.xemelios.data.DataResultSet;
import fr.gouv.finances.cp.xemelios.ui.export.DlgSearchConfig;
import fr.gouv.finances.cp.xemelios.ui.resulttable.DataHandler;
import fr.gouv.finances.cp.xemelios.ui.resulttable.EtatResultTableModel;

import fr.gouv.finances.cp.xemelios.utils.HashSwap;

import java.lang.reflect.*;
import fr.gouv.finances.cp.xemelios.utils.LongueurMax;

public class Exporter extends SwingWorker {

    public class ExportedElementTag {

        @LongueurMax(value = 1)
        public int dummy;

        public ExportedElementTag(int i) {
            dummy = i;
        }
    }
    static Logger logger = Logger.getLogger(Exporter.class);
    private JDialog parentFrame = null;
    //	private EtatResultTableModel ertm;
    private DataResultSet drs = null;
    File fDest;
    private ConfigModel cf = null;
    private DocumentModel dm = null;
    private String etatId;
    private ElementModel em = null;
    // LM statistiques
    double timeStartGenerate = 0;
    double timeEndGenerate = 0;
    double gapGenerate = 0;
    // Lm objet redefinissable reealisanteffectivement  l'export. 
    // Selon sa classe genere Excel pur ou  csv ou table Html(ou tout a fait autre chose que du Excel si on veut)
    private ExportUndertaker undertaker;
    // contiendra la liste des id d'élément déjà exportés 
    // -- regroupés par type d'élément. --
    // Ceci afin de ne pas exporter plusieurs fois le même élément
    // La valeur ajoutée dans le Vector est constituée de la concaténation des éléments formant la clef d'un Element
    // On trouve la liste des éléments d'une clef dans la liste des champs, de la listeResultat d'un ElementModel
    //  private Hashtable<String, Hashtable<String,String >> exportedElements = new Hashtable<String, Hashtable<String,String >>();
    private Hashtable<String, HashSwap<ExportedElementTag>> exportedElements = new Hashtable<String, HashSwap<ExportedElementTag>>();
    /**
     * Contient, pour chaque type d'élément exporté (Bordereau, Titre, Ligne de titre), un compteur indiquant quel était le dernier numéro de ligne
     */
    private Hashtable<String, Integer> lignesExporteesForElement = new Hashtable<String, Integer>();

    private ExportUndertaker getUndertaker() {
        String undertakerClassName = System.getProperty(Constants.SYS_PROP_XEMELIOS_EXPORTER_CLASS,
                "fr.gouv.finances.cp.xemelios.export.JxlExporter");


        try {
            Class undertakerClass = Class.forName(undertakerClassName);
            Class[] exporterArgsClass = new Class[]{Exporter.class};
            Object[] exporterArgs = new Object[]{this};
            Constructor constructor = undertakerClass.getConstructor(exporterArgsClass);
            ExportUndertaker undertaker = (ExportUndertaker) constructor.newInstance(exporterArgs);
//            System.out.println("Builr undertaker of class " + undertakerClassName);

            return undertaker;
        } catch (ClassNotFoundException e) {
            logger.error("",e);
            return null;
        } catch (NoSuchMethodException e) {
            logger.error("",e);
            return null;
        } catch (InstantiationException e) {
            logger.error("",e);
            return null;
        } catch (IllegalAccessException e) {
            logger.error("",e);
            return null;
        } catch (IllegalArgumentException e) {
            logger.error("",e);
            return null;
        } catch (InvocationTargetException e) {
            logger.error("",e);
            return null;
        }
    }

    private void addLine(String el) {
        Integer l = lignesExporteesForElement.get(el);
        if (l == null) {
            l = new Integer(0);
        }
        l = new Integer(l.intValue() + 1);
        lignesExporteesForElement.put(el, l);

        undertaker.flushLine(el);
    }

    int getLastLine(String el) {
        Integer l = lignesExporteesForElement.get(el);
        if (l == null) {
            l = new Integer(0);
        }
        return l.intValue();
    }

    public Exporter(File f, EtatResultTableModel ertm, DocumentModel dm, String etatId, ElementModel em, ConfigModel cf) {
        //		this.ertm = ertm;
        drs = ertm.getResultSet();
        this.fDest = f;
        this.dm = dm;
        this.etatId = etatId;
        this.em = em;
        this.cf = cf;
        this.undertaker = getUndertaker(); // new HtmlTableExporter(this);// CsvExporter(this); new JxlExporter(this) ;// new ExportUndertaker(this); //a remplacer par sous classe adhoc
    }

    public Exporter(File f, DataResultSet drs, DocumentModel dm, String etatId, ElementModel em, ConfigModel cf) {
        //		this.ertm = ertm;
        this.drs = drs;
        this.fDest = f;
        this.dm = dm;
        this.etatId = etatId;
        this.em = em;
        this.cf = cf;
        this.undertaker = getUndertaker();
    }

    @Override
    protected Object doInBackground() throws Exception {
        doExport();
        if (!isCancelled()) {
            ((DlgSearchConfig) getParentFrame()).showXLS();
        } else {
            ((DlgSearchConfig) getParentFrame()).getGlassPane().setVisible(false);
        }
        return null;
    }

    @Override
    protected void done() {
        super.done();
        // nettoyage de la mémoire
        timeEndGenerate = System.currentTimeMillis();
        gapGenerate = timeEndGenerate - timeStartGenerate;
//        System.out.println("====> Duree generation Excel : " + gapGenerate);
        //quelle est la memoire utilisee par exported elements
        System.gc();
//        System.out.println("Taille de la table de hachage : " + exportedElements.size());
        Enumeration cles = exportedElements.keys();
        while (cles.hasMoreElements()) {
            String k = (String) cles.nextElement();
            HashSwap<ExportedElementTag> val = (HashSwap<ExportedElementTag>) exportedElements.get(k);
            try {
//                System.out.println(" table " + k + ": " + val.size() + " elements");
                val.destruct();
            } catch (Exception e) {
                e.printStackTrace();
            }
        /*    int j = 0;
        Enumeration elems = val.keys();
        while(elems.hasMoreElements() ){
        String s = (String)elems .nextElement() ;
        System.out.println( " element " + j++  +  ": " + s);
        }*/

        }


        exportedElements.clear();
        exportedElements = null;
        System.gc();

    }

    /** Le point d'entrée est l'élément que l'on a recherché (em)
     *  a partir de lui, on exporte ses champs en respectant la config
     *  puis on exporte son/ses parent(s)
     *  puis on exporte son/ses enfant(s)
     */
    private void doExport() {
        try {

            timeStartGenerate = System.currentTimeMillis();
            undertaker.initialize();

            /**
             * Création des feuilles du classeur
             */
            for (ExportElementModel eem : cf.getExportModel().getEnfants()) {
                if (shouldEEMBeExported(eem)) {
                    undertaker.buildSheet(eem);
                }
            }
            /**
             * Export de l'élément concerné par la recherche
             */
            int ligne = 2;
            //			 DataResultSet drs = ertm.getResultSet();
	    /*
             * le LRM contient une série de champs qui ne correspond pas forcément à la sélection de l'utilisateur dans la configuration de l'export
             * on le clone, vide sa liste de champs et y ajoute tous les champs exportables
             */
            ListeResultatModel lrm = drs.getListeResultatModel();
            ListeResultatModel lrmClone = lrm.clone();
            lrmClone.setParent(lrm.getParent());
            // il est possible que le tri se fasse sur des champs non exportables; il faut les inclure dans le select quand même
            String tridefault = lrmClone.getTriDefaut();
            lrmClone.removeAllChamps();
            addAllExportableChamps(lrmClone, drs.getListeResultatModel().getParent(), tridefault);
            drs.setListeResultatModel(lrmClone);

            drs.firstPage();
            int page = 0;
            do {

                // on avance d'une page que si l'on ne lit pas la première.
                // comment ça il ne vous revient pas mon "++!=" ?? :p
                if (page++ != 0) {
                    drs.nextPage();
                }
                // on avance d'une ligne
                while (drs.hasNext() && !isCancelled()) {
                    DataHandler dh = drs.next();
                    exportEM(drs.getListeResultatModel().getParent(), dh, drs.getListeResultatModel().getParent().getId(), ligne);
                    ligne++;
                }
            } while (drs.hasNextPage() && !isCancelled());

            if (!isCancelled()) {
                undertaker.finish();
            }
        } catch (Exception e) {
            logger.error("Exporter Error catched", e);
        } finally {
            undertaker.close();
        }
    }

    /**
     * Ajoute au <tt>ListeResultatModel</tt> tous les champs exportables contenus dans l'<tt>ElementModel</tt>
     * @param l
     * @param e
     * @throws Exception
     */
    private void addAllExportableChamps(ListeResultatModel l, ElementModel e, String tridefault) throws Exception {
        Vector<String> tri = new Vector<String>();
        for (String s : tridefault.split(",")) {
            tri.add(s);
        }
        for (ChampModel c : e.getListeResultat().getListeChamps()) {
            if (c.isExportable() || tri.contains(c.getId())) {
                l.addChild(c, ChampModel.QN);
            }
        }
    }

    /**
     * Rempli une ligne de <tt>WritableSheet</tt> avec les informations contenues dans le <tt>DataHandler</tt>
     * conformément à la configuration
     * @param _em
     * @param _dh
     * @param _ws
     * @param _ligne
     * @throws Exception
     */
    private void exportEM(ElementModel _em, DataHandler _dh, String sheetId, int _ligne) throws Exception {
        // La configuration de l'élément concerné
        ExportElementModel eem = null;
        for (ExportElementModel e : cf.getExportModel().getEnfants()) {
            eem = getEEMFromId(e, _em.getId());
            if (eem != null) {
                break;
            }
        }
        // remplissage des feuilles des parents
        Vector<SimpleElement> parents = pushParents(_em, "");
        exportParents(parents, _dh);

        // Ajout des colonnes des parents
        int col = 0, oldCol = 0;
        // si _ws est null, on ne peut pas exporter. C'est notamment le cas quand on a fait une recherche sur des lignes et qu'on exporte que le bordereau.
        if (undertaker.sheetExists(sheetId)) {
            for (int i = (parents.size() - 1); i >= 0; i--) {
//                if (_ligne < 2) {
//                    System.out.println("exportEM 334 _ligne < 2 :" + _ligne);
//                }
                col = writeCellsOfParent(dm.getEtatById(etatId).getElementById(parents.get(i).getElement()), parents.get(i).getPseudoCompletePath(), _dh, sheetId, _ligne, col, (_ligne <= 2), false);
                undertaker.mergeCells(sheetId, oldCol, 0, col - 1, 0);
                oldCol = col;
            }

            boolean firstCol = true;
            // parcours des colonnes configurées
            for (ExportChampModel ecm : eem.getChamps()) {
                if (ecm.isSelectionne()) {
                    // première ligne => entete
                    if (_ligne == 2) {
                        if (firstCol) {
                            undertaker.writeCell(sheetId, _em.getTitre(), 0, col, true);
                        }
                        undertaker.writeCell(sheetId, ecm.getLibelle(), 1, col, true);
                    }
                    undertaker.writeCell(sheetId, _dh.getValueAtColumn(getColumnIndex(ecm.getId(), _em)), _ligne, col, false);
                    col++;
                    firstCol = false;
                }
            }

            undertaker.mergeCells(sheetId, oldCol, 0, col - 1, 0);
            // remplissage des feuilles des enfants
            if (hasChildrenToExport(em)) {
                exportEnfants(parents, em, _dh);
            }

            // mise à jour de la liste des éléments exportés
            /*   Hashtable<String, String> h = exportedElements.get(_em.getId());
            if (h == null) h = new 	Hashtable<String, String>();
             */
            HashSwap<ExportedElementTag> h = exportedElements.get(_em.getId());
            //  Hashtable<String, String> h = exportedElements.get(_em.getId());
            if (h == null) {
                logger.info("new  HashSwap..");
                h = new HashSwap<ExportedElementTag>(ExportedElementTag.class);
                logger.info("done");
                exportedElements.put(_em.getId(), h);

            }

            //		String vId = getIdentifiant (eem, _dh, _em);
            String vId = getIdentifiant(_dh, _em);
            if (!h.containsKey(vId)) {
                h.put(vId, new ExportedElementTag(1));
            }

            addLine(sheetId);
        }
    }

    private void exportEnfants(Vector<SimpleElement> parents, ElementModel _em, DataHandler _dh) throws Exception {
        if (_em.getEnfant() != null) {
            for (String s : _em.getEnfant().getEnfants().keySet()) {
                SimpleElement enfantToExport = _em.getEnfant().getEnfants().get(s);

                // la clé de cet élément
                // La configuration de l'élément concerné
                ExportElementModel eem = null;
                for (ExportElementModel e : cf.getExportModel().getEnfants()) {
                    eem = getEEMFromId(e, s);
                    if (eem != null) {
                        break;
                    }
                }
                ExportElementModel eemP = null;
                for (ExportElementModel e : cf.getExportModel().getEnfants()) {
                    eemP = getEEMFromId(e, _em.getId());
                    if (eemP != null) {
                        break;
                    }
                }

                // doit-on exporter cet enfant ?
                if (shouldEEMBeExported(eem)) {

                    enfantToExport.setPseudoCompletePath("./" + enfantToExport.getPath());

                    // export des valeurs du fils en question
                    // export des clés de ce fils dans les feuilles de ses fils
                    Vector<Object> parentsValues = getParentsValues(parents, _dh);
                    for (ExportChampModel ecm : eemP.getChamps()) {
                        if (ecm.isIdentifiant()) {
                            parentsValues.add(_dh.getValueAtColumn(getColumnIndex(ecm.getId(), _em)));
                        }
                    }

                    exportEnfant(parentsValues, enfantToExport.getPseudoCompletePath(), enfantToExport, _dh.getElement(), enfantToExport.getElement(), _dh);
                }
            }
        }
    }

    private Vector<Object> getParentsValues(Vector<SimpleElement> parents, DataHandler _dh) throws Exception {
        Vector<Object> ret = new Vector<Object>();

        for (int p = parents.size() - 1; p >= 0; p--) {
            ret.addAll(getParentValues(parents.get(p), _dh));
        }

        return ret;
    }

    private Vector<Object> getParentValues(SimpleElement parent, DataHandler _dh) throws Exception {
        Vector<Object> ret = new Vector<Object>();

        ElementModel em = getEMFromId(parent.getElement());

        XPathFactory xpf;
        try {
            xpf = XPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI);
        } catch (Throwable t) {
            xpf = XPathFactory.newInstance();
        }
        XPath xp = xpf.newXPath();
        xp.setNamespaceContext(dm.getNamespaces());
        Node n = (Node) xp.evaluate(parent.getPseudoCompletePath(), _dh.getElement(), XPathConstants.NODE);
        for (ChampModel cm : em.getListeResultat().getListeChamps()) {
            if (cm.isIdentifiant()) {
                DomPath dp = new DomPath(cm.getPath().getPath(), dm.getNamespaces());
                ret.add(dp.getValue(n, false));
            }
        }

        return ret;

    }

    private Vector<Object> getParentId(ElementModel parent, Object rootElement) throws Exception {
        Vector<Object> ret = new Vector<Object>();

        for (ChampModel cm : parent.getListeResultat().getListeChamps()) {
            if (cm.isIdentifiant()) {
                DomPath dp = new DomPath(cm.getPath().getPath(), dm.getNamespaces());
                ret.add(dp.getValue((Node) rootElement, false));
            }
        }

        return ret;

    }

    private void exportEnfant(Vector<Object> parentsValues, String pathToEnfant, SimpleElement enfantToExport, Object rootElement, String sheetId, DataHandler _dh) throws Exception {
        //		int col = 0;

        int ligne = undertaker.getLastLineLastSheet(sheetId);
        //		// écriture des clefs des parents
        //		for (int p=0; p<parentsValues.size(); p++) {
        //			writeCell (_ws, parentsValues.get(p), ligne, col++); 
        //		}

        // écriture des valeurs de moi même
        Vector<Node> nodes = writeCellsOfEnfant(parentsValues, getEMFromId(enfantToExport.getElement()), rootElement, pathToEnfant, sheetId, ligne/*, col*/, (ligne <= 2));

        // appel de l'export de chaque enfant en lui passant ma clef, constituée de celle de mon parent et de la mienne
        ElementModel e = getEMFromId(enfantToExport.getElement());
        if (e.getEnfant() != null) {
            for (String s : e.getEnfant().getEnfants().keySet()) {
                SimpleElement se = e.getEnfant().getEnfants().get(s);

                ExportElementModel eem = null;
                for (ExportElementModel m : cf.getExportModel().getEnfants()) {
                    eem = getEEMFromId(m, se.getElement());
                    if (eem != null) {
                        break;
                    }
                }
                if (shouldEEMBeExported(eem)) {
                    for (Node n : nodes) {
                        Vector<Object> newParentsValues = new Vector<Object>();
                        newParentsValues.addAll(parentsValues);
                        newParentsValues.addAll(getParentId(getEMFromId(enfantToExport.getElement()), n));
                        exportEnfant(newParentsValues, se.getPath(), se, n, se.getElement(), _dh);
                    }
                } else {
                    break;
                }
            }
        }
    }

    private void exportParents(Vector<SimpleElement> parents, DataHandler _dh) throws Exception {
        for (int i = 0; i < parents.size(); i++) {
            SimpleElement parentToExport = parents.get(i);
            if (undertaker.sheetExists(parentToExport.getElement())) {

                // si ws est null, c'est un parent qu'on ne veux pas exporter

                int ligne = getLastLine(parentToExport.getElement());
                exportParent(parentToExport, parents, i, parentToExport.getElement(), _dh, ligne, true);
            }
        }
    }

    private int exportParent(SimpleElement parentToExport, Vector<SimpleElement> parents, int indInParents, String sheetId, DataHandler _dh, int ligne, boolean doCheckMAJ) throws Exception {
        // a-t-on déjà exporté cet élément ?
        ElementModel em = getEMFromId(parentToExport.getElement());
        String parentPath = parentToExport.getPseudoCompletePath();
        if (doCheckMAJ && isExporte(parentToExport.getElement(), getIdentifiant(_dh, parentPath, em))) {
            return 0;
        }

        // on est arrivé au parent de plus haut niveau
        int col = 0;
        if (indInParents == parents.size() - 1) {
            if (ligne < 2) {
//                System.out.println("exportParent 523 _ligne < 2 :" + ligne);
            }
            col += writeCellsOfParent(em, parentToExport.getPseudoCompletePath(), _dh, sheetId, ligne, col, (ligne <= 2), (firstSheetId.equals(sheetId)));
            if (doCheckMAJ) {
                majExportes(parentToExport.getElement(), _dh, parentPath, em);
            }
        } // sinon il nous faut exporter les parents de plus haut niveau avant celui-ci
        else {
            // dans le cas où ce parent a lui aussi un parent à exporter, il faut l'exporter, peu importe si on l'a déjà fait car on en a besoin
            col += exportParent(parents.get(indInParents + 1), parents, (indInParents + 1), sheetId, _dh, ligne, false);
            if (indInParents == parents.size() - 1) {
                if (ligne < 2) {
                    System.out.println("exportParent 5343 _ligne < 2 :" + ligne);
                }
                col += writeCellsOfParent(em, parentToExport.getPseudoCompletePath(), _dh, sheetId, ligne, col, (ligne <= 2), true);
                if (doCheckMAJ) {
                    majExportes(parentToExport.getElement(), _dh, parentPath, em);
                }
            }
        }
        return col;
    }

    private boolean isExporte(String idColl, String id) {
        //	Hashtable<String,String> h = exportedElements.get(idColl);

        boolean ret = false;
        HashSwap<ExportedElementTag> h = exportedElements.get(idColl);
        if (h == null) {
            return false;
        } else {
            try {
                if (h.containsKey(id)) {
                    ret = true;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    private void majExportes(String id, DataHandler _dh, String parentPath, ElementModel em) throws Exception {
        //	Hashtable<String,String> h  = exportedElements.get(id);

        HashSwap<ExportedElementTag> h = exportedElements.get(id);
        if (h == null) {
            //    h = new Hashtable<String,String>();
            logger.info("new  HashSwap..");
            h = new HashSwap<ExportedElementTag>(ExportedElementTag.class);
            logger.info("done");
            exportedElements.put(id, h);
        }
        String vId = getIdentifiant(_dh, parentPath, em);
        if (!h.containsKey(vId)) {
            h.put(vId, new ExportedElementTag(1));
        }
    }

    /**
     * Empile dans un <tt>Vector</tt> les ancêtres de _em du plus proche au plus ancien 
     * @param _em
     * @param parentPath
     * @return
     */
    private Vector<SimpleElement> pushParents(ElementModel _em, String parentPath) {
        Vector<SimpleElement> ret = new Vector<SimpleElement>();

        SimpleElement parent = _em.getSimpleParent();
        if (parent != null) {
            parent.setPseudoCompletePath(parent.getPath() + (!parentPath.equals("") ? "/" + parentPath : ""));
            ret.add(parent);
            ret.addAll(pushParents(dm.getEtatById(etatId).getElementById(parent.getElement()), parent.getPseudoCompletePath()));
        }

        return ret;
    }

    /**
     * crée les cellules d'un parent d'un élément
     * @param parent
     * @param parentPath
     * @param _dh
     * @param _ws
     * @param _ligne
     * @return
     * @throws Exception
     */
    private int writeCellsOfParent(ElementModel parent, String parentPath, DataHandler _dh, String sheetId, int _ligne, int _col, boolean firstLine, boolean completeLine) throws Exception {
        int oldCol = _col;
        XPathFactory xpf;
        try {
            xpf = XPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI);
        } catch (Throwable t) {
            xpf = XPathFactory.newInstance();
        }
        XPath xp = xpf.newXPath();
        xp.setNamespaceContext(dm.getNamespaces());
        Node n = (Node) xp.evaluate(parentPath, _dh.getElement(), XPathConstants.NODE);
        boolean firstCol = true;
        for (ChampModel cm : parent.getListeResultat().getListeChamps()) {
            if (cm.isIdentifiant()) {
                DomPath dp = new DomPath(cm.getPath().getPath(), dm.getNamespaces());
                Object o = dp.getValue(n, false);
                // première ligne => entete
                if (firstLine) {
                    // si c'est la première cellule, on ajoute le nom du parent au dessus
                    if (firstCol) {
                        undertaker.writeCell(sheetId, parent.getTitre(), 0, _col, true);
                    }
                    undertaker.writeCell(sheetId, cm.getLibelle(), 1, _col, true);
                }
                if (_ligne < 2) {
//                    System.out.println("writeCellsOfParent=_ligne < 2 !!! :" + _ligne);
                    _ligne += 2;
                }
                Object value = cm.getValueOf(o);
                undertaker.writeCell(sheetId, value, _ligne, _col++, false);
                firstCol = false;
            }
        }
        if (completeLine) {
            addLine(sheetId);
        }

        // merge des cellules
        if (firstLine) {
            undertaker.mergeCells(sheetId, oldCol, 0, _col - 1, 0);
        }

        return _col;
    }

    private Vector<Node> writeCellsOfEnfant(Vector<Object> parentsValues, ElementModel enfant, Object rootElement, String enfantPath, String sheetId, int _ligne/*, int _col*/, boolean firstLine) throws Exception {
        Vector<Node> ret = new Vector<Node>();
        XPathFactory xpf;
        try {
            xpf = XPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI);
        } catch (Throwable t) {
            xpf = XPathFactory.newInstance();
        }
        XPath xp = xpf.newXPath();
        xp.setNamespaceContext(dm.getNamespaces());
        NodeList nodeListe = (NodeList) xp.evaluate(enfantPath, rootElement, XPathConstants.NODESET);

        // La configuration de l'élément concerné
        ExportElementModel eem = null;
        for (ExportElementModel e : cf.getExportModel().getEnfants()) {
            eem = getEEMFromId(e, enfant.getId());
            if (eem != null) {
                break;
            }
        }

        if (firstLine) {
            Stack<ExportElementModel> s = new Stack<ExportElementModel>();
            ExportElementModel tmp = eem;
            while (true) {
                ExportElementModel p = tmp.getParent();
                tmp = p;
                if (p != null) {
                    s.push(p);
                } else {
                    break;
                }
            }

            ExportElementModel ptmp = null;
            int oldColTitre = 0, colTitre = 0;
            while (!s.empty()) {
                ptmp = s.pop();
                oldColTitre = colTitre;
                undertaker.writeCell(sheetId, ptmp.getLibelle(), 0, colTitre, true);

                for (ExportChampModel ecm : ptmp.getChamps()) {
                    if (ecm.isIdentifiant()) {
                        undertaker.writeCell(sheetId, ecm.getLibelle(), 1, colTitre++, true);
                    }
                }
                undertaker.mergeCells(sheetId, oldColTitre, 0, (colTitre - 1), 0);

            }
        }

        boolean firstCol = true;
        int maligne = _ligne;
        int macolonne = 0;
        int maxCol = 0;
        for (int n = 0; n < nodeListe.getLength(); n++) {
            // écriture des clefs des parents
            int col = 0;
            for (int p = 0; p < parentsValues.size(); p++) {
                undertaker.writeCell(sheetId, parentsValues.get(p), maligne, col++, false);
            }
            macolonne = col;

            Node node = nodeListe.item(n);
            ret.add(node);
            for (ExportChampModel ecm : eem.getChamps()) {
                if (ecm.isSelectionne()) {
                    DomPath dp = new DomPath(enfant.getListeResultat().getChamps().get(ecm.getId()).getPath().getPath(), dm.getNamespaces());
                    Object o = "";
                    try {
                        o = dp.getValue(node, false);
                    } catch (Exception e) {
                    }
                    // première ligne => entete
                    if (firstLine) {
                        // si c'est la première cellule, on ajoute le nom du parent au dessus
                        if (firstCol) {
                            undertaker.writeCell(sheetId, enfant.getTitre(), 0, macolonne, true);
                        }
                        undertaker.writeCell(sheetId, ecm.getLibelle(), 1, macolonne, true);
                    }
                    try {
                        Object computed = enfant.getListeResultat().getChamps().get(ecm.getId()).getValueOf(o);
                        undertaker.writeCell(sheetId, computed, maligne, macolonne++, false);
                    }
                     catch(Exception ex) {
                         logger.error("while exporting "+enfant.getId()+"."+ecm.getId());
                     }
                    firstCol = false;
                }
            }
            addLine(sheetId);

            maligne++;
            maxCol = macolonne;
            macolonne = col;
            // merge des cellules de l'entête
            if (firstLine) {
                firstLine = false;
                undertaker.mergeCells(sheetId, col, 0, maxCol - 1, 0);
            }
        }

        return ret;
    }

    /**
     * Renvoie une chaine constituée de la concaténation des valeurs des champs formant l'identifiant (xx-yyy-zzzz)
     * @param _eem
     * @param _dh
     * @param _em
     * @return
     * @throws Exception
     */
    private String getIdentifiant(DataHandler _dh, String parentPath, ElementModel _em) throws Exception {
        String ret = "";

        XPathFactory xpf;
        try {
            xpf = XPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI);
        } catch (Throwable t) {
            xpf = XPathFactory.newInstance();
        }
        XPath xp = xpf.newXPath();
        xp.setNamespaceContext(dm.getNamespaces());
        Node node = (Node) xp.evaluate(parentPath, _dh.getElement(), XPathConstants.NODE);

        for (ChampModel cm : _em.getListeResultat().getListeChamps()) {
            if (cm.isIdentifiant()) {
                DomPath dp = new DomPath(cm.getPath().getPath(), dm.getNamespaces());
                Object o = dp.getValue(node, false);
                if (o != null) {
                    if (ret.length() > 0) {
                        ret += "-";
                    }
                    ret += String.valueOf(o);
                }
            }
        }

        return ret;
    }

    private String getIdentifiant(DataHandler _dh, ElementModel _em) throws Exception {
        String ret = "";

        for (ChampModel cm : _em.getListeResultat().getListeChamps()) {
            if (cm.isIdentifiant()) {
                Object o = _dh.getValueAtColumn(getColumnIndex(cm.getId(), _em));
                if (o != null) {
                    if (ret.length() > 0) {
                        ret += "-";
                    }
                    ret += String.valueOf(o);
                }
            }
        }

        return ret;
    }

    /**
     * Retourne l'ExportElementModel de l'élément identifié par son id dans la configuration
     * @param id
     * @return
     */
    private ExportElementModel getEEMFromId(ExportElementModel _eem, String id) {
        ExportElementModel ret = null;
        if (_eem.getId().equals(id)) {
            ret = _eem;
        } else {
            for (ExportElementModel e : _eem.getEnfants()) {
                ret = getEEMFromId(e, id);
                if (ret != null) {
                    break;
                } // on a trouvé; inutile d'aller plus loin
            }
        }
        return ret;
    }

    /**
     * Retourne l'ElementModel de l'élément identifié par son id
     * @param id
     * @return
     */
    /*private*/ ElementModel getEMFromId(String id) {
        return dm.getEtatById(etatId).getElementById(id);
    }

    /**
     * Retourne l'index d'une colonne à partir de son id
     * @param id l'id de la colonne
     * @param em l'élément dans lequel on chreche la colonne
     * @return
     */
    private int getColumnIndex(String id, ElementModel em) {
        int ret = -1;
        int j = 0;
        for (ChampModel cm : em.getListeResultat().getListeChamps()) {
            if (cm.isExportable()) {
                if (id.equals(cm.getId())) {
                    ret = j;
                    break;
                }
                j++;
            }
        }
        return ret;
    }
    String firstSheetId = "";

    /**
     * Indique si un ExportElementModel a au moins un champ qui doit être exporté et donc si on doit l'exporter
     * @param _eem
     * @return
     */
    private boolean shouldEEMBeExported(ExportElementModel _eem) {
        if (_eem != null) {
            for (ExportChampModel ecm : _eem.getChamps()) {
                if (ecm.isSelectionne()) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Détermine si l'élément _em a au moins un enfant qui doit être exporté.
     * On peut se contenter de tester les enfants directs de l'élément. 
     * En effet, si un petit fils est sélectionné à l'export, on aura pris soin de sélectionner
     * ce qui constitue la clef de ses parents.  
     * @param _em
     * @return
     */
    private boolean hasChildrenToExport(ElementModel _em) {
        if (_em.getEnfant() == null) {
            return false;
        } else {
            for (String s : _em.getEnfant().getEnfants().keySet()) {
                SimpleElement se = _em.getEnfant().getEnfants().get(s);
                // La configuration de l'élément concerné
                ExportElementModel eem = null;
                for (ExportElementModel e : cf.getExportModel().getEnfants()) {
                    eem = getEEMFromId(e, se.getElement());
                    if (eem != null) {
                        break;
                    }
                }
                if (shouldEEMBeExported(eem)) {
                    return true;
                }
            }
            return false;
        }
    }

    public JDialog getParentFrame() {
        return parentFrame;
    }

    public void setParentFrame(JDialog parentFrame) {
        this.parentFrame = parentFrame;
    }
}
