/*
 * 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 java.io.IOException;
import java.util.Random;

import de.jstacs.NonParsableException;
import de.jstacs.WrongAlphabetException;
import de.jstacs.data.AlphabetContainer;
import de.jstacs.data.Sample;
import de.jstacs.data.Sequence;
import de.jstacs.data.sequences.ArbitrarySequence;
import de.jstacs.data.sequences.IntSequence;
import de.jstacs.data.sequences.WrongSequenceTypeException;
import de.jstacs.io.XMLParser;
import de.jstacs.results.NumericalResultSet;

/**
 * This class represents a uniform model. Sometimes it's also called uninformed model. It can be used if nothing is
 * known about a statistical process.
 * 
 * @author Jens Keilwagen
 */
public class UniformModel extends AbstractModel
{
	private static final long serialVersionUID = 1L;

	private static final String XML_TAG = "DiscreteUniformModel";

	/**
	 * Creates a new {@link UniformModel} using a given {@link AlphabetContainer}.
	 * 
	 * @param alphabet the alphabets used in the model
	 */
	public UniformModel( AlphabetContainer alphabet )
	{
		super( alphabet, alphabet.getPossibleLength() );
	}

	/**
	 * The standard constructor for the interface {@link de.jstacs.Storable}.
	 * Creates a new {@link UniformModel} out of a {@link StringBuffer}.
	 * 
	 * @param stringBuff  the {@link StringBuffer} to be parsed
	 * 
	 * @throws NonParsableException  if the {@link StringBuffer} is not parsable
	 */
	public UniformModel( StringBuffer stringBuff ) throws NonParsableException
	{
		super( stringBuff );
	}

	/* (non-Javadoc)
	 * @see de.jstacs.models.AbstractModel#clone()
	 */
	@Override
	public UniformModel clone() throws CloneNotSupportedException
	{
		return (UniformModel) super.clone();
	}

	/* (non-Javadoc)
	 * @see de.jstacs.models.Model#getProbFor(de.jstacs.data.Sequence, int, int)
	 */
	public double getProbFor( Sequence sequence, int startpos, int endpos ) throws IllegalArgumentException,
			WrongAlphabetException
	{
		if( !alphabets.checkConsistency( sequence.getAlphabetContainer() ) )
		{
			throw new WrongAlphabetException( "This sequence and model doesnot match." );
		}
		else if( 0 > startpos || endpos > sequence.getLength() )
		{
			throw new IllegalArgumentException( "Attention: 0 <= startpos, endpos < sequence.length()" );
		}
		else if( endpos - startpos + 1 < 0 )
		{
			throw new IllegalArgumentException(
					"The sequence does not have a correct length. The length has to be non-negative." );
		}
		else
		{
			if( length > 0 )
			{
				if( endpos - startpos + 1 == length )
				{
					double p = 1d;
					for( int i = 0; i < length; i++ )
					{
						p /= alphabets.getAlphabetLengthAt( i );
					}
					return p;
				}
				else
				{
					throw new IllegalArgumentException( "The sequence does not have a correct length (" + length + ")." );
				}
			}
			else
			{
				return Math.pow( alphabets.getAlphabetLengthAt( 0 ), startpos - 1 - endpos );
			}
		}
	}

	/**
	 * Returns <code>true</code> if the model is trained, <code>false</code> otherwise.
	 * 
	 * @return <code>true</code> if the model is trained, <code>false</code> otherwise
	 */
	public boolean isTrained()
	{
		return true;
	}

	/* (non-Javadoc)
	 * @see de.jstacs.models.AbstractModel#fromXML(java.lang.StringBuffer)
	 */
	@Override
	public void fromXML( StringBuffer representation ) throws NonParsableException
	{
		alphabets = new AlphabetContainer( XMLParser.extractForTag( representation, XML_TAG ) );
		length = alphabets.getPossibleLength();
	}

	/* (non-Javadoc)
	 * @see de.jstacs.Storable#toXML()
	 */
	public StringBuffer toXML()
	{
		StringBuffer xml = alphabets.toXML();
		XMLParser.addTags( xml, XML_TAG );
		return xml;
	}

	/**
	 * Returns the String &quot;&quot;.
	 */
	@Override
	public String toString()
	{
		return "";
	}

	/* (non-Javadoc)
	 * @see de.jstacs.models.Model#train(de.jstacs.data.Sample, double[])
	 */
	@Deprecated
	public void train( Sample data, double[] weights ) throws IOException
	{
		// nothing
	}

	/* (non-Javadoc)
	 * @see de.jstacs.models.AbstractModel#emitSample(int, int[])
	 */
	@Override
	public Sample emitSample( int n, int... lengths ) throws Exception
	{
		Sequence[] seq;
		if( length == 0 )
		{
			if( lengths.length == 1 )
			{
				seq = getSequences( n, lengths[0] );
			}
			else
			{
				seq = new Sequence[n];
				for( int i = 0; i < n; i++ )
				{
					seq[i] = getSequences( 1, lengths[i] )[0];
				}
			}
		}
		else
		{
			if( !(lengths == null || lengths.length == 0) )
			{
				throw new Exception( "This is an inhomogeneous model. Please check parameter lengths." );
			}
			double[] content = new double[length];
			seq = new Sequence[n];
			for( int j, i = 0; i < n; i++ )
			{
				for( j = 0; j < length; j++ )
				{
					content[j] = alphabets.getMin( j ) + r.nextDouble() * alphabets.getAlphabetLengthAt( j );
					if( alphabets.isDiscreteAt( j ) )
					{
						content[j] = (int) content[j];
					}
				}
				seq[i] = new ArbitrarySequence( alphabets, content );
			}
		}
		return new Sample( "sampled from " + getInstanceName(), seq );
	}
	
	private static final Random r = new Random();

	private Sequence[] getSequences( int n, int length ) throws WrongAlphabetException,
			WrongSequenceTypeException
	{
		Sequence[] seqs = new Sequence[n];
		if( alphabets.isDiscrete() )
		{
			int[] seq = new int[length];
			int i, j = 0, l = (int) alphabets.getAlphabetLengthAt( 0 );
			while( j < n )
			{
				for( i = 0; i < length; i++ )
				{
					seq[i] = r.nextInt( l );
				}
				seqs[j++] = new IntSequence( alphabets, seq );
			}
		}
		else
		{
			double[] seq = new double[length];
			double m = alphabets.getMin( 0 ), l = alphabets.getAlphabetLengthAt( 0 );
			int i, j = 0;
			while( j < n )
			{
				for( i = 0; i < length; i++ )
				{
					seq[i] = m + r.nextDouble() * l;
				}
				seqs[j++] = new ArbitrarySequence( alphabets, seq );
			}
		}
		return seqs;
	}

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

	/* (non-Javadoc)
	 * @see de.jstacs.models.AbstractModel#getMaximalMarkovOrder()
	 */
	@Override
	public byte getMaximalMarkovOrder() throws UnsupportedOperationException
	{
		return (byte) 0;
	}

	/* (non-Javadoc)
	 * @see de.jstacs.models.Model#getNumericalCharacteristics()
	 */
	public NumericalResultSet getNumericalCharacteristics() throws Exception
	{
		return null;
	}

	/* (non-Javadoc)
	 * @see de.jstacs.models.Model#getInstanceName()
	 */
	public String getInstanceName()
	{
		return "uniform";
	}
}
