import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;

/**
 * A "Controller" has an associated MosaicPanel and is repsonsible for responding to mouse 
 * events and menu events by taking appropriate actions.
 */
public class Controller implements MouseListener, MouseMotionListener {
	
	private MosaicPanel mosaic;  // The MosaicPanel on which this controller acts.

	int randomness = 71;         // Current amount of randomness, corresponding to "Moderate Randomness".
	
	private int currentRed = 170;    // The red component of the current drawing color.
	private int currentGreen = 240;  // The green component of the current drawing color.
	private int currentBlue = 170;   // the blue component of the current drawing color.
	
	private static final int DRAW_TOOL = 0;  // Indicates that mouse is used to color squares it touches.
	private static final int ERASE_TOOL = 1; // Indicates that the mouse is used to erase squares it touches.
	
	private int tool = DRAW_TOOL;  // Currently selected drawing tool; one of the above constants.

	
	/**
	 * Constructor; creates a Controller object and specifies the MosaicPanel on which the controller acts.
	 * This method also sets up "listening" for mouse events and for mouse motion events on the MosaicPanel.
	 * When such an event occurs, the appropriate method in this class will be called.  (Mouse event
	 * methods are defined at the bottom of this class. 
	 */
	public Controller(MosaicPanel controllee) {
		mosaic = controllee;
		mosaic.addMouseListener(this);
		mosaic.addMouseMotionListener(this);
	}

	/**
	 * This method is called when the user selects a command from one of the menus.
	 * (The menus are actually defined in the class named Menus.)  This method simply
	 * checks the command name and calls another method to actually carry out the command.
	 * @param commandName the text of the command, as shown in the menu.
	 */
	public void doMenuCommand(String commandName) {
		if (commandName.equals("Set Color..."))
			doSetColor();
		else if (commandName.equals("No Randomness"))
			randomness = 0;
		else if (commandName.equals("Some Randomness"))
			randomness = 41;
		else if (commandName.equals("Moderate Randomness"))
			randomness = 71;
		else if (commandName.equals("Much Randomness"))
			randomness = 151;
		else if (commandName.equals("Save PNG Image..."))
			doSaveImage("PNG");
		else if (commandName.equals("Save JPEG Image..."))
			doSaveImage("JPEG");
		else if (commandName.equals("Quit"))
			System.exit(0);
		else if (commandName.equals("Draw"))
			tool = DRAW_TOOL;
		else if (commandName.equals("Erase"))
			tool = ERASE_TOOL;
		else { // in case the command is not among those checked above, a message is displayed to the user.
			JOptionPane.showMessageDialog(mosaic,"   Sorry, the command \"" + commandName 
					+ "\"\nis not yet implemented.");
		}
	}
	
	/**
	 * Lets the user select the current drawing color.  A color choice dialog box is displayed to the user.
	 * This method is called by the doMenuCommmand() method when the user selects "Set Color..." from the menus.
	 */
	private void doSetColor() {
		Color c = new Color(currentRed, currentGreen, currentBlue);
		c = JColorChooser.showDialog(mosaic, "Select Drawing Color", c);
		if (c != null) {  // If c is null, it means that the user canceled the dialog.
			currentRed = c.getRed();
			currentGreen = c.getGreen();
			currentBlue = c.getBlue();
		}
	}
	
	/**
	 * Saves the current mosaic image to a file in PNG or JPEG format.  A file dialog box is displayed to the
	 * user where the user can name the file and select the destination directory.  If the user
	 * selects a file name that already exists, the user is asked whether the existing file should
	 * be replaced.  This method is called by the doMenuCommand() method when the user selects "Save PNG Image"
	 * or "Save JPEG Image" from the menu.  The imageFormat is either "PNG" or "JPEG", depending on which
	 * command was chosen. 
	 */
	private void doSaveImage(String imageFormat) {
		BufferedImage image = mosaic.getImage();  // The image currently displayed in the MosaicPanel.
		JFileChooser fileDialog = new JFileChooser();
		String defaultName = "mosaic." + imageFormat.toLowerCase(); // Default name for file to be saved.
		File selectedFile = new File(defaultName);
		fileDialog.setSelectedFile(selectedFile);
		fileDialog.setDialogTitle("Save Image as " + imageFormat + " File");
		int option = fileDialog.showSaveDialog(mosaic);  // Presents the "Save File" dialog to the user.
		if (option != JFileChooser.APPROVE_OPTION)
			return;  // user canceled
		selectedFile = fileDialog.getSelectedFile();  // The file the user has elected to save.
		if (selectedFile.exists()) {
			int response = JOptionPane.showConfirmDialog(mosaic,
					"The file \"" + selectedFile.getName() + "\" already exists.\nDo you want to replace it?",
					"Replace file?",
					JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
			if (response == JOptionPane.NO_OPTION)
				return; // user does not want to replace existing file
		}
		try {
			if ( ! ImageIO.write(image,imageFormat,selectedFile) )  // This actually writes the image to the file.
				JOptionPane.showMessageDialog(mosaic,"Sorry, it looks like " + imageFormat + " files are not supported!");
		}
		catch (Exception e) {
			JOptionPane.showMessageDialog(mosaic,"Sorry, an error occurred while trying to save the file:\n" + e.getMessage());
		}
	}
	
	/**
	 * Apply the current drawing color to the specified squre in the mosaic.  The RGB components of
	 * the current drawing color are in the member variables currentRed, currentGreen, and currentBlue,
	 * modified by adding some random variation if any degree of randomness has been selected.
	 * This method should always be used to set the color of a square, since it accounts for the
	 * "randomness" that can be applied to the color.
	 * @param row the row number of the square whose color is to be changed
	 * @param col the column number of the square whose color is to be changed.
	 */
	private void applyColor(int row, int col) {
		int r = currentRed;
		int g = currentGreen;
		int b = currentBlue;
		if (randomness > 0) {
			r += (int)(randomness*Math.random()) - randomness/2;
			g += (int)(randomness*Math.random()) - randomness/2;
			b += (int)(randomness*Math.random()) - randomness/2;
		}
		mosaic.setColor(row, col, r, g, b);
	}
	
	//------------------------------------- Implement Mouse Events --------------------------
	
	/**
	 * This method is called when the user first presses a mouse button on the MosaicPanel.
	 */
	public void mousePressed(MouseEvent evt) {
		int x = evt.getX();  // Horizontal coord of the point where mouse was pressed.
		int y = evt.getY();  // Vertical coord of the point where mouse was pressed.
		int row = mosaic.yCoordToRowNumber(y);     // The row in the mosaic in which the mouse was pressed.
		int col = mosaic.xCoordToColumnNumber(x);  // The column in the moasic in which the mouse was pressed.
		switch (tool) {
		case DRAW_TOOL:    // Apply color to the square in which the mouse was pressed.
			applyColor(row,col);
			break;
		case ERASE_TOOL:   // Remove the color from the square, retruning it to the default background color.
			mosaic.setColor(row,col,null);
			break;
		}
	}

	/**
	 * This method is called when the user drags the mouse while holding down the button.  This method
	 * is called even when the mouse is dragged outside the border of the mosaic, so a test is made
	 * to make sure that the row and column numbers are actually inside the mosaic before anyting is
	 * done to the square at that position.
	 */
	public void mouseDragged(MouseEvent evt) {
		int x = evt.getX();  // Horizontal coord of the point where mouse is currently located.
		int y = evt.getY();  // Vertical coord of the point where mouse is currently located.
		int row = mosaic.yCoordToRowNumber(y);     // The row in the mosaic in which the mouse is located.
		int col = mosaic.xCoordToColumnNumber(x);  // The column in the moasic in which the mouse is located.
		switch (tool) {
		case DRAW_TOOL:    // Apply color to the square to which the mouse has been dragged.
			if (row >= 0 && row < mosaic.getRowCount() && col >= 0 && col < mosaic.getColumnCount())
				applyColor(row,col);
			break;
		case ERASE_TOOL:   // Remove the color from the square, retruning it to the default background color.
			if (row >= 0 && row < mosaic.getRowCount() && col >= 0 && col < mosaic.getColumnCount())
				mosaic.setColor(row,col,null);
			break;
		}
	}

	/**
	 * This method is called when the mouse button is released at the end of a mouse drag operation.
	 */
	public void mouseReleased(MouseEvent evt) {
	}

	public void mouseMoved(MouseEvent e) {}	// Required my Mouse interfaces but not used in this program.
	public void mouseClicked(MouseEvent e) {}
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}

	
}
