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


/**
 * AbstractSequenceAlignment.java
 *
 * An abstract class to extend for the basic definition of a multiple
 * sequence alignment. This class basically ensures listener handling.
 *
 * Created: Mon Feb 21 13:02:10 2000
 *
 * @author J Selley
 * @version $Id: AbstractSequenceAlignment.java,v 1.13 2001/04/11 17:04:43 lord Exp $
 */

public abstract class AbstractSequenceAlignment 
  implements SequenceAlignment, VetoableSequenceListener
{
  
  protected abstract void setLengthQuietly( int length );

  /**
   * Adds a sequence to the alignment, quietly, without notifying
   * listeners.
   * @param sequence the sequence
   * @param inset the inset of the sequence
   */
  protected abstract void addSequenceQuietly(GappedSequence sequence, int inset);

  public SequenceAlignment getSubAlignment( SequenceAlignmentRectangle rect )
  {
    return getSubAlignment( rect.getX(), rect.getWidth(), rect.getY(), rect.getHeight() );
  }
  
  /**
   * Adds a sequence to the alignment. This action can be vetoed by a
   * listener.
   * @param seq the sequence
   * @exception AlignmentVetoException veto to the addition
   */
  public void addSequence(GappedSequence seq, int inset) 
    throws AlignmentVetoException 
  {
    // signal listeners of the intention to add a sequence
    msls.fireVetoableAlignmentEvent
      (new VetoableAlignmentEvent(this, this.getNumberSequences() + 1, 
				  AlignmentEventType.INSERT));
    
    // add the sequence
    addSequenceQuietly(seq, inset);
    
    // check to see whether this has caused an increase in the overall
    // length of the Alignment and signal the subclass implementation
    // if it has. 
    ensureLengthIncrease( seq.getGappedLength() + inset, getNumberSequences() );
    
    // notify listeners that the removal has taken place successfully
    msls.fireAlignmentEvent
      (new AlignmentEvent(this, this.getNumberSequences() + 1, 
			  AlignmentEventType.INSERT));
  }
  
  /**
   * Calls addSequence on every sequence in the supplied
   * alignment. This basically provides a simple shortcut method.
   *
   * @param align the alignment of sequences to be added
   * @exception AlignmentVetoException veto to the addition - this can
   * be sequence based as well as alignment based
   */
  public void addSequence(SequenceAlignment align) 
    throws AlignmentVetoException 
  {
    /* (PENDING: JNS) 11.12.00 This may be overly complex, and result
     * in a lot of alignment event firing. i.e., this may need
     * cleaning up.
     */
    // signal listeners of intention to add the alignment
    msls.fireVetoableAlignmentEvent
      (new VetoableAlignmentEvent(this, this.getNumberSequences() + 
				  align.getNumberSequences(),
				  AlignmentEventType.INSERT));
    
    // for each sequence in the given alignment...
    for (int i = 0; i < align.getNumberSequences(); i++) {
      // add the sequence via call to addSequence
      addSequence(align.getSequenceAt(i), align.getInset(i));
    }

    // notify listeners that the insertion has taken place
    // successfully
    msls.fireAlignmentEvent
      (new AlignmentEvent(this, this.getNumberSequences() + 
			  align.getNumberSequences(),
			  AlignmentEventType.INSERT));
  }
  
  /**
   * Remove a sequence from the alignment, quietly, without notifying
   * listeners.
   * @param seqIndex the sequence index
   * @return the sequence
   */
  protected abstract GappedSequence removeSequenceQuietly(int seqIndex);
  
  /**
   * Removes a sequence from the alignment. This action can be veetoed
   * by a listener.
   * @param seqIndex the sequence index
   * @return the sequence
   * @exception AlignmentVetoException veto to the removal
   */
  public GappedSequence removeSequence(int seqIndex) 
    throws AlignmentVetoException 
  {
    // signal listeners of the intention to remove a sequence
    msls.fireVetoableAlignmentEvent
      (new VetoableAlignmentEvent(this, seqIndex, AlignmentEventType.DELETE));
    
    // remove the sequence
    GappedSequence rtn = removeSequenceQuietly(seqIndex);
    
    // notify listeners that the removal has taken place successfully
    msls.fireAlignmentEvent
      (new AlignmentEvent(this, seqIndex, AlignmentEventType.DELETE));

    // return the sequence
    return rtn;
  }
  
  /**
   * Set the inset (or number of preceeding gaps) in a sequence, quietly 
   * without notifying listeners.
   *
   * @param seqIndex the sequence index
   * @param inset the inset or preceeding number of gaps
   */
  protected abstract void setInsetQuietly(int seqIndex, int inset);
  
  
  /**
   * Set the inset (or number of preceeding gaps) in a sequence.
   *
   * @param seqIndex the sequence index
   * @param inset the inset or preceeding number of gaps
   */
  public void setInset(int seqIndex, int inset) 
    throws AlignmentVetoException
  {
    // bomb out if no change has occurred
    if( inset == getInset( seqIndex ) ) return;
	
    // signal listeners of the change to occur
    msls.fireVetoableAlignmentEvent
      (new VetoableAlignmentEvent(this, seqIndex, AlignmentEventType.INSET_CHANGE));
    
    // we may need to signal that the length has increased. 
    // (PENDING:- PL) We should also check here that length has not decreased!!!
    if( inset > getInset( seqIndex ) ){
      ensureLengthIncrease( inset + getSequenceAt( seqIndex ).getGappedLength(), seqIndex );
    }
    else{
      ensureLengthDecrease();
    }
    
    // do the insertion
    setInsetQuietly(seqIndex, inset);
    // notify listeners of the change
    msls.fireAlignmentEvent
      (new AlignmentEvent(this, seqIndex, AlignmentEventType.INSET_CHANGE));
  }

  /**
   * This method checks to see whether a length increase has occurred,
   * and if it has signals events, and changes the length
   * @param newLength what is potentially the new length if its long
   * enough!
   * @param seqIndex the sequence which may have caused the change or
   * 0 if not known
   */
  private void ensureLengthIncrease( int newLength, int seqIndex ) throws AlignmentVetoException
  {
    if( newLength > getLength() ){
      fireVetoableAlignmentEvent
	( new VetoableAlignmentEvent( this, seqIndex, AlignmentEventType.LENGTH_CHANGE ) );
      setLengthQuietly( newLength );
      fireAlignmentEvent
	( new AlignmentEvent( this, seqIndex, AlignmentEventType.LENGTH_CHANGE ) );
    }
  }
  
  /**
   * This method checks to see whether a length decrease has
   * occurred. At the moment it exhaustively checks through all of the
   * sequences because I can not think of an easier way of doing
   * this. 
   */
  private void ensureLengthDecrease() throws AlignmentVetoException
  {
    int longest = 0;
    for( int i = 1; i < getNumberSequences() + 1; i++ ){
      int currLength = getSequenceAt( i ).getGappedLength() + getInset( i );
      
      if( currLength > longest ) longest = currLength;
    }
    
    if( longest != getLength() ){
      fireVetoableAlignmentEvent
	( new VetoableAlignmentEvent( this, 0, AlignmentEventType.LENGTH_CHANGE ) );
      setLengthQuietly( longest );
      fireAlignmentEvent
	( new AlignmentEvent( this, 0, AlignmentEventType.LENGTH_CHANGE ) );
    }
  }
  
  // alignment event production
  private AlignmentListenerSupport msls = new AlignmentListenerSupport();

  protected void fireAlignmentEvent( AlignmentEvent event )
  {
    msls.fireAlignmentEvent( event );
  }
  
  protected void fireVetoableAlignmentEvent( VetoableAlignmentEvent event )
    throws AlignmentVetoException
  {
    msls.fireVetoableAlignmentEvent( event );
  }
  
  /**
   * Delegates to the AlignmentListenerSupport object, the addition
   * of a Multiple Sequence (MS) listener.
   *
   * @param listener a MS listener
   */
  public void addAlignmentListener(AlignmentListener listener)
  {
    if (listener != null) msls.addAlignmentListener(listener);
  }
  
  /**
   * Delegates to the AlignmentListenerSupport the removal of the
   * MS listener.
   *
   * @param listener the MS listener to be removed
   */
  public void removeAlignmentListener(AlignmentListener listener)
  {
    if (listener != null) msls.removeAlignmentListener(listener);
  }
  
  /**
   * Delegates to the AlignmentListenerSupport the addition of a
   * vetoable MS listener.
   *
   * @param listener a vetoable MS listener to be added
   */
  public void addVetoableAlignmentListener
    (VetoableAlignmentListener listener)
  {
    if (listener != null) addVetoableAlignmentListener(listener);
  }
  
  /**
   * Delegates to the AlignmentListenerSupport the removal of the
   * vetoable MS listener.
   *
   * @param listener the vetoable MS listener to be removed
   */
  public void removeVetoableAlignmentListener
    (VetoableAlignmentListener listener)
  {
    if (listener != null) removeVetoableAlignmentListener(listener);
  }

  // SequenceEventProvider support
  private SequenceListenerSupport supp = new SequenceListenerSupport();
  
  private void fireVetoableSequenceEvent( VetoableSequenceEvent event ) throws SequenceVetoException
  {
    supp.fireVetoableSequenceEvent( event );
  }
  
  private void fireSequenceEvent( SequenceEvent event )
  {
    supp.fireSequenceEvent( event );
  }
  
  public void addSequenceListener( SequenceListener listener )
  {
    supp.addSequenceListener( listener );
  }
  
  public void addVetoableSequenceListener( VetoableSequenceListener listener )
  {
    supp.addVetoableSequenceListener( listener );
  }
  
  public void removeSequenceListener( SequenceListener listener )
  {
    supp.removeSequenceListener( listener );
  }

  public void removeVetoableSequenceListener( VetoableSequenceListener listener )
  {
    supp.removeVetoableSequenceListener( listener );
  }

  // implementation of listener 
  public void vetoableChangeOccurred( VetoableSequenceEvent event ) 
    throws SequenceVetoException
  {
    fireVetoableSequenceEvent( event );
  }
  
  // this sequence is used to cache the furthest right sequence
  //private GappedSequence longestSequence = null;
  public void changeOccurred( SequenceEvent event )
  {
    // fire the event to this objects listeners
    fireSequenceEvent( event );
    
    /* (PENDING: JNS) 25.10.00 May have to revise this - particularly
     * whether or not to throw a vetoable event
     */
    // update the length if necessary for an insert
    if ((event.getType() == SequenceEventType.GAPINSERT) && 
	((((GappedSequence)event.getSource()).getGappedLength() + 
	 getInset(getSequenceIndex((GappedSequence)event.getSource()))) > 
	 getLength())) {
      setLengthQuietly(((GappedSequence)event.getSource()).getGappedLength() + 
		       getInset(getSequenceIndex((GappedSequence)event.
						 getSource())));
      // fire a further event to signal the length has changed
      fireAlignmentEvent(new AlignmentEvent
	(this, getSequenceIndex((GappedSequence)event.getSource()), 
	 AlignmentEventType.LENGTH_CHANGE));
    }
    
    // update the length when removing gaps
    if (event.getType() == SequenceEventType.GAPDELETE) {
      GappedSequence source = (GappedSequence)event.getSource();
    
      try{
	// (PENDING:- PL) Horrible code. This all needs changing to a
	// better system. 
	ensureLengthDecrease();
      }
      catch( Exception exp ){
      }
      
      
      //  if ((longestSequence == null) || 
//  	  (source == longestSequence) ||
//  	  ((source.getGappedLength() + getInset(getSequenceIndex(source))) == 
//  	   (longestSequence.getGappedLength() + 
//  	    getInset(getSequenceIndex(longestSequence))))) {
	
//        }
      
      // fire a further event to signal the length has changed
      fireAlignmentEvent(new AlignmentEvent
	(this, getSequenceIndex(source), AlignmentEventType.LENGTH_CHANGE));
    }
  }
} // AbstractSequenceAlignment



/*
 * ChangeLog
 * $Log: AbstractSequenceAlignment.java,v $
 * Revision 1.13  2001/04/11 17:04:43  lord
 * Added License agreements to all code
 *
 * Revision 1.12  2001/01/23 17:58:57  lord
 * Added getSubAlignment( SequenceAlignmentRectangle ) method because I
 * thought it would be useful.
 *
 * Revision 1.11  2000/12/20 17:46:09  jns
 * o added a method to add a sequence alignment to a sequence
 * alignment. This code may be too inefficient and may be altered at a
 * later stage, but the idea is to put in import stuff as well as open.
 *
 * Revision 1.10  2000/10/31 15:50:11  lord
 * Put in some code to check for length changes. Its rather imperfect at
 * the moment and the event handling is not working well.
 *
 * Revision 1.9  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.8  2000/08/01 12:47:05  jns
 * o removed references to BioInterface and BioObject.
 *
 * Revision 1.7  2000/06/13 11:09:17  lord
 * Fixed getLength/getGappedLength bug.
 *
 * Revision 1.6  2000/03/27 18:47:21  lord
 * Inserted setLengthQuietly
 * setInset now checks for length change. Code moved upwards from
 * DefaultSequenceAlignment.
 * Changed exception handling
 *
 * Revision 1.5  2000/03/20 16:47:21  lord
 * Moved getInsert upto SequenceAlignment class
 *
 * Revision 1.4  2000/03/17 19:40:30  jns
 * added stuff for insets/preceeding gaps to an sequence within an
 * alignment.
 *
 * Revision 1.4  2000/03/17 18:30:48  jns
 * added in the concept of inset/preceeding gaps to a sequence
 * in a sequence alignment.
 *
 * Revision 1.3  2000/03/14 16:06:16  lord
 * Implements VetoableSequenceListener
 *
 * Revision 1.2  2000/03/10 17:56:17  lord
 * Added protected event firing methods
 * Now also handles most of the sequence event multicasting
 *
 * Revision 1.1  2000/03/02 19:21:30  jns
 * initial code.
 *
 *
 */
