/*
 *This library is free software; you can redistribute it and/or
 *modify it under the terms of the GNU Lesser General Public
 *License as published by the Free Software Foundation; either
 *version 2.1 of the License, or (at your option) any later version.
 *
 *This library 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
 *Lesser General Public License for more details.
 *
 *You should have received a copy of the GNU Lesser General Public
 *License along with this library; if not, write to the Free Software
 *Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* 
 * This software was written by Julian Selley (j.selley@man.ac.uk)
 * whilst at the University of Manchester as a Research Assistant. 
 *
 * The initial code base is copyright by the University of Manchester. 
 * Modifications to the initial code base are copyright of their 
 * respective authors, or their employers as appropriate. Authorship 
 * of the modifications may be determined from the ChangeLog placed 
 * at the end of this file
 */

package uk.ac.man.bioinf.sequence.alignment; // Package name inserted by JPack

import java.util.ArrayList;
import uk.ac.man.bioinf.sequence.Element;
import uk.ac.man.bioinf.sequence.InvalidSequenceTypeException;
import uk.ac.man.bioinf.sequence.NoSuchSequenceElementException;
import uk.ac.man.bioinf.sequence.SequenceType;
import uk.ac.man.bioinf.sequence.Sequences;
import uk.ac.man.bioinf.sequence.alignment.NoSuchSequenceException;
import uk.ac.man.bioinf.sequence.identifier.Identifier;
import uk.ac.man.bioinf.sequence.identifier.NoIdentifier;
import uk.ac.man.bioinf.util.IntArrayList;


/**
 * DefaultSequenceAlignment.java
 *
 * A class to model the multiple sequence alignment.
 * NB: Sequence numbering starts at 1 <b>NOT</b> 0.
 *
 * Created: Mon Feb 21 13:51:11 2000
 *
 * @author J Selley
 * @version $Id: DefaultSequenceAlignment.java,v 1.24 2001/04/11 17:04:43 lord Exp $
 */

public class DefaultSequenceAlignment extends AbstractSequenceAlignment
{
  /* (PENDING: JNS) 24.10.00 Change msa from ArrayList to
   * GappedSequenceArrayList to prevent nasty casting.
   */
  // having array list here makes it nasty for casting
  private ArrayList msa;
  private Identifier ident;
  private SequenceType type;
  private int length;
  private IntArrayList inset;

  protected void init(GappedSequence[] seqs, SequenceType type,
		      int[] inset, Identifier ident) 
  {
    // set up msa, inset, type and ident
    this.msa = new ArrayList();
    this.inset = new IntArrayList();
    this.type = type;
    this.ident = ident;
    
    // create sequences and put in msa
    for (int i = 0; i < seqs.length; i++) {
      this.msa.add(seqs[ i ]);
      this.inset.add(inset[ i ]);
      /*
       * this may be changed later to enable controlled firing of events
       * ie: so that events are not nedlessly fired until this object
       * has a listener, at which point this will register with all
       * sequences and start relaying the events. However, for the meantime
       * this trivial implementation has been provided because MSAs
       * will rarely not have a listener
       */
      ((GappedSequence)msa.get(i)).addSequenceListener(this);
      // check the length of the sequence is not the longest
      if (((GappedSequence)msa.get(i)).getGappedLength() + this.inset.get(i) > this.length) 
	this.length = ((GappedSequence)msa.get(i)).getGappedLength() + this.inset.get(i);
    }
  }
  
  public DefaultSequenceAlignment(SequenceType type) 
  {
    this.init(new GappedSequence[0], type, new int[0], new NoIdentifier());
  }

  public DefaultSequenceAlignment(SequenceType type, Identifier ident) 
  {
    this.init(new GappedSequence[0], type, new int[0], ident);
  }
  
  public DefaultSequenceAlignment(Element[][] seqs, SequenceType type)
  {
    GappedSequence[] msa = new GappedSequence[seqs.length];
    
    // create sequences and put in msa
    for (int i = 0; i < seqs.length; i++) {
      msa[i] = new DefaultGappedSequence(seqs[i], type);
      /*
       * this may be changed later to enable controlled firing of events
       * ie: so that events are not nedlessly fired until this object
       * has a listener, at which point this will register with all
       * sequences and start relaying the events. However, for the meantime
       * this trivial implementation has been provided because MSAs
       * will rarely not have a listener
       */
      msa[i].addSequenceListener(this);
    }

    // create inset arry to correspond to sequences
    this.init(msa, type, new int[msa.length], new NoIdentifier());
  }

  public DefaultSequenceAlignment( GappedSequence[] seqs, SequenceType type, 
				   int[] inset, Identifier ident )
  {
    this.init(seqs, type, inset, ident);
  }
    
    
  public DefaultSequenceAlignment( GappedSequence[] seqs, SequenceType type, int[] inset )
  {
    this.init(seqs, type, inset, new NoIdentifier());
  }
 
  public DefaultSequenceAlignment(GappedSequence[] seqs, SequenceType type)
  {
    this.init(seqs, type, new int[seqs.length], new NoIdentifier());
  }

  public DefaultSequenceAlignment
    (SequenceAlignment msa, SequenceType type)
  {
    GappedSequence[] seqs = new GappedSequence[msa.getNumberSequences()];
    int[] inset = new int[msa.getNumberSequences()];
    
    // iterate through the sequences in msa to copy
    for (int i = 0; i < msa.getNumberSequences(); i++) {
      seqs[i] = Sequences.getElementsAsGappedSequence
	((msa.getSequenceAt(i)).getGappedSequenceAsElements(), type);
      inset[i] = msa.getInset(i);
      /*
       * this may be changed later to enable controlled firing of events
       * ie: so that events are not nedlessly fired until this object
       * has a listener, at which point this will register with all
       * sequences and start relaying the events. However, for the meantime
       * this trivial implementation has been provided because MSAs
       * will rarely not have a listener
       */
      seqs[i].addSequenceListener(this);
    }

    this.init(seqs, type, inset, new NoIdentifier());
  }
  
  /**
   * Returns a sub-alignment, given the exact location in this alignment.
   * NB: Sequencing numbering and alignment numbering start at 1 <b>NOT</b>
   * 0.
   *
   * @param startPos the start position for the sub-alignment (i.e: x1)
   * @param length   the length of the sub-alignment
   * @param startSeq the first sequence in the current alignment to be
   *                 included in the sub-alignment (i.e: y1)
   * @param numSeqs  the number of sequences to be included in the 
   *                 sub-alignment
   * @return the sub-MSA
   */
  public SequenceAlignment getSubAlignment(int startPos, int length,
					   int startSeq, int numSeqs)
  {
    // check all params are "logical"
    // NB: all numbering starts at 1 NOT 0
    if (startPos < 1)
      throw new NoSuchSequenceElementException
	("Attempt to access element at zero or negative index. Sequences start " +
	 "at index 1");
    if ( (startPos + length) > getLength() )
      throw new NoSuchSequenceElementException
	("Attempt to access element greater than sequence alignment length");
    if (startSeq < 1) 
      throw new NoSuchSequenceException
	("Attempt to access a sequence at zero or negative index. Indexing " +
	 "starts at 1");
    if (((startSeq - 1) + numSeqs) > msa.size())
      throw new NoSuchSequenceException
	("Attempt to access a sequence with an index greater than the " +
	 "alignment length");
    
    // define the sub-alignment
    GappedSequence[] sequences = new GappedSequence[ numSeqs ];
    int[] insets = new int[ numSeqs ];
    
    int index = 0;
    
    // ...and cycle through entering each sub-sequence into alignment
    for ( int i = startSeq; i < ( startSeq + numSeqs ); i++ ) {
      // there are several different possibilities that we need to
      // cover here. The first of these is that the GappedSequence
      // either finishes before the start of this subsequence, or
      // starts after it.
      
      // starts after it...
      if( getInset( i ) > ( startPos + length ) ||
	  // ends before it
	  ( getInset( i ) + getSequenceAt( i ).getLength() ) < startPos ){
	
	insets[ index ] = startPos + length;
	sequences[ index++ ] = EmptyGappedSequence.getInstance();
      }
      else{
	// calculate distance to start of sequence
	insets[ index ] = getInset( i ) - startPos + 1;
	// we only want this distance if its positive, otherwise there
	// is not inset. 
	insets[ index ] = Math.max( insets[ index ], 0 );
        
	
	
	// calculate the start position. This is 1 minimum (if we are
	// starting before the sequence starts) or the startPosition
	// in the alignment minus the inset. 
	int beginingSubSeq = Math.max( 1, startPos - getInset( i ) );
	
        // work out the length that we want to cut
	int subSeqLength = Math.min
	  // this one deals with the situation when we are going of
	  // the end of the sequence. 
	  ( (getSequenceAt( i ).getLength() - beginingSubSeq + 1),
	    // and this one deals with the situation where we are not.
	    (length - insets[ index ] ) );
        
	// fetch the new sub sequence
        sequences[ index++ ] = 
          getSequenceAt( i ).getGappedSubSequence( beginingSubSeq, subSeqLength );
      }
    }
    
    // return a new instance of DefaultSequenceAlignment
    return new DefaultSequenceAlignment( sequences, this.type, insets );
  }
  
  /**
   * Returns the sequence type for the alignment (e.g: Protien Sequence).
   *
   * @return the sequence type
   */
  public SequenceType getSequenceType()
  {
    return this.type;
  }

  /**
   * Returns the sequence, given a specific location in the MSA.
   * NB: Alignment sequence numbering begins at 1 <b>NOT</b> 0.
   *
   * @param index the location in the MSA of the target sequence
   * @return the sequence
   */
  public GappedSequence getSequenceAt(int index)
  {
    try{
      // remember sequence numbering in an alignment begins at 1 NOT 0
      return (GappedSequence)this.msa.get(index - 1);
    }
    catch( IndexOutOfBoundsException aie ){
      // throw and exception of the right type
      throw new NoSuchSequenceException( "The index " + index + " is out of bounds", this, index );
    }
  }
  
  /**
   * Returns the location of a sequence, in the multiple sequence alignment.
   * NB: 
   *
   * @param seq the sequence of interest
   * @return the location (-1 if sequence not found)
   */
  public int getSequenceIndex(GappedSequence seq)
  {
    // iterate through msa and return i if sequence found
    for (int i = 0; i < msa.size(); i++) {
      if ((GappedSequence)msa.get(i) == seq)
	// sequence numbering starts at 1 NOT 0
	return i + 1;
    }
    
    // else return -1
    return -1;
  }
  
  protected void setLengthQuietly( int length )
  {
    this.length = length;
  }
  
  /**
   * Adds a sequence quietly to the alignment (i.e., without notifying
   * listeners - it should really only be done from it's parent
   * procedure addSequence).
   *
   * @param seq the sequence
   * @param inset the sequence inset in the alignment
   */
  protected void addSequenceQuietly(GappedSequence seq, int inset) 
  {
    if( seq.getSequenceType() != type ) 
      throw new InvalidSequenceTypeException
	( "All sequences must be of the same type" );
    
    // add this object as a listener of the sequence
    seq.addSequenceListener(this);

    // add the sequence and its inset to the alignment
    this.msa.add(seq);
    this.inset.add(inset);
  }
  
  /**
   * Removes a sequence from the alignment, quietly, i.e., without
   * notifying any listeners. This procedure ought only to be called
   * from its "parent", removeSequence (unless you really know what
   * you are doing - hence the public nature of the function).
   * @param seqIndex the sequence index
   * @return the removed sequence
   */
  protected GappedSequence removeSequenceQuietly(int seqIndex) 
  {
    GappedSequence removedSequence = null;
    
    try {
      // N.B.: sequence indexing starts at 1 NOT 0
      // remove the sequence from the msa and store
      removedSequence = (GappedSequence)msa. remove(seqIndex - 1);
      // remove the associated inset
      inset.remove(seqIndex - 1);

      // remove ourself as a listener of the sequence
      removedSequence.removeSequenceListener(this);
    } catch (IndexOutOfBoundsException e) {
      throw new NoSuchSequenceException(this, seqIndex);
    }

    // return the sequence removed - will be null if couldn't find
    return removedSequence;
  }
  
  /**
   * Sets the inset (or preceeding number of gaps) for a sequence in the
   * alignment.
   *
   * @param seqIndex the sequence index for the inset to be added to
   * @param inset the inset
   */
  protected void setInsetQuietly(int seqIndex, int inset)
  {
    try{
      // NB: seqIndex runs from 1 NOT 0
      this.inset.set(seqIndex - 1, inset);
    }
    catch( ArrayIndexOutOfBoundsException aie ){
      throw new NoSuchSequenceException(this, seqIndex);
    }
  }

  /**
   * Returns the inset of a particular sequence in the alignment.
   *
   * @param seqIndex the sequence index
   * @return the inset
   */
  public int getInset(int seqIndex) 
  {
    try{
      // NB: seqIndex starts at 1 NOT 0
      return this.inset.get(seqIndex - 1);
    }
    catch( ArrayIndexOutOfBoundsException aie ){
      throw new NoSuchSequenceException(this, seqIndex);
    }
  }
  
  /**
   * Returns the total number of sequences in the multiple sequence alignment.
   *
   * @return the total number of sequences
   */
  public int getNumberSequences()
  {
    return msa.size();
  }
  
  /**
   * Returns the length of the multiple sequence alignment.
   *
   * @return the length
   */
  public int getLength()
  {
    return this.length;
  }
  
  public Identifier getIdentifier()
  {
    return ident;
  }
} // DefaultSequenceAlignment



/*
 * ChangeLog
 * $Log: DefaultSequenceAlignment.java,v $
 * Revision 1.24  2001/04/11 17:04:43  lord
 * Added License agreements to all code
 *
 * Revision 1.23  2001/01/19 19:49:11  lord
 * Fixed bug in exception handling
 *
 * Revision 1.22  2001/01/04 15:08:49  lord
 * getSubAlignment now copes correctly with insets.
 *
 * Revision 1.21  2000/10/31 15:49:41  lord
 * Tightened access on some methods which bypass the event system
 *
 * Revision 1.20  2000/10/26 18:54:18  lord
 * setInsetQuietly methods to be protected
 *
 * Revision 1.19  2000/10/26 12:42:49  jns
 * o added editing facilities to SA - this includes insertion/deletion of gaps,
 * addition/removal of sequences from an alignment. It involved resolving some
 * conflicts with the group stuff.
 *
 * Revision 1.18  2000/09/22 14:31:00  jns
 * o bug fixing: the length of the alignment did not take into account
 * the insets. This has now been fixed by setting an init function which
 * all constructors have to and will have to call. Not elegant, but it
 * works.
 *
 * Revision 1.17  2000/09/11 13:18:13  lord
 * Added identifier support
 *
 * Revision 1.16  2000/06/13 11:08:12  lord
 * Fixed getLength/getGappedLength bug.
 * Exception Handling now fulfils the interface requirements
 *
 * Revision 1.15  2000/06/05 14:24:29  lord
 * Cosmetic changes
 *
 * Revision 1.14  2000/05/30 16:04:18  lord
 * Have rationalised and sorted all the import statements
 *
 * Revision 1.13  2000/05/18 17:04:00  lord
 * No longer clones all of the Sequences that it is given in the cons.
 * Added support for passing insets into cons
 *
 * Revision 1.12  2000/03/27 18:48:12  lord
 * Removed length increase handling upwards.
 * Added setLengthQuietly method
 *
 * Revision 1.11  2000/03/21 18:22:33  lord
 * Fixed bug in setInsetQuiety. Now increases alignment length if necessary
 *
 * Revision 1.10  2000/03/21 14:33:14  jns
 * o corrected my ****-up, and sorted this class so it compiles.
 *
 * Revision 1.9  2000/03/21 13:13:03  jns
 * adding index checking to setInset and getInset.
 *
 * Revision 1.8  2000/03/20 16:08:40  jns
 * o setting ability to deal with insets.
 *
 * Revision 1.7  2000/03/14 19:44:25  jns
 * o debugging
 * o sorting problem of translation between gapped and ungapped indicies.
 *
 * Revision 1.6  2000/03/14 16:53:43  jns
 * o messed up on getSubAlignment... corrected.
 *
 * Revision 1.5  2000/03/14 16:42:41  jns
 * o changed getSubAlignment to use Sequences static class.
 *
 * Revision 1.4  2000/03/14 16:34:22  jns
 * o changed getSubAlignment to deal with length and numSeqs rather than endSeq
 * and endPos.
 * o added sequence listener support to register with all sequences as added to
 * the aligment in the constructors. This may be changed at a later date to
 * prevent the firing of events when there are no listeners to this object. This
 * would work by only registering with each sequence when a listener registers
 * with this object. The reason for not immediately implementing this is that
 * it is assumed that normally an alignment will have listeners.
 *
 * Revision 1.3  2000/03/14 16:13:46  jns
 * added use of Sequences static class.
 *
 * Revision 1.2  2000/03/10 17:55:15  lord
 * Moved all of the listener handling to super class
 *
 * Revision 1.1  2000/03/10 12:11:19  jns
 * initial code.
 *
 */


