/*
 * EditorHtmlViewer.java
 *
 * Created on 14 novembre 2007, 15:16
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */


package fr.gouv.finances.cp.xemelios.controls.editors;

import fr.gouv.finances.cp.utils.IoUtils;
import fr.gouv.finances.cp.utils.NavigationContext;
import fr.gouv.finances.cp.utils.StartUrl;
import fr.gouv.finances.cp.utils.ui.DisplayExceptionDlg;
import fr.gouv.finances.cp.utils.ui.IhmFactory;
import fr.gouv.finances.cp.xemelios.common.Constants;
import fr.gouv.finances.cp.xemelios.common.DematTransform;
import fr.gouv.finances.cp.xemelios.common.config.DocumentModel;
import fr.gouv.finances.cp.xemelios.common.config.DocumentsModel;
import fr.gouv.finances.cp.xemelios.common.config.ElementModel;
import fr.gouv.finances.cp.xemelios.common.config.EtatModel;
import fr.gouv.finances.cp.xemelios.ui.HtmlViewer;
import fr.gouv.finances.cp.xemelios.ui.ImageResources;
import fr.gouv.finances.cp.xemelios.ui.UpdatableToolbar;
import fr.gouv.finances.cp.xemelios.ui.xhtmlviewer.Dest;
import fr.gouv.finances.cp.xemelios.ui.xhtmlviewer.LocalException;
import fr.gouv.finances.cp.xemelios.ui.xhtmlviewer.XemeliosUserAgent;
import fr.gouv.finances.cp.xemelios.ui.xhtmlviewer.XemeliosXhtmlPanel;
import fr.gouv.finances.cp.xemelios.utils.AbstractUriTransformer;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Hashtable;
import java.util.StringTokenizer;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.xhtmlrenderer.pdf.ITextRenderer;
import org.xhtmlrenderer.simple.FSScrollPane;
import org.xhtmlrenderer.swing.LinkListener;
import org.xhtmlrenderer.swing.ScalableXHTMLPanel;
import org.xhtmlrenderer.swing.ScaleChangeEvent;
import org.xhtmlrenderer.swing.ScaleChangeListener;

/**
 *
 * @author chm
 */
public class EditorHtmlViewer extends JFrame implements UpdatableToolbar, ScaleChangeListener {
    public static String DEFAULT_TITLE = "Document Xemelios";
    
    private Hashtable<String,File> documents = null;
    private DocumentsModel documentsModel = null;
    private XemeliosUserAgent userAgent = null;
    private final XemeliosXhtmlPanel xhtmlRenderer;
    private JComboBox cbxZoom;
    private HtmlViewer.ScaleFactor[] availableScales;
    private JButton pbPrint, pbPrevious, pbNext;
    private JTextField dfUrl;
    
    /** Creates a new instance of EditorHtmlViewer */
    public EditorHtmlViewer(Hashtable<String,File> documents, DocumentsModel documentsModel, String uri) {
        super(DEFAULT_TITLE);
        this.documents=documents;
        this.documentsModel=documentsModel;
        userAgent = new LocalUserAgent((Component)this,this,this.documentsModel,this.documents);
        xhtmlRenderer = new XemeliosXhtmlPanel(userAgent, this);
        initializeComponents(uri);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
    
    public static class LocalUserAgent extends XemeliosUserAgent {
        private DocumentBuilder db = null;
        private DocumentsModel documentsModel = null;
        private Hashtable<String,File> documents = null;

        public LocalUserAgent(Component parent, UpdatableToolbar tb, DocumentsModel docs, Hashtable<String,File> documents) {
            super(null,parent,tb);
            this.documentsModel=docs;
            this.documents=documents;
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
            try {
                db = dbf.newDocumentBuilder();
            } catch(Throwable t) {
                // we ignore it, suppose Dom is correctly configured if editors can build xml files...
            }
        }
        public Dest calculateQuery(String query) {
System.out.println(query);
            AbstractUriTransformer uriTransformer = null;
            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)) {
                StringTokenizer st = new StringTokenizer(sTmp,"&");
                while(st.hasMoreTokens()) {
                    String data = st.nextToken();
                    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");
                        } catch(Throwable t) {
                        }
                        if("docId".equals(paramName)) {
                            ctx.setDocId(paramValue);
                            String className = documentsModel.getDocumentById(paramValue).getGlobalUriTransformer();
                            if(className!=null) {
                                try {
                                    Class clazz = Class.forName(className);
                                    Object o = clazz.newInstance();
                                    if(o instanceof AbstractUriTransformer) {
                                        uriTransformer = (AbstractUriTransformer)o;
                                    }
                                } catch(Exception ex) {
                                    ex.printStackTrace(System.err);
                                }
                            }
                        }
                        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")) {
                                int virg = paramValue.indexOf(',');
                                String pName = paramValue.substring(1,virg);
                                String pValue = paramValue.substring(virg+1,paramValue.length()-1);
                                params.put(pName,pValue);
                            }
                        }
                        if(uriTransformer!=null) uriTransformer.transformUriParam(paramName, paramValue, params);
                    }
                }
                return generateDestContext(ctx,params,HtmlViewer.INTERNAL_DESTINATION);
            } else if("resource".equals(servlet)) {
                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)) {
                return null;
            }
            return null;
        }
        @Override
        public Dest generateDestContext(NavigationContext ctx, Hashtable<String,Object> params, final boolean toExternalDest) {
            boolean noException = false;
            Dest ret = null;
            try {
                DocumentModel dm = documentsModel.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());
                File fDoc = documents.get(dm.getId());
                if(fDoc==null) throw new LocalException("Aucun "+dm.getTitre()+" disponible.");
                Document doc = db.parse(fDoc);
                File 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
                parameters.put("repository", doc);
                // on passe la config
                parameters.put("config",documentsModel.getSmallDOM());
                parameters.put("collectivite",ctx.getCollectivite());
                parameters.put("budget",ctx.getBudget());
                InputStream htmlStream = DematTransform.transform(doc,is,parameters);
                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);
            } catch(LocalException lEx) {
                JOptionPane.showMessageDialog(getViewer(),lEx.getMessage(),"Erreur de paramétrage",JOptionPane.ERROR_MESSAGE);
            } catch(Exception ex) {
                Component comp2 = getViewer();
                while(comp2!=null && !(comp2 instanceof JFrame))
                    comp2 = comp2.getParent();
                if(comp2!=null)
                    new DisplayExceptionDlg((JFrame)comp2,ex);
                else
                    new DisplayExceptionDlg((JFrame)null,ex);
            }
            return ret;
        }
        
    }

    public void scaleChanged(ScaleChangeEvent evt) {
        if(evt.getComponent()==xhtmlRenderer) {
            double scale = evt.getScale();
            int ix = -1;
            for(int i=0;i<availableScales.length;i++) {
                HtmlViewer.ScaleFactor sf=availableScales[i];
                if(sf.getFactor()==scale) {
                    ix = i;
                    break;
                }
            }
            ItemListener[] iListeners = cbxZoom.getItemListeners();
            for(ItemListener il:iListeners) cbxZoom.removeItemListener(il);
            if(ix==-1) {
                int scalePolicy = xhtmlRenderer.getScalePolicy();
                switch (scalePolicy) {
                    case ScalableXHTMLPanel.SCALE_POLICY_FIT_WIDTH: ix = 8; break;
                    case ScalableXHTMLPanel.SCALE_POLICY_FIT_HEIGHT: ix = 9; break;
                    case ScalableXHTMLPanel.SCALE_POLICY_FIT_WHOLE: ix = 10; break;
                }
            }
            cbxZoom.setSelectedIndex(ix);
            for(ItemListener il:iListeners) cbxZoom.addItemListener(il);
        }
    }
    protected void initializeComponents(String uri) {
        HtmlViewer.ScaleFactor[] scales = HtmlViewer.initializeScales();
        availableScales=scales;
        IhmFactory iF=IhmFactory.newInstance();
        xhtmlRenderer.addScaleChangeListener(this);
        MouseListener[] lls = xhtmlRenderer.getMouseListeners();
        for (int i = 0; i < lls.length; i++) {
            MouseListener ll = lls[i];
            if ( ll instanceof LinkListener ) {
                xhtmlRenderer.addMouseMotionListener((MouseMotionListener)ll);
            }
        }
        
        FSScrollPane scroller;
        getContentPane().setLayout(new BorderLayout());
        JToolBar tb = new JToolBar();
        tb.add(pbPrevious=new JButton(iF.getIconFromResource(ImageResources.BRW_PREVIOUS)));
        tb.add(pbNext=new JButton(iF.getIconFromResource(ImageResources.BRW_NEXT)));
        tb.add(pbPrint = new JButton(iF.getIconFromResource(ImageResources.PRINT)));
        tb.addSeparator();
        tb.add(iF.newLabel("URL ","normal"));
        tb.add(dfUrl = new JTextField());
        dfUrl.setEditable(false);
        Dimension dim = dfUrl.getMinimumSize();
        dfUrl.setMinimumSize(new Dimension(100,(int)dim.getHeight()));
        tb.addSeparator();
        tb.add(iF.newLabel("Zoom ","normal"));
        cbxZoom = new JComboBox(scales);
        cbxZoom.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                    if (e.getStateChange() == ItemEvent.SELECTED) {
                            HtmlViewer.ScaleFactor factor = (HtmlViewer.ScaleFactor) e.getItem();
                            if (factor.getFactor() == HtmlViewer.ScaleFactor.PAGE_WIDTH) {
                                    xhtmlRenderer.setScalePolicy(ScalableXHTMLPanel.SCALE_POLICY_FIT_WIDTH);
                                    xhtmlRenderer.doLayout();
                            } else if (factor.getFactor() == HtmlViewer.ScaleFactor.PAGE_HEIGHT) {
                                    xhtmlRenderer.setScalePolicy(ScalableXHTMLPanel.SCALE_POLICY_FIT_HEIGHT);
                                    xhtmlRenderer.doLayout();
                            } else if (factor.getFactor() == HtmlViewer.ScaleFactor.PAGE_WHOLE) {
                                    xhtmlRenderer.setScalePolicy(ScalableXHTMLPanel.SCALE_POLICY_FIT_WHOLE);
                                    xhtmlRenderer.doLayout();
                            } else {
                                    xhtmlRenderer.setScale(factor.getFactor());
                            }
                    }
            }
        });
        cbxZoom.setSelectedIndex(2);
        tb.add(cbxZoom);
        getContentPane().add(tb,BorderLayout.PAGE_START);
        getContentPane().add(scroller = new FSScrollPane(xhtmlRenderer),BorderLayout.CENTER);
        scroller.setMinimumSize(new Dimension(WIDTH,HEIGHT));
        scroller.setPreferredSize(new Dimension(WIDTH, HEIGHT));
        scroller.setIgnoreRepaint(false);
        pbPrint.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                printToPDF();
            }
        });
        pbPrint.setToolTipText("Imprimer");
        pbPrevious.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                goBack();
            }
        });
        pbNext.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                goForward();
            }
        });
        pbPrevious.setEnabled(false); pbNext.setEnabled(false);
        if(uri!=null) {
            try {
                String eForm = userAgent.resolveURI(uri);
                if(eForm!=null) {
                    dfUrl.setText(eForm);
                    xhtmlRenderer.setDocument(eForm);
                }
            } catch(Throwable t) {
                t.printStackTrace();
                System.exit(1);
            }
        }
        pack();
        setSize(700,600);
    }
    protected void printToPDF() {
        try {
            File tmpDir = new File(System.getProperty("java.io.tmpdir"));
            tmpDir = new File(tmpDir,Constants.NOM_APP);
            tmpDir.mkdirs();
            File tmp = File.createTempFile("xemelios-data-",".pdf",tmpDir);
            OutputStream os = new FileOutputStream(tmp);
            ITextRenderer renderer = new ITextRenderer();
            File fToPDFize = new File(new URL(dfUrl.getText()).toURI());
//logger.debug("pdf'ising "+fToPDFize.toURI().toURL().toExternalForm());
            if(fToPDFize.exists()) {
                renderer.setDocument(fToPDFize);
                renderer.layout();
                renderer.createPDF(os,true);
//                renderer.finishPDF();
                os.flush(); os.close();
                StartUrl.startUrl(tmp.getAbsolutePath());
            }
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
    protected void goBack() {
        if(userAgent.hasBack())
            xhtmlRenderer.setDocument(userAgent.getBack());
        else updateToolbarButtons();
    }
    protected void goForward() {
        if(userAgent.hasForward())
            xhtmlRenderer.setDocument(userAgent.getForward());
        else updateToolbarButtons();
    }
    public void updateToolbarButtons() {
        pbPrevious.setEnabled(userAgent.hasBack());
        pbNext.setEnabled(userAgent.hasForward());
    }
    public void setBaseUrl(String url) {
        dfUrl.setText(url);
    }
}
