import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.Border;


public class GeneticImageFrame extends JFrame
{ private static final long serialVersionUID = 1L;
  private static final int MAX_SOURCE_IMAGE_SIZE = 512;
  private static final int EDGE = 12;
 
  private Container canvas;
  private GeneticImagePanel sourcePanel, geneticPanel;
  private BufferedImage sourceImage, geneticImage;
  private ControlPanel controlPanel;
  
  private GA_RandomTriangles ga_randomTriangles;
  
  private DNA lastDNA, currentDNA;
  private boolean lastGenToggle = false;
  private boolean amimate = false;
  
  //These volatile flags are needed to control the thread.
  private static volatile boolean threadPause = true; 
  private static volatile boolean threadNext = false;
  private static volatile boolean threadRunning = false;
  private static volatile boolean threadKill = false;
  //This threadID is not needed in this simple version, but will be useful
  // when you are running 4 GA threads.
  private static volatile int threadID = 0;

  public GeneticImageFrame() 
  { int controlPanelHeight = 50;
    int frameWidth = 2*MAX_SOURCE_IMAGE_SIZE + 6*EDGE;
    int frameHeight = MAX_SOURCE_IMAGE_SIZE + 6*EDGE + controlPanelHeight;
    
    this.setBounds(0, 0, frameWidth, frameHeight);
    this.setResizable(false);
  
    this.setTitle("Genetic Algorithm Image Matcher");
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
    //NOTE: ===========================================================
    // Since Java 1.2, containers (buttons, sliders, etc) should not 
    // be added directly to a JFrame. You need to get an instance of the 
    // JFrame's ContentPane() and add containers to that. 
    // By contrast, containers are directly to JPanels. 
    canvas = this.getContentPane();
    canvas.setLayout(null);
    canvas.setBackground(ControlPanel.DIALOG_BACKGROUND);
    //==================================================================
    
    
    sourcePanel = new GeneticImagePanel();
    geneticPanel = new GeneticImagePanel();

    canvas.add(sourcePanel);
    canvas.add(geneticPanel);
    sourcePanel.setLocation(EDGE, EDGE);

    this.setVisible(true);
    //NOTE: ===========================================================
    // ControlPanel is passed this instance of GeneticImageFrame.
    // This is how ControlPanel is able to call non-static methods of
    // GeneticImageFrame.
    // The controlPanel needs to be added last because it will load
    // the a source image and call setSourceImage() in this class.
    controlPanel = new ControlPanel(this);
    canvas.add(controlPanel);
    controlPanel.setLocation(EDGE, MAX_SOURCE_IMAGE_SIZE + EDGE);
    //==================================================================
    
    sourcePanel.repaint();
    geneticPanel.repaint();
    
    ga_randomTriangles = new GA_RandomTriangles();
    lastDNA = new DNA(GA_RandomTriangles.TRIANGLE_COUNT);
    
    BufferedImage sourceImage = GeneticImagePanel.loadImage("MonaLisa-512x413.png", this);
    setSourceImage(sourceImage);
  }
  
  public void setSourceImage(BufferedImage sourceImage)
  { 
    sourcePanel.setBufferedImage(sourceImage);
    int imageWidth = sourcePanel.getImageWidth();
    int imageHeight = sourcePanel.getImageHeight();
    sourcePanel.repaint();
    
    DNA.setLimits(imageWidth, imageHeight);
    geneticPanel.setImageSize(imageWidth, imageHeight);
    geneticPanel.setLocation(EDGE*2+imageWidth, EDGE);
    
    this.sourceImage = sourceImage;
    geneticImage = geneticPanel.getBufferedImage();
    //Color imageModeColor = GeneticImagePanel.getModeColor(sourceImage);
    //geneticPanel.setBacground(imageModeColor);
    geneticPanel.setBacground(Color.WHITE);
    reset();
  }
  
  public static void copyDNA(DNA snkDNA, DNA srcDNA)
  { if (srcDNA == null) return;
    int n = srcDNA.getTriangleCount();
    if (n != snkDNA.getTriangleCount()) return;
  
    for (int i=0; i<n; i++)
    { snkDNA.setTriangleGenes(i, srcDNA.getTriangleGenes(i));
    }
  }
  
  
  public void pause()
  { threadPause = true; 
  }
  
  public void start()
  { threadPause = false;
  }
  
  public void nextGeneration()
  { threadNext = true;
  }
  
  public void reset()
  { threadKill = true;
    while (threadRunning)
    { try { Thread.sleep(50); }
      catch (InterruptedException e) { }
    }
    
    geneticPanel.clear();
    geneticPanel.repaint();
    double fitness = GeneticImagePanel.calculateFitness(sourceImage, geneticImage);
    controlPanel.setFitness(fitness);
    startUpGeneticAlgrothimThread();
  }
  
  public void setAnimate(boolean value)
  { amimate = value;
  }
  
  public void startUpGeneticAlgrothimThread()
  { if (threadRunning) return;
    threadKill = false;
    threadRunning = true;
    threadPause = true; 
    
    GenerationThread worker = new GenerationThread();
    worker.start();
  }
  

  public void lastGeneration()
  { lastGenToggle = !lastGenToggle;
    if (lastGenToggle) renderImage(geneticPanel, lastDNA);
    else               renderImage(geneticPanel, currentDNA);

    double fitness = GeneticImagePanel.calculateFitness(sourceImage, geneticImage);
    controlPanel.setFitness(fitness);
    geneticPanel.repaint();
  }
  

 

  public void renderImage(GeneticImagePanel panel, DNA dna)
  {
    int n = dna.getTriangleCount();
    
    //Whenever possible, you want to avoid calling new within loops.
    //Thus, they are created before the loop and just set within the loop.
    int[] xCoor = new int[3];
    int[] yCoor = new int[3];
    
    panel.clear();
    if (amimate) panel.repaint();
    for (int i=0; i<n; i++)
    { 
      //int[] GENE, declares an array pointer, but does not call new
      //Therefore, it is fine to declare within the loop.
      int[] GENE = dna.getTriangleGenes(i);
      Color myColor = new Color(GENE[DNA.GENE_ALPHA], GENE[DNA.GENE_RED],
                                GENE[DNA.GENE_GREEN], GENE[DNA.GENE_BLUE]);
 
      //System.out.println("["+GENE[DNA.GENE_ALPHA]+", "+GENE[DNA.GENE_RED]+", "+GENE[DNA.GENE_GREEN]+"]");
      xCoor[0] = GENE[DNA.GENE_X1];
      xCoor[1] = GENE[DNA.GENE_X2];
      xCoor[2] = GENE[DNA.GENE_X3];
      
      yCoor[0] = GENE[DNA.GENE_Y1];
      yCoor[1] = GENE[DNA.GENE_Y2];
      yCoor[2] = GENE[DNA.GENE_Y3];
      
      panel.graphics_buf.setColor(myColor);
      panel.graphics_buf.fillPolygon(xCoor, yCoor, 3);
      
      if (amimate) //also draw directly to the screen buffer
      { panel.repaint();
        { try 
          { int delay = controlPanel.getMilliSecDelay();
            Thread.sleep(delay); 
          }
          catch (InterruptedException e) { }
        }
      }
      
    }
  }
  
  
  class GenerationThread extends Thread
  { public void run()
    { //In this program, the starting thread message should always be followed 
      //by an ending thread message before another starting thread message
      //is printed.
      threadID++;
      System.out.println("===> Starting GA thread with ID: "+ threadID);
      while(!threadKill)
      { copyDNA(lastDNA, currentDNA);
        currentDNA = ga_randomTriangles.nextGeneration();
        checkPause();
        renderImage(geneticPanel, currentDNA);
        checkPause();
        double fitness = GeneticImagePanel.calculateFitness(sourceImage, geneticImage);
        controlPanel.setFitness(fitness); 
        geneticPanel.repaint();
        
        threadNext = false; //After repaint(), one generation has been
                       //completed. Thus, it is safe to set threadNext false.
        checkPause();
        try 
        { int delay = controlPanel.getMilliSecDelay();
          Thread.sleep(delay); 
        }
        catch (InterruptedException e) { }
      }
      threadRunning = false;
      System.out.println("<=== Ending GA thread with ID: "+ threadID);
    }
    
    private void checkPause()
    { while (threadPause && !threadNext && !threadKill)
      { try { Thread.sleep(500); }
        catch (InterruptedException e) { }
      }
      Thread.yield();
    }
  }

  
  
  public static void main(String[] argv)
  { new GeneticImageFrame();
  }
  
  
  
  
}