package com.gradesscope.photoeditor;

import javax.imageio.ImageIO;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.*;

/**
 * A class that represents a simple picture. A simple picture may have an
 * 	associated file name and a title. A simple picture has pixels, width, and
 * 	height. A simple picture uses a BufferedImage to hold the pixels. You can
 * 	show a simple picture in a PictureFrame (a JFrame).
 * 
 * @author Barb Ericson (ericson@cc.gatech.edu)
 * 	(Copyright Georgia Institute of Technology 2004)
 */
public class SimplePicture
{

	///////////////////////////////// Fields ///////////////////////////////////

	/**
	 * Name of the project.
	 */
	private static String projectName = " - Project: PiCTURE";

	/**
	 * Filename associated with this SimplePicture.
	 */
	private String fileName;

	/**
	 * Title of this SimplePicture.
	 */
	private String title;
	
	/**
	 * BufferedImage to hold the pixels for this SimplePicture.
	 */
	private BufferedImage bufferedImage;

	/**
	 * PictureFrame used to display the SimplePicture.
	 */
	private PictureFrame pictureFrame;

	/** 
	 * Extension for this file (jpg or bmp).
	 */
	private String extension;

	/////////////////////////////// Constructors //////////////////////////////

	/**
	 * A constructor that takes no arguments. All fields will be null.
	 * 	A no-argument constructor must be given in order for a class to
	 * 	be able to be subclassed. By default, all subclasses will implicitly
	 * 	call this in their parent's no-argument constructor unless a 
	 * 	different call to super() is explicitly made as the first
	 * 	line of code in a constructor.
	 */
	public SimplePicture() {
		this(200, 100);
	}

	/**
	 * A constructor that takes a file name and uses the file to create
	 * 	a picture.
	 * 
	 * @param fileName The file name to use in creating the picture.
	 */
	public SimplePicture(String fileName) {
		// Load the picture into the BufferedImage. 
		load(fileName);
	}

	/**
	 * A constructor that takes the width and height desired for a picture
	 * 	and creates a BufferedImage of that size. This constructor does not
	 * 	show the picture.
	 * 
	 * @param width The desired width.
	 * @param height The desired height.
	 */
	public SimplePicture(int width, int height) {
		bufferedImage = new BufferedImage(width, height,
				BufferedImage.TYPE_INT_RGB);
		title = "No current picture" + projectName;
//		fileName = "None";
		extension = "jpg";
		setAllPixelsToAColor(Color.white);
	}

	/**
	 * A constructor that takes the width and height desired for a picture
	 * 	and creates a BufferedImage of that size. It also takes the color
	 * 	to use for the background of the picture.
	 * 
	 * @param width The desired width.
	 * @param height The desired height.
	 * @param theColor The background color for the picture.
	 */
	public SimplePicture(int width, int height, Color theColor) {
		this(width, height);
		setAllPixelsToAColor(theColor);
	}

	/**
	 * A constructor that takes another SimplePicture to copy information from.
	 * 
	 * @param copyPicture The SimplePicture to copy from.
	 */
	public SimplePicture(SimplePicture copyPicture) {
		if (copyPicture.fileName != null)
		{
			this.fileName = new String(copyPicture.fileName);
			this.extension = copyPicture.extension;
		}

		if (copyPicture.title != null)
			this.title = new String(copyPicture.title);

		if (copyPicture.bufferedImage != null)
		{
			this.bufferedImage = new BufferedImage(copyPicture.getWidth(),
					copyPicture.getHeight(), BufferedImage.TYPE_INT_RGB);
			this.copyPicture(copyPicture);
		}
	}

	//////////////////////////////// Methods //////////////////////////////////

	/**
	 * copy filename from another picture
	 */
	public void copyFilenameFromPicture(SimplePicture SP) {
		this.fileName = SP.fileName;
		this.extension = SP.extension;
	}
	
	/**
	 * @return The extension (jpg or bmp) of this picture.
	 */
	public String getExtension() { return extension; }

	/**
	 * Copies all of the source SimplePicture provided into the
	 * 	current SimplePicture object.
	 *  
	 * @param sourcePicture The SimplePicture object to copy.
	 */
	public void copyPicture(SimplePicture sourcePicture) {
		Pixel sourcePixel = null;
		Pixel targetPixel = null;

		// Loop through the columns
		for (int sourceX = 0, targetX = 0; 
		sourceX < sourcePicture.getWidth() && targetX < this.getWidth();
		sourceX++, targetX++) {
			// Loop through the rows
			for (int sourceY = 0, targetY = 0; 
			sourceY < sourcePicture.getHeight() && 
			targetY < this.getHeight();
			sourceY++, targetY++)
			{
				sourcePixel = sourcePicture.getPixel(sourceX, sourceY);
				targetPixel = this.getPixel(targetX, targetY);
				targetPixel.setColor(sourcePixel.getColor());
			}
		}
		this.copyFilenameFromPicture(sourcePicture);
	}

	/**
	 * Sets the Color in this SimplePicture to the Color provided.
	 * 
	 * @param color The Color to set to.
	 */
	public void setAllPixelsToAColor(Color color) {
		// Loop through all x.
		for (int x = 0; x < this.getWidth(); x++)
		{
			// Loop through all y.
			for (int y = 0; y < this.getHeight(); y++)
			{
				getPixel(x, y).setColor(color);
			}
		}
	}

	/**
	 * @return The BufferedImage in this SimplePicture. 
	 */
	public BufferedImage getBufferedImage() {
		return bufferedImage;
	}

	/**
	 * @return A Graphics object to be used by this SimplePicture for drawing.
	 */
	public Graphics getGraphics() {
		return bufferedImage.getGraphics();
	}

	/**
	 *@return A Graphics2D object for this SimplePicture
	 *	which can be used to do 2D drawing on the picture.
	 */
	public Graphics2D createGraphics() {
		return bufferedImage.createGraphics();
	}

	/**
	 * @return The file name associated with this SimplePicture.
	 */
	public String getFileName() { return fileName; }

	/**
	 * @return The title of this SimplePicture.
	 */
	public String getTitle() { return title; }

	/**
	 * Sets the title for this SimplePicture.
	 * 
	 * @param title The title to use for this SimplePicture.
	 */
	public void setTitle(String title) {
		this.title = title + projectName;
		if (pictureFrame != null)
			pictureFrame.setTitle(title);
	}

	/**
	 * @return The width of this SimplePicture in pixels.
	 */
	public int getWidth() { return bufferedImage.getWidth(); }

	/**
	 * @return The height of this SimplePicture picture in pixels.
	 */
	public int getHeight() { return bufferedImage.getHeight(); }

	/**
	 * @return the PictureFrame associated with this
	 * 	SimplePicture (it may be null).
	 */
	public PictureFrame getPictureFrame() { return pictureFrame; }

	/**
	 * Sets the PictureFrame for this SimplePicture.
	 * 
	 * @param pictureFrame The PictureFrame to use. 
	 */
	public void setPictureFrame(PictureFrame pictureFrame) {
		this.pictureFrame = pictureFrame;
	}

	/**
	 * @return The Image associated with this SimplePicture.
	 */
	public Image getImage() {
		// Return the BufferedImage object because it is an Image.
		return bufferedImage;
	}

	/**
	 * @param x The x-coordinate of the pixel.
	 * @param y The y-coordinate of the pixel.
	 * 
	 * @return The pixel value as an integer, with alpha, red, green,
	 * 	blue, each occupying 8 bits each.
	 */
	public int getBasicPixel(int x, int y) {
		return bufferedImage.getRGB(x, y);
	}

	/** 
	 * Sets the value of a pixel to a color provided by an int.
	 * 
	 * @param x The x-coordinate of the pixel.
	 * @param y The y-coordinate of the pixel.
	 * @param rgb The new RGB value of the pixel (alpha, red, green, blue).
	 */     
	public void setBasicPixel(int x, int y, int rgb) {
		bufferedImage.setRGB(x, y, rgb);
	}

	/**
	 * @param x The x-coordinate of the pixel.
	 * @param y The y-coordinate of the pixel.
	 * 
	 * @return a Pixel object for this location
	 */
	public Pixel getPixel(int x, int y) {
		/* Create the Pixel object for this SimplePicture
		 * and the given x- and y-coordinates. */
		if (x > this.getWidth() || y > this.getHeight()){
			String errorMsg = "\n\nError message provided by CS60 staff :-)" 
					+ "\nYour code attempted to access a pixel outside of the Picture\n"
					+ "The picture has width: " + this.getWidth() 
					+ " and height: " + this.getHeight() 
					+ "\nbut tried to access x=" + x + " and y=" + y 
					+ "\nClick on the links within Picture.java below to see what line the error was on!" 
					+ "\n******* Yo! - Please read the error message above! *******";
					throw new ArrayIndexOutOfBoundsException(errorMsg);
		}
		return new Pixel(this, x, y);
	}

	/**
	 * Loads the BufferedImage associated with this
	 * 	SimplePicture with the Image provided.
	 * 
	 * @param image The Image to use.
	 */
	public void load(Image image) {
		// Get a Graphics context to use to draw on the BufferedImage.
		Graphics2D graphics2d = bufferedImage.createGraphics();

		/* Draw the Image on the BufferedImage
		 * starting at (0,0). */
		graphics2d.drawImage(image, 0, 0, null);

		// Show the new image.
		show();
	}

	/**
	 * Shows this SimplePicture in a PictureFrame.
	 */
	public void show() {
		if (pictureFrame != null) {
			pictureFrame.updateImageAndShowIt();
		} else {
			pictureFrame = new PictureFrame(this);
		}
	}

	/**
	 * Hides this SimplePicture.
	 */
	public void hide() {
		if (pictureFrame != null)
			pictureFrame.setVisible(false);
	}

	/**
	 * Sets the visibility of this SimplePicture.
	 * 
	 * @param flag True, if the picture is to be visible; false, otherwise.
	 */
	public void setVisible(boolean flag) {
		if (flag)
			this.show();
		else 
			this.hide();
	}

	/**
	 * Opens a PictureExplorer on a copy of this SimplePicture.
	 */
	public void explore() {
		new PictureExplorer(new Picture(this));
	}

	/**
	 * Forces this SimplePicture to redraw itself. This is very useful
	 * 	after pixels in the SimplePicture have been changed.
	 */
	public void repaint() {
		// If there is a PictureFrame, tell it to repaint.
		if (pictureFrame != null)
			pictureFrame.repaint();

		// Else, create a new PictureFrame.
		else
			pictureFrame = new PictureFrame(this);
	}

	/**
	 * Loads this SimplePicture from the file name provided.
	 * 
	 * @param fileName The file name to use to load the
	 * 	SimplePicture from.
	 */
	public void loadOrFail(String fileName) throws IOException {
		// Set the file name of this SimplePicture.
		this.fileName = fileName;

		// Set the extension.
		int posDot = fileName.indexOf('.');
		if (posDot >= 0)
			this.extension = fileName.substring(posDot + 1);

		// If the current title is null, use the file name.
		if (title == null)
			title = fileName + projectName;

		File file = new File(this.fileName);

		if (!file.canRead()) 
			throw new IOException(this.fileName +
					" could not be opened. " +
			"Check to see that you can read to the directory.");

		bufferedImage = ImageIO.read(file);
	}

	/**
	 * Writes the contents of this SimplePicture from a file with
	 * the name provided, without throwing errors.
	 * 
	 * @param fileName The name of the file to write the picture from.
	 * 
	 * @return True, if successful; false, otherwise.
	 */
	public boolean load(String fileName) {
		try {
			this.loadOrFail(fileName);
			return true;
		} catch (Exception ex) {
			return false;
		}

	}

	/**
	 * Loads this SimplePicture from the file name provided. This
	 * 	simply calls load(fileName) and is made available for name
	 * 	compatibility.
	 * 
	 * @param fileName The file name to use to load this
	 * 	SimplePicture from.
	 * @return True, if successful; false, otherwise.
	 */
	public boolean loadImage(String fileName) {
		return load(fileName);
	}

	/**
	 * Draws a message as a String on the BufferedImage.
	 *  
	 * @param message The message to draw on the BufferedImage.
	 * @param xPos The x-coordinate of the leftmost point
	 * 	of the String. 
	 * @param yPos The y-coordinate of the top of
	 * 	the String.
	 */
	public void addMessage(String message, int xPos, int yPos) {
		// Get a graphics context to use to draw on the BufferedImage.
		Graphics2D graphics2d = bufferedImage.createGraphics();

		// Set the color to white.
		graphics2d.setPaint(Color.white);

		// Set the font to Helvetica bold style with size 16.
		graphics2d.setFont(new Font("Helvetica", Font.BOLD,16));

		// Draw the message.
		graphics2d.drawString(message, xPos, yPos);

	}

	/**
	 * Draws a String at the given location on the Picture.
	 * 
	 * @param text The text to draw.
	 * @param xPos The leftmost x-coordinate for the text. 
	 * @param yPos The topmost y-coordinate for the text.
	 */
	public void drawString(String text, int xPos, int yPos) {
		addMessage(text, xPos, yPos);
	}

	/**
	 * Creates a new SimplePicture with the height provided. 
	 * The aspect ratio of the width and height will stay the same.
	 * 
	 * @param height The desired height.
	 * 
	 * @return The resulting SimplePicture.
	 */
	public SimplePicture getPictureWithHeight(int height) {
		// Set up the scale transform.
		double yFactor = (double) height / this.getHeight();
		AffineTransform scaleTransform = new AffineTransform();
		scaleTransform.scale(yFactor,yFactor);

		// Create a new SimplePicture object of the right size.
		SimplePicture result =
			new SimplePicture((int) (getWidth() * yFactor),
					(int) (getHeight() * yFactor));

		// Get the Graphics2D object to draw on the result.
		Graphics graphics = result.getGraphics();
		Graphics2D g2 = (Graphics2D) graphics;

		// Draw the current Image onto the result image.
		g2.drawImage(this.getImage(), scaleTransform, null);

		return result;
	}

	/**
	 * Loads a SimplePicture from a file name and show it
	 * 	in a PictureFrame.
	 * 
	 * @param fileName The file name to load the SimplePicture from.
	 * 
	 * @return True, if successful; false, otherwise.
	 */
	public boolean loadPictureAndShowIt(String fileName) {
		boolean result = true;  // The default assumption is that it worked.

		// Try to load.
		result = load(fileName);

		// Show the picture.
		show();

		return result;
	}

	/**
	 * Writes the contents of the SimplePicture to a file with 
	 * the name provided.
	 * 
	 * @param fileName The name of the file to write the Picture to.
	 */
	public void writeOrFail(String fileName) throws IOException {
		String extension = this.extension; // The default is current.

		// Create the file object.
		File file = new File(fileName);
		File fileLoc = file.getParentFile();

		// canWrite is true only when the file exists already! (alexr)
		if (!fileLoc.canWrite()) {
			throw new IOException(fileName +
					" could not be opened. Check to see if you can" +
			" write to the directory.");
		}

		// Get the extension.
		int posDot = fileName.indexOf('.');
		if (posDot >= 0)
			extension = fileName.substring(posDot + 1);

		/* Write the contents of the BufferedImage to the file
		 * as JPEG. */
		ImageIO.write(bufferedImage, extension, file);
	}

	/**
	 * Write the contents of this SimplePicture to a file with 
	 * the passed name without throwing errors.
	 * 
	 * @param fileName The name of the file to write the picture to.
	 * 
	 * @return True, if successful; false, otherwise.
	 */
	public boolean write(String fileName) {
		try {
			this.writeOrFail(fileName);
			return true;
		} catch (Exception ex) {
			System.out.println("There was an error trying to write " +
					fileName);
			return false;
		}
	}

	/**
	 * Returns the coordinates of the enclosing rectangle after this
	 * transformation is applied to the current picture.
	 * 
	 * @return The enclosing rectangle.
	 */
	public Rectangle2D getTransformEnclosingRect(AffineTransform trans) {
		int width = getWidth();
		int height = getHeight();
		double maxX = width - 1;
		double maxY = height - 1;
		double minX, minY;
		Point2D.Double p1 = new Point2D.Double(0, 0);
		Point2D.Double p2 = new Point2D.Double(maxX, 0);
		Point2D.Double p3 = new Point2D.Double(maxX, maxY);
		Point2D.Double p4 = new Point2D.Double(0, maxY);
		Point2D.Double result = new Point2D.Double(0, 0);
		Rectangle2D.Double rect = null;

		// Get the new points and min/max x and y.
		trans.deltaTransform(p1, result);
		minX = result.getX();
		maxX = result.getX();
		minY = result.getY();
		maxY = result.getY();
		trans.deltaTransform(p2, result);
		minX = Math.min(minX, result.getX());
		maxX = Math.max(maxX, result.getX());
		minY = Math.min(minY, result.getY());
		maxY = Math.max(maxY, result.getY());
		trans.deltaTransform(p3, result);
		minX = Math.min(minX, result.getX());
		maxX = Math.max(maxX, result.getX());
		minY = Math.min(minY, result.getY());
		maxY = Math.max(maxY, result.getY());
		trans.deltaTransform(p4, result);
		minX = Math.min(minX, result.getX());
		maxX = Math.max(maxX, result.getX());
		minY = Math.min(minY, result.getY());
		maxY = Math.max(maxY, result.getY());

		// Create the bounding rectangle to return.
		rect = new Rectangle2D.Double(minX, minY, 
				maxX - minX + 1, maxY - minY + 1);
		return rect;
	}

	/**
	 * Returns a String with information about this
	 * 	SimplePicture.
	 * 
	 * @return A String with information about this
	 * 	SimplePicture. 
	 */
	public String toString() {
		String output = "Simple Picture, filename = " + fileName + 
		", height = " + getHeight() + ", width = " + getWidth();
		return output;
	}

} // End of SimplePicture</tt> class.
