//=====================================================================
// File:    Cutoff.java
// Class:   Cutoff
// Package: AFLPcore
//
// Author:  James J. Benham
// Date:    August 10, 1998
// Contact: james_benham@hmc.edu
//
// Genographer v1.0 - Computer assisted scoring of gels.
// Copyright (C) 1998  Montana State University
// 
// This program 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; version 2
// of the License.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// The GNU General Public License is distributed in the file GPL
//=====================================================================

package AFLPcore;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

/**
 * This class represents different cutoffs for a lane. This is used by
 * <code>ScoreFunction</code> classes to determine how to score things.
 * Each cutoff consists of a starting position (in bp) and a number
 * of levels. Each level can be a different <code>CutoffFunction</code>.
 * The levels must have an order. The smallest levels should be those with the 
 * smallest cutoff value. Since some functions are not linear, the ordering
 * will be based at the cutoff value at the start position. 
 *
 * @see ScoreFunction
 * @see CutoffFunction
 * @see Lane
 *
 * @author James J. Benham
 * @version 1.0.0
 * @date August 10, 1998
 */

public class Cutoff extends SortableData implements Cloneable
{
  public final static int AREA = 0;
  public final static int HEIGHT = 1;

  private int cutoffMode;
  private int numLevels;
  private double startPos;
  private CutoffFunction[] cutoffs;

  /**
   * Creates a new cutoff with the specified number of levels and starting
   * at the specified position. 
   *
   * @param startPos  the size in bp at which the cutoff first applies
   * @param numLevels the number of different cutoff supported by this level
   */
  public Cutoff(double startPos, int numLevels)
  {
    this.startPos = startPos;
    this.numLevels = numLevels;
    cutoffMode = HEIGHT;

    cutoffs = new CutoffFunction[numLevels];
  }

  /**
   * Gives the starting position for this cutoff.
   *
   * @return the start size, in bp.
   */
  public final double getSearchKey()
  {
    return startPos;
  }

  /**
   * Gives the starting position for this cutoff.
   *
   * @return the start size, in bp.
   */
  public final double getStartPos()
  {
    return startPos;
  }

  /**
   * Adjusts the starting position. <b>Warning:</b> if this is in a sorted
   * list, this must be used with caution since it could alter the order
   * that the elements should be in, but does not rearrange any list.
   */
  public void setStartPos(double newPosition)
  {
    startPos = newPosition;
  }

  /**
   * Gives the number of different cutoff levels supported by this object.
   * Each level can be associated with different confidences. For example,
   * something above the first cutoff level would most likely be a peak,
   * but something over only the second cutoff level might be a peak.
   *
   * @return The maximum number of levels that can be supported.
   */
  public int getNumLevels()
  {
    return numLevels;
  }

  /**
   * Sets the cutoff so that it applies either to peak height or to peak
   * area. The values for these are constants in this class.
   *
   * @param modeSelector  either <code>AREA</code> or <code>HEIGHT</code>
   *
   * @exception IllegalArgumentException occurs if <code>modeSelector</code>
   *                isn't one of the accepted values.
   */
  public void setMode(int modeSelector)
  {
    if( (modeSelector != AREA) && (modeSelector != HEIGHT))
      throw new IllegalArgumentException("Invalid cutoff mode.");

    cutoffMode = modeSelector;
  }

  /**
   * Gives the current cutoff mode, either height or area.
   *
   * @return either <code>AREA</code> or <code>HEIGHT</code>
   */
  public int getMode()
  {
    return cutoffMode;
  }

  /**
   * Gives the cutoff function for the specified level.
   * 
   * @param level  the confidence level, must be >= 0 and < numLevels.
   *
   * @return the function used at the given level.
   *
   * @exception IllegalArgumentException  occurs if level fails to
   *    meet the above.
   */
  public CutoffFunction getCutoffFunction(int level)
  {
    if( (level < 0) || (level >= numLevels))
      throw new IllegalArgumentException("Cannot retrieve cutoff because " +
                          "specified level is invalid: " + 
                          level);
    return cutoffs[level];
  }

  /**
   * Sets the cutoff function for the specified level.
   * 
   * @param func   the function used to determine the cutoff.
   * @param level  the confidence level, must be >= 0 and < numLevels.
   *
   * @exception IllegalArgumentException  occurs if level fails to
   *    meet the above.
   */
  public void setCutoffFunction(CutoffFunction func, int level)
  {
    if( (level < 0) || (level >= numLevels))
      throw new IllegalArgumentException("Cannot set cutoff function because"+
                          "specified level is invalid: " + 
                          level);
    cutoffs[level] = func;
  }

  /**
   * Gives the value for the height/area above which all peaks should
   * be counted with a confidence relative to the level.
   *
   * @param size  the size in bp to determine the cutoff value for.
   * @param level  the confidence level, must be >= 0 and < numLevels.
   *
   * @exception IllegalArgumentException  occurs if level fails to
   *    meet the above.
   */

  public double getCutoff(double size, int level)
  {
    if( (level < 0) || (level >= numLevels))
      throw new IllegalArgumentException("Cannot get cutoff value because " +
                          "specified level is invalid: " + 
                          level);
    return (cutoffs[level].getCutoff(size));
  }

  /**
   * Adds the specified function to this cutoff. Therefore, the number of
   * levels in the cutoff is increased accordingly. The new function
   * is added at a level so that the level ordering, lower values are
   * at lower levels, is maintained.
   *
   * @param newFunction  the function to add to this cutoff, it's height
   *      should already be set to the desired value.
   */
  public void addFunction(CutoffFunction newFunction)
  {
    // find out where the new function belongs
    int index = 0;
    while( (index < numLevels) && 
        (newFunction.getCutoff(startPos) >
         cutoffs[index].getCutoff(startPos)))
      index++;

    numLevels++;
    CutoffFunction newCutoffs[] = new CutoffFunction[numLevels];

    // copy the stuff over.
    for(int i=0; i < index; i++)
      newCutoffs[i] = cutoffs[i];

    newCutoffs[index] = newFunction;

    for(int i = (index+1); i < numLevels; i++)
      newCutoffs[i] = cutoffs[i-1];

    cutoffs = newCutoffs;
  }

  /**
   * Removes the specified level from this cutoff. The number of levels in
   * the cutoff is decreased accordingly.
   *
   * @param level  the level to remove.
   */
  public void deleteFunction(int level)
  {
    numLevels--;
    CutoffFunction newCutoffs[] = new CutoffFunction[numLevels];

    for(int i=0; i < level; i++)
      newCutoffs[i] = cutoffs[i];

    for(int i= (level + 1); i < (numLevels + 1); i++)
      newCutoffs[i-1] = cutoffs[i];

    cutoffs = newCutoffs;
  }

  /**
   * Gives an object where all of the data is the same as this one.
   *
   * @return a copy of this object.
   */
  public Object clone()
  {
    Cutoff ct = new Cutoff(startPos, numLevels);
    ct.setMode(cutoffMode);
    for(int i=0; i < numLevels; i++)
      ct.setCutoffFunction((CutoffFunction) (cutoffs[i].clone()), i);

    return ct;
  }

  /**
   * Writes all of the information this class needs to store in order
   * to be recreated. This will be used for things like storing the
   * class in a file. Therefore, the write should output enough information
   * so that <code>read</code> can recreate the essential properties of this
   * class.
   *
   * @param out  the destination to write the data to.
   *
   * @exception IOException  occurs when a problem is encountered when
   *     writing to the stream.
   */
  public void write(DataOutputStream out) throws IOException
  {
    out.writeDouble(startPos);
    out.writeInt(cutoffMode);
    out.writeInt(numLevels);
    for(int i=0; i < numLevels; i++)
      {
     out.writeUTF(cutoffs[i].getName());
     cutoffs[i].write(out);
      }
  }

  /**
   * Reads in the properties of this class from the specified input stream.
   * The stream data should have been created by <code>write</code>. This
   * will retrieve this classes state from the input stream. All of the
   * current data in this class will be replaced by the data from the 
   * stream.
   *
   * @param in  the input stream with the data for the class.
   *
   * @exception IOException  occurs when a problem is encountered when
   *     writing to the stream.
   */
  public void read(DataInputStream in) throws IOException
  {
    startPos = in.readDouble();
    cutoffMode = in.readInt();
    numLevels = in.readInt();

    cutoffs = new CutoffFunction[numLevels];

    String name;
    CutoffFunction ctfn = new LinearCutoff();
    for(int i=0; i < numLevels; i++)
      {
     name = in.readUTF();
     try{
       ctfn = (CutoffFunction) FeatureList.getCutoffMgr().get(name);
     } catch(java.util.NoSuchElementException e) {
       throw new IOException("Could not get cutoff function named " + name);
     }

     ctfn = (CutoffFunction) ctfn.clone();
     ctfn.read(in);
     cutoffs[i] = ctfn;
      }
  }
}
