/**
   * Alter the color of each pixel in the image. Make the image sepia.
   *
   * @param bi The BufferedImage on which the color variation is done.
   */
  public static void sepia(BufferedImage bi) {

    // Get image size
    int xSize = bi.getWidth();
    int ySize = bi.getHeight();
    int sepia = 25;

    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {
        int rgb = bi.getRGB(x, y);

        int[] rgbValues = unpackPixel(rgb);
        int alpha = rgbValues[0];
        int red = rgbValues[1];
        int green = rgbValues[2];
        int blue = rgbValues[3];

        // Equalizes all RGB values, and then uses Sepia depth to increase Red and Green amounts by
        // specified value to achieve sepia effect
        int equalizer = (red + green + blue) / 3;
        red = equalizer + (sepia * 2);
        blue = equalizer;
        green = equalizer + sepia;

        // Restriction for values of RGB so to make sure image does not become over manipulated
        if (red > 255) red = 255;
        if (green > 255) green = 255;
        if (blue > 255) blue = 255;

        int newColour = packagePixel(red, green, blue, alpha);
        bi.setRGB(x, y, newColour);
      }
    }
    addImage(bi);
  }
  /**
   * Alter the color of each pixel in the image. Make the image cooler in color.
   *
   * @param bi The BufferedImage on which the color variation is done.
   */
  public static void cooler(BufferedImage bi) {
    int xSize = bi.getWidth();
    int ySize = bi.getHeight();

    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {
        int rgb = bi.getRGB(x, y);

        int[] rgbValues = unpackPixel(rgb);
        int alpha = rgbValues[0];
        int red = rgbValues[1];
        int green = rgbValues[2];
        int blue = rgbValues[3];

        // Apply changes to the rgb value.
        if (blue <= 190) {
          blue += 5;
        }
        if (red >= 130) {
          red--;
        }
        if (green >= 130) {
          green--;
        }

        int newColour = packagePixel(red, green, blue, alpha);
        bi.setRGB(x, y, newColour);
      }
    }
    addImage(bi);
  }
  /**
   * Alter the color of each pixel in the image. Make the image greyscaled.
   *
   * @param bi The BufferedImage on which the color variation is done.
   */
  public static void greyScale(BufferedImage bi) {

    int xSize = bi.getWidth();
    int ySize = bi.getHeight();

    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {
        // return R G B and alpha values in integer form
        int rgb = bi.getRGB(x, y);
        // unpack to get individual integers
        int[] rgbValues = unpackPixel(rgb);
        int alpha = rgbValues[0];
        int red = rgbValues[1];
        int green = rgbValues[2];
        int blue = rgbValues[3];
        int average = (red + green + blue) / 3;
        // get rgb values in integers for manipulation
        if (red <= 255) {
          red = average;
        }
        if (green <= 255) {
          green = average;
        }
        if (blue <= 255) {
          blue = average;
        }

        int newColour = packagePixel(red, green, blue, alpha);
        bi.setRGB(x, y, newColour);
      }
    }
    addImage(bi);
  }
  /**
   * Alter the color of each pixel in the image. Make the image negatively colored.
   *
   * @param bi The BufferedImage on which the color variation is done.
   */
  public static void negative(BufferedImage bi) {

    int xSize = bi.getWidth();
    int ySize = bi.getHeight();

    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {
        int rgb = bi.getRGB(x, y);

        // calling unpack pixel to get individual values for RGB
        // this allows for manipulation of these values easily
        int[] rgbValues = unpackPixel(rgb);
        int alpha = rgbValues[0];
        int red = rgbValues[1];
        int green = rgbValues[2];
        int blue = rgbValues[3];

        red = 255 - red;
        green = 255 - green;
        blue = 255 - blue;

        int newColour = packagePixel(red, green, blue, alpha);
        bi.setRGB(x, y, newColour);
      }
    }
    addImage(bi);
  }
  /**
   * Mirrors the image horizontally by reassigning specific pixels around the image.
   *
   * @param bi The BufferedImage on which the translation is done
   */
  public static void mirrorHorizontally(BufferedImage bi) {
    // Get image size to be used in loops to assign rgba values
    int xSize = bi.getWidth();
    int ySize = bi.getHeight();

    // Temp image
    BufferedImage newBi = new BufferedImage(xSize, ySize, 3);
    // mirroring by flipping half an image
    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {
        int rgb = bi.getRGB(xSize - x - 1, y);

        // retrieving the four rgba values using unpack pixel
        int[] rgbValues = unpackPixel(rgb);
        int alpha = rgbValues[0];
        int red = rgbValues[1];
        int green = rgbValues[2];
        int blue = rgbValues[3];
        int newColour =
            packagePixel(
                red, green, blue,
                alpha); // packs pixels to be placed into the buffered image that was initially
                        // used.
        bi.setRGB(x, y, newColour);
      }
    }
    addImage(bi);
  }
  /** Displays the redone manipulated image to the user. */
  public static BufferedImage redo() {
    try {
      previousImage = imageRedo.get(imageRedo.size() - 1);
      addImage(imageRedo.get(imageRedo.size() - 1));
      imageRedo.remove(imageRedo.size() - 1);

    } catch (ArrayIndexOutOfBoundsException e) {

    }

    return previousImage;
  }
  /**
   * Alter the color of each pixel in the image. Make the image brighter or darker.
   *
   * @param bi The BufferedImage on which the brightness variation is done
   * @param side If true, then make the image bright ,else make image dark
   */
  public static void brightness(BufferedImage bi, boolean side) {
    // Get image size
    int xSize = bi.getWidth();
    int ySize = bi.getHeight();

    // The values that are to be changed in the image
    int redNew = 0;
    int greenNew = 0;
    int blueNew = 0;

    // assigning new values for rgb
    if (side == true) {
      redNew = 5;
      greenNew = 5;
      blueNew = 5;
    } else {
      redNew = -5;
      greenNew = -5;
      blueNew = -5;
    }

    // assigning new rgba values usign size of image as limit for loops
    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {
        int rgb = bi.getRGB(x, y);

        int[] rgbValues = unpackPixel(rgb);
        int alpha = rgbValues[0];
        int red = rgbValues[1];
        int green = rgbValues[2];
        int blue = rgbValues[3];

        // Apply changes to the rgb value.
        if (blue <= 205 && blue >= 50) {
          blue += blueNew;
        }
        if (red <= 205 && red >= 50) {
          red += redNew;
        }
        if (green <= 205 && green >= 50) {
          green += greenNew;
        }

        int newColour = packagePixel(red, green, blue, alpha);
        bi.setRGB(x, y, newColour);
      }
    }
    addImage(bi);
  }
  /**
   * Rotates the image 90 degrees counter clock-wise by reassigning specific pixels around the
   * image.
   *
   * @param bi The BufferedImage on which the translation is done
   * @return BufferedImage The manipuated image
   */
  public static BufferedImage rotateCCW(BufferedImage bi) {

    int xSize = bi.getWidth();
    int ySize = bi.getHeight();

    // Temp image
    BufferedImage newBi = new BufferedImage(ySize, xSize, 3);

    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {
        newBi.setRGB(y, xSize - x - 1, bi.getRGB(x, y));
      }
    }
    addImage(newBi);
    return newBi;
  }
  /**
   * Rotates the image 90 degrees clock-wise by reassigning specific pixels around the image.
   *
   * @param bi The BufferedImage on which the translation is done
   * @return BufferedImage The manipuated image
   */
  public static BufferedImage rotateCW(BufferedImage bi) {
    // getting image size
    int xSize = bi.getWidth();
    int ySize = bi.getHeight();

    // Temp image
    BufferedImage newBi = new BufferedImage(ySize, xSize, 3);
    // rotating picture
    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {
        newBi.setRGB(ySize - y - 1, x, bi.getRGB(x, y));
      }
    }
    addImage(newBi);

    return newBi;
  }
  /**
   * Blurs the image.
   *
   * @param bi The BufferedImage on which the blur effect is done.
   * @return BufferedImage The manipuated image
   */
  public static BufferedImage blur(BufferedImage bi) {
    int xSize = bi.getWidth();
    int ySize = bi.getHeight();

    BufferedImage newBi = new BufferedImage(xSize, ySize, 3);

    float[] matrix = {
      0.111f, 0.111f, 0.111f,
      0.111f, 0.111f, 0.111f,
      0.111f, 0.111f, 0.111f,
    };

    BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, matrix));
    bi = op.filter(bi, newBi);

    addImage(bi);
    return newBi;
  }
  /**
   * Swap the major colors of the image (red,green,blue)
   *
   * @param bi The BufferedImage that is being manipulated.
   * @param type The type of switch to be performed.
   */
  public static void swapColors(BufferedImage bi, int type) {
    // Get image size
    int xSize = bi.getWidth();
    int ySize = bi.getHeight();

    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {
        int rgb = bi.getRGB(x, y);

        int[] rgbValues = unpackPixel(rgb);
        int alpha = rgbValues[0];
        int red = rgbValues[1];
        int green = rgbValues[2];
        int blue = rgbValues[3];

        // Swapping the rgb values
        int redNew = red;
        int greenNew = green;
        int blueNew = blue;

        if (type == 0) {
          redNew = green;
          greenNew = red;
        } else if (type == 1) {
          redNew = blue;
          blueNew = red;
        } else if (type == 2) {
          greenNew = blue;
          blueNew = green;
        } else if (type == 3) {
          redNew = green;
          greenNew = blue;
          blueNew = red;
        }
        // assigning newly swapped rgb values
        int newColour = packagePixel(redNew, greenNew, blueNew, alpha);
        bi.setRGB(x, y, newColour);
      }
    }
    addImage(bi);
  }
  /**
   * Flips the image horizontally by reassigning specific pixels around the image.
   *
   * @param bi The BufferedImage on which the translation is done
   */
  public static void flipHorizontal(BufferedImage bi) {

    int xSize = bi.getWidth();
    int ySize = bi.getHeight();

    // Temp image, to store pixels as we reverse everything
    BufferedImage newBi = new BufferedImage(xSize, ySize, 3);

    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {
        newBi.setRGB(xSize - x - 1, y, bi.getRGB(x, y));
      }
    }

    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {
        bi.setRGB(x, y, newBi.getRGB(x, y));
      }
    }
    addImage(bi);
  }
  /**
   * Flips the image vertically by reassigning specific pixels around the image.
   *
   * @param bi The BufferedImage on which the translation is done
   */
  public static void flipVertical(BufferedImage bi) {
    // getting image size
    int xSize = bi.getWidth();
    int ySize = bi.getHeight();

    // temporary image to flip
    BufferedImage newBi = new BufferedImage(xSize, ySize, 3);

    // flipping pixels
    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {
        newBi.setRGB(x, ySize - y - 1, bi.getRGB(x, y));
      }
    }

    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {
        bi.setRGB(x, y, newBi.getRGB(x, y));
      }
    }
    addImage(bi);
  }
  /**
   * Mirrors the image vertically by reassigning specific pixels around the image.
   *
   * @param bi The BufferedImage on which the translation is done
   */
  public static void mirrorVertically(BufferedImage bi) {

    // Get image size
    int xSize = bi.getWidth();
    int ySize = bi.getHeight();

    // Temp image
    BufferedImage newBi = new BufferedImage(xSize, ySize, 3);
    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {

        int rgb = bi.getRGB(x, ySize - y - 1);

        int[] rgbValues = unpackPixel(rgb);
        int alpha = rgbValues[0];
        int red = rgbValues[1];
        int green = rgbValues[2];
        int blue = rgbValues[3];
        int newColour = packagePixel(red, green, blue, alpha);
        bi.setRGB(x, y, newColour);
      }
    }
    addImage(bi);
  }
 /** Displays the initial un-manipulated image to the user. */
 public static BufferedImage reset() {
   previousImage = images.get(0);
   addImage(previousImage);
   return previousImage;
 }
  /**
   * Alter the color of the image by changing the rgba value. Make a specific color more dominant in
   * the image.
   *
   * @param bi The BufferedImage on which the color variation is done
   * @param colorSet The idicator that spcifies which color variation to carry out. 0 for blue, 1
   *     for green, 2 for red, 3 for yellow, 4 for purple.
   */
  public static void colorify(BufferedImage bi, int colorSet) {
    // Get image size
    int xSize = bi.getWidth();
    int ySize = bi.getHeight();

    // The values that are to be changed in the image
    int redNew = 0;
    int greenNew = 0;
    int blueNew = 0;

    // blueify
    if (colorSet == 0) {
      redNew = -2;
      greenNew = -2;
      blueNew = 10;
    }
    // greenify
    else if (colorSet == 1) {
      redNew = -2;
      greenNew = 10;
      blueNew = -2;
    }
    // redify
    else if (colorSet == 2) {
      redNew = 10;
      greenNew = -2;
      blueNew = -2;
    }
    // yellowify
    else if (colorSet == 3) {
      redNew = 5;
      greenNew = 5;
      blueNew = -2;
    }
    // purpleify
    else if (colorSet == 4) {
      redNew = 5;
      greenNew = -2;
      blueNew = 5;
    }

    // Using the 2d array image size as the limit for the nested loops to assign the new colors
    for (int x = 0; x < xSize; x++) {
      for (int y = 0; y < ySize; y++) {
        int rgb = bi.getRGB(x, y);

        int[] rgbValues = unpackPixel(rgb);
        int alpha = rgbValues[0];
        int red = rgbValues[1];
        int green = rgbValues[2];
        int blue = rgbValues[3];

        // Apply changes to the rgb value.
        if (blue <= 205 && blue >= 50) {
          blue += blueNew;
        }
        if (red <= 205 && red >= 50) {
          red += redNew;
        }
        if (green <= 205 && green >= 50) {
          green += greenNew;
        }

        int newColour = packagePixel(red, green, blue, alpha);
        bi.setRGB(x, y, newColour);
      }
    }
    addImage(bi);
  }