import java.awt.* ;
import javax.swing.* ;
import java.awt.event.ComponentListener ;
import java.awt.event.ComponentEvent ;
import java.util.* ;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.lang.*;
import java.io.*;
import java.awt.Graphics2D ;
import java.awt.geom.Rectangle2D ;
import java.awt.geom.Ellipse2D ;
import java.awt.geom.AffineTransform ;
import java.awt.image.BufferedImage;

public class ArtPanel extends JPanel {

	private int IMAGE_SIZE = 150; // images will always be square, so this is both length and width
	private int MAX_NUM_PICTURES = 16;
	private int MAX_HISTORY_BUFFER = 1000;
	private BufferedImage[] images = new BufferedImage[MAX_NUM_PICTURES];
	private Chromozome[] chromozomes = new Chromozome[MAX_NUM_PICTURES];
	private PreCombinedImage[] preCombinedImages = new PreCombinedImage[MAX_NUM_PICTURES];

	/*
	2-d array for holding previous generations incase user wants to go back
	and pointer for current location
	*/
	private Chromozome[][] chromozomeHistory = new Chromozome[MAX_HISTORY_BUFFER][MAX_NUM_PICTURES];
	private int historyIndex = 0;

	/*
	Color masks
	If == true that color should be displayed in all images
	*/
	private boolean redMask = true;
	private boolean greenMask = true;
	private boolean blueMask = true;

	private int nodeSize = 20; // keeps track of nodeSize of all chromozomes

	// these will be used in conjunction with mouse actions for users
	// to select pictures
	private Rectangle2D.Double[] selectionRectangles = new Rectangle2D.Double[MAX_NUM_PICTURES];
	private boolean[] selection = new boolean[MAX_NUM_PICTURES];
	private int numberOfSelections = 0; // holds the count of selected images
	private int mostRecentlyChanged = 0; // holds the index of the most recently clicked item


	// needs a handle on this panel so that it can disable all controls while computing
	private ControlPanel controlPanel;

	// allows outside and internal methods to check if a calculation thread for
	// images is still running
	private boolean threadAlive = false;

	/*
	Genetic Variables
	*/
	private boolean mutationOnly = false; // if true, no crossover, if false, crossover and maybe mutation
	private Random random;


	//keeps track of generation
	private int generation = 0;


	 public ArtPanel()
	 {

		 addMouseListener( new MouseHandler() ) ;

		 for(int i = 0; i < MAX_NUM_PICTURES; i++)
		 {
			 chromozomes[i] = GeneticOperators.generateChromozome(nodeSize);
			 preCombinedImages[i] = new PreCombinedImage(GeneticOperators.calculateImage(chromozomes[i], IMAGE_SIZE, IMAGE_SIZE));
			 int[] tempImage = preCombinedImages[i].combineImage(redMask, greenMask, blueMask);
			 BufferedImage tempBufferedImage = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB);
			 tempBufferedImage.setRGB(0, 0, IMAGE_SIZE, IMAGE_SIZE, tempImage, 0, IMAGE_SIZE);
			 images[i] = tempBufferedImage;
		 }

		 // make sure everything is using same random number generator
		 random = GeneticOperators.getRandom();
	 }

	 public void setControlPanel(ControlPanel panel)
	 {
		 controlPanel = panel;
	 }

	 public int getNumberOfSelections()
	 {
		 return numberOfSelections;
	 }

	 public int getNodeSize()
	 {
		 return nodeSize;
	 }

	 public int getGeneration()
	 {
		 return generation;
	 }

	 // only returns an image if ONLY one is selected
	 public BufferedImage getSelectedImage()
	 {
		if( numberOfSelections == 1)
		{
			for(int i = 0; i < images.length; i++)
			{
				if(selection[i]) // if true (selected)
				{
					return images[i];
				}
			}
		}

		return null;
	 }

	 // only returns an chromosome if ONLY one is selected
	 public Chromozome getSelectedChromosome()
	 {
		if( numberOfSelections == 1)
		{
			for(int i = 0; i < images.length; i++)
			{
				if(selection[i]) // if true (selected)
				{
					return chromozomes[i];
				}
			}
		}

		return null;
	 }

	 public void ImportChromosome(Chromozome a)
	 {
		 // find the currently selected image index
		 // this index is where the imported image is placed

		 int importIndex = 0;

		 for(int i = 0; i < images.length; i++)
		 {
			 if(selection[i]) // if true (selected)
			 {
				 importIndex = i;
				 break;
			 }
		 }

		 // Once lcoation is found, replace chromosome at that location with imported
		 chromozomes[importIndex] = a;

		 // calculate the newly imported image, and then repaint
			PreCombinedImage image = new PreCombinedImage(GeneticOperators.calculateImage(chromozomes[importIndex], IMAGE_SIZE, IMAGE_SIZE));
			preCombinedImages[importIndex] = image;

		 	int[] tempImage = image.combineImage(redMask, greenMask, blueMask);
			BufferedImage tempBufferedImage = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB);
		 	tempBufferedImage.setRGB(0, 0, IMAGE_SIZE, IMAGE_SIZE, tempImage, 0, IMAGE_SIZE);
		 	images[importIndex] = tempBufferedImage;
		 	repaint();
	 }

	 public void setMutationOnly(boolean t)
	 {
		 mutationOnly = t;
	 }

	 public void createNewGeneration(int nodSize)
	 {
		 generation = 0;

		 // set so that other objects and methods know that thread is running
		 threadAlive = true;

		 nodeSize = nodSize;

		 // whipe out history and re-set index
		 chromozomeHistory = new Chromozome[MAX_HISTORY_BUFFER][MAX_NUM_PICTURES];
		 historyIndex = 0;

		 // turn off all controls so that user can't mess up anything
		 controlPanel.setEnableAll(false);

		 // delete all images computed and non computed
		 preCombinedImages = new PreCombinedImage[MAX_NUM_PICTURES];
		 images = new BufferedImage[MAX_NUM_PICTURES];

		 //reset what is selected
		 numberOfSelections = 0;
		 for(int i = 0; i < selection.length; i++)
		 {
			 selection[i] = false;
		 }

		 //create new set of chromozomes
		 for(int i = 0; i < MAX_NUM_PICTURES; i++)
		 {
			 chromozomes[i] = GeneticOperators.generateChromozome(nodeSize);
		 }

		 //create thread to compute images
		 ImageComputationThread computeImages = new ImageComputationThread(chromozomes, this, IMAGE_SIZE, redMask, greenMask, blueMask);
		 //start thread
		 computeImages.start();
	 }

	 public void calcFunctionChange()
	 {
		 controlPanel.setEnableAll(false);
		 threadAlive = true;
		 ImageComputationThread computeImages = new ImageComputationThread(chromozomes, this, IMAGE_SIZE, redMask, greenMask, blueMask);
		 computeImages.start();
	 }

	 public void nextGeneration()
	 {

		 generation++;
		 controlPanel.repaint();
		 //create copy of chromozome array
		 Chromozome[] tempCopy = new Chromozome[chromozomes.length];
		 for(int i = 0; i < chromozomes.length; i++)
		 {
			 tempCopy[i] = chromozomes[i].copy();
		 }

		 // save the current set of chromozomes before changing them
		 // temp copy is saved cause the reference to chromozomes and what is contained there in will change
		 chromozomeHistory[historyIndex] = tempCopy;
		 historyIndex++; // increment counter

		 threadAlive = true;
		 // turn off all controls so that user can't mess up anything
		 controlPanel.setEnableAll(false);

		 // delete all images computed and non computed
		 preCombinedImages = new PreCombinedImage[MAX_NUM_PICTURES];
		 images = new BufferedImage[MAX_NUM_PICTURES];


		 if(numberOfSelections == 0) // random mating between everything
		 {
			 int countOfPopulation = 0;

			 while(countOfPopulation < MAX_NUM_PICTURES)
			 {
				 // determine which genetic operators to do
				 if(mutationOnly)
				 {
					 // pick a parent randomly
					 int parent = random.nextInt(MAX_NUM_PICTURES);
					 // create a new child with the mutated parents chromozomes
					 chromozomes[countOfPopulation] = GeneticOperators.mutate(tempCopy[parent]);
				 }
				 else // crossover and maybe mutation
				 {
					 // randomly choose 2 different parents from the population
					 int parent1 = 0;
					 int parent2 = 0;

					 while(parent1 == parent2)
					 {
						 parent1 = random.nextInt(MAX_NUM_PICTURES);
						 parent2 = random.nextInt(MAX_NUM_PICTURES);
					 }

					 Chromozome[] children = GeneticOperators.crossOver(tempCopy[parent1], tempCopy[parent2]);

					 // choose one of the two children to remain in next generation
					 chromozomes[countOfPopulation] = children[random.nextInt(2)];
				 }

				 countOfPopulation++;
			 }
		 }
		 else if(numberOfSelections == 1) // mate selected with everything
		 {
			 // find parent one in selection array
			 int parent1 = 0;
			 for(int i = 0; i < selection.length; i++)
			 {
				 if(selection[i])
				 {
					 parent1 = i;
				  	 break;
			 	 }
			 }

			 int countOfPopulation = 0;

			 while(countOfPopulation < MAX_NUM_PICTURES)
			 {
				 // we want to keep the original parent, so we save the index value from being changed
				 if(parent1 != countOfPopulation)
				 {
					 // determine which genetic operators to do
					 if(mutationOnly)
					 {
						 // create a new child with the mutated parents chromozomes
						 chromozomes[countOfPopulation] = GeneticOperators.mutate(tempCopy[parent1]);
					 }
					 else // crossover and maybe mutation
					 {
						 // randomly choose the other parent from the population
						 int parent2 = parent1;

						 while(parent1 == parent2)
						 {
							 parent2 = random.nextInt(MAX_NUM_PICTURES);
						 }

						 Chromozome[] children = GeneticOperators.crossOver(tempCopy[parent1], tempCopy[parent2]);

						 // choose one of the two children to remain in next generation
						 chromozomes[countOfPopulation] = children[random.nextInt(2)];
					 }
				 }

				 countOfPopulation++;
			 }
		 }
		 else // mate selected with other selected, throwing out the de-selected
		 {
			 // create an array the size of the total number of selected images, will always be > 1
			 int[] parents = new int[numberOfSelections];

			 // find each index, and store it
			 int count = 0;
			 for(int i = 0; i < selection.length; i++)
			 {
				 if(selection[i])
				 {
					 parents[count] = i;
					 count++;
				 }
			 }

			 int countOfPopulation = 0;

			 while(countOfPopulation < MAX_NUM_PICTURES)
			 {
				 // we want to keep the original parent, so we save the index value from being changed
				 if(!isParentInSelected(parents, countOfPopulation))
				 {
					 // determine which genetic operators to do
					 if(mutationOnly)
					 {
						 // choose one of the selected parents
						 int parent = random.nextInt(parents.length);
						 // create a new child with the mutated parents chromozomes
						 chromozomes[countOfPopulation] = GeneticOperators.mutate(tempCopy[parents[parent]]);
					 }
					 else // crossover and maybe mutation
					 {
						 // randomly choose 2 different parents from the selected parents
						 int parent1 = 0;
						 int parent2 = 0;

						 while(parent1 == parent2)
						 {
							 parent1 = random.nextInt(parents.length);
							 parent2 = random.nextInt(parents.length);
						 }

						 Chromozome[] children = GeneticOperators.crossOver(tempCopy[parents[parent1]], tempCopy[parents[parent2]]);

						 // choose one of the two children to remain in next generation
						 chromozomes[countOfPopulation] = children[random.nextInt(2)];
					 }
				 }

				 countOfPopulation++;
			 }

		 }

		 //create thread to compute images
		 ImageComputationThread computeImages = new ImageComputationThread(chromozomes, this, IMAGE_SIZE, redMask, greenMask, blueMask);
		 //start thread
		 computeImages.start();
	 }

	private boolean isParentInSelected(int[] parents, int index)
	{
		for(int i = 0; i < parents.length; i++)
		{
			if(parents[i] == index)
				return true;
		}

		return false;
	}

	 public void setPreCombinedImage(int index, PreCombinedImage image)
	 {
		 preCombinedImages[index] = image;
	 }

	 public void setBufferedImage(int index, BufferedImage image)
	 {
		  images[index] = image;
		  // repaint so you can see the images pop onto the screen in real time
		  repaint();
	 }

	 public void threadCompletedComputation()
	 {
		 // re-enable control panel, so users are back in control
		 controlPanel.setEnableAll(true);
		 // turn to false because thread is now dead
		 threadAlive = false;
	 }

	 public boolean hasCompletedComputation()
	 {
		 // this method looks to see if the thread is done
		if(threadAlive)
			return false;
		else
			return true;
	 }

	 public void setRedMask(boolean b)
	 {
		 redMask = b;

		 // for each pre-combined image, combine only the colors the user has selected
		 for(int i = 0; i < preCombinedImages.length; i++)
		 {
		 	int[] tempImage = preCombinedImages[i].combineImage(redMask, greenMask, blueMask);
			BufferedImage tempBufferedImage = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB);
		 	tempBufferedImage.setRGB(0, 0, IMAGE_SIZE, IMAGE_SIZE, tempImage, 0, IMAGE_SIZE);
		 	images[i] = tempBufferedImage;
		 }
		 repaint();
	 }

	 public void setGreenMask(boolean b)
	 {
		 greenMask = b;
		 // for each pre-combined image, combine only the colors the user has selected
		 for(int i = 0; i < preCombinedImages.length; i++)
		 {
		 	int[] tempImage = preCombinedImages[i].combineImage(redMask, greenMask, blueMask);
			BufferedImage tempBufferedImage = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB);
		 	tempBufferedImage.setRGB(0, 0, IMAGE_SIZE, IMAGE_SIZE, tempImage, 0, IMAGE_SIZE);
		 	images[i] = tempBufferedImage;
		 }
		 repaint();
	 }

	 public void setBlueMask(boolean b)
	 {
		 blueMask = b;
		 // for each pre-combined image, combine only the colors the user has selected
		 for(int i = 0; i < preCombinedImages.length; i++)
		 {
		 	int[] tempImage = preCombinedImages[i].combineImage(redMask, greenMask, blueMask);
			BufferedImage tempBufferedImage = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB);
		 	tempBufferedImage.setRGB(0, 0, IMAGE_SIZE, IMAGE_SIZE, tempImage, 0, IMAGE_SIZE);
		 	images[i] = tempBufferedImage;
		 }
		 repaint();
	 }

	 public void paintComponent( Graphics g )
	 {
		  Graphics2D g2 = ( Graphics2D ) g ;
		  super.paintComponent( g2 ) ;

		  g2.setColor( new Color( 0 , 0 , 0 ) ) ;
		  g2.fill( new Rectangle2D.Double( 0 , 0 , this.getWidth() , this.getHeight() ) ) ;

		//get the height and width of the panel
		  int y = this.getHeight() ;
		  int x = this.getWidth() ;

		  // the space going from left to right between all pictures
		  int xBufferSpace = (int)((x - (IMAGE_SIZE * 4)) / 5);

		  // the space going from top to bottom between all pictures
		  int yBufferSpace = (int)((y - (IMAGE_SIZE * 4)) / 5);


		  //There are 4 rows and 4 columns of pictures
		  // creates rectangles
		  for(int i = 0; i < 4; i++)
		  {
			  for(int j = 0; j < 4; j++)
			  {
				 selectionRectangles[i*4 + j] = new Rectangle2D.Double( (j+1)*xBufferSpace-2 + IMAGE_SIZE*j , (i+1)*yBufferSpace-2 + IMAGE_SIZE*i, IMAGE_SIZE+4 , IMAGE_SIZE+4 ) ;
			  }
		  }

		  //draws the rectangles
		  // this is done first so that the pictures fill in the center of shape
		  for(int i = 0; i < selectionRectangles.length; i++)
		  {
			  if(selection[i]) // if true draw white
		  		 g2.setColor( new Color( 255 , 255 , 255 ) ) ;
		  	  else // else draw black
		  	  	g2.setColor( new Color( 0 , 0 , 0 ) ) ;

		  	  g2.fill(selectionRectangles[i]);
		  }

		  AffineTransform a;

		  for(int i = 0; i < 4; i++)
		  {
			  for(int j = 0; j < 4; j++)
			  {
				  a = new AffineTransform() ;
				  a.translate( (j+1)*xBufferSpace + IMAGE_SIZE*j , (i+1)*yBufferSpace + IMAGE_SIZE*i );
		  		  if(images[i*4+j] != null) g2.drawImage( images[i*4 + j] , a , this );
			  }
		  }

	 }

     private class MouseHandler extends MouseAdapter
 	 {
		  public void mouseClicked( MouseEvent event )
		  {
			  // check if double click
			  if(event.getClickCount() == 2)
			  {
				  selection[mostRecentlyChanged] = !selection[mostRecentlyChanged];
				  if(selection[mostRecentlyChanged]) numberOfSelections++;
				  else numberOfSelections--;
				  repaint();

				  // pop up window with selected image

				  ImageFrame frame = new ImageFrame(chromozomes[mostRecentlyChanged].copy()
				  			, images[mostRecentlyChanged]);
				  //frame.setSize(165, 210);
				  frame.setSize(520, 512);
				  frame.show();
				  frame.setSize(165, 210);
			  }
			  else // else single click
			  {
					boolean somethingChanged = false;

					for(int i = 0; i < selection.length; i++)
					{
						if(selectionRectangles[i] != null)
						{
							if(selectionRectangles[i].contains(event.getPoint()) )
							{
								selection[i] = !selection[i];
								if(selection[i]) numberOfSelections++;
								else numberOfSelections--;
								somethingChanged = true;
								mostRecentlyChanged = i;
								break;
							}
						}
					}

					if(somethingChanged) repaint();
				}
		  }
	 }
}

