/// 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)
            // Choose two parents (ok if they are the same)
            int parentIndex1 = (int)Math.round((Math.random() * numParents));
            int parentIndex2 = (int)Math.round((Math.random() * numParents));
            if (parentIndex1 == numParents) {
                parentIndex1 = numParents - 1;
            }
            if (parentIndex2 == numParents) {
                parentIndex2 = numParents - 1;
            }

            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;
    }
}
