/*******************************************************************************
 * This file is part of GECAMed.
 * 
 * GECAMed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (L-GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GECAMed 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 (L-GPL)
 * along with GECAMed.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * GECAMed is Copyrighted by the Centre de Recherche Public Henri Tudor (http://www.tudor.lu)
 * (c) CRP Henri Tudor, Luxembourg, 2008
 *******************************************************************************/
package lu.tudor.santec.gecamed.core.gui.widgets;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.PixelGrabber;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;

import javax.media.jai.RenderedImageAdapter;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;

import lu.tudor.santec.gecamed.core.gui.GECAMedColors;
import lu.tudor.santec.gecamed.core.gui.GECAMedIconNames;
import lu.tudor.santec.gecamed.core.gui.GECAMedModule;
import lu.tudor.santec.gecamed.core.gui.MainFrame;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.HospitalPrescriptionPage;
import lu.tudor.santec.gecamed.usermanagement.gui.settings.UserSettingsPlugin;
import lu.tudor.santec.i18n.Translatrix;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import com.l2fprod.common.swing.JButtonBar;
import com.sun.media.jai.codec.ByteArraySeekableStream;
import com.sun.media.jai.codec.ImageCodec;
import com.sun.media.jai.codec.ImageDecoder;
import com.sun.media.jai.codec.TIFFDecodeParam;


/**
 * Dialog to view TIFF images (multislice capable)
 * The dialog is used to view scanned prescriptions.
 * 
 * @author Johannes Hermen johannes.hermen(at)tudor.lu
 *
 * @Version
 * <br>$Log: TIFFViewer.java,v $
 * <br>Revision 1.15  2013-12-27 18:07:51  donak
 * <br>Cleanup of imports
 * <br>
 * <br>Revision 1.14  2013-07-15 06:18:34  ferring
 * <br>logging changed
 * <br>
 * <br>Revision 1.13  2009-06-04 14:42:23  mack
 * <br>Attempt to fixe a sealing issue with java 6 update 6
 * <br>
 * <br>Revision 1.12  2008-09-25 09:43:06  heinemann
 * <br>fixed copyrights
 * <br>
 * <br>Revision 1.11  2008-01-15 09:29:38  hermen
 * <br>updated Javadoc and refactured code
 * <br>
 * <br>Revision 1.10  2007-12-17 14:59:59  heinemann
 * <br>plugins and file store and open
 * <br>
 * <br>Revision 1.9  2007-12-06 14:46:44  hermen
 * <br>updated Javadoc and refactured code
 * <br>
 *
 */
public class TIFFViewer extends JDialog implements ActionListener, Printable {

	private static Logger logger = Logger.getLogger(TIFFViewer.class.getName());
	
	private static final long serialVersionUID = 1L;
	private static double c_PageAspectRatio = 1.4142;

	private BufferedImage image;
	private Vector<BufferedImage> imageVector = new Vector<BufferedImage>();
	private ImageIcon thumb;
	
	private JToolBar toolBar;
	private JScrollPane jsp;
	private JLabel imageLabel;
	private JLabel zoomLabel;
	private ButtonGroup group= new ButtonGroup();
	private JButtonBar pageBar;
	private JButton zoomOutButton;
	private JButton zoomInButton;
	private JButton printButton;
	private JButton printCurrentButton;
	private JButton zoom1Button;
	private JButton zoomFPButton;

	private NumberFormat nf = NumberFormat.getInstance();
	private int currentPage = 0;
	private double zoom = 0;
	private boolean printCurrent;

	private TIFFDecodeParam decodeParam;
	
	/**
	 * Viewer Component for TIFF images 
	 * @param parent the parent compoent 
	 * @param title the window Title
	 */
	public TIFFViewer(JFrame parent, String title) {
		super(parent, title);
		this.setModal(true);
		this.setLayout(new BorderLayout());
		this.getContentPane().setBackground(Color.WHITE);

		toolBar = new JToolBar();
		toolBar.setOpaque(false);
		
		printCurrentButton = new JButton(
				GECAMedModule.getMediumIcon(GECAMedIconNames.PRINT_1));
		printCurrentButton.setToolTipText(Translatrix.getTranslationString("core.print1"));
		printCurrentButton.setOpaque(false);
		printCurrentButton.addActionListener(this);
		toolBar.add(printCurrentButton);
		
		printButton = new JButton(
				GECAMedModule.getMediumIcon(GECAMedIconNames.PRINT_ALL));
		printButton.setToolTipText(Translatrix.getTranslationString("core.printAll"));
		printButton.setOpaque(false);
		printButton.addActionListener(this);
		toolBar.add(printButton);
		
		toolBar.addSeparator();
		
		zoomOutButton = new JButton(
				GECAMedModule.getMediumIcon(GECAMedIconNames.ZOOMOUT));
		zoomOutButton.setOpaque(false);
		zoomOutButton.addActionListener(this);
		toolBar.add(zoomOutButton);

		zoomInButton = new JButton(
				GECAMedModule.getMediumIcon(GECAMedIconNames.ZOOMIN));
		zoomInButton.setOpaque(false);
		zoomInButton.addActionListener(this);
		toolBar.add(zoomInButton);

		zoom1Button = new JButton(
				GECAMedModule.getMediumIcon(GECAMedIconNames.ZOOM1_1));
		zoom1Button.setOpaque(false);
		zoom1Button.addActionListener(this);
		toolBar.add(zoom1Button);
		
		zoomFPButton = new JButton(
				GECAMedModule.getMediumIcon(GECAMedIconNames.ZOOMMAX));
		zoomFPButton.setOpaque(false);
		zoomFPButton.addActionListener(this);
		toolBar.add(zoomFPButton);
		
		toolBar.addSeparator();
		
		zoomLabel = new JLabel();
		toolBar.add(zoomLabel);
		
		this.add(toolBar, BorderLayout.NORTH);
		
		imageLabel = new JLabel();
		imageLabel.setHorizontalAlignment(JLabel.CENTER);
		imageLabel.setVerticalAlignment(JLabel.CENTER);
		imageLabel.setBackground(Color.WHITE);
		jsp = new JScrollPane(imageLabel);
		jsp.setOpaque(false);
		jsp.getViewport().setOpaque(false);
		jsp.getHorizontalScrollBar().setUnitIncrement(20);
		jsp.getVerticalScrollBar().setUnitIncrement(20); 
		this.add(jsp, BorderLayout.CENTER);
		
		this.pageBar = new JButtonBar(JButtonBar.VERTICAL);
		this.pageBar.setBackground(GECAMedColors.c_GECAMedBackground);
		this.add(pageBar, BorderLayout.EAST);
		
		Dimension l_ScreenSize = Toolkit.getDefaultToolkit().getScreenSize();
		int l_Height = l_ScreenSize.height - 80;
		int l_Width = (int) (l_Height / c_PageAspectRatio) + pageBar.getWidth();

		setLocation(5, 5);
		this.setSize(l_Width, l_Height);
		this.setPreferredSize(new Dimension(l_Width, l_Height));
		
		this.decodeParam = new TIFFDecodeParam();
        this.decodeParam.setDecodePaletteAsShorts(true);
		
		this.pack();
	}
	
	/**
	 * reads a File into a byte[] (for testing purpose)
	 * @param file
	 * @return
	 * @throws IOException
	 */
	public static byte[] read(String file) throws IOException {
		   InputStream in = new FileInputStream(file);
		   byte[] data = new byte[in.available()];
		   in.read(data);
		   return data;
	}
	
	/**
	 * shows the TIFF image/s provided in the byte[]
	 * @param img
	 */
	public void showTIFFImage(byte[] img) {	
        pageBar.removeAll();
        group = new ButtonGroup();
        imageVector.removeAllElements();
		
        Vector<BufferedImage> v = getAsBufferedImages(img);
        for (Iterator iter = v.iterator(); iter.hasNext();) {
			BufferedImage image = (BufferedImage) iter.next();
			imageVector.add(image);
			
			if (zoom == 0) {
				int width = image.getWidth();       
				int labelwidth = jsp.getWidth();
				zoom =((0.0+ labelwidth) / width) - 0.02;
			}
			thumb = new ImageIcon(image.getScaledInstance(-1, 64, Image.SCALE_SMOOTH));
			JToggleButton b = new JToggleButton("1", thumb, true);
			b.setVerticalTextPosition(SwingConstants.CENTER);
			b.setHorizontalTextPosition(SwingConstants.LEFT);
			b.setActionCommand(1+"");
			b.addActionListener(this);
			pageBar.add(b);
			group.add(b);		
		}
        currentPage = 1;
        zoomImage();
        setVisible(true);
	}
	
	/**
	 * @param img
	 */
	public void showImage(byte[] img) {
		/* ================================================== */
		long time = System.currentTimeMillis();
		
		this.setSize(1100,1000);
		this.setPreferredSize(new Dimension(1100, 1000));
		
		this.pack();
		
		
		
		 pageBar.removeAll();
	        group = new ButtonGroup();
	        imageVector.removeAllElements();
			System.out.println("time bevor bi: " + (System.currentTimeMillis()-time));
			time = System.currentTimeMillis();
	        image = toBufferedImage(img);
	        System.out.println("time after bi: " + (System.currentTimeMillis()-time));
	        time = System.currentTimeMillis();
			imageVector.add(image);
			
//			if (zoom == 0) {
				int width = image.getWidth();       
				int labelwidth = jsp.getWidth();
				zoom =((0.0+ labelwidth) / width) - 0.02;
//			}
				System.out.println("time before thumb: " + (System.currentTimeMillis()-time));
		        time = System.currentTimeMillis();
//			thumb = new ImageIcon(image.getScaledInstance(-1, 64, Image.SCALE_FAST));
//			System.out.println("time after thumb: " + (System.currentTimeMillis()-time));
//	        time = System.currentTimeMillis();
//			JToggleButton b = new JToggleButton("1", thumb, true);
//			b.setVerticalTextPosition(SwingConstants.CENTER);
//			b.setHorizontalTextPosition(SwingConstants.LEFT);
//			b.setActionCommand(1+"");
//			b.addActionListener(this);
//			pageBar.add(b);
//			group.add(b);		

	        currentPage = 1;
	        System.out.println("time before zoom: " + (System.currentTimeMillis()-time));
	        time = System.currentTimeMillis();
	        zoomImage();
	        System.out.println("time after zoom: " + (System.currentTimeMillis()-time));
	        
	        
	        
	        setVisible(true);
		/* ================================================== */
	}
	
	
	/**
	 * shows the TIFF images provided in the several byte[]'s
	 * @param images
	 */
	public void showTIFFImages(Collection<byte[]> images) {	
        pageBar.removeAll();
        group = new ButtonGroup();
        imageVector.removeAllElements();
		
        int page = 1;
		for (Iterator iter = images.iterator(); iter.hasNext();) {
			byte[] img = (byte[]) iter.next();
			Vector<BufferedImage> tmpImages = getAsBufferedImages(img);
	        for (Iterator iterator = tmpImages.iterator(); iterator.hasNext();) {
				BufferedImage tmpImage = (BufferedImage) iterator.next();
				imageVector.add(tmpImage);
				thumb = new ImageIcon(tmpImage.getScaledInstance(-1, 64, Image.SCALE_SMOOTH));
				JToggleButton b = new JToggleButton(page + "", thumb, true);
				b.setVerticalTextPosition(SwingConstants.CENTER);
				b.setHorizontalTextPosition(SwingConstants.LEFT);
				b.setActionCommand(page+"");
				b.addActionListener(this);
				pageBar.add(b);
				group.add(b);
				if (page == 1 ) {
					this.image = tmpImage;
					b.setSelected(true);
				}
				page++;			
			}
		}
        currentPage = 1;
        if (zoom == 0) {
	        int width = image.getWidth();       
	        int labelwidth = jsp.getWidth();
	        zoom =((0.0+ labelwidth) / width) - 0.02;
        }
        zoomImage();
        setVisible(true);
	}
	
	/**
	 * zooms the viewed image to the zoom factor
	 */
	private void zoomImage() {
		setWaitCursor(true);
		zoomLabel.setText("zoom: " + nf.format(zoom));
		int newWidth = ((int) (image.getWidth() * zoom));
		int newHeight = ((int) (image.getHeight() * zoom));
        imageLabel.setIcon(new ImageIcon(image.getScaledInstance(newWidth, newHeight, Image.SCALE_FAST)));
        setWaitCursor(false);
	}
	
	/**
	 * Just for testing
	 * @param args
	 */
	public static void main(String[] args) {
		TIFFViewer tv = new TIFFViewer(new JFrame(), "Tiff test");
		try {
			Vector<byte[]> v = new Vector<byte[]>();
//			v.add(read("/home/heine/download/lagoedit1.jpg"));
//			v.add(read(""));
//			tv.showTIFFImages(v);
			tv.showImage(read("/home/heine/download/lagoedit1.jpg"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/* (non-Javadoc)
	 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
	 */
	public void actionPerformed(ActionEvent ev) {
		if (ev.getSource().equals(this.zoomOutButton)) {
			zoom -= 0.1;
	        int width = image.getWidth();       
	        int labelwidth = jsp.getWidth();
	        double minZoom =((0.0+ labelwidth) / width) - 0.02;
	        if (zoom < minZoom) {
	        	zoom = minZoom;
	        }
		} else if (ev.getSource().equals(this.zoomInButton)) {
			zoom += 0.1;
			if (zoom > 1) {
				zoom = 1;
			}
		} else if (ev.getSource().equals(this.zoom1Button)) {
			zoom = 1;
		} else if (ev.getSource().equals(this.zoomFPButton)) {
	        int width = image.getWidth();       
	        int labelwidth = jsp.getWidth();
	        zoom =((0.0+ labelwidth) / width) - 0.02;
		} else if (ev.getSource().equals(this.printButton)) {
			print();
		} else if (ev.getSource().equals(this.printCurrentButton)) {
			printCurrent = true;
			print();
			printCurrent = false;
		}else {
			try {
				currentPage = Integer.parseInt(ev.getActionCommand());
				this.image = imageVector.get(currentPage-1);				
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		zoomImage();
	}
	
	/**
	 * sets the Mousecursor of the MainFrame to a WaitCursor and Back
	 *
	 * @param on true=waitcursor false=normalcursor
	 */
	public void setWaitCursor(boolean on) {
		if (on) {
			this.setGlassPane(new JLabel());
			this.getGlassPane().setVisible(true);
			this.getGlassPane().setCursor(
					Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
		} else {
			this.getGlassPane().setCursor(
					Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
			this.getGlassPane().setVisible(false);
		}
	}
	
	/**
	 * prints the shown image/s
	 */
	void print() {
		PrinterJob printJob = PrinterJob.getPrinterJob();
	    printJob.setPrintable(this);
	    if (showPrinterDialog()) {
		    if (printJob.printDialog())
		      try {
		        printJob.print();
		      } catch(PrinterException pe) {
		        logger.log(Level.WARN, "Error printing: ", pe);
		      }
	    } else {
	    	try {
		        printJob.print();
		      } catch(PrinterException pe) {
		    	  logger.log(Level.WARN, "Error printing: ", pe);
		      }
	    }
	}

	/* (non-Javadoc)
	 * @see java.awt.print.Printable#print(java.awt.Graphics, java.awt.print.PageFormat, int)
	 */
	public int print(Graphics g, PageFormat pageFormat, int pageIndex) throws PrinterException {
		// print current page only
		if (printCurrent) {
			if (pageIndex > 0) {
			      return(NO_SUCH_PAGE);
			} else {
			    	BufferedImage tmp = imageVector.get(currentPage-1);
			    	Image printimage = tmp.getScaledInstance(tmp.getWidth()/2, tmp.getHeight()/2, Image.SCALE_SMOOTH);
			    	g.drawImage(
			    			printimage, 
			    			(int)pageFormat.getImageableX()-10, 
			    			(int)pageFormat.getImageableY()-10,  
			    			(int)pageFormat.getImageableWidth()+10, 
			    			(int)pageFormat.getImageableHeight()+10, 
			    			this);	
			      return(PAGE_EXISTS);
			}
		}
		//	print all pages 
		if (pageIndex > imageVector.size()-1) {
		      return(NO_SUCH_PAGE);
		} else {
		    	BufferedImage tmp = imageVector.get(pageIndex);
		    	Image printimage = tmp.getScaledInstance(tmp.getWidth()/2, tmp.getHeight()/2, Image.SCALE_SMOOTH);
		    	g.drawImage(
		    			printimage, 
		    			(int)pageFormat.getImageableX()-10, 
		    			(int)pageFormat.getImageableY()-10,  
		    			(int)pageFormat.getImageableWidth()+10, 
		    			(int)pageFormat.getImageableHeight()+10, 
		    			this);	
		      return(PAGE_EXISTS);
		}
	}

	/**
	 * @return
	 */
	private boolean showPrinterDialog() {
		try {
			return (Boolean)MainFrame.getInstance().userSettings.getValue(UserSettingsPlugin.PRINTER_DIALOG_ENABLED);			
		} catch (Exception e) {
		}
		return true;
	}

	/**
	 * shows the TIFF image/s provided in the HospitalPrescriptionPages
	 * @param pages
	 */
	public void showTIFFImage(Collection<HospitalPrescriptionPage> pages) {
		Iterator <HospitalPrescriptionPage> pageIterator;
		HospitalPrescriptionPage			page;
		Collection <byte[]>					content;
		if (pages == null) return;
		content = new ArrayList <byte[]> ();
		pageIterator = pages.iterator();
		while (pageIterator.hasNext())
			{
			page = pageIterator.next();
			content.add(page.getContent());
			}
		this.showTIFFImages(content);
	}
	
	
	/**
	 * converts the TIFF's from the byte[] to buffered images
	 * @param bArr
	 * @return
	 */
	private Vector<BufferedImage> getAsBufferedImages(byte[] bArr) {
		Vector<BufferedImage> v = new Vector<BufferedImage>();
		ByteArraySeekableStream stream = null;
        try {
            stream = new ByteArraySeekableStream(bArr);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
        	BufferedImage bi = null;

        	ImageDecoder dec = ImageCodec.createImageDecoder("tiff", stream, decodeParam);
        	for (int i = 0; i < dec.getNumPages(); i++) {
        		RenderedImageAdapter ria = new RenderedImageAdapter(dec.decodeAsRenderedImage(i));
        		bi = ria.getAsBufferedImage();
        		v.add(bi);
			}
        } catch (IOException e) {
        	logger.log(Level.WARN, "Error converting TIFFs", e);
		}
		return v;
	}
	
	
	
	
	
	
	
	
    /**
     * This method returns a buffered image with the contents of an image
     * 
     * @param image
     * @return
     */
    private static BufferedImage toBufferedImage(byte[] bytes) {
//        if (image instanceof BufferedImage) {
//            return (BufferedImage)image;
//        }
    
        // This code ensures that all the pixels in the image are loaded
        Image image = new ImageIcon(bytes).getImage();
    
        // Determine if the image has transparent pixels; for this method's
        // implementation, see e661 Determining If an Image Has Transparent Pixels
        boolean hasAlpha = hasAlpha(image);
    
        // Create a buffered image with a format that's compatible with the screen
        BufferedImage bimage = null;
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        try {
            // Determine the type of transparency of the new buffered image
            int transparency = Transparency.OPAQUE;
            if (hasAlpha) {
                transparency = Transparency.BITMASK;
            }
    
            // Create the buffered image
            GraphicsDevice gs = ge.getDefaultScreenDevice();
            GraphicsConfiguration gc = gs.getDefaultConfiguration();
            bimage = gc.createCompatibleImage(
                image.getWidth(null), image.getHeight(null), transparency);
        } catch (HeadlessException e) {
            // The system does not have a screen
        }
    
        if (bimage == null) {
            // Create a buffered image using the default color model
            int type = BufferedImage.TYPE_INT_RGB;
            if (hasAlpha) {
                type = BufferedImage.TYPE_INT_ARGB;
            }
            bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
        }
    
        // Copy image to buffered image
        Graphics g = bimage.createGraphics();
    
        // Paint the image onto the buffered image
        g.drawImage(image, 0, 0, null);
        g.dispose();
    
        return bimage;
    }
	
	
    /**
  	 * This method returns true if the specified image has transparent pixels
     * @param image
     * @return
     */
    private static boolean hasAlpha(Image image) {
        // If buffered image, the color model is readily available
        if (image instanceof BufferedImage) {
            BufferedImage bimage = (BufferedImage)image;
            return bimage.getColorModel().hasAlpha();
        }
    
        // Use a pixel grabber to retrieve the image's color model;
        // grabbing a single pixel is usually sufficient
         PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
        try {
            pg.grabPixels();
        } catch (InterruptedException e) {
        }
    
        // Get the image's color model
        ColorModel cm = pg.getColorModel();
        return cm.hasAlpha();
    }

	
	
	
}
