/*
 *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 Phillip Lord (p.lord@hgmp.mrc.ac.uk)
 * whilst at the University of Manchester as a Pfizer post-doctoral 
 * Research Fellow. 
 *
 * The initial code base is copyright by Pfizer, or 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.group; // Package name inserted by JPack
import java.lang.UnsupportedOperationException;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import uk.ac.man.bioinf.sequence.Sequence;
import uk.ac.man.bioinf.sequence.SequenceType;
import uk.ac.man.bioinf.sequence.alignment.GappedSequence;
import uk.ac.man.bioinf.sequence.alignment.NoSuchSequenceException;
import uk.ac.man.bioinf.sequence.alignment.SequenceAlignment;
import uk.ac.man.bioinf.sequence.alignment.event.AlignmentEvent;
import uk.ac.man.bioinf.sequence.alignment.event.AlignmentEventType;
import uk.ac.man.bioinf.sequence.alignment.event.AlignmentListener;
import uk.ac.man.bioinf.sequence.alignment.event.AlignmentListenerSupport;
import uk.ac.man.bioinf.sequence.alignment.event.AlignmentVetoException;
import uk.ac.man.bioinf.sequence.alignment.event.VetoableAlignmentEvent;
import uk.ac.man.bioinf.sequence.alignment.event.VetoableAlignmentListener;
import uk.ac.man.bioinf.sequence.event.SequenceEvent;
import uk.ac.man.bioinf.sequence.event.SequenceListener;
import uk.ac.man.bioinf.sequence.event.SequenceListenerSupport;
import uk.ac.man.bioinf.sequence.event.SequenceVetoException;
import uk.ac.man.bioinf.sequence.event.VetoableSequenceEvent;
import uk.ac.man.bioinf.sequence.event.VetoableSequenceListener;
import uk.ac.man.bioinf.sequence.geom.SequenceAlignmentRectangle;
import uk.ac.man.bioinf.sequence.identifier.Identifier;
import uk.ac.man.bioinf.util.IntArrayList;


/**
 * GroupSequenceAlignment.java
 *
 * This is a sequence group which also reflects a sequence
 * alignment. The sequences will be given out in the same order, as
 * they are in the SequenceAlignment. A given group is linked
 * specifically to a single SequenceAlignment. If sequences are
 * removed from the SequenceAlignment then they are automatically
 * removed from this class. 
 *
 * Created: Thu Jun  1 23:20:37 2000
 *
 * @author Phillip Lord
 * @version $Id: GroupSequenceAlignment.java,v 1.9 2001/04/11 17:04:43 lord Exp $
 */

public class GroupSequenceAlignment extends AbstractGappedSequenceGroup implements SequenceAlignment, AlignmentListener
{
  private SequenceAlignment alignment;
  // this should store the index of the sequence in the sequence
  // alignment, at the relevant index in this SequenceAlignment
  private IntArrayList seqArray = new IntArrayList();
  private int changeNumber = 0;
  private AlignmentListenerSupport alignSupp = new AlignmentListenerSupport();
  private SequenceListenerSupport  seqSupp   = new SequenceListenerSupport();
  
  /**
   * Start an empty sequence group associated with the given sequence
   * alignment. 
   * @param alignment the alignment
   */
  public GroupSequenceAlignment( SequenceAlignment alignment )
  {
    this.alignment = alignment;
    alignment.addAlignmentListener( this );
    alignment.addSequenceListener ( this );
  }
  
  /**
   * Starts an Sequence group with the sequences given in the IntArrayList
   * @param alignment
   * @param seqArray
   */
  public GroupSequenceAlignment( SequenceAlignment alignment, IntArrayList seqArray )
  {
    this( alignment );
    
    int length = alignment.getNumberSequences();
    for( int i = 0; i < seqArray.size(); i++ ){
      int val = seqArray.get( i );
      if( val < 1 || val > length ){
	throw new 
	  IllegalArgumentException( "The value " + val + " at position " + i + " is outside the allowable range" );
      }
    }
  }
  
  
  public Sequence[] getSequences()
  {
    Sequence[] retn = new Sequence[ seqArray.size() ];
    
    for( int i = 0; i < retn.length; i++ ){
      retn[ i ] = alignment.getSequenceAt( seqArray.get( i ) );
    } //end for( i < retn.length; )
    
    return retn;
  }
  
  /**
   * Adds a sequence to this group. 
   * @throws IllegalArgumentException if this sequence is not part of
   * the associated SequenceAlignment
   * @param sequence the sequence
   */
  public void addSequenceToGroup( Sequence sequence )
  {
    try{
      alignSupp.fireVetoableAlignmentEvent
	( new VetoableAlignmentEvent( this, seqArray.size(), AlignmentEventType.INSERT ) );
    }
    catch( AlignmentVetoException ave ){
      // (PENDING:- PL) I'm not really sure what I should do at this
      // point
    }
    
    int index = alignment.getSequenceIndex( (GappedSequence)sequence );
    if( index == -1 ) 
      throw new IllegalArgumentException( "The sequence must be a member of the associated sequence alignment" );
    seqArray.add( index );
    
    alignSupp.fireAlignmentEvent
      ( new AlignmentEvent( this, seqArray.size(), AlignmentEventType.INSERT ) );
    
    changeNumber++;
  }
  
  public void addSequence(GappedSequence seq, int inset) 
    throws AlignmentVetoException 
  {
    /* (PENDING: JNS) 26.10.00 an implementation would be nice when I
     * understand what this should do in respect of the group.
     */
  }
    
  public int getLength()
  {
    return alignment.getLength();
  }
  
  public void removeSequenceFromGroup( Sequence sequence )
  {
    int index = alignment.getSequenceIndex( (GappedSequence)sequence );
    if( index != -1 ){

      try{
	alignSupp.fireVetoableAlignmentEvent
	  ( new VetoableAlignmentEvent( this, index, AlignmentEventType.DELETE ) );
      }
      catch( AlignmentVetoException ave ){
	// (PENDING:- PL) I'm not really sure what to do at this point
      }

      // may be able to move this to a binary search if I ensure the
      // array is sorted.
      seqArray.remove( seqArray.linearSearch( index ) );
      changeNumber++;

      alignSupp.fireAlignmentEvent
	( new AlignmentEvent( this, index, AlignmentEventType.DELETE ) );
    }
  }

  public GappedSequence removeSequence(int seqIndex)
    throws AlignmentVetoException 
  {
    /* (PENDING: JNS) 26.10.00 an implementation would be nice when I
     * understand what this should do in respect of the group.
     */
    return null;
  }

  public void setInset(int seqIndex, int size) throws AlignmentVetoException 
  {
    /* (PENDING: JNS) 26.10.00 an implementation would be nice when I
     * understand what this should do in respect of the group.
     */
  }
  
  public void clearSequences()
  {
    try{
      alignSupp.fireVetoableAlignmentEvent
	( new VetoableAlignmentEvent( this, 1, seqArray.size(), AlignmentEventType.DELETE ) );
    }
    catch( AlignmentVetoException sve ){
      // (PENDING:- PL) Not really sure what to do here      
    }
    
    seqArray.clear();
    changeNumber++;
    
    alignSupp.fireAlignmentEvent
      ( new AlignmentEvent( this, 1, seqArray.size(), AlignmentEventType.DELETE ) );
  }
  
  public int getNumberSequences()
  { 
    return seqArray.size();
  }
  
  public boolean containsSequence( Sequence sequence )
  {
    int alignmentIndex = alignment.getSequenceIndex( (GappedSequence)sequence );
    return ( seqArray.linearSearch( alignmentIndex ) != -1 );
  }

  public Iterator iterator()
  {
    return new Iterator(){
	private int expectedChangeNumber = changeNumber;
	private int index = 0;
	public boolean hasNext()
	{
	  if( expectedChangeNumber != changeNumber ){
	    throw new ConcurrentModificationException( "GroupSequenceAlignment has been modified" );
	  }
	  
	  return( index < getLength() );
	}
	
	public Object next()
	{
	  if( expectedChangeNumber != changeNumber ){
	    throw new ConcurrentModificationException( "GroupSequenceAlignment has been modified" );
	  }
	  
	  return alignment.getSequenceAt( seqArray.get( index++ ) );
	}
	
	public void remove()
	{
	  throw new UnsupportedOperationException( "GroupSequenceAlignment does not support the remove method" );
	}
      };
  }

  // sequence alignment interface.
  public GappedSequence getSequenceAt( int index )
  {
    return alignment.getSequenceAt( seqArray.get( index - 1) );
  }
    
  public SequenceAlignment getSubAlignment( int startPos, int endPos, int startSeq, int endSeq )
  {
    throw new RuntimeException( "Not implemented yet" );
  }
  
  public SequenceAlignment getSubAlignment( SequenceAlignmentRectangle rect )
  {
    return getSubAlignment( rect.getX(), rect.getWidth(), rect.getY(), rect.getHeight() );
  }
  public SequenceType getSequenceType()
  {
    return alignment.getSequenceType();
  }

  public int getInset( int index )
  {
    try{
      return alignment.getInset( seqArray.get( index - 1 ) );
    }
    catch( NoSuchElementException nsee ){
      throw new NoSuchSequenceException( this, index );
    }
  }
  
  public int getSequenceIndex( GappedSequence sequence )
  {
    return seqArray.linearSearch( alignment.getSequenceIndex( sequence ) ) + 1;
  }
  
  public void removeAlignmentListener( AlignmentListener listener )
  {
    alignSupp.removeAlignmentListener( listener );
  }
  
  public void addAlignmentListener( AlignmentListener listener )
  {
    alignSupp.addAlignmentListener( listener );
  }
  
  public void removeVetoableAlignmentListener( VetoableAlignmentListener listener )
  {
    alignSupp.removeVetoableAlignmentListener( listener );
  }
  
  public void addVetoableAlignmentListener( VetoableAlignmentListener listener )
  {
    alignSupp.addVetoableAlignmentListener( listener );
  }
  
  
  public void changeOccurred( SequenceEvent event )
  {
    if( containsSequence( (Sequence)event.getSource() ) )
      seqSupp.fireSequenceEvent( event );
  }
  
  public void vetoableChangeOccurred( VetoableSequenceEvent event ) throws SequenceVetoException
  {
    if( containsSequence( (Sequence)event.getSource() ) )
      seqSupp.fireVetoableSequenceEvent( event );
  }

  public void changeOccurred( AlignmentEvent event )
  {
    alignSupp.fireAlignmentEvent( new AlignmentEvent( this, 1, getNumberSequences(), 
                                                      AlignmentEventType.UNSPECIFIED ) );
  }
  
  public void vetoableChangeOccurred( VetoableAlignmentEvent event ) throws AlignmentVetoException
  {
    alignSupp.fireVetoableAlignmentEvent( new VetoableAlignmentEvent( this, 1, getNumberSequences(), 
                                                                      AlignmentEventType.UNSPECIFIED ) );
  }
  
  public void addSequenceListener( SequenceListener listener )
  {
    seqSupp.addSequenceListener( listener );
  }
  
  public void removeSequenceListener( SequenceListener listener )
  {
    seqSupp.removeSequenceListener( listener );
  }
  
  // this method can safely delegate to the sequence alignment. 
  public void addVetoableSequenceListener( VetoableSequenceListener listener )
  {
    seqSupp.addVetoableSequenceListener( listener );
  }
  
  public void removeVetoableSequenceListener( VetoableSequenceListener listener )
  {
    seqSupp.removeVetoableSequenceListener( listener );
  }

  public Identifier getIdentifier()
  {
    return alignment.getIdentifier();
  }
} // GroupSequenceAlignment



/*
 * ChangeLog
 * $Log: GroupSequenceAlignment.java,v $
 * Revision 1.9  2001/04/11 17:04:43  lord
 * Added License agreements to all code
 *
 * Revision 1.8  2001/03/12 16:50:21  lord
 * Now fully supports interface, and sequence change events
 *
 * Revision 1.7  2001/01/23 17:59:19  lord
 * Added getSubAlignment( SequenceAlignmentRectangle ) method because I
 * thought it would be useful.
 *
 * Revision 1.6  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.5  2000/10/19 17:54:25  lord
 * New constructor added.
 *
 * Revision 1.4  2000/09/11 13:17:10  lord
 * Added identifiable support
 *
 * Revision 1.3  2000/06/27 16:11:46  lord
 * Added event handling to the "clear" method which clearly needs it
 *
 * Revision 1.2  2000/06/13 11:02:58  lord
 * Fixed event handling
 *
 * Revision 1.1  2000/06/05 14:46:25  lord
 * Initial checkin
 *
 */
