/*
 * {{{ header & license
 * Copyright (c) 2004 Joshua Marinacci
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * }}}
 */
package org.xhtmlrenderer.demo.browser;

import org.w3c.dom.Document;
import org.xhtmlrenderer.event.DocumentListener;
import org.xhtmlrenderer.layout.SharedContext;
import org.xhtmlrenderer.pdf.ITextRenderer;
import org.xhtmlrenderer.pdf.PDFCreationListener;
import org.xhtmlrenderer.pdf.util.XHtmlMetaToPdfInfoAdapter;
import org.xhtmlrenderer.resource.XMLResource;
import org.xhtmlrenderer.simple.FSScrollPane;
import org.xhtmlrenderer.swing.ImageResourceLoader;
import org.xhtmlrenderer.swing.ScalableXHTMLPanel;
import org.xhtmlrenderer.swing.SwingReplacedElementFactory;
import org.xhtmlrenderer.util.GeneralUtil;
import org.xhtmlrenderer.util.Uu;
import org.xhtmlrenderer.util.XRLog;
import org.xhtmlrenderer.util.XRRuntimeException;

import javax.swing.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * Description of the Class
 *
 * @author empty
 */
public class BrowserPanel extends JPanel implements DocumentListener {
	private static final long serialVersionUID = 1L;

	/**
	 * Description of the Field
	 */
	JButton forward;
	/**
	 * Description of the Field
	 */
	JButton backward;
	/**
	 * Description of the Field
	 */
	JButton stop;
	/**
	 * Description of the Field
	 */
	JButton reload;
	/**
	 * Description of the Field
	 */
	JButton goHome;
	/**
	 * Description of the Field
	 */
	JButton font_inc;
	/**
	 * Description of the Field
	 */
	JButton font_rst;
	/**
	 * Description of the Field
	 */
	JButton font_dec;
	JButton print;
	/**
	 * Description of the Field
	 */
	JTextField url;
	/**
	 * Description of the Field
	 */
	BrowserStatus status;
	/**
	 * Description of the Field
	 */
	public ScalableXHTMLPanel view;
	/**
	 * Description of the Field
	 */
	JScrollPane scroll;
	/**
	 * Description of the Field
	 */
	BrowserStartup root;
	/**
	 * Description of the Field
	 */
	BrowserPanelListener listener;

	JButton print_preview;

	/**
	 * Description of the Field
	 */
	public static final Logger logger = Logger.getLogger("app.browser");

	private PanelManager manager;
	JButton goToPage;
	public JToolBar toolbar;

	/**
	 * Constructor for the BrowserPanel object
	 *
	 * @param root	 PARAM
	 * @param listener PARAM
	 */
	public BrowserPanel(BrowserStartup root, BrowserPanelListener listener) {
		super();
		this.root = root;
		this.listener = listener;
	}

	/**
	 * Description of the Method
	 */
	public void init() {
		forward = new JButton();
		backward = new JButton();
		stop = new JButton();
		reload = new JButton();
		goToPage = new JButton();
		goHome = new JButton();

		url = new JTextField();
		url.addFocusListener(new FocusAdapter() {
			public void focusGained(FocusEvent e) {
				super.focusGained(e);
				url.selectAll();
			}

			public void focusLost(FocusEvent e) {
				super.focusLost(e);
				url.select(0, 0);
			}
		});


		manager = new PanelManager();
        view = new ScalableXHTMLPanel(manager);
        manager.setRepaintListener(view);
        ImageResourceLoader irl = new ImageResourceLoader();
        irl.setRepaintListener(view);
        manager.setImageResourceLoader(irl);
        view.getSharedContext().setReplacedElementFactory(new SwingReplacedElementFactory(view, irl));
        view.addDocumentListener(manager);
        view.setCenteredPagedView(true);
        view.setBackground(Color.LIGHT_GRAY);
        scroll = new FSScrollPane(view);
		print_preview = new JButton();
		print = new JButton();

		loadCustomFonts();

		status = new BrowserStatus();
		status.init();

		initToolbar();

		int text_width = 200;
		view.setPreferredSize(new Dimension(text_width, text_width));

		setLayout(new BorderLayout());
		this.add(scroll, BorderLayout.CENTER);
	}

	private void initToolbar() {
		toolbar = new JToolBar();
		toolbar.setRollover(true);
		toolbar.add(backward);
		toolbar.add(forward);
		toolbar.add(reload);
		toolbar.add(goHome);
		toolbar.add(url);
		toolbar.add(goToPage);
		// disabled for R6
		// toolbar.add(print);
        toolbar.setFloatable(false);
    }

	private void loadCustomFonts() {
		SharedContext rc = view.getSharedContext();
		try {
			rc.setFontMapping("Fuzz", Font.createFont(Font.TRUETYPE_FONT,
					new DemoMarker().getClass().getResourceAsStream("/demos/fonts/fuzz.ttf")));
		} catch (Exception ex) {
			Uu.p(ex);
		}
	}

	/**
	 * Description of the Method
	 */
	public void createLayout() {
		GridBagLayout gbl = new GridBagLayout();
		GridBagConstraints c = new GridBagConstraints();
		setLayout(gbl);

		c.gridx = 0;
		c.gridy = 0;
		c.weightx = c.weighty = 0.0;
		c.fill = GridBagConstraints.HORIZONTAL;
		gbl.setConstraints(toolbar, c);
		add(toolbar);

		//c.gridx = 0;
		c.gridx++;
		c.gridy++;
		c.weightx = c.weighty = 0.0;
		c.insets = new Insets(5, 0, 5, 5);
		gbl.setConstraints(backward, c);
		add(backward);

		c.gridx++;
		gbl.setConstraints(forward, c);
		add(forward);

		c.gridx++;
		gbl.setConstraints(reload, c);
		add(reload);

		c.gridx++;
		c.fill = GridBagConstraints.NONE;
		c.weightx = c.weighty = 0.0;
		gbl.setConstraints(print_preview, c);
		add(print_preview);

		c.gridx++;
		c.fill = GridBagConstraints.HORIZONTAL;
		c.ipadx = 5;
		c.ipady = 5;
		c.weightx = 10.0;
		c.insets = new Insets(5, 0, 5, 0);
		gbl.setConstraints(url, c);
		url.setBorder(BorderFactory.createLoweredBevelBorder());
		add(url);

		c.gridx++;
		c.fill = GridBagConstraints.NONE;
		c.weightx = c.weighty = 0.0;
		c.insets = new Insets(0, 5, 0, 0);
		gbl.setConstraints(goToPage, c);
		add(goToPage);

		c.gridx = 0;
		c.gridy++;
		c.ipadx = 0;
		c.ipady = 0;
		c.fill = GridBagConstraints.BOTH;
		c.gridwidth = 7;
		c.weightx = c.weighty = 10.0;
		gbl.setConstraints(scroll, c);
		add(scroll);

		c.gridx = 0;
		c.gridy++;
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weighty = 0.1;
		gbl.setConstraints(status, c);
		add(status);

	}

	/**
	 * Description of the Method
	 */
	public void createActions() {
		// set text to "" to avoid showing action text in button--
		// we only want it in menu items
		backward.setAction(root.actions.backward);
		backward.setText("");
		forward.setAction(root.actions.forward);
		forward.setText("");
		reload.setAction(root.actions.reload);
		reload.setText("");
		goHome.setAction(root.actions.goHome);
		goHome.setText("");
		print_preview.setAction(root.actions.print_preview);
		print_preview.setText("");

		url.setAction(root.actions.load);
		goToPage.setAction(root.actions.goToPage);
		updateButtons();
	}


	/**
	 * Description of the Method
	 */
	public void goForward() {
		String uri = manager.getForward();
		view.setDocument(uri);
		updateButtons();
	}

	/**
	 * Description of the Method
	 */
	public void goBack() {
		String uri = manager.getBack();
		view.setDocument(uri);
		updateButtons();
	}

	/**
	 * Description of the Method
	 */
	public void reloadPage() {
		logger.info("Reloading Page: ");
		if (manager.getBaseURL() != null) {
			loadPage(manager.getBaseURL());
		}
	}

	/**
	 * Description of the Method
	 *
	 * @param url_text PARAM
	 */
	//TODO: make this part of an implementation of UserAgentCallback instead
	public void loadPage(final String url_text) {
		try {
			logger.info("Loading Page: " + url_text);
			view.setCursor(new Cursor(Cursor.WAIT_CURSOR));
			view.setDocument(url_text);
			view.addDocumentListener(BrowserPanel.this);

			updateButtons();

			setStatus("Successfully loaded: " + url_text);

			if (listener != null) {
				listener.pageLoadSuccess(url_text, view.getDocumentTitle());
			}
		} catch (XRRuntimeException ex) {
			XRLog.general(Level.SEVERE, "Runtime exception", ex);
            setStatus("Can't load document");
            handlePageLoadFailed(url_text, ex);
        } catch (Exception ex) {
			XRLog.general(Level.SEVERE, "Could not load page for display.", ex);
			ex.printStackTrace();
		}
	}
	
	public void exportToPdf( String path )
	{
       if (manager.getBaseURL() != null) {
           setStatus( "Exporting to " + path + "..." );
           OutputStream os = null;
           try {
               os = new FileOutputStream(path);
               try {
               ITextRenderer renderer = new ITextRenderer();

               DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
               DocumentBuilder db = dbf.newDocumentBuilder();
               Document doc =  db.parse(manager.getBaseURL());

               PDFCreationListener pdfCreationListener = new XHtmlMetaToPdfInfoAdapter( doc );
               renderer.setListener( pdfCreationListener );
                              
               renderer.setDocument(manager.getBaseURL());
               renderer.layout();

               renderer.createPDF(os);
               setStatus( "Done export." );
            } catch (Exception e) {
                XRLog.general(Level.SEVERE, "Could not export PDF.", e);
                e.printStackTrace();
                setStatus( "Error exporting to PDF." );
               } finally {
                   try {
                       os.close();
                   } catch (IOException e) {
                       // swallow
            }
        }
           } catch (Exception e) {
               e.printStackTrace();
	}
       }
	}

    private void handlePageLoadFailed(String url_text, XRRuntimeException ex) {
        final XMLResource xr;
        final String rootCause = getRootCause(ex);
        final String msg = GeneralUtil.escapeHTML(addLineBreaks(rootCause, 80));
        String notFound =
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
        "<!DOCTYPE html PUBLIC \" -//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" +
                "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" +
                        "<body>\n" +
                        "<h1>Document can't be loaded</h1>\n" +
                        "<p>Could not load the page at \n" +
                        "<pre>" + GeneralUtil.escapeHTML(url_text) + "</pre>\n" +
                        "</p>\n" +
                        "<p>The page failed to load; the error was </p>\n" +
                        "<pre>" + msg + "</pre>\n" +
                        "</body>\n" +
                        "</html>";

        xr = XMLResource.load(new StringReader(notFound));
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                root.panel.view.setDocument(xr.getDocument(), null);
            }
        });
   }

    private String addLineBreaks(String _text, int maxLineLength) {
        StringBuffer broken = new StringBuffer(_text.length() + 10);
        boolean needBreak = false;
        for (int i = 0; i < _text.length(); i++) {
            if (i > 0 && i % maxLineLength == 0) needBreak = true;

            final char c = _text.charAt(i);
            if (needBreak && Character.isWhitespace(c)) {
                System.out.println("Breaking: " + broken.toString());
                needBreak = false;
                broken.append('\n');
            } else {
                broken.append(c);
            }
        }
        System.out.println("Broken! " + broken.toString());
        return broken.toString();  
    }

    private String getRootCause(Exception ex) {
        // FIXME
        Throwable cause = ex;
        while (cause != null) {
            cause = cause.getCause();
        }

        return cause == null ? ex.getMessage() : cause.getMessage();
    }

    public void documentStarted() {
		// TODO...
	}

	/**
	 * Description of the Method
	 */
	public void documentLoaded() {
		view.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
	}

	/**
	 * Sets the status attribute of the BrowserPanel object
	 *
	 * @param txt The new status value
	 */
	public void setStatus(String txt) {
		status.text.setText(txt);
	}

	/**
	 * Description of the Method
	 */
	protected void updateButtons() {
		if (manager.hasBack()) {
			root.actions.backward.setEnabled(true);
		} else {
			root.actions.backward.setEnabled(false);
		}
		if (manager.hasForward()) {
			root.actions.forward.setEnabled(true);
		} else {
			root.actions.forward.setEnabled(false);
		}

		url.setText(manager.getBaseURL());
	}


	public void onLayoutException(Throwable t) {
        // TODO: clean
        t.printStackTrace();
	}

	public void onRenderException(Throwable t) {
        // TODO: clean
		t.printStackTrace();
	}
}

/*
 * $Id$
 *
 * $Log$
 * Revision 1.41  2009/08/03 19:36:29  pdoubleya
 * Add new listener for PDF generation which automatically parses HTML header information and adds it as PDF properties (e.g. title, subject). Patch submitted by Tim Telcik in email. Thanks!
 *
 * Revision 1.38  2008/09/06 18:44:29  peterbrant
 * Add PDF export to browser (patch from Mykola Gurov)
 *
 * Revision 1.37  2008/05/30 13:25:00  pdoubleya
 * Remove commented code blocks, add error handling if can't load page.
 *
 * Revision 1.36  2007/07/14 12:56:40  pdoubleya
 * Browser toolbar should not be movable
 *
 * Revision 1.35  2007/07/13 13:32:31  pdoubleya
 * Add webstart entry point for browser with no URL or File/open option. Move Zoom to menu entry, add warning on first zoom. Move preview to menu entry. Reorganize launch method a little to allow for multiple entry points.
 *
 * Revision 1.34  2007/06/19 21:28:41  pdoubleya
 * Support for document scaling, from Christophe Marchand via email. Browser demo panel is now scalable as well.
 *
 * Revision 1.33  2007/04/03 13:18:15  peterbrant
 * Implement new DocumentListener API
 *
 * Revision 1.32  2006/08/06 21:27:00  pdoubleya
 * Removed printing for R6.
 *
 * Revision 1.31  2006/08/03 14:15:01  pdoubleya
 * Print action
 *
 * Revision 1.30  2006/07/31 14:20:54  pdoubleya
 * Bunch of cleanups and fixes. Now using a toolbar for actions, added Home button, next/prev navigation actions to facilitate demo file browsing, loading demo pages from a list, about dlg and link to user's manual.
 *
 * Revision 1.29  2005/10/27 00:08:50  tobega
 * Sorted out Context into RenderingContext and LayoutContext
 *
 * Revision 1.28  2005/10/08 17:40:17  tobega
 * Patch from Peter Brant
 *
 * Revision 1.27  2005/06/19 23:32:46  joshy
 * cursor stuff
 * Issue number:
 * Obtained from:
 * Submitted by:
 * Reviewed by:
 *
 * Revision 1.26  2005/06/16 07:24:43  tobega
 * Fixed background image bug.
 * Caching images in browser.
 * Enhanced LinkListener.
 * Some house-cleaning, playing with Idea's code inspection utility.
 *
 * Revision 1.25  2005/06/15 13:35:27  tobega
 * Fixed history
 *
 * Revision 1.24  2005/06/15 10:56:13  tobega
 * cleaned up a bit of URL mess, centralizing URI-resolution and loading to UserAgentCallback
 *
 * Revision 1.23  2005/06/01 21:36:34  tobega
 * Got image scaling working, and did some refactoring along the way
 *
 * Revision 1.22  2005/03/28 20:02:01  pdoubleya
 * Removed commented code, cleaned menu bar.
 *
 * Revision 1.21  2005/02/05 11:35:59  pdoubleya
 * Load pages directly from XMLResource.
 *
 * Revision 1.20  2005/02/03 23:19:43  pdoubleya
 * Uses ResourceProvider for loading files.
 *
 * Revision 1.19  2005/01/30 10:21:49  pdoubleya
 * Extracted keyboard actions to FSScrollPane.
 *
 * Revision 1.18  2005/01/29 20:17:42  pdoubleya
 * Updated panels to support page up/down properly, and formatted/cleaned.
 *
 * Revision 1.17  2005/01/13 00:48:47  tobega
 * Added preparation of values for a form submission
 *
 * Revision 1.16  2004/12/29 10:39:38  tobega
 * Separated current state Context into LayoutContext and the rest into SharedContext.
 *
 * Revision 1.15  2004/12/12 16:11:04  tobega
 * Fixed bug concerning order of inline content. Added a demo for pseudo-elements.
 *
 * Revision 1.14  2004/12/12 03:33:07  tobega
 * Renamed x and u to avoid confusing IDE. But that got cvs in a twist. See if this does it
 *
 * Revision 1.13  2004/12/12 02:54:11  tobega
 * Making progress
 *
 * Revision 1.12  2004/12/01 00:13:34  tobega
 * Fixed incorrect handling of http urls.
 *
 * Revision 1.11  2004/11/27 15:46:37  joshy
 * lots of cleanup to make the code clearer
 *
 * Issue number:
 * Obtained from:
 * Submitted by:
 * Reviewed by:
 *
 * Revision 1.10  2004/11/17 14:58:17  joshy
 * added actions for font resizing
 *
 * Issue number:
 * Obtained from:
 * Submitted by:
 * Reviewed by:
 *
 * Revision 1.9  2004/11/17 00:44:54  joshy
 * fixed bug in the history manager
 * added cursor support to the link listener
 *
 *
 * Issue number:
 * Obtained from:
 * Submitted by:
 * Reviewed by:
 *
 * Revision 1.8  2004/11/16 07:25:20  tobega
 * Renamed HTMLPanel to BasicPanel
 *
 * Revision 1.7  2004/11/16 03:43:25  joshy
 * first pass at printing support
 * Issue number:
 * Obtained from:
 * Submitted by:
 * Reviewed by:
 *
 * Revision 1.6  2004/11/12 20:43:29  joshy
 * added demo of custom font
 *
 * Issue number:
 * Obtained from:
 * Submitted by:
 * Reviewed by:
 *
 * Revision 1.5  2004/11/12 02:23:56  joshy
 * added new APIs for rendering context, xhtmlpanel, and graphics2drenderer.
 * initial support for font mapping additions
 *
 *
 * Issue number:
 * Obtained from:
 * Submitted by:
 * Reviewed by:
 *
 * Revision 1.4  2004/10/23 14:38:58  pdoubleya
 * Re-formatted using JavaStyle tool.
 * Cleaned imports to resolve wildcards except for common packages (java.io, java.util, etc)
 * Added CVS log comments at bottom.
 *
 *
 */