/*
 * Copyright 2007-2011 Tom Castle & Lawrence Beadle
 * Licensed under GNU Lesser General Public License
 * 
 * This file is part of EpochX: genetic programming software for research
 * 
 * EpochX is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * EpochX 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with EpochX. If not, see <http://www.gnu.org/licenses/>.
 * 
 * The latest version is available from: http://www.epochx.org
 */
package org.epochx.ge.representation;

import java.util.*;

import org.apache.commons.lang3.ObjectUtils;
import org.epochx.ge.model.GEModel;
import org.epochx.representation.CandidateProgram;
import org.epochx.tools.grammar.*;

/**
 * A <code>GECandidateProgram</code> encapsulates an individual program within a
 * generation of a GE run.
 * 
 * <p>
 * The main components of a GECandidateProgram are a series of codons, which are
 * the individual's genotype; a source string in someway derived from the codons
 * based upon a grammar, which is the individuals phenotype; and a fitness score
 * calculated by the model that is controlling execution.
 * 
 * <p>
 * To ensure caching can provide maximum performance, the source code and
 * fitness of a program should always be obtained directly from the program
 * instance itself, with a call to <code>getSourceCode()</code> or
 * <code>getFitness()</code>, rather than forcing the model or otherwise to
 * calculate them on each call.
 * 
 * <p>
 * Note: this class has a natural ordering that is inconsistent with equals.
 */
public class GECandidateProgram extends CandidateProgram {

	// The controlling model.
	private GEModel model;

	// The genotype.
	private List<Integer> codons;

	// How the codons looked when we last built the phenotype.
	private List<Integer> sourceCacheCodons;

	// How the codons looked when we last evaluated the program fitness.
	private List<Integer> fitnessCacheCodons;

	// The phenotype cache.
	private NonTerminalSymbol parseTree;

	// The fitness of the phenotype cache.
	private double fitness;

	// How many of the codons are actually used during mapping.
	private int noActiveCodons;

	/**
	 * Constructs a new program individual with an empty sequence of codons.
	 * 
	 * @param model the controlling model which provides the configuration
	 *        parameters for the run.
	 */
	public GECandidateProgram(final GEModel model) {
		this(new ArrayList<Integer>(), model);
	}

	/**
	 * Constructs a new program individual with the specified codons as the
	 * genotypic chromosome.
	 * 
	 * @param codons the program's initial genotypic chromosome.
	 * @param model the controlling model which provides the configuration
	 *        parameters for the run.
	 */
	public GECandidateProgram(final List<Integer> codons, final GEModel model) {
		this.codons = codons;
		this.model = model;

		parseTree = null;
		fitness = -1;

		sourceCacheCodons = null;
		fitnessCacheCodons = null;
	}

	/**
	 * Appends a codon generated with the model's codon generator to the end of
	 * the candidate program's chromosome. All caches will be cleared.
	 */
	public void appendNewCodon() {
		appendCodon(model.getCodonGenerator().getCodon());
	}

	/**
	 * Appends codons generated with the model's codon generator to the end of
	 * the candidate program's chromosome. All caches will be cleared.
	 * 
	 * @param no the number of codons to be generated and appended.
	 */
	public void appendNewCodons(final int no) {
		for (int i = 0; i < no; i++) {
			appendNewCodon();
		}
	}

	/**
	 * Inserts a new codon into the chromosome at the specified index. The
	 * codon currently at that index and all following codons will be shifted
	 * right. The new codon will be generated with the model's codon generator.
	 * All caches will be cleared.
	 * 
	 * @param index index at which the new codon should be inserted.
	 */
	public void insertNewCodon(final int index) {
		codons.add(index, model.getCodonGenerator().getCodon());
	}

	/**
	 * Removes the codon at the specified position in the chromosome. Any
	 * subsequent codons will be shifted left. Returns the codon that was
	 * removed from the chromosome. All caches will be cleared.
	 * 
	 * @param index the index of the codon to be removed.
	 * @return the codon that was removed from the chromosome.
	 */
	public int removeCodon(final int index) {
		return codons.remove(index);
	}

	/**
	 * Removes a sequence of codons from the chromosome, between the specified
	 * indexes. All indexes in the range <code>from &lt;= x &lt; to</code> will
	 * be removed. The removed codons will be returned in a list. All caches
	 * will be cleared.
	 * 
	 * @param from index from which codons will be removed, inclusive of this
	 *        index.
	 * @param to index up to which codons will be remove, exclusive of this
	 *        index.
	 * @return a list of the codons that were removed.
	 */
	public List<Integer> removeCodons(final int from, final int to) {
		final List<Integer> removed = new ArrayList<Integer>();
		for (int i = from; i < to; i++) {
			// As we remove them the list will shrink so we use from not i.
			removed.add(codons.remove(from));
		}

		return removed;
	}

	/**
	 * Appends the specified codon to the program's chromosome. Care should be
	 * taken when using this method as it allows the generation of codons in
	 * any manner regardless of the models response to getCodonGenerator(). All
	 * caches will be cleared.
	 * 
	 * @param newCodon the codon to append to the chromosome.
	 */
	public void appendCodon(final int newCodon) {
		codons.add(newCodon);
	}

	/**
	 * Appends multiple codons to the candidate program's chromosome. Care
	 * should be taken when using this method as it allows the generation
	 * of codons in any manner regardless of the models response to
	 * getCodonGenerator(). All caches will be cleared.
	 * 
	 * @param newCodons a list of new codons to append to the chromosome.
	 */
	public void appendCodons(final List<Integer> newCodons) {
		for (int i = 0; i < newCodons.size(); i++) {
			appendCodon(newCodons.get(i));
		}
	}

	/**
	 * Replaces the codon at the specified index with the given codon. All
	 * caches will be cleared.
	 * 
	 * @param index index of the codon to be replaced.
	 * @param newCodon codon to be stored at the specified position.
	 */
	public void setCodon(final int index, final int newCodon) {
		codons.set(index, model.getCodonGenerator().getCodon());
	}

	/**
	 * Sets each codon from the startIndex. If the number of codons takes the
	 * indexes beyond the length of the current codons then the new codons will
	 * be added to the end of the list of codons. However if the first index is
	 * greater than the length of the current set of codons then an
	 * ArrayIndexOutOfBoundsException will be thrown. All caches will be
	 * cleared.
	 * 
	 * @param startIndex the index from which the codons should be replaced or
	 *        otherwise set.
	 * @param newCodons the codons that should be stored at the specified
	 *        positions.
	 * @throws ArrayIndexOutOfBoundsException If the first index is beyond the
	 *         end of the current list of codons.
	 */
	public void setCodons(final int startIndex, final List<Integer> newCodons) {
		// If more than one beyond the end of current codons.
		if (startIndex > codons.size()) {
			throw new ArrayIndexOutOfBoundsException(startIndex);
		}

		for (int i = 0; i < newCodons.size(); i++) {
			final int c = newCodons.get(i);
			if (i + startIndex < codons.size()) {
				codons.set(i + startIndex, c);
			} else {
				appendCodon(c);
			}
		}
	}

	/**
	 * Returns the codon at the specified index.
	 * 
	 * @param index the index at which the codon should be retrieved, between 0
	 *        and getNoCodons()-1.
	 * @return the integer codon found at the specified index.
	 */
	public int getCodon(final int index) {
		return codons.get(index);
	}

	/**
	 * Returns a sequence of codons from the specified <tt>from</tt> index,
	 * inclusive, to the <tt>to</tt> index, exclusive. An
	 * IndexOutOfBoundsException will be thrown if either of the indexes is
	 * outside the range of possible indexes.
	 * 
	 * @param from index from which codons should be returned, inclusive.
	 * @param to index, up to which codons should be returned, exclusive.
	 * @return a list of the codons between the specified indexes.
	 */
	public List<Integer> getCodons(final int from, final int to) {
		return codons.subList(from, to);
	}

	/**
	 * Returns a copy of the full list of codons in this program's chromosome.
	 * Note that because it is a copy that is returned, modifying the returned
	 * list will not modify the GECandidateProgram's list of codons. But also
	 * that calling this method frequently just to obtain the codons would be
	 * bad for performance as it creates a copy of the codons each time.
	 * 
	 * @return a copy of this program's codon string.
	 */
	public List<Integer> getCodons() {
		return codons;
	}

	/**
	 * Returns the number of codons in this program's chromosome.
	 * 
	 * @return the number of codons this program has.
	 */
	public int getNoCodons() {
		return codons.size();
	}

	public int getParseTreeDepth() {
		int depth = 0;

		if (parseTree != null) {
			depth = parseTree.getDepth();
		}

		return depth;
	}

	/**
	 * Returns the fitness of the candidate program. This method should be
	 * called over calling model.getFitness(GEModel) as it may be able to
	 * perform caching. If caching is in use then the fitness will never be
	 * calculated twice for the same codon string.
	 * 
	 * @return the fitness score of this candidate program according to the
	 *         model's getFitness method.
	 */
	@Override
	public double getFitness() {
		// If we're not caching or the cache is out of date.
		if (!model.cacheFitness() || !codons.equals(fitnessCacheCodons)) {
			fitness = model.getFitness(this);
			fitnessCacheCodons = new ArrayList<Integer>(codons);
		}

		return fitness;
	}

	/**
	 * Returns a source code string that is the phenotype to this candidate
	 * program's genotypic codon string. This method should always be called
	 * over calling the mapper directly as it may be able to perform caching.
	 * If caching is in use then the source code will never be generated twice
	 * for the same codon string.
	 * 
	 * <p>
	 * The source code is generated by performing mapping using the mapper
	 * specified in the controlling model.
	 * 
	 * @return the source code of this candidate program.
	 */
	public String getSourceCode() {
		// If we have not already done the mapping then do it now and stash it.
		if (!model.cacheSource() || !codons.equals(sourceCacheCodons)) {
			// May be null still after if the map was invalid.
			parseTree = model.getMapper().map(this);
			// Update the number of codons that were used in mapping.
			noActiveCodons = model.getMapper().getNoMappedCodons();
			// Store the codons as they are now.
			sourceCacheCodons = new ArrayList<Integer>(codons);
		}

		return (parseTree == null) ? null : parseTree.toString();
	}

	/**
	 * Returns the number of codons that are actually active. That is, the
	 * number of codons that are actually used in the mapping process to
	 * convert from chromosome to source code.
	 * 
	 * @return the number of active codons this program has.
	 */
	public int getNoActiveCodons() {
		return noActiveCodons;
	}

	/**
	 * Whether the current program is valid. If the chromosome hasn't been
	 * mapped already then this method will trigger a map operation. A
	 * GECandidateProgram is usually valid if it does not exceed any maximum
	 * depth or length restrictions from the model, however this is controlled
	 * by the mapper in use.
	 * 
	 * @return true if this program is valid, false otherwise.
	 */
	@Override
	public boolean isValid() {
		boolean valid = true;

		final int maxChromosomeLength = model.getMaxChromosomeLength();
		final int maxProgramDepth = model.getMaxDepth();

		if (getSourceCode() == null) {
			valid = false;
		} else if ((maxChromosomeLength != -1) && (codons.size() > maxChromosomeLength)) {
			valid = false;
		} else if ((maxProgramDepth != -1) && (parseTree.getDepth() > maxProgramDepth)) {
			valid = false;
		}

		return valid;
	}

	/**
	 * Create a clone of this GECandidateProgram. The list of codons are copied
	 * as are all caches.
	 * 
	 * @return a copy of this GECandidateProgram instance.
	 */
	@Override
	public CandidateProgram clone() {
		final GECandidateProgram clone = (GECandidateProgram) super.clone();

		// Copy codons.
		clone.codons = new ArrayList<Integer>(codons);

		// If codons are the same then the source and fitness should be the
		// same.
		if (parseTree == null) {
			clone.parseTree = null;
		} else {
			clone.parseTree = parseTree.clone();
		}

		clone.fitness = fitness;
		clone.sourceCacheCodons = new ArrayList<Integer>(sourceCacheCodons);
		clone.fitnessCacheCodons = new ArrayList<Integer>(fitnessCacheCodons);

		// Shallow copy the model.
		clone.model = model;

		return clone;
	}

	/**
	 * Returns a string representation of this program. This will be either the
	 * genotype or the phenotype, depending on whether the program has
	 * undergone mapping or not.
	 */
	@Override
	public String toString() {
		if (codons.equals(sourceCacheCodons) && (parseTree != null)) {
			return parseTree.toString();
		} else {
			return codons.toString();
		}
	}

	/**
	 * Compares the given argument for equivalence to this GECandidateProgram.
	 * Two candidate programs are equal if they have equal genotypes, or if
	 * they have equal (but non-null) phenotypes.
	 * 
	 * @return true if the object is an equivalent candidate program, false
	 *         otherwise.
	 */
	@Override
	public boolean equals(final Object o) {
		final GECandidateProgram prog = (GECandidateProgram) o;

		final Symbol thisParseTree = parseTree;
		final Symbol progParseTree = prog.parseTree;

		if ((thisParseTree == null) && (progParseTree == null)) {
			// Compare genotypes.
			return codons.equals(prog.codons);
		} else {
			// Compare phenotypes.
			return ObjectUtils.equals(thisParseTree, progParseTree);
		}
	}
}
