package lu.tudor.santec.gecamed.esante.gui.dialogs;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.nio.ByteBuffer;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;

import lu.tudor.santec.gecamed.core.gui.MainFrame;
import lu.tudor.santec.gecamed.core.gui.plugin.filehandler.FileOpener;
import lu.tudor.santec.gecamed.core.gui.widgets.ErrorDialog;
import lu.tudor.santec.gecamed.esante.gui.utils.ESanteGuiUtils;
import lu.tudor.santec.i18n.Translatrix;

import org.apache.log4j.Logger;

import com.sun.pdfview.OutlineNode;
import com.sun.pdfview.PDFDestination;
import com.sun.pdfview.PDFFile;
import com.sun.pdfview.PDFObject;
import com.sun.pdfview.PDFPage;
import com.sun.pdfview.PageChangeListener;
import com.sun.pdfview.PagePanel;
import com.sun.pdfview.ThumbPanel;
import com.sun.pdfview.action.GoToAction;
import com.sun.pdfview.action.PDFAction;

/**
 * Simple PDF viewer for validating generated PDF/A documents before uploading them to the eSanté platform.<br/>
 * <br/>
 * <i>This code is based on the PDF-Viewer example from Swinglabs that has been created for the Kenai project: <b>https://java.net/projects/pdf-renderer</b></i>
 * @author donak
 * @version <br>
 *          $Log: PdfPreviewDialog.java,v $
 *          Revision 1.8  2014-02-12 13:30:45  ferring
 *          Thumb Thread and center SplitPane removed
 *
 *          Revision 1.7  2014-02-12 12:14:56  ferring
 *          PDF preview reformatted, comments removed and PDF button added
 *
 *          Revision 1.6  2014-01-29 16:57:50  donak
 *          Renamed references to HPD to eHealth in respect to the request of the eHealth Agency
 *          Removed template DSP-11-12 as it is not needed and was created only for internal testing purposes
 *
 *          Revision 1.5  2014-01-28 16:02:44  ferring
 *          eHealth ID management implemented
 *
 *          Revision 1.4  2014-01-13 09:32:34  ferring
 *          TODO translate
 *
 *          Revision 1.3  2014-01-10 10:51:14  ferring
 *          some display problems fixed
 *
 *          Revision 1.2  2014-01-09 14:30:50  ferring
 *          Preview dialog improved
 *
 *          Revision 1.1  2014-01-07 13:19:44  donak
 *          Adding initial version of pdf preview dialog
 *          Several bug fixes
 *
 */
public class PdfPreviewDialog extends ESanteDialog implements KeyListener, TreeSelectionListener, PageChangeListener
{
	
	private static final long		serialVersionUID	= -6569033218912163044L;
	
	public static final String		TITLE				= "SwingLabs PDF Viewer";
	
	/** the logger Object for this class */
	private static Logger			logger				= Logger.getLogger(PdfPreviewDialog.class.getName());
	
	private static PdfPreviewDialog	viewerInstance		= null;
	
	
	
	private byte[]					fileBytes;
	private PDFFile					curFile;
	private JSplitPane				navigationSplit;
	private JScrollPane				outlineScroller;
//	private JSplitPane				split;
	private JScrollPane				thumbscroll;
	private ThumbPanel				thumbs;
	private PagePanel				page;
	private int						curpage				= -1;
	private JTextField				pageField;
	private OutlineNode				outline				= null;
	private PagePreparer			pagePrep;
//	private ThumbAction				thumbAction			= new ThumbAction();
	private PageBuilder				pb					= new PageBuilder();
	
	
	
	public PdfPreviewDialog ()
	{
		super(MainFrame.getInstance(),
				Translatrix.getTranslationString("esante.pdfValidationDialog.Title"),
				OK_CANCEL_BUTTON_MODE);
		init();
	}
	
	
	protected void init ()
	{
		JButton jb;
		JPanel navigationPanel;
		
		page = new PagePanel();
		page.addKeyListener(this);
		
//		split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
//		split.addPropertyChangeListener("dividerLocation", this.thumbAction);
//		split.setOneTouchExpandable(true);
		
		thumbs = new ThumbPanel(null);
		thumbscroll = new JScrollPane(this.thumbs, 22, 31);
		
		
		navigationPanel = new JPanel(new BorderLayout());
		navigationPanel.setOpaque(false);
		
		navigationSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
		navigationSplit.setOneTouchExpandable(true);
		navigationSplit.setOpaque(false);
		
		outlineScroller = new JScrollPane();
		outlineScroller.getViewport().setOpaque(false);
		outlineScroller.setOpaque(false);
		
		JToolBar toolbar = new JToolBar();
		toolbar.setFloatable(false);
		
		jb = new JButton(new AbstractAction("First")
		{
			private static final long	serialVersionUID	= 1L;
			
			
			public void actionPerformed (ActionEvent evt)
			{
				PdfPreviewDialog.this.doFirst();
			}
		});
		jb.setText("<<");
		toolbar.add(jb);
		
		jb = new JButton(new AbstractAction("Prev")
		{
			private static final long	serialVersionUID	= 1L;
			
			
			public void actionPerformed (ActionEvent evt)
			{
				PdfPreviewDialog.this.doPrev();
			}
		});
		jb.setText("<");
		toolbar.add(jb);
		
		pageField = new JTextField("-", 3);
		pageField.setMaximumSize(new Dimension(45, 32));
		pageField.addActionListener(new ActionListener()
		{
			public void actionPerformed (ActionEvent evt)
			{
				PdfPreviewDialog.this.doPageTyped();
			}
		});
		toolbar.add(this.pageField);
		
		jb = new JButton(new AbstractAction("Next")
		{
			private static final long	serialVersionUID	= 1L;
			
			
			public void actionPerformed (ActionEvent evt)
			{
				PdfPreviewDialog.this.doNext();
			}
		});
		jb.setText(">");
		toolbar.add(jb);
		
		jb = new JButton(new AbstractAction("Last")
		{
			private static final long	serialVersionUID	= 1L;
			
			
			public void actionPerformed (ActionEvent evt)
			{
				PdfPreviewDialog.this.doLast();
			}
		});
		jb.setText(">>");
		toolbar.add(jb);
		
		// add components
		navigationSplit.setTopComponent(outlineScroller);
		navigationSplit.setBottomComponent(thumbscroll);
		
		navigationPanel.add(toolbar, BorderLayout.NORTH);
		navigationPanel.add(navigationSplit, BorderLayout.CENTER);
		
//		split.setLeftComponent(navigationPanel);
//		split.setRightComponent(page);
		
		// add description
		JLabel description = new JLabel(Translatrix.getTranslationString("esante.pdfValidationDialog.description"));
		description.setBorder(BorderFactory.createEmptyBorder(10, 10, 20, 10));
		
		mainPanel.setLayout(new BorderLayout());
		mainPanel.add(page,				BorderLayout.CENTER);
		mainPanel.add(navigationPanel,	BorderLayout.WEST);
		mainPanel.add(description,		BorderLayout.NORTH);
		
		JButton pdfButton = new JButton(new AbstractAction(
				Translatrix.getTranslationString("esante.pdfValidationDialog.pdfButton"),
				ESanteGuiUtils.getIcon("pdf.png", 20))
		{
			private static final long	serialVersionUID	= 1L;

			public void actionPerformed (ActionEvent e)
			{
				openPdf();
			}
		});
		addButton(pdfButton);
		
		page.getPage();
	}
	
	
	public void gotoPage (int pagenum)
	{
		if (pagenum < 0)
			pagenum = 0;
		else if (pagenum >= this.curFile.getNumPages())
		{
			pagenum = this.curFile.getNumPages() - 1;
		}
		forceGotoPage(pagenum);
	}
	
	
	public void forceGotoPage (int pagenum)
	{
		if (pagenum <= 0)
			pagenum = 0;
		else if (pagenum >= curFile.getNumPages())
			pagenum = curFile.getNumPages() - 1;
		
		curpage = pagenum;
		
		pageField.setText(String.valueOf(this.curpage + 1));
		
		PDFPage pg = curFile.getPage(pagenum + 1);
		page.showPage(pg);
		page.requestFocus();
		thumbs.pageShown(pagenum);
		
		if (pagePrep != null)
			pagePrep.quit();
		
		pagePrep = new PagePreparer(pagenum);
		pagePrep.start();
		
		page.getPage();
	}
	
	
	
	/**
	 * Displays a generated pdf/a file on screen and allows the user to validate if the content is correctly displayed
	 * 
	 * @param pdfFile
	 *            The data of the pdf file
	 * @param pdfName
	 *            The name of the pdf file that should be displayed in the dialog title
	 * @return True, if the user validated the file, false otherwise
	 */
	public static boolean validatePdf (byte[] pdfFile, String pdfName)
	{
		boolean validationState = false;
		
		PdfPreviewDialog validator = PdfPreviewDialog.getInstance();
		validationState = validator.showPreview(pdfFile, pdfName);
		return validationState;
	}
	
	
	/**
	 * Open a pdf file, display it on screen, and allow the user to validate it
	 * @param pdfFile
	 *            The data of the pdf file
	 * @param pdfName
	 *            The name of the pdf file that should be displayed in the dialog title
	 * @return True, if the user validated the file, false otherwise
	 * @throws IOException If the pdf could not be displayed (e.g. due to corrupt data)
	 */
	private boolean showPreview (byte[] pdf, String pdfName)
	{
		doClose();
		
		fileBytes	= pdf;
		try
		{
			ByteBuffer buf = ByteBuffer.allocate(fileBytes.length);
			buf.put(fileBytes);
			curFile = new PDFFile(buf);
		}
		catch (IOException ioe)
		{
			logger.error("Error while trying to create a PDF preview", ioe);
			ErrorDialog.showErrorDialog(MainFrame.getInstance(),
					Translatrix.getTranslationString("esante.pdfValidationDialog.errorTitle"),
					Translatrix.getTranslationString("esante.pdfValidationDialog.errorMessage"),
					ioe);
			
			return false;
		}
		
		setTitle(Translatrix.getTranslationString("esante.pdfValidationDialog.Title"));
		
		thumbs = new ThumbPanel(this.curFile);
		thumbs.addPageChangeListener(this);
		thumbscroll.getViewport().setView(this.thumbs);
		thumbscroll.getViewport().setBackground(Color.gray);
		page.getPage();
		
		if (this.outline != null)
		{
			JTree jt = new JTree(this.outline);
			jt.setRootVisible(false);
			jt.addTreeSelectionListener(this);
			outlineScroller.setViewportView(jt);
			if (jt.getRowCount() > 0)
				jt.setSelectionRow(0);
			navigationSplit.setDividerLocation(0.25d);
			navigationSplit.setDividerSize(5);
		}
		else
		{
			navigationSplit.setDividerLocation(0.0d);
			navigationSplit.setDividerSize(0);
		}
		forceGotoPage(0);
		
		// define the default size
		Dimension mfSize = MainFrame.getInstance().getSize();
		Dimension size = new Dimension((int)(mfSize.width / 3.0d * 2.0d), (int)(mfSize.height / 3.0d * 2.0d));
		if (size.width < 800)
			size.width = mfSize.width < 800 ? mfSize.width : 800;
		if (size.height < 600)
			size.height = mfSize.height < 600 ? mfSize.height : 600;
		setSize(size);
		
		// resize to last size, if a size is saved
		setResizingOptions(RESIZING_SIZE);
		showCenteredDialog();
		
		return getButtonOption() == OK_OPTION;
	}
	
	
	public void doClose ()
	{
		if (this.thumbs != null)
		{
			this.thumbs.stop();
		}
		this.thumbs = new ThumbPanel(null);
		this.thumbscroll.getViewport().setView(this.thumbs);
		
		this.page.showPage(null);
		this.curFile = null;
		setTitle("GECAMed PDF Validation");
		page.getPage();
	}
	
	
	public void doFitInWindow ()
	{
		this.page.useZoomTool(false);
		this.page.setClip(null);
	}
	
	
//	public void doThumbs (boolean show)
//	{
//		if (show)
//		{
//			this.split.setDividerLocation(this.thumbs.getPreferredSize().width + this.thumbscroll.getVerticalScrollBar().getWidth() + 4);
//		}
//		else
//		{
//			this.split.setDividerLocation(0);
//		}
//	}
	
	
	public void openPdf ()
	{
		FileOpener.open(fileBytes, ".pdf", null);
	}
	
	
	public void doNext ()
	{
		gotoPage(this.curpage + 1);
	}
	
	
	public void doPrev ()
	{
		gotoPage(this.curpage - 1);
	}
	
	
	public void doFirst ()
	{
		gotoPage(0);
	}
	
	
	public void doLast ()
	{
		gotoPage(this.curFile.getNumPages() - 1);
	}
	
	
	public void doPageTyped ()
	{
		int pagenum = -1;
		try
		{
			pagenum = Integer.parseInt(this.pageField.getText()) - 1;
		}
		catch (NumberFormatException nfe)
		{
		}
		if (pagenum >= this.curFile.getNumPages())
		{
			pagenum = this.curFile.getNumPages() - 1;
		}
		if (pagenum >= 0)
		{
			if (pagenum != this.curpage)
				gotoPage(pagenum);
		}
		else
			this.pageField.setText(String.valueOf(this.curpage));
	}
	
	
	/**
	 * Provides a reference to the pdf viewer instance
	 * 
	 * @return The (only) pdf viewer instance
	 */
	private static PdfPreviewDialog getInstance ()
	{
		if (PdfPreviewDialog.viewerInstance == null) 
		{
			PdfPreviewDialog.viewerInstance = new PdfPreviewDialog();
		}
		return PdfPreviewDialog.viewerInstance;
	}
	
	
	public void keyPressed (KeyEvent evt)
	{
		reactOnKey(evt);
	}
	
	
	public void keyReleased (KeyEvent evt)
	{
	}
	
	
	public void keyTyped (KeyEvent evt)
	{
		char key = evt.getKeyChar();
		if ((key >= '0') && (key <= '9'))
		{
			int val = key - '0';
			this.pb.keyTyped(val);
		}
	}
	
	
	private void reactOnKey (KeyEvent evt)
	{
		int code = evt.getKeyCode();
		
		switch (code)
		{
			case KeyEvent.VK_LEFT:		// 37
			case KeyEvent.VK_PAGE_UP:	// 33
			case KeyEvent.VK_UP:		// 38
				doPrev();
				break;
			
			case KeyEvent.VK_RIGHT:		// 39
			case KeyEvent.VK_DOWN:		// 40
			case KeyEvent.VK_PAGE_DOWN:	// 34
			case KeyEvent.VK_SPACE:		// 32
				doNext();
				break;
			
			case KeyEvent.VK_HOME:		// 36
				doFirst();
				break;
			
			case KeyEvent.VK_END:		// 35
				doLast();
				break;
		
//			case KeyEvent.VK_ESCAPE:	// 27
//				setFullScreenMode(false, false);
//				break;
		}
	}
	
	
	public void valueChanged (TreeSelectionEvent e)
	{
		if (e.isAddedPath())
		{
			OutlineNode node = (OutlineNode) e.getPath().getLastPathComponent();
			if (node == null)
			{
				return;
			}
			try
			{
				PDFAction action = node.getAction();
				if (action == null)
				{
					return;
				}
				
				if ((action instanceof GoToAction))
				{
					PDFDestination dest = ((GoToAction) action).getDestination();
					if (dest == null)
					{
						return;
					}
					
					PDFObject page = dest.getPage();
					if (page == null)
					{
						return;
					}
					
					int pageNum = this.curFile.getPageNumber(page);
					if (pageNum >= 0)
						gotoPage(pageNum);
				}
			}
			catch (IOException ioe)
			{
				logger.warn("Couldn't call page of PDF upload preview", ioe);
			}
		}
	}
	
	
	
	private class PageBuilder implements Runnable
	{
		private int					value	= 0;
		private long				timeout;
		private Thread				anim;
		private static final long	TIMEOUT	= 500L;
		
		
		public synchronized void keyTyped (int keyval)
		{
			this.value = (this.value * 10 + keyval);
			this.timeout = (System.currentTimeMillis() + TIMEOUT);
			if (this.anim == null)
			{
				this.anim = new Thread(this);
				this.anim.setName(getClass().getName());
				this.anim.start();
			}
		}
		
		
		public void run ()
		{
			long now;
			long then;
			synchronized (this)
			{
				now = System.currentTimeMillis();
				then = this.timeout;
			}
			while (now < then)
			{
				try
				{
					Thread.sleep(this.timeout - now);
				}
				catch (InterruptedException ie)
				{
				}
				synchronized (this)
				{
					now = System.currentTimeMillis();
					then = this.timeout;
				}
			}
			synchronized (this)
			{
				PdfPreviewDialog.this.gotoPage(this.value - 1);
				this.anim = null;
				this.value = 0;
			}
		}
	}
	
	
	
	private class PagePreparer extends Thread
	{
		private int	waitForPage;
		private int	prepPage;
		
		
		public PagePreparer (int waitForPage)
		{
			setDaemon(true);
			setName(getClass().getName());
			
			this.waitForPage = waitForPage;
			this.prepPage = (this.waitForPage + 1);
		}
		
		
		public void quit ()
		{
			this.waitForPage = -1;
		}
		
		
		public void run ()
		{
			Dimension size = null;
			Rectangle2D clip = null;
			
			// wait for the dialog to show
			while (!PdfPreviewDialog.this.isVisible())
			{
				try
				{
					Thread.sleep(100);
				}
				catch (InterruptedException e)
				{
					logger.warn("Error while waiting for Dialog to show.", e);
				}
			}
			
			if (PdfPreviewDialog.this.page != null)
			{
//				System.out.println("Start waiting: " + getId());
//				page.waitForCurrentPage();
//				System.out.println("Finished waiting: " + getId());
				size = PdfPreviewDialog.this.page.getCurSize();
				if (size == null)
					size = new Dimension(page.getWidth(), page.getHeight());
				clip = PdfPreviewDialog.this.page.getCurClip();
			}
			
			if (this.waitForPage == curpage)
			{
				PDFPage pdfPage = PdfPreviewDialog.this.curFile.getPage(this.prepPage + 1, true);
				if ((pdfPage != null) && (size != null) && (this.waitForPage == PdfPreviewDialog.this.curpage))
				{
					pdfPage.getImage(size.width, size.height, clip, null, true, true);
				}
			}
			
			
//			pack();
//			validate();
//			repaint();
			doFitInWindow();
		}
	}
	
	
	
//	private class ThumbAction extends AbstractAction implements PropertyChangeListener
//	{
//		private static final long	serialVersionUID	= 2262540123074373889L;
//		
//		boolean						isOpen				= true;
//		
//		
//		public ThumbAction ()
//		{
//			super();
//		}
//		
//		
//		public void propertyChange (PropertyChangeEvent evt)
//		{
//			int v = ((Integer) evt.getNewValue()).intValue();
//			if (v <= 1)
//			{
//				this.isOpen = false;
//				putValue("ActionCommandKey", "Show thumbnails");
//				putValue("Name", "Show thumbnails");
//			}
//			else
//			{
//				this.isOpen = true;
//				putValue("ActionCommandKey", "Hide thumbnails");
//				putValue("Name", "Hide thumbnails");
//			}
//		}
//		
//		
//		public void actionPerformed (ActionEvent evt)
//		{
////			PdfPreviewDialog.this.doThumbs(!this.isOpen);
//		}
//	}
}