/* AlignmentViewer.java
 *
 * created: Mon Jul 12 1999
 *
 * This file is part of Artemis
 *
 * Copyright (C) 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/AlignmentViewer.java,v 1.19 2000/08/31 11:07:45 kmr Exp $
 */

package diana.components;

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

import uk.ac.sanger.pathogens.OutOfRangeException;
import uk.ac.sanger.pathogens.embl.Range;

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

/**
 *  This component shows an alignment of two sequences using the data from a
 *  ComparisonData object.
 *
 *  @author Kim Rutherford
 *  @version $Id: AlignmentViewer.java,v 1.19 2000/08/31 11:07:45 kmr Exp $
 **/

public class AlignmentViewer extends CanvasPanel
    implements SequenceChangeListener {
  /**
   *  Create a new AlignmentViewer for the given entries.
   *  @param comparison_data Provides the AlignMatch objects that will be
   *    displayed.
   **/
  public AlignmentViewer (final Comparator owner,
                          final ComparisonData comparison_data) {
    this.owner              = owner;
    this.comparison_data    = comparison_data;

    final Bases subject_bases = owner.getSubjectEntryGroup ().getBases ();
    final Bases query_bases = owner.getQueryEntryGroup ().getBases ();

    subject_bases.addSequenceChangeListener (this, 0);
    query_bases.addSequenceChangeListener (this, 0);

    subject_forward_strand = getSubjectForwardStrand ();
    query_forward_strand = getQueryForwardStrand ();

    score_colours = makeColours ();

    getCanvas ().addMouseListener (new MouseAdapter () {
      public void mousePressed (final MouseEvent event) {
        handleCanvasMousePress (event);



        if (event.isPopupTrigger () || event.isMetaDown ()) {

//            final PopupMenu popup = new PopupMenu ();

//            final MenuItem cutoffs_item = new MenuItem ("Set Cutoffs ...");

//            popup.add (cutoffs_item);

//            cutoffs_item.addActionListener (new ActionListener () {
//              public void actionPerformed (ActionEvent _) {
          final ScoreChangeListener minimum_listener =
            new ScoreChangeListener () {
              public void scoreChanged (final ScoreChangeEvent event) {
                minimum_score = event.getValue ();
                repaintCanvas ();
              }
            };

          final ScoreChangeListener maximum_listener =
            new ScoreChangeListener () {
              public void scoreChanged (final ScoreChangeEvent event) {
                maximum_score = event.getValue ();
                repaintCanvas ();
              }
            };

          final ScoreChanger score_changer =
            new ScoreChanger ("Score Cutoffs",
                              minimum_listener, maximum_listener);

          score_changer.setVisible (true);
//              }
//            });

//            getCanvas ().add (popup);

//            popup.show (canvas, event.getX (), event.getY ());

        }
      }
    });

    getCanvas ().addMouseMotionListener (new MouseMotionAdapter () {
      public void mouseDragged (final MouseEvent event) {
        setSelection (event.getPoint ());
        repaintCanvas ();
      }
    });

    scroll_bar = new Scrollbar (Scrollbar.VERTICAL, 1, 1, 1, 1000);

    scroll_bar.setBlockIncrement (10);

    scroll_bar.addAdjustmentListener (new AdjustmentListener () {
      public void adjustmentValueChanged(AdjustmentEvent e) {
        repaintCanvas ();
      }
    });

    add (scroll_bar, "East");
  }

  /**
   *  Select those matches that overlap the given range on the subject
   *  sequence.
   **/
  public void selectFromSubjectRange (final Range select_range) {
    if (select_range == null) {
      return;
    }

    if (dont_update_from_range) {
      return;
    }

    selected_matches = null;

    final AlignMatch [] visible_matches = comparison_data.getMatches ();

    for (int i = 0 ; i < visible_matches.length ; ++i) {
      final AlignMatch this_match = visible_matches [i];

      int subject_sequence_start = getRealSubjectSequenceStart (this_match);
      int subject_sequence_end = getRealSubjectSequenceEnd (this_match);

      if (subject_sequence_end < subject_sequence_start) {
        final int tmp = subject_sequence_start;
        subject_sequence_start = subject_sequence_end;
        subject_sequence_end = tmp;
      }

      if (select_range.getStart () < subject_sequence_start &&
          select_range.getEnd () < subject_sequence_start) {
        continue;
      }

      if (select_range.getStart () > subject_sequence_end &&
          select_range.getEnd () > subject_sequence_end) {
        continue;
      }

      if (selected_matches == null) {
        selected_matches = new AlignMatchVector ();
      }

      selected_matches.add (this_match);
    }

    repaintCanvas ();
  }

  /**
   *  Select those matches that overlap the given range on the query sequence.
   **/
  public void selectFromQueryRange (final Range select_range) {
    if (select_range == null) {
      return;
    }

    if (dont_update_from_range) {
      return;
    }

    selected_matches = null;

    final AlignMatch [] visible_matches = comparison_data.getMatches ();

    for (int i = 0 ; i < visible_matches.length ; ++i) {
      final AlignMatch this_match = visible_matches [i];

      int query_sequence_start = getRealQuerySequenceStart (this_match);
      int query_sequence_end   = getRealQuerySequenceEnd (this_match);

      if (query_sequence_end < query_sequence_start) {
        final int tmp = query_sequence_start;
        query_sequence_start = query_sequence_end;
        query_sequence_end = tmp;
      }

      if (select_range.getStart () < query_sequence_start &&
          select_range.getEnd () < query_sequence_start) {
        continue;
      }

      if (select_range.getStart () > query_sequence_end &&
          select_range.getEnd () > query_sequence_end) {
        continue;
      }

      if (selected_matches == null) {
        selected_matches = new AlignMatchVector ();
      }

      selected_matches.add (this_match);
    }

    repaintCanvas ();
  }

  /**
   *  This method tells this AlignmentViewer component where the subject
   *  sequence is now.
   **/
  public void setSubjectSequencePosition (final DisplayAdjustmentEvent event) {
    last_subject_event = event;
    repaintCanvas ();
  }

  /**
   *  This method tells this AlignmentViewer component where the query
   *  sequence is now.
   **/
  public void setQuerySequencePosition (final DisplayAdjustmentEvent event) {
    last_query_event = event;
    repaintCanvas ();
  }

  /**
   *  Implementation of the SequenceChangeListener interface.  The display is
   *  redrawn if there is an event.
   **/
  public void sequenceChanged (final SequenceChangeEvent event) {
    repaintCanvas ();
  }

  /**
   *  Handle a mouse press event on the drawing canvas - select on click,
   *  select and broadcast it on double click.
   **/
  private void handleCanvasMousePress (final MouseEvent event) {
    if (event.getID() != MouseEvent.MOUSE_PRESSED) {
      return;
    }

    if (event.getClickCount () == 2) {
      handleCanvasDoubleClick (event);
    } else {
      handleCanvasSingleClick (event);
    }

    repaintCanvas ();
  }

  /**
   *  Handle a double click on the canvas.
   **/
  private void handleCanvasDoubleClick (final MouseEvent event) {
    if (selected_matches != null) {
      // there should be only one match in the array
      alignAt (selected_matches.elementAt (0));
    }
  }

  /**
   *  Scroll both the subject and query
   **/
  private void alignAt (final AlignMatch align_match) {
    owner.unlockDisplays ();

    dont_update_from_range = true;

    maybeRevCompQuery (align_match);

    final int subject_sequence_start = getRealSubjectSequenceStart (align_match);
    final int subject_sequence_end   = getRealSubjectSequenceEnd (align_match);
    final int query_sequence_start   = getRealQuerySequenceStart (align_match);
    final int query_sequence_end     = getRealQuerySequenceEnd (align_match);

    final int new_subject_base =
      subject_sequence_start +
      (subject_sequence_end - subject_sequence_start) / 2;
    owner.getSubjectDisplay ().makeBaseVisible (new_subject_base);

    try {
      final Strand subject_strand = getSubjectForwardStrand ();
      final MarkerRange new_subject_marker =
        subject_strand.makeMarkerRangeFromPositions (subject_sequence_start,
                                                 subject_sequence_end);
      owner.getSubjectSelection ().setMarkerRange (new_subject_marker);
    } catch (OutOfRangeException e) {
      throw new Error ("internal error - unexpected exception: " + e);
    }

    final int new_query_base =
      query_sequence_start +
      (query_sequence_end - query_sequence_start) / 2;
    owner.getQueryDisplay ().makeBaseVisible (new_query_base);

    try {
      final Strand query_strand = getQueryForwardStrand ();
      final MarkerRange new_query_marker =
        query_strand.makeMarkerRangeFromPositions (query_sequence_start,
                                                    query_sequence_end);
      owner.getQuerySelection ().setMarkerRange (new_query_marker);
    } catch (OutOfRangeException e) {
      throw new Error ("internal error - unexpected exception: " + e);
    }

    dont_update_from_range = false;

    owner.lockDisplays ();
  }

  /**
   *  Called by alignAt () to reverse and complement the query sequence if the
   *  given AlignMatch is currently a match from the subject sequence to the
   *  reverse complement of the query sequence.
   **/
  private void maybeRevCompQuery (final AlignMatch align_match) {
    final int subject_sequence_start =
      getRealSubjectSequenceStart (align_match);
    final int subject_sequence_end = getRealSubjectSequenceEnd (align_match);
    final int query_sequence_start = getRealQuerySequenceStart (align_match);
    final int query_sequence_end   = getRealQuerySequenceEnd (align_match);

    if (subject_sequence_start > subject_sequence_end &&
        query_sequence_end > query_sequence_start ||
        subject_sequence_start < subject_sequence_end &&
        query_sequence_end < query_sequence_start) {
      try {
        final YesNoDialog yes_no_dialog =
          new YesNoDialog (owner, "reverse and complement query sequence?");
        if (yes_no_dialog.getResult ()) {
          owner.getQueryEntryGroup ().reverseComplement ();

          final Selection query_selection = owner.getQuerySelection ();

          final Marker marker = query_selection.getStartBaseOfSelection ();

          final GotoEvent new_event = new GotoEvent (this, marker);

          owner.getQueryGotoEventSource ().sendGotoEvent (new_event);
        }
      } catch (uk.ac.sanger.pathogens.ReadOnlyException e) {
        new MessageFrame ("Query sequence is read-only").setVisible (true);
      }
    }
  }

  /**
   *  Handle a single click on the canvas.
   **/
  private void handleCanvasSingleClick (final MouseEvent event) {
    if ((event.getModifiers () & InputEvent.BUTTON2_MASK) != 0 ||
        event.isAltDown () || event.isControlDown ()) {
      owner.toggleDisplayLock ();
    } else {
      setSelection (event.getPoint ());
    }
  }

  /**
   *  Set the selection to be the match at the given point.
   **/
  private void setSelection (final Point point) {
    if (last_subject_event == null || last_query_event == null) {
      return;
    }

    final AlignMatch clicked_align_match =
      getAlignMatchFromPosition (point);

    if (clicked_align_match == null) {
      selected_matches = null;
    } else {
      selected_matches = new AlignMatchVector ();

      selected_matches.add (clicked_align_match);
    }
  }

  /**
   *  Return the AlignMatch at the given Point on screen or null if there is
   *  no match at that point.  The alignment_data_array is searched in reverse
   *  order.
   **/
  private AlignMatch getAlignMatchFromPosition (final Point click_point) {
    final int canvas_height = getCanvas ().getSize ().height;
    final int canvas_width = getCanvas ().getSize ().width;

    final AlignMatch [] matches = comparison_data.getMatches ();

    for (int i = matches.length - 1 ; i >= 0  ; --i) {
      final AlignMatch this_match = matches [i];

      final int [] match_x_positions =
        getMatchCoords (canvas_width, this_match);

      if (match_x_positions == null) {
        continue;
      }

      if (!checkMatch (this_match)) {
        continue;
      }

      final int subject_start_x = match_x_positions[0];
      final int subject_end_x = match_x_positions[1];
      final int query_start_x = match_x_positions[2];
      final int query_end_x = match_x_positions[3];

      // this is the x coordinate of the point where the line y = click_point
      // hits the left edge of the match box
      final double match_left_x =
        subject_start_x +
        (1.0 * (query_start_x - subject_start_x)) *
        (1.0 * click_point.y / canvas_height);

      // this is the x coordinate of the point where the line y = click_point
      // hits the right edge of the match box
      final double match_right_x =
        subject_end_x +
        (1.0 * (query_end_x - subject_end_x)) *
        (1.0 * click_point.y / canvas_height);

//        System.err.println ("match_left_x: " + match_left_x + "  " +
//                            click_point.y);
//        System.err.println ("match_right_x: " + match_right_x);
//        System.err.println ("point: " + click_point);

      if (match_left_x <= click_point.x && match_right_x >= click_point.x ||
          match_left_x >= click_point.x && match_right_x <= click_point.x) {
        return this_match;
      }
    }

    return null;
  }

  /**
   *  Call repaint () on the canvas.
   **/
  private void repaintCanvas () {
    getCanvas ().repaint ();
  }

  /**
   *  The main paint function for the canvas.  An off screen image used for
   *  double buffering when drawing the canvas.
   *  @param g The Graphics object of the canvas.
   **/
  protected void paintCanvas (final Graphics g) {
    fillBackground (g);

    if (last_subject_event != null && last_query_event != null) {
      drawAlignments (g);
      drawLabels (g);
    }
  }

  /**
   *  Draw the background colour of the frames.
   **/
  private void fillBackground (final Graphics g) {
    g.setColor (Color.white);

    g.fillRect (0, 0, getCanvas ().getSize ().width,
                getCanvas ().getSize ().height);
  }

  /**
   *  Draw the labels into the given Graphics object.  There is a label at the
   *  top left showing info about the current AlignMatch object,
   *
   *  XXX
   *  a label at
   *  the bottom left showing whether or not the query sequence is reverse
   *  complemented
   *  XXX
   *
   *  and a label beside the Scrollbar showing the current score
   *  cutoff.
   **/
  private void drawLabels (final Graphics g) {
    final FontMetrics fm = g.getFontMetrics ();
    final int canvas_width = getCanvas ().getSize ().width;
    final int canvas_height = getCanvas ().getSize ().height;

    final String cutoff_label =
      Integer.toString (scroll_bar.getValue ());

    final int cutoff_label_width = fm.stringWidth (cutoff_label);

    int cutoff_label_position =
      (int)((scroll_bar.getValue () -
             scroll_bar.getMinimum ()) / (1.0 *
            (scroll_bar.getMaximum () -
             scroll_bar.getMinimum ())) * canvas_height);

    if (cutoff_label_position < getFontHeight ()) {
      cutoff_label_position = getFontHeight ();
    }


    final int [] cutoff_x_points = {
      canvas_width - cutoff_label_width,
      canvas_width - 2,
      canvas_width - 2,
      canvas_width - cutoff_label_width,
    };

    final int [] cutoff_y_points = {
      cutoff_label_position + 1,
      cutoff_label_position + 1,
      cutoff_label_position - getFontHeight (),
      cutoff_label_position - getFontHeight (),
    };

    g.setColor (Color.white);
    g.fillPolygon (cutoff_x_points, cutoff_y_points, 4);

    g.setColor (Color.black);

    g.drawString (cutoff_label, canvas_width - cutoff_label_width,
                  cutoff_label_position);


    if (selected_matches != null) {
      final String match_string;

      if (selected_matches.size () > 1) {
        match_string = selected_matches.size () + " matches selected";
      } else {
        final AlignMatch selected_align_match = selected_matches.elementAt (0);

        match_string =
          selected_align_match.getQuerySequenceStart () + ".." +
          selected_align_match.getQuerySequenceEnd () + " -> " +
          selected_align_match.getSubjectSequenceStart () + ".." +
          selected_align_match.getSubjectSequenceEnd () + " score " +
          selected_align_match.getScore ();
      }

      final int match_string_width = fm.stringWidth (match_string);

      final int [] match_x_points = {
        0, 0, match_string_width, match_string_width
      };

      final int [] match_y_points = {
        0, getFontHeight () + 1, getFontHeight () + 1, 0
      };

      g.setColor (Color.white);
      g.fillPolygon (match_x_points, match_y_points, 4);

      g.setColor (Color.black);

      g.drawString (match_string, 0, getFontHeight ());
    }
  }

  /**
   *  Draw the alignments into the given Graphics object.
   **/
  private void drawAlignments (final Graphics g) {
    final int canvas_height = getCanvas ().getSize ().height;
    final int canvas_width = getCanvas ().getSize ().width;

    final Range subject_visible_range =
      owner.getSubjectDisplay ().getVisibleRange ();
    final Range query_visible_range =
      owner.getQueryDisplay ().getVisibleRange ();

    final AlignMatch [] visible_matches = comparison_data.getMatches ();
//        comparison_data.getAlignmentsInRange (subject_visible_range,
//                                              query_visible_range);


    for (int i = 0 ; i < visible_matches.length ; ++i) {
      final AlignMatch this_match = visible_matches [i];

      final int [] match_x_positions =
        getMatchCoords (canvas_width, this_match);

      if (match_x_positions == null) {
        continue;
      }

      final int subject_start_x = match_x_positions[0];
      final int subject_end_x = match_x_positions[1];
      final int query_start_x = match_x_positions[2];
      final int query_end_x = match_x_positions[3];

      final int [] x_coords = new int [4];
      final int [] y_coords = new int [4];

      x_coords[0] = subject_start_x;
      y_coords[0] = 0;
      x_coords[1] = query_start_x;
      y_coords[1] = canvas_height;
      x_coords[2] = query_end_x;
      y_coords[2] = canvas_height;
      x_coords[3] = subject_end_x;
      y_coords[3] = 0;

      if (!checkMatch (this_match)) {
        continue;
      }

      final boolean highlight_this_match;

      if (selected_matches != null &&
          selected_matches.contains (this_match)) {
        highlight_this_match = true;
      } else {
        highlight_this_match = false;
      }

      final int OFFSCREEN = 3000;

      final int score = this_match.getScore ();

      if (highlight_this_match) {
        g.setColor (Color.yellow);
      } else {
        if (score == -1) {
          g.setColor (Color.red);
        } else {
          int colour_index = score_colours.length - 1;

          if (maximum_score > minimum_score) {
            colour_index =
              (int)(score_colours.length * 0.999 *
                    (score - minimum_score) /
                    (maximum_score - minimum_score));

          }
          g.setColor (score_colours[colour_index]);
        }
      }

      g.fillPolygon (x_coords, y_coords, x_coords.length);

      if (subject_end_x - subject_start_x < 5 &&
          subject_end_x - subject_start_x > -5 ||
          subject_start_x < -OFFSCREEN ||
          subject_end_x > OFFSCREEN ||
          query_start_x < -OFFSCREEN ||
          query_end_x > OFFSCREEN) {

        // match is (probably) narrow - draw an outline in the match
        // colour to thicken it up a bit

      } else {

        // draw a black outline the make the match stand out
        g.setColor (Color.black);
      }

      g.drawLine (subject_start_x, 0, query_start_x, canvas_height);
      g.drawLine (subject_end_x, 0, query_end_x, canvas_height);
    }
  }

  /**
   *  Return true if and only if the given match is currently visible.
   **/
  private boolean checkMatch (final AlignMatch match) {
    final int score = match.getScore ();

    if (score > -1) {
      if (score < minimum_score || score > maximum_score) {
        return false;
      }
    }

    if (Math.abs (match.getSubjectSequenceStart () -
                  match.getSubjectSequenceEnd ()) <
        scroll_bar.getValue ()) {
      return false;
    }

    return true;
  }

  /**
   *  Return the screen x positions of the corners of the given match.  The
   *  order is Top Left, Top Right, Bottom Left, Bottom Right, unless the
   *  match is an inversion, in which case it will be TL,TR,BR,BL.  Returns
   *  null if and only if the match is not currently visible.
   **/
  private int [] getMatchCoords (final int canvas_width,
                                 final AlignMatch this_match) {
    final int subject_sequence_start =
      getRealSubjectSequenceStart (this_match);
    final int subject_sequence_end =
      getRealSubjectSequenceEnd (this_match);
    final int query_sequence_start = getRealQuerySequenceStart (this_match);
    final int query_sequence_end   = getRealQuerySequenceEnd (this_match);

    final int subject_start_x =
      getScreenPosition (canvas_width, last_subject_event,
                         subject_sequence_start);
    final int subject_end_x =
      getScreenPosition (canvas_width, last_subject_event,
                         subject_sequence_end + 1);

    final int query_start_x =
      getScreenPosition (canvas_width, last_query_event,
                         query_sequence_start);
    final int query_end_x =
      getScreenPosition (canvas_width, last_query_event,
                         query_sequence_end + 1);

    boolean subject_off_left = false;
    boolean subject_off_right = false;
    boolean query_off_left = false;
    boolean query_off_right = false;

    if (subject_start_x < 0 && subject_end_x < 0) {
      subject_off_left = true;
    }

    if (subject_start_x >= canvas_width && subject_end_x >= canvas_width) {
      subject_off_right = true;
    }

    if (query_start_x < 0 && query_end_x < 0) {
      query_off_left = true;
    }

    if (query_start_x >= canvas_width && query_end_x >= canvas_width) {
      query_off_right = true;
    }

    if ((subject_off_left ? 1 : 0) +
        (query_off_left ? 1 : 0) +
        (subject_off_right ? 1 : 0) +
        (query_off_right ? 1 : 0) == 2) {
      return null;
    } else {
      final int [] return_values = new int [4];

      return_values[0] = subject_start_x;
      return_values[1] = subject_end_x;
      return_values[2] = query_start_x;
      return_values[3] = query_end_x;

      return return_values;
    }
  }

  /**
   *  Return the current forward Strand of the subject EntryGroup.
   **/
  private Strand getSubjectForwardStrand () {
    return owner.getSubjectEntryGroup ().getBases ().getForwardStrand ();
  }

  /**
   *  Return the current forward Strand of the query EntryGroup.
   **/
  private Strand getQueryForwardStrand () {
    return owner.getQueryEntryGroup ().getBases ().getForwardStrand ();
  }

  /**
   *  Return the start position in the subject sequence of the given
   *  AlignMatch, taking into account the current orientation of the
   *  sequences.
   **/
  private int getRealSubjectSequenceStart (final AlignMatch match) {
    final Strand current_subject_forward_strand = getSubjectForwardStrand ();

    final int subject_sequence_start;

    // the query sequence of the AlignMatch was the subject of the hit, which
    // we display on subject
    if (subject_forward_strand == current_subject_forward_strand) {
      return match.getSubjectSequenceStart ();
    } else {
      final int tmp_start = match.getSubjectSequenceStart ();
      final Bases subject_bases = subject_forward_strand.getBases ();
      return subject_bases.getComplementPosition (tmp_start);
    }
  }

  /**
   *  Return the end position in the subject sequence of the given AlignMatch,
   *  taking into account the current orientation of the sequences.
   **/
  private int getRealSubjectSequenceEnd (final AlignMatch match) {
    final Strand current_subject_forward_strand =
      owner.getSubjectEntryGroup ().getBases ().getForwardStrand ();

    final int subject_sequence_end;

    if (subject_forward_strand == current_subject_forward_strand) {
      return match.getSubjectSequenceEnd ();
    } else {
      final int tmp_end = match.getSubjectSequenceEnd ();
      final Bases subject_bases = subject_forward_strand.getBases ();
      return subject_bases.getComplementPosition (tmp_end);
    }
  }

  /**
   *  Return the start position in the query sequence of the given AlignMatch,
   *  taking into account the current orientation of the sequences.
   **/
  private int getRealQuerySequenceStart (final AlignMatch match) {
    final Strand current_query_forward_strand =
      owner.getQueryEntryGroup ().getBases ().getForwardStrand ();

    // the subject sequence of the AlignMatch was the subject of the hit, which
    // we display on query
    if (query_forward_strand == current_query_forward_strand) {
      return match.getQuerySequenceStart ();
    } else {
      final int tmp_start = match.getQuerySequenceStart ();
      final Bases query_bases = query_forward_strand.getBases ();
      return query_bases.getComplementPosition (tmp_start);
    }
  }

  /**
   *  Return the end position in the query sequence of the given AlignMatch,
   *  taking into account the current orientation of the sequences.
   **/
  private int getRealQuerySequenceEnd (final AlignMatch match) {
    final Strand current_query_forward_strand =
      owner.getQueryEntryGroup ().getBases ().getForwardStrand ();

    if (query_forward_strand == current_query_forward_strand) {
      return match.getQuerySequenceEnd ();
    } else {
      final int tmp_end = match.getQuerySequenceEnd ();
      final Bases query_bases = query_forward_strand.getBases ();
      return query_bases.getComplementPosition (tmp_end);
    }
  }

  /**
   *  Convert a base position into a screen x coordinate.
   **/
  private int getScreenPosition (final int canvas_width,
                                 final DisplayAdjustmentEvent event,
                                 final int base_position) {
    // this is the base that is at the left of the screen
    final int screen_start_base = event.getStart ();

    final double base_pos_float =
      event.getBaseWidth () * (base_position - screen_start_base);

    if (base_pos_float > 30000) {
      return 30000;
    } else {
      if (base_pos_float < -30000) {
        return -30000;
      } else {
        return (int) base_pos_float;
      }
    }
  }

  /**
   *  Return an array of colours that will be used for colouring the matches
   *  (depending on score).
   **/
  private Color [] makeColours () {
    final Color [] return_colours = {
      new Color (255,191,191),
      new Color (255,175,175),
      new Color (255,159,159),
      new Color (255,143,143),
      new Color (255,127,127),
      new Color (255,111,111),
      new Color (255,95,95),
      new Color (255,79,79),
      new Color (255,63,63),
      new Color (255,47,47),
      new Color (255,31,31),
      new Color (255,15,15),
      new Color (255,0,0),
    };

    return return_colours;
  }

  /**
   *  The reference of the Comparator that owns this component.
   **/
  final Comparator owner;

  /**
   *  The comparison data that will be displayed in this component.
   **/
  final ComparisonData comparison_data;

  /**
   *  This is the last DisplayAdjustmentEvent reference that was passed to
   *  setSubjectSeqeuencePosition ().
   **/
  private DisplayAdjustmentEvent last_subject_event;

  /**
   *  This is the last DisplayAdjustmentEvent reference that was passed to
   *  setQuerySeqeuencePosition ().
   **/
  private DisplayAdjustmentEvent last_query_event;

  /**
   *  Set by the constructor to be the original forward strand for the subject
   *  sequence.  This is use to determine whether to subject sequence has been
   *  reverse-complemented or not.
   **/
  private Strand subject_forward_strand;

  /**
   *  Set by the constructor to be the original forward strand for the query
   *  sequence.  This is use to determine whether to query sequence has been
   *  reverse-complemented or not.
   **/
  private Strand query_forward_strand;

  /**
   *  The selected matches.
   **/
  private AlignMatchVector selected_matches = null;

  /**
   *  The spectrum of colours use to display the scores of matches.
   **/
  private Color [] score_colours = null;

  /**
   *  The scroll bar used to set the minimum length of the visible matches.
   **/
  private Scrollbar scroll_bar = null;

  /**
   *  Matches with scores below this values will not be shown.
   **/
  private int minimum_score = 0;

  /**
   *  Matches with scores above this values will not be shown.
   **/
  private int maximum_score = 100;

  /**
   *  Set by alignAt () so that selectFromQueryRange () and
   *  selectFromSubjectRange () don't change change the selection after a
   *  double click.
   **/
  private boolean dont_update_from_range = false;
}
