/*
 * Copyright
 *   2005 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 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.plugins.paye;

import java.sql.Connection;
import java.sql.ResultSet;
import java.text.DecimalFormat;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
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.dgfip.utils.xml.InvalidPathExpressionException;
import fr.gouv.finances.dgfip.utils.xml.PathNotFoundException;
import fr.gouv.finances.dgfip.utils.xml.xpath.FunctionResolver;
import fr.gouv.finances.dgfip.xemelios.common.config.ChampModel;
import fr.gouv.finances.dgfip.xemelios.common.config.ListeResultatModel;
import fr.gouv.finances.dgfip.xemelios.common.config.PropertyModel;
import fr.gouv.finances.dgfip.xemelios.data.DataConfigurationException;
import fr.gouv.finances.dgfip.xemelios.data.DataLayerManager;
import fr.gouv.finances.cp.xemelios.data.impl.MySqlDataLayer;
import fr.gouv.finances.dgfip.xemelios.data.impl.sqlconfig.TDocument;
import fr.gouv.finances.dgfip.xemelios.data.impl.sqlconfig.TEtat;
import fr.gouv.finances.dgfip.xemelios.auth.XemeliosUser;
import fr.gouv.finances.dgfip.xemelios.plugins.ExportPlugin;
import java.io.OutputStreamWriter;
import java.io.Writer;
import org.apache.commons.lang.StringEscapeUtils;

/**
 * @author chm
 */
public class ExportLignes extends ExportPlugin {

    private XemeliosUser user;

    private static Logger logger = Logger.getLogger(ExportLignes.class);
    public static final transient String PROPERTY_REQUEST = "request";
    public static final transient String PROPERTY_EXPORT_XPATH = "export.xpath";
    public static final transient String PROPERTY_HEADER1 = "header.field.1";
    public static final transient String PROPERTY_HEADER2 = "header.field.2";
    public static final transient String PROPERTY_FILE_EXTENSION = "default.file.extension";
    
    private String request, exportXPath, value1, value2, fileExtension;
    private String separator;

    private Writer writer;
    
    private MySqlDataLayer layer = null;
    private TDocument pc = null;
    private TEtat ec = null;
    private XPathFactory xpf = null;
    
    //private DecimalFormatSymbols dfs;
    DecimalFormat dfAmount = new DecimalFormat("#0.00");
    DecimalFormat dfFloat = new DecimalFormat("#0.000");
    
    public ExportLignes(XemeliosUser user) {
        super(user);
        try {
            xpf = XPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI);
        } catch(Throwable t) {
            xpf = XPathFactory.newInstance();
        }
        xpf.setXPathFunctionResolver(new FunctionResolver());
    }
    
    @Override
    public void computeParameters() throws Exception {
        for(PropertyModel pm:getPluginModel().getProperties()) {
            if(PROPERTY_REQUEST.equals(pm.getName())) {
                request = pm.getValue();
            } else if(PROPERTY_EXPORT_XPATH.equals(pm.getName())) {
                exportXPath = pm.getValue().replace('{','[').replace('}',']');
            } else if(PROPERTY_HEADER1.equals(pm.getName())) {
                value1=pm.getValue();
            } else if(PROPERTY_HEADER2.equals(pm.getName())) {
                value2=pm.getValue();
            } else if(PROPERTY_FILE_EXTENSION.equals(pm.getName())) {
                fileExtension = pm.getValue();
            }
        }
        separator = getPluginModel().getSeparator();
        Hashtable p = getParameters();
        for(Enumeration enumer=p.keys();enumer.hasMoreElements();) {
            String key = (String)enumer.nextElement();
            logger.debug(key+"/"+p.get(key));
        }
        logger.debug("separator="+separator);
        try {
            layer = (MySqlDataLayer)DataLayerManager.getImplementation();
            pc = layer.getPersistenceConfig(getDocumentModel(),getUser()).getLayer("mysql").getDocument(getDocumentModel().getId());
            ec = pc.getEtat(getEtatModel().getId());
        } catch(Throwable t) {
            t.printStackTrace();
        }
    }
    protected String getParameterizedRequest(final String rq) throws DataConfigurationException {
        String data = rq;
        // first, change table name
        int posParam = data.indexOf("${");
        while(posParam>=0) {
            int posEnd = data.indexOf('}',posParam);
            if(posEnd<0) throw new DataConfigurationException("Invalid request: "+rq);
            String paramName = data.substring(posParam+2,posEnd);
            String paramValue = null;
            if(paramName.startsWith("table-")) {
                String typeTable = paramName.substring(6);
                if(typeTable.startsWith("index-")) {
                    String indexId = typeTable.substring(6);
                    paramValue = ec.getIndexTable(indexId).getBaseName();
                } else {
                    throw new DataConfigurationException("can only deal with index tables: ${"+paramName+"}");
                }
            } else if(paramName.equals("collectivite")) {
                paramValue=StringEscapeUtils.escapeSql(getCollectivite().key);
            } else if(paramName.equals("budget")) {
                paramValue = StringEscapeUtils.escapeSql(getBudget().key);
            } else {
                throw new DataConfigurationException("can only deal with tables parameters: ${"+paramName+"}");
            }
            if(paramValue==null) throw new DataConfigurationException("no value found for param ${"+paramName+"}");
            data = data.replaceAll("\\$\\{"+paramName+"\\}",paramValue);
            // next round
            posParam = data.indexOf("${");
        }
        logger.debug(data);
        
        // next, change parameters
        int pos = data.indexOf("##");
        while(pos>=0) {
            int endPos=data.indexOf("##",pos+2);
            if(endPos<0) throw new DataConfigurationException("invalid expression near "+data.substring(pos));
            String pattern=data.substring(pos,endPos+2);
            String paramName = pattern.substring(2,pattern.length()-2);
//            logger.debug("itemName="+itemName);
            String paramValue=getParameters().get(paramName);
            if (paramValue!=null) {
                data = data.replace(pattern,paramValue);
            } else {
                data = data.replace(pattern,"\"\"");
            }
            pos = data.indexOf("##");
        }
        return data;
    }
    protected String getParameterizedXPath(final String rq,final String element) {
        String data = rq;
        int pos = data.indexOf("##");
        while(pos>=0) {
            int endPos=data.indexOf("##",pos+2);
            String pattern=data.substring(pos,endPos+2);
            String paramName = pattern.substring(2,pattern.length()-2);
            String paramValue = null;
            if("ELEMENT".equals(paramName)) {
                paramValue = element;
            } else {
                paramValue = getParameters().get(paramName);
            }
            if (paramValue!=null) {
                data = data.replace(pattern,paramValue);
            } else {
                data = data.replace(pattern,"\"\"");
            }
            pos = data.indexOf("##");
        }
        return data;
    }
    /* (non-Javadoc)
     * @see fr.gouv.finances.cp.xemelios.plugins.ExportPluginRunner#doExport()
     */
    @Override
    public Long doExport() throws Exception {
        logger.debug("entering doExport");
        Vector<String> docs = new Vector<String>();
        Vector<String> matricules = new Vector<String>();
        String sql = getParameterizedRequest(request);
        logger.debug("SQL: "+sql);
        ResultSet rs = null;
        Connection con = null;
        try {
            rs = layer.execute(sql);
            con = rs.getStatement().getConnection();
            while(rs.next()) {
                String docId = rs.getString(1);
                logger.debug("found element "+docId);
                docs.add(docId);
                matricules.add(rs.getString(2));
            }
            //StringBuffer buff = new StringBuffer();
            if(getPluginModel().getEntete()!=null) {
                //buff.append(getPluginModel().getEntete().getLibelle()).append("\r\n");
                getWriter().write(getPluginModel().getEntete().getLibelle());
                getWriter().write("\r\n");
            }
            if(getProgressListener()!=null) {
                getProgressListener().setProgressInfos(docs.size()+1,getPluginModel().getPluginTitle());
                getProgressListener().setProgressVisible(true);
            }
            long count = 0;
            for(int i=0;i<docs.size();i++) {
                String docId = docs.elementAt(i);
                String matricule = matricules.elementAt(i);
                logger.debug("exporting data for element "+docId);
                if(getProgressListener()!=null)
                    getProgressListener().pushProgress();
                String xPath = getParameterizedXPath(exportXPath,docId);
                logger.debug("Export: "+xPath);
                Document doc = layer.getDocumentAsDom(getEtatModel(),getCollectivite(),getBudget(),docId,getUser());
                XPath pather = xpf.newXPath();
                pather.setNamespaceContext(getDocumentModel().getNamespaces());
                NodeList list = (NodeList)pather.evaluate(xPath,doc,XPathConstants.NODESET);
                for(int line=0;line<list.getLength();line++) {
                    Element el = (Element)list.item(line);
                    getWriter().write(writeLine(el,matricule));
                    count++;
                }
                rs.close();
                logger.debug("element "+docId+" has "+count+" lines");
            }
            return new Long(count);
        } finally {
            getWriter().flush();
            if(con!=null) layer.releaseConnection(con);
        }
    }
    protected String getLineHeader(String element) {
        StringBuffer sb = new StringBuffer();
        if(value1!=null) {
            sb.append(getParameters().get(value1));
        }
        sb.append(getSeparator());
        if(value2!=null) {
            sb.append(getParameters().get(value2));
        }
        sb.append(getSeparator());
        sb.append(element).append(getSeparator());
        return sb.toString();
    }
    private String getSeparator() { return separator; }
    @Override
    public String getDefaultFileExtension() {
        return fileExtension;
    }
    protected String writeLine(Element el,String matricule) throws DataConfigurationException {
        StringBuilder sb = new StringBuilder();
        sb.append(getLineHeader(matricule));
        ListeResultatModel lrm = getPluginModel().getListeExport();
        for(Enumeration<ChampModel> enumer=lrm.getListeChamps().elements();enumer.hasMoreElements();) {
            ChampModel cm = enumer.nextElement();
            try {
                Object sValue = cm.getPath().getDomPath(getDocumentModel().getNamespaces()).getValue(el,false);
                sb.append(formatValue(sValue,cm.getDatatype()));
            } catch(PathNotFoundException pnfEx) {
//    			logger.warn("path unknown: "+cm.getPath().getPath());
            } catch (InvalidPathExpressionException ipeEx) {
                throw new DataConfigurationException(ipeEx);
            }
            sb.append(getSeparator());
        }
        sb.append(System.getProperty("line.separator"));
        return sb.toString();
    }
    protected String formatValue(Object value,String datatype) {
        if(value instanceof String) {
            String ret = (String)value;
            if("amount".equals(datatype)) {
                Float f = Float.valueOf(ret);
                String s = dfAmount.format(f);
                ret = s.replaceAll("\\.",",");
            } else if("decimal".equals(datatype)) {
                Float f = Float.valueOf(ret);
                String s = dfFloat.format(f);
                ret = s.replaceAll("\\.",",");
            }
            return ret;
        } else if(value!=null) {
            return value.toString();
        } else {
            return "";
        }
    }

    protected Writer getWriter() {
        if(writer==null) {
            writer = new OutputStreamWriter(getOutputStream());
        }
        return writer;
    }
}
