/*
 * Encog(tm) Core v3.0 - Java Version
 * http://www.heatonresearch.com/encog/
 * http://code.google.com/p/encog-java/
 
 * Copyright 2008-2011 Heaton Research, Inc.
 *
 * 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.
 *   
 * For more information on Heaton Research copyrights, licenses 
 * and trademarks visit:
 * http://www.heatonresearch.com/copyright
 */
package org.encog.ml.genetic.genome;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.encog.Encog;
import org.encog.ml.genetic.GeneticAlgorithm;
import org.encog.ml.genetic.GeneticError;
import org.encog.ml.genetic.population.Population;

/**
 * A basic abstract genome. Provides base functionality.
 */
public abstract class BasicGenome implements Genome, Serializable {

	/**
	 * Serial id.
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * The adjusted score.
	 */
	private double adjustedScore;

	/**
	 * The amount to spawn.
	 */
	private double amountToSpawn;

	/**
	 * The chromosomes for this gene.
	 */
	private final List<Chromosome> chromosomes = new ArrayList<Chromosome>();

	/**
	 * The genetic algorithm for this gene.
	 */
	private transient GeneticAlgorithm geneticAlgorithm;

	/**
	 * The genome id.
	 */
	private long genomeID;

	/**
	 * The organism generated by this gene.
	 */
	private transient Object organism;

	/**
	 * The score of this genome.
	 */
	private double score = 0;

	/**
	 * The population this genome belongs to.
	 */
	private Population population;

	/**
	 * @return The number of genes in this genome.
	 */
	@Override
	public final int calculateGeneCount() {
		int result = 0;

		// sum the genes in the chromosomes.
		for (final Chromosome chromosome : this.chromosomes) {
			result += chromosome.getGenes().size();
		}
		return result;
	}
	
	public boolean equals(final Genome other) {
		if( other==this )
			return true;
		else {
			return Math.abs(other.getScore()-getScore())<Encog.DEFAULT_DOUBLE_EQUAL;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public final int compareTo(final Genome other) {

		if( equals(other) ) {
			return 0;
		} else if (this.geneticAlgorithm.getCalculateScore().shouldMinimize()) {
			if (getScore() > other.getScore()) {
				return 1;
			}
			return -1;
		} else {
			if (getScore() > other.getScore()) {
				return -1;
			}
			return 1;
		}
	}

	/**
	 * @return The adjusted score, which considers bonuses.
	 */
	@Override
	public final double getAdjustedScore() {
		return this.adjustedScore;
	}

	/**
	 * @return The amount this genome will spawn.
	 */
	@Override
	public final double getAmountToSpawn() {
		return this.amountToSpawn;
	}

	/**
	 * @return The number of chromosomes.
	 */
	@Override
	public final List<Chromosome> getChromosomes() {
		return this.chromosomes;
	}

	/**
	 * @return The genetic algorithm.
	 */
	@Override
	public final GeneticAlgorithm getGeneticAlgorithm() {
		return this.geneticAlgorithm;
	}

	/**
	 * @return The genome id.
	 */
	@Override
	public final long getGenomeID() {
		return this.genomeID;
	}

	/**
	 * @return The organism produced.
	 */
	@Override
	public final Object getOrganism() {
		return this.organism;
	}

	/**
	 * @return the population
	 */
	@Override
	public final Population getPopulation() {
		return this.population;
	}

	/**
	 * @return The score.
	 */
	@Override
	public final double getScore() {
		return this.score;
	}

	/**
	 * Mate two genomes. Will loop over all chromosomes.
	 * 
	 * @param father
	 *            The father.
	 * @param child1
	 *            The first child.
	 * @param child2
	 *            The second child.
	 */
	@Override
	public final void mate(final Genome father, final Genome child1,
			final Genome child2) {
		final int motherChromosomes = getChromosomes().size();
		final int fatherChromosomes = father.getChromosomes().size();

		if (motherChromosomes != fatherChromosomes) {
			throw new GeneticError(
					"Mother and father must have same chromosome count, Mother:"
							+ motherChromosomes + ",Father:"
							+ fatherChromosomes);
		}

		for (int i = 0; i < fatherChromosomes; i++) {
			final Chromosome motherChromosome = this.chromosomes.get(i);
			final Chromosome fatherChromosome = father.getChromosomes().get(i);
			final Chromosome offspring1Chromosome = child1.getChromosomes()
					.get(i);
			final Chromosome offspring2Chromosome = child2.getChromosomes()
					.get(i);

			this.geneticAlgorithm.getCrossover().mate(motherChromosome,
					fatherChromosome, offspring1Chromosome,
					offspring2Chromosome);

			if (Math.random() < this.geneticAlgorithm.getMutationPercent()) {
				this.geneticAlgorithm.getMutate().performMutation(
						offspring1Chromosome);
			}

			if (Math.random() < this.geneticAlgorithm.getMutationPercent()) {
				this.geneticAlgorithm.getMutate().performMutation(
						offspring2Chromosome);
			}
		}

		child1.decode();
		child2.decode();
		this.geneticAlgorithm.calculateScore(child1);
		this.geneticAlgorithm.calculateScore(child2);
	}

	/**
	 * Set the adjusted score.
	 * 
	 * @param theAdjustedScore
	 *            The score.
	 */
	@Override
	public final void setAdjustedScore(final double theAdjustedScore) {
		this.adjustedScore = theAdjustedScore;
	}

	/**
	 * Set the amount to spawn.
	 * 
	 * @param theAmountToSpawn
	 *            The amount to spawn.
	 */
	@Override
	public final void setAmountToSpawn(final double theAmountToSpawn) {
		this.amountToSpawn = theAmountToSpawn;
	}

	/**
	 * Set the genetic algorithm to use.
	 * 
	 * @param ga
	 *            The genetic algorithm to use.
	 */
	@Override
	public final void setGeneticAlgorithm(final GeneticAlgorithm ga) {
		this.geneticAlgorithm = ga;
	}

	/**
	 * Set the genome id.
	 * 
	 * @param theGenomeID
	 *            the genome id.
	 */
	@Override
	public final void setGenomeID(final long theGenomeID) {
		this.genomeID = theGenomeID;
	}

	/**
	 * Set the organism.
	 * 
	 * @param theOrganism
	 *            The organism.
	 */
	public final void setOrganism(final Object theOrganism) {
		this.organism = theOrganism;
	}

	/**
	 * @param thePopulation
	 *            the population to set
	 */
	@Override
	public final void setPopulation(final Population thePopulation) {
		this.population = thePopulation;
	}

	/**
	 * Set the score.
	 * 
	 * @param theScore
	 *            Set the score.
	 */
	@Override
	public final void setScore(final double theScore) {
		this.score = theScore;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public final String toString() {
		final StringBuilder builder = new StringBuilder();
		builder.append("[");
		builder.append(this.getClass().getSimpleName());
		builder.append(": score=");
		builder.append(getScore());
		return builder.toString();
	}

}
