/* Navigator.java
 *
 * created: Sun Jan 10 1999
 *
 * 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/Navigator.java,v 1.23 2000/04/12 10:20:29 kmr Exp $
 */

package diana.components;

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

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

/**
 *  This component allows the user to navigate around the Entry.
 *
 *  @author Kim Rutherford
 *  @version $Id: Navigator.java,v 1.23 2000/04/12 10:20:29 kmr Exp $
 **/

public class Navigator extends Frame {
  /**
   *  Create a new Navigator component.
   *  @param selection The Selection that the commands will operate on.
   *  @param goto_event_source The object the we will call gotoBase () on.
   *  @param entry_group The EntryGroup object used when searching for
   *    qualifier text in features.
   **/
  public Navigator (final Selection selection,
                    final GotoEventSource goto_event_source,
                    final EntryGroup entry_group) {
    super ("Artemis Navigator");

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

    final Font default_font = Options.getOptions ().getFont ();

    GridBagLayout gridbag = new GridBagLayout();
    setLayout (gridbag);

    setFont (default_font);

    GridBagConstraints c = new GridBagConstraints();

    c.fill = GridBagConstraints.HORIZONTAL;
    c.anchor = GridBagConstraints.NORTH;
    c.weighty = 0;

    final CheckboxGroup checkbox_group = new CheckboxGroup ();

    goto_base_button = new Checkbox ("Goto Base:", checkbox_group, true);
    final Panel goto_base_panel = new Panel ();
    goto_base_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    goto_base_panel.add (goto_base_button);
    c.gridwidth = 2;
    gridbag.setConstraints (goto_base_panel, c);
    add (goto_base_panel);

    goto_base_text = new TextField ("", 18);
    c.gridwidth = GridBagConstraints.REMAINDER;
    gridbag.setConstraints (goto_base_text, c);
    add (goto_base_text);

    goto_base_text.addKeyListener (new KeyAdapter () {
      public void keyTyped(final KeyEvent e) {
        goto_base_button.setState (true);
        if (e.getKeyChar () == '\n') {
//  this sometimes causes an infinite loop in v1.1.7
//          doGoto ();
        }
      }
    });


    goto_text_button =
      new Checkbox ("Goto Feature With This Text:", checkbox_group, true);
    final Panel goto_text_panel = new Panel ();
    goto_text_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    goto_text_panel.add (goto_text_button);
    c.gridwidth = 2;
    gridbag.setConstraints (goto_text_panel, c);
    add (goto_text_panel);

    goto_feature_text_textfield = new TextField ("", 18);
    c.gridwidth = GridBagConstraints.REMAINDER;
    gridbag.setConstraints (goto_feature_text_textfield, c);
    add (goto_feature_text_textfield);

    goto_feature_text_textfield.addKeyListener (new KeyAdapter () {
      public void keyTyped(final KeyEvent e) {
        goto_text_button.setState (true);
        if (e.getKeyChar () == '\n') {
//  this sometimes causes an infinite loop in v1.1.7
//          doGoto ();
        }
      }
    });


    goto_key_button =
      new Checkbox ("Goto Feature With This Key:", checkbox_group, true);
    final Panel goto_key_panel = new Panel ();
    goto_key_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    goto_key_panel.add (goto_key_button);
    c.gridwidth = 2;
    gridbag.setConstraints (goto_key_panel, c);
    add (goto_key_panel);

    goto_feature_key_textfield = new TextField ("", 18);
    c.gridwidth = GridBagConstraints.REMAINDER;
    gridbag.setConstraints (goto_feature_key_textfield, c);
    add (goto_feature_key_textfield);

    goto_feature_key_textfield.addKeyListener (new KeyAdapter () {
      public void keyTyped(final KeyEvent e) {
        goto_key_button.setState (true);
        if (e.getKeyChar () == '\n') {
//  this sometimes causes an infinite loop in v1.1.7
//          doGoto ();
        }
      }
    });


    goto_base_pattern_button =
      new Checkbox ("Find Base Pattern:", checkbox_group, true);
    final Panel goto_base_pattern_panel = new Panel ();
    goto_base_pattern_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    goto_base_pattern_panel.add (goto_base_pattern_button);
    c.gridwidth = 2;
    gridbag.setConstraints (goto_base_pattern_panel, c);
    add (goto_base_pattern_panel);

    goto_base_pattern_text = new TextField ("", 18);
    c.gridwidth = GridBagConstraints.REMAINDER;
    gridbag.setConstraints (goto_base_pattern_text, c);
    add (goto_base_pattern_text);

    goto_base_pattern_text.addKeyListener (new KeyAdapter () {
      public void keyTyped(final KeyEvent e) {
        goto_base_pattern_button.setState (true);
        if (e.getKeyChar () == '\n') {
//  this sometimes causes an infinite loop in v1.1.7
//          doGoto ();
        }
      }
    });

    goto_aa_pattern_button =
      new Checkbox ("Find Amino Acid String:", checkbox_group, true);
    final Panel goto_aa_pattern_panel = new Panel ();
    goto_aa_pattern_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    goto_aa_pattern_panel.add (goto_aa_pattern_button);
    c.gridwidth = 2;
    gridbag.setConstraints (goto_aa_pattern_panel, c);
    add (goto_aa_pattern_panel);

    goto_aa_pattern_text = new TextField ("", 18);
    c.gridwidth = GridBagConstraints.REMAINDER;
    gridbag.setConstraints (goto_aa_pattern_text, c);
    add (goto_aa_pattern_text);

    goto_aa_pattern_text.addKeyListener (new KeyAdapter () {
      public void keyTyped(final KeyEvent e) {
        goto_aa_pattern_button.setState (true);
        if (e.getKeyChar () == '\n') {
//  this sometimes causes an infinite loop in v1.1.7
//          doGoto ();
        }
      }
    });


    checkbox_group.setSelectedCheckbox (goto_base_button);

    final CheckboxGroup start_position_checkbox_group =
      new CheckboxGroup ();


    final Panel start_at_an_end_panel = new Panel ();

    start_at_an_end_button =
      new Checkbox ("Start search at beginning (or end)",
                    start_position_checkbox_group,
                    true);

    c.gridwidth = GridBagConstraints.REMAINDER;

    start_at_an_end_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    start_at_an_end_panel.add (start_at_an_end_button);

    gridbag.setConstraints (start_at_an_end_panel, c);
    add (start_at_an_end_panel);


    final Panel start_at_selection_panel = new Panel ();

    start_at_selection_button =
      new Checkbox ("Start search at selection",
                    start_position_checkbox_group,
                    false);

    start_at_selection_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    start_at_selection_panel.add (start_at_selection_button);

//    c.gridwidth = 2;

    c.gridwidth = GridBagConstraints.REMAINDER;

    gridbag.setConstraints (start_at_selection_panel, c);
    add (start_at_selection_panel);


    final Panel search_backward_panel = new Panel ();

    search_backward_button = new Checkbox ("Search Backward", false);

    search_backward_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    search_backward_panel.add (search_backward_button);

    c.gridwidth = GridBagConstraints.REMAINDER;

    gridbag.setConstraints (search_backward_panel, c);
    add (search_backward_panel);


    final Button goto_button = new Button ("Goto");

    goto_button.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent e) {
        doGoto ();
      }
    });


    final Button clear_button = new Button ("Clear");

    clear_button.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent e) {
        clear ();
      }
    });


    final Button close_button = new Button ("Close");

    close_button.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent e) {
        Navigator.this.dispose ();
      }
    });


    final FlowLayout flow_layout =
      new FlowLayout (FlowLayout.CENTER, 15, 5);

    final Panel close_and_goto_panel = new Panel (flow_layout);

    close_and_goto_panel.add (goto_button);
    close_and_goto_panel.add (clear_button);
    close_and_goto_panel.add (close_button);

    gridbag.setConstraints (close_and_goto_panel, c);
    add (close_and_goto_panel);


    addWindowListener (new WindowAdapter () {
      public void windowClosing (WindowEvent event) {
        Navigator.this.dispose ();
      }
    });


    pack ();

    final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
    setLocation (new Point ((screen.width - getSize ().width) / 2,
                            (screen.height - getSize ().height) / 2));

    setVisible (true);
  }


  /**
   *  This method finds the given pattern in the Bases of the given EntryGroup
   *  and returns a MarkerRange for it.
   *  @param pattern This is the pattern to search for.
   *  @param entry_group The EntryGroup to search.
   *  @param selection The current selection.
   *  @param start_at_end If true or if there is nothing in the Selection then
   *    the search will start at the first or last base (depending on value of
   *    the next parameter), otherwise the search will start at the Selection.
   *  @param search_backwards If true the search will move from last base to
   *    first base, otherwise first to last.
   *  @return The range that matches the pattern or null if there is no match.
   **/
  public static MarkerRange findBasePattern (final BasePattern pattern,
                                             final EntryGroup entry_group,
                                             final Selection selection,
                                             final boolean start_at_end,
                                             final boolean search_backwards) {
    // if start_at_end is false we want to start the search at the selection
    final Marker selection_base = selection.getLowestBaseOfSelection ();

    final Marker start_position;

    if (start_at_end || selection_base == null) {
      // null means start the search from the beginning
      start_position = null;
    } else {
      start_position = selection_base;
    }

    final MarkerRange match_range =
      pattern.findMatch (entry_group.getBases (),
                         start_position,
                         entry_group.getSequenceLength (),
                         search_backwards);

    if (match_range == null) {
      return null;
    } else {
      return match_range;
    }
  }

  /**
   *  This method finds the given amino acid seqeunce, sets the selection to
   *  the matching bases and then sends a GotoEvent to all the GotoEvent
   *  listeners that will make the first base of the match visible.
   *  @param sequence This is the pattern to search for.
   *  @param entry_group The EntryGroup to search.
   *  @param selection The current selection.
   *  @param start_at_end If true or if there is nothing in the Selection then
   *    the search will start at first or last base (depending on value of the
   *    next parameter), otherwise the search will start at the Selection.
   *  @param search_backwards If true the search will move from last base to
   *    first base, otherwise first to last.
   *  @return The range that matches the pattern or null if there is no match.
   **/
  public static MarkerRange findAminoAcidSequence (final AminoAcidSequence
                                                     sequence,
                                                   final EntryGroup
                                                     entry_group,
                                                   final Selection selection,
                                                   final boolean start_at_end,
                                                   final boolean
                                                     search_backwards) {
    // if start_at_end is false we want to start the search at the selection
    final Marker selection_base = selection.getLowestBaseOfSelection ();

    final Marker start_position;

    if (start_at_end || selection_base == null) {
      // null means start the search from the beginning
      start_position = null;
    } else {
      start_position = selection_base;
    }

    final MarkerRange match_range;

    if (search_backwards) {
      match_range = sequence.findMatch (entry_group.getBases (),
                                        start_position,
                                        true);
    } else {
      match_range = sequence.findMatch (entry_group.getBases (),
                                        start_position,
                                        false);
    }

    if (match_range == null) {
      return null;
    } else {
      return match_range;
    }
  }

  /**
   *  This method will perform the selected goto function.
   **/
  private void doGoto () {
    if (goto_base_button.getState () == true) {
      doGotoBase ();
      return;
    }

    if (goto_base_pattern_button.getState () == true) {
      doGotoBasePattern ();
      return;
    }

    if (goto_aa_pattern_button.getState () == true) {
      doGotoAAPattern ();
      return;
    }

    if (goto_text_button.getState () == true) {
      doGotoText ();
      return;
    }

    if (goto_key_button.getState () == true) {
      doGotoKey ();
      return;
    }
  }

  /**
   *  Clear all the TextField components.
   **/
  private void clear () {
    goto_base_text.setText ("");
    goto_base_pattern_text.setText ("");
    goto_aa_pattern_text.setText ("");
    goto_feature_text_textfield.setText ("");
    goto_feature_key_textfield.setText ("");
  }

  /**
   *  Go to the base that the user typed in to the goto_base_text TextArea.
   **/
  private void doGotoBase () {
    final String number_string = goto_base_text.getText ().trim ();

    if (number_string.length () == 0) {
      new MessageDialog (this, "you have not entered a number to go to");
      return;
    }

    try {
      final int destination_base =
        Integer.valueOf (number_string).intValue ();

//        System.out.println ("going to: " + destination_base);

      final MarkerRange destination_range =
        goto_event_source.gotoBase (destination_base);

      if (destination_range == null) {
        new MessageDialog (this,
                           "the base is out of range for this sequence");
      } else {
        // success select that base
        getSelection ().setMarkerRange (destination_range);
      }
    } catch (NumberFormatException e) {
      new MessageDialog (this, "Cannot understand this number: " +
                         goto_base_text.getText ());
    }
  }

  /**
   *  Go to the base pattern that the user typed in to the
   *  goto_base_pattern_text TextArea.
   **/
  private void doGotoBasePattern () {
    try {
      final String pattern_string =
        goto_base_pattern_text.getText ().trim ();

      if (pattern_string.length () == 0) {
        new MessageDialog (this, "you have not entered a pattern to go to");
        return;
      }

      final BasePattern pattern = new BasePattern (pattern_string);

      final boolean start_at_an_end = start_at_an_end_button.getState ();

      start_at_selection_button.setState (true);

      final MarkerRange match_range =
        findBasePattern (pattern,
                         getEntryGroup (),
                         getSelection (),
                         start_at_an_end,
                         search_backward_button.getState ());

      if (match_range == null) {
        new MessageDialog (this, "reached the end of sequence");
      } else {
        getSelection ().setMarkerRange (match_range);

        final Marker first_selected_base =
          getSelection ().getLowestBaseOfSelection ();

        goto_event_source.gotoBase (first_selected_base);
      }
    } catch (BasePatternFormatException e) {
      new MessageDialog (this,
                         "Illegal base pattern: " +
                         goto_base_pattern_text.getText ());
    }
  }

  /**
   *  Find the amino acid pattern that the user typed in to the
   *  goto_aa_pattern_text TextArea.
   **/
  private void doGotoAAPattern () {
    final String pattern_string =
      goto_aa_pattern_text.getText ().trim ();

    if (pattern_string.length () == 0) {
      new MessageDialog (this, "you have not entered a pattern to go to");
      return;
    }

    final AminoAcidSequence pattern = new AminoAcidSequence (pattern_string);

    final boolean start_at_an_end = start_at_an_end_button.getState ();

    start_at_selection_button.setState (true);

    final boolean search_backwards = search_backward_button.getState ();

    final MarkerRange match_range =
      findAminoAcidSequence (pattern,
                             getEntryGroup (),
                             getSelection (),
                             start_at_an_end,
                             search_backwards);

    if (match_range == null) {
      new MessageDialog (this, "reached the end of sequence");
    } else {
      getSelection ().setMarkerRange (match_range);

      goto_event_source.gotoBase (getSelection ().getLowestBaseOfSelection ());
    }
  }

  /**
   *  Select the next feature that contains the text given by the user in the
   *  goto_feature_text_textfield TextArea.
   **/
  private void doGotoText () {
    final FeatureVector selected_features =
      getSelection ().getAllFeatures ();

    final int index;

    if (selected_features.size () == 0 ||
        start_at_an_end_button.getState ()) {
      index = -1;
    } else {
      index = getEntryGroup ().indexOf (selected_features.elementAt (0));
    }

    final int first_search_feature_index;

    final String search_text = goto_feature_text_textfield.getText ().trim ();

    Feature found_feature = null;

    if (search_backward_button.getState ()) {
      if (index == -1) {
        // nothing was selected so start the search at the first feature
        first_search_feature_index =
          getEntryGroup ().getAllFeaturesCount () - 1;
      } else {
        first_search_feature_index = index - 1;
      }

      for (int i = first_search_feature_index ; i >= 0 ; --i) {
        final Feature this_feature = getEntryGroup ().featureAt (i);

        if (this_feature.containsText (search_text, true)) {
          found_feature = this_feature;
          break;
        }
      }
    } else {
      if (index == -1) {
        // nothing was selected so start the search at the first feature
        first_search_feature_index = 0;
      } else {
        first_search_feature_index = index + 1;
      }

      for (int i = first_search_feature_index ;
           i < getEntryGroup ().getAllFeaturesCount () ;
           ++i) {
        final Feature this_feature = getEntryGroup ().featureAt (i);

        if (this_feature.containsText (search_text, true)) {
          found_feature = this_feature;
          break;
        }
      }
    }

    if (found_feature == null) {
      getSelection ().clear ();
      start_at_an_end_button.setState (true);

      new MessageDialog (this, "text not found");
    } else {
      getSelection ().set (found_feature);
      goto_event_source.gotoBase (getSelection ().getLowestBaseOfSelection ());
      start_at_selection_button.setState (true);
    }
  }

  /**
   *  Select the next feature that has the key given by the user in the
   *  goto_feature_key_textfield TextArea.
   **/
  private void doGotoKey () {
    final FeatureVector selected_features =
      getSelection ().getAllFeatures ();

    final int index;

    if (selected_features.size () == 0 ||
        start_at_an_end_button.getState ()) {
      index = -1;
    } else {
      index = getEntryGroup ().indexOf (selected_features.elementAt (0));
    }

    final int first_search_feature_index;

    final String search_key = goto_feature_key_textfield.getText ().trim ();

    Feature found_feature = null;

    if (search_backward_button.getState ()) {
      if (index == -1) {
        // nothing was selected so start the search at the first feature
        first_search_feature_index =
          getEntryGroup ().getAllFeaturesCount () - 1;
      } else {
        first_search_feature_index = index - 1;
      }

      for (int i = first_search_feature_index ; i >= 0 ; --i) {
        final Feature this_feature = getEntryGroup ().featureAt (i);

        final String lower_key =
          this_feature.getKey ().toString ().toLowerCase ();

        if (lower_key.equals (search_key.toLowerCase ())) {
          found_feature = this_feature;
          break;
        }
      }
    } else {
      if (index == -1) {
        // nothing was selected so start the search at the first feature
        first_search_feature_index = 0;
      } else {
        first_search_feature_index = index + 1;
      }

      for (int i = first_search_feature_index ;
           i < getEntryGroup ().getAllFeaturesCount () ;
           ++i) {
        final Feature this_feature = getEntryGroup ().featureAt (i);

        final String lower_key =
          this_feature.getKey ().toString ().toLowerCase ();

        if (lower_key.equals (search_key.toLowerCase ())) {
          found_feature = this_feature;
          break;
        }
      }
    }

    if (found_feature == null) {
      getSelection ().clear ();
      start_at_an_end_button.setState (true);

      new MessageDialog (this, "key not found");
    } else {
      getSelection ().set (found_feature);
      goto_event_source.gotoBase (getSelection ().getLowestBaseOfSelection ());
      start_at_selection_button.setState (true);
    }
  }

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

  /**
   *  Return the EntryGroup object that was passed to the constructor.
   **/
  private EntryGroup getEntryGroup () {
    return entry_group;
  }

  /**
   *  The Checkbox that selects the goto base function.
   **/
  final Checkbox goto_base_button;

  /**
   *  The Checkbox that selects the goto base pattern function.
   **/
  final Checkbox goto_base_pattern_button;

  /**
   *  The Checkbox that selects the find amino acid sequence function.
   **/
  final Checkbox goto_aa_pattern_button;

  /**
   *  The Checkbox that selects the goto feature text function.
   **/
  final Checkbox goto_text_button;

  /**
   *  The Checkbox that selects the goto feature key function.
   **/
  final Checkbox goto_key_button;

  /**
   *  This contains the pattern to search for if the user has selected the
   *  goto base function.
   **/
  final TextField goto_base_text;

  /**
   *  This contains the pattern to search for if the user has selected the
   *  goto base pattern function.
   **/
  final TextField goto_base_pattern_text;

  /**
   *  This contains the pattern to search for if the user has selected the
   *  goto amino acid function.
   **/
  final TextField goto_aa_pattern_text;

  /**
   *  This contains the pattern to search for if the user has selected the
   *  goto feature text function.
   **/
  final TextField goto_feature_text_textfield;

  /**
   *  This contains the key to search for if the user has selected the
   *  goto key function.
   **/
  final TextField goto_feature_key_textfield;

  /**
   *  The user selects this Checkbox if the search should start at first/last
   *  base or first/last feature (depending on the search type).
   **/
  Checkbox start_at_an_end_button;

  /**
   *  The user selects this Checkbox if the search should start at the
   *  position of the current selection.
   **/
  final Checkbox start_at_selection_button;

  /**
   *  If checked the search will go backwards.
   **/
  final Checkbox search_backward_button;

  /**
   *  The GotoEventSource object that was passed to the constructor.
   **/
  final GotoEventSource goto_event_source;

  /**
   *  The EntryGroup object that was passed to the constructor.
   **/
  final EntryGroup entry_group;

  /**
   *  This is the Selection that was passed to the constructor.
   **/
  final private Selection selection;
}
