//=============================================================================
// 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.swing.evolutionmonitor;

import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import org.uncommons.watchmaker.framework.PopulationData;
import org.uncommons.watchmaker.framework.islands.IslandEvolutionObserver;

/**
 * Status bar component for the evolution monitor.  Can also be used separately to
 * provide basic status information without having to use the full evolution monitor.  
 * @author Daniel Dyer
 */
public class StatusBar extends Box implements IslandEvolutionObserver<Object>
{
    private final JLabel generationsLabel = new JLabel("N/A", JLabel.RIGHT);
    private final JLabel timeLabel = new JLabel("N/A", JLabel.RIGHT);
    private final JLabel populationLabel = new JLabel("N/A", JLabel.RIGHT);
    private final JLabel elitismLabel = new JLabel("N/A", JLabel.RIGHT);

    private final AtomicInteger islandPopulationSize = new AtomicInteger(-1);


    /**
     * Creates a status bar configured for non-island evolution.
     */
    public StatusBar()
    {
        this(false);
    }


    /**
     * @param islands Whether the status bar should be configured for updates from
     * {@link org.uncommons.watchmaker.framework.islands.IslandEvolution}.  Set this
     * parameter to false when using a standard {@link org.uncommons.watchmaker.framework.EvolutionEngine}
     */
    public StatusBar(boolean islands)
    {
        super(BoxLayout.X_AXIS);
        add(new JLabel("Population: "));
        add(populationLabel);
        add(createHorizontalStrut(15));
        add(new JLabel("Elitism: "));
        add(elitismLabel);
        add(createHorizontalStrut(15));
        add(new JLabel(islands ? "Epochs: " : "Generations: "));
        add(generationsLabel);
        add(createHorizontalStrut(15));
        add(new JLabel("Elapsed Time: "));
        add(timeLabel);
        setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));

        // Set component names for easy look-up from tests.
        populationLabel.setName("Population");
        elitismLabel.setName("Elitism");
        generationsLabel.setName("Generations");
        timeLabel.setName("Time");
    }


    /**
     * {@inheritDoc}
     */
    public void populationUpdate(final PopulationData<?> populationData)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                if (populationData.getGenerationNumber() == 0)
                {
                    int islandSize = islandPopulationSize.get();
                    if (islandSize > 0)
                    {
                        int islandCount = populationData.getPopulationSize() / islandSize;
                        populationLabel.setText(islandCount + "x" + islandSize);
                        elitismLabel.setText(islandCount + "x" + populationData.getEliteCount());
                    }
                    else
                    {
                        populationLabel.setText(String.valueOf(populationData.getPopulationSize()));
                        elitismLabel.setText(String.valueOf(populationData.getEliteCount()));
                    }
                }
                generationsLabel.setText(String.valueOf(populationData.getGenerationNumber() + 1));
                timeLabel.setText(formatTime(populationData.getElapsedTime()));
            }
        });
    }


    /**
     * {@inheritDoc}
     */
    public void islandPopulationUpdate(int islandIndex,
                                       final PopulationData<? extends Object> populationData)
    {
        islandPopulationSize.compareAndSet(-1, populationData.getPopulationSize());
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                timeLabel.setText(formatTime(populationData.getElapsedTime()));
            }
        });
    }


    private String formatTime(long time)
    {
        long seconds = time / 1000;
        long minutes = seconds / 60;
        seconds %= 60;
        long hours = minutes / 60;
        minutes %= 60;
        StringBuilder buffer = new StringBuilder();
        if (hours < 10)
        {
            buffer.append('0');
        }
        buffer.append(hours);
        buffer.append(':');
        if (minutes < 10)
        {
            buffer.append('0');
        }
        buffer.append(minutes);
        buffer.append(':');
        if (seconds < 10)
        {
            buffer.append('0');
        }
        buffer.append(seconds);
        return buffer.toString();
    }
}
