/// Evolver Copyright 2000, Morgan McGuire
/// matrix@alum.mit.edu   http://graphics3d.com/matrix
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.net.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

/**
 * The applet/application.  Run with 'java Evolver'.
 */
public class Evolver extends JApplet
{  

    public static final Color backgroundColor = new Color(181, 181, 181);

    /**
     * A pointer to the global instance of this object.
     */
    public static Evolver instance = null;
  
    /**
     * Panel showing the current generation.
     */
    JPanel generationDisplay;

    /**
     * Displays for the current generation.
     */
    BehaviorDisplay behaviorDisplay[];

    /**
     * Display for text on the bottom.
     */
    JTextArea textDisplay;

    /**
     * The current generation.
     */
    Behavior behavior[];
  
    /**
     * The number of behaviors in the generation.
     */
    int numBehaviors;
  
    /**
     * Width of the thumbnails
     */
    int frameWidth = 96;
    int frameHeight = 96;

    /** Display LEDs */
    LED power;

    /** Display LEDs */
    LED status;

    /** The timer that blinks the status light */
    Timer statusBlink;

    JFrame geneticConsoleWindow;
    GeneticParameters geneticParameters;

    JFrame behaviorViewerWindow;
    BehaviorViewer behaviorViewer;

    JFrame morphViewerWindow;
    MorphViewer morphViewer;

    // Keeps track of the number of calls to beginWait that have been
    // made.
    int waiting = 0;

    public static void main(String s[]) {
	System.out.println("recommended command line is: 'java -Xincgc -Xmx100M Evolver'");
	System.out.print("Loading Java...");

	JFrame frame = new JFrame("Evolver");
	frame.setIconImage(new ImageIcon("icon.jpg").getImage());
	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	System.out.println("done");

	Evolver evolver = new Evolver();
	frame.setContentPane(evolver);
	frame.pack();
	frame.setVisible(true);
    }
  
    public Evolver()
    {
	instance = this;
	geneticParameters = new GeneticParameters(".");
	createGUI();
	createConsole();
	createBehaviorViewer();
	createMorphViewer();
	AnimationEditor animationEditor = new AnimationEditor();
	animationEditor.setVisible(true);
	System.out.println("done constructor");
    }

    private void createMorphViewer()
    {
	morphViewer = new MorphViewer();
	morphViewerWindow = new JFrame("Morph Viewer");
	morphViewerWindow.setIconImage(new ImageIcon("morph-icon.jpg").getImage());    
	morphViewerWindow.setContentPane(morphViewer);
	morphViewerWindow.pack();
	morphViewerWindow.setVisible(true);
    }

    private void createBehaviorViewer()
    {
	behaviorViewer = new BehaviorViewer();
	behaviorViewerWindow = new JFrame("Behavior Viewer");
	behaviorViewerWindow.setIconImage(new ImageIcon("bv-icon.jpg").getImage());    
	behaviorViewerWindow.setContentPane(behaviorViewer);
	behaviorViewerWindow.pack();
	behaviorViewerWindow.setVisible(true);
    }

    private void createGUI()
    {
	System.out.print("Loading Evolver...");
	setBackground(backgroundColor);

	// The window pane
	Container pane = getContentPane();

	// The actual contents
	JPanel panel = new FatBorderPanel("file:lb", ".gif", new java.awt.Color(181,181,181));

	GridBagLayout gridBag = new GridBagLayout();
	GridBagConstraints constraints = new GridBagConstraints();

	panel.setLayout(gridBag);
	pane.add(panel);

	Component evolverLogo = new JLabel(new ImageIcon("evolver.gif"));

	textDisplay = new JTextArea("Welcome to Evolver");
	// Make lines wrap, at whitespace boundaries
	textDisplay.setLineWrap(true);
	textDisplay.setWrapStyleWord(true);

	textDisplay.setFont(new Font("Arial", Font.PLAIN, 16));
	textDisplay.setForeground(new java.awt.Color(61,61,61));
	textDisplay.setEditable(false);
	textDisplay.setBackground(new java.awt.Color(153, 196,167));
	textDisplay.setMargin(new Insets(14,14,14,14));
	textDisplay.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));

	JButton evolveButton = new ImageButton("file:sevolve-up.jpg", "file:sevolve-down.jpg");
	evolveButton.addActionListener(new ActionListener() {
	    public void actionPerformed(ActionEvent e) 
		{
		    breed();
		}
	});

	evolveButton.setToolTipText("Evolve a new generation of behaviors.");

	power = new LED("sgled", ".jpg", LED.ON);
	status = new LED("syled", ".jpg", LED.OFF);
	statusBlink = new javax.swing.Timer(250, new ActionListener() {
	    public void actionPerformed(ActionEvent e) {
		status.setState(! status.getState());
	    }});

	Container LEDPanel = new JPanel(new GridLayout(1,2));
	LEDPanel.add(power);
	LEDPanel.add(status);

	constraints.weightx = 1.0;
	constraints.weighty = 1.0;
	constraints.anchor = GridBagConstraints.NORTHWEST;
	constraints.gridwidth = 1;
	gridBag.setConstraints(LEDPanel, constraints);
	panel.add(LEDPanel);

	JButton resetButton = new ImageButton("file:reset-up.gif", "file:reset-dn.gif");
	resetButton.setToolTipText("Create a completely random generation.");
	resetButton.addActionListener(new ActionListener() {
	    public void actionPerformed(ActionEvent e)  {
		reset();
	    }});

	constraints.gridwidth = GridBagConstraints.RELATIVE;
	constraints.anchor = GridBagConstraints.NORTH;
	gridBag.setConstraints(resetButton, constraints);
	panel.add(resetButton);
    
	constraints.weightx = 1.0;
	constraints.gridwidth = GridBagConstraints.REMAINDER;
	constraints.anchor = GridBagConstraints.NORTHEAST;
	gridBag.setConstraints(evolverLogo, constraints);
	panel.add(evolverLogo);

	constraints.anchor = GridBagConstraints.CENTER;
	generationDisplay = new JPanel();
	gridBag.setConstraints(generationDisplay, constraints);
	panel.add(generationDisplay);
        
	constraints.insets = new Insets(5,0,5,5);
	constraints.gridwidth = GridBagConstraints.RELATIVE;
	constraints.anchor = GridBagConstraints.SOUTHWEST;
	constraints.weightx = 2;
	constraints.fill = GridBagConstraints.BOTH;
	gridBag.setConstraints(textDisplay, constraints);
	panel.add(textDisplay);

	constraints.insets = new Insets(0,0,0,0);
	constraints.fill = GridBagConstraints.NONE;
	constraints.gridwidth = GridBagConstraints.REMAINDER;
	constraints.anchor = GridBagConstraints.SOUTHEAST;
	constraints.weightx = 0;
	gridBag.setConstraints(evolveButton, constraints);
	panel.add(evolveButton);

	setNumBehaviors(16);
	System.out.println("done");

	startBlinkingStatus();
    }

    synchronized public void beginWaiting() 
    {
	waiting++;

	if (waiting > 0) {
	    setCursor(new Cursor(Cursor.WAIT_CURSOR));
	}
    }

    synchronized public void endWaiting() 
    {
	waiting--;

	if (waiting <= 0) {
	    setCursor(null);
	}
    }

    /**
     * Produces a new generation by breeding selected members of the
     * current generation.
     */
    public void breed()
    {
	Behavior parent[] = new Behavior[behaviorDisplay.length];
	Behavior child[] = new Behavior[behaviorDisplay.length];

	int numParents = 0;
	for(int b = 0; b < behaviorDisplay.length; b++) {
	    if (behaviorDisplay[b].isSelected()) {
		parent[numParents] = behavior[b];
		numParents++;
	    }
	    behaviorDisplay[b].setSelected(false);
	}

	if (numParents == 0) {
	    System.err.println("No behaviors selected for mating");
	    return;
	}

	beginWaiting();

	int numChildren = 0;
	while (numChildren < behavior.length) {
	    System.out.print(".");
	    // Choose two parents (ok if they are the same)
	    int parentIndex1 = (int)(Math.random() * numParents);
	    int parentIndex2 = (int)(Math.random() * numParents);

	    Behavior parent1 = parent[parentIndex1];
	    Behavior parent2 = parent[parentIndex2];

	    ELExpression parentExpression1 = parent1.expression;
	    ELExpression parentExpression2 = parent2.expression;

	    // Breed
	    ELExpression childExpression = geneticParameters.mate(parentExpression1, parentExpression2);

	    // Evaluate the fitness of the child
	    Behavior childBehavior = 
		new Behavior(childExpression, frameWidth, frameHeight,
			     (int)Math.max(parent1.numGenerations, parent2.numGenerations));
      
	    boolean boring = false;

	    // Compare this child to other children so far.  It is boring if
	    // it is the same as a child we already have.
	    for(int c = 0; c < numChildren; c++) {
		if (childBehavior.sameExpression(child[c])) {
		    boring = true;
		    break;
		}
	    }
      
	    boring = boring || childBehavior.isBoring();

	    // Add this child if it is not boring.
	    if (! boring) {
		child[numChildren] = childBehavior;
		numChildren++;
	    }
	}

	// Replace the previous generation
	behavior = child;

	// Force repaints
	for(int b = 0; b < behaviorDisplay.length; b++) {
	    behaviorDisplay[b].setBehavior(behavior[b]);
	    behaviorDisplay[b].setSelected(false);
	}

	System.out.println();
	endWaiting();
    }
 
    /**
     * Produce a new generation from scratch.
     */
    public void reset()
    {
	System.out.print("Creating random generation");
	beginWaiting();
	setCursor(new Cursor(Cursor.WAIT_CURSOR));
	for(int b = 0; b < behavior.length; b++) {
	    behavior[b].setRandomExpression(geneticParameters);
	    behaviorDisplay[b].setSelected(false);
	    behaviorDisplay[b].repaint();
	}
	endWaiting();
	System.out.println("done");
    }

    public void setMessageText(String t)
    {
	textDisplay.setText(t);
    }

    private void createConsole()
    {
	geneticConsoleWindow = new JFrame("Genetic Console");
	GeneticConsole console = new GeneticConsole(geneticParameters);
	geneticConsoleWindow.setIconImage(new ImageIcon("dna-icon.jpg").getImage());
	geneticConsoleWindow.setContentPane(console);
	geneticConsoleWindow.pack();
	geneticConsoleWindow.setVisible(true);
    }

    /**
     * Makes the status light blink.
     */
    private void startBlinkingStatus()
    {
	statusBlink.start();
	status.setState(LED.ON);
    }

    private void stopBlinkingStatus()
    {
	statusBlink.stop();
	status.setState(LED.OFF);
    }
  
    /**
     * Changes the number of behaviors in a generation.
     */
    public void setNumBehaviors(int numBehaviors)
    {
	this.numBehaviors = numBehaviors;

	// Clear any old behavior displays
	generationDisplay.removeAll();
	int n = (int)Math.max(1, Math.floor(Math.sqrt(numBehaviors)));
	int m = (int)Math.ceil(numBehaviors / (double)n);
	generationDisplay.setLayout(new GridLayout(n, m));
    
	behaviorDisplay = new BehaviorDisplay[numBehaviors];
	behavior = new Behavior[numBehaviors];
    
	for(int b = 0; b < numBehaviors; b++) {
	    behavior[b] = new Behavior(geneticParameters, frameWidth, frameHeight);
	    behaviorDisplay[b] = new BehaviorDisplay(behavior[b]);
	    generationDisplay.add(behaviorDisplay[b]);
	}
    }
}

class LED extends JLabel
{
    private ImageIcon onIcon = null;
    private ImageIcon offIcon = null;
    private boolean state;

    public static final boolean ON = true;
    public static final boolean OFF = false;

    public LED(String imageBase, String imageExtension, boolean state)
    {
	onIcon = new ImageIcon(imageBase + "-on" + imageExtension);
	offIcon = new ImageIcon(imageBase + "-off" + imageExtension);
	this.state = ! state;
	setState(state);    
    }

    public void setState(boolean state)
    {
	if (this.state == state) {
	    return;
	}

	this.state = state;
	if (state == ON) {
	    setIcon(onIcon);
	} else {
	    setIcon(offIcon);
	}
	repaint();
    }

    public boolean getState()
    {
	return state;
    }
}
