/*
 * 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.Base64;
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.utils.xml.NamespaceContextImpl;
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 fr.gouv.finances.cp.xemelios.utils.XmlUtils;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
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.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.zip.GZIPInputStream;
import javax.swing.AbstractAction;
import javax.swing.Action;
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.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.xhtmlrenderer.css.style.derived.RectPropertySet;
import org.xhtmlrenderer.event.DocumentListener;
import org.xhtmlrenderer.pdf.ITextRenderer;
import org.xhtmlrenderer.render.Box;
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;
import org.xhtmlrenderer.swing.SelectionHighlighter;

/**
 *
 * @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;
    private SelectionHighlighter highlighter;
    private JButton pbCopy;
    
    /** 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) {
            public void setDocument(File file) throws Exception {
                super.setDocument(file);
                setScalePolicy(SCALE_POLICY_NONE);
            }

            public void setDocument(Document doc, String url) {
                super.setDocument(doc, url);
                setScalePolicy(SCALE_POLICY_NONE);
            }

            public void setDocument(InputStream stream, String url) throws Exception {
                super.setDocument(stream, url);
                setScalePolicy(SCALE_POLICY_NONE);
            }
        };
        initializeComponents(uri);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
    
    public 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...
            }
        }
        @Override
        public Dest calculateQuery(String 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)) {
                DocumentModel dm = documentsModel.getDocumentById(ctx.getDocId());
//                System.out.println(sTmp);
                if(dm.getId().equals("PES_Aller")) {
                    File fDoc = documents.get(ctx.getDocId());
                    if(fDoc==null) {
                        JOptionPane.showMessageDialog(EditorHtmlViewer.this,"Aucun "+dm.getTitre()+" disponible.","Erreur",JOptionPane.ERROR_MESSAGE);
                    }
                    try {
                        String[] args = sTmp.split("&");
                        String idUnique = null;
                        for(String s:args) {
                            if(s.startsWith("pjId")) {
                                idUnique = s.substring(5);
                                break;
                            }
                        }
                        Document doc = db.parse(fDoc);
                        XPathFactory xpf = XPathFactory.newInstance();
                        XPath xp = xpf.newXPath();
                        NamespaceContextImpl ns = new NamespaceContextImpl();
                        ns.addMapping("n", "http://www.minefi.gouv.fr/cp/helios/pes_v2/Rev0/aller");
                        xp.setNamespaceContext(ns);
                        Element pj = (Element)xp.evaluate("//n:PJ[n:IdUnique/@V='"+idUnique+"']", doc, XPathConstants.NODE);
                        if(pj!=null) {
                            String nomFichier = xp.evaluate("//n:NomPJ/@V",pj);
                            Element contenu = (Element)xp.evaluate("//n:Contenu",pj,XPathConstants.NODE);
                            if(contenu!=null) {
                                File tmpDir = new File(System.getProperty("java.io.tmpdir"));
                                tmpDir = new File(tmpDir,Constants.NOM_APP);
                                tmpDir.mkdirs();
                                File tmp = new File(tmpDir,nomFichier);
                                if(tmp.exists()) tmp.delete();
                                Element pesFacture = (Element)xp.evaluate("n:PESFacture",contenu,XPathConstants.NODE);
                                if(pesFacture!=null) {
                                    TransformerFactory tFactory = TransformerFactory.newInstance();
                                    Transformer t = tFactory.newTransformer();
                                    t.transform(new DOMSource(pesFacture), new StreamResult(tmp));
                                } else {
                                    // c'est un fichier
                                    String text = xp.evaluate("//n:Fichier/text()",contenu);
                                    byte[] buffer = Base64.decode(text);
                                    GZIPInputStream gz = new GZIPInputStream(new ByteArrayInputStream(buffer));
                                    FileOutputStream fos = new FileOutputStream(tmp);
                                    byte[] array = new byte[1024];
                                    int read = gz.read(array);
                                    while(read>0) {
                                        fos.write(array, 0, read);
                                        read = gz.read(array);
                                    }
                                    fos.flush(); fos.close();
                                }
                                StartUrl.startUrl(tmp.toURL().toExternalForm());
                            } else {
                                JOptionPane.showMessageDialog(EditorHtmlViewer.this,"Le contenu de la PJ n'est pas fournit","Erreur",JOptionPane.ERROR_MESSAGE);
                            }
                        } else {
                            JOptionPane.showMessageDialog(EditorHtmlViewer.this,"La PJ "+idUnique+" n'est pas disponible dans le flux.","Erreur",JOptionPane.ERROR_MESSAGE);
                        }
                    } catch(Exception ex) {
                        JOptionPane.showMessageDialog(EditorHtmlViewer.this,ex.getMessage(),ex.getClass().getName(),JOptionPane.ERROR_MESSAGE);
                        ex.printStackTrace();
                    }
                }
//                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());
                if(ctx.getCollectivite()!=null)
                    parameters.put("collectivite",ctx.getCollectivite());
                if(ctx.getBudget()!=null)
                    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);
            }
        }
        highlighter = new SelectionHighlighter();
        highlighter.install(xhtmlRenderer);
        xhtmlRenderer.addMouseListener(highlighter);
        
        FSScrollPane scroller;
        getContentPane().setLayout(new BorderLayout());
        JToolBar tb = new JToolBar();
        tb.add(pbPrevious=new JButton(IhmFactory.getIconFromResource(ImageResources.BRW_PREVIOUS)));
        tb.add(pbNext=new JButton(IhmFactory.getIconFromResource(ImageResources.BRW_NEXT)));
        tb.add(pbPrint = new JButton(IhmFactory.getIconFromResource(ImageResources.PRINT)));
        final Action copyAction = new AbstractAction(null,IhmFactory.getIconFromResource(ImageResources.EDIT_COPY)) {

            public void actionPerformed(ActionEvent e) {
                DocumentFragment df = highlighter.getSelectionRange().extractContents();
                StringSelection ss = new StringSelection(XmlUtils.getStringRepresentation(df));
                Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, ss);
            }
        };
        tb.add(pbCopy = new JButton(copyAction));
        pbCopy.setToolTipText("Copier");

        final Action selectAllAction = new AbstractAction(null,IhmFactory.getIconFromResource(ImageResources.EDIT_SELECT_ALL)) {

            public void actionPerformed(ActionEvent e) {
                highlighter.selectAll();
            }

        };
        JButton pbSelectAll = new JButton(selectAllAction);
        pbSelectAll.setToolTipText("Tout sélectionner");
        tb.add(pbSelectAll);

        getRootPane().registerKeyboardAction(copyAction, "copy", KeyStroke.getKeyStroke("ctrl C"), JComponent.WHEN_IN_FOCUSED_WINDOW);
        getRootPane().registerKeyboardAction(selectAllAction, "selectAll", KeyStroke.getKeyStroke("ctrl A"), JComponent.WHEN_IN_FOCUSED_WINDOW);
        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();
            // G. DON contribution
            String fileName = dfUrl.getText();
            int posDiez = fileName.indexOf("#");
            if (posDiez!=-1) {
                fileName = fileName.substring(0,posDiez);
            }
            File fToPDFize = new File(new URL(fileName).toURI());
            // END contribution G.DON
            if(fToPDFize.exists()) {
                renderer.setDocument(fToPDFize);
                renderer.layout();
                renderer.createPDF(os,true);
                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);
    }
}
