//=============================================================================
// Copyright 2006-2010 Daniel W. Dyer
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//=============================================================================
package org.uncommons.watchmaker.examples.biomorphs;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SpringLayout;
import javax.swing.SwingUtilities;
import org.uncommons.maths.random.MersenneTwisterRNG;
import org.uncommons.maths.random.Probability;
import org.uncommons.swing.SpringUtilities;
import org.uncommons.swing.SwingBackgroundTask;
import org.uncommons.watchmaker.examples.AbstractExampleApplet;
import org.uncommons.watchmaker.framework.EvolutionEngine;
import org.uncommons.watchmaker.framework.EvolutionObserver;
import org.uncommons.watchmaker.framework.EvolutionaryOperator;
import org.uncommons.watchmaker.framework.GenerationalEvolutionEngine;
import org.uncommons.watchmaker.framework.PopulationData;
import org.uncommons.watchmaker.framework.interactive.InteractiveSelection;
import org.uncommons.watchmaker.framework.interactive.Renderer;
import org.uncommons.watchmaker.framework.termination.GenerationCount;
import org.uncommons.watchmaker.swing.SwingConsole;

/**
 * Watchmaker Framework implementation of Dawkin's biomorph program. 
 * @author Daniel Dyer
 */
public class BiomorphApplet extends AbstractExampleApplet
{
    private Renderer<Biomorph, JComponent> renderer;
    private SwingConsole console;
    private JDialog selectionDialog;
    private JPanel biomorphHolder;


    /**
     * Initialise and layout the GUI.
     * @param container The Swing component that will contain the GUI controls.
     */
    @Override
    protected void prepareGUI(Container container)
    {
        renderer = new SwingBiomorphRenderer();
        console = new SwingConsole(5);
        selectionDialog = new JDialog((JFrame) null, "Biomorph Selection", true);
        biomorphHolder = new JPanel(new GridLayout(1, 1));

        container.add(new ControlPanel(), BorderLayout.WEST);
        container.add(biomorphHolder, BorderLayout.CENTER);
        biomorphHolder.setBorder(BorderFactory.createTitledBorder("Last Evolved Biomorph"));
        biomorphHolder.add(new JLabel("Nothing generated yet.", JLabel.CENTER));
        selectionDialog.add(console, BorderLayout.CENTER);
        selectionDialog.setSize(800, 600);
        selectionDialog.validate();
    }


    /**
     * Helper method to create a background task for running the interactive evolutionary
     * algorithm.
     * @param populationSize How big the population used by the created evolution engine
     * should be.
     * @param generationCount How many generations to use when the evolution engine is
     * invoked.
     * @param random If true use random mutation, otherwise use Dawkins mutation.
     * @return A Swing task that will execute on a background thread and update
     * the GUI when it is done.
     */
    private SwingBackgroundTask<Biomorph> createTask(final int populationSize,
                                                     final int generationCount,
                                                     final boolean random)
    {
        return new SwingBackgroundTask<Biomorph>()
        {
            @Override
            protected Biomorph performTask()
            {
                EvolutionaryOperator<Biomorph> mutation = random ? new RandomBiomorphMutation(new Probability(0.12d))
                                                                 : new DawkinsBiomorphMutation();
                InteractiveSelection<Biomorph> selection = new InteractiveSelection<Biomorph>(console,
                                                                                              renderer,
                                                                                              populationSize,
                                                                                              1);
                EvolutionEngine<Biomorph> engine = new GenerationalEvolutionEngine<Biomorph>(new BiomorphFactory(),
                                                                                             mutation,
                                                                                             selection,
                                                                                             new MersenneTwisterRNG());
                engine.addEvolutionObserver(new GenerationTracker());
                return engine.evolve(populationSize,
                                     0,
                                     new GenerationCount(generationCount));
            }

            @Override
            protected void postProcessing(Biomorph result)
            {
                selectionDialog.setVisible(false);
                biomorphHolder.removeAll();
                biomorphHolder.add(renderer.render(result));
                biomorphHolder.revalidate();
            }
        };
    }


    /**
     * Entry point for running this example as an application rather than an applet.
     * @param args Program arguments (ignored).
     */
    public static void main(String[] args)
    {
        new BiomorphApplet().displayInFrame("Watchmaker Framework - Biomporphs Example");
    }


    /**
     * Simple observer to update the dialog title every time the evolution advances
     * to a new generation.
     */
    private final class GenerationTracker implements EvolutionObserver<Biomorph>
    {
        public void populationUpdate(final PopulationData<? extends Biomorph> populationData)
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    selectionDialog.setTitle("Biomorph Selection - Generation "
                                             + (populationData.getGenerationNumber() + 1));
                }
            });
        }
    }


    /**
     * Panel for controlling the evolutionary algorithm parameters.
     */
    private final class ControlPanel extends JPanel
    {
        private JSpinner populationSpinner;
        private JSpinner generationsSpinner;
        private JComboBox mutationCombo;

        ControlPanel()
        {
            super(new BorderLayout());
            add(createInputPanel(), BorderLayout.NORTH);
            add(createButtonPanel(), BorderLayout.SOUTH);
            setBorder(BorderFactory.createTitledBorder("Evolution Controls"));
        }


        private JComponent createInputPanel()
        {
            JPanel inputPanel = new JPanel(new SpringLayout());
            JLabel populationLabel = new JLabel("Population Size: ");
            populationSpinner = new JSpinner(new SpinnerNumberModel(18, 2, 25, 1));
            populationSpinner.setEnabled(false);
            populationLabel.setLabelFor(populationSpinner);
            inputPanel.add(populationLabel);
            inputPanel.add(populationSpinner);
            JLabel generationsLabel = new JLabel("Number of Generations: ");
            generationsSpinner = new JSpinner(new SpinnerNumberModel(20, 1, 100, 1));
            generationsLabel.setLabelFor(generationsSpinner);
            inputPanel.add(generationsLabel);
            inputPanel.add(generationsSpinner);
            JLabel mutationLabel = new JLabel("Mutation Type: ");
            mutationCombo = new JComboBox(new String[]{"Dawkins (Non-random)", "Random"});
            mutationCombo.addItemListener(new ItemListener()
            {
                public void itemStateChanged(ItemEvent itemEvent)
                {
                    if (mutationCombo.getSelectedIndex() == 0)
                    {
                        populationSpinner.setValue(18);
                        populationSpinner.setEnabled(false);
                    }
                    else
                    {
                        populationSpinner.setEnabled(true);
                    }
                }
            });
            inputPanel.add(mutationLabel);
            inputPanel.add(mutationCombo);

            SpringUtilities.makeCompactGrid(inputPanel, 3, 2, 30, 6, 6, 6);

            return inputPanel;
        }


        private JComponent createButtonPanel()
        {
            JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
            JButton startButton = new JButton("Start");
            startButton.addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent actionEvent)
                {
                    createTask((Integer) populationSpinner.getValue(),
                               (Integer) generationsSpinner.getValue(),
                               mutationCombo.getSelectedIndex() == 1).execute();
                    selectionDialog.setVisible(true);
                }
            });
            buttonPanel.add(startButton);
            return buttonPanel;
        }
    }
}
