/*
 * Encog(tm) Core v3.2 - Java Version
 * http://www.heatonresearch.com/encog/
 * https://github.com/encog/encog-java-core
 
 * Copyright 2008-2013 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.neural.neat.training.opp;

import org.encog.mathutil.randomize.RangeRandomizer;
import org.encog.ml.ea.genome.Genome;
import org.encog.ml.ea.opp.EvolutionaryOperator;
import org.encog.ml.ea.train.EvolutionaryAlgorithm;
import org.encog.neural.neat.NEATNeuronType;
import org.encog.neural.neat.NEATPopulation;
import org.encog.neural.neat.training.NEATGenome;
import org.encog.neural.neat.training.NEATInnovation;
import org.encog.neural.neat.training.NEATLinkGene;
import org.encog.neural.neat.training.NEATNeuronGene;

/**
 * This class represents a NEAT mutation. NEAT supports several different types
 * of mutations. This class provides common utility needed by any sort of a NEAT
 * mutation. This class is abstract and cannot be used by itself.
 * 
 * -----------------------------------------------------------------------------
 * http://www.cs.ucf.edu/~kstanley/ Encog's NEAT implementation was drawn from
 * the following three Journal Articles. For more complete BibTeX sources, see
 * NEATNetwork.java.
 * 
 * Evolving Neural Networks Through Augmenting Topologies
 * 
 * Generating Large-Scale Neural Networks Through Discovering Geometric
 * Regularities
 * 
 * Automatic feature selection in neuroevolution
 */
public abstract class NEATMutation implements EvolutionaryOperator {

	/**
	 * The trainer that owns this class.
	 */
	private EvolutionaryAlgorithm owner;

	/**
	 * Choose a random neuron.
	 * 
	 * @param target
	 *            The target genome. Should the input and bias neurons be
	 *            included.
	 * @param choosingFrom
	 *            True if we are chosing from all neurons, false if we exclude
	 *            the input and bias.
	 * @return The random neuron.
	 */
	public NEATNeuronGene chooseRandomNeuron(final NEATGenome target,
			final boolean choosingFrom) {
		int start;

		if (choosingFrom) {
			start = 0;
		} else {
			start = target.getInputCount() + 1;
		}

		// if this network will not "cycle" then output neurons cannot be source
		// neurons
		if (!choosingFrom) {
			final int ac = ((NEATPopulation) target.getPopulation())
					.getActivationCycles();
			if (ac == 1) {
				start += target.getOutputCount();
			}
		}

		final int end = target.getNeuronsChromosome().size() - 1;

		// no neurons to pick!
		if (start > end) {
			return null;
		}

		final int neuronPos = RangeRandomizer.randomInt(start, end);
		final NEATNeuronGene neuronGene = target.getNeuronsChromosome().get(
				neuronPos);
		return neuronGene;

	}

	/**
	 * Create a link between two neuron id's. Create or find any necessary
	 * innovation records.
	 * 
	 * @param target
	 *            The target genome.
	 * @param neuron1ID
	 *            The id of the source neuron.
	 * @param neuron2ID
	 *            The id of the target neuron.
	 * @param weight
	 *            The weight of this new link.
	 */
	public void createLink(final NEATGenome target, final long neuron1ID,
			final long neuron2ID, final double weight) {

		// first, does this link exist? (and if so, hopefully disabled,
		// otherwise we have a problem)
		for (final NEATLinkGene linkGene : target.getLinksChromosome()) {
			if ((linkGene.getFromNeuronID() == neuron1ID)
					&& (linkGene.getToNeuronID() == neuron2ID)) {
				// bring the link back, at the new weight
				linkGene.setEnabled(true);
				linkGene.setWeight(weight);
				return;
			}
		}

		// check to see if this innovation has already been tried
		final NEATInnovation innovation = ((NEATPopulation) target
				.getPopulation()).getInnovations().findInnovation(neuron1ID,
				neuron2ID);

		// now create this link
		final NEATLinkGene linkGene = new NEATLinkGene(neuron1ID, neuron2ID,
				true, innovation.getInnovationID(), weight);
		target.getLinksChromosome().add(linkGene);
	}

	/**
	 * Get the specified neuron's index.
	 * 
	 * @param neuronID
	 *            The neuron id to check for.
	 * @return The index.
	 */
	public int getElementPos(final NEATGenome target, final long neuronID) {

		for (int i = 0; i < target.getNeuronsChromosome().size(); i++) {
			final NEATNeuronGene neuronGene = target.getNeuronsChromosome()
					.get(i);
			if (neuronGene.getId() == neuronID) {
				return i;
			}
		}

		return -1;
	}

	/**
	 * @return the owner
	 */
	public EvolutionaryAlgorithm getOwner() {
		return this.owner;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void init(final EvolutionaryAlgorithm theOwner) {
		this.owner = theOwner;
	}

	/**
	 * Determine if this is a duplicate link.
	 * 
	 * @param fromNeuronID
	 *            The from neuron id.
	 * @param toNeuronID
	 *            The to neuron id.
	 * @return True if this is a duplicate link.
	 */
	public boolean isDuplicateLink(final NEATGenome target,
			final long fromNeuronID, final long toNeuronID) {
		for (final NEATLinkGene linkGene : target.getLinksChromosome()) {
			if ((linkGene.isEnabled())
					&& (linkGene.getFromNeuronID() == fromNeuronID)
					&& (linkGene.getToNeuronID() == toNeuronID)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Determines if a neuron is still needed. If all links to/from a neuron
	 * have been removed, then the neuron is no longer needed.
	 * 
	 * @param target
	 *            The target genome.
	 * @param neuronID
	 *            The neuron id to check for.
	 * @return Returns true, if the neuron is still needed.
	 */
	public boolean isNeuronNeeded(final NEATGenome target, final long neuronID) {

		// do not remove bias or input neurons or output
		for (final NEATNeuronGene gene : target.getNeuronsChromosome()) {
			if (gene.getId() == neuronID) {
				final NEATNeuronGene neuron = gene;
				if ((neuron.getNeuronType() == NEATNeuronType.Input)
						|| (neuron.getNeuronType() == NEATNeuronType.Bias)
						|| (neuron.getNeuronType() == NEATNeuronType.Output)) {
					return true;
				}
			}
		}

		// Now check to see if the neuron is used in any links
		for (final NEATLinkGene gene : target.getLinksChromosome()) {
			final NEATLinkGene linkGene = gene;
			if (linkGene.getFromNeuronID() == neuronID) {
				return true;
			}
			if (linkGene.getToNeuronID() == neuronID) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Obtain the NEATGenome that we will mutate. NEAT mutates the genomes in
	 * place. So the parent and child genome must be the same literal object.
	 * Throw an exception, if this is not the case.
	 * 
	 * @param parents
	 *            The parents.
	 * @param parentIndex
	 *            The parent index.
	 * @param offspring
	 *            The offspring.
	 * @param offspringIndex
	 *            The offspring index.
	 * @return The genome that we will mutate.
	 */
	public NEATGenome obtainGenome(final Genome[] parents,
			final int parentIndex, final Genome[] offspring,
			final int offspringIndex) {
		offspring[offspringIndex] = this.getOwner().getPopulation()
				.getGenomeFactory().factor(parents[0]);
		return (NEATGenome) offspring[offspringIndex];
	}

	/**
	 * @return Returns 1, as NEAT mutations only produce one child.
	 */
	@Override
	public int offspringProduced() {
		return 1;
	}

	/**
	 * @return Returns 1, as mutations typically are asexual and only require a
	 *         single parent.
	 */
	@Override
	public int parentsNeeded() {
		return 1;
	}

	/**
	 * Remove the specified neuron.
	 * 
	 * @param target
	 *            The target genome.
	 * @param neuronID
	 *            The neuron to remove.
	 */
	public void removeNeuron(final NEATGenome target, final long neuronID) {
		for (final NEATNeuronGene gene : target.getNeuronsChromosome()) {
			if (gene.getId() == neuronID) {
				target.getNeuronsChromosome().remove(gene);
				return;
			}
		}
	}

}
