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

import javax.naming.OperationNotSupportedException;

import de.jstacs.data.Sequence;
import de.jstacs.data.WrongLengthException;
import de.jstacs.data.sequences.annotation.SequenceAnnotation;

/**
 * This class is for multidimensional sequences that can be used, for instance, for phylogenetic footprinting.
 * 
 * @author Jens Keilwagen
 */
public class MultiDimensionalDiscreteSequence extends DiscreteSequence {

	private SimpleDiscreteSequence[] content;
	
	/**
	 * This constructor creates an {@link MultiDimensionalDiscreteSequence} from a set of individual {@link SimpleDiscreteSequence}s.
	 * 
	 * @param seqAnnotations the annotations for the aligned sequences
	 * @param sequence the individual sequences that have been aligned
	 * 
	 * @throws WrongLengthException if the sequence have different length
	 */
	public MultiDimensionalDiscreteSequence( SequenceAnnotation[] seqAnnotations, SimpleDiscreteSequence... sequence ) throws WrongLengthException {
		super( sequence[0].getAlphabetContainer(), seqAnnotations );
		int l = sequence[0].getLength();
		for( int s = 1; s < sequence.length; s++ ) {
			if( sequence[s].getLength() != l ) {
				throw new WrongLengthException( "Creating an multi-dimensional sequence, all sequence have to have the same length" );
			}
		}
		content = sequence.clone();
		annotation = seqAnnotations == null ? null : seqAnnotations.clone();
	}

	@Override
	public double continuousVal(int pos) {
		return content[0].continuousVal( pos );
	}

	@Override
	public int discreteVal(int pos) {
		return content[0].discreteVal( pos );
	}

	@Override
	public void fillContainer(int[] container, int pos) {
		for( int s = 0; s < content.length; s++ ) {
			container[s] = content[s].discreteVal( pos );
		}
	}

	@Override
	public int[] getEmptyContainer() {
		return new int[content.length];
	}

	@Override
	public int getLength() {
		return content[0].getLength();
	}

	@Override
	public boolean isMultiDimensional() {
		return true;
	}

	@Override
	protected Sequence<int[]> flatCloneWithoutAnnotation() {
		try {
			return new MultiDimensionalDiscreteSequence(null,content);
		} catch ( WrongLengthException doesNotHappen ) {
			throw new RuntimeException( doesNotHappen.getMessage() );
		}
	}
	
	public MultiDimensionalDiscreteSequence complement( int start, int end ) throws OperationNotSupportedException {
		if( alphabetCon.isReverseComplementable() ) {
			SimpleDiscreteSequence[] compContent = new SimpleDiscreteSequence[content.length];
			for( int s = 0; s < content.length; s++ ) {
				compContent[s] = (SimpleDiscreteSequence) content[s].complement( start, end );
			}
			try {
				return new MultiDimensionalDiscreteSequence( null, compContent );
			} catch ( WrongLengthException doesNotHappen ) {
				throw new RuntimeException( doesNotHappen.getMessage() );
			}
		} else {
			throw new OperationNotSupportedException( "The alphabet of sequence has to be complementable." );
		}
	}

	public MultiDimensionalDiscreteSequence reverseComplement( int start, int end ) throws OperationNotSupportedException {
		if( rc != null && start == 0 && end == getLength() ) {
			return (MultiDimensionalDiscreteSequence) rc;
		} else if( alphabetCon.isReverseComplementable() ) {
			MultiDimensionalDiscreteSequence revComp;
			try {
				SimpleDiscreteSequence[] revCompContent = new SimpleDiscreteSequence[content.length];
				for( int s = 0; s < content.length; s++ ) {
					revCompContent[s] = (SimpleDiscreteSequence) content[s].reverseComplement( start, end );
				}
				revComp = new MultiDimensionalDiscreteSequence( null, revCompContent );
				if( start == 0 && end == getLength() ) {
					rc = revComp;
					((MultiDimensionalDiscreteSequence)rc).rc = this;
				}
				return revComp;
			} catch ( Exception e ) {
				// the current sequence is defined on the correct alphabet => so no WrongAlphabetException can occur
				RuntimeException doesNotHappen = new RuntimeException( e.getMessage() );
				doesNotHappen.setStackTrace( e.getStackTrace() );
				throw doesNotHappen;
			}
		} else {
			throw new OperationNotSupportedException( "The alphabet of sequence has to be reverse-complementable." );
		}
	}
	
	protected Object getEmptyRepresentation() {
		StringBuffer[] rep = new StringBuffer[content.length];
		for( int i = 0; i < content.length; i++ ) {
			rep[i] = new StringBuffer();
		}
		return rep;
	}
	
	protected void addToRepresentation( Object representation, int pos, String delim ) {
		for( int i = 0; i < content.length; i++ ) {
			((StringBuffer[])representation)[i].append( alphabetCon.getSymbol( pos, content[i].discreteVal( pos ) ) + delim );
		}
	}
	protected String getStringRepresentation( Object representation ) {
		StringBuffer res = new StringBuffer();
		for( int i = 0; i < content.length; i++ ) {
			res.append( ((StringBuffer[])representation)[i] );
			res.append( "\n" );
		}
		return res.toString();
	}
	
	protected int hashCodeForPos( int pos ) {
		int h = 0;
		for( int i = 0; i < content.length; i++ ) {
			h = 31 * h + content[i].hashCodeForPos( pos );
		}
		return h;
	}
}
