/*  Copyright (C) 2003-2012 JabRef contributors.
    This program 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.

    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 General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package net.sf.jabref;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.print.PrinterException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;

import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.JobName;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;

import net.sf.jabref.export.layout.Layout;
import net.sf.jabref.export.layout.LayoutHelper;
import net.sf.jabref.export.ExportFormats;
import net.sf.jabref.gui.PreviewPanelTransferHandler;

/**
 * Displays an BibtexEntry using the given layout format.
 */
public class PreviewPanel extends JPanel implements VetoableChangeListener, SearchTextListener, EntryContainer {

	/**
	 * The bibtex entry currently shown
	 */
	BibtexEntry entry;

	MetaData metaData;

	/**
	 * If a database is set, the preview will attempt to resolve strings in the
	 * previewed entry using that database.
	 */
	BibtexDatabase database;

	Layout layout;

	String layoutFile;

	public JEditorPane previewPane;

	JScrollPane scrollPane;
	PdfPreviewPanel pdfPreviewPanel;

	BasePanel panel;

    /**
     * @param database
     *            (may be null) Optionally used to resolve strings.
     * @param entry
     *            (may be null) If given this entry is shown otherwise you have
     *            to call setEntry to make something visible.
     * @param panel
     *            (may be null) If not given no toolbar is shown on the right
     *            hand side.
     * @param metaData
     *            (must be given) Used for resolving pdf directories for links.
     * @param layoutFile
     *            (must be given) Used for layout
     */
    public PreviewPanel(BibtexDatabase database, BibtexEntry entry,
        BasePanel panel, MetaData metaData, String layoutFile) {
        this(database, entry, panel, metaData, layoutFile, false);
    }

    /**
	 * @param database
	 *            (may be null) Optionally used to resolve strings.
	 * @param entry
	 *            (may be null) If given this entry is shown otherwise you have
	 *            to call setEntry to make something visible.
	 * @param panel
	 *            (may be null) If not given no toolbar is shown on the right
	 *            hand side.
	 * @param metaData
	 *            (must be given) Used for resolving pdf directories for links.
	 * @param layoutFile
	 *            (must be given) Used for layout
	 * @param withPDFPreview if true, a PDF preview is included in the PreviewPanel
	 */
	public PreviewPanel(BibtexDatabase database, BibtexEntry entry,
		BasePanel panel, MetaData metaData, String layoutFile, boolean withPDFPreview) {
		this(panel, metaData, layoutFile, withPDFPreview);
		this.database = database;
		setEntry(entry);
	}
	
    /**
     * 
     * @param panel
     *            (may be null) If not given no toolbar is shown on the right
     *            hand side.
     * @param metaData
     *            (must be given) Used for resolving pdf directories for links.
     * @param layoutFile
     *            (must be given) Used for layout
     */
    public PreviewPanel(BasePanel panel, MetaData metaData, String layoutFile) {
        this(panel, metaData, layoutFile, false);
    }

    /**
	 * 
	 * @param panel
	 *            (may be null) If not given no toolbar is shown on the right
	 *            hand side.
	 * @param metaData
	 *            (must be given) Used for resolving pdf directories for links.
	 * @param layoutFile
	 *            (must be given) Used for layout
     * @param withPDFPreview if true, a PDF preview is included in the PreviewPanel. 
     * The user can override this setting by setting the config setting JabRefPreferences.PDF_PREVIEW to false.
     */
	private PreviewPanel(BasePanel panel, MetaData metaData, String layoutFile, boolean withPDFPreview) {
		super(new BorderLayout(), true);

		withPDFPreview = withPDFPreview && JabRefPreferences.getInstance().getBoolean(JabRefPreferences.PDF_PREVIEW);
		
		this.panel = panel;
		this.metaData = metaData;
		this.layoutFile = layoutFile;
		this.previewPane = createPreviewPane();
		if (withPDFPreview) {
			this.pdfPreviewPanel = new PdfPreviewPanel(metaData);
		} else {
			this.pdfPreviewPanel = null;
		}
		if (panel != null) {
			// dropped files handler only created for main window
			// not for Windows as like the search results window
			this.previewPane.setTransferHandler(new PreviewPanelTransferHandler(panel.frame(), this, this.previewPane.getTransferHandler()));
		}

		// Set up scroll pane for preview pane
		scrollPane = new JScrollPane(previewPane,
			JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
			JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		scrollPane.setBorder(null);
		
		/*
		 * If we have been given a panel and the preference option
		 * previewPrintButton is set, show the tool bar
		 */
		if (panel != null
			&& JabRefPreferences.getInstance().getBoolean("previewPrintButton")) {
			add(createToolBar(), BorderLayout.LINE_START);
		}

		if (withPDFPreview) {
			JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
					scrollPane, pdfPreviewPanel);
			splitPane.setOneTouchExpandable(true);
			
			// int oneThird = panel.getWidth()/3;
			int oneThird = 400; // arbitrarily set as panel.getWidth() always
								// returns 0 at this point
			splitPane.setDividerLocation(oneThird*2);

			// Provide minimum sizes for the two components in the split pane
//			Dimension minimumSize = new Dimension(oneThird * 2, 50);
//			scrollPane.setMinimumSize(minimumSize);
//			minimumSize = new Dimension(oneThird, 50);
//			pdfScrollPane.setMinimumSize(minimumSize);
			add(splitPane);
		} else {
			add(scrollPane, BorderLayout.CENTER);
		}



	}
	
	class PrintAction extends AbstractAction {

		public PrintAction() {
			super(Globals.lang("Print Preview"), GUIGlobals.getImage("psSmall"));
			putValue(SHORT_DESCRIPTION, Globals.lang("Print Preview"));
		}

		//DocumentPrinter printerService;

		public void actionPerformed(ActionEvent arg0) {

			// Background this, as it takes a while.
			new Thread() {
				public void run() {
					try {
						PrintRequestAttributeSet pras = new HashPrintRequestAttributeSet();
						pras.add(new JobName(entry.getCiteKey(), null));
						previewPane.print(null, null, true, null, pras, false);

					} catch (PrinterException e) {

						// Inform the user... we don't know what to do.
						JOptionPane.showMessageDialog(PreviewPanel.this,
							Globals.lang("Could not print preview") + ".\n"
								+ e.getMessage(),
                                                                Globals.lang("Printing Entry Preview"),
							JOptionPane.ERROR_MESSAGE);
					}
				}
			}.start();
		}
	}

	Action printAction;

	public Action getPrintAction() {
		if (printAction == null)
			printAction = new PrintAction();
		return printAction;
	}

	class CloseAction extends AbstractAction {
		public CloseAction() {
			super(Globals.lang("Close window"), GUIGlobals.getImage("close"));
			putValue(SHORT_DESCRIPTION, Globals.lang("Close window"));
		}

		public void actionPerformed(ActionEvent e) {
			panel.hideBottomComponent();
		}
	}

	Action closeAction;

	private ArrayList<String> wordsToHighlight = null;

	public Action getCloseAction() {
		if (closeAction == null)
			closeAction = new CloseAction();
		return closeAction;
	}

	JPopupMenu createPopupMenu() {
		JPopupMenu menu = new JPopupMenu();
		menu.add(getPrintAction());
		if (panel != null) {
			menu.add(panel.frame.switchPreview);
		}
		return menu;
	}

	JToolBar createToolBar() {

		JToolBar tlb = new JToolBar(JToolBar.VERTICAL);
		JabRefPreferences prefs = JabRefPreferences.getInstance();
		Action printAction = getPrintAction();
		Action closeAction = getCloseAction();

		tlb.setMargin(new Insets(0, 0, 0, 2));

		// The toolbar carries all the key bindings that are valid for the whole
		// window.
		ActionMap am = tlb.getActionMap();
		InputMap im = tlb.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);

		im.put(prefs.getKey("Close entry preview"), "close");
		am.put("close", closeAction);

		im.put(prefs.getKey("Print entry preview"), "print");
		am.put("print", printAction);

		tlb.setFloatable(false);

		// Add actions (and thus buttons)
		tlb.add(closeAction);

		tlb.addSeparator();

		tlb.add(printAction);

		Component[] comps = tlb.getComponents();

		for (int i = 0; i < comps.length; i++)
			((JComponent) comps[i]).setOpaque(false);

		return tlb;
	}

	JEditorPane createPreviewPane() {
		JEditorPane previewPane = new JEditorPane() {
			public Dimension getPreferredScrollableViewportSize() {
				return getPreferredSize();
			}

		};
		previewPane.setMargin(new Insets(3, 3, 3, 3));

		previewPane.setComponentPopupMenu(createPopupMenu());
		
		previewPane.setEditable(false);
		previewPane.setDragEnabled(true); // this has an effect only, if no custom transfer handler is registered. We keep the statement if the transfer handler is removed.
		previewPane.setContentType("text/html");
		previewPane.addHyperlinkListener(new HyperlinkListener() {
			public void hyperlinkUpdate(HyperlinkEvent hyperlinkEvent) {
				if (hyperlinkEvent.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
					try {
						String address = hyperlinkEvent.getURL().toString();
						Util.openExternalViewer(PreviewPanel.this.metaData,
							address, "url");
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		});
		
		return previewPane;
	}

	public void setDatabase(BibtexDatabase db) {
		database = db;
	}

    public void setMetaData(MetaData metaData) {
        this.metaData = metaData;
    }

    public void readLayout(String layoutFormat) throws Exception {
		layoutFile = layoutFormat;
		readLayout();
	}

	public void readLayout() throws Exception {
		StringReader sr = new StringReader(layoutFile.replaceAll("__NEWLINE__",
			"\n"));
		layout = new LayoutHelper(sr)
			.getLayoutFromText(Globals.FORMATTER_PACKAGE);
	}

    public void setLayout(Layout layout) {
        this.layout = layout;
    }

    public void setEntry(BibtexEntry newEntry) {
		if (newEntry != entry) {
			if (entry != null)
				entry.removePropertyChangeListener(this);
			newEntry.addPropertyChangeListener(this);
		}
		entry = newEntry;
		try {
			readLayout();
			update();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
    
    public BibtexEntry getEntry() {
    	return this.entry;
    }

	public void update() {

		StringBuffer sb = new StringBuffer();
        ExportFormats.entryNumber = 1; // Set entry number in case that is included in the preview layout.
		if (entry != null) {
			sb.append(layout.doLayout(entry, database, wordsToHighlight));
		}
		previewPane.setText(sb.toString());
		previewPane.revalidate();

		// Scroll to top:
		final JScrollBar bar = scrollPane.getVerticalScrollBar();
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				bar.setValue(0);
			}
		});
		
		// update pdf preview
		if (pdfPreviewPanel != null) {
			pdfPreviewPanel.updatePanel(entry);
		}
	}


	public boolean hasEntry() {
		return (entry != null);
	}

	/**
	 * The PreviewPanel has registered itself as an event listener with the
	 * currently displayed BibtexEntry. If the entry changes, an event is
	 * received here, and we can update the preview immediately.
	 */
	public void vetoableChange(PropertyChangeEvent evt)
		throws PropertyVetoException {
		// TODO updating here is not really necessary isn't it?
		// Only if we are visible.
		update();
	}

	@Override
	public void searchText(ArrayList<String> words) {
		if (Globals.prefs.getBoolean("highLightWords")) {
			this.wordsToHighlight = words;
			update();
		} else {
			if (this.wordsToHighlight != null) {
				// setting of "highLightWords" seems to have changed.
				// clear all highlights and remember the clearing (by wordsToHighlight = null)
				this.wordsToHighlight = null;
				update();
			}
		}
	}
}
