package mosaiccreator;

import java.io.File;
import java.awt.Image;
import java.awt.Color;
import javax.swing.JProgressBar;
import javax.swing.JLabel;
import java.util.Vector;
import java.awt.Toolkit;

/**
 *
 * <p>Title: Mosaic creator Algorithm</p>
 * <p>Description: creates a mosaic </p>
 * @author Lance Finfrock
 * @version 1.0
 */
public class MosaicAlgorithm implements Runnable {
  public PicPixels MainPicture;
  public File[] FilesOfPieces;
  private JProgressBar progressBar;
  private JLabel UserDisplaylabel;
  private int divisionWidth;
  private int divisionHeight;
  public boolean isAlive;
  public MainWindow frame;
  private boolean isReplacement;

  /**
   *
   * @param FileNameToOriginal String the path to the mosaic image to be mapped
   * @param filesOfPieces File[] all the files for the mosaic piece to be used
   * @param detailLevel int amount of detail wanted to match the pictures the hight the beter match
   * @param divwide int the amount of division wide of the mosaic
   * @param divheight int the amount of division high of the mosaic
   * @param isReplacement1 boolean if the mosaic is to be created with duplcates
   * @param f MainWindow the window that called the class
   * @param jProgressBar JProgressBar the progress bar to show the user progress
   * @param label JLabel the label to write to the user what is going on
   */
  public MosaicAlgorithm(String FileNameToOriginal,
                         File[] filesOfPieces,
                         int detailLevel,
                         int divwide,
                         int divheight,
                         boolean isReplacement1,
                         MainWindow f,
                         JProgressBar jProgressBar,
                         JLabel label) {

    this.progressBar = jProgressBar;
    this.UserDisplaylabel = label;
    this.frame = f;
    this.isReplacement = isReplacement1;
    this.isAlive = true;
    this.FilesOfPieces = filesOfPieces;
    this.divisionWidth = divwide;
    this.divisionHeight = divheight;
    this.progressBar.setVisible(true);
    this.MainPicture = CreatePictureToCopy(FileNameToOriginal, detailLevel);

    //creating a local thread
    Thread local = new Thread(this);
    local.setPriority(Thread.MIN_PRIORITY);// so the user can do something else on thier computer while the algorithm is running
    local.start();

  }

  public void run() {
    PicPixels[] Pieces;

    //duplicates
    if(this.isReplacement)
      Pieces = CreatePicWithReplacement();
    else//no duplicates
      Pieces = CreatePicWithOutReplacement();

    //setting progress bar back to zero
    this.progressBar.setValue(0);
    this.UserDisplaylabel.setText(" ");
    this.progressBar.setVisible(false);

    //the algorithm was not stopped
    if(this.isAlive){

      MainWindow.currentMosaic = new Mosaic(Pieces, this.MainPicture, this.divisionWidth,
                                            this.divisionHeight);

      int windowWidth = (int) Toolkit.getDefaultToolkit().getScreenSize().
          getWidth();
      int windowHeight = (int) Toolkit.getDefaultToolkit().getScreenSize().
          getHeight();

      //resize the mosaic to the size of the screen
      MainWindow.currentMosaic.resizeMosaic(windowWidth, windowHeight,
                                            this.frame);

      //return to the main window
      this.frame.StoreMosaic();

    }
    else{//the algorithm was stopped

      MainWindow.currentMosaic = null;

      this.frame.StoreMosaic();

    }

  }

  /**
   * creates the mosaic with duplicates
   * @return PicPixels[]
   */
  public PicPixels[] CreatePicWithReplacement(){
    int subPicWidth = Math.round(MainPicture.width /divisionWidth);
    int subPicHeight = Math.round(MainPicture.height / divisionHeight);

    //get all the mosaic pieces
    PicPixels[] pictures = ReadPictures(subPicWidth, subPicHeight);

    //the mosaic created
    PicPixels[] MosaicPiecesPixels = new PicPixels[divisionWidth * divisionHeight];

    int Height = (-1)* subPicHeight;
    int Width = 0;

    //telling the user what is going on
    UserDisplaylabel.setText("Calculating...");

    for (int i = 0; i < MosaicPiecesPixels.length; i++) {
      if (i % this.divisionWidth == 0){
        Height += subPicHeight;
      }
      Width = i % this.divisionWidth * subPicWidth;

      //showing the user the progress
      progressBar.setValue((int)(((double)i/MosaicPiecesPixels.length)*100));

      //to end the creation process
      if(!this.isAlive)return null;

      //find the best match
      MosaicPiecesPixels[i] = FindMatch(Width, Height, pictures);
    }

    return MosaicPiecesPixels;
  }

  /**
   * creates the mosaic with no duplicates
   * @return PicPixels[] the created mosaic
   */
  public PicPixels[] CreatePicWithOutReplacement() {

    int subPicWidth = Math.round(MainPicture.width / divisionWidth);
    int subPicHeight = Math.round(MainPicture.height / divisionHeight);

    //get the possiable mosaic pieces
    Vector pictures = ReadPicturesIntoVector(subPicWidth, subPicHeight);

    //the mosaic created
    PicPixels[] MosaicPiecesPixels = new PicPixels[divisionWidth * divisionHeight];


    int Height = ( -1) * subPicHeight;
    int Width = 0;

    //telling the user what is going on
    UserDisplaylabel.setText("Calculating...");

    for (int i = 0; i < MosaicPiecesPixels.length; i++) {
      if (i % this.divisionWidth == 0) {
        Height += subPicHeight;
      }
      Width = i % this.divisionWidth * subPicWidth;

      //showing the user the progress of the algorithm
      progressBar.setValue( (int) ( ( (double) i / MosaicPiecesPixels.length) *
                                   100));

     //to end the creation process
      if (!this.isAlive)return null;

      //find the best piece and remove it from the possiable mosaic pieces
      MosaicPiecesPixels[i] = FindMatch(Width, Height, pictures);
    }

    return MosaicPiecesPixels;

  }

    /**
     * create the mosaic image to match
     * @param originalPicturePath String the path to the original image
     * @param MaxDimension int the detial level
     * @return PicPixels the mosaic image to match
     */
    private PicPixels CreatePictureToCopy(String originalPicturePath,
                                          int MaxDimension) {

      int width, height, newWidth, newHeight;
      Image image = PictureTools.readImage(originalPicturePath, null);

      width = image.getWidth(null);
      height = image.getHeight(null);

      if (width > height) {
        newWidth = MaxDimension;
        newHeight = (newWidth * height) / width;
      }
      else {
        newHeight = MaxDimension;
        newWidth = (newHeight * width) / height;
      }

      return new PicPixels(originalPicturePath, newWidth, newHeight);
    }

    /**
     * read in all the possiable mosaic pieces
     * @param subPicWidth int the width of a mosaic piece
     * @param subPicHeight int the height of a mosaic piece
     * @return PicPixels[] the mosaic pieces
     */
    public PicPixels[] ReadPictures(int subPicWidth, int subPicHeight) {

      PicPixels[] SubPicPixels = new PicPixels[this.FilesOfPieces.length];

      //tell the user what is going on
      UserDisplaylabel.setText("Reading in pictures...");

      try {
        for (int i = 0; i < SubPicPixels.length; i++) {

          //stop the algorithm
          if (!this.isAlive)return null;

          //showing the user the progress
          progressBar.setValue( (int) ( ( (double) i / FilesOfPieces.length) *
                                       100)); //giving the user a feel for how long is left in reading the pictures

          //reading in a piece
          SubPicPixels[i] = new PicPixels(FilesOfPieces[i].getPath(), subPicWidth,
                                          subPicHeight);
        }

      }
      catch (Exception ex) {
        System.err.println(
            "error in the reading in the pictures in mosaic creator");
      }

      return SubPicPixels;
    }

    /**
     * read in all the possiable mosaic pieces
     * @param subPicWidth int the width of a mosaic piece
     * @param subPicHeight int the height of a mosaic piece
     * @return Vector the mosaic pieces
     */
    public Vector ReadPicturesIntoVector(int subPicWidth, int subPicHeight) {

      Vector SubPicPixels = new Vector(this.FilesOfPieces.length);

      //telling the user what is going on
      UserDisplaylabel.setText("Reading in pictures...");

      try {

        for (int i = 0; i < FilesOfPieces.length; i++) {
           if (!this.isAlive)
            return null;

          //giving the user a feel for how long is left in reading the pictures
          progressBar.setValue( (int) ( ( (double) i / FilesOfPieces.length) * 100));


          SubPicPixels.add(new PicPixels(FilesOfPieces[i].getPath(), subPicWidth,
                                          subPicHeight));
        }

      }
      catch (Exception ex) {
        System.err.println(
            "error in the reading in the pictures in mosaic creator");
      }

      return SubPicPixels;

    }

    /**
     * find the best match with no duplicates
     * @param width int the width in the original image
     * @param height int the height in the original image
     * @param pictures Vector the mosaic pieces
     * @return PicPixels the mosaic piece choosen
     */
    private PicPixels FindMatch(int width, int height, Vector pictures) {
      int min = 99999999;
      int current;
      PicPixels MinPic = null;

      if (pictures.get(0) != null) MinPic = (PicPixels) pictures.get(0);

      for (int i = 1; i < pictures.size(); i++) {

        current = Compare(width, height, (PicPixels) pictures.get(i));

        if (current < min) {

          MinPic = (PicPixels) pictures.get(i);
          min = current;
        }

      }
      //remove the mosaic piece choosen
      pictures.remove(MinPic);

      return MinPic;
    }

    /**
     * find the best match with duplicates
     * @param width int the width in the original image
     * @param height int the height in the original image
     * @param pictures PicPixels[] the mosaic pieces
     * @return PicPixels the mosaic piece choosen
     */
    private PicPixels FindMatch(int width, int height, PicPixels[] pictures) {
      int min = 99999999;
      int current;
      PicPixels MinPic = null;

      //set the best to the first image
      if (pictures[0] != null) MinPic = pictures[0];

      for (int i = 1; i < pictures.length; i++) {

        current = Compare(width, height, pictures[i]);

        if (current < min) {
          MinPic = pictures[i];
          min = current;
        }

      }

      return MinPic;
    }

    /**
     *
     * @param width int the width where the original image is
     * @param height int the height where the original image is
     * @param mosaicPiece PicPixels the mosaic piece
     * @return int the difference
     */
    private int Compare(int width, int height, PicPixels mosaicPiece) {

      //mosaic piece was read in correctly
      if (mosaicPiece != null) {
        int diffence = 0;

        for (int i = 0; i < mosaicPiece.width; i++) {
          for (int j = 0; j < mosaicPiece.height; j++) {
            diffence +=
                differenceInColor(mosaicPiece.getColor(i, j),
                                  MainPicture.getColor(i + width, j + height));
          }
        }

        return diffence;
      }

      //mosaic piece was not read in correctly send the highest number
      return 999999999;
    }

    /**
     * finds the difference between two colors
     * @param color1 Color
     * @param color2 Color
     * @return int the difference
     */
    private int differenceInColor(Color color1, Color color2) {
      int diffence = 0;

      diffence += Math.abs(color1.getRed() - color2.getRed());
      diffence += Math.abs(color1.getBlue() - color2.getBlue());
      diffence += Math.abs(color1.getGreen() - color2.getGreen());

      return diffence;
    }
}
