/*
 * 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.ui;

import com.lowagie.text.DocumentException;
import fr.gouv.finances.dgfip.utils.Dest;
import fr.gouv.finances.cp.xemelios.ui.xhtmlviewer.XemeliosUserAgent;
import fr.gouv.finances.cp.xemelios.ui.xhtmlviewer.XemeliosXhtmlPanel;
import java.awt.BorderLayout;
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.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.StringTokenizer;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JInternalFrame;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.text.html.FormSubmitEvent;

import org.apache.log4j.Logger;
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;

import fr.gouv.finances.dgfip.utils.NavigationContext;
import fr.gouv.finances.cp.utils.StartUrl;
import fr.gouv.finances.cp.utils.ui.IhmFactory;
import fr.gouv.finances.dgfip.xemelios.common.Constants;
import fr.gouv.finances.dgfip.xemelios.common.ToolException;
import fr.gouv.finances.dgfip.xemelios.ui.ImageResources;
import fr.gouv.finances.dgfip.xemelios.utils.XmlUtils;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import org.w3c.dom.DocumentFragment;
import org.xhtmlrenderer.context.StyleReference;
import org.xhtmlrenderer.pdf.ITextOutputDevice;
import org.xhtmlrenderer.pdf.ITextUserAgent;
import org.xhtmlrenderer.swing.SelectionHighlighter;

/**
 * Permet d'afficher du HTML
 *
 * @author chm
 */
public class HtmlViewer extends JInternalFrame implements UpdatableToolbar, ScaleChangeListener {
    private static Logger logger = Logger.getLogger(HtmlViewer.class);
    public static final String PARAM_DESTINATION = "browser-destination";
    public static final String VALUE_DESTINATION_EXTERNAL = "external";
    public static final String VALUE_DESTINATION_INTERNAL = "internal";
    public static final boolean EXTERNAL_DESTINATION = true;
    public static final boolean INTERNAL_DESTINATION = false;
    /**
     * Comment for <code>serialVersionUID</code>
     */
    private static final long serialVersionUID = 3257008752400807221L;
    private static final int WIDTH = 540;
    private static final int HEIGHT = 320;
    private URL documentUrl;
    private final XemeliosXhtmlPanel xhtmlRenderer;
    private JTextField dfUrl;
    private MainWindow mainWindow;
    private JButton pbPrint, pbPrevious, pbNext;
    private XemeliosUserAgent userAgent = null;
    private JComboBox cbxZoom;
    private ScaleFactor[] availableScales;
    private NavigationContext nc;
    private SelectionHighlighter highlighter;
    private JButton pbCopy;
//    private static final boolean IS_SELECTION_ENABLED = "true".equals(System.getProperty(Constants.SYS_PROP_XHTMLCOPY_ENABLED));
    private static final boolean IS_SELECTION_ENABLED = true;
    
    /**
     *
     */
    public HtmlViewer(URL documentToDisplay, String title,NavigationContext nc, MainWindow mw) {
        this(documentToDisplay,title,mw);
        this.nc=nc;
    }
    public HtmlViewer(URL documentToDisplay, String title,MainWindow mw) {
        super(title, true, true, true, true);
        documentUrl = documentToDisplay;
        mainWindow=mw;
        userAgent = new XemeliosUserAgent(mainWindow,this,this,mainWindow.getConnectedUser());
        xhtmlRenderer = new XemeliosXhtmlPanel(userAgent, this);
        initializeComponents();
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    }
    public HtmlViewer(String uri, String title,MainWindow mw) {
        super(title, true, true, true, true);
        mainWindow=mw;
        userAgent = new XemeliosUserAgent(mainWindow,this,this,mainWindow.getConnectedUser());
        xhtmlRenderer = new XemeliosXhtmlPanel(userAgent, this);
        initializeComponents();
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        Dest dest = userAgent.calculateQuery(uri);
        try { browseUrl(dest); } catch(Throwable t) { logger.error("in HtmlViewer(String,String,MainWindow)",t); }
    }
    public static ScaleFactor[] initializeScales() {
        ScaleFactor[] scales = new ScaleFactor[11];
        int i = 0;
        scales[i++] = new ScaleFactor(2.0d, "200%");
        scales[i++] = new ScaleFactor(1.5d, "150%");
        scales[i++] = new ScaleFactor(1.0d, "100%");
        scales[i++] = new ScaleFactor(0.85d, "85%");
        scales[i++] = new ScaleFactor(0.75d, "75%");
        scales[i++] = new ScaleFactor(0.5d, "50%");
        scales[i++] = new ScaleFactor(0.33d, "33%");
        scales[i++] = new ScaleFactor(0.25d, "25%");
        scales[i++] = new ScaleFactor(ScaleFactor.PAGE_WIDTH, "Largeur page");
        scales[i++] = new ScaleFactor(ScaleFactor.PAGE_HEIGHT, "Hauteur page");
        scales[i++] = new ScaleFactor(ScaleFactor.PAGE_WHOLE, "Page entière");
        return scales;
    }
    public void scaleChanged(ScaleChangeEvent evt) {
        if(evt.getComponent()==xhtmlRenderer) {
            double scale = evt.getScale();
            logger.debug("received scaleChanged with "+scale);
            int ix = -1;
            for(int i=0;i<availableScales.length;i++) {
                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) {
                //TODO
                int scalePolicy = xhtmlRenderer.getScalePolicy();
//                int scalePolicy = ScalableXHTMLPanel.SCALE_POLICY_NONE;
                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() {
        ScaleFactor[] scales = initializeScales();
        availableScales=scales;
        IhmFactory iF=IhmFactory.newInstance();
//        xhtmlRenderer.getSharedContext().setReplacedElementFactory(new XemeliosReplacedElementFactory(xhtmlRenderer));
//        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);
            }
        }
        if(IS_SELECTION_ENABLED) {
            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)));
        if(IS_SELECTION_ENABLED) {
            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);
        // TODO
        cbxZoom.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                    if (e.getStateChange() == ItemEvent.SELECTED) {
                            ScaleFactor factor = (ScaleFactor) e.getItem();
                            if (factor.getFactor() == ScaleFactor.PAGE_WIDTH) {
                                    xhtmlRenderer.setScalePolicy(ScalableXHTMLPanel.SCALE_POLICY_FIT_WIDTH);
                                    xhtmlRenderer.doLayout();
                            } else if (factor.getFactor() == ScaleFactor.PAGE_HEIGHT) {
                                    xhtmlRenderer.setScalePolicy(ScalableXHTMLPanel.SCALE_POLICY_FIT_HEIGHT);
                                    xhtmlRenderer.doLayout();
                            } else if (factor.getFactor() == 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.setPreferredSize(new Dimension(WIDTH, HEIGHT));
        scroller.setIgnoreRepaint(false);
        pbPrint.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
//                final PrinterJob printJob = PrinterJob.getPrinterJob();
//                printJob.setPrintable(new XHTMLPrintable(xhtmlRenderer));
//                
//                if (printJob.printDialog()) {
//                    new Thread(new Runnable() {
//                        public void run() {
//                            try {
//                                printJob.print();
//                            } catch (PrinterException ex) {
//                                logger.error("while printing",ex);
//                            }
//                        }
//                    }).start();
//                }
                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(documentUrl!=null) {
            try { browseUrl(documentUrl,INTERNAL_DESTINATION); } catch(Throwable t) {}
        }
        pack();
    }
    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();
            CustomUserAgentCallback callback = new CustomUserAgentCallback(renderer.getOutputDevice());
            callback.setSharedContext(renderer.getSharedContext());
            renderer.getSharedContext().setUserAgentCallback(callback);
            StyleReference css = new StyleReference(callback);
            String url = dfUrl.getText();
            if(url.indexOf("#")>0) url = url.substring(0, url.indexOf("#"));
            File fToPDFize = new File(new URL(url).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(IOException ioEx) {
            logger.error("while printing to PDF: ",ioEx);
        } catch(ToolException tEx) {
            logger.error("while printing to PDF: ",tEx);
        } catch(URISyntaxException uriEx) {
            logger.error("while printing to PDF: ",uriEx);
        } catch(DocumentException docEx) {
            logger.error("while printing to PDF: ",docEx);
        }
    }
    protected void browseUri(URI uri) throws ToolException {
        String query = uri.getQuery();
        Dest dest = userAgent.calculateQuery(query);
        browseUrl(dest);
    }
    protected void browseUrl(Dest dest) throws ToolException {
        browseUrl(dest.url,dest.toExternal);
    }
    protected void browseUrl(URL url,boolean toExternalDestination) throws ToolException {
        try {
            if(toExternalDestination) {
                StartUrl.startUrl(url.toString());
            } else {
                String eForm = url.toExternalForm();
                dfUrl.setText(eForm);
                xhtmlRenderer.setDocument(eForm);
            }
        } catch(Exception ioEx) {
            throw new ToolException(ioEx,ToolException.ERROR_INVALID_XHTML);
        }
    }
    protected Dest browseFormSubmission(FormSubmitEvent fse) throws UnsupportedEncodingException {
        StringTokenizer st = new StringTokenizer(fse.getData(),"&");
        Hashtable<String,String> params = new Hashtable<String,String>();
        while(st.hasMoreTokens()) {
            String token = st.nextToken();
            int pos = token.indexOf('=');
            String paramName = token.substring(0,pos);
            String value = URLDecoder.decode(token.substring(pos+1),"UTF-8");
            // at this point, only one value per param is allowed
            params.put(paramName, value);
        }
        String path = params.get("path");
        if(path!=null) {
            Set<String> keys = params.keySet();
            Set<String> toRemove = new HashSet<String>();
            for(String key:keys) {
                if(key.equals("path")) continue;
                if(path.indexOf("%"+key+"%")>=0) {
                    path = path.replaceAll("%"+key+"%",params.get(key));
                    toRemove.add(key);
                }
            }
            if(!toRemove.isEmpty()) {
                keys.removeAll(toRemove);
            }
            params.put("path",URLEncoder.encode(path,"UTF-8"));
        }
        path="";
        for(String key:params.keySet()) {
            path = path+key+"="+params.get(key)+"&";
        }
        path=path.substring(0,path.length()-1);
        return userAgent.calculateQuery(path);
    }
    /**
     * Opens designed page in external browser by filtering it through outputter.xsl
     * @param page The page to open
     */
    protected void output(NavigationContext ctx) {
        Hashtable<String,Object> params = new Hashtable<String,Object>();
        params.put(PARAM_DESTINATION,VALUE_DESTINATION_EXTERNAL);
        Dest dest = userAgent.generateDestContext(ctx,params,EXTERNAL_DESTINATION);
        try {
            browseUrl(dest.url,EXTERNAL_DESTINATION);
        } catch(ToolException tEx) {
            // on se fout que le xhtml ne soit pas bon, c'est le probleme du browser
        }
    }
    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);
    }

    class CustomUserAgentCallback extends ITextUserAgent {
        public CustomUserAgentCallback(ITextOutputDevice outputDevice) {
            super(outputDevice);
        }

        @Override
        public String resolveURI(String uri) {
            if(uri.startsWith("xemelios:")) {
                String servlet = null;
                String sTmp = uri;
                int start = uri.indexOf('?');
                if(start>=0) {
                    servlet = uri.substring(uri.indexOf("/")+1,start);
                    sTmp = uri.substring(start+1);
                }
                if("resource".equals(servlet)) {
                    String ret = null;
                    try {
                        File resourceDir = new File(System.getProperty("xemelios.resources.location"));
                        File res = new File(resourceDir,sTmp);
                        ret = res.toURL().toExternalForm();
                    } catch(Throwable t) {}
                    return ret;
                } else {
                    return super.resolveURI(uri);
                }
            } else {
                return super.resolveURI(uri);
            }
        }

    }
	public static class ScaleFactor {
		public static final double PAGE_WIDTH = -2.0d;
		public static final double PAGE_HEIGHT = -3.0d;
		public static final double PAGE_WHOLE = -4.0d;
		private Double factor;
		private String libelle;

		public ScaleFactor(double factor, String libelle) {
			super();
			this.factor = new Double(factor);
			this.libelle = libelle;
		}

		public double getFactor() {
			return factor.doubleValue();
		}

		public String getLibelle() {
			return libelle;
		}

		public String toString() {
			return getLibelle();
		}
	}
}
