/* CVS: $Id: Evolution.java,v 1.33 2001/03/19 10:29:31 gvijf Exp $ */

package evolution;

import java.util.*;

import evolution.events.*;
import evolution.resources.*;

/**
 * Represent evolution (time).
 * @stereotype singleton
 */
public class Evolution {

    /**
     * The clock as a seperate thread.
     */
    class ClockThread extends Thread {
        public void run() {
            while (true) {
                try {
                    sleep((long) (1000.0 * factor));
                } catch (InterruptedException e) {
                }
            	if(isRunning()) {
                	//System.err.println();
                	tick();
            	}
            }
        }
    }

    /**
     * Initialize the instance of this singleton.
     */
    public static void initialize() {
        instance = new Evolution();
    }

    /**
     * Get the singleton instance.
     * Precondition: Evolution.initialize() must have been called.
     */
    public static Evolution getInst() {
        return instance;
    }

    /**
     * Construct an Evolution object.
     * The new object will be in pause mode.
     */
    protected Evolution() {
        clock = new ClockThread();
      	clock.start();
    	pause();
    }

    /**
     * Pause the evolution.
     */
    public void pause() {
        running = false;
    }

    /**
     * Resume the evolution.
     */
    public void resume() {
        running = true;
    }

    /**
     * Register an Evolver.
     * Every time the clock ticks evolve() will be called on the Evolver.
     */
    public synchronized void register(Evolver e) {
        if(e.producesEnergy()) energyProducers.add(e);
        else energyConsumers.add(e);

    }

    /**
     * Deregister an Evolver.
     */
    public synchronized void deregister(Evolver e) {
        energyProducers.remove(e);
        energyConsumers.remove(e);

    }

    /**
     * Is evolution running?
     */
    public boolean isRunning() {
        return running;
    }

    /**
     * Get the time factor.
     */
    public double getTimeFactor() {
        return factor;
    }

    /**
     * Get the list of evolvers.
     */
    public List getEvolverList() {
        energyProducers.addAll(energyConsumers);
        return energyProducers;
    }

    /**
     * Set the time factor.
     */
    public void setTimeFactor(double factor) {
        this.factor = factor;
    }

    /**
     * Calculate the difference of two (String, Double) maps.
     * Returns a (String, Double) Map.
     */
    protected Map difference(Map m1, Map m2) {
        Map result = new HashMap();
        Iterator it = m1.keySet().iterator();
        while(it.hasNext()) {
            String name = (String) it.next();
            Double value = new Double(((Double) m1.get(name)).doubleValue() -
                ((Double) m2.get(name)).doubleValue());
            result.put(name, value);
        }
        return result;
    }

    /**
     * This method is called on regular basis when running.
     */
    protected synchronized void tick() {
        ResourceKnowledgeCatalog rKc = ResourceKnowledgeCatalog.getInst();
        // step 0:
        // sort the producers and consumers according to their priority
        List energyP = new ArrayList(energyProducers);
        List energyC = new ArrayList(energyConsumers);
        Collections.sort(energyP, new PriorityComparator());
        Collections.sort(energyC, new PriorityComparator());
        // step 1:
        // reset all temporary resources
        rKc.resetTemporaries();
        try {
            rKc.getResource("Energy").setValue(0);
        } catch(Exception e) {
        }
        // step 2:
        // create 2 maps,
        //  * one with the current resource allocation: leftOvers
        //  * one with the maximum resource production: maxProduction
        // these maps will be initialized with the current values
        // of the world resources
        Map leftOvers = rKc.getResourcesStringDoubleMap();
        Map maxProduction = rKc.getResourcesStringDoubleMap();
        //System.err.println("\ncurrent = " + leftOvers + " " + maxProduction);
        // step 3:
        // let the producers dummy produce the maximum possible and
        // so calculate the maximum energy production
        //System.err.println("We have " + energyP.size() + " producers");
        Iterator producers = energyP.iterator();
        while(producers.hasNext()) {
            Evolver e = (Evolver) producers.next();
            e.maxEnergyProduction(maxProduction, leftOvers);
        }
        //System.err.println("maxProduction = " + leftOvers + " " + maxProduction);
        double maxEnergy = ((Double) maxProduction.get("Energy")).doubleValue();
        //System.err.println("Maximum energy production = " + maxEnergy);
        // step 3b:
        // calculate the used resoruces for the producers
        // copy the leftOvers to world
        Map usedResources = difference(maxProduction, leftOvers);
        //System.err.println("Producers used to max = " + usedResources);
        //System.err.println("Setting world to = " + leftOvers);
        rKc.setResources(leftOvers);
        // step 4:
        // let the consumers consume (for 100%)
        Iterator consumers = energyC.iterator();
        while(consumers.hasNext()) {
            Evolver e = (Evolver) consumers.next();
            e.evolve(1);
        }
        //System.err.println("Consumers left = " + rKc.getResourcesStringDoubleMap());
        // step 5:
        // see how much energy was needed
        double usedEnergy = maxEnergy - rKc.getResource("Energy").getValue();
        //System.err.println("Energy used = " + usedEnergy);
        double productionPercentage;
        if((maxEnergy == 0) || (usedEnergy == 0)) productionPercentage = 0;
        else productionPercentage = 1 - (maxEnergy - usedEnergy) / maxEnergy;
        //System.err.println("Production percentage = " + productionPercentage);
        // step 6:
        // adjust the world resources so the producers can now evolve
        rKc.resetTemporaries();
        try {
            rKc.getResource("Energy").setValue(0);
        } catch(Exception e) {
        }
        //rKc.addResources(usedResources, productionPercentage);
        //System.err.println("Added the needed for the producers = " + rKc.getResourcesStringDoubleMap());
        // step 7:
        // let the producers evolve for productionPercentage percentage
        producers = energyP.iterator();
        while(producers.hasNext()) {
            Evolver e = (Evolver) producers.next();
            e.evolve(productionPercentage);
        }
        // step 8:
        // set the resources
        // the temporary resources have to be set, to reflect what
        // has been produced
        rKc.setTemporaries(maxProduction, productionPercentage);
        try {
            rKc.getResource("Max Energy").setValue(maxEnergy);
        } catch(Exception e) {
        }
        //System.err.println("Result = " + rKc.getResourcesStringDoubleMap());

        EventManager.getInst().signalEvent(new ClockTickEvt());
    }

    /**
     * For debugging.
     */
    class LivingBeingTester implements Evolver {
        public LivingBeingTester(String name, int ttl) {
            this.name = name;
            this.ttl = ttl;
            Evolution.getInst().register(this);
        }
        public void evolve(double value) {
            System.out.println(name + ": " + (--ttl));
            if(ttl <= 0) {
                Evolution.getInst().deregister(this);
                System.out.println(name + " is dead");
            }
        }
        public double adjust(Map maximumProduction, Map leftOvers) {
        return 0;
    	}
        public boolean producesEnergy() { return false; }
        public double maxEnergyProduction(Map m1, Map m2) { return 0; }
        public double getPriority() { return 0; }
        private String name;
        private int ttl;
    }

    /**
     * For debugging.
     */
    protected void test(int ttl) {
        for(int i = 0; i < 5; i++) {
            new LivingBeingTester("Being" + i + "(" + ttl + ")", ttl);
        }
    }

    /**
     * For debugging.
     */
    public static void main(String args[]) throws InterruptedException {
        Evolution.initialize();
        Evolution.getInst().test(2);
        Evolution.getInst().test(4);
        Evolution.getInst().test(6);
        Evolution.getInst().test(8);
        Evolution.getInst().resume();
        System.out.println("Started");
        Thread.currentThread().sleep(8000);
        System.out.println("Stopped");
        System.exit(0);
    }

    /**
     * Factor influences the speed of evolution.
     * Increasing the value of this factor, decreases the speed of the
     * evolution of the game
     */
    private double factor = 0.8;

    /**
     * A list of evolvers that produce energy.
     */
    private List energyProducers = new ArrayList();

    /**
     * A list of evolvers that can't produce energy.
     */
    private List energyConsumers = new ArrayList();

    /**
     * The clock thread.
     */
    private ClockThread clock;

    /**
     * Is the clock running?
     */
    private boolean running = false;

    /**
     * The singleton instance.
     */
    private static Evolution instance = null;

}
