/*
 * Copyright
 *   2007 axYus - www.axyus.com
 *   2007 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.ui.xhtmlviewer;

import fr.gouv.finances.dgfip.utils.IoUtils;
import fr.gouv.finances.dgfip.utils.NavigationContext;
import fr.gouv.finances.dgfip.utils.Pair;
import fr.gouv.finances.dgfip.utils.Dest;
import fr.gouv.finances.cp.utils.StartUrl;
import fr.gouv.finances.cp.utils.ui.DisplayExceptionDlg;
import fr.gouv.finances.dgfip.xemelios.common.Constants;
import fr.gouv.finances.dgfip.xemelios.common.DematTransform;
import fr.gouv.finances.dgfip.xemelios.common.NetAccess;
import fr.gouv.finances.dgfip.xemelios.common.PJRef;
import fr.gouv.finances.dgfip.xemelios.common.ToolException;
import fr.gouv.finances.dgfip.xemelios.common.config.DocumentModel;
import fr.gouv.finances.dgfip.xemelios.common.config.ElementModel;
import fr.gouv.finances.dgfip.xemelios.common.config.EtatModel;
import fr.gouv.finances.dgfip.xemelios.data.DataAccessException;
import fr.gouv.finances.dgfip.xemelios.data.DataConfigurationException;
import fr.gouv.finances.dgfip.xemelios.data.DataImpl;
import fr.gouv.finances.dgfip.xemelios.data.DataLayerManager;
import fr.gouv.finances.cp.xemelios.ui.HtmlViewer;
import fr.gouv.finances.cp.xemelios.ui.MainWindow;
import fr.gouv.finances.cp.xemelios.ui.UpdatableToolbar;
import fr.gouv.finances.dgfip.xemelios.auth.UnauthorizedException;
import fr.gouv.finances.dgfip.xemelios.auth.XemeliosUser;
import fr.gouv.finances.dgfip.xemelios.common.config.Loader;
import fr.gouv.finances.dgfip.xemelios.common.config.customLinks.CustomLinkParameters;
import fr.gouv.finances.dgfip.xemelios.common.config.customLinks.CustomLinkResolver;
import fr.gouv.finances.dgfip.xemelios.common.config.customLinks.UnresolvedException;
import java.awt.Component;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.xhtmlrenderer.swing.NaiveUserAgent;

/**
 *
 * @author chm
 */
public class XemeliosUserAgent extends NaiveUserAgent {
    private static final Logger logger = Logger.getLogger(XemeliosUserAgent.class);
    public static final int MAX_VISITED_URIS=100;
    LinkedHashMap<String,Object> visitedUris;
    private int index = -1;
    private ArrayList<String> history = new ArrayList<String>();
    private String baseUrl;
    private UpdatableToolbar toolbar;
    protected NavigationContext nc;
    private MainWindow mainWindow;
//    private HtmlViewer viewer;
    private Component viewer;
    private XPathFactory xpf;
    private XemeliosUser user;

    public XemeliosUserAgent(MainWindow mw, Component viewer, UpdatableToolbar toolbar, XemeliosUser user) {
        this(viewer,user);
        this.mainWindow=mw;
        this.toolbar=toolbar;
        xpf = XPathFactory.newInstance();
    }
    protected XemeliosUserAgent(Component viewer,XemeliosUser user) {
        super();
        this.user=user;
        this.viewer=viewer;
        visitedUris = new LinkedHashMap<String,Object>(16,0.75f,true) {
            private static final long serialVersionUID = 1L;
            @Override
            protected boolean removeEldestEntry(Entry<String, Object> eldest) {
                return size()==MAX_VISITED_URIS;
            }
        };
    }
    @Override
    public boolean isVisited(String uri) {
        return visitedUris.containsKey(uri);
    }
    @Override
    public String resolveURI(String uri) {
        if(uri==null) return getBaseURL();
        if(uri.startsWith("xemelios:")) {
            logger.debug("resolving "+uri);
            String shortUri;
            String anchor = null;
            int indexSharp = uri.indexOf("#");
            if(indexSharp>0) {
                shortUri = uri.substring(0,indexSharp);
                anchor = uri.substring(indexSharp+1);
            } else {
                shortUri = uri;
            }
            Dest dest = (Dest)visitedUris.get(shortUri);
            if(dest==null || "true".equals(System.getProperty("xemelios.navigation.force-reload"))) {
                dest=calculateQuery(shortUri);
                visitedUris.put(shortUri,dest);
            }
            if(dest==null && nc!=null) dest = nc.getLastDestination();
            if(dest!=null && dest.url!=null) {
                if(dest.toExternal) {
                    try {
                        StartUrl.startUrl(dest.url.toExternalForm());
                    } catch(ToolException toEx) {}
                    return null;
                } else {
                    String ret = dest.url.toExternalForm();
                    if(indexSharp>0) {
                        ret += uri.substring(indexSharp);
                    }
                    //if(anchor==null) return ret;
                    //else return ret+"#"+anchor;
                    return ret;
                }
            }
        }
        return super.resolveURI(uri);
    }
    @Override
    public void setBaseURL(String url) {
        super.setBaseURL(url);
        if(baseUrl !=null &&  baseUrl.startsWith("error:")) baseUrl = null;
        baseUrl = url;
        if(baseUrl==null) return;
        if (index >= 0) {
            String historic = (String) history.get(index);
            if (historic.equals(baseUrl)) {
                toolbar.updateToolbarButtons();
                return;//moved in history
            }
        }
        index++;
        for (int i = index; i < history.size(); history.remove(i)) ;
        history.add(index, baseUrl);
        toolbar.updateToolbarButtons();
        toolbar.setBaseUrl(url);
    }
    public String getForward() {
        index++;
        return (String) history.get(index);
    }

    public String getBack() {
        index--;
        return (String) history.get(index);
    }

    public boolean hasForward() {
        return (index + 1 < history.size() && index >= 0);
    }

    public boolean hasBack() {
        return (index > 0);
    }

    @Override
    protected InputStream resolveAndOpenStream(String uri) {
        InputStream retValue=null;
        try {
            URL url = new URL(uri);
            if("http".equals(url.getProtocol())) {
                HttpClient client = NetAccess.getHttpClient();
                HttpMethod meth = new GetMethod(uri);
                int retCode = client.executeMethod(meth);
                if(retCode<=300) {
                    retValue = meth.getResponseBodyAsStream();
                }
            }
        } catch(MalformedURLException muEx) {
            // TODO
        } catch(DataConfigurationException dcEx) {
            // TODO
        } catch(IOException ioEx) {
            // TODO
        }
        if(retValue==null) {
            retValue = super.resolveAndOpenStream(uri);
        }
        return retValue;
    }
    public Dest calculateQuery(String query) {
        NavigationContext ctx = null;
        if(nc==null) {
            ctx=new NavigationContext();
        } else {
            ctx = nc.clone();
        }
        Hashtable<String,Object> params = new Hashtable<String,Object>();
        params.put(HtmlViewer.PARAM_DESTINATION,HtmlViewer.VALUE_DESTINATION_INTERNAL);
        String servlet = null;
        String sTmp = query;
        int start = query.indexOf('?');
        if(start>=0) {
            servlet = query.substring(query.indexOf("/")+1,start);
            sTmp = query.substring(start+1);
        }
        if("query".equals(servlet)) {
            Properties additionalParameters = new Properties();
            StringTokenizer st = new StringTokenizer(sTmp,"&");
            while(st.hasMoreTokens()) {
                String data = st.nextToken();
                logger.debug("parsing "+data);
                int pos = data.indexOf('=');
                if(pos>0) {
                    String paramName = data.substring(0,pos);
                    String paramValue = null;
                    try {
                        //String sTmp = data.substring(pos+1);
                        paramValue=URLDecoder.decode(data.substring(pos+1),"UTF-8");
                        logger.debug(paramName+"="+paramValue);
                    } catch(Throwable t) {
                        logger.error("decoding",t);
                    }
                    if("docId".equals(paramName)) ctx.setDocId(paramValue);
                    else if("etatId".equals(paramName)) ctx.setEtatId(paramValue);
                    else if("elementId".equals(paramName)) ctx.setElementId(paramValue);
                    else if("collectivite".equals(paramName)) ctx.setCollectivite(paramValue);
                    else if("budget".equals(paramName)) ctx.setBudget(paramValue);
                    else if("sp1".equals(paramName)) ctx.setSp1(paramValue);
                    else if("sp2".equals(paramName)) ctx.setSp2(paramValue);
                    else if("path".equals(paramName)) ctx.setPath(paramValue);
                    else {
                        if(paramName.startsWith("xsl:param")) {
                            //                        logger.debug("xsl:param -> "+paramValue);
                            int virg = paramValue.indexOf(',');
                            String pName = paramValue.substring(1,virg);
                            String pValue = paramValue.substring(virg+1,paramValue.length()-1);
                            //                        logger.debug("Adding "+pName+"="+pValue);
                            params.put(pName,pValue);
                        } else {
                            additionalParameters.setProperty(paramName, paramValue);
                        }
                    }
                }
            }
            ctx.configXPath(additionalParameters);
            return generateDestContext(ctx,params,HtmlViewer.INTERNAL_DESTINATION);
        } else if("resource".equals(servlet)) {
//            logger.debug("looking for resource "+sTmp);
            Dest ret = null;
            try {
                File resourceDir = new File(System.getProperty("xemelios.resources.location"));
                File res = new File(resourceDir,sTmp);
                ret = new Dest(res.toURL(),false);
            } catch(Throwable t) {}
            return ret;
        } else if("attachment".equals(servlet)) {
            StringTokenizer st = new StringTokenizer(sTmp,"&");
            String pjCollectivite = ctx.getCollectivite();
            String pjId = null;
            while(st.hasMoreTokens()) {
                String data = st.nextToken();
                logger.debug("parsing "+data);
                int pos = data.indexOf('=');
                if(pos>0) {
                    String paramName = data.substring(0,pos);
                    String paramValue = null;
                    try {
                        //String sTmp = data.substring(pos+1);
                        paramValue=URLDecoder.decode(data.substring(pos+1),"UTF-8");
                    } catch(Throwable t) { }
                    if("collectivite".equals(paramName)) pjCollectivite = paramValue;
                    else if("pjId".equals(paramName)) pjId = paramValue;
                }
            }
            if(pjId!=null && pjCollectivite!=null) {
                try {
                    DataImpl impl = DataLayerManager.getImplementation();
                    PJRef pjRef = impl.getPjByUniqueId(new Pair(pjCollectivite,""), pjId, user);
                    if(pjRef!=null) {
                        File tmpDir = new File(System.getProperty("java.io.tmpdir"));
                        File tmpFile = File.createTempFile("tmp-",pjRef.getFileName(),tmpDir);
                        FileOutputStream fos = new FileOutputStream(tmpFile);
                        fos.write(pjRef.getData());
                        fos.flush();
                        fos.close();
                        return new Dest(tmpFile.toURL(),true);
                    } else {
                        JOptionPane.showMessageDialog(viewer,"Pièce non trouvée","Erreur",JOptionPane.ERROR_MESSAGE);
                    }
                } catch(UnauthorizedException ex) {
                    JOptionPane.showMessageDialog(viewer,ex.getMessage(),"Droits insuffisants",JOptionPane.WARNING_MESSAGE);
                } catch(DataConfigurationException dcEx) {
                } catch(DataAccessException daEx) {
                } catch(IOException ioEx) {
                }
            }
        } else if("customLink".equals(servlet)) {
            CustomLinkParameters clp = new CustomLinkParameters();
            StringTokenizer st = new StringTokenizer(sTmp,"&");
            while(st.hasMoreTokens()) {
                String data = st.nextToken();
                logger.debug("parsing "+data);
                int pos = data.indexOf('=');
                if(pos>0) {
                    String paramName = data.substring(0,pos);
                    String paramValue = null;
                    try {
                        paramValue=URLDecoder.decode(data.substring(pos+1),"UTF-8");
                        logger.debug(paramName+"="+paramValue);
                    } catch(Throwable t) {
                        logger.error("decoding",t);
                    }
                    if("srcDocId".equals(paramName)){ clp.setSrcDocId(paramValue);
                    } else if("srcEtatId".equals(paramName)) { clp.setSrcEtatId(paramValue);
                    } else if("srcElementId".equals(paramName)) { clp.setSrcElementId(paramValue);
                    } else if("srcCollectivite".equals(paramName)) { clp.setSrcCollectivite(paramValue);
                    } else if("srcBudget".equals(paramName)) { clp.setSrcBudget(paramValue);
                    } else {
                    	clp.addParameter(paramName, paramValue);
                    }
                }
            }
            try {
                String clrClassName = clp.getCustomLinkResolverName(mainWindow.getDocModels());
                Class clazz = Class.forName(clrClassName);
                CustomLinkResolver clr = (CustomLinkResolver)clazz.newInstance();
                return generateDestContext(ctx,clr,clp,HtmlViewer.INTERNAL_DESTINATION);
            } catch(Exception ex) {
                logger.error("calculateQuery("+query+")",ex);
            }
        }
        return null;
    }
    public Dest generateDestContext(NavigationContext ctx, CustomLinkResolver resolver, CustomLinkParameters parameters, boolean toExternalDest) {
        Dest ret = null;
        File tmpDir = new File(System.getProperty("java.io.tmpdir"));
        tmpDir = new File(tmpDir,Constants.NOM_APP);
        tmpDir.mkdirs();
        try {
            File tmp = File.createTempFile("xemelios-data-",".html",tmpDir);
            ctx = resolver.resolve(parameters, new StreamResult(tmp), ctx, user);
            URL url = tmp.toURL();
            nc=ctx;
            ret = new Dest(url,toExternalDest);
        } catch(UnresolvedException unEx) {
            JOptionPane.showMessageDialog(viewer, unEx.getMessage(),"Erreur",JOptionPane.INFORMATION_MESSAGE);
        } catch(Exception ex) {
            logger.error("generateDestContext(NavigationContext,CustomLinkResolver,CustomLinkParameters,boolean)",ex);
        }
        return ret;
    }
    public Dest generateDestContext(NavigationContext ctx, Hashtable<String,Object> params, final boolean toExternalDest) {
//        logger.debug(ctx);
        boolean noException = false;
        Dest ret = null;
        try {
            DocumentModel dm = mainWindow.getDocModels().getDocumentById(ctx.getDocId());
            if(dm==null) throw new LocalException("doc "+ctx.getDocId()+" inconnu");
            EtatModel em = dm.getEtatById(ctx.getEtatId());
            if(em==null) throw new LocalException("etat "+ctx.getEtatId()+" inconnu");
            ElementModel element=(ctx.getElementId()!=null?em.getElementById(ctx.getElementId()):em.getBrowsableElement());
            if(ctx.getCollectivite()==null) throw new LocalException("collectivite non definie");
            if(ctx.getBudget()==null) throw new LocalException("budget non defini");
            DataImpl impl = DataLayerManager.getImplementation();
            String docId = null;
            if(ctx.getPath()==null) {
                ArrayList<String> documents = impl.getDocumentListFromSpecialKeys(element,ctx.getCollectivite(),ctx.getBudget(),ctx.getSp1(),ctx.getSp2(),ctx.getSp3(),user);
                if(documents.size()==0) {
                    JOptionPane.showMessageDialog(viewer,"Le document est introuvable","",JOptionPane.WARNING_MESSAGE);
                    noException = true;
                } else if(documents.size()>1 && !em.isMultiPage()) {
                    JOptionPane.showMessageDialog(viewer,"Plusieurs documents répondant à ces critères ont été trouvés.","Erreur",JOptionPane.ERROR_MESSAGE);
                    noException = true;
                } else {
                    docId = documents.get(0);
                }
            } else {
                if(ctx.getElementId()==null) {
                    ctx.setElementId(element.getId());
                }
                if(ctx.getPath()!=null && ctx.getPath().startsWith("[")) {
                    ctx.setPath(element.getPath().getFormattedPath()+ctx.getPath());
                }
                ArrayList<String> documents = impl.getDocumentListFromXPath(element,ctx.getCollectivite(),ctx.getBudget(),ctx.getSp1(),ctx.getSp2(),ctx.getSp3(),ctx.getPath(), user);
                if(documents.size()==0) {
                    JOptionPane.showMessageDialog(viewer,"Le document est introuvable","",JOptionPane.WARNING_MESSAGE);
                    noException = true;
                } else if(documents.size()>1 && !em.isMultiPage()) {
                    JOptionPane.showMessageDialog(viewer,"Plusieurs documents répondant à ces critères ont été trouvés.","Erreur",JOptionPane.ERROR_MESSAGE);
                    noException = true;
                } else {
                    docId = documents.get(0);
                }
            }
            if(docId!=null) {
                Document doc = impl.getDocumentAsDom(em,new Pair(ctx.getCollectivite(),null),new Pair(ctx.getBudget(),null),docId, user);
                File f = null;
                f = new File(new File(new File(System.getProperty("user.home")),"xemelios/documents-def-ovrrd"),element.getXslt());
                if(!f.exists())
                    f = new File(em.getParent().getBaseDirectory(),element.getXslt());
// DEBUG
//                FileChannel fChIn = new FileInputStream(f).getChannel();
//                WritableByteChannel wbc = Channels.newChannel(System.out);
//                fChIn.transferTo(0, fChIn.size(), wbc);
//                fChIn.close(); wbc.close();
// END DEBUG
                InputStream is = IoUtils.getInputStream(f.getPath());
                Hashtable<String,Object> parameters = new Hashtable<String,Object>();
                parameters.putAll(params);
                if(parameters.get(HtmlViewer.PARAM_DESTINATION)==null) {
                    parameters.put(HtmlViewer.PARAM_DESTINATION,em.useExternalBrowser()?HtmlViewer.VALUE_DESTINATION_EXTERNAL:HtmlViewer.VALUE_DESTINATION_INTERNAL);
                }
                // on passe la nomenclature en parametre
                // on recupere l'ID de la nomenclature
                XPath xp = xpf.newXPath();
                xp.setNamespaceContext(dm.getNamespaces());
                String idRepository = null;
                if(dm.getReferenceNomenclaturePath()!=null && dm.getReferenceNomenclaturePath().getCodePath()!=null) {
                    idRepository = (String)xp.evaluate(dm.getReferenceNomenclaturePath().getCodePath().getPath(),doc.getDocumentElement(),XPathConstants.STRING);
                }
                if(idRepository!=null && idRepository.length()==0) idRepository=null;
                Document repository = impl.getRepositoryAsDom(dm, new Pair(ctx.getCollectivite(),null),idRepository,user);
                if(repository!=null)
                    parameters.put("repository", repository);
                // on passe la config
                parameters.put("config",mainWindow.getDocModels().getSmallDOM());
                parameters.put("collectivite",ctx.getCollectivite());
                parameters.put("budget",ctx.getBudget());
                InputStream htmlStream = DematTransform.transform(doc,is,parameters,f.getParentFile().toURI().toURL().toExternalForm());
                File tmpDir = new File(System.getProperty("java.io.tmpdir"));
                tmpDir = new File(tmpDir,Constants.NOM_APP);
                tmpDir.mkdirs();
                File tmp = File.createTempFile("xemelios-data-",".html",tmpDir);
                BufferedInputStream bis = new BufferedInputStream(htmlStream);
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tmp));
                byte[] buff = new byte[1024];
                int lengthRead = bis.read(buff);
                while(lengthRead>0) {
                    bos.write(buff,0,lengthRead);
                    lengthRead = bis.read(buff);
                }
                bos.flush();
                bos.close();
                URL url = tmp.toURL();
                nc=ctx;
                ret = new Dest(url,toExternalDest);
            } else if(!noException){
                throw new NotFoundException("Pas de "+element.getTitre()+" trouvé(e)");
            }
        } catch(LocalException lEx) {
            JOptionPane.showMessageDialog(viewer,lEx.getMessage(),"Erreur de paramétrage",JOptionPane.ERROR_MESSAGE);
        } catch(Exception ex) {
            Component comp2 = viewer;
            while(comp2!=null && !(comp2 instanceof JFrame))
                comp2 = comp2.getParent();
            if(comp2!=null)
                new DisplayExceptionDlg((JFrame)comp2,ex);
            else
                new DisplayExceptionDlg((JFrame)null,ex);
        }
        if(ret!=null) ctx.setLastDestination(ret);
        return ret;
    }
    public Component getViewer() { return viewer; }
}
