/*
 *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.analysis.misc; // Package name inserted by JPack


import uk.ac.man.bioinf.sequence.Element;
import uk.ac.man.bioinf.sequence.SequenceType;
import uk.ac.man.bioinf.sequence.alignment.AlignmentColumn;
import uk.ac.man.bioinf.sequence.alignment.Gap;
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.AlignmentListener;
import uk.ac.man.bioinf.sequence.event.SequenceEvent;
import uk.ac.man.bioinf.sequence.event.SequenceListener;
import uk.ac.man.bioinf.util.ChangeableListenerSupport;


/**
 * PercentageIDCalculator.java
 *
 * This class performs a variance calculation on the given sequence
 * alignment. The calculation is done by calling the
 * calculateAlignment method, and the results got from the
 * getVariance, and getConsensus methods. Both calculations are done
 * by a simple Percentage ID calculation.  
 *
 * Created: Fri Jun 16 18:38:00 2000
 *
 * @author Phillip Lord
 * @version $Id: PercentageIDCalculator.java,v 1.6 2001/04/11 17:04:41 lord Exp $
 */

public class PercentageIDCalculator implements AlignmentListener, SequenceListener
{ 
  private AlignmentColumn col = new AlignmentColumn();
  private double[] variance;
  private Element[] consensus;
  private SequenceAlignment alignment;
  private ChangeableListenerSupport supp = new ChangeableListenerSupport();
  private boolean invalid = true;
  private int checksum = 0;
  
  public PercentageIDCalculator( SequenceAlignment alignment )
  {
    this.alignment = alignment;
    alignment.addAlignmentListener( this );
    alignment.addSequenceListener( this );
  }

  private synchronized void calculate()
  {
    // we need the sequence type to before several operations for us. 
    SequenceType type = alignment.getSequenceType();
    
    // start new arrays to store the results. 
    consensus = new Element[ alignment.getLength() ];
    variance = new double[ alignment.getLength() ];
    
    // this hash is used to store the number of an element in a
    // column. 
    int[] hash = new int[ type.size() ];
    col.setAlignment( alignment );
    
    int lastColumn = alignment.getLength() + 1;
    int lastRow =    alignment.getNumberSequences() + 1;
    
    // iterate through all of the columns
    for( int i = 1; i < lastColumn; i++ ){
      int gapElements = 0;
      col.setColumn( i );

      // count up the number of a specific element type. 
      for( int j = 1; j < lastRow; j++ ){
	Element elementAtRow = col.getElementAtRow( j );
	
	if( elementAtRow != Gap.GAP ){
	  hash[ type.getIntForElement( elementAtRow ) ]++;
	}
	else{
	  gapElements++;
	}
      }
      
      int largestValue = 0;
      for( int j = 0; j < hash.length; j++ ){
	// find how many times the most frequently occuring element
	// occurs
	if( hash[ j ] > largestValue ){
	  largestValue = hash[ j ];
	  consensus[ i - 1 ] = type.getElementForInt( j );
	}
	
	// zero this for the next time around. 
	hash[ j ] = 0;

	// check that gaps did not occur more frequently.
	if( gapElements > largestValue ){
	  consensus[ i - 1 ] = Gap.GAP;
	}
      }
      
      // dont want to divide by zero
      if( lastRow != 1 ){
	// calculate the variance at this row. 
	variance[ i - 1 ] = ((double)largestValue) / ((double)(lastRow - 1) );
      }
    }
    invalid = false;
  }
  
  private void invalidate()
  {  
    checksum++;
    invalid = true;
  }
  
  public void changeOccurred( AlignmentEvent event )
  {
    invalidate();
  }
  
  public void changeOccurred( SequenceEvent event )
  {
    invalidate();
  }
  
  /**
   * Returns a checksum value. If this value has changed there
   * consensus sequence may well have changed. 
   * @return a value of type 'int'
   */
  public synchronized int getCheckSum()
  {
    return checksum;
  }
  
  public synchronized double[] getVariance()
  {
    if( invalid ) calculate();
    
    return variance;
  }
  
  public synchronized Element[] getConsensus()
  {
    if( invalid ) calculate();
    
    return consensus;
  }
  
  public SequenceAlignment getAlignment()
  {
    return alignment;
  }
} // PercentageIDCalculator



/*
 * ChangeLog
 * $Log: PercentageIDCalculator.java,v $
 * Revision 1.6  2001/04/11 17:04:41  lord
 * Added License agreements to all code
 *
 * Revision 1.5  2001/01/31 17:39:33  lord
 * Spelling correction
 *
 * Revision 1.4  2000/11/10 15:11:28  lord
 * Removed some imports
 *
 * Revision 1.3  2000/11/08 18:28:54  lord
 * Lots of changes. Object now has state. Added checksum, added listeners.
 *
 * Revision 1.2  2000/07/26 13:25:44  lord
 * Now implements Changeable, and it tied to a specific SequenceAlignment instance
 *
 * Revision 1.1  2000/06/27 15:53:56  lord
 * Initial checkin
 *
 */
