/*
 * Copyright
 *   2007 axYus - www.axyus.com
 *   2005 C.Marchand - christophe.marchand@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.ui.resulttable;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
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.utils.xml.xpath.FunctionResolver;
import fr.gouv.finances.cp.xemelios.common.config.ChampModel;
import fr.gouv.finances.cp.xemelios.common.config.ListeResultatModel;
import fr.gouv.finances.cp.xemelios.data.DataAccessException;
import fr.gouv.finances.cp.xemelios.data.DataConfigurationException;
import fr.gouv.finances.cp.xemelios.data.DataResultSet;

/**
 * Permet de stocker un element et sa valeur cible pour le tri
 * @author chm
 */
public class DataHandler implements Comparable {
	public static final transient int ORDER_NO = 0;
	public static final transient int ORDER_DESC = 1;
	public static final transient int ORDER_ASC = -1;
	
	public static long timeCountLocal = 0;
	public static long timeCountExtern = 0;
	
	private static Logger logger = Logger.getLogger(DataHandler.class);
	private static XPathFactory xpf = null;
    static {
    	try {
    		xpf = XPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI);
    	} catch(Throwable t) {
    		xpf = XPathFactory.newInstance();
    	}
        xpf.setXPathFunctionResolver(new FunctionResolver());
    }

	private Element el;
	private Object value;
	private ListeResultatModel lrm;
	private int orderByCol, sort;
	private DataResultSet drs;
	private String resultKey;
	private Comparable[] cachedData;
	private CachedData globalData;
	private Object[] innerData;
	private Document document = null;
        private String documentEncoding = null;
	private XPath xPath = null;
	private int positionsFilled = 0;
	
	public DataHandler(ListeResultatModel lrm, Element el,DataResultSet drs, String resultKey, CachedData globalData) {
		super();
		this.lrm=lrm;
		this.el=el;
		this.drs=drs;
		this.resultKey=resultKey;
		this.globalData=globalData;
		xPath = xpf.newXPath();
		cachedData = new Comparable[lrm.getListeChamps().size()+lrm.getHiddens().size()];
		innerData = new Object[cachedData.length];
		setOrderByColumn(getColumnTri(lrm.getTriDefaut()),getOrdreTri(lrm.getOrdre()));
	}
	
	
	public void setOrderByColumn(int index,int sort) {
		orderByCol = index;
		this.sort = sort; 
		try {
			computeValue();
		} catch(Exception ex) {
			logger.error("setOrderByColumn",ex);
		}
	}
	public void setOrderByColumn(String colId, int sort) {
		Vector<ChampModel> chps = lrm.getListeChamps();
		String sColId = getColumnTri(colId);
//		if(colId.indexOf(',')>=0) sColId = colId.substring(0,colId.indexOf(','));
		for (int i = 0; i < chps.size(); i++) {
			if(chps.elementAt(i).getId().equals(sColId)) {
				setOrderByColumn(i,sort);
				break;
			}
		}
	}
	public int getOrderByColumn() { return orderByCol; }
	public int getSortOrder() { return sort; }
	protected void computeValue() throws ParseException {
		value=getValueAtColumn(orderByCol);
	}
	public Object getValue() throws ParseException {
		if(value==null) computeValue();
		return value;
	}
	@SuppressWarnings("unchecked")
	public int compareTo(Object arg0) {
	    if(arg0==this) return 0;
		if(arg0 instanceof DataHandler) {
			DataHandler other = (DataHandler)arg0;
			int ret = 0;
			try {
			    Comparable maValeur = (Comparable)getValue();
			    Comparable autreValeur = (Comparable)other.getValue();
			    if(maValeur==null) ret = -1;
			    else if(autreValeur==null) ret = 1;
			    else {
			        ret = maValeur.compareTo(autreValeur);
			    }
			     
			} catch(ParseException pEx) { /* on est oblige d'ignorer */ }
			if(ret==0) {
				// on ne veut vraiment pas que les choses soient �gales
				// on rajoute un "a" a l'autre si les instances sont differentes
				if(arg0!=this) {
					ret = -1;
				}
			}
			return (sort*ORDER_ASC)*ret;
		}
		return -1;
	}
	@Override
	public boolean equals(Object arg0) {
		return compareTo(arg0)==0;
	}
	public Object getValueAtColumn(int pos) throws ParseException {
		Object ret = "<error>";
		if(innerData[pos]==null) {
		    Comparable computed = null;
			String path ;
                        if(pos<lrm.getListeChamps().size()) {
                            path = lrm.getListeChamps().get(pos).getPath().getPath();
                        } else {
                            path = lrm.getHiddens().get(pos - lrm.getListeChamps().size()).getPath().getPath();
                        }
			long now = System.currentTimeMillis();
			if(path.startsWith("/")) {
				try {
				    ret = queryCachedData(path);
				} catch(Exception ex) {
					logger.error("querying document: ",ex);
					globalData.setCachedData(resultKey,path,ret);
				}
			    timeCountExtern+=(System.currentTimeMillis()-now);
			} else {
				try {
                                    if(pos < lrm.getListeChamps().size()) {
					if(!ChampModel.DATATYPE_PJ.equals(lrm.getListeChamps().get(pos).getDatatype())) {
					    DomPath domPath = lrm.getListeChamps().get(pos).getPath().getDomPath(lrm.getParent().getParent().getParent().getNamespaces());
					    ret = domPath.getValue(el,true);
					} else {
						PjRefHandler pjHandler = new PjRefHandler();
                                                xPath.setNamespaceContext(lrm.getParent().getParent().getParent().getNamespaces());
						NodeList nl = (NodeList)xPath.evaluate(lrm.getListeChamps().get(pos).getPath().getFormattedPath(),el,XPathConstants.NODESET);
						for(int i=0;i<nl.getLength();i++) {
							Element el = (Element)nl.item(i);
							pjHandler.addPj(lrm.getParent().getParent().getParent(),el);
						}
						ret = pjHandler;
					}
                                    } else {
                                        DomPath domPath = lrm.getHiddens().get(pos - lrm.getListeChamps().size()).getPath().getDomPath(lrm.getParent().getParent().getParent().getNamespaces());
                                        ret = domPath.getValue(el,true);
                                    }
				} catch(Exception ex) {
					logger.error("getValueAtColumn",ex);
					globalData.setCachedData(resultKey,path,ret);
				}
			    timeCountLocal+=(System.currentTimeMillis()-now);
			}
			// TODO: verifier l'usage de ca
		    if(ret==null || ret.toString().length()==0) {
		    	computed = null;
		    	innerData[pos]="";
		    }
		    else {
				Class classToReturn;
                                if(pos>=lrm.getListeChamps().size()) {
                                    classToReturn = String.class;
                                } else {
                                    classToReturn = lrm.getListeChamps().get(pos).getDataClass();
                                }
				if(classToReturn.equals(Float.class)) {
				    try {
				        computed = new Float(Float.parseFloat((String)ret));
				    } catch(NumberFormatException nfEx) {
				        computed = new Float(Float.NaN);
				    }
				}
				else if(classToReturn.equals(Integer.class)) {
				    try {
				        computed = new Integer(Integer.parseInt((String)ret));
				    } catch(NumberFormatException nfEx) {
				        computed = "NaN";
				    }
				}
				else if(classToReturn.equals(Amount.class)) {
				    try {
				        computed = new Amount((String)ret);
				    } catch(NumberFormatException nfEx) {
				        computed = new Amount(Amount.NaN);
				    }
				}
				else if(classToReturn.equals(java.util.Date.class)) {
				    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//				    SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
				    computed = sdf.parse((String)ret);
				} else if(classToReturn.equals(PjRefHandler.class)) {
					computed = (Comparable)ret;
				} else computed = ret.toString();	// pour les trucs qui sont vraiment pas triables !
		    }
			cachedData[pos]=computed;
			if(innerData[pos]==null) innerData[pos]=ret;
			positionsFilled++;
			if(positionsFilled==cachedData.length) {
				document=null;
//				logger.debug("freeing document");
			}
		}
		return cachedData[pos];
	}
	public Object getInnerDataAt(int pos) {
	    return innerData[pos];
	}
	public Element getElement() { return el; }
	public Document getDocument() throws DataConfigurationException, DataAccessException {
	    if(document==null) {
	    	// TODO: get document from DataResultSet
	        //document=DataLayerManager.getImplementation().getDocumentAsDom(drs.getEtatModel(),drs.getCollectivite(),drs.getBudget(),resultKey);
	    	document = drs.getDocument(resultKey);
	    }
	    return document;
	}
        public String getDocumentEncoding() throws DataConfigurationException, DataAccessException {
            if(documentEncoding==null) documentEncoding = drs.getDocumentEncoding(resultKey);
            return documentEncoding;
            
        }
	private Object queryCachedData(String path) throws DataConfigurationException, DataAccessException {
	    Object ret = globalData.getCachedData(resultKey,path);
	    if(ret==null) {
	        try {
	            ret = xPath.evaluate(path,getDocument());
	        } catch(XPathExpressionException xpExpr) {
	            throw new DataConfigurationException(xpExpr);
	        }
			globalData.setCachedData(resultKey,path,ret);
	    }
	    return ret;
	}
	/**
	 * This method returns the first column of the <tt>orderByColumns</tt>, which is a comma-separated list
	 * @param orderByColumns
	 * @return
	 */
	public static String getColumnTri(String orderByColumns) {
		StringTokenizer tokenizer = new StringTokenizer(orderByColumns,",");
		if(tokenizer.hasMoreTokens()) return tokenizer.nextToken();
		return null;
	}
	public static int getOrdreTri(String orderByOrdres) {
		StringTokenizer tokenizer = new StringTokenizer(orderByOrdres,",");
		if(tokenizer.hasMoreTokens()) {
			return "DESC".equals(tokenizer.nextToken())?ORDER_DESC:ORDER_ASC;
		}
		return ORDER_ASC;
	}
}
