/* FeaturePlot.java
 *
 * created: Wed Dec 16 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: /cvsroot/pathsoft/artemis/uk/ac/sanger/artemis/components/FeaturePlot.java,v 1.4 2004/11/29 14:09:54 tjc Exp $
 */

package uk.ac.sanger.artemis.components;

import uk.ac.sanger.artemis.sequence.*;
import uk.ac.sanger.artemis.*;
import uk.ac.sanger.artemis.plot.*;
import java.awt.*;
import java.awt.event.*;

/**
 *  The components of this class display a plot of a FeatureAlgorithm for a
 *  particular feature.
 *
 *  @author Kim Rutherford
 *  @version $Id: FeaturePlot.java,v 1.4 2004/11/29 14:09:54 tjc Exp $
 **/

public class FeaturePlot extends Plot
    implements DisplayAdjustmentListener, FeatureChangeListener {
  /**
   *  Create a new FeatureDisplay object.
   *  @param algorithm The object that will generate the values we plot in
   *    this component.
   **/
  public FeaturePlot (FeatureAlgorithm algorithm) {
    super (algorithm, true);    // true means draw a scale at the bottom of
                                // the graph

    getFeature ().addFeatureChangeListener (this);

    recalculate_flag = true;
  }

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

  static {
    final Integer feature_plot_height =
      Options.getOptions ().getIntegerProperty ("feature_plot_height");

    if (feature_plot_height == null) {
      HEIGHT = 160;
    } else {
      HEIGHT = feature_plot_height.intValue ();
    }
  }

  /**
   *  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));
  }
  
  /**
   *  Remove this object as a feature change listener.  (Called by
   *  FeaturePlotGroup)
   **/
  void stopListening () {
    getFeature ().removeFeatureChangeListener (this);
  }

  /**
   *  Implementation of the DisplayAdjustmentListener interface.  Invoked when
   *  a component scrolls 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;

    repaint();
  }

  /**
   *  Implementation of the FeatureChangeListener interface.
   *  @param event The change event.
   **/
  public void featureChanged (FeatureChangeEvent event) {
    recalculate_flag = true;
  }

  /**
   *  Return the algorithm that was passed to the constructor.
   **/
  public FeatureAlgorithm getFeatureAlgorithm () {
    return (FeatureAlgorithm) 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 drawGraph ().  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 () {
    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 =
      getFeatureAlgorithm ().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
    }

    if (!isVisible ()) {
      return;
    }

    float [] temp_values = new float [get_values_return_count];

    for (int i = 0 ; i < number_of_values ; ++i) {
      getFeatureAlgorithm ().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.
   **/
  protected int drawMultiValueGraph (Graphics g, Color[] frameColour) {
    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 0;
    }
    
    final int window_size = getWindowSize ();

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

    // the number of x positions to plot points at
    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 (100, 255, 100));
          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,
                  getSize ().width,
                  0, // no offset.
                  value_array_array[value_index]);
    }

    drawMinMax (g, min_value, max_value);

    drawScaleLine (g, getStart (), getEnd ());

    final int cross_hair_position = getCrossHairPosition ();

    if (cross_hair_position >= 0) {
      if (cross_hair_position >= getTranslation ().length ()) {
        cancelCrossHairs ();
      } else {
        drawCrossHair (g, cross_hair_position,
                       String.valueOf (getPointPosition (cross_hair_position +
                                                         getStart () - 1)),
                       0);
      }
    }

    return get_values_return_count;
  }

  /**
   *  Get the position in the Feature of the given canvas x position.  This
   *  amino acid 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 canvas_x_position + 1;
  }

  /**
   *  Return the feature that this component is plotting.
   **/
  private Feature getFeature () {
    return getFeatureAlgorithm ().getFeature ();
  }

  /**
   *  Return the translation of the bases of the feature we are plotting.
   **/
  private AminoAcidSequence getTranslation () {
    return getFeature ().getTranslation ();
  }

  /**
   *  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;
}
