/*
 * This file is part of Jstacs.
 *
 * Jstacs 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 3 of the License, or
 * (at your option) any later version.
 * 
 * Jstacs 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jstacs. If not, see <http://www.gnu.org/licenses/>.
 * 
 * For more information on Jstacs, visit http://www.jstacs.de
 */

package de.jstacs.models;

import de.jstacs.NonParsableException;
import de.jstacs.NotTrainedException;
import de.jstacs.Storable;
import de.jstacs.data.AlphabetContainer;
import de.jstacs.data.Sample;
import de.jstacs.data.Sequence;
import de.jstacs.data.Sample.ElementEnumerator;
import de.jstacs.results.Result;
import de.jstacs.results.ResultSet;
import de.jstacs.results.StorableResult;

/**
 * Abstract class for a model for pattern recognition. <br>
 * For writing or reading a {@link StringBuffer} to or from a file (
 * {@link #fromXML(StringBuffer)}, {@link #toXML()}) you can use the class
 * {@link de.jstacs.io.FileManager}.
 * 
 * @see de.jstacs.io.FileManager
 * 
 * @author Andre Gohr, Jan Grau, Jens Keilwagen
 */
public abstract class AbstractModel implements Cloneable, Storable, Model {
	/**
	 * The length of the sequences the model can classify. For models that can
	 * take sequences of arbitrary length this value should be set to 0
	 */
	protected int length;

	/**
	 * The underlying alphabets
	 */
	protected AlphabetContainer alphabets;

	/**
	 * Constructor that sets the length of the model to <code>length</code> and
	 * the {@link AlphabetContainer} to <code>alphabets</code>.
	 * 
	 * <br>
	 * 
	 * The parameter <code>length</code> gives the length of the sequences the
	 * model can classify. Models that can only classify sequences of defined
	 * length are e.g. PWM or inhomogeneous Markov models. If the model can
	 * classify sequences of arbitrary length, e.g. homogeneous Markov models,
	 * this parameter must be set to 0 (zero).
	 * 
	 * <br>
	 * 
	 * The <code>length</code> and <code>alphabets</code> define the type of
	 * data that can be modeled and therefore both has to be checked before any
	 * evaluation (e.g. <code>getProbFor</code>)
	 * 
	 * @param alphabets
	 *            the alphabets in an {@link AlphabetContainer}
	 * @param length
	 *            the length of the sequences a model can classify, 0 for
	 *            arbitrary length
	 */
	public AbstractModel(AlphabetContainer alphabets, int length) {
		this.length = length;
		this.alphabets = alphabets;
		if (alphabets.getPossibleLength() > 0
				&& alphabets.getPossibleLength() != length) {
			throw new IllegalArgumentException(
					"The length and the alphabet container does not match.");
		}
	}

	/**
	 * The standard constructor for the interface {@link de.jstacs.Storable}.
	 * Creates a new {@link AbstractModel} out of a {@link StringBuffer}.
	 * 
	 * @param stringBuff
	 *            the {@link StringBuffer} to be parsed
	 * 
	 * @throws NonParsableException
	 *             is thrown if the {@link StringBuffer} could not be parsed
	 */
	public AbstractModel(StringBuffer stringBuff) throws NonParsableException {
		alphabets = null;
		length = -1;
		fromXML(stringBuff);
		if (alphabets == null) {
			throw new NonParsableException(
					"The alphabets were not set correctly.");
		}
		if (length < 0) {
			throw new NonParsableException("The length was not set correctly.");
		}
		if (alphabets.getPossibleLength() > 0
				&& alphabets.getPossibleLength() != length) {
			throw new IllegalArgumentException(
					"The length and the alphabet container doesnot not match.");
		}
	}

	/**
	 * Follows the conventions of {@link Object}'s <code>clone()</code>-method.
	 * 
	 * @return an object, that is a copy of the current {@link AbstractModel}
	 *         (the member-{@link AlphabetContainer} isn't deeply cloned since
	 *         it is assumed to be immutable). The type of the returned object
	 *         is defined by the class <code>X</code> directly inherited from
	 *         {@link AbstractModel}. Hence <code>X</code>'s
	 *         <code>clone()</code>-method should work as:<br>
	 *         1. <code>Object o = (X)super.clone();</code> <br>
	 *         2. all additional member variables of <code>o</code> defined by
	 *         <code>X</code> that are not of simple data-types like
	 *         <code>int</code>, <code>double</code>, ... have to be deeply
	 *         copied <br>
	 *         3. <code>return o</code>
	 */
	@Override
	public AbstractModel clone() throws CloneNotSupportedException {
		return (AbstractModel) super.clone();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.models.Model#train(de.jstacs.data.Sample)
	 */
	public void train(Sample data) throws Exception {
		train(data, null);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.models.Model#getProbFor(de.jstacs.data.Sequence)
	 */
	public double getProbFor(Sequence sequence) throws NotTrainedException,
			Exception {
		return getProbFor(sequence, 0, sequence.getLength() - 1);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.models.Model#getProbFor(de.jstacs.data.Sequence, int)
	 */
	public double getProbFor(Sequence sequence, int startpos)
			throws NotTrainedException, Exception {
		if (length == 0) {
			return getProbFor(sequence, startpos, sequence.getLength() - 1);
		} else {
			return getProbFor(sequence, startpos, startpos + length - 1);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.models.Model#getLogProbFor(de.jstacs.data.Sequence, int,
	 * int)
	 */
	public double getLogProbFor(Sequence sequence, int startpos, int endpos)
			throws Exception {
		return StrictMath.log(getProbFor(sequence, startpos, endpos));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.models.Model#getLogProbFor(de.jstacs.data.Sequence, int)
	 */
	public double getLogProbFor(Sequence sequence, int startpos)
			throws Exception {
		if (length == 0) {
			return getLogProbFor(sequence, startpos, sequence.getLength() - 1);
		} else {
			return getLogProbFor(sequence, startpos, startpos + length - 1);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.models.Model#getLogProbFor(de.jstacs.data.Sequence)
	 */
	public double getLogProbFor(Sequence sequence) throws Exception {
		return getLogProbFor(sequence, 0, sequence.getLength() - 1);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.models.Model#getLogProbFor(de.jstacs.data.Sample)
	 */
	public double[] getLogProbFor(Sample data) throws Exception {
		double[] res = new double[data.getNumberOfElements()];
		getLogProbFor(data, res);
		return res;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.models.Model#getLogProbFor(de.jstacs.data.Sample,
	 * double[])
	 */
	public void getLogProbFor(Sample data, double[] res) throws Exception {
		if (res.length != data.getNumberOfElements()) {
			throw new IllegalArgumentException("The array has wrong dimension.");
		}
		ElementEnumerator ei = new ElementEnumerator(data);
		for (int i = 0; i < res.length; i++) {
			res[i] = getLogProbFor(ei.nextElement());
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.models.Model#getPriorTerm()
	 */
	public double getPriorTerm() throws Exception {
		return Math.exp(getLogPriorTerm());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.models.Model#emitSample(int, int)
	 */
	public Sample emitSample(int numberOfSequences, int... seqLength)
			throws NotTrainedException, Exception {
		throw new Exception(
				"Standard implementation of emitSample used for "
						+ getInstanceName()
						+ ". You have to overwrite this method to use it in a proper way.");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.models.Model#getAlphabetContainer()
	 */
	public final AlphabetContainer getAlphabetContainer() {
		return alphabets;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.models.Model#getLength()
	 */
	public final int getLength() {
		return length;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.models.Model#getMaximalMarkovOrder()
	 */
	public byte getMaximalMarkovOrder() throws UnsupportedOperationException {
		throw new UnsupportedOperationException(
				"The maximal markov order for this model in undefined.");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.models.Model#getCharacteristics()
	 */
	public ResultSet getCharacteristics() throws Exception {
		return new ResultSet(getNumericalCharacteristics().getResults(),
				new Result[] { new StorableResult("model",
						"the xml representation of the model", this) });
	}

	/**
	 * This method should only be used by the constructor that works on a
	 * {@link StringBuffer}. It is the counter part of {@link #toXML()}.
	 * 
	 * @param xml
	 *            the XML representation of the model
	 * 
	 * @throws NonParsableException
	 *             if the {@link StringBuffer} is not parsable or the
	 *             representation is conflicting
	 * 
	 * @see AbstractModel#AbstractModel(StringBuffer)
	 */
	protected abstract void fromXML(StringBuffer xml)
			throws NonParsableException;

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.jstacs.models.Model#setNewAlphabetContainerInstance(de.jstacs.data
	 * .AlphabetContainer)
	 */
	public final boolean setNewAlphabetContainerInstance(AlphabetContainer abc) {
		if (abc.checkConsistency(alphabets)) {
			set(abc);
			alphabets = abc;
			return true;
		} else {
			return false;
		}
	}

	/**
	 * This method should only be invoked by the method
	 * {@link #setNewAlphabetContainerInstance(AlphabetContainer)} and <b>not be
	 * made public</b>.
	 * 
	 * <br>
	 * <br>
	 * 
	 * It enables you to do more with the method
	 * {@link #setNewAlphabetContainerInstance(AlphabetContainer)}, e.g. setting
	 * a new {@link AlphabetContainer} instance for subcomponents.
	 * 
	 * @param abc
	 *            the new instance
	 */
	protected void set(AlphabetContainer abc) {
	}
}