package ac.essex.ooechs.lcs.test;

import ac.essex.ooechs.lcs.representation.Condition;
import ac.essex.ooechs.lcs.Classifier;

import java.util.Vector;

/**
 * The discovery component in XCS - a simple Genetic Algorithm which
 * runs crossover and mutation operations on rules in a match set.
 * <p/>
 * <p/>
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version,
 * provided that any use properly credits the author.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details at http://www.gnu.org
 * </p>
 *
 * @author Olly Oechsle, University of Essex, Date: 15-Jan-2008
 * @version 1.0
 */
public class GeneticAlgorithm_OLD {

    public static double predictionErrorReduction = 1.0;
    public static double fitnessReduction = 0.1;

    protected int executionCount = 0;

    protected XCS_OLD xcs;
    protected XCSParams_OLD settings;

    public GeneticAlgorithm_OLD(XCS_OLD xcs, XCSParams_OLD settings) {
        this.xcs = xcs;
        this.settings = settings;
    }

    /**
     * Selects two parents, performs crossover and mutation on them and adds the children
     * to the rule set. The GA is not run if the average age of the match set is too little.
     */
    public void runIfAverageAgeIsHighEnough(Vector<Classifier> matchSet, int currentTime) {

        // return if the match set is too small or not enough time has passed
        if (matchSet.size() == 0 || (currentTime - getAverageTimeStamp(matchSet)) < settings.theta)
            return;

        run(matchSet, currentTime);
    }

    /**
     * Selects two parents, performs crossover and mutation on them and adds the children
     * to the rule set.
     */    
    public void run(Vector<Classifier> matchSet, int currentTime) {

        executionCount++;

        // choose two parents
        Classifier parent1 = tournamentSelect(matchSet);
        Classifier parent2 = tournamentSelect(matchSet);

        Condition newConditions[];

        if (Math.random() < settings.pCrossover) {

            // create new conditions using crossover
            newConditions = parent1.condition.crossover(parent2.condition);

        } else {

            // no crossover
            newConditions = new Condition[2];
            newConditions[0] = parent1.condition.copy();
            newConditions[1] = parent2.condition.copy();

        }

        // perform mutation on the conditions according to pMutation probability
        newConditions[0].mutate(settings.pMutation);
        newConditions[1].mutate(settings.pMutation);

        // create two new rules
        Classifier child1 = new Classifier(newConditions[0], parent1.action, currentTime);
        Classifier child2 = new Classifier(newConditions[1], parent2.action, currentTime);

        // average the prediction error
        double e = predictionErrorReduction * (parent1.e + parent2.e) / 2;
        child1.e = e;
        child2.e = e;

        // average the fitness
        double fitness = fitnessReduction * (parent1.fitness + parent2.fitness) / 2;
        child1.fitness = fitness;
        child2.fitness = fitness;

        // addClassifier to the population
        xcs.add(child1);
        xcs.add(child2);

        //  reset the clocks on the match set
        for (int i = 0; i < matchSet.size(); i++) {
            Classifier classifier = matchSet.elementAt(i);
            classifier.timestamp = currentTime;
        }

    }

    /**
     * Selects a rule from the match set using tournament selection
     * with tournament size t=2
     */
    protected Classifier tournamentSelect(Vector<Classifier> matchSet) {

        // tournament size is 2
        Classifier competitor1 = getRandomRule(matchSet);
        Classifier competitor2 = getRandomRule(matchSet);

        if (competitor1.fitness > competitor2.fitness) return competitor1;
        else return competitor1;

    }

    /**
     * Selects a random rule from the match set
     */
    protected Classifier getRandomRule(Vector<Classifier> matchSet) {
        return matchSet.elementAt((int) Math.floor((Math.random() * matchSet.size())));
    }

    /**
     * Gets the average time stamp for all rules in the set.
     */
    protected double getAverageTimeStamp(Vector<Classifier> matchSet) {
        double total = 0;
        int totalRules = 0;
        for (int i = 0; i < matchSet.size(); i++) {
            Classifier classifier = matchSet.elementAt(i);
            total += (classifier.timestamp * classifier.numerosity);
            totalRules += classifier.numerosity;
        }
        return total / totalRules;
    }

}
