package org.jpedal.jbig2.examples.jai;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Iterator;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;

public class JBIG2ReaderPluginTester extends JFrame {
	final static String appTitle = "JBIG2 Reader Plug-in Tester";

	final static int FORMAT_NAME = 0;
	final static int INPUT = 1;
	final static int MIME_TYPE = 2;
	final static int SUFFIX = 3;

	// Most recently read PCX image.

	BufferedImage biImage;

	// Offset in destination image where future decoded pixels will be placed.

	int dstOffX, dstOffY;

	// Image height and width.

	int height, width;

	// Source region definition.

	int srcX, srcY, srcWidth, srcHeight = 1;

	// Subsampling horizontal and vertical periods.

	int xSS = 1, ySS = 1;

	// Application status bar -- holds path and name of most recent PCX file.

	JLabel lblStatus;

	// Current method for getting an image reader.

	int method = FORMAT_NAME;

	// The picture panel displays the contents of the most recently read PCX
	// image.

	PicPanel pp;

	// The scroll pane allows the user to scroll around images that are bigger
	// than the picture panel.

	JScrollPane jsp;

	// Construct the PCXRPT GUI and indirectly start AWT helper threads.

	public JBIG2ReaderPluginTester(String title) {
		// Pass application title to superclass so that it appears on the title
		// bar.

		super(title);

		// Terminate the application when the user clicks the tiny x button on
		// the title bar.

		setDefaultCloseOperation(EXIT_ON_CLOSE);

		// Construct an open file chooser. Initialize the starting directory to
		// the current directory.

		final JFileChooser fcOpen = new JFileChooser();
		fcOpen.setCurrentDirectory(new File(System.getProperty("user.dir")));

		// Construct the application's menu bar.

		JMenuBar mb = new JMenuBar();

		// The only menu to appear on the menu bar is File. The user invokes
		// menu items on this menu to open PCX images, configure the PCX reader
		// plug-in, and terminate the application.

		JMenu menuFile = new JMenu("File");

		// Create and install the open menu item.

		JMenuItem miOpen = new JMenuItem("Open...");

		ActionListener openl;
		openl = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				// Present the "open" file chooser without any file selected. If
				// the user cancels this file chooser, exit this method.

				fcOpen.setSelectedFile(null);
				if (fcOpen.showOpenDialog(JBIG2ReaderPluginTester.this) != JFileChooser.APPROVE_OPTION)
					return;

				// Attempt to read the image from the selected file. If something
				// goes wrong, doOpen() presents an appropriate error message and
				// false returns. Exit this method.

				if (!doOpen(fcOpen.getSelectedFile()))
					return;

				// Provide the user with assorted information.

				lblStatus.setText("Width: " + width + ", Height: " + height + ", File: " + fcOpen.getSelectedFile().getAbsolutePath());

				// Display the new PCX image in the picture panel. The picture
				// panel automatically adjusts its dimensions, causing the
				// scrollpane to determine if scrollbars should be displayed.

				pp.setBufferedImage(biImage);

				// Reset the scroll positions so that the image's upper-left
				// corner is visible.

				jsp.getHorizontalScrollBar().setValue(0);
				jsp.getVerticalScrollBar().setValue(0);
			}
		};
		miOpen.addActionListener(openl);

		menuFile.add(miOpen);

		// Create and install the configure menu item.

		JMenuItem miConfigure = new JMenuItem("Configure...");

		ActionListener cfgl;
		cfgl = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				CfgDialog cfgdlg = new CfgDialog(JBIG2ReaderPluginTester.this, dstOffX, dstOffY, method, srcX, srcY, srcWidth, srcHeight, xSS, ySS);
				cfgdlg.setVisible(true);

				if (cfgdlg.isCanceled())
					return;

				dstOffX = cfgdlg.getDstOffX();
				dstOffY = cfgdlg.getDstOffY();

				method = cfgdlg.getMethod();

				srcX = cfgdlg.getSrcX();
				srcY = cfgdlg.getSrcY();
				srcWidth = cfgdlg.getSrcWidth();
				srcHeight = cfgdlg.getSrcHeight();

				xSS = cfgdlg.getXSS();
				ySS = cfgdlg.getYSS();
			}
		};
		miConfigure.addActionListener(cfgl);

		menuFile.add(miConfigure);

		menuFile.addSeparator();

		// Create and install the exit menu item.

		JMenuItem miExit = new JMenuItem("Exit");

		ActionListener exitl;
		exitl = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		};
		miExit.addActionListener(exitl);

		menuFile.add(miExit);

		// Add the file menu to the menu bar.

		mb.add(menuFile);

		// Install the menu bar.

		setJMenuBar(mb);

		// Create an initial picture panel that does not display a picture, but
		// has a default size.

		pp = new PicPanel(null);

		// Indirectly add the picture panel, by way of a scrollpane, to the
		// application's contentpane.

		getContentPane().add(jsp = new JScrollPane(pp));

		// Create a status bar that displays the method used to obtain an image
		// reader, the height and width of the current image, and the path and
		// name of the current image's file. Initialize the status bar text to
		// one space so that it will be displayed at a height corresponding to
		// its current font's size. Surround the status bar with an etched
		// border to visually separate this component from the picture panel.

		lblStatus = new JLabel(" ");
		lblStatus.setBorder(BorderFactory.createEtchedBorder());

		// Add the status bar to the bottom of the application's contentpane.

		getContentPane().add(lblStatus, BorderLayout.SOUTH);

		// Resize all components to their preferred sizes.

		pack();

		// Display GUI and start GUI processing.

		setVisible(true);
	}

	// Open the specified JBIG2 file and read the file's JBIG2 image.

	boolean doOpen(File file) {
		if (!file.exists()) {
			JOptionPane.showMessageDialog(JBIG2ReaderPluginTester.this, "File does not exist!", appTitle, JOptionPane.ERROR_MESSAGE);

			return false;
		}

		try {
			// Validate file extension.

			String path = file.getAbsolutePath().toLowerCase();

			if (!path.endsWith(".jbig2") && !path.endsWith(".jb2")) {
				JOptionPane.showMessageDialog(JBIG2ReaderPluginTester.this, "Incorrect file extension!", appTitle, JOptionPane.ERROR_MESSAGE);

				return false;
			}

			// Obtain an appropriate reader.

			ImageInputStream iis = ImageIO.createImageInputStream(file);

			Iterator iter;
			if (method == FORMAT_NAME)
				iter = ImageIO.getImageReadersByFormatName("jbig2");
			else if (method == MIME_TYPE)
				iter = ImageIO.getImageReadersByMIMEType("image/x-jbig2");
			else if (method == SUFFIX)
				iter = ImageIO.getImageReadersBySuffix("jbig2");
			else
				iter = ImageIO.getImageReaders(iis);

			// Validate existence of reader. A reader will not be returned by
			// getImageReaders() if JBIG2ImageReaderSpi's canDecodeInput() method
			// returns false.

			if (!iter.hasNext()) {
				JOptionPane.showMessageDialog(JBIG2ReaderPluginTester.this, "Unable to obtain reader!", appTitle, JOptionPane.ERROR_MESSAGE);

				return false;
			}

			// Extract reader.

			ImageReader reader = (ImageReader) iter.next();

			// Configure reader's input source.

			reader.setInput(iis, true);

			// Configure reader parameters.

			ImageReadParam irp = reader.getDefaultReadParam();

			if (!(dstOffX == 0 && dstOffY == 0))
				irp.setDestinationOffset(new Point(dstOffX, dstOffY));

			if (srcWidth != 0)
				irp.setSourceRegion(new Rectangle(srcX, srcY, srcWidth, srcHeight));

			if (!(xSS == 1 && ySS == 1))
				irp.setSourceSubsampling(xSS, ySS, 0, 0);

			// Read the image.
			biImage = reader.read(0, irp);

			// Obtain the image's width and height.

			width = reader.getWidth(0);
			height = reader.getHeight(0);

			// Cleanup.

			reader.dispose();

			return true;
		} catch (Exception e) {
			JOptionPane.showMessageDialog(JBIG2ReaderPluginTester.this, e.getMessage(), appTitle, JOptionPane.ERROR_MESSAGE);

			return false;
		}
	}

	// Application entry point.

	public static void main(String[] args) {
		// Create the application's GUI and start the application.

		new JBIG2ReaderPluginTester(appTitle);
	}
}

class CfgDialog extends JDialog {
	private final static int MAX_DSTOFFX = 9999;
	private final static int MAX_DSTOFFY = 9999;

	private final static int MAX_XSS = 9999;
	private final static int MAX_YSS = 9999;

	private final static int MAX_SRCX = 9999;
	private final static int MAX_SRCY = 9999;

	private final static int MAX_SRCWIDTH = 9999;
	private final static int MAX_SRCHEIGHT = 9999;

	private boolean canceled;

	private int dstOffX, dstOffY;

	private int srcHeight, srcWidth, srcX, srcY;

	private int xSS, ySS;

	private int method;

	CfgDialog(JFrame f, int dstOffX, int dstOffY, int method, int srcX, int srcY, int srcWidth, int srcHeight, int xSS, int ySS) {
		// Assign title to dialog box's title bar and ensure dialog box is
		// modal.

		super(f, "Configure", true);

		// Create a main layout panel that divides the GUI into several
		// sections, where each section has the same width and height.

		JPanel pnlLayout = new JPanel();
		pnlLayout.setLayout(new GridLayout(5, 1));

		// Create and install the destination offset section.

		JPanel pnl = new JPanel();
		Border bd = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
		pnl.setBorder(BorderFactory.createTitledBorder(bd, "Destination Offset"));

		pnl.add(new JLabel("X"));
		final JSpinner spnDstOffX = new JSpinner(new SpinnerNumberModel(dstOffX, 0, MAX_DSTOFFX, 1));
		pnl.add(spnDstOffX);

		pnl.add(new JLabel("Y"));
		final JSpinner spnDstOffY = new JSpinner(new SpinnerNumberModel(dstOffY, 0, MAX_DSTOFFY, 1));
		pnl.add(spnDstOffY);

		pnlLayout.add(pnl);

		// Create and install the method section.

		pnl = new JPanel();
		bd = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
		pnl.setBorder(BorderFactory.createTitledBorder(bd, "Method"));

		final JRadioButton rbChoice1 = new JRadioButton("Format name");
		if (method == JBIG2ReaderPluginTester.FORMAT_NAME)
			rbChoice1.setSelected(true);
		pnl.add(rbChoice1);

		final JRadioButton rbChoice2 = new JRadioButton("Input");
		if (method == JBIG2ReaderPluginTester.INPUT)
			rbChoice2.setSelected(true);
		pnl.add(rbChoice2);

		final JRadioButton rbChoice3 = new JRadioButton("MIME type");
		if (method == JBIG2ReaderPluginTester.MIME_TYPE)
			rbChoice3.setSelected(true);
		pnl.add(rbChoice3);

		final JRadioButton rbChoice4 = new JRadioButton("Suffix");
		if (method == JBIG2ReaderPluginTester.SUFFIX)
			rbChoice4.setSelected(true);
		pnl.add(rbChoice4);

		final ButtonGroup bg = new ButtonGroup();
		bg.add(rbChoice1);
		bg.add(rbChoice2);
		bg.add(rbChoice3);
		bg.add(rbChoice4);

		pnlLayout.add(pnl);

		// Create and install the source region section.

		pnl = new JPanel();
		bd = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
		pnl.setBorder(BorderFactory.createTitledBorder(bd, "Source Region"));

		pnl.add(new JLabel("Src X"));
		final JSpinner spnSrcX = new JSpinner(new SpinnerNumberModel(srcX, 0, MAX_SRCX, 1));
		pnl.add(spnSrcX);

		pnl.add(new JLabel("Src Y"));
		final JSpinner spnSrcY = new JSpinner(new SpinnerNumberModel(srcY, 0, MAX_SRCY, 1));
		pnl.add(spnSrcY);

		pnl.add(new JLabel("Src Width"));
		final JSpinner spnSrcWidth = new JSpinner(new SpinnerNumberModel(srcWidth, 0, MAX_SRCWIDTH, 1));
		pnl.add(spnSrcWidth);

		pnl.add(new JLabel("Src Height"));
		final JSpinner spnSrcHeight = new JSpinner(new SpinnerNumberModel(srcHeight, 1, MAX_SRCHEIGHT, 1));
		pnl.add(spnSrcHeight);

		pnlLayout.add(pnl);

		// Create and install the source subsampling section.

		pnl = new JPanel();
		bd = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
		pnl.setBorder(BorderFactory.createTitledBorder(bd, "Source Subsampling"));

		pnl.add(new JLabel("X Subsampling"));
		final JSpinner spnXSS = new JSpinner(new SpinnerNumberModel(xSS, 1, MAX_XSS, 1));
		pnl.add(spnXSS);

		pnl.add(new JLabel("Y Subsampling"));
		final JSpinner spnYSS = new JSpinner(new SpinnerNumberModel(ySS, 1, MAX_YSS, 1));
		pnl.add(spnYSS);

		pnlLayout.add(pnl);

		// Create and install the button section. 

		pnl = new JPanel();
		pnl.setLayout(new FlowLayout(FlowLayout.RIGHT));

		JButton btn = new JButton("OK");
		pnl.add(btn);
		btn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				canceled = false;

				if (rbChoice1.isSelected())
					CfgDialog.this.method = JBIG2ReaderPluginTester.FORMAT_NAME;
				else if (rbChoice2.isSelected())
					CfgDialog.this.method = JBIG2ReaderPluginTester.INPUT;
				else if (rbChoice3.isSelected())
					CfgDialog.this.method = JBIG2ReaderPluginTester.MIME_TYPE;
				else
					CfgDialog.this.method = JBIG2ReaderPluginTester.SUFFIX;

				CfgDialog.this.dstOffX = ((Integer) spnDstOffX.getValue()).intValue();
				CfgDialog.this.dstOffY = ((Integer) spnDstOffY.getValue()).intValue();

				CfgDialog.this.xSS = ((Integer) spnXSS.getValue()).intValue();
				CfgDialog.this.ySS = ((Integer) spnYSS.getValue()).intValue();

				CfgDialog.this.srcX = ((Integer) spnSrcX.getValue()).intValue();
				CfgDialog.this.srcY = ((Integer) spnSrcY.getValue()).intValue();
				CfgDialog.this.srcWidth = ((Integer) spnSrcWidth.getValue()).intValue();
				CfgDialog.this.srcHeight = ((Integer) spnSrcHeight.getValue()).intValue();

				dispose();
			}
		});

		btn = new JButton("Cancel");
		pnl.add(btn);
		btn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				canceled = true;

				dispose();
			}
		});

		pnlLayout.add(pnl);

		// Add main layout panel to content pane.

		getContentPane().add(pnlLayout);

		// Resize dialog box to union of collective preferred sizes of all
		// contained components.

		pack();
	}

	int getDstOffX() {
		return dstOffX;
	}

	int getDstOffY() {
		return dstOffY;
	}

	int getMethod() {
		return method;
	}

	int getSrcHeight() {
		return srcHeight;
	}

	int getSrcWidth() {
		return srcWidth;
	}

	int getSrcX() {
		return srcX;
	}

	int getSrcY() {
		return srcY;
	}

	int getXSS() {
		return xSS;
	}

	int getYSS() {
		return ySS;
	}

	boolean isCanceled() {
		return canceled;
	}
}

class PicPanel extends JPanel {
	// Dimensions of picture panel's preferred size.

	final static int WIDTH = 600;
	final static int HEIGHT = 440;

	// Reference to BufferedImage whose image is displayed in panel. If null
	// reference, nothing is displayed in panel area.

	private BufferedImage bi;

	// Create a picture panel component.

	PicPanel(BufferedImage bi) {
		// Install the buffered image for this panel.

		setBufferedImage(bi);
	}

	// Retrieve this component's preferred size for layout purposes.

	public Dimension getPreferredSize() {
		// When the program starts, there is no installed buffered image so a
		// default preferred size is chosen. After a buffered image has been
		// installed, that buffered image's size is returned as the preferred
		// size.

		if (bi == null)
			return new Dimension(WIDTH, HEIGHT);
		else
			return new Dimension(bi.getWidth(), bi.getHeight());
	}

	// Redraw the picture panel.

	public void paintComponent(Graphics g) {
		// Paint the component's background to prevent artifacts from appearing.

		super.paintComponent(g);

		// If a buffered image has been installed, paint its contents on the
		// panel.

		if (bi != null)
			g.drawImage(bi, 0, 0, this);
	}

	// Install a new buffered image into the picture panel.

	public void setBufferedImage(BufferedImage bi) {
		// Save the buffered image for future painting.

		this.bi = bi;

		// The following method call invalidates this component and then adds
		// this component's validateRoot (the JScrollPane in which the picture
		// panel is contained) to a list of components that need to be validated.
		// Validation results in a call to this component's getPreferredSize()
		// method; this information will be used by the scrollpane to determine
		// if its scrollbars should be shown.

		revalidate();

		// Paint the new image on the panel.

		repaint();
	}
}
