//=====================================================================
// File:    Gel.java
// Class:   Gel
// 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;import com.visualtek.png.*;

/**
 * A gel contains most of the information needed by the program. It contains
 * a set of lanes and a set of bins. Additionally, the gel contains 
 * methods to convert the lanes into an array which can be converted into
 * an image. The image can be controlled by setting the size range, borders,
 * and intensity. <b>Note:</b> the gel will NOT check to see if the size
 * ranges specified are valid. The image can also be written out in a PPM
 * image format. This format is not widely used, but it is easy to code. 
 * More formats may be added in the future. Finally, the gel can be written
 * to an output stream as well as read in from an input stream. As mentioned
 * before, this effectively writes all of the critical data for the program.
 *
 * @see Bin
 * @see Lane
 *
 * @author James J. Benham
 * @version 1.0.0
 * @date January 8, 1999
 */
public class Gel extends Data
{
  private DataList lanes;
  private DataList selectedLanes;

  private DataList bins;

  private int numLanes;

  private double maxGlobalIntensity;

  private int laneWidth;
  private int laneBorder;
  private int gelTopBorder;
  private int bgColor;

  private double minSize;
  private double maxSize;
  private double intensity;

  private double sizeInc;  //scale of bp per pixel for pixel array

  private int pix[];                    // the array representing the image


  /**
   * Create a new Gel.
   */
  public Gel()
  {
    laneWidth = 10;
    laneBorder = 3;
    gelTopBorder = 5;
    numLanes = 0;

    minSize = 0;
    maxSize = 10;
    intensity = 1;

    bgColor = 0xff000000;  //black

    // stores the maximum value in the gel
    maxGlobalIntensity = 0;

    lanes = new DataList();
    selectedLanes = new DataList();

    bins = new DataList();
  }

  /**
   * Adds a lane to this gel.
   *
   * @param ln the lane to add
   */
  public void addLane(Lane ln)
  {
    // keep track of how many lanes we have, even though lanes.length()
    // should get us the same value.
    numLanes++;
    lanes.addData(ln);

    double maxValue = ln.getMaxGlobalHeight();
    if (maxValue > maxGlobalIntensity)
      maxGlobalIntensity = maxValue;

    // This invalidates all of the scoring for the bins
    for(int i=0; i < bins.size(); i++)
      ((Bin) bins.dataAt(i)).setScore(false);
  }

  public void removeLane(Lane ln)
  {
    boolean found;
    // remove from both lanes and selected lanes, if it isn't there,
    // we won't decrease the number of lanes.
    found = lanes.removeElement(ln);
    selectedLanes.removeElement(ln);

    if(found)
      numLanes--;
  }

  /**
   * Gives the lane at the specified index.
   */
  public Lane laneAt(int index)
  {
    return (Lane) lanes.dataAt(index);
  }

  /**
   * Gives a list of all of the lanes in the gel. The list is not sorted
   * and is in the same order as the lanes appear in the gel, left from right.
   *
   * @return the lanes in the gel. Empty if there are none.
   */
  public DataList getLanes()
  {
    return lanes;
  }

  /**
   * Gives a list of all of the selected lanes in the gel. The list is not 
   * sorted and is in the same order as the lanes appear in the gel, left 
   * from right, except not every lane is included in this list.
   *
   * @return the lanes in that are selected. Empty if there are none.
   */
  public DataList getSelectedLanes()
  {
    return selectedLanes;
  }

  /**
   * Changes the selection state of the specified lane. If the lane is
   * already selected, it is removed from the list of selected lanes.
   * If it is not selected, it is added to the list. The order of the
   * selected lanes is identical to the the ordering of the lanes in the
   * gel.
   * <p> The list is updated when a lane is added by moving through
   * every lane in the gel, seeing if it should be selected, and then 
   * adding it to a new list if it should. Finally, the new list becomes
   * the list of selected lanes.
   *
   * @param ln the lane to toggle
   */
  public void toggleSelectedLane(Lane ln)
  {
    int index = selectedLanes.indexOf(ln);
    if(index == -1)
      {
     DataList newSelection = new DataList();

     int selectIndex;

     // move through the lanes and look for lanes in the selected
     // list
     for(index = 0; index < lanes.size(); index++)
       {
         // See if it is in the the selected list
         Lane currentLane = (Lane) lanes.elementAt(index);
         selectIndex = selectedLanes.indexOf(currentLane);
         if(selectIndex != -1)
           newSelection.addElement(selectedLanes.elementAt(selectIndex));
         else if(currentLane == ln)
           {
          // see if it is the lane to add.
          newSelection.addElement(ln);
           }
       }

     selectedLanes = newSelection;
      }
    else
      {
     // the lane is in the list, so remove it
     selectedLanes.removeElementAt(index);
      }
  }

  /**
   * Adds the specified bin to the gel.
   *
   * @param bin the new bin, complete with size and width set.
   */
  public void addBin(Bin bin)
  {
    bins.include(bin);
  }

  /**
   * Removes the specified bin from the list of bins for this gel.
   *
   * @param bin the bin to remove
   */
  public void removeBin(Bin bin)
  {
    bins.removeElement(bin);
  }

  /**
   * Gives a list of all the bins in the gel.
   *
   * @return the bins in the gel. If there are none, the list will be
   * empty.
   */
  public DataList getBins()
  {
    return bins;
  }

  /**
   * Gives the bin at the specified location. It works be searching the
   * bin list for the specified size. If the size is found, that bin is
   * returned. Otherwise, it retrieves the bin smaller than <code>size</code>
   * and checks to see if size is within that range. If it is not, the
   * first bin larger than size is checked in s similar manner.
   *
   * @param size  the location to look for bins at
   * 
   * @return the bin that contains the given size. <code>null</code> if no
   *      such bin exists.
   */
  public Bin binAtSize(double size)
  {
    // Make sure it isn't an empty list
    if(bins.isEmpty())
      return null;

    Bin tempBin;

    SearchReturn searchResult = bins.find(size);

    // If we found it, we are certainly within the range,
    if(searchResult.location != -1)
      return (Bin) bins.elementAt(searchResult.location);

    // Check the one below (if |x-a| <= epsilon)
    tempBin = (Bin) bins.elementAt(searchResult.insertIndex);
    if( Math.abs(size - tempBin.getSearchKey()) <= tempBin.getRange())
      return (Bin) bins.elementAt(searchResult.insertIndex);

    // Now check the one above
    // First see that we don't go over the top
    if( (searchResult.insertIndex + 1) >= bins.size())
      return null;

    tempBin = (Bin) bins.elementAt(searchResult.insertIndex +1);
    if( Math.abs(size - tempBin.getSearchKey()) <= tempBin.getRange())
      return (Bin) bins.elementAt(searchResult.insertIndex + 1);

    // We didn't find it
    return null;
  }

  /**
   * Gives an array of pixels that represents the gel as an image. This
   * can easily be converted into an <code>Image</code> using the
   * <code>MemoryImageSource</code>. The array is one dimensional, with
   * the form <i>value(x,y) = pix[y*width + x]</i> The data in the lanes
   * of the gel are converted from heights to color values. The image
   * will represent the data from minSize to maxSize which where set
   * for this gel with <code>setMinSize</code> and <code>setMaxSize</code>.
   * The color scaling is contolled by the intensity with 
   * <code>setIntensity</code>.
   *
   * @param length  the length in pixels of the image in the array
   *
   */
  public int[] getPixels(int length)
  {
    return getPixels(minSize, maxSize, length, intensity);
  } 

  /**
   * Gives an array of pixels that represents the gel as an image. This
   * can easily be converted into an <code>Image</code> using the
   * <code>MemoryImageSource</code>. The array is one dimensional, with
   * the form <i>value(x,y) = pix[y*width + x]</i> The data in the lanes
   * of the gel are converted from heights to color values. The image
   * will represent the data from minSize to maxSize.
   *
   * @param minSize     the size of the smallest fragement to display
   * @param maxSize     the size of the largest tragement to display
   * @param length      the length, in pixels, of the gel image.
   * @param maxHeight   the value which will give the brightest color
   *                    Used to determine the color for the lane.
   *
   * @see Gel#writeLane
   * @see MemoryImageSource
   */
  public int[] getPixels(double minSize, double maxSize, int length, 
                double maxHeight)
  {
    double scaleFactor;           // convert from height to color
    int traceLength;              // the length, in pixels, of the lane

    //===========DEBUG=================
    //System.out.println("maxHeight: " + maxHeight);
    //System.out.println("minSize:   " + minSize);
    //System.out.println("maxSize:   " + maxSize);
    //System.out.println("imgLength: " + length);
    //===========DEBUG=================

    // Used to convert trace heights to colors
    scaleFactor = 255/maxHeight;

    // ===============Create the array================
    // Get the sizes, border on both sides, so add extra laneBorder
    int width = numLanes*(laneWidth + laneBorder) + laneBorder;
    int height = length;

    pix = new int[width*height];

    //===========DEBUG=================
    //System.out.println("scaleFactor: " + scaleFactor);
    //System.out.println("width: " + width);
    //System.out.println("height: " + height);
    //===========DEBUG=================

    // ===============Get size scale==================
    //   This line causes the JIT from Symantec NOT to screw up. It can
    //   be added in so that the JIT can be used. However, this
    //   error seems to have gone away now. Still, look here for
    //   mysterious problems. Use caution
    //
    //System.out.println("Equals: " + ((400 == length) ? "Yes" : "No"));

    sizeInc = (maxSize - minSize)/(length - 1);

    // Find the length of the lane in pixels
    traceLength = length - gelTopBorder;

    //===========DEBUG=================
    //System.out.println("sizeInc: " + sizeInc);
    //===========DEBUG=================

    //===============Write in the top border===========
    int stopPos = width*gelTopBorder; // move out of loop. I don't trust the
    for(int i=0; i < stopPos; i++)     // compiler to optimize.
      pix[i] = bgColor;

    //===============Write in the bottom border========
    for(int i = (traceLength*width); i < (width*height); i++)
      pix[i] = (0xff << 24);

    //===============Write out lanes and vertical borders===========
    // Note that this will not happen if there are no lanes, but the
    // final border outside of this loop will.
    for(int i=0; i < numLanes; i++)
      {
     writeBorder(pix, traceLength, i, width);
     writeLane(pix, traceLength, (Lane) lanes.dataAt(i), 
            i, width, scaleFactor, maxSize);
      }

    //===============Write the final vertical border===========
    writeBorder(pix, traceLength, numLanes, width);

    //===========DEBUG=================
    //System.out.println("Pixels created.");
    //===========DEBUG=================
    
    return pix;
  }

  /**
   * Writes the specified lane data into the specified pixel array at the
   * given location. Since the destination is not the same size as the 
   * source, some points from the lane will be skipped or used multiple
   * times. The former is more likely since lanes usually consist of several
   * thousand points. The height at the point in a lane is converted to
   * a color, where the height of the point determines the brightness of
   * the color. For color values that exceed 255, they will be clipped to
   * 255.
   *
   * @param pix          the array representing the image
   * @param length       the length in pixels of the lane on the image
   * @param lane         the lane with the data of interest
   * @param horzIndex    determines the horizontal position in the image
   *                     to write the lane to. It uses the laneWidth and
   *                     laneBorder as well.
   * @param width        the width of the image in the pixel array
   * @param scaleFactor  the value to multiply the height by to get the
   *                     color value, which maxes out at 255.
   * @param maxSize      the maximum size, in bp, for the lane. This size
   *                     will be the first value shown in the lane.
   */
  protected void writeLane(int pix[], int length, Lane lane, int horzIndex,
               int width, double scaleFactor, double maxSize)
  {
    int colorValue;
    double size = maxSize;

    for(int i=0; i < length; i++)
      {
     // set the correct color value, between 0 and 255.
     colorValue = (int) (lane.getHeightAt(size) * scaleFactor);

     // check to see if the colorValue is still less than 255, if not,
     // clip it.
     if(colorValue > 255)
       colorValue = 255;
    
     // now make it the right color
     switch (lane.getColor())
       {
       case Lane.RED:
         colorValue = colorValue << 16;
         break;
       case Lane.GREEN:
         colorValue = colorValue << 8;
         break;
       case Lane.BLUE:
         // Value is already in the correct position
         break;
       case Lane.YELLOW:
         colorValue = (colorValue << 16) | (colorValue << 8);
         break;
       }
     
     // set the transparency value to 255=0xff, not transparent. 
     colorValue = (0xff << 24) | colorValue;
     
     // Write the value to the correct position in the pix array.
     // Since the pix array represents a 2D image, the position is
     // pix[y*width + x], with the standard computer graphic coordinate
     // system. 

     // y = i, the row in the image
     // x = the start of the lane + j
     int horzOffset = horzIndex*(laneBorder + laneWidth) + laneBorder;

     for(int j=0; j < laneWidth; j++)
       {
         pix[i*width + horzOffset + j] = colorValue;
       }

     // Move to the next size
     size -= sizeInc;
      }
  }

  /**
   * Writes the border to the specified pixel array. The other parameters
   * help determine where to write in the pixel array.
   *
   * @param pix       the pixel array, represents the gel image.
   * @param length    the length in pixels of the lane graphic and the 
   *                  vertical border.
   * @param horzIndex a number indicating which border to write, the 0th,
   *                  1st, 2nd, etc. This allows the method to determine
   *                  the x-coordinate of the border.
   * @param width     this is the width of the image in pix.
   */
  private void writeBorder(int pix[], int length, int horzIndex, int width)
  {
    int strip[] = new int[laneBorder];

    // make a little strip
    for(int i=0; i < laneBorder; i++)
      strip[i] = bgColor;

    // find the horizontal offset
    int offset = horzIndex * (laneWidth + laneBorder);
    
    // copy the strip all the way down.
    for(int i=0; i < length; i++)
      {
	  System.arraycopy(strip, 0, pix, i*width + offset, laneBorder);
      }
  }

  /**
   * Gives the width of a lane, in pixels. This value is used to determine
   * how wide to make the lanes in the gel image.
   *
   * @return the width
   *
   */
  public int getLaneWidth()
  {
    return laneWidth;
  }

  /**
   * Sets the width of a lane in the gel image.
   *
   * @param value  the width in pixels
   *
   * @see Gel#getPixels
   */
  public void setLaneWidth(int value)
  {
    laneWidth = value;
  }

  /**
   * Gives the spacing between two lanes in the gel image.
   *
   * @return the width in pixels between two lanes in the gel image, also
   *         the left and right borders.
   *
   * @see Gel#getPixels
   */
public int getLaneBorder()
  {
    return laneBorder; 
  }

  /**
   * Sets the spacing between lanes as well as the left and right border
   * on the gel image.
   *
   * @param value the width of the spacing, in pixels
   *
   * @see Gel#getPixels
   */
  public void setLaneBorder(int value)
  {
    laneBorder = value;
  }

  /**
   * Gives the height of the border on the top and bottom of the gel
   * image.
   *
   * @return the height in pixels.
   *
   * @see Gel#getPixels
   */
  public int getGelTopBorder()
  {
    return gelTopBorder;
  }

  /**
   * Sets the border on the top and bottom of the gel image to the
   * specified value.
   *
   * @param value the height of the border in pixels
   *
   * @see Gel#getPixels
   */
  public void setGelTopBorder(int value)
  {
    gelTopBorder = value;
  }

  /**
   * Gives the maximum intensity in the gel between the two
   * specified sizes.
   *
   * @param minSize  the lower bound for searching, in bp
   * @param maxSize  the upper bound for searching, in bp
   *
   * @return the maximum intensity in the gel, -1 if the sizes are invalid
   * or if the max < min.
   */
  public double getMaxIntensity(double minSize, double maxSize)
  {
    // initalize the max
    double gelMax = -1;
    double laneMax;

    // go through all of the lanes, and compare the lane max to the gel
    // max.
    for(int i=0; i < numLanes; i++)
      {
     laneMax = ((Lane) lanes.dataAt(i)).getMaxHeight(minSize, maxSize);
     if(laneMax > gelMax)
       gelMax = laneMax;
      }

    return gelMax;
  }

  /**
   * Gives the maximum intensity of the gel. This method will execute
   * considerable faster than <code>getMaxIntensity(double, double) 
   * because this value is stored in the gel and updated only when
   * lanes are added. However, the slower method will scan for the
   * maximum in the current lanes and in a limited range.
   *
   * @return the maximum intensity
   */
  public double getGlobalMaxIntensity()
  {
    return maxGlobalIntensity;
  }

  /**
   * Gives the number of lanes stored in this gel.
   *
   * @return the number of lanes
   */
  public int getNumLanes()
  {
    return numLanes;
  }

  /**
   * Gives the size in bp represented by each pixel in the pixel
   * array.
   *
   * @return the scale of the image, bps per pixel.
   */
  public double getSizeInc()
  {
    return sizeInc;
  }

  /**
   * Gives the smallest size that will be included in the gel image when
   * it is produced.
   *
   * @return the size in bp
   *
   * @see Gel#getPixels
   */
  public double getMinSize()
  {
    return minSize;
  }

  /**
   * Sets the smallest size that will be included in the gel image when
   * it is produced. This method will <b>not</b> check to see if the
   * specified value is valid for this gel.
   *
   * @param minSize  the size in bp
   *
   * @see Gel#getPixels
   */
  public void setMinSize(double minSize)
  {
    this.minSize = minSize;
  }

  /**
   * Gives the largest size that will be included in the gel image when
   * it is produced.
   *
   * @return the size in bp
   *
   * @see Gel#getPixels
   */
  public double getMaxSize()
  {
    return maxSize;
  }

  /**
   * Sets the largest size that will be included in the gel image when
   * it is produced. This method will <b>not</b> check to see if the
   * specified value is valid for this gel.
   *
   * @param maxSize  the size in bp
   *
   * @see Gel#getPixels
   */
  public void setMaxSize(double maxSize)
  {
    this.maxSize = maxSize;
  }

  /**
   * Gives the height to which all values will be scaled to transform 
   * them into colors. The smaller this number is, the brighter a gel 
   * will appear. Remember that anything above this value will simply
   * be clipped to the maximum color value.
   *
   * @return the maximum expected height of a trace.
   *
   * @see Gel#getPixels
   */
  public double getIntensity()
  {
    return intensity;
  }

  /**
   * Sets the intensity to the specified value. The intensity is used when
   * the pixel array is assebled. It is the value which corresponds to the
   * brightest color on the gel. Any value below it will be dimmer, and any
   * value above it will have the same intensity.
   *
   * @param intensity   the new value
   *
   * @see Gel#getPixels
   */
  public void setIntensity(double intensity)
  {
    this.intensity = intensity;
  }

  /**
   * Gives the color of the background for the gel.
   *
   * @return an integer representing the color, in the form
   *     0xAARRGGBB, in hex, where 'A' represents the alpha value,
   *     'R' the red, 'G', the green, and 'B' the blue.
   */
  public int getBackGroundColor()
  {
    return bgColor;
  }

  /**
   * Set the background color to the specified value.
   *
   * @param color  the color as a 32-bit value, in the form 0xAARRGGBB, for
   *     alpha, red, green, and blue components.
   */
  public void setBackGroundColor(int color)
  {
    bgColor = color;
  }

  /**
   * Clones the gel <b>Not Implemented</b>.
   */
  public Object clone()
  {
    System.out.println("Gel clone not implemented.");
    return this;
  }

  /**
   * Writes the gel image in a PPM format to the specified output
   * stream. If the pixels have been created using <code>getPixels</code>
   * then that will be used to write the PPM. Otherwise, an image of
   * length 500 will be created.
   *
   * @param out   the stream to write the PPM to
   *
   * @exception IOException occurs if problems writing to the stream are
   *        incountered.
   */
  public void writePPM(DataOutputStream out) throws IOException
  {
    // check and see if the pixels have been created
    if(pix == null)
      pix = getPixels(500);

    // Write out some of the info
    // the magic number
    out.write("P6\n".getBytes());

    // Now a couple of comments.
    // Remove the comments, since LView can't handle them.
    //out.write("# Created by Gene Score.\n".getBytes());
    //out.write(("# " + numLanes + " lanes with sizes from " + minSize 
    //        + " to " + maxSize + ".\n").getBytes());
    //out.write(("# Max Height  of " + intensity + "\n").getBytes());

    int width = numLanes*(laneWidth + laneBorder) + laneBorder;
    int height = pix.length/width;

    // the width and the height
    out.write(("" + width + " " + height + "\n").getBytes());
    // the maximum color value
    out.write("255\n".getBytes());

    // now for the pixel info
    byte color[] = new byte[3]; // each triple.
    for(int i=0; i < pix.length; i++)
      {
     color[0] = (byte) (pix[i] >> 16); //red
     color[1] = (byte) (pix[i] >> 8);  //blue
     color[2] = (byte) (pix[i]);       //green
     out.write(color);
      }    
  }

  /**
   * Writes the gel image in a PNG format to the specified output
   * stream. If the pixels have been created using <code>getPixels</code>
   * then that will be used to write the PNG. Otherwise, an image of
   * length 500 will be created.
   *
   * <p>To do the actual writing, the method uses a library produced
   * by VisualTek. The library is specified in full here, as well
   * as the java.awt.Image class. My thanks to them.
   *
   * <p>Java PNG by VisualTek Solutions Inc.<BR>
   * <a href="http://www.visualtek.com/PNG/">http://www.visualtek.com/PNG/</a>
   *
   * <p>An image is created from the pixels and it is then passed to
   * the libaray.
   *
   * @param out   the stream to write the PPM to
   *
   * @exception IOException occurs if problems writing to the stream are
   *        incountered.
   */
  public void writePNG(DataOutputStream out) throws IOException
  {
      //    java.awt.Image gelImage;
    java.awt.image.MemoryImageSource imgSource;
    //    java.awt.Tookit tk;
    com.visualtek.png.PNGEncoder pngWriter;

    // check and see if the pixels have been created
    if(pix == null)
      pix = getPixels(500);

    // get the width
    int width = numLanes*(laneWidth + laneBorder) + laneBorder;

    // get the height
    int length = pix.length/width;
    
    // Make the new image and store it.
    imgSource = new java.awt.image.MemoryImageSource(width, length, pix, 
						     0, width);
    //    tk = java.awt.Toolkit.getDefaultToolkit();

    //    gelImage = tk.createImage(imgSource);

    pngWriter = new com.visualtek.png.PNGEncoder(imgSource, out);
    pngWriter.encode();
  }  

  /**
   * 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.writeInt(numLanes);
    out.writeDouble(maxGlobalIntensity);

    out.writeInt(laneWidth);
    out.writeInt(laneBorder);
    out.writeInt(gelTopBorder);
    out.writeInt(bgColor);

    out.writeDouble(minSize);
    out.writeDouble(maxSize);
    out.writeDouble(intensity);

    out.writeDouble(sizeInc);

    // Lanes
    for(int i=0; i < numLanes; i++)
      ((Lane) lanes.dataAt(i)).write(out);

    // Selected Lanes
    out.writeInt(selectedLanes.size());
    for(int i=0; i < selectedLanes.size(); i++)
      out.writeShort( (short) ((Lane)
selectedLanes.dataAt(i)).getLaneIndex());

    // Bins
    out.writeInt(bins.size());
    for(int i=0; i < bins.size(); i++)
      ((Bin) bins.dataAt(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
  {
    numLanes = in.readInt();
    maxGlobalIntensity = in.readDouble();

    laneWidth = in.readInt();
    laneBorder = in.readInt();
    gelTopBorder = in.readInt();
    bgColor = in.readInt();

    minSize = in.readDouble();
    maxSize = in.readDouble();
    intensity = in.readDouble();

    sizeInc = in.readDouble();

    Lane ln;
    lanes = new DataList(numLanes);
    for(int i=0; i < numLanes; i++)
      {
     ln = new Lane(null);
     ln.read(in);
     lanes.addData(ln);
      }

    // selected lanes
    int length = in.readInt();
    int laneID;
    selectedLanes = new DataList(8);
    for(int i=0; i < length; i++)
      {
     laneID = in.readShort();
     // try to match it to one of the lanes
     for(int j=0; j < numLanes; j++)
       {
         ln = (Lane) lanes.dataAt(j);
         if(laneID == ln.getLaneIndex())
           {
          selectedLanes.addData(ln);
          break;
           }
       }
      }

    // Bins
    Bin b;
    length = in.readInt();
    int numLanes;
    bins = new DataList(length);
    for(int i=0; i < length; i++)
      {
     b = new Bin(1);
     b.read(in);
     bins.addData(b);
     
     if(b.isScored())
       {
         // get the scoring info, which can't be done by the bin.
         // this is a horrible hack, but it keeps nicer ways would make
         // the class way more open than I want it to be
         numLanes = in.readInt();
         int keyID;
         String value;
         for(int j=0; j < numLanes; j++)
           {
          keyID = in.readInt();
          value = in.readUTF();
          
          // try to match the number to one of the lanes
          for(int k=0; k < numLanes; k++)
            {
              ln = (Lane) lanes.dataAt(k);
              if(keyID == ln.getLaneIndex())
                {
               b.setScore(ln, value);
               break;
                }
            }
           }
       }
      }
  }
}
