/*
 * Copyright 
 *   2007 axYus - www.axyus.com
 *   2007 N.Le Corre - nicolas.lecorre@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 General Public License as published by
 * the Free Software Foundation; either version 2 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU 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.Date;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;

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

import jxl.Cell;
import jxl.CellType;
import jxl.LabelCell;
import jxl.Range;
import jxl.Workbook;
import jxl.WorkbookSettings;
import jxl.format.CellFormat;
import jxl.write.Blank;
import jxl.write.DateFormat;
import jxl.write.Label;
import jxl.write.NumberFormats;
import jxl.write.WritableCell;
import jxl.write.WritableCellFormat;
import jxl.write.WritableFont;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
import jxl.write.WriteException;
import jxl.write.biff.RowsExceededException;

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.Amount;
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;

public class Exporter extends SwingWorker {
	private static Logger logger = Logger.getLogger(Exporter.class);
	
	/**
	 * Le nombre maximum de ligne que l'on écrit par feuille excel
	 */
	private static final int MAX_ROWS_PER_SHEET = 65000;
	
	private JDialog parentFrame = null;
	
//	private EtatResultTableModel ertm;
	private DataResultSet drs = null;
	private File fDest;
	private ConfigModel cf = null;
	private DocumentModel dm = null;
	private String etatId;
	private ElementModel em = null;
	
	// formattage des colonnes d'entête
	WritableFont fontEntete1 = new WritableFont(WritableFont.TIMES, 16, WritableFont.BOLD, false);
	WritableFont fontEntete2 = new WritableFont(WritableFont.TIMES, 12, WritableFont.BOLD, false);
	WritableFont fontAutre = new WritableFont(WritableFont.TIMES, 10);
	
	// contiendra la liste des sheets XLS, dans l'ordre devant être créé (par ex : SHEET_Bordereau, SHEET_Titre, SHEET_Ligne)
	private Hashtable<String, Vector<WritableSheet>> sheets = new Hashtable<String, Vector<WritableSheet>>();
	
	// 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, Vector<String>> exportedElements = new Hashtable<String, Vector<String>>();
	
	/**
	 * 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 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);
	}
	private int getLastLine (String el) {
		Integer l = lignesExporteesForElement.get(el); 
		if (l == null) {
			l = new Integer (0);
		}
		return l.intValue();
	}

	WritableWorkbook workbook = null;
	
	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;
	}
	
	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;
	}
	
	@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
		sheets.clear();
		sheets = null;
		exportedElements.clear();
		exportedElements = null;
		workbook = 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 {
			/**
			 * Création du workbook
			 */
	WorkbookSettings ws = new WorkbookSettings();
	ws.setUseTemporaryFileDuringWrite(true);
	ws.setTemporaryFileDuringWriteDirectory(new File("."));
	ws.setCellValidationDisabled(true);
	ws.setGCDisabled(false);
			workbook = Workbook.createWorkbook(fDest, ws);
			
			/**
			 * Création des feuilles du classeur
			 */
			for (ExportElementModel eem:cf.getExportModel().getEnfants()) {
				buildSheet (eem, workbook);
			}
			/**
			 * 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());
			 // recopie des entêtes dans les nouvelles feuilles créées
             if(!isCancelled()) {
                enteteNewSheets();
             }
		} catch (Exception e) {
			logger.error("",e);
		} finally {
			if (workbook!=null) {
				try {
					workbook.write();
					workbook.close();
				} catch (Throwable t) {}
			}
		}
	}
	
	/**
	 * 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 (getLastSheet(sheetId) != null) {
			for (int i=(parents.size()-1); i>=0; i--) {
				col = writeCellsOfParent (dm.getEtatById(etatId).getElementById(parents.get(i).getElement()), parents.get(i).getPseudoCompletePath(), _dh, sheetId, _ligne, col, (_ligne==2), false);
				getLastSheet(sheetId).mergeCells(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) {
	        				writeCell (sheetId, _em.getTitre(), 0, col, true);
	        			}
	        			writeCell (sheetId, ecm.getLibelle(), 1, col, true);
	        		}				
					writeCell (sheetId, _dh.getValueAtColumn(getColumnIndex(ecm.getId(), _em)), _ligne, col, false);
					col++;
					firstCol = false;
				}
			}
			
			getLastSheet(sheetId).mergeCells(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
			Vector<String> v = exportedElements.get(_em.getId());
			if (v == null) v = new Vector<String>();
	//		String vId = getIdentifiant (eem, _dh, _em);
			String vId = getIdentifiant (_dh, _em);
			if (!v.contains(vId)) {
				v.add(vId);
			}
			
			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;
		WritableSheet _ws = getLastSheet(sheetId);
		int ligne = (_ws.getRows()<2?2:_ws.getRows());
//		// é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);
			WritableSheet ws = getLastSheet(parentToExport.getElement());
			// si ws est null, c'est un parent qu'on ne veux pas exporter
			if (ws == null) continue;
			int ligne = (ws.getRows()<2?2:ws.getRows());
			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) {
			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);
			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) {
		Vector<String> v = exportedElements.get(idColl);
		if (v == null) return false;
		else {
			if (v.contains(id)) return true;
			else return false;
		}
	}

	private void majExportes (String id, DataHandler _dh, String parentPath, ElementModel em) throws Exception {
		Vector<String> v = exportedElements.get(id);
		if (v == null) {
			v = new Vector<String>();
			exportedElements.put (id, v);
		}
		String vId = getIdentifiant (_dh, parentPath, em);
		if (!v.contains(vId)) {
			v.add(vId);
		}		
	}
	/**
	 * 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);
        WritableSheet _ws = getLastSheet(sheetId);
        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)
        			{
        				writeCell (sheetId, parent.getTitre(), 0, _col, true);
        			}
        			writeCell (sheetId, cm.getLibelle(), 1, _col, true);
        		}
        		writeCell (sheetId, o, _ligne, _col++, false);
        		firstCol = false;
        	}
        }
        if (completeLine)
        	addLine(sheetId);
        
        // merge des cellules
        if (firstLine) {
			_ws.mergeCells(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>();
//		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());
        NodeList nodeListe = (NodeList)xp.evaluate(enfantPath,rootElement,XPathConstants.NODESET);
        WritableSheet _ws = getLastSheet(sheetId);
		// 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;
				writeCell (sheetId, ptmp.getLibelle(), 0, colTitre, true);
				
				for (ExportChampModel ecm:ptmp.getChamps()) {
					if (ecm.isIdentifiant()) {
						writeCell (sheetId, ecm.getLibelle(), 1, colTitre++, true);
					}
				}
				_ws.mergeCells(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++) {
    			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)
	        			{
	        				writeCell (sheetId, enfant.getTitre(), 0, macolonne, true);
	        			}
	        			writeCell (sheetId, ecm.getLibelle(), 1, macolonne, true);
	        		}
	        		writeCell (sheetId, o, maligne, macolonne++, false);
	        		firstCol = false;
	        	}
	        }
	        addLine(sheetId);

	        maligne++;
	        maxCol = macolonne;
	        macolonne = col;
	        // merge des cellules de l'entête
	        if (firstLine) {
	        	firstLine = false;
				_ws.mergeCells(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 = "";
	/**
	 * Crée un <tt>WritableSheet</tt> et ajoute la liste des entetes de colonne
	 * @param eem
	 * @param workbook
	 * @throws Exception
	 */
	private void buildSheet (ExportElementModel eem, WritableWorkbook workbook) throws Exception {
		if (shouldEEMBeExported(eem)) {
			Vector<WritableSheet> v = sheets.get(eem.getId());
			if (v == null) v = new Vector<WritableSheet>();
			sheets.put(eem.getId(), v);
			v.add (workbook.createSheet(eem.getLibelle(), workbook.getNumberOfSheets()));
			
			for (ExportElementModel e:eem.getEnfants()) {
				buildSheet (e, workbook);
			}
			firstSheetId = eem.getId(); 
		}
	}
	
	private WritableSheet getLastSheet (String sheetId) {
		Vector<WritableSheet> v = sheets.get (sheetId);
		return v.get (v.size()-1);
	}
	
	private WritableSheet getRootSheetBefore (String id) {
		WritableSheet ret = null;
		ElementModel e = getEMFromId(id);
		if (e.getSimpleParent()!=null) {
			String pName = e.getSimpleParent().getElement();
			ret = sheets.get(pName).get(0);
		}
		return ret;
		
/*		
		String sName = sheets.get(id).get(0).getName();
		String[] names = workbook.getSheetNames();
		for (int i=0; i<names.length; i++) {
			if (names[i].equals(sName)) {
				if (i>0) {
					ret = workbook.getSheet(names[i-1]);
					break;
				}
			}
		}		
		return ret;
*/	}
	
	private WritableSheet splitSheet (String id) {
		Vector<WritableSheet> v = sheets.get (id);
		WritableSheet wsRef = v.get(0);
		
		WritableSheet ws = workbook.createSheet(wsRef.getName()+"_"+(v.size()), afterSheet (v.get(v.size()-1).getName()));
		v.add (ws);
		return workbook.getSheet(workbook.getNumberOfSheets()-1);
	}
	
	private void enteteNewSheets () throws RowsExceededException, WriteException {
		for (Vector<WritableSheet> v:sheets.values()) {
			if (v.size()>1) {
				WritableSheet wsRef = v.get(0);
				for (int iv=1; iv<v.size(); iv++) {
					WritableSheet ws = v.get(iv);
					for (int i = 0 ; i < 2 ; i++)
					{
						for (int j = 0 ; j < wsRef.getColumns() ; j++)
						{
							WritableCell readCell = (WritableCell)wsRef.getCell(j, i);
							WritableCell newCell = readCell.copyTo(j, i);
							CellFormat readFormat = readCell.getCellFormat();
							if (readFormat!=null) {
								WritableCellFormat newFormat = new WritableCellFormat(readFormat);
								newCell.setCellFormat(newFormat);
							}
							ws.addCell(newCell);
						}
					}
					
					for (int mc=0; mc<wsRef.getMergedCells().length; mc++) {
						Range r = wsRef.getMergedCells()[mc];
						if (r.getTopLeft().getColumn()<r.getBottomRight().getColumn()) {
							if (r.getTopLeft().getRow()<r.getBottomRight().getRow()) {
								ws.mergeCells(r.getTopLeft().getColumn(), r.getTopLeft().getRow(), r.getBottomRight().getColumn(), r.getBottomRight().getRow());
							} else {
								ws.mergeCells(r.getTopLeft().getColumn(), r.getBottomRight().getRow(), r.getBottomRight().getColumn(), r.getTopLeft().getRow());
							}
						} else {
							if (r.getTopLeft().getRow()<r.getBottomRight().getRow()) {
								ws.mergeCells(r.getBottomRight().getColumn(), r.getTopLeft().getRow(), r.getTopLeft().getColumn(), r.getBottomRight().getRow());
							} else {
								ws.mergeCells(r.getBottomRight().getColumn(), r.getBottomRight().getRow(), r.getTopLeft().getColumn(), r.getTopLeft().getRow());
							}
						}
					}
				}
			}
		}
	}
	
	private int afterSheet (String id) {
		String[]names = workbook.getSheetNames();
		for (int i=1; i<names.length; i++) {
			if (names[i].equals(id)) return i+1;
		}
		return 1;
	}
	
	/**
	 * 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;
		}
	}
	
	/**
	 * Rempli une cellule avec la valeur correctement formatée.
	 * @param sheet
	 * @param o
	 * @param ligne
	 * @param col
	 * @throws WriteException
	 */
	private void writeCell (String sheetId, Object o, int line, int col, boolean bypassLineRenum) throws WriteException {
		int newline = 0;
		if (bypassLineRenum) {
			newline = line;
		} else {
			int _ligne = getLastLine(sheetId);
			if (_ligne>=(MAX_ROWS_PER_SHEET*sheets.get(sheetId).size())) {
				splitSheet (sheetId);
			}
			
			// renumérotation de la ligne
			newline = _ligne;
			newline=_ligne%(MAX_ROWS_PER_SHEET)+2;
		}
		WritableSheet sheet = getLastSheet(sheetId);
		
		 if (o instanceof java.lang.Integer) {
			 WritableCellFormat integerFormat = new WritableCellFormat ((newline==0?fontEntete1:newline==1?fontEntete2:fontAutre), NumberFormats.INTEGER);
			 jxl.write.Number number = new jxl.write.Number(col, newline, (Integer)o, integerFormat);
			 sheet.addCell(number); 
		 } else if (o instanceof java.lang.Float) {
			 WritableCellFormat floatFormat = new WritableCellFormat ((newline==0?fontEntete1:newline==1?fontEntete2:fontAutre), NumberFormats.FLOAT);
			 jxl.write.Number number = new jxl.write.Number(col, newline, (Float)o, floatFormat);
			 sheet.addCell(number);						 
		 } else if (o instanceof java.util.Date) {
			 DateFormat myDateFormat = new DateFormat (System.getProperty(Constants.SYS_PROP_DATE_FORMAT));
			 WritableCellFormat dateFormat = new WritableCellFormat ((newline==0?fontEntete1:newline==1?fontEntete2:fontAutre), myDateFormat);
			 jxl.write.DateTime date = new jxl.write.DateTime(col, newline, (Date)o, dateFormat);
			 sheet.addCell(date);
		 } else if (o instanceof Amount) {
			 WritableCellFormat floatFormat = new WritableCellFormat ((newline==0?fontEntete1:newline==1?fontEntete2:fontAutre), NumberFormats.FLOAT);
			 jxl.write.Number number = new jxl.write.Number(col, newline, ((Amount)o).doubleValue(), floatFormat);
			 sheet.addCell(number);
		 } else {
			 jxl.write.Label label = new jxl.write.Label(col, newline, (String)o, new WritableCellFormat (newline==0?fontEntete1:newline==1?fontEntete2:fontAutre));
			 sheet.addCell(label);
		 }		
	}
	public JDialog getParentFrame() {
		return parentFrame;
	}
	public void setParentFrame(JDialog parentFrame) {
		this.parentFrame = parentFrame;
	}
}
