package com.gradescope.photoeditor;

import java.awt.*;
import java.net.URL;
// The secret image:
/**
 * A class that represents a picture.  This class inherits from SimplePicture
 *      and allows the student to add functionality and picture effects.
 *
 * @author 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).
 * @author Modified by Zach Dodds
 *
 * Login(s):
 *
 * Extra credit completed: (please describe clearly below)
 *
 *
 */
public class Picture extends SimplePicture
{
        /////////////////////////// Static Variables //////////////////////////////

        // Different axes available to flip a picture.
        public static final int HORIZONTAL = 1;
        public static final int VERTICAL = 2;
        public static final int FORWARD_DIAGONAL = 3;
        public static final int BACKWARD_DIAGONAL = 4;


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

        /**
         * A constructor that takes no arguments.
         */
        public Picture () {
                super();
        }

        /**
         * Creates a Picture from the file name provided.
         *
         * @param fileName The name of the file to create the picture from.
         */
        public Picture(String fileName) {
                // Let the parent class handle this fileName.
                super(fileName);
        }

        /**
         * Creates a Picture from the width and height provided.
         *
         * @param width the width of the desired picture.
         * @param height the height of the desired picture.
         */
        public Picture(int width, int height) {
                // Let the parent class handle this width and height.
                super(width, height);
        }

        /**
         * Creates a copy of the Picture provided.
         *
         * @param pictureToCopy Picture to be copied.
         */
        public Picture (Picture pictureToCopy) {
                // Let the parent class do the copying.
                super(pictureToCopy);
        }

        /**
         * Creates a copy of the SimplePicture provided.
         *
         * @param pictureToCopy SimplePicture to be copied.
         */
        public Picture (SimplePicture pictureToCopy) {
                // Let the parent class do the copying.
                super(pictureToCopy);
        }

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

        //////////////////////////// Provided Methods /////////////////////////////////

        /**
         * Helper method to determine if a x and y coordinate is valid (within the image)
         *
         * @param ix is the x value that might be outside of the image
         * @param iy is the y value that might be outside of the image
         * @return true if the x and y values are within the image and false otherwise
         */
        @SuppressWarnings("unused")
        private boolean inImage(int ix, int iy) {
                return ix >= 0 && ix < this.getWidth() && iy >= 0
                                && iy < this.getHeight();
        }

        /**
         * @return A string with information about the picture, such as
         *      filename, height, and width.
         */
        public String toString() {
                String output = "Picture, filename = " + this.getFileName() + "," +
                " height = " + this.getHeight() + ", width = " + this.getWidth();
                return output;
        }
        /**
         * Equals method for two Picture objects.
         *
         * @param obj is an Object to compare to the current Picture object
         * @return true if obj is a Picture object with the same size as the
         *         original and with the same color at each Pixel
         */
        public boolean equals(Object obj) {
                if (!(obj instanceof Picture)) {
                        return false;
                }

                Picture p = (Picture) obj;
                // Check that the two pictures have the same dimensions.
                if ((p.getWidth() != this.getWidth()) ||
                                (p.getHeight() != this.getHeight())) {
                        return false;
                }

                // Check each pixel.
                for (int x = 0; x < this.getWidth(); x++) {
                        for(int y = 0; y < this.getHeight(); y++) {
                                if (!this.getPixel(x, y).equals(p.getPixel(x, y))) {
                                        return false;
                                }
                        }
                }

                return true;
        }

        /**
         * Helper method for loading a picture in the current directory.
         */
        protected static Picture loadPicture(String pictureName) {
                URL url = Picture.class.getResource(pictureName);
                return new Picture(url.getFile().replaceAll("%20", " "));
        }

        //////////////////////////// Debugging Methods /////////////////////////////////

        /**
         * Method to print out a table of the intensity for each Pixel in an image
         */
        public void printLuminosity(){
                int pictureHeight = this.getHeight();
                int pictureWidth = this.getWidth();
                System.out.println("Luminosity:");
                for(int y = 0; y < pictureHeight; y++) {
                        System.out.print("[");
                        for(int x = 0; x < pictureWidth; x++) {
                                System.out.print(this.luminosityOfPixel(x, y) + "\t");
                        }
                        System.out.println("]");
                }
        }
        /**
         * Method to print out a table of the energy for each Pixel in an image
         */
        public void printEnergy(){
                int pictureHeight = this.getHeight();
                int pictureWidth = this.getWidth();
                System.out.println("Energy:");
                for(int y = 0; y < pictureHeight; y++) {
                        System.out.print("[");
                        for(int x = 0; x < pictureWidth; x++) {
                                System.out.print(this.getEnergy(x, y) + "\t");
                        }
                        System.out.println("]");
                }
        }

        /**
         * Prints a two dimensional array of ints
         * @param array
         */
        public void printArray(int[][] array) {
                int height = array.length;
                int width = array[0].length;
                for (int r = 0; r < width; r++) {
                        for (int c = 0; c < height; c++) {
                                System.out.print(array[c][r] + "\t");
                        }
                        System.out.println();
                }
        }

        /**
         * This method can be used like the other Picture methods, to create a
         * Picture that shows what Pixels are different between two Picture objects.
         *
         * @param picture2 is a Picture to compare the current Picture to
         * @return returns a new Picture with red pixels indicating differences between
         *                      the two Pictures
         */
        public Picture showDifferences(Picture picture2){
                Picture newPicture = new Picture(this);

                int pictureHeight = this.getHeight();
                int pictureWidth = this.getWidth();
                Color red = new Color(255, 0, 0);
                for(int x = 0; x < pictureWidth; x++) {
                        for(int y = 0; y < pictureHeight; y++) {
                                if (!this.getPixel(x, y).equals(picture2.getPixel(x, y))) {
                                        Pixel p = newPicture.getPixel(x, y);
                                        p.setColor(red);
                                }
                        }
                }
                return newPicture;
        }

        /**
         * This helper method is used to test the method fillTables. Specifically it
         * focuses on the 2D array "table"
         */
        public int[][] getTableAfterFillTables() {
                int height = this.getHeight();
                int width = this.getWidth();

                int[][] table = new int[width][height];
                int[][] parent = new int[width][height];
                this.fillTables(table, parent);
                return table;
        }

        /**
         * This helper method is used to test the method fillTables. Specifically it
         * focuses on the 2D array "parent"
         */
        public int[][] getParentAfterFillTables() {
                int height = this.getHeight();
                int width = this.getWidth();

                int[][] table = new int[width][height];
                int[][] parent = new int[width][height];
                this.fillTables(table, parent);
                return parent;
        }

        //////////////////////////// Grayscale Example /////////////////////////////////
        /*
         * Each of the methods below is constructive: in other words, each of the
         * methods below generates a new Picture, without permanently modifying the
         * original Picture.
         */

        /**
         * Returns a new Picture, which is the gray version of the current Picture (this)
         *
         * This is an example where all of the pixel-processing occurs within
         * the nested for loops (over the columns, x, and rows, y).
         *
         * @return A new Picture that is the grayscale version of this Picture.
         */
        public Picture grayscale2() {
                Picture newPicture = new Picture(this);

                int pictureHeight = this.getHeight();
                int pictureWidth = this.getWidth();

                for (int x = 0; x < pictureWidth; x++) {
                        for (int y = 0; y < pictureHeight; y++) {

                                Pixel currentPixel = newPicture.getPixel(x, y);

                                Color c = currentPixel.getColor();
                                int redComponent = c.getRed();
                                int greenComponent = c.getGreen();
                                int blueComponent = c.getBlue();

                                int average = (redComponent + greenComponent + blueComponent) / 3;

                                currentPixel.setRed(average);
                                currentPixel.setGreen(average);
                                currentPixel.setBlue(average);
                        }
                }
                return newPicture;
        }


        /**
         * Converts the Picture into grayscale. Since any variation of gray
         *      is obtained by setting the red, green, and blue components to the same
         *      value, a Picture can be converted into its grayscale component
         *      by setting the red, green, and blue components of each pixel in the
         *      new picture to the same value: the average of the red, green, and blue
         *      components of the same pixel in the original.
         *
         * This example shows a more modular approach: grayscale uses a helper
         * named setPixelToGray; setPixelToGray, in turn, uses the helper averageOfRGB.
         *
         * @return A new Picture that is the grayscale version of this Picture.
         */
        public Picture grayscale() {
                Picture newPicture = new Picture(this);

                int pictureHeight = this.getHeight();
                int pictureWidth = this.getWidth();

                for(int x = 0; x < pictureWidth; x++) {
                        for(int y = 0; y < pictureHeight; y++) {
                                newPicture.setPixelToGray(x, y);
                        }
                }
                return newPicture;
        }

        /**
         * Helper method for grayscale() to set a pixel at (x, y) to be gray.
         *
         * @param x The x-coordinate of the pixel to be set to gray.
         * @param y The y-coordinate of the pixel to be set to gray.
         */
        private void setPixelToGray(int x, int y) {
                Pixel currentPixel = this.getPixel(x, y);
                int average = Picture.averageOfRGB(currentPixel.getColor());
                currentPixel.setRed(average);
                currentPixel.setGreen(average);
                currentPixel.setBlue(average);
        }
        /**
         * Helper method for grayscale() to calculate the
         * average value of red, green and blue.
         *
         * @param c is the Color to be averaged
         * @return The average of the red, green and blue values of this Color
         */
        private static int averageOfRGB(Color c) {
                int redComponent = c.getRed();
                int greenComponent = c.getGreen();
                int blueComponent = c.getBlue();

                // this uses integer division, which is what we want here
                // pixels always need to have integer values from 0 to 255 (inclusive)
                // for their red, green, and blue components:
                int average = (redComponent + greenComponent + blueComponent) / 3;
                return average;
        }

        //////////////////////////// Change Colors Menu /////////////////////////////////

        //////////////////////////// Negate /////////////////////////////////

        /**
         * Converts the Picture into its photonegative version. The photonegative
         *      version of an image is obtained by setting each of the red, green,
         *      and blue components of every pixel to a value that is 255 minus their
         *      current values.
         *
         * @return A new Picture that is the photonegative version of this Picture.
         */
        public Picture negate() {
                // TODO: Write negate
                return null;
        }
        //////////////////////////// Lighten /////////////////////////////////

        /**
         * Creates an image that is lighter than the original image. The range of
         * each color component should be between 0 and 255 in the new image. The
         * alpha value should not be changed.
         *
         * @return A new Picture that has every color value of the Picture increased
         *         by the lightenAmount.
         */
        public Picture lighten(int lightenAmount) {
                // TODO: Write lighten
                return null;
        }

        //////////////////////////// Darken /////////////////////////////////

        /**
         * Creates an image that is darker than the original image.The range of
         * each color component should be between 0 and 255 in the new image. The
         * alpha value should not be changed.
         *
         * @return A new Picture that has every color value of the Picture decreased
         *         by the darkenAmount.
         */
        public Picture darken(int darkenAmount) {
                // TODO: Write darken
                // NOTE - This is REALLY similar to lighten, could you write a helper
                // method that both call? Don't just copy and paste! :)
                return null;
        }

        //////////////////////////// Add[Blue,Green,Red] /////////////////////////////////

        /**
         * Creates an image where the blue value has been increased by amount.The range of
         * each color component should be between 0 and 255 in the new image. The
         * alpha value should not be changed.
         *
         * @return A new Picture that has every blue value of the Picture increased
         *         by amount.
         */
        public Picture addBlue(int amount) {
                // TODO: Write addBlue
                // NOTE - This is REALLY similar to lighten, could you write a helper
                // method that both call? Don't just copy and paste! :)
                return null;
        }

        /**
         * Creates an image where the red value has been increased by amount. The range of
         * each color component should be between 0 and 255 in the new image. The
         * alpha value should not be changed.
         *
         * @return A new Picture that has every red value of the Picture increased
         *         by amount.
         */
        public Picture addRed(int amount) {
                // TODO: Write addRed
                // NOTE - This is REALLY similar to lighten, could you write a helper
                // method that both call? Don't just copy and paste! :)
                return null;
        }

        /**
         * Creates an image where the green value has been increased by amount. The range of
         * each color component should be between 0 and 255 in the new image. The
         * alpha value should not be changed.
         *
         * @return A new Picture that has every green value of the Picture increased
         *         by amount.
         */
        public Picture addGreen(int amount) {
                // TODO: Write addGreen
                // NOTE - This is REALLY similar to lighten, could you write a helper
                // method that both call? Don't just copy and paste! :)
                return null;
        }

        //////////////////////////// Rotate Right /////////////////////////////////

        /**
         * Returns a new picture where the Picture is rotated to the right by 90
         * degrees. If the picture was originally 50 Pixels by 70 Pixels, the new
         * Picture should be 70 Pixels by 50 Pixels.
         *
         * @return a new Picture rotated right by 90 degrees
         */
        public Picture rotateRight() {
                // TODO: Write rotateRight
                return null;
        }

        //////////////////////////// Seam Carving Section /////////////////////////////////

        //////////////////////////// Luminosity /////////////////////////////////
        /**
         * Returns a Picture of a version of grayscale using luminosity instead
         * of a direct average. The Picture should be converted into its luminosity
         * version by setting the red, green, and blue components of each pixel in
         * the new picture to the same value: the luminosity of the red, green, and
         * blue components of the same pixel in the original. Where luminosity =
         * 0.21 * redness + 0.72 * greenness + 0.07 * blueness
         *
         * @return A new Picture that is the luminosity version of this Picture.
         */
        public Picture luminosity(){
                // TODO: Write luminosity
                return null;
        }


        /**
         * Helper method for luminosity() to calculate the
         * luminosity of a pixel at (x,y).
         *
         * @param x  the x-coordinate of the pixel
         * @param y  the y-coordinate of the pixel
         * @return The luminosity of that pixel
         */
        private int luminosityOfPixel(int x, int y) {
                // TODO: Write luminosityOfPixel
                return 0;
        }

        //////////////////////////// Energy /////////////////////////////////

        /**
         * Returns a Picture into a version of the energy of the Picture
         *
         * @return A new Picture that is the energy version of this Picture.
         */
        public Picture energy(){
                // TODO: Write energy
                return null;
        }

        /**
         * Helper method for energy() to calculate the
         * energy of a Pixel.
         *
         * @param x is the x value of the Pixel to be evaluated
         * @param y is the y value of the Pixel to be evaluated
         * @return The energy of this Pixel
         */
        private int getEnergy(int x, int y) {
                // TODO: Write getEnergy
                return 0;
        }


        //////////////////////////// Compute Seam /////////////////////////////////

        /**
         * helper method computeSeam returns an int array with the x-coordinates
         * (columns) of the lowest-energy seam running from the top row to the
         * bottom row.
         *
         * We would normally make this method private, but it is public to enable us
         * to test it. See the course assignment for additional details.
         */
        public int[] computeSeam() {
                int height = this.getHeight();
                int width = this.getWidth();

                int[][] table = new int[width][height];
                int[][] parent = new int[width][height];
                this.fillTables(table, parent);

                // TODO: Write the rest of computeSeam
                return null;
        }
        /**
         * helper method fillTables is passed two, 2D arrays and fills them with the
         * "table" and "parent" contents as described in the assignment.
         *
         * See the course assignment for additional details.
         */
        private void fillTables(int[][] table, int[][] parent) {
                // TODO: Write fillTables
        }


        //////////////////////////// Show Seam /////////////////////////////////

        /**
         * Returns a new image, with the lowest cost seam shown in red. The lowest
         * cost seam is calculated by calling computeSeam()
         *
         * @return a new Picture
         */
        public Picture showSeam(){
                // TODO: Write showSeam
                return null;
        }

        //////////////////////////// Carving (2 methods) /////////////////////////////////

        /**
         * Returns a new picture, where the seam identified by calling computeSeam() is
         * removed. The resulting image should be the same height as the original
         * but have a width that is one smaller than the original.
         */
        public Picture carve(){
                // TODO: Write carve
                return null;
        }

        /**
         * This returns a new Picture that has a number of seams removed.
         *
         * If the input is greater than the width of the Picture, first print an error using
         * System.err instead of System.out, then return null. Here is the error message:
         *
         * System.err.println("Cannot call carveMany with argument " + numSeams + " on image of width " + this.getWidth());
         *
         * @param numSeams is the number of times that carve should be called
         * @return a new picture with numSeams removed
         */
        public Picture carveMany(int numSeams){
                // TODO: Write carveMany
                return null;
        }


        //////////////////////////// Extra Credit /////////////////////////////////

        /**
         * @param x x-coordinate of the pixel currently selected.
         * @param y y-coordinate of the pixel currently selected.
         * @param background Picture to use as the background.
         * @param threshold Threshold within which to replace pixels.
         *
         * @return A new Picture where all the pixels in the original Picture,
         *      which differ from the currently selected pixel within the provided
         *      threshold (in terms of color distance), are replaced with the
         *      corresponding pixels in the background picture provided.
         *
         *      If the two Pictures are of different dimensions, the new Picture will
         *      have length equal to the smallest of the two Pictures being combined,
         *      and height equal to the smallest of the two Pictures being combined.
         *      In this case, the Pictures are combined as if they were aligned at
         *      the top left corner (0, 0).
         */
        public Picture chromaKey(int xRef, int yRef, Picture background, int threshold) {
                // TODO: Write chromaKey (Extra Credit)
                return null;
        }

        //////////////////////////// Flip /////////////////////////////////

        /**
         * Flips this Picture about the given axis. The axis can be one of
         *      four static integer constants:
         *
         *      (a) Picture.HORIZONTAL: The picture should be flipped about
         *              a horizontal axis passing through the center of the picture.
         *      (b) Picture.VERTICAL: The picture should be flipped about
         *              a vertical axis passing through the center of the picture.
         *      (c) Picture.FORWARD_DIAGONAL: The picture should be flipped about
         *              the line that passes through the southwest corner of the
         *              picture and that extends at 45deg. to the northeast
         *      (d) Picture.BACKWARD_DIAGONAL: The picture should be flipped about
         *              an axis that passes through the north-west corner and extends
         *              at a 45deg angle to the southeast
         *
         * If the input is not one of these static variables, print an error using
         * System.err (instead of System.out):
         *                              System.err.println("Invalid flip request");
         *   ... and then return null.
         *
         *
         * @param axis Axis about which to flip the Picture provided.
         *
         * @return A new Picture flipped about the axis provided.
         */
        public Picture flip(int axis) {
                // TODO: Write flip (Extra Credit)
                return null;
        }

        //////////////////////////// Show Edges /////////////////////////////////

        /**
         * @param threshold
         *            Threshold to use to determine the presence of edges.
         *
         * @return A new Picture that contains only the edges of this Picture. For
         *         each pixel, we separately consider the color distance between
         *         that pixel and the one pixel to its left, and also the color
         *         distance between that pixel and the one pixel to the north, where
         *         applicable. As an example, we would compare the pixel at (3, 4)
         *         with the pixels at (3, 3) and the pixels at (2, 4). Also, since
         *         the pixel at (0, 4) only has a pixel to its north, we would only
         *         compare it to that pixel. If either of the color distances is
         *         larger than the provided color threshold, it is set to black
         *         (with an alpha of 255); otherwise, the pixel is set to white
         *         (with an alpha of 255). The pixel at (0, 0) will always be set to
         *         white.
         */
        public Picture showEdges(int threshold) {
                // TODO: Write show edges (Extra Credit)
                return null;
        }

        //////////////////////////////// Blur //////////////////////////////////

        /**
         * Blurs this Picture. To achieve this, the algorithm takes a pixel, and
         * sets it to the average value of all the pixels in a square of side (2 *
         * blurThreshold) + 1, centered at that pixel. For example, if blurThreshold
         * is 2, and the current pixel is at location (8, 10), then we will consider
         * the pixels in a 5 by 5 square that has corners at pixels (6, 8), (10, 8),
         * (6, 12), and (10, 12). If there are not enough pixels available -- if the
         * pixel is at the edge, for example, or if the threshold is larger than the
         * image -- then the missing pixels are ignored, and the average is taken
         * only of the pixels available.
         *
         * The red, blue, green and alpha values should each be averaged separately.
         *
         * @param blurThreshold
         *            Size of the blurring square around the pixel.
         *
         * @return A new Picture that is the blurred version of this Picture, using
         *         a blurring square of size (2 * threshold) + 1.
         */
        public Picture blur(int blurThreshold) {
                // TODO: Write blur (Extra Credit)
                return null;
        }

        //////////////////////////////// Paint Bucket //////////////////////////////////

        /**
         * @param x x-coordinate of the pixel currently selected.
         * @param y y-coordinate of the pixel currently selected.
         * @param threshold Threshold within which to delete pixels.
         * @param newColor New color to color pixels.
         *
         * @return A new Picture where all the pixels connected to the currently
         *      selected pixel, and which differ from the selected pixel within the
         *      provided threshold (in terms of color distance), are colored with
         *      the new color provided.
         */
        public Picture paintBucket(int x, int y, int threshold, Color newColor) {
                // TODO: Write paintBucket (Extra Credit)
                return null;
        }

        //////////////////////////////// Main Method //////////////////////////////////

        public static void main(String[] args) {
                // Try this code as you start debugging...
                //              Picture tiny            = Picture.loadPicture("Tiny.bmp");
                //              Picture tinyGray    = tiny.grayscale();
                //              tiny.explore(); // opens in the regular, zoomable window
                //              tinyGray.show(); // opens in a simpler window without the controls

                // This asks you to pick a file and then launches the PictureExplorer
                //              Picture initialPicture = new PictureStarter(
                //                              FileChooser.pickAFile(FileChooser.OPEN));
                //              initialPicture.explore();
                Picture pic             = Picture.loadPicture("Maria1.bmp");
                pic.explore();
        }

} // End of Picture class

