package com.gradesscope.photoeditor;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.*;

/**
 * Displays a picture and allows exploration of the picture by displaying
 * 	the x, y, red, green, and blue values of the pixel at the cursor when
 * 	a mouse button is clicked or a mouse button is pressed and held while
 * 	moving the cursor. The x and y values of a pixel can also be entered
 * 	to see the color at that pixel.
 * 
 * @author Keith McDermott (gte047w@cc.gatech.edu) and
 * 	Barb Ericson (ericson@cc.gatech.edu)
 * 	(Copyright Georgia Institute of Technology 2004)
 * @author Modified by Colleen Lewis (lewis@cs.hmc.edu),
 * 	Jonathan Kotker (jo_ko_berkeley@berkeley.edu),
 * 	Kaushik Iyer (kiyer@berkeley.edu), George Wang (georgewang@berkeley.edu),
 * 	and David Zeng (davidzeng@berkeley.edu).

 */
public class PictureExplorer implements MouseMotionListener, ActionListener, MouseListener
{

	// Current x- and y-index.
	private int xIndex = 0;
	private int yIndex = 0;

	// Main GUI variables.
	private JFrame pictureFrame;
	private JScrollPane scrollPane;

	// Information bar variables.
	private JLabel xLabel;
	private JLabel yLabel;
	private JButton xPrevButton;
	private JButton yPrevButton;
	private JButton xNextButton;
	private JButton yNextButton;
	private JTextField xValue;
	private JTextField yValue;
	private JLabel rLabel;
	private JLabel rValue;
	private JLabel gLabel;
	private JLabel gValue;
	private JLabel bLabel;
	private JLabel bValue;
	private JLabel colorLabel;
	private JPanel colorPanel;

	// Menu components.
	private JMenuBar menuBar;

	// File menu.
	private JMenu fileMenu;
	private JMenuItem openMenuItem;
	private JMenuItem resetMenuItem;
	private JMenuItem saveMenuItem;
	private JMenuItem saveAsMenuItem;
	private JMenuItem exitMenuItem;

	// Strings used in file menu items.
	private static final String STR_FILE = "File";
	private static final String STR_OPEN = "Open...";
	private static final String STR_RESET = "Reset Picture";
	private static final String STR_SAVE = "Save";
	private static final String STR_SAVEAS = "Save As...";
	private static final String STR_EXIT = "Exit";

	// Zoom menu.
	private JMenu zoomMenu;
	private JMenuItem twentyFiveMenuItem;
	private JMenuItem fiftyMenuItem;
	private JMenuItem seventyFiveMenuItem;
	private JMenuItem hundredMenuItem;
	private JMenuItem hundredFiftyMenuItem;
	private JMenuItem twoHundredMenuItem;
	private JMenuItem fiveHundredMenuItem;
	private JMenuItem fiveThousandMenuItem;

	// Strings used in zoom menu items.
	private static final String STR_ZOOM = "Zoom";
	private static final String STR_25PERC = "25%";
	private static final String STR_50PERC = "50%";
	private static final String STR_75PERC = "75%";
	private static final String STR_100PERC = "100%";
	private static final String STR_150PERC = "150%";
	private static final String STR_200PERC = "200%";
	private static final String STR_500PERC = "500%";
	private static final String STR_5000PERC = "5000%";

	// Picture effects menu.
	private JMenu pictureEffectsMenu;
	private JMenuItem chromakeyMenuItem;
	private JMenuItem blurMenuItem;
	private JMenuItem showEdgesMenuItem;
	private JMenuItem paintBucketMenuItem;
	
	private static final String STR_PICTURE_EFFECTS = "Picture Effects";
	private static final String STR_CHROMAKEY = "Chromakey on Current Point";
	private static final String STR_BLUR = "Blur Image";
	private static final String STR_SHOW_EDGES = "Show Edges";
	private static final String STR_PAINT_BUCKET = "Paint Bucket on Current Point";
	
	// Picture rotate menu
	private JMenu rotateflipMenu;
	private JMenuItem flipHorzMenuItem;
	private JMenuItem flipVertMenuItem;
	private JMenuItem flipForwardMenuItem;
	private JMenuItem flipBackwardMenuItem;
	private JMenuItem rotateMenuItem;

	private static final String STR_ROTATE_MENU = "Rotate/Flip";
	private static final String STR_ROTATE_RIGHT = "Rotate Image Right";
	private static final String STR_FLIP_HORIZONTAL = "Flip Horizontally";
	private static final String STR_FLIP_VERTICAL = "Flip Vertically";
	private static final String STR_FLIP_FORWARD = "Flip on Forward Slash";
	private static final String STR_FLIP_BACKWARD = "Flip on Backward Slash";
	
	// Picture color change menu 
	private JMenu changeColorMenu;
	private JMenuItem grayscaleMenuItem;
	private JMenuItem negateMenuItem;
	private JMenuItem lightenMenuItem;
	private JMenuItem darkenMenuItem;
	private JMenuItem addRedMenuItem;
	private JMenuItem addGreenMenuItem;
	private JMenuItem addBlueMenuItem;
	
	private static final String STR_COLOR_CHANGE = "Change Colors";
	private static final String STR_GRAYSCALE = "Grayscale";
	private static final String STR_NEGATE 	 = "Photonegative";
	private static final String STR_LIGHTEN  = "Lighten";
	private static final String STR_DARKEN 	 = "Darken";
	private static final String STR_ADD_RED 	 = "Add Red";
	private static final String STR_ADD_GREEN = "Add Green";
	private static final String STR_ADD_BLUE	 = "Add Blue";
	
	// Picture seam carving menu
	private JMenu seamCarvingMenu;
	private JMenuItem luminosityMenuItem;
	private JMenuItem energyMenuItem;
	private JMenuItem showSeamMenuItem;
	private JMenuItem carveMenuItem;
	private JMenuItem carveManyMenuItem;

	private static final String STR_SEAM_CARVING = "Seam Carving";
	private static final String STR_LUMINOSITY  = "Luminosity";
	private static final String STR_ENERGY      = "Energy";
	private static final String STR_SHOW_SEAM    = "Show Seam";
	private static final String STR_CARVE       = "Carve";
	private static final String STR_CARVE_MANY   = "Carve Many";


	// Strings used in the ImageDisplay and in the pixel navigation system.
	private static final String imageDisplayTooltip = "Click a mouse button " +
	"on a pixel to see the pixel information";
	private static final String STR_CLICK_DESC = "Click to go to the ";
	private static final String xNextTooltip = STR_CLICK_DESC + "next x value";
	private static final String xPrevTooltip = STR_CLICK_DESC
			+ "previous x value";
	private static final String yNextTooltip = STR_CLICK_DESC + "next y value";
	private static final String yPrevTooltip = STR_CLICK_DESC
			+ "previous y value";
	private static final String redLabel = "R: ";
	private static final String greenLabel = "G: ";
	private static final String blueLabel = "B: ";
	private static final String cannotShowText = "N/A";

	/** The Picture being explored. */
	private Picture picture;

	
	/** The ImageDisplay. */
	private ImageDisplay imageDisplay;

	/** The zoom factor (amount to zoom). */
	private double zoomFactor;

	/** The number system to use.
	 * 	0 means starting at 0, 1 means starting at 1. */
	private int numberBase = 0;

	/**
	 * Public constructor.
	 * 
	 * @param picture The Picture to explore.
	 */
	public PictureExplorer(Picture picture) {
		this.picture = picture;
		this.zoomFactor = 1;

		// Create the window and set things up.
		this.createWindow();
	}

	/**
	 * Changes the number system to start at base one.
	 */
	public void changeToBaseOne() {
		this.numberBase = 1;
	}

	/**
	 * Sets the title of the frame.
	 * 
	 * @param title The title to use.
	 */
	public void setTitle(String title) {
		this.pictureFrame.setTitle(title);
	}

	/**
	 * Creates and initializes the picture frame.
	 */
	private void createAndInitPictureFrame() {
		this.pictureFrame = new JFrame(); // Create the JFrame.
		this.pictureFrame.setResizable(true);  // Allow the user to resize it.
		this.pictureFrame.getContentPane().
		setLayout(new BorderLayout()); // Use border layout.
		this.pictureFrame.setDefaultCloseOperation
		(JFrame.DISPOSE_ON_CLOSE); // When closed, stop.
		this.pictureFrame.setTitle(picture.getTitle());
		PictureExplorerFocusTraversalPolicy newPolicy = 
			new PictureExplorerFocusTraversalPolicy();
		this.pictureFrame.setFocusTraversalPolicy(newPolicy);
	}
	
	/**
	 * Creates the menu bar, menus, and menu items.
	 */
	private void setUpMenuBar() {
		// Create menu bar.
		this.menuBar = new JMenuBar();
		
		// Create each sub-menu in the menu bar
		this.setUpMenuBar_FileMenu();
		this.setUpMenuBar_ZoomMenu();
		this.setUpMenuBar_PictureEffectsMenu();
		this.setUpMenuBar_FlipMenu();	
		this.setUpMenuBar_ColorChangeMenu();
		this.setUpMenuBar_SeamCarvingMenu();

		// Set the menu bar to this menu.
		this.pictureFrame.setJMenuBar(menuBar);
	}

	/**
	 * Creates the File Menu
	 */
	private void setUpMenuBar_FileMenu(){
		// Add the file menu.
		this.fileMenu = new JMenu(STR_FILE);
		this.openMenuItem = new JMenuItem(STR_OPEN);
		this.resetMenuItem = new JMenuItem(STR_RESET);
		this.saveMenuItem = new JMenuItem(STR_SAVE);
		this.saveAsMenuItem = new JMenuItem(STR_SAVEAS);
		this.exitMenuItem = new JMenuItem(STR_EXIT);
		
		// Add the action listeners for the file menu.
		this.openMenuItem.addActionListener(this);
		this.resetMenuItem.addActionListener(this);
		this.saveMenuItem.addActionListener(this);
		this.saveAsMenuItem.addActionListener(this);
		this.exitMenuItem.addActionListener(this);
		
		// Add the menu items to the file menu.
		this.fileMenu.add(openMenuItem);
		this.fileMenu.add(resetMenuItem);
		this.fileMenu.addSeparator();
		this.fileMenu.add(saveMenuItem);
		this.fileMenu.add(saveAsMenuItem);
		this.fileMenu.addSeparator();
		this.fileMenu.add(exitMenuItem);
		this.menuBar.add(fileMenu);
	}

	/**
	 * Creates the Zoom Menu
	 */
	private void setUpMenuBar_ZoomMenu(){
		// Add the zoom menu.
		this.zoomMenu = new JMenu(STR_ZOOM);
		this.twentyFiveMenuItem = new JMenuItem(STR_25PERC);
		this.fiftyMenuItem = new JMenuItem(STR_50PERC);
		this.seventyFiveMenuItem = new JMenuItem(STR_75PERC);
		this.hundredMenuItem = new JMenuItem(STR_100PERC);
		this.hundredMenuItem.setEnabled(false);
		this.hundredFiftyMenuItem = new JMenuItem(STR_150PERC);
		this.twoHundredMenuItem = new JMenuItem(STR_200PERC);
		this.fiveHundredMenuItem = new JMenuItem(STR_500PERC);
		this.fiveThousandMenuItem = new JMenuItem(STR_5000PERC);

		// Add the action listeners for the zoom menu.
		this.twentyFiveMenuItem.addActionListener(this);
		this.fiftyMenuItem.addActionListener(this);
		this.seventyFiveMenuItem.addActionListener(this);
		this.hundredMenuItem.addActionListener(this);
		this.hundredFiftyMenuItem.addActionListener(this);
		this.twoHundredMenuItem.addActionListener(this);
		this.fiveHundredMenuItem.addActionListener(this);
		this.fiveThousandMenuItem.addActionListener(this);

		// Add the menu items to the zoom menu.
		this.zoomMenu.add(twentyFiveMenuItem);
		this.zoomMenu.add(fiftyMenuItem);
		this.zoomMenu.add(seventyFiveMenuItem);
		this.zoomMenu.add(hundredMenuItem);
		this.zoomMenu.add(hundredFiftyMenuItem);
		this.zoomMenu.add(twoHundredMenuItem);
		this.zoomMenu.add(fiveHundredMenuItem);
		this.zoomMenu.add(fiveThousandMenuItem);
		this.menuBar.add(zoomMenu);

	}
	/**
	 * Creates the Picture Effects Menu
	 */
	private void setUpMenuBar_PictureEffectsMenu(){
		// Add the picture effects menu.
		this.pictureEffectsMenu 	 = new JMenu(STR_PICTURE_EFFECTS);
		this.chromakeyMenuItem 	 = new JMenuItem(STR_CHROMAKEY);
		this.blurMenuItem 		 = new JMenuItem(STR_BLUR);
		this.showEdgesMenuItem 	 = new JMenuItem(STR_SHOW_EDGES);
		this.paintBucketMenuItem  = new JMenuItem(STR_PAINT_BUCKET);

		// Add the action listeners for the picture effects menu.
		this.chromakeyMenuItem.addActionListener(this);
		this.blurMenuItem.addActionListener(this);
		this.showEdgesMenuItem.addActionListener(this);
		this.paintBucketMenuItem.addActionListener(this);

		this.pictureEffectsMenu.add(chromakeyMenuItem);
		this.pictureEffectsMenu.add(blurMenuItem);
		this.pictureEffectsMenu.add(showEdgesMenuItem);
		this.pictureEffectsMenu.add(paintBucketMenuItem);
		this.menuBar.add(pictureEffectsMenu);
	}
	/**
	 * Creates the Flip Menu
	 */
	private void setUpMenuBar_FlipMenu(){
		// Add the rotate flip menu.
		this.rotateflipMenu 		 = new JMenu(STR_ROTATE_MENU);
		this.rotateMenuItem 		 = new JMenuItem(STR_ROTATE_RIGHT);
		this.flipHorzMenuItem 	 = new JMenuItem(STR_FLIP_HORIZONTAL);
		this.flipVertMenuItem 	 = new JMenuItem(STR_FLIP_VERTICAL);
		this.flipForwardMenuItem  = new JMenuItem(STR_FLIP_FORWARD);
		this.flipBackwardMenuItem = new JMenuItem(STR_FLIP_BACKWARD);

		// Add the action listeners for the rotate/flip effects menu.
		this.rotateMenuItem.addActionListener(this);
		this.flipHorzMenuItem.addActionListener(this);
		this.flipVertMenuItem.addActionListener(this);
		this.flipForwardMenuItem.addActionListener(this);
		this.flipBackwardMenuItem.addActionListener(this);

		this.rotateflipMenu.add(rotateMenuItem);
		this.rotateflipMenu.add(flipHorzMenuItem);
		this.rotateflipMenu.add(flipVertMenuItem);
		this.rotateflipMenu.add(flipForwardMenuItem);
		this.rotateflipMenu.add(flipBackwardMenuItem);
		this.menuBar.add(rotateflipMenu);

	}
	/**
	 * Creates the Color Change Menu
	 */
	private void setUpMenuBar_ColorChangeMenu(){
		// Add the color change menu
		this.changeColorMenu 	 = new JMenu(STR_COLOR_CHANGE);
		this.grayscaleMenuItem 	 = new JMenuItem(STR_GRAYSCALE);
		this.negateMenuItem 		 = new JMenuItem(STR_NEGATE);
		this.lightenMenuItem 	 = new JMenuItem(STR_LIGHTEN);
		this.darkenMenuItem 		 = new JMenuItem(STR_DARKEN);
		this.addRedMenuItem 		 = new JMenuItem(STR_ADD_RED);
		this.addGreenMenuItem 	 = new JMenuItem(STR_ADD_GREEN);
		this.addBlueMenuItem 	 = new JMenuItem(STR_ADD_BLUE);

		// Add the action listeners for the color change menu.
		this.grayscaleMenuItem.addActionListener(this);
		this.negateMenuItem.addActionListener(this);
		this.lightenMenuItem.addActionListener(this);
		this.darkenMenuItem.addActionListener(this);
		this.addRedMenuItem.addActionListener(this);
		this.addGreenMenuItem.addActionListener(this);
		this.addBlueMenuItem.addActionListener(this);

		this.changeColorMenu.add(grayscaleMenuItem);
		this.changeColorMenu.add(negateMenuItem);
		this.changeColorMenu.add(lightenMenuItem);
		this.changeColorMenu.add(darkenMenuItem);
		this.changeColorMenu.add(addRedMenuItem);
		this.changeColorMenu.add(addGreenMenuItem);
		this.changeColorMenu.add(addBlueMenuItem);
		this.menuBar.add(changeColorMenu);

	}
	
	/**
	 * Creates the Seam Carving Menu
	 */
	private void setUpMenuBar_SeamCarvingMenu(){
		// Add the seam carving menu
		this.seamCarvingMenu 	 = new JMenu(STR_SEAM_CARVING);
		this.luminosityMenuItem 	 = new JMenuItem(STR_LUMINOSITY);
		this.energyMenuItem 	 	 = new JMenuItem(STR_ENERGY);
		this.showSeamMenuItem 	 = new JMenuItem(STR_SHOW_SEAM);
		this.carveMenuItem 		 = new JMenuItem(STR_CARVE);
		this.carveManyMenuItem 	 = new JMenuItem(STR_CARVE_MANY);		
				
		// Add the action listeners for the seam carving menu.
		this.luminosityMenuItem.addActionListener(this);
		this.energyMenuItem.addActionListener(this);
		this.showSeamMenuItem.addActionListener(this);
		this.carveMenuItem.addActionListener(this);
		this.carveManyMenuItem.addActionListener(this);	

		this.seamCarvingMenu.add(luminosityMenuItem);
		this.seamCarvingMenu.add(energyMenuItem);
		this.seamCarvingMenu.add(showSeamMenuItem);
		this.seamCarvingMenu.add(carveMenuItem);
		this.seamCarvingMenu.add(carveManyMenuItem);
		this.menuBar.add(seamCarvingMenu);
	}
	/**
	 * Creates and initializes the scrolling image.
	 */
	private void createAndInitScrollingImage() {
		if (this.scrollPane != null) {
			this.pictureFrame.getContentPane().remove(this.scrollPane);
		}

		this.scrollPane = new JScrollPane();

		BufferedImage bimg = this.picture.getBufferedImage();
		this.imageDisplay = new ImageDisplay(bimg);
		this.imageDisplay.addMouseMotionListener(this);
		this.imageDisplay.addMouseListener(this);
		this.imageDisplay.setToolTipText(imageDisplayTooltip);
		this.scrollPane.setViewportView(imageDisplay);

		this.pictureFrame.getContentPane().add(this.scrollPane, BorderLayout.CENTER);
		this.pictureFrame.validate();
	}

	/**
	 * Creates the JFrame and sets everything up.
	 */
	private void createWindow() {
		// Create the picture frame and initializes it.
		this.createAndInitPictureFrame();

		// Set up the menu bar.
		this.setUpMenuBar();

		// Create the information panel.
		this.createInfoPanel();

		// Create the scrollpane for the picture.
		this.createAndInitScrollingImage();

		// Show the picture in the frame at the size it needs to be.
		this.pictureFrame.pack();
		this.pictureFrame.setVisible(true);
	}

	/**
	 * Sets up the next and previous buttons for the pixel location
	 * information.
	 */
	private void setUpNextAndPreviousButtons() {
		// Create the image icons for the buttons.
		Icon prevIcon = new ImageIcon(
				PictureExplorer.class.getResource("leftArrow.gif"),
		"previous index");
		Icon nextIcon = new ImageIcon(
				PictureExplorer.class.getResource("rightArrow.gif"),
		"next index");

		// Create the arrow buttons.
		this.xPrevButton = new JButton(prevIcon);
		this.xNextButton = new JButton(nextIcon);
		this.yPrevButton = new JButton(prevIcon);
		this.yNextButton = new JButton(nextIcon);

		// Set the tool tip text.
		this.xNextButton.setToolTipText(xNextTooltip);
		this.xPrevButton.setToolTipText(xPrevTooltip);
		this.yNextButton.setToolTipText(yNextTooltip);
		this.yPrevButton.setToolTipText(yPrevTooltip);

		// Set the sizes of the buttons.
		int prevWidth = prevIcon.getIconWidth() + 2;
		int nextWidth = nextIcon.getIconWidth() + 2;
		int prevHeight = prevIcon.getIconHeight() + 2;
		int nextHeight = nextIcon.getIconHeight() + 2;
		Dimension prevDimension = new Dimension(prevWidth,prevHeight);
		Dimension nextDimension = new Dimension(nextWidth, nextHeight);
		this.xPrevButton.setPreferredSize(prevDimension);
		this.yPrevButton.setPreferredSize(prevDimension);
		this.xNextButton.setPreferredSize(nextDimension);
		this.yNextButton.setPreferredSize(nextDimension);

		// Handle previous x-button press.
		this.xPrevButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				xIndex--;
				if (xIndex < 0)
					xIndex = 0;
				displayPixelInformation(xIndex, yIndex);
			}
		});

		// Handle previous y button press.
		yPrevButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				yIndex--;
				if (yIndex < 0)
					yIndex = 0;
				displayPixelInformation(xIndex, yIndex);
			}
		});

		// Handle next x button press.
		xNextButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				xIndex++;
				if (xIndex >= picture.getWidth())
					xIndex = picture.getWidth() - 1;
				displayPixelInformation(xIndex, yIndex);
			}
		});

		// Handle next y button press.
		yNextButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				yIndex++;
				if (yIndex >= picture.getHeight())
					yIndex = picture.getHeight() - 1;
				displayPixelInformation(xIndex, yIndex);
			}
		});
	}

	/**
	 * Creates the pixel location panel.
	 * 
	 * @param labelFont The font for the labels.
	 * 
	 * @return The location panel.
	 */
	public JPanel createLocationPanel(Font labelFont) {

		// Create a location panel.
		JPanel locationPanel = new JPanel();
		locationPanel.setLayout(new FlowLayout());
		Box hBox = Box.createHorizontalBox();

		// Create the labels.
		this.xLabel = new JLabel("X:");
		this.yLabel = new JLabel("Y:");

		// Create the text fields.
		this.xValue = new JTextField(Integer.toString(xIndex + numberBase), 6);
		this.xValue.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				displayPixelInformation(xValue.getText(), yValue.getText());
			}
		});
		this.yValue = new JTextField(Integer.toString(yIndex + numberBase), 6);
		this.yValue.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				displayPixelInformation(xValue.getText(), yValue.getText());
			}
		});

		// Set up the next and previous buttons.
		this.setUpNextAndPreviousButtons();

		// Set up the font for the labels.
		this.xLabel.setFont(labelFont);
		this.yLabel.setFont(labelFont);
		this.xValue.setFont(labelFont);
		this.yValue.setFont(labelFont);

		// Add the items to the vertical box and the box to the panel.
		hBox.add(Box.createHorizontalGlue());
		hBox.add(xLabel);
		hBox.add(xPrevButton);
		hBox.add(xValue);
		hBox.add(xNextButton);
		hBox.add(Box.createHorizontalStrut(10));
		hBox.add(yLabel);
		hBox.add(yPrevButton);
		hBox.add(yValue);
		hBox.add(yNextButton);
		locationPanel.add(hBox);
		hBox.add(Box.createHorizontalGlue());

		return locationPanel;
	}

	/**
	 * Creates the color information panel.
	 * 
	 * @param labelFont The font to use for labels.
	 * 
	 * @return the color information panel.
	 */
	private JPanel createColorInfoPanel(Font labelFont) {
		// Create a color information panel.
		JPanel colorInfoPanel = new JPanel();
		colorInfoPanel.setLayout(new FlowLayout());

		// Get the pixel at the current x and y.
		Pixel pixel = picture.getPixel(xIndex, yIndex);

		// Create the labels.
		this.rLabel = new JLabel(redLabel);
		this.rValue = new JLabel(Integer.toString(pixel.getRed()));
		this.gLabel = new JLabel(greenLabel);
		this.gValue = new JLabel(Integer.toString(pixel.getGreen()));
		this.bLabel = new JLabel(blueLabel);
		this.bValue = new JLabel(Integer.toString(pixel.getBlue()));

		// Create the sample color panel and label.
		this.colorLabel = new JLabel("Color at location: ");
		this.colorPanel = new JPanel();
		this.colorPanel.setBorder(new LineBorder(Color.black, 1));

		// Set the color sample to the pixel color.
		this.colorPanel.setBackground(pixel.getColor());

		// Set the font.
		this.rLabel.setFont(labelFont);
		this.rValue.setFont(labelFont);
		this.gLabel.setFont(labelFont);
		this.gValue.setFont(labelFont);
		this.bLabel.setFont(labelFont);
		this.bValue.setFont(labelFont);
		this.colorLabel.setFont(labelFont);
		this.colorPanel.setPreferredSize(new Dimension(25, 25));

		// Add items to the color information panel.
		colorInfoPanel.add(rLabel);
		colorInfoPanel.add(rValue);
		colorInfoPanel.add(gLabel);
		colorInfoPanel.add(gValue);
		colorInfoPanel.add(bLabel);
		colorInfoPanel.add(bValue);
		colorInfoPanel.add(colorLabel);
		colorInfoPanel.add(colorPanel);

		return colorInfoPanel; 
	}

	/**
	 * Creates the north JPanel with all the pixel location
	 * and color information.
	 */
	private void createInfoPanel() {
		// Create the info panel and set the layout.
		JPanel infoPanel = new JPanel();
		infoPanel.setLayout(new BorderLayout());

		// Create the font.
		Font largerFont = new Font(infoPanel.getFont().getName(),
				infoPanel.getFont().getStyle(), 14);

		// Create the pixel location panel.
		JPanel locationPanel = createLocationPanel(largerFont);

		// Create the color information panel.
		JPanel colorInfoPanel = createColorInfoPanel(largerFont);

		// Add the panels to the info panel.
		infoPanel.add(BorderLayout.NORTH, locationPanel);
		infoPanel.add(BorderLayout.SOUTH, colorInfoPanel); 

		// Add the info panel.
		pictureFrame.getContentPane().add(BorderLayout.NORTH, infoPanel);
	} 

	/**
	 * Checks that the current position is in the viewing area and if
	 * 	not, scroll to center the current position, if possible.
	 */
	public void checkScroll() {
		// Get the x and y position in pixels.
		int xPos = (int) (xIndex * zoomFactor); 
		int yPos = (int) (yIndex * zoomFactor); 

		// Only do this if the image is larger than normal.
		if (zoomFactor > 1) {

			// Get the rectangle that defines the current view.
			JViewport viewport = scrollPane.getViewport();
			Rectangle rect = viewport.getViewRect();
			int rectWidth = (int) rect.getWidth();
			int rectHeight = (int) rect.getHeight();

			// Get the maximum possible x and y index.
			int maxIndexX = (int) (picture.getWidth() * zoomFactor)
			- rectWidth - 1;
			int maxIndexY = (int) (picture.getHeight() * zoomFactor)
			- rectHeight - 1;

			/* Calculate how to position the current position in the
			 * middle of the viewing area. */
			int viewX = xPos - (int) (rectWidth / 2);
			int viewY = yPos - (int) (rectHeight / 2);

			// Reposition the viewX and viewY if outside allowed values.
			if (viewX < 0) {
				viewX = 0;
			}
			else if (viewX > maxIndexX) {
				viewX = maxIndexX;
			}
			if (viewY < 0) {
				viewY = 0;
			}
			else if (viewY > maxIndexY) {
				viewY = maxIndexY;
			}

			// Move the viewport upper left point.
			viewport.scrollRectToVisible(new Rectangle(viewX,
					viewY, rectWidth, rectHeight));
		}
	}

	/**
	 * Zooms in on the picture by scaling the image.
	 * Caution: It is extremely memory intensive.
	 * 
	 * @param factor The amount to zoom by.
	 */
	public void zoom(double factor) {
		// Save the current zoom factor.
		this.zoomFactor = factor;

		// Calculate the new width and height and get an image that size.
		int width = (int)(this.picture.getWidth() * this.zoomFactor);
		int height = (int)(this.picture.getHeight() * this.zoomFactor);
		BufferedImage bimg = this.picture.getBufferedImage();

		// Set the scroll image icon to the new image.
		this.imageDisplay.setImage(bimg.getScaledInstance(width, height,
				Image.SCALE_DEFAULT));
		this.imageDisplay.setCurrentX((int) (this.xIndex * this.zoomFactor));
		this.imageDisplay.setCurrentY((int) (this.yIndex * this.zoomFactor));
		this.imageDisplay.revalidate();
		checkScroll();  // Check if need to reposition scroll.
	}

	/**
	 * Repaints the image on the scrollpane.
	 */
	public void repaint() {
		this.pictureFrame.repaint();
	}

	///////////////////////////// Event Listeners /////////////////////////////

	/**
	 * Called when the mouse is dragged (button held down and moved).
	 * 
	 * @param e the mouse event
	 */
	public void mouseDragged(MouseEvent e) {
		this.displayPixelInformation(e);
	}

	/**
	 * Checks if the given x and y coordinates
	 * 	are in the picture.
	 * 
	 * @param x The horizontal coordinate.
	 * @param y The vertical coordinate.
	 * 
	 * @return True, if the horizontal and vertical coordinates provided
	 * 	are in the picture; false, otherwise.
	 */
	private boolean isLocationInPicture(int x, int y) {
		boolean result = false; // Default is false.
		if (x >= 0 && x < picture.getWidth() &&
				y >= 0 && y < picture.getHeight()) {
			result = true;
		}
		return result;
	}

	/**
	 * Displays the pixel information from the x and y
	 * 	coordinates provided.
	 * 
	 * @param xString The x value as a String from the user.
	 * @param yString The y value as a String from the user.
	 */
	public void displayPixelInformation(String xString, String yString) {
		int x = -1;
		int y = -1;
		try {
			x = Integer.parseInt(xString);
			x = x - numberBase;
			y = Integer.parseInt(yString);
			y = y - numberBase;
		} catch (Exception ex) {
		}

		if (x >= 0 && y >= 0) {
			displayPixelInformation(x,y);
		}
	}

	/**
	 * Display pixel information for the x and y
	 * 	coordinates provided.
	 * 
	 * @param pictureX The x value in the picture.
	 * @param pictureY the y value in the picture.
	 */
	private void displayPixelInformation(int pictureX, int pictureY) {
		// Check that this x and y is in range.
		if (isLocationInPicture(pictureX, pictureY))
		{
			// Save the current x and y index.
			this.xIndex = pictureX;
			this.yIndex = pictureY;

			// Get the pixel at the x and y provided.
			Pixel pixel = this.picture.getPixel(this.xIndex, this.yIndex);

			// Set the values based on the pixel.
			this.xValue.setText(Integer.toString(this.xIndex + this.numberBase));
			this.yValue.setText(Integer.toString(this.yIndex + this.numberBase));

			int redValue = pixel.getRed();
			int greenValue = pixel.getGreen();
			int blueValue = pixel.getBlue();
			this.rValue.setText(Integer.toString(redValue));
			this.gValue.setText(Integer.toString(greenValue));
			this.bValue.setText(Integer.toString(blueValue));
			this.colorPanel.setBackground(
					new Color(redValue, greenValue, blueValue));

		} else {
			this.clearInformation();
		}

		// Notify the image display of the current x and y.
		this.imageDisplay.setCurrentX((int) (this.xIndex * this.zoomFactor));
		this.imageDisplay.setCurrentY((int) (this.yIndex * this.zoomFactor));
	}

	/**
	 * Displays pixel information based on a mouse event.
	 * 
	 * @param e The mouse event.
	 */
	private void displayPixelInformation(MouseEvent e) {
		// Get the cursor x- and y-coordinate.
		int cursorX = e.getX();
		int cursorY = e.getY();

		// Get the x and y in original (not scaled) image.
		int pictureX = (int) (cursorX / this.zoomFactor + this.numberBase);
		int pictureY = (int) (cursorY / this.zoomFactor + this.numberBase);

		// Display the information for this x and y.
		this.displayPixelInformation(pictureX, pictureY);
	}

	/**
	 * Clears the labels and current color and resets the current index to -1.
	 */
	private void clearInformation() {
		this.xValue.setText(cannotShowText);
		this.yValue.setText(cannotShowText);
		this.rValue.setText(cannotShowText);
		this.gValue.setText(cannotShowText);
		this.bValue.setText(cannotShowText);
		this.colorPanel.setBackground(Color.black);
		this.xIndex = -1;
		this.yIndex = -1;
	}

	/**
	 * Called when the mouse is moved with no buttons down.
	 * 
	 * @param e The mouse event.
	 */
	public void mouseMoved(MouseEvent e) {}

	/**
	 * Called when the mouse is clicked.
	 * 
	 * @param e The mouse event.
	 */
	public void mouseClicked(MouseEvent e) {
		this.displayPixelInformation(e);
	}

	/**
	 * Called when the mouse button is pushed down.
	 * 
	 * @param e The mouse event.
	 */ 
	public void mousePressed(MouseEvent e) {
		this.displayPixelInformation(e);
	}

	/**
	 * Called when the mouse button is released.
	 * 
	 * @param e The mouse event.
	 */
	public void mouseReleased(MouseEvent e) { }

	/**
	 * Called when the component is entered (mouse moves over it).
	 * 
	 * @param e The mouse event.
	 */
	public void mouseEntered(MouseEvent e) {
	}

	/**
	 * Called when the mouse moves over the component.
	 * 
	 * @param e The mouse event
	 */
	public void mouseExited(MouseEvent e) { }

	/**
	 * Enables all zoom menu commands.
	 */
	private void enableZoomItems() {
		this.twentyFiveMenuItem.setEnabled(true);
		this.fiftyMenuItem.setEnabled(true);
		this.seventyFiveMenuItem.setEnabled(true);
		this.hundredMenuItem.setEnabled(true);
		this.hundredFiftyMenuItem.setEnabled(true);
		this.twoHundredMenuItem.setEnabled(true);
		this.fiveHundredMenuItem.setEnabled(true);
		this.fiveThousandMenuItem.setEnabled(true);
	}


	/**
	 * Controls the menu bar.
	 *
	 * @param a the ActionEvent 
	 */
	public void actionPerformed(ActionEvent a) {
		// Actions reset the zoom (so update the zoom menu)
		this.enableZoomItems();
		this.hundredMenuItem.setEnabled(false);

		if (a.getActionCommand().equals(STR_OPEN)) {
			String fileName = FileChooser.pickAFile(FileChooser.OPEN);

			if (fileName != null) {
				try {
					this.picture.loadOrFail(fileName);
					this.pictureFrame.setTitle(this.picture.getTitle());
					this.createAndInitScrollingImage();
				} catch (IOException e) {
					JOptionPane.showMessageDialog(pictureFrame,
						    "Could not open file: " + fileName,
						    "Open Error",
						    JOptionPane.ERROR_MESSAGE);
				}
			}
		}
		if (a.getActionCommand().equals(STR_RESET)) {
			//System.out.println("RESET");
			String fileName = this.picture.getFileName();
			if (fileName != null) {
				//System.out.println("non null");
				try {
					this.picture.loadOrFail(fileName);
					this.createAndInitScrollingImage();
				} catch (IOException e) {
					JOptionPane.showMessageDialog(pictureFrame,
						    "Could not open file: " + fileName,
						    "Open Error",
						    JOptionPane.ERROR_MESSAGE);
				}
			} else {
				System.out.println("There was no filename associated with this Picture.");
			}
		}
		if (a.getActionCommand().equals(STR_SAVE)) {
			this.saveFile();
		}
		
		if (a.getActionCommand().equals(STR_SAVEAS)) {
			String fileName = FileChooser.pickAFile(FileChooser.SAVE);
			
			if (fileName != null) {
				try {
					String extension = this.picture.getExtension();

					// User may have provided a file extension.
					int posDot = fileName.indexOf('.');
					if (posDot >= 0)
						extension = fileName.substring(posDot + 1);
					else
						fileName = fileName + "." + extension;

					File file = new File(fileName);
					
					ImageIO.write(this.picture.getBufferedImage(),
							this.picture.getExtension(), file);
				} catch (IOException e) {
					JOptionPane.showMessageDialog(this.pictureFrame,
						    "Could not save file. If you have added an " +
						    "image extension (such as JPG or BMP), please" +
						    "ensure that the extension is a vaild one.",
						    "Save Error",
						    JOptionPane.ERROR_MESSAGE);
				}
			}
		}

		if (a.getActionCommand().equals(STR_EXIT)) {
			System.exit(0);
		}
		
		if (a.getActionCommand().equals(STR_25PERC)) {
			zoom(.25);
			enableZoomItems();
			twentyFiveMenuItem.setEnabled(false);
		}

		if (a.getActionCommand().equals(STR_50PERC)) {
			zoom(.50);
			enableZoomItems();
			fiftyMenuItem.setEnabled(false);
		}

		if (a.getActionCommand().equals(STR_75PERC)) {
			zoom(.75);
			enableZoomItems();
			seventyFiveMenuItem.setEnabled(false);
		}

		if (a.getActionCommand().equals(STR_100PERC)) {
			zoom(1.0);
			enableZoomItems();
			hundredMenuItem.setEnabled(false);
		}

		if (a.getActionCommand().equals(STR_150PERC)) {
			zoom(1.5);
			enableZoomItems();
			hundredFiftyMenuItem.setEnabled(false);
		}

		if (a.getActionCommand().equals(STR_200PERC)) {
			zoom(2.0);
			enableZoomItems();
			twoHundredMenuItem.setEnabled(false);
		}

		if (a.getActionCommand().equals(STR_500PERC)) {
			zoom(5.0);
			enableZoomItems();
			fiveHundredMenuItem.setEnabled(false);
		}
		if (a.getActionCommand().equals(STR_5000PERC)) {
			zoom(50.0);
			enableZoomItems();
			fiveThousandMenuItem.setEnabled(false);
		}

		if (a.getActionCommand().equals(STR_GRAYSCALE)) {
			picture = new Picture(picture.grayscale());
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_NEGATE)) {
			picture = new Picture(picture.negate());
			createAndInitScrollingImage();
		}

		if (a.getActionCommand().equals(STR_CHROMAKEY)) {
			int threshold =  getParameterValue("the color threshold", 1, 100);			
			picture = new Picture(picture.chromaKey(xIndex, yIndex, new Picture(FileChooser.pickAFile(FileChooser.OPEN)), threshold));
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_ROTATE_RIGHT)) {
			picture = new Picture(picture.rotateRight());
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_BLUR)) {
			int blurThreshold =  getParameterValue("the blur threshold", 1, 5);
			picture = new Picture(picture.blur(blurThreshold));
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_SHOW_EDGES)) {
			int threshold =  getParameterValue("the edge threshold", 1, 100);
			picture = new Picture(picture.showEdges(threshold));
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_FLIP_HORIZONTAL)) {
			picture = picture.flip(Picture.HORIZONTAL);
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_FLIP_VERTICAL)) {
			picture = picture.flip(Picture.VERTICAL);
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_FLIP_FORWARD)) {
			picture = picture.flip(Picture.FORWARD_DIAGONAL);
			// repaint();
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_FLIP_BACKWARD)) {
			picture = picture.flip(Picture.BACKWARD_DIAGONAL);
			// repaint();
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_PAINT_BUCKET)) {
			int threshold =  getParameterValue("the color threshold", 1, 100);
			picture = new Picture(picture.paintBucket(xIndex, yIndex, threshold, new Color(0, 0, 255)));
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_LIGHTEN)) {
			int value =  getParameterValue("the amount to increase all colors ", 1, 255);
			picture = new Picture(picture.lighten(value));
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_DARKEN)) {
			int value =  getParameterValue("the amount to decrease all colors ", 1, 255);
			picture = new Picture(picture.darken(value));
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_ADD_RED)) {
			int value =  getParameterValue("the amount to increase red", 1, 255);
			picture = new Picture(picture.addRed(value));
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_ADD_GREEN)) {
			int value =  getParameterValue("the amount to increase green", 1, 255);
			picture = new Picture(picture.addGreen(value));
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_ADD_BLUE)) {
			int value =  getParameterValue("the amount to increase blue", 1, 255);
			picture = new Picture(picture.addBlue(value));
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_LUMINOSITY)) {
			picture = picture.luminosity();
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_ENERGY)) {
			picture = picture.energy();
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_SHOW_SEAM)) {
			picture = picture.showSeam();
			createAndInitScrollingImage();
		}
		if (a.getActionCommand().equals(STR_CARVE)) {
			picture = picture.carve();
			createAndInitScrollingImage();
		}		
		if (a.getActionCommand().equals(STR_CARVE_MANY)) {
			int value =  getParameterValue("the number of seams to carve", 1, 30);
			picture = new Picture(picture.carveMany(value));
			createAndInitScrollingImage();
		}
	
	}

	/**
	 * Saves the file corresponding to the current picture.
	 */
	private void saveFile() {
		File file = new File(this.picture.getFileName());

		try {
			ImageIO.write(this.picture.getBufferedImage(),
					this.picture.getExtension(), file);
		} catch (IOException e) {
			JOptionPane.showMessageDialog(this.pictureFrame,
				    "Could not save file.",
				    "Save Error",
				    JOptionPane.WARNING_MESSAGE);
		}

	}

	/**
	 * Handle input to functions
	 */
	public int getParameterValue(String parameterName, int min, int max) {
		String inputValue = JOptionPane
				.showInputDialog("Please input a value for " + parameterName
						+ " in the range of " + min + " to " + max);
		try {
			// the String to int conversion happens here
			int i = Integer.parseInt(inputValue.trim());
			if (i > max) {
				JOptionPane.showMessageDialog(null, "Your input " + inputValue
						+ " for " + parameterName
						+ " was too large. The maximum value " + max
						+ " will be used.", "Invalid Input",
						JOptionPane.ERROR_MESSAGE);
				return max;
			} else if (i < min) {
				JOptionPane.showMessageDialog(null, "Your input " + inputValue
						+ " for " + parameterName
						+ " was too small. The minimum value " + min
						+ " will be used.", "Invalid Input",
						JOptionPane.ERROR_MESSAGE);
				return min;
			} else {
				// they provide input in a valid range.
				return i;
			}
		} catch (NumberFormatException nfe) {
			JOptionPane.showMessageDialog(null, "Your input " + inputValue
					+ " for " + parameterName
					+ " was invalid. The minimum value " + min
					+ " will be used.", "Invalid Input",
					JOptionPane.ERROR_MESSAGE);
			return min;
		}

	}
	/**
	 * Class for establishing the focus for the textfields.
	 */
	private class PictureExplorerFocusTraversalPolicy
	extends FocusTraversalPolicy {

		/**
		 * Gets the next component for focus.
		 */
		public Component getComponentAfter(Container focusCycleRoot,
				Component aComponent) {
			if (aComponent.equals(xValue))
				return yValue;
			else 
				return xValue;
		}

		   
		/**
		 * Gets the previous component for focus.
		 */
		public Component getComponentBefore(Container focusCycleRoot,
				Component aComponent) {
			if (aComponent.equals(xValue))
				return yValue;
			else 
				return xValue;
		}

		public Component getDefaultComponent(Container focusCycleRoot) {
			return xValue;
		}

		public Component getLastComponent(Container focusCycleRoot) {
			return yValue;
		}

		public Component getFirstComponent(Container focusCycleRoot) {
			return xValue;
		}
	}

} // End of PictureExplorer class.
