/* BasePlot.java
 *
 * created: Tue Dec 15 1998
 *
 * This file is part of Artemis
 *
 * Copyright (C) 1998,1999,2000  Genome Research Limited
 *
 * 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; either version 2
 * of the License, or (at your option) any later version.
 *
 * 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.
 *
 * $Header: /nfs/disk222/yeastpub/Repository/powmap/diana/components/BasePlot.java,v 1.25 2000/09/18 09:51:31 kmr Exp $
 **/

package diana.components;

import diana.*;
import diana.sequence.*;
import diana.plot.*;

import java.awt.*;
import java.awt.event.*;

/**
 *  A component for plotting functions over the base sequence.  Scrolling and
 *  scale is tied to a FeatureDisplay component.
 *
 *  @author Kim Rutherford
 *  @version $Id: BasePlot.java,v 1.25 2000/09/18 09:51:31 kmr Exp $
 **/

public class BasePlot extends Plot
    implements DisplayAdjustmentListener, SelectionChangeListener {
  /**
   *  Create a new FeatureDisplay object.
   *  @param algorithm The object that will generate the value we plot in
   *    this component.
   *  @param selection Used to set and display the current selection in the
   *    BasePlot.
   *  @param goto_event_source The object the we will call gotoBase () on.
   *    This allows the user to double click on a base in a BasePlot and have
   *    the FeatureDisplay follow.
   **/
  public BasePlot (final BaseAlgorithm algorithm,
                   final Selection selection,
                   final GotoEventSource goto_event_source) {
    super (algorithm, false);   // false means don't draw the scale line

    this.selection = selection;
    this.goto_event_source = goto_event_source;

    getSelection ().addSelectionChangeListener (this);

    addPlotMouseListener (new PlotMouseListener () {
      /**
       *  Set the selection to be a range from start_base to end_base.
       **/
      private void setSelectionRange (final int start_base,
                                      final int end_base) {
        final Strand strand =
          getBaseAlgorithm ().getBases ().getForwardStrand ();

        try {
          final MarkerRange marker_range =
            strand.makeMarkerRangeFromPositions (start_base, end_base);
          getSelection ().setMarkerRange (marker_range);
        } catch (uk.ac.sanger.pathogens.OutOfRangeException e) {
          getSelection ().clear ();
        }
      }

      /**
       *  Called when the user clicks somewhere on the plot canvas.
       *  @param position the base/amino acid position of the click.  This is
       *    -1 if and only if the click was outside the graph (eg. in the
       *    label at the top)
       **/
      public void mouseClick (final int position) {

      }

      /**
       *  Called when the user drags the mouse over the plot.
       *  @param drag_start_position The base/amnino acid position where the
       *    drag started or -1 if the drag was started outside the graph.
       *  @param current_position the base/amino acid position of the click.
       *    This is -1 if and only if the user has dragged the mouse out of
       *    the graph (eg. in the label at the top)
       **/
      public void mouseDrag (final int drag_start_position,
                             final int current_position) {
        setSelectionRange (drag_start_position, current_position);
      }

      /**
       *  Called when the user double-clicks somewhere on the plot.
       *  @param position the base/amino acid position of the click.  This is
       *    -1 if and only if the click was outside the graph (eg. in the
       *    label at the top)
       **/
      public void mouseDoubleClick (final int position) {
        setSelectionRange (position, position);
        getGotoEventSource ().gotoBase (position);
      }
    });
  }

  /**
   *  Used by getPreferredSize () and getMinimumSize ();
   **/
  private final static int HEIGHT = 150;

  /**
   *  Overridden to set the component height to 150.
   **/
  public Dimension getPreferredSize() {
    return (new Dimension(getSize ().width, HEIGHT));
  }
  
  /**
   *  Overridden to set the component height to 150.
   **/
  public Dimension getMinimumSize() {
    return (new Dimension(getSize ().width, HEIGHT));
  }
  
  /**
   *  Implementation of the DisplayAdjustmentListener interface.  Invoked when
   *  a component or changes the scale.
   **/
  public void displayAdjustmentValueChanged (DisplayAdjustmentEvent event) {
    start_base = event.getStart ();
    end_base = event.getEnd ();
    width_in_bases = event.getWidthInBases ();

    recalculate_flag = true;

    repaintCanvas ();
  }

  /**
   *  We listen for SelectionChange events so that we can update the
   *  crosshairs.
   **/
  public void selectionChanged (SelectionChangeEvent event) {
    selection_start_marker = null;
    selection_end_marker = null;
    repaintCanvas ();
  }

  /**
   *  Return the algorithm that was passed to the constructor.
   **/
  public BaseAlgorithm getBaseAlgorithm () {
    return (BaseAlgorithm) super.getAlgorithm ();
  }

  /**
   *  Return the new start base to display, from the last event.
   **/
  private int getStart () {
    return start_base;
  }

  /**
   *  Return the new end base to display, from the last event.
   **/
  private int getEnd () {
    return end_base;
  }

  /**
   *  Return the width in bases of the display, from the last event.
   **/
  private int getWidthInBases () {
    return width_in_bases;
  }

  /**
   *  This array is used by drawMultiValueGraph ().  It is reallocated when
   *  the scale changes.
   **/
  private float [][] value_array_array = null;

  /**
   *  The number of bases to step before each evaluation of the algorithm.
   *  (Set by recalculateValues ()).
   **/
  private int step_size = 0;

  /**
   *  The maximum of the values in value_array_array.
   **/
  private float min_value = Float.MAX_VALUE;

  /**
   *  The minimum of the values in value_array_array.
   **/
  private float max_value = Float.MIN_VALUE;

  /**
   *  Recalculate the values in value_array_array, step_size, min_value and
   *  max_value.
   **/
  protected void recalculateValues () {
//      try {
//        throw new Error ("");
//      } catch (Error e) {
//        System.out.println ("recalculating:");
//        e.printStackTrace ();
//      }

    final Float algorithm_minimum = getAlgorithm ().getMinimum ();
    final Float algorithm_maximum = getAlgorithm ().getMaximum ();

    // use the Algorithm specified maximum if there is one - otherwise
    // calculate it
    if (algorithm_maximum == null) {
      max_value = Float.MIN_VALUE;
    } else {
      max_value = algorithm_maximum.floatValue ();
    }

    // use the Algorithm specified minimum if there is one - otherwise
    // calculate it
    if (algorithm_minimum == null) {
      min_value = Float.MAX_VALUE;
    } else {
      min_value = algorithm_minimum.floatValue ();
    }

    final int window_size = getWindowSize ();

    final Integer default_step_size =
      getAlgorithm ().getDefaultStepSize (window_size);

    if (default_step_size == null) {
      step_size = window_size;
    } else {
      if (default_step_size.intValue () < window_size) {
        step_size = default_step_size.intValue ();
      } else {
        step_size = window_size;
      }
    }

    final int unit_count = getEnd () - getStart ();

    // the number of plot points in the graph
    final int number_of_values =
      (unit_count - (window_size - step_size)) / step_size;

    if (number_of_values < 2) {
      // there is nothing to plot
      value_array_array = null;
      return;
    }

    // the number of values that getValues () will return
    final int get_values_return_count =
      getBaseAlgorithm ().getValueCount ();

    if (value_array_array == null) {
      value_array_array = new float [get_values_return_count][];
    }

    if (value_array_array[0] == null ||
        value_array_array[0].length != number_of_values) {
      for (int i = 0 ; i < value_array_array.length ; ++i) {
        value_array_array[i] = new float [number_of_values];
      }
    } else {
      // reuse the previous arrays
    }

    float [] temp_values = new float [get_values_return_count];

    for (int i = 0 ; i < number_of_values ; ++i) {
      getBaseAlgorithm ().getValues (getStart () + i * step_size,
                                     getStart () + i * step_size +
                                     window_size - 1,
                                     temp_values);

      for (int value_index = 0 ;
           value_index < get_values_return_count ;
           ++value_index) {
        final float current_value = temp_values[value_index];

        value_array_array[value_index][i] = current_value;

        // use the Algorithm specified maximum if there is one - otherwise
        // calculate it
        if (algorithm_maximum == null) {
          if (current_value > max_value) {
            max_value = current_value;
          }
        }

        // use the Algorithm specified minimum if there is one - otherwise
        // calculate it
        if (algorithm_minimum == null) {
          if (current_value < min_value) {
            min_value = current_value;
          }
        }
      }
    }

    recalculate_flag = false;
  }

  /**
   *  Redraw the graph on the canvas using the algorithm, start_base and
   *  end_base.  This method plots BaseWindowAlgorithm objects only.
   *  @param g The object to draw into.
   **/
  public void drawMultiValueGraph (Graphics g) {
    if (recalculate_flag) {
      recalculateValues ();
    }

    if (value_array_array == null) {
      // there is nothing to draw - probably because the sequence is too short
      drawMinMax (g, 0, 1);

      return;
    }

    final int window_size = getWindowSize ();

    // the number of values to plot at each x position
    final int get_values_return_count =
      getBaseAlgorithm ().getValueCount ();

    final int number_of_values = value_array_array[0].length;

    if (number_of_values > 1) {
      drawGlobalAverage (g, min_value, max_value);
    }

    for (int value_index = 0 ;
         value_index < get_values_return_count ;
         ++value_index) {
      if (get_values_return_count == 1) {
        g.setColor (Color.black);
      } else {
        switch (value_index) {
        case 0:
          g.setColor (new Color (255, 0, 0));
          break;
        case 1:
          g.setColor (new Color (0, 200, 0));
          break;
        case 2:
          g.setColor (new Color (0, 0, 255));
          break;
        default:
          g.setColor (Color.black);
        }
      }

      drawPoints (g, min_value, max_value, step_size, window_size,
                  getWidthInBases (),
                  value_array_array[value_index]);
    }

    drawMinMax (g, min_value, max_value);

    if (getCrossHairPosition () >= 0) {
      final int cross_hair_position = getCrossHairPosition ();

      final int base = getPointPosition (cross_hair_position);

      if (base > end_base) {
        cancelCrossHairs ();
      } else {
        drawCrossHair (g, cross_hair_position,
                       String.valueOf (base),
                       0);
      }
    }

    if (getCrossHairPosition () >= 0 && getSelectionStartMarker () != null) {
      final int first_base = getSelectionStartMarker ().getRawPosition ();

      if (Math.abs (first_base -
                    getPointPosition (getCrossHairPosition ())) > 3) {
        drawCrossHair (g, getCanvasPosition (first_base),
                       String.valueOf (first_base), 1);
      } else {
        // don't draw - too close to main cross hair
      }
    }

    if (getCrossHairPosition () >= 0 && getSelectionEndMarker () != null) {
      if (getSelectionStartMarker () != null &&
          Math.abs ((getSelectionEndMarker ().getRawPosition () -
                     getSelectionStartMarker ().getRawPosition ())) >= 3) {
        final int last_base = getSelectionEndMarker ().getRawPosition ();
        if (Math.abs (last_base -
                      getPointPosition (getCrossHairPosition ())) > 3) {
          drawCrossHair (g, getCanvasPosition (last_base),
                         String.valueOf (last_base), 2);
        } else {
          // don't draw - too close to main cross hair
        }
      }
    }
  }

  /**
   *  Get the position in the Feature of the given canvas x position.  This
   *  base position is the label used when the user clicks the mouse in on the
   *  canvas (see drawCrossHair ()).
   **/
  protected int getPointPosition (final int canvas_x_position) {
    return (int) ((1.0 * canvas_x_position / getCanvas ().getSize ().width) *
                  getWidthInBases ()) + getStart ();
  }

  /**
   *  Return the canvas position of the given base,
   **/
  private int getCanvasPosition (final int base) {
    return (int) ((1.0 * base - getStart ()) / getWidthInBases () *
                  getCanvas ().getSize ().width);
  }

  /**
   *  Return the Marker of the start base of the Selection or null if there is
   *  nothing selected.
   **/
  private Marker getSelectionStartMarker () {
    if (selection_start_marker == null) {
      selection_start_marker = getSelection ().getLowestBaseOfSelection ();
    }

    return selection_start_marker;
  }

  /**
   *  Return the Marker of the end base of the Selection or null if there is
   *  nothing selected.
   **/
  private Marker getSelectionEndMarker () {
    if (selection_end_marker == null) {
      selection_end_marker = getSelection ().getHighestBaseOfSelection ();
    }

    return selection_end_marker;
  }

  /**
   *  Return the Selection object that was passed to the constructor.
   **/
  private Selection getSelection () {
    return selection;
  }

  /**
   *  Return the GotoEventSource object that was passed to the constructor.
   **/
  private GotoEventSource getGotoEventSource () {
    return goto_event_source;
  }

  /**
   *  The start base to plot, as obtained from the DisplayAdjustmentEvent.
   **/
  private int start_base;

  /**
   *  The end base to plot, as obtained from the DisplayAdjustmentEvent.
   **/
  private int end_base;

  /**
   *  The width in bases of the display, as obtained from the
   *  DisplayAdjustmentEvent.
   **/
  private int width_in_bases;

  /**
   *  The Marker of the start of the selection.  This is a cache used by
   *  getSelectionStartMarker ().
   **/
  private Marker selection_start_marker = null;

  /**
   *  The Marker of the end of the selection.  This is a cache used by
   *  getSelectionEndMarker ()
   **/
  private Marker selection_end_marker = null;

  /**
   *  The Selection that was passed to the constructor.
   **/
  private Selection selection;

  /**
   *  The GotoEventSource that was passed to the constructor.
   **/
  private GotoEventSource goto_event_source;
}
