//=====================================================================
// File:    Lane.java
// Class:   Lane
// Package: AFLPcore
//
// Author:  James J. Benham
// Date:    January 4, 1999
// 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 a lane. A lane contains a trace, as well as 
 * a sample name, a gel name (for the original gel), a lane number, a
 * lane index (which is unique to a lane), cutoffs, and peaks. 
 *
 * <p>The trace consists of a bunch of points indicating the height at.
 * The first point will contain the height at time=0, the next point
 * will be at time=timeInc, and so on. These different time increments 
 * are called scan numbers throughout the program. (The ABI 377 scans
 * a point in the gel every 1.6 seconds.) The lane must also have
 * a <code>SizeFunction</code>, which is used to convert between scan
 * numbers and sizes in bp. The lane can actually contain two traces.
 * One is the raw data trace, which should not be modified, and the other
 * is the normalized trace. Normalization is not provided in the lane file,
 * but can be added as either a <code>GelOperation</code> or a 
 * <code>LaneOperation</code>. This allows easy implementation of different
 * normilization algorithms.
 *
 * <p>The lane also contains a list of <code>Cutoff</code>s. These are used
 * to help locate peaks and to score bins. Each cutoff is defined by a
 * starting position. A cutoff will apply to the region between its start
 * position and either the start of the next cutoff in the lane or until
 * the end of the lane. The list of cutoffs is sorted by start location.
 *
 * <p>The lane also contains a list of defined peaks. This list can be
 * obtained and then manipulated by calling <code>getPeaks()</code>. Again
 * the lane itself does not find the peaks, but relies on other parts of
 * the program to find them. This allows the peak locating algorithm to
 * be changed easily.
 *
 * @see SizeFunction
 * @see Cutoff
 * @see Operation
 *
 * @author James J. Benham
 * @version 1.1.0
 * @date January 4, 1999
 */

public class Lane extends Data
{
  /** Constant for lane color */
  public final static int YELLOW = 2;
  /** Constant for lane color */
  public final static int RED = 3;
  /** Constant for lane color */
  public final static int BLUE = 0;
  /** Constant for lane color */
  public final static int GREEN = 1;

  /** Constant to tell which type of trace the lane is using.*/
  public final static int ORIGINAL = 0;
  /** Constant to tell which type of trace the lane is using.*/
  public final static int NORMALIZED = 1;

  private static int laneCount = 0;

  private String name;
  private String gelName;
  private int laneNum;

  private int laneIndex;

  private double totalSignal;
  
  private double rawTrace[];
  private double normTrace[];
  private double trace[];

  private int color;
  private SizeFunction sizeFn;

  private DataList peaks;
  private DataList cutoffs;

  /**
   * Create a new lane with the specified trace. The default name will be
   * "unknown", the gelName will be "__", and the laneNum will be 0. These
   * can and should be changed with the <code>set<i>ParamName</i></code>
   * methods. 
   *
   * @param trace  each value represent the intensity of the light read at
   *     a given point, either scan or size. This should be a raw trace,
   *     not a normalized one.
   */
  public Lane(double trace[])
  {
    // set the trace
    rawTrace = trace;

    laneIndex = laneCount;
    laneCount++;

    // give the other stuff default values
    color = YELLOW;
    name = "unknown";
    gelName = "__";
    laneNum = 0;

    totalSignal = -1;

    this.trace = rawTrace;
    normTrace = null;

    sizeFn = new NoSize();

    peaks = new DataList();
    cutoffs = new DataList();
  }

  /**
   * Gives the intensity of the light for the color at the specified
   * size. This also corresponds to the height of a trace. The trace can
   * either be the normalized trace or the original trace, depending on
   * what the current trace is set to.
   *
   * @param size   the location of interest, specified in bp
   *
   * @return  the value of the trace at the given point. -1 will be returned
   *     if the specified point is not in the Lane.
   *
   * @see Lane#useTrace
   */
  public double getHeightAt(double size)
  {
    int index = indexOf(size);
    return( (index != -1) ? trace[index] : -1);
  }

  /**
   * Uses the size method to find the index for the trace array that
   * corresponds to that size.
   *
   * @param size  the size in bp
   *
   * @return  the index to the trace array, -1 if the index is outside of
   *    the array
   */
  private int indexOf(double size)
  {
    int scan = sizeFn.getScan(size);

    // Do some sizing and checking
    if( (scan >= trace.length) || (scan < 0))
      {
	//DEBUG: System.out.println("Size param out of trace.");
	return -1;
      }
    return scan;
  }

  /**
   * Gives the name of the sample run on this lane. This can be set using
   * <code>setName</code>.
   *
   * @return the name, "unknown" by default
   */
  public final String getName()
  {
    return name;
  }

  /**
   * Set the name of the sample run on this lane to the specified string.
   *
   * @param newName  the value to set the name to.
   */
  public final void setName(String newName)
  {
    name = newName;
  }

  /**
   * Gives the name of the Gel that this lane was extracted from. This can be
   * set using <code>setGelName</code>.
   *
   * @return the gel name, "__" by default
   */
  public final String getGelName()
  {
    return gelName;
  }

  /**
   * Set the name of the gel that the lane was on to the specified value.
   *
   * @param newName  the value to set the name to.
   */
  public final void setGelName(String newName)
  {
    gelName = newName;
  }

  /**
   * Gives the number of the lane on the gel that contianed this information.
   *
   * return the lane number
   */
  public final int getLaneNumber()
  {
    return laneNum;
  }

  /**
   * Set the lane number of this lane to the specified value.
   *
   * @param newName  the value to set the lane number to to.
   */
  public final void setLaneNumber(int newLaneNum)
  {
    laneNum = newLaneNum;
  }

  /**
   * Gives the color of the data in this lane. Possible values are
   * <code>RED</code>, <code>YELLOW</code>, <code>GREEN</code>, and
   * <code>BLUE</code>, which are defined in this class.
   *
   * @return the color of this lane.
   */
  public final int getColor()
  {
    return color;
  }

  /**
   * Set the color channel used with this lane to the specified value.
   *
   * @param the color, possible values are given in this class.
   */
  public final void setColor(int color)
  {
    this.color = color;
  }

  /**
   * Gives the object that represents the conversion between size in bp
   * and scan number (trace index).
   *
   * @return the size object
   */
  public SizeFunction getSizeFunction()
  {
    return sizeFn;
  }

  /**
   * Set the size curve/function to be used by this lane.
   *
   * @param sizeFunction  the new way to convert between scan number and size
   */
  public void setSizeFunction(SizeFunction sizeFunction)
  {
    sizeFn = sizeFunction;
  }

  /**
   * Produces a list of peaks that are in the given range. That is, for the
   * peak location, <i>x</i>, min <= <i>x</i> <= max must be true. For every
   * such peak, the peak is added to the list, which is sorted.
   *
   * @param min  the minimum size in bp
   * @param max  the maximum size in bp
   *
   * @return a list of peaks in the range. If there are no peaks, the list
   *     will be empty.
   */
  public DataList getPeaksInRange(double min, double max)
  {
    // This method works by finding the first peak that is less than the
    // max, then finding the peak that is less than the first peak, and so
    // on until either no more peaks are found or the location of the peak
    // found dips below the minimum.

    // place to store the peaks
    DataList inRangeList = new DataList();
    Peak tempPeak;

    double currentSize = max;

    // find the value closest to the max
    tempPeak = (Peak) peaks.findNearestUnder(currentSize);
    while( (tempPeak != null ) && 
	   (tempPeak.getLocation() >= min))
      {
	// add the peak
	inRangeList.include(tempPeak);

	// move the size to the peak we just found, and then look below that.
	// so that we don't find the same one again.
	currentSize = tempPeak.getLocation() - 1E-12;
  
	// get the next peak
	tempPeak = (Peak) peaks.findNearestUnder(currentSize);
      }

    return inRangeList;
  }

  /**
   * Gives the list of peaks in the lane.
   *
   * @return the peak list.
   */
  public DataList getPeaks()
  {
    return peaks;
  }

  /**
   * Gives the maximum height in the specified range.
   *
   * @param minSize   the size in bp of the lower bound
   * @param maxSize   the size in bp of the upper bound
   *
   * @return the maximum intensity, -1 if maxSize < minSize or if the
   *   sizes are out of bounds.
   */
  public double getMaxHeight(double minSize, double maxSize)
  {
    return getMaxPoint(minSize, maxSize).height;
  }

  /**
   * Gives the maximum height in the lane
   *
   * @return the maximum height
   */
  public double getMaxGlobalHeight()
  {
    return findMaxPoint(0, trace.length - 1).height;
  }

  /**
   * Gives the maximum point, <i>(size, height)</i> ,in the specified range.
   *
   * @param minSize   the size in bp of the lower bound
   * @param maxSize   the size in bp of the upper bound
   *
   * @return the maximum intensity point, -1 for the height if
   *     maxSize < minSize or if the sizes are out of bounds.
   */
  public TracePoint getMaxPoint(double minSize, double maxSize)
  {
    // Go from size to index into the array
    int minIndex = indexOf(minSize);
    int maxIndex = indexOf(maxSize);

    // Check to see that minIndex is valid, max will automatically return
    // -1 since the loop won't execute.
    if(minIndex == -1)
      return new TracePoint(-1, -1);

    return findMaxPoint(minIndex, maxIndex);
  }

  /**
   * Gives the maximum point, <i>(scan, height)</i>, in the trace, based
   * on the range given as scan numbers.
   *
   * @param minScan   the lowest scan to include in the search
   * @param maxScan   the upper bound for the search (inclusive)
   *
   * @return the maximum intensity point, -1 for the height if
   *     maxSize < minSize or if the sizes are out of bounds.
   */
  private TracePoint findMaxPoint(int minScan, int maxScan)
  {
    double max = -1;  // initialize the maximum
    int maxLocation = minScan; // initialze the index

    // Find the maximum.
    for(int i=minScan; i <= maxScan; i++)
      {
	if(trace[i] > max)
	  {
	    max = trace[i];
	    maxLocation = i;
	  }
      }

    return new TracePoint(maxLocation, max);
  }    

  /**
   * This returns the number of points in the trace. Therefore, anything
   * that attempts to display more information than this will simply be
   * scaling the points, instead of accessing intermedite points. However,
   * since the trace can be 4000+ points long, that's a little hard to 
   * display in one image.
   *
   * @return the number of data points in the trace.
   */
  public int getNumPoints()
  {
    return rawTrace.length;
  }

  /**
   * Sets the trace to be used when retrieving height, finding peaks, etc.
   * The trace can either be the original trace that came from some
   * input file, or the normalized trace.
   *
   * @param trace_num  indicates which trace to use, possiblities are
   *    <code>ORIGINAL</code> or <code>NORMALIZED</code>
   *
   * @exception IllegalArgumentException   occurs when <code>trace_num</code>
   *    is not one of the excepted values.
   * @exception NoDataError   occurs if this lane has not yet been
   *    normalized. If this is the case, then the normalized trace does not
   *    exist and therefore cannot be used. 
   */
  public void useTrace(int trace_num)
  {
    // check the argument
    if( (trace_num != NORMALIZED) && (trace_num != ORIGINAL) )
      {
	throw new IllegalArgumentException("No such trace.");
      }

    // if it's normalized, make sure we have the data
    if( (trace_num == NORMALIZED) && (normTrace == null))
      {
	throw new NoDataError("The normalized trace cannot be used" +
			      "because it does not exist.");
      }

    // no more errors, so just set it
    if(trace_num == ORIGINAL)
      trace = rawTrace;
    else
      trace = normTrace;
  }

  /**
   * Set the normalized trace to the specified trace.
   *
   * @param trace  the normalized version of the raw trace.
   */
  public void setNormTrace(double trace[])
  {
    normTrace = trace;
  }

  /**
   * Gives the current trace for this lane, which is either the normalized
   * trace or the raw trace. The raw trace should <b>NOT</b> modified.
   * Access is granted to it only to make creating a normalized trace 
   * easier.
   *
   * @return  the lane trace
   *
   * @see Lane#useTrace
   */
  public double[] getTrace()
  {
    return trace;
  }

  /**
   * Gives an integer that is unique to this lane.
   *
   * @return an integer which identifies this lane.
   */
  public int getLaneIndex()
  {
    return laneIndex;
  }

  /**
   * Gives an integer that is equivalent to the lane index, which is a
   * unique number for this lane.
   *
   * @return a code suitable for a hashtable.
   */
  public int hashCode()
  {
    return laneIndex;
  }

  /**
   * Gives the largest size, in bp, represented by this lane. Any size
   * greater than this will not be in the lane data. <b>Warning:</b> If 
   * there is a round off error in the sizing function, converting the 
   * maximum size back into a value may result in exceeding the data size.
   * This is of course depended on the size function.
   *
   * @return the maximum size
   */
  public double getMaxSize()
  {
    return (sizeFn.getSize(trace.length - 1));
  }

  /**
   * Gives the smallest size, in bp, represented by this lane. Any size
   * less than this will not be in the lane data. <b>Warning:</b> If 
   * there is a round off error in the sizing function, converting the 
   * minimum size back into a value may result in an out of bounds problem.
   * This is of course depended on the size function.
   *
   * @return the minimum size
   */
  public double getMinSize()
  {
    return (sizeFn.getSize(0));
  }

  /**
   * Clones this lane <b>Not implemented</b>
   */
  public Object clone()
  {
    System.err.println("Lane clone not implemented.");
    return this;
  }

  /**
   * Gives the strength of the signal for this lane. The signal stength
   * is found by summing every point in the trace. It is in effect, the
   * area of the trace curve.
   *
   * @return a value equal to the strength of the signal in the lane.
   */
  public double getTotalSignal()
    {
      // see if the value has been calculted yet.
      if(totalSignal == -1)
	  {
	    totalSignal = 0;
	    for(int i=0; i < rawTrace.length; i++)
	      totalSignal += rawTrace[i];
	  }

      return totalSignal;
    }

  /**
   * Returns the list of cutoffs associated with this lane.
   *
   * @return the list, empty if there are no cutoffs set
   */
  public DataList getCutoffs()
  {
    return cutoffs;
  }

  /**
   * Adds the specified cutoff to the list of known cutoffs for this lane.
   * Any cutoff can contian multiple levels.
   *
   * @param ct  the cutoff to be added.
   */
  public void addCutoff(Cutoff ct)
  {
    cutoffs.include(ct);
  }

  /**
   * Gives the first cutoff in the lane that is less than or equal to the
   * specified size.
   *
   * @param size  the size which the cutoff must be lower than
   *
   * @return  the first such cutoff found, <code>null</code> if none exists.
   */
  public Cutoff cutoffUnder(double size)
  {
    return (Cutoff) cutoffs.findNearestUnder(size);
  }
	    
    /**
   * 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.writeUTF(name);
    out.writeInt(laneNum);
    out.writeUTF(gelName);
    
    out.writeInt(laneIndex);
    out.writeInt(color);
    out.writeBoolean( (trace == rawTrace) );

    out.writeInt(rawTrace.length);
    for(int i=0; i < rawTrace.length; i++)
      out.writeShort( (short) rawTrace[i]);

    if(normTrace == null)
      out.writeInt(0);
    else
      {
	out.writeInt(normTrace.length);
	for(int i=0; i < normTrace.length; i++)
	  out.writeFloat( (float) normTrace[i]);
      }

    out.writeInt(peaks.size());
    for(int i=0; i < peaks.size(); i++)
      ((Peak) peaks.dataAt(i)).write(out);

    out.writeInt(cutoffs.size());
    for(int i=0; i < cutoffs.size(); i++)
      ((Cutoff) cutoffs.dataAt(i)).write(out);

    out.writeUTF(sizeFn.getName());
    sizeFn.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
  {
    name = in.readUTF();
    laneNum = in.readInt();
    gelName = in.readUTF();

    laneIndex = in.readInt();
    // set to -1 so it forces a recomputation. Do this so we don't
    // change the file format.
    totalSignal = -1;
    color = in.readInt();

    boolean usingRaw = in.readBoolean();

    int length = in.readInt();
    rawTrace = new double[length];
    for(int i=0; i < length; i++)
      rawTrace[i] = in.readShort();

    length = in.readInt();
    if(length == 0)
      normTrace = null;
    else
      {
	normTrace = new double[length];
	for(int i=0; i < length; i++)
	  normTrace[i] = in.readFloat();
      }
    
    // set the trace to the correct value
    if(usingRaw)
      trace = rawTrace;
    else
      trace = normTrace;

    Peak pk;
    length = in.readInt();
    peaks = new DataList(length);
    for(int i=0; i < length; i++)
      {
	pk = new Peak(0, 0, 0);
	pk.read(in);
	peaks.addData(pk);
      }

    Cutoff ct;
    length = in.readInt();
    cutoffs = new DataList(length);
    for(int i=0; i < length; i++)
      {
	ct = new Cutoff(1, 1);
	ct.read(in);
	cutoffs.addData(ct);
      }

    String sizeName = in.readUTF();
    try {
      sizeFn = (SizeFunction) FeatureList.getSizeMgr().get(sizeName);
      sizeFn = (SizeFunction) sizeFn.clone();
      sizeFn.read(in);
    } catch(java.util.NoSuchElementException e) {
      throw new IOException("The sizing function " + sizeName + " could " +
			    "not be found.");
    }
  }
}

