/*
 * 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.scoringFunctions;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;

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.io.ArrayHandler;
import de.jstacs.io.XMLParser;
import de.jstacs.motifDiscovery.MotifDiscoverer;
import de.jstacs.motifDiscovery.MutableMotifDiscoverer;
import de.jstacs.scoringFunctions.homogeneous.HomogeneousScoringFunction;
import de.jstacs.scoringFunctions.mix.AbstractMixtureScoringFunction;
import de.jstacs.utils.DoubleList;
import de.jstacs.utils.IntList;
import de.jstacs.utils.Normalisation;

/**
 * This class enables the user to model parts of a sequence independent of each
 * other. For instance, the first part of the sequence is modeled by the first
 * {@link NormalizableScoringFunction} and has the length of the first
 * {@link NormalizableScoringFunction}, the second part starts directly after
 * the first part, is modeled by the second {@link NormalizableScoringFunction}
 * ... etc. It is also possible to use a {@link NormalizableScoringFunction} for
 * more than one sequence part and in both orientations (if possible).
 * 
 * <br><br>
 * 
 * It is important to set the equivalent sample size (ESS) of each instance carefully, i.e., corresponding to the ESS of the parts.  
 * 
 * @author Jens Keilwagen
 */
public class IndependentProductScoringFunction extends AbstractNormalizableScoringFunction implements MutableMotifDiscoverer
{

	private NormalizableScoringFunction[] score;

	private int[] index, start, partialLength, params, motifs, componentsUntilNow;
	 /**
	 * The first entry is the index of the corresponding {@link ScoringFunction}
	 * in {@link IndependentProductScoringFunction#score}. The second entry is
	 * the <b>global</b> motif index in the corresponding
	 * {@link ScoringFunction}. The third entry indicates by a &quot;1&quot;
	 * that the corresponding {@link ScoringFunction} implements
	 * {@link MutableMotifDiscoverer}, otherwise &quot;-1&quot;.
	 */
	private int[] motifIndexArray;
	/**
	 * The array <code>components</code> stores how many components each {@link ScoringFunction} in <code>score</code> has.
	 */
	private int[] components;
	/**
	 * The components of each part.
	 */
	private int[] componentIndexArray;
	
	private boolean[] reverse, isVariable;

	private double ess;
	
	private IntList partIList;
	
	private boolean plugIn;

	private final static AlphabetContainer getAlphabetContainer( NormalizableScoringFunction[] functions, int[] index, int length[], boolean[] reverse ) throws IllegalArgumentException, WrongAlphabetException {
		boolean direct = index == null;
		int len = direct ? functions.length : index.length;
		AlphabetContainer[] cons = new AlphabetContainer[len];
		int[] lengths = new int[len];
		for( int j, i = 0; i < len; i++ ) {
			if( direct ) {
				j = i;
			} else {
				j = index[i];
			}
			cons[i] = functions[j].getAlphabetContainer();
			lengths[i] = length[i];
			if( reverse != null && reverse[i] == true && !cons[i].isReverseComplementable() ) {
				throw new WrongAlphabetException( "The AlpabetContainer is not reverse complementable." );
			}
		}
		return new AlphabetContainer( cons, lengths );
	}

	private final static int sum( int[] length ) throws IllegalArgumentException {
		int res = 0, i = 0;
		while( i < length.length && length[i] > 0 ) {
			res += length[i++];
		}
		if( i != length.length ) {
			throw new IllegalArgumentException( "The length with index " + i + " is 0." );
		}
		return res;
	}

	private final static int[] getLengthArray( NormalizableScoringFunction... functions ) throws IllegalArgumentException {
		int i = 0;
		int[] res = new int[functions.length];
		while( i < functions.length && functions[i].getLength() > 0 ) {
			res[i] = functions[i].getLength();
			i++;
		}
		if( i != functions.length ) {
			throw new IllegalArgumentException( "The NormalizableScoringFunction with index " + i + " has a length 0." );
		}
		return res;
	}

	/**
	 * This constructor creates an instance of an
	 * {@link IndependentProductScoringFunction} from a given series of
	 * independent {@link NormalizableScoringFunction}s. The length that is
	 * modeled by each component is determined by
	 * {@link ScoringFunction#getLength()}. So the length should not be 0.
	 * 
	 * @param ess
	 * 			  the equivalent sample size
	 * @param plugIn whether to use plugIn parameters for the parts, otherwise the last parameters are used for parts that are instance of {@link HomogeneousScoringFunction}
	 * @param functions
	 *            the components, i.e. the given series of independent
	 *            {@link NormalizableScoringFunction}s
	 *             
	 * @throws CloneNotSupportedException
	 *             if at least one element of <code>functions</code> could not
	 *             be cloned
	 * @throws IllegalArgumentException
	 *             if at least one component has length 0 or if the
	 *             equivalent sample size (ess) is smaller than zero (0)
	 * @throws WrongAlphabetException
	 *             if the user tries to use an alphabet for a reverse complement that can not be used for a reverse complement.
	 *             
	 * @see IndependentProductScoringFunction#IndependentProductScoringFunction(double, boolean, NormalizableScoringFunction[], int[])
	 */
	public IndependentProductScoringFunction( double ess, boolean plugIn, NormalizableScoringFunction... functions ) throws CloneNotSupportedException,
																						IllegalArgumentException, WrongAlphabetException {
		this( ess, plugIn, functions, getLengthArray( functions ) );
	}

	/**
	 * This constructor creates an instance of an
	 * {@link IndependentProductScoringFunction} from given series of
	 * independent {@link NormalizableScoringFunction}s and lengths.
	 * 
	 * @param ess
	 * 			  the equivalent sample size
	 * @param plugIn whether to use plugIn parameters for the parts, otherwise the last parameters are used for parts that are instance of {@link HomogeneousScoringFunction}
	 * @param functions
	 *            the components, i.e. the given series of independent
	 *            {@link NormalizableScoringFunction}s
	 * @param length
	 *            the lengths, one for each component
	 * 
	 * @throws CloneNotSupportedException
	 *             if at least one component could not be cloned
	 * @throws IllegalArgumentException
	 *             if the lengths and the components are not matching or if the
	 *             equivalent sample size (ess) is smaller than zero (0)
	 * @throws WrongAlphabetException
	 *             if the user tries to use an alphabet for a reverse complement that can not be used for a reverse complement.
	 * 
	 * @see IndependentProductScoringFunction#IndependentProductScoringFunction(double, boolean, NormalizableScoringFunction[], int[], int[], boolean[])
	 */
	public IndependentProductScoringFunction( double ess, boolean plugIn, NormalizableScoringFunction[] functions, int[] length ) throws CloneNotSupportedException,
																										IllegalArgumentException, WrongAlphabetException {
		this( ess, plugIn, functions, null, length, null );
	}

	/**
	 * This is the main constructor.
	 *
	 * @param ess the equivalent sample size
	 * @param plugIn whether to use plugIn parameters for the parts, otherwise the last parameters are used for parts that are instance of {@link HomogeneousScoringFunction}
	 * @param functions the {@link NormalizableScoringFunction}
	 * @param index the index of the {@link NormalizableScoringFunction} at each part
	 * @param length the length of each part
	 * @param reverse a switch whether to use it directly or the reverse complementary strand
	 * 
	 * @throws CloneNotSupportedException
	 *             if at least one component could not be cloned
	 * @throws IllegalArgumentException
	 *             if the lengths and the components are not matching or if the
	 *             equivalent sample size (ess) is smaller than zero (0)
	 * @throws WrongAlphabetException
	 *             if the user tries to use an alphabet for a reverse complement that can not be used for a reverse complement. 
	 * 
	 */
	public IndependentProductScoringFunction( double ess, boolean plugIn, NormalizableScoringFunction[] functions, int[] index, int[] length, boolean[] reverse ) throws CloneNotSupportedException,
	IllegalArgumentException, WrongAlphabetException {
		super( getAlphabetContainer( functions, index, length, reverse ), sum( length ) );
		if( ess < 0 ) {
			throw new IllegalArgumentException( "The equivalent sample size (ess) has to be non-negative." );
		}
		this.ess = ess;
		this.plugIn = plugIn;
		score = ArrayHandler.clone( functions );
		set( index, length, reverse );
		setParamsStarts();
		prepare();
	}
	
	private void prepare() {
		motifs = new int[score.length + 1];
		components = new int[score.length];
		for( int i = 0; i < score.length; i++ ) {
			motifs[i + 1] = motifs[i];
			if( score[i] instanceof MotifDiscoverer ) {
				motifs[i + 1] += ( (MotifDiscoverer)score[i] ).getNumberOfMotifs();
			}
			if( score[i] instanceof AbstractMixtureScoringFunction ) {
				components[i] = ( (AbstractMixtureScoringFunction)score[i] ).getNumberOfComponents();
			} else if( score[i] instanceof MotifDiscoverer ) {
				components[i] = ( (MotifDiscoverer)score[i] ).getNumberOfComponents();
			} else {
				components[i] = 1;
			}
		}
		componentsUntilNow = new int[index.length+1];
		componentsUntilNow[0] = 1;
		for( int i = 0; i < index.length; i++ ){
			componentsUntilNow[i+1] = componentsUntilNow[i] * components[index[i]];
		}
		motifIndexArray = new int[3];
		componentIndexArray = new int[index.length];
	}

	/**
	 * This is the constructor for the interface {@link de.jstacs.Storable}.
	 * Creates a new {@link IndependentProductScoringFunction} out of a
	 * {@link StringBuffer} as returned by {@link #toXML()}.
	 * 
	 * @param source
	 *            the XML representation as {@link StringBuffer}
	 * 
	 * @throws NonParsableException
	 *             if the XML representation could not be parsed
	 */
	public IndependentProductScoringFunction( StringBuffer source ) throws NonParsableException {
		super( source );
	}

	private void set( int[] index, int[] length, boolean[] reverse ) throws IllegalArgumentException {
		int[] used = new int[score.length];
		boolean direct = index == null;
		int oldStart = 0, len = direct ? score.length : index.length;
		start = new int[len];
		partialLength = new int[len];
		isVariable = new boolean[score.length];
		this.index = new int[len];
		this.reverse = new boolean[len];
		for( int i = 0; i < len; i++ ) {
			if( direct ) {
				this.index[i] = i;
			} else {
				if( 0 <= index[i] && index[i] < score.length ) {
					this.index[i] = index[i]; 
				} else {
					throw new IndexOutOfBoundsException( "index " + index[i] );
				}
			}
			used[this.index[i]]++;
			if( reverse == null ) {
				this.reverse[i] = false;
			} else {
				this.reverse[i] = reverse[i];
			}
			
			start[i] = oldStart;
			partialLength[i] = length[i];
			isVariable[this.index[i]] = score[this.index[i]] instanceof VariableLengthScoringFunction;
			if( !isVariable[this.index[i]] && score[this.index[i]].getLength() != partialLength[i] ) {
				throw new IllegalArgumentException( "Could not use length " + partialLength[i] + " at part " + i + " for NormalizableScoringFunction with index " + this.index[i] + "." );
			}
			oldStart += partialLength[i];
		}
		for( int i = 0; i < used.length; i++ ) {
			if( used[i] == 0 ) {
				throw new IllegalArgumentException( "The ScoringFunction with index " + i + " is never used." );
			}
		}
		partIList = new IntList();
	}

	private void setParamsStarts() {
		if( params == null ) {
			params = new int[score.length + 1];
		}
		for( int n, i = 0; i < score.length; i++ ) {
			n = score[i].getNumberOfParameters();
			if( n == UNKNOWN ) {
				params = null;
				break;
			} else {
				params[i + 1] = params[i] + n;
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.AbstractNormalizableScoringFunction#clone()
	 */
	public IndependentProductScoringFunction clone() throws CloneNotSupportedException {
		IndependentProductScoringFunction clone = (IndependentProductScoringFunction)super.clone();
		clone.score = ArrayHandler.clone( score );
		clone.set( index, partialLength, reverse );
		clone.params = null;
		clone.setParamsStarts();
		clone.motifs = motifs.clone();
		clone.motifIndexArray = motifIndexArray.clone();
		clone.components = components.clone();
		clone.componentsUntilNow = componentsUntilNow.clone();
		clone.componentIndexArray = componentIndexArray.clone();
		return clone;
	}

	private int getSFIndex( int index ) {
		int i = 1;
		while( i < params.length && index >= params[i] ) {
			i++;
		}
		return i - 1;
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.NormalizableScoringFunction#getSizeOfEventSpaceForRandomVariablesOfParameter(int)
	 */
	public int getSizeOfEventSpaceForRandomVariablesOfParameter( int index ) {
		int i = getSFIndex( index );
		return score[i].getSizeOfEventSpaceForRandomVariablesOfParameter( index - params[i] );
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.NormalizableScoringFunction#getLogNormalizationConstant()
	 */
	public double getLogNormalizationConstant() {
		double norm = 0;
		for( int i = 0; i < index.length; i++ ) {
			if( isVariable[index[i]] ) {
				norm += ((VariableLengthScoringFunction) score[index[i]]).getLogNormalizationConstant( partialLength[i] );
			} else {
				norm += score[index[i]].getLogNormalizationConstant();
			}
		}
		return norm;
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.NormalizableScoringFunction#getLogPartialNormalizationConstant(int)
	 */
	public double getLogPartialNormalizationConstant( int parameterIndex ) throws Exception {
		int i = 0, j = getSFIndex( parameterIndex ), k = parameterIndex - params[j];
		double normOfOther = 0, myNorm = 0, myPartNorm = Double.NEGATIVE_INFINITY, n, p;
		for( ; i < index.length; i++ ) {
			if( index[i] == j ) {
				if( isVariable[index[i]] ) {
					p = ((VariableLengthScoringFunction) score[j]).getLogPartialNormalizationConstant( k, partialLength[i] );
					n = ((VariableLengthScoringFunction) score[j]).getLogNormalizationConstant( partialLength[i] );
				} else {
					p = score[j].getLogPartialNormalizationConstant( k );
					n = score[j].getLogNormalizationConstant();
				}
				myPartNorm = Normalisation.getLogSum( myPartNorm+n, myNorm+p );
				myNorm += n;
			} else {
				if( isVariable[index[i]] ) {
					normOfOther += ((VariableLengthScoringFunction) score[index[i]]).getLogNormalizationConstant( partialLength[i] );
				} else {
					normOfOther += score[index[i]].getLogNormalizationConstant();
				}
			}
		}
		return normOfOther + myPartNorm;
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.NormalizableScoringFunction#getEss()
	 */
	public double getEss() {
		return ess;
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.ScoringFunction#initializeFunction(int, boolean, de.jstacs.data.Sample[], double[][])
	 */
	public void initializeFunction( int index, boolean freeParams, Sample[] data, double[][] weights ) throws Exception {
		Sample[] part = new Sample[data.length];
		double[][] help;
		for( int a, i = 0; i < score.length; i++ ) {
			if( plugIn || !(score[i] instanceof HomogeneousScoringFunction) ) {
				a = extractSequenceParts( i, data, part );
				help = a==1 ? weights : extractWeights( a, weights );
				score[i].initializeFunction( index, freeParams, part, help );
			}
		}		
		setParamsStarts();
	}
	
	/**
	 * This method extracts the corresponding {@link Sequence} parts for a specific {@link ScoringFunction}.
	 * 
	 * @param scoringFunctionIndex the index of the {@link ScoringFunction}
	 * @param data the original data
	 * @param result an array for the resulting {@link Sample}s of {@link Sequence}s; has to have same length as <code>data</code>
	 * 
	 * @return the number how often the {@link ScoringFunction} was used
	 * 
	 * @throws Exception if the Sample can not be created
	 */
	public int extractSequenceParts( int scoringFunctionIndex, Sample[] data, Sample[] result ) throws Exception {
		Sample current;
		Arrays.fill( result, null );
		int used = 0;
		for( int n, j, k = 0; k < index.length; k++ ) {
			if( index[k] == scoringFunctionIndex ) {
				used++;
				for( j = 0; j < data.length; j++ ) {
					if( data[j] != null ) {
						current = data[j].getInfixSample( start[k], partialLength[k] );
						if( reverse[k] ) {
							Sequence[] seq = new Sequence[current.getNumberOfElements()];
							for( n = 0; n < seq.length; n++ ) {
								seq[n] = current.getElementAt( n ).reverseComplement();
							}
							current = new Sample( "reverse complement of \"" + current.getAnnotation() +"\"", seq );
						}
						if( result[j] == null ) {
							result[j] = current;
						} else {
							result[j] = Sample.union( result[j], current );
						}
					}
				}
			}
		}
		return used;
	}
	
	/**
	 * This method creates the weights for {@link IndependentProductScoringFunction#extractSequenceParts(int, Sample[], Sample[])}.
	 * 
	 * @param number the number how often the weights should be copied after each other.
	 * @param weights the original weights
	 * 
	 * @return the new weights (might be <code>null</code>)
	 * 
	 * @see #extractSequenceParts(int, Sample[], Sample[])
	 */
	public double[][] extractWeights( int number, double[][] weights ) {
		double[][] res;
		if( number == 1 || weights == null ) {
			res = weights;
		} else {
			res = new double[weights.length][];
			for( int n, j, k = 0; k < res.length; k++ ) {
				n = weights[k].length;
				res[k] = new double[number*n];
				for( j = 0; j < number; j++ ) {
					System.arraycopy( weights[k], 0, res[k], j*n, n );
				}
			}
		}
		return res;
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.AbstractNormalizableScoringFunction#fromXML(java.lang.StringBuffer)
	 */
	protected void fromXML( StringBuffer rep ) throws NonParsableException {
		StringBuffer xml = XMLParser.extractForTag( rep, getInstanceName() );
		alphabets = XMLParser.extractObjectForTags( xml, "AlphabetContainer", AlphabetContainer.class );// TODO XMLP14CONV This and (possibly) the following lines have been converted automatically
		length = XMLParser.extractObjectForTags( xml, "length", int.class );
		ess = XMLParser.extractObjectForTags( xml, "ess", double.class );
		score = XMLParser.extractObjectForTags( xml, "ScoringFunctions", NormalizableScoringFunction[].class );
		set( XMLParser.extractObjectForTags( xml, "index", int[].class ),
				XMLParser.extractObjectForTags( xml, "partialLength", int[].class ),
				XMLParser.extractObjectForTags( xml, "reverse", boolean[].class ));
		try {
			plugIn = XMLParser.extractObjectForTags( xml, "plugIn", boolean.class );
		} catch( Exception e ) {
			plugIn = true;//TODO
		}
		setParamsStarts();
		prepare();
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.ScoringFunction#getInstanceName()
	 */
	public String getInstanceName() {
		return getClass().getSimpleName();
	}
	
	/**
	 * This method returns a deep copy of the internally used {@link NormalizableScoringFunction}.
	 * 
	 * @return a deep copy of the internally used {@link NormalizableScoringFunction}
	 * 
	 * @throws Exception if at least one {@link NormalizableScoringFunction} could not be cloned
	 * 
	 * @see IndependentProductScoringFunction#getIndices()
	 * @see IndependentProductScoringFunction#getPartialLengths()
	 * @see IndependentProductScoringFunction#getReverseSwitches()
	 */
	public NormalizableScoringFunction[] getFunctions() throws Exception {
		return ArrayHandler.clone( score );
	}
	
	/**
	 * This method returns a deep copy of the internally used indices of the {@link NormalizableScoringFunction} for the parts.
	 * 
	 * @return a deep copy of the internally used indices of the {@link NormalizableScoringFunction} for the parts
	 * 
	 * @see IndependentProductScoringFunction#getFunctions()
	 * @see IndependentProductScoringFunction#getPartialLengths()
	 * @see IndependentProductScoringFunction#getReverseSwitches()
	 */
	public int[] getIndices() {
		return index.clone();
	}
	
	/**
	 * This method returns a deep copy of the internally used partial lengths of the parts.
	 * 
	 * @return a deep copy of the internally used partial lengths of the parts
	 * 
	 * @see IndependentProductScoringFunction#getFunctions()
	 * @see IndependentProductScoringFunction#getIndices()
	 * @see IndependentProductScoringFunction#getReverseSwitches()
	 */
	public int[] getPartialLengths() {
		return partialLength.clone();
	}
	
	/**
	 * This method returns a deep copy of the internally used switches for the parts whether to use the corresponding
	 * {@link NormalizableScoringFunction} forward or as reverse complement.
	 * 
	 * @return a deep copy of the internally used switches for the parts whether to use the corresponding 
	 * {@link NormalizableScoringFunction} forward or as reverse complement
	 * 
	 * @see IndependentProductScoringFunction#getFunctions()
	 * @see IndependentProductScoringFunction#getIndices()
	 * @see IndependentProductScoringFunction#getPartialLengths()
	 */
	public boolean[] getReverseSwitches() {
		return reverse.clone();
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.ScoringFunction#getCurrentParameterValues()
	 */
	public double[] getCurrentParameterValues() throws Exception {
		int numPars = this.getNumberOfParameters();
		double[] pars = new double[numPars], help;
		for( int k = 0, i = 0; i < score.length; i++ ) {
			help = score[i].getCurrentParameterValues();
			System.arraycopy( help, 0, pars, k, help.length );
			k += help.length;
		}
		return pars;
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.ScoringFunction#getLogScore(de.jstacs.data.Sequence, int)
	 */
	public double getLogScore( Sequence seq, int start ) {
		double s = 0;
		for( int i = 0; i < index.length; i++ ) {
			if( reverse[i] ) {
				try {
					if( isVariable[index[i]] ) {
						s += ( (VariableLengthScoringFunction)score[index[i]] ).getLogScore( seq.reverseComplement(), seq.getLength() - start - this.start[i] - partialLength[i], partialLength[i] );
					} else {
						s += score[index[i]].getLogScore( seq.reverseComplement(), seq.getLength() - start - this.start[i] - partialLength[i] );
					}
				} catch ( Exception e ) {
					throw new RuntimeException( e.getMessage() );
				}
			} else {
				if( isVariable[index[i]] ) {
					s += ( (VariableLengthScoringFunction)score[index[i]] ).getLogScore( seq, start + this.start[i], partialLength[i] );
				} else {
					s += score[index[i]].getLogScore( seq, start + this.start[i] );
				}
			}
		}
		return s;
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.ScoringFunction#getLogScoreAndPartialDerivation(de.jstacs.data.Sequence, int, de.jstacs.utils.IntList, de.jstacs.utils.DoubleList)
	 */
	public double getLogScoreAndPartialDerivation( Sequence seq, int start, IntList indices, DoubleList partialDer ) {
		double s = 0;
		for( int j, i = 0; i < index.length; i++ ) {
			partIList.clear();
			if( reverse[i] ) {
				try {
					if( isVariable[index[i]] ) {
						s += ( (VariableLengthScoringFunction)score[index[i]] ).getLogScoreAndPartialDerivation( seq.reverseComplement(),
								seq.getLength() - start - this.start[i] - partialLength[i], partialLength[i], partIList, partialDer );
					} else {
						s += score[index[i]].getLogScoreAndPartialDerivation( seq.reverseComplement(), seq.getLength() - start - this.start[i] - partialLength[i], partIList, partialDer );
					}
				} catch ( Exception e ) {
					throw new RuntimeException( e.getMessage() );
				}
			} else {
				if( isVariable[index[i]] ) {
					s += ( (VariableLengthScoringFunction)score[index[i]] ).getLogScoreAndPartialDerivation( seq,
							start + this.start[i], partialLength[i], partIList, partialDer );
				} else {
					s += score[index[i]].getLogScoreAndPartialDerivation( seq, start + this.start[i], partIList, partialDer );
				}
			}
			for( j = 0; j < partIList.length(); j++ ) {
				indices.add( partIList.get( j ) + params[index[i]] );
			}
		}
		return s;
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.ScoringFunction#getNumberOfParameters()
	 */
	public int getNumberOfParameters() {
		if( params == null ) {
			return UNKNOWN;
		} else {
			return params[score.length];
		}
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.AbstractNormalizableScoringFunction#getNumberOfRecommendedStarts()
	 */
	public int getNumberOfRecommendedStarts() {
		int max = score[0].getNumberOfRecommendedStarts();
		for( int i = 1; i < score.length; i++ ) {
			max = Math.max( max, score[i].getNumberOfRecommendedStarts() );
		}
		return max;
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.ScoringFunction#setParameters(double[], int)
	 */
	public void setParameters( double[] params, int start ) {
		for( int i = 0; i < score.length; i++ ) {
			score[i].setParameters( params, start + this.params[i] );
		}
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.Storable#toXML()
	 */
	public StringBuffer toXML() {
		StringBuffer xml = new StringBuffer( 10000 );
		XMLParser.appendObjectWithTags( xml, alphabets, "AlphabetContainer" );
		XMLParser.appendObjectWithTags( xml, length, "length" );
		XMLParser.appendObjectWithTags( xml, ess, "ess" );
		XMLParser.appendObjectWithTags( xml, score, "ScoringFunctions" );
		XMLParser.appendObjectWithTags( xml, index, "index" );
		XMLParser.appendObjectWithTags( xml, partialLength, "partialLength" );
		XMLParser.appendObjectWithTags( xml, reverse, "reverse" );
		XMLParser.appendObjectWithTags( xml, plugIn, "plugIn" );
		XMLParser.addTags( xml, getInstanceName() );
		return xml;
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		StringBuffer sb = new StringBuffer( 100000 );
		for( int i = 0; i < score.length; i++ ) {
			sb.append( "ScoringFunction " + i + ": " + score[i].getInstanceName() + "\n" );
			sb.append( score[i].toString() + "\n" );
		}
		sb.append( "digraph {\n\trankdir=LR\n" );
		sb.append( "\tn" + 0 + "[shape=house, orientation=" + (reverse[0]?90:-90) + ", label=\"emission: " + index[0] + "\\nduration: " + partialLength[0] + "\"]\n" );
		for( int i = 1; i < index.length; i++ ) {
			sb.append( "\tn" + i + "[shape=house, orientation=" + (reverse[i]?90:-90) + ", label=\"emission: " + index[i] + "\\nduration: " + partialLength[i] + "\"]\n" );
			sb.append( "\tn" + (i-1) + "->n" + i  + "\n");
		}
		sb.append( "}" );
		return sb.toString();
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.NormalizableScoringFunction#getLogPriorTerm()
	 */
	public double getLogPriorTerm() {
		double val = 0;
		for( int i = 0; i < score.length; i++ ) {
			val += score[i].getLogPriorTerm();
		}
		return val;
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.NormalizableScoringFunction#addGradientOfLogPriorTerm(double[], int)
	 */
	public void addGradientOfLogPriorTerm( double[] grad, int start ) throws Exception {
		for( int i = 0; i < score.length; i++ ) {
			score[i].addGradientOfLogPriorTerm( grad, start + params[i] );
		}
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.ScoringFunction#isInitialized()
	 */
	public boolean isInitialized() {
		int i = 0;
		while( i < score.length && score[i].isInitialized() ) {
			i++;
		}
		return i == score.length;
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.scoringFunctions.ScoringFunction#initializeFunctionRandomly(boolean)
	 */
	public void initializeFunctionRandomly( boolean freeParams ) throws Exception {
		for( int i = 0; i < score.length; i++ ) {
			score[i].initializeFunctionRandomly( freeParams );
		}
		setParamsStarts();
	}

	/**
	 * This method fills the array {@link IndependentProductScoringFunction#motifIndexArray} in the following way.
	 * The first entry is the index of the {@link ScoringFunction} that handles the motif with index <code>motifIndex</code>.
	 * The second entry is the index of the motif with index <code>motifIndex</code> in the corresponding {@link ScoringFunction}.
	 * The third entry indicates by a &quot;1&quot; that the corresponding {@link ScoringFunction} implements
	 * {@link MutableMotifDiscoverer}, otherwise &quot;-1&quot;.
	 * 
	 * @param motifIndex the index of the motif
	 * 
	 * @throws IndexOutOfBoundsException if the motif index is out of bounds
	 * 
	 * @see IndependentProductScoringFunction#getNumberOfMotifs()
	 */
	private void fillMotifIndexArray( int motifIndex ) throws IndexOutOfBoundsException {
		motifIndexArray[0] = -1;
		for( int i = 1; i < motifs.length; i++ ) {
			if( score[i - 1] instanceof MotifDiscoverer &&
					( motifIndex >= motifs[i-1] && motifIndex < motifs[i] ) ) {
				motifIndexArray[0] = i - 1;
				motifIndexArray[1] = motifIndex - motifs[motifIndexArray[0]];
				motifIndexArray[2] = score[motifIndexArray[0]] instanceof MutableMotifDiscoverer ? 1 : -1;
				return;
			}
		}
		throw new IndexOutOfBoundsException( "Try to get information about motif " + motifIndex + " (but # motifs = " + getNumberOfMotifs() + ")." );
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.motifDiscovery.MutableMotifDiscoverer#initializeMotif(int, de.jstacs.data.Sample, double[])
	 */
	public void initializeMotif( int motifIndex, Sample data, double[] weights ) throws Exception {
		fillMotifIndexArray( motifIndex );
		if( motifIndexArray[2] >= 0 ) {
			( (MutableMotifDiscoverer)score[motifIndexArray[0]] ).initializeMotif( motifIndexArray[1], data, weights );
			setParamsStarts();
		} else {
			System.out.println( "warning: not possible" );
		}
	}
	
	public void initializeMotifRandomly( int motif ) throws Exception {
		fillMotifIndexArray( motif );
		if( motifIndexArray[2] >= 0 ) {
			( (MutableMotifDiscoverer)score[motifIndexArray[0]] ).initializeMotifRandomly( motifIndexArray[1] );
			setParamsStarts();
		} else {
			System.out.println( "warning: not possible" );
		}
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.motifDiscovery.MutableMotifDiscoverer#modifyMotif(int, int, int)
	 */
	public boolean modifyMotif( int motifIndex, int offsetLeft, int offsetRight ) throws Exception {
		fillMotifIndexArray( motifIndex );
		if( motifIndexArray[2] >= 0 ) {
			boolean b = ( (MutableMotifDiscoverer)score[motifIndexArray[0]] ).modifyMotif( motifIndexArray[1], offsetLeft, offsetRight );
			setParamsStarts();
			return b;
		} else {
			return false;
		}
	}

	/**
	 * This method fills for the user-specified value <code>componentIndex</code> the array
	 * {@link IndependentProductScoringFunction#componentIndexArray} with the used component of each
	 * corresponding {@link ScoringFunction} of {@link IndependentProductScoringFunction#score} using
	 * {@link IndependentProductScoringFunction#index}, i.e., it breaks down <code>componentIndex</code>
	 * into the components of each part.
	 * 
	 * @param componentIndex the index of the component in the current model
	 *
	 * @throws IndexOutOfBoundsException if the index is out of bounds
	 */
	private void fillComponentIndexArray( int componentIndex ) throws IndexOutOfBoundsException {
		if( componentIndex < 0 || componentIndex > getNumberOfComponents() ) {
			throw new IndexOutOfBoundsException();
		}
		for( int i = index.length - 1; i >= 0; i-- ) {
			componentIndexArray[i] = componentIndex % components[i];
			componentIndex /= components[i];
		}
	}

	/**
	 * This method fills for the user-specified values <code>componentIndex</code> and <code>motifIndex</code>
	 * the arrays {@link IndependentProductScoringFunction#motifIndexArray} and {@link IndependentProductScoringFunction#componentIndexArray}.
	 * For the later see method {@link IndependentProductScoringFunction#fillComponentIndexArray(int)}.
	 * For {@link IndependentProductScoringFunction#motifIndexArray}, it sets the with the following values. 
	 * The first entry is the index of the corresponding {@link ScoringFunction} in {@link IndependentProductScoringFunction#score}.
	 * The second entry is the <b>global</b> motif index in the corresponding {@link ScoringFunction}.
	 * The third entry indicates by a &quot;1&quot; that the corresponding {@link ScoringFunction} implements
	 * {@link MutableMotifDiscoverer}, otherwise &quot;-1&quot;.
	 * 
	 * @param componentIndex the index of the component of the current instance
	 * @param motifIndex the (local) index of the motif of the current instance
	 * 
	 * @throws IndexOutOfBoundsException if the indices are out of bounds
	 */
	private void fillMotifIndexArrayForComponent( int componentIndex, int motifIndex ) throws IndexOutOfBoundsException {
		fillComponentIndexArray( componentIndex );
		int i = -1, anz = 0;
		do {
			motifIndex -= anz;
			i++;
			if( i == score.length ) {
				throw new IndexOutOfBoundsException();
			}
			fillHashWith( i );
			anz = hash.size();
		} while( anz != 0 && motifIndex < anz );
		motifIndexArray[0] = i;
		motifIndexArray[2] = score[motifIndexArray[0]] instanceof MutableMotifDiscoverer ? 1 : -1;
		
		int[] specificMotifs = new int[anz];
		Iterator<Integer> it = hash.iterator();
		for( i = 0; i < anz; i++ ) {
			specificMotifs[i] = it.next();
		}
		Arrays.sort( specificMotifs );
		
		motifIndexArray[1] = specificMotifs[motifIndex];
	}
	
	private HashSet<Integer> hash;
	
	/**
	 * This method collects in the {@link HashSet} {@link IndependentProductScoringFunction#hash}
	 * the global indices of the motifs of {@link ScoringFunction} <code>score[index]</code> for a given filled
	 * array {@link IndependentProductScoringFunction#components}.
	 *  
	 * @param scoreIndex the index of the {@link ScoringFunction}.
	 * 
	 * @see IndependentProductScoringFunction#score
	 */
	private void fillHashWith( int scoreIndex ) {
		if ( hash == null ) {
			hash = new HashSet<Integer>();
		}
		if( scoreIndex == score.length ) {
			throw new IndexOutOfBoundsException();
		}
		hash.clear();
		if( score[scoreIndex] instanceof MotifDiscoverer ) {
			MotifDiscoverer md = (MotifDiscoverer) score[scoreIndex];
			for( int h, l, m, idx = 0; idx < index.length; idx++ ) {
				if( index[idx] == scoreIndex ) {
					m = md.getNumberOfMotifsInComponent( components[idx] );
					for( l = 0; l < m; l++ ) {
						h = md.getGlobalIndexOfMotifInComponent( components[idx], l );
						if( !hash.contains( h ) ) {
							hash.add( h );
						}
					}
				}
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.motifDiscovery.MotifDiscoverer#getGlobalIndexOfMotifInComponent(int, int)
	 */
	public int getGlobalIndexOfMotifInComponent( int component, int motif ) {
		fillMotifIndexArrayForComponent( component, motif );
		return motifs[motifIndexArray[0]] + motifIndexArray[1];
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.motifDiscovery.MotifDiscoverer#getIndexOfMaximalComponentFor(de.jstacs.data.Sequence)
	 */
	public int getIndexOfMaximalComponentFor( Sequence sequence ) throws Exception {
		int c = 0, idx;
		for( int i = 0; i < index.length; i++ ) {
			if( score[index[i]] instanceof AbstractMixtureScoringFunction ) {
				if( reverse[i] ) {
					idx = ( (AbstractMixtureScoringFunction)score[index[i]] ).getIndexOfMaximalComponentFor(
							sequence.reverseComplement(), sequence.getLength() - start[i] - partialLength[i] );
				} else {
					idx = ( (AbstractMixtureScoringFunction)score[index[i]] ).getIndexOfMaximalComponentFor( sequence, start[i] );
				}
			} else if( score[i] instanceof MotifDiscoverer ) {
				if( reverse[i] ) {
					idx = ( (MotifDiscoverer)score[index[i]] ).getIndexOfMaximalComponentFor(
							sequence.reverseComplement( sequence.getLength() - start[i] - partialLength[i],
									sequence.getLength() - start[i] ) );
				} else {
					idx = ( (MotifDiscoverer)score[index[i]] ).getIndexOfMaximalComponentFor(
							sequence.getSubSequence( score[i].getAlphabetContainer(),
							start[i],
							partialLength[i] ) );
				}
			} else {
				idx = 0;
			}
			c += componentsUntilNow[i] * idx;
		}
		return c;
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.motifDiscovery.MotifDiscoverer#getMotifLength(int)
	 */
	public int getMotifLength( int motif ) {
		fillMotifIndexArray( motif );
		return ( (MotifDiscoverer)score[motifIndexArray[0]] ).getMotifLength( motifIndexArray[1] );
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.motifDiscovery.MotifDiscoverer#getNumberOfComponents()
	 */
	public int getNumberOfComponents() {
		return componentsUntilNow[index.length];
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.motifDiscovery.MotifDiscoverer#getNumberOfMotifs()
	 */
	public int getNumberOfMotifs() {
		return motifs[score.length];
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.motifDiscovery.MotifDiscoverer#getNumberOfMotifsInComponent(int)
	 */
	public int getNumberOfMotifsInComponent( int component ) {
		fillComponentIndexArray( component );
		int i = 0, anz = 0;
		for( ; i < score.length; i++ ) {
			fillHashWith( i );
			anz += hash.size();
		}
		return anz;
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.motifDiscovery.MotifDiscoverer#getProfileOfScoresFor(int, int, de.jstacs.data.Sequence, int, de.jstacs.motifDiscovery.MotifDiscoverer.KindOfProfile)
	 */
	public double[] getProfileOfScoresFor( int component, int motif, Sequence sequence, int startpos, KindOfProfile dist ) throws Exception {
		fillMotifIndexArrayForComponent( component, motif );
		MotifDiscoverer md = (MotifDiscoverer)score[motifIndexArray[0]];
		int w =  md.getMotifLength( motifIndexArray[1] );
		double[] erg = new double[length - w + 1];
		double[] part;
		Arrays.fill( erg, Double.NEGATIVE_INFINITY );
		for( int idx, k, j, i = 0; i < index.length; i++ ) {
			if( index[i] == motifIndexArray[0] ) {
				// part from that ScoringFunction that contains the motif
				
				//find local index
				idx = 0;
				while( md.getGlobalIndexOfMotifInComponent( componentIndexArray[i], idx ) != motifIndexArray[1] ) {
					idx++;
				}
				//compute profile and fill in result array erg
				if( reverse[i] ) {
					part = md.getProfileOfScoresFor( componentIndexArray[i], idx,
							sequence.reverseComplement(), sequence.getLength() - startpos - start[i] - partialLength[i], dist );
					System.arraycopy( part, 0, erg, start[i], partialLength[i] - w + 1 );
				} else {
					part = md.getProfileOfScoresFor( componentIndexArray[i], idx,
							sequence, startpos + start[i], dist );
					for( k = start[i], j = part.length-1; j >= 0; j--, k++ ){
						erg[k] = part[j];
					}
				}
			}
		}
		return erg;
	}

	/*
	 * (non-Javadoc)
	 * @see de.jstacs.motifDiscovery.MotifDiscoverer#getStrandFor(int, int, de.jstacs.data.Sequence, int)
	 */
	public double[] getStrandProbabilitiesFor( int component, int motif, Sequence sequence, int startpos ) throws Exception {
		//TODO
		return new double[]{0.5,0.5};
	}
	
	
	public boolean isNormalized() {
		return isNormalized( score );
	}

	public void adjustHiddenParameters( int index, Sample[] data, double[][] weights ) throws Exception {
		Sample[] part = new Sample[data.length];
		double[][] help;
		for( int a, i = 0; i < score.length; i++ ) {
			if( score[i] instanceof MutableMotifDiscoverer ) {
				a = extractSequenceParts( i, data, part );
				help = a==1 ? weights : extractWeights( a, weights );
				((MutableMotifDiscoverer) score[i]).adjustHiddenParameters( index, part, help );
			}
		}		
		setParamsStarts();
	}
}
