/* Selector.java
 *
 * created: Tue Apr 11 2000
 *
 * This file is part of Artemis
 *
 * Copyright (C) 2000,2001,2002  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/pathsoft/repository/powmap/diana/components/Selector.java,v 1.17 2002/11/14 15:49:52 kmr Exp $
 */

package diana.components;

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

import uk.ac.sanger.pathogens.embl.EntryInformation;

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

/**
 *  This component allows the user to set the selection by filtering the
 *  features in an EntryGroup on key and contents.
 *
 *  @author Kim Rutherford <kmr@sanger.ac.uk>
 *  @version $Id: Selector.java,v 1.17 2002/11/14 15:49:52 kmr Exp $
 **/

public class Selector extends Frame
    implements EntryGroupChangeListener {
  /**
   *  Create a new Selector that van set the given Selection.
   *  @param selection The Selection that the commands in the menu will
   *    operate on.
   *  @param entry_group The component will choose features from this
   *    EntryGroup.
   *  @param goto_event_source The object the we will call gotoBase () on.
   *  @param base_plot_group The BasePlotGroup associated with this Menu -
   *    needed to call getCodonUsageAlgorithm()
   **/
  public Selector (final Selection selection,
                   final GotoEventSource goto_event_source,
                   final EntryGroup entry_group,
                   final BasePlotGroup base_plot_group) {
    super ("Artemis Feature Selector");

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

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

    setFont (default_font);

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

    GridBagConstraints c = new GridBagConstraints();

    c.fill = GridBagConstraints.HORIZONTAL;
    c.anchor = GridBagConstraints.WEST;
    c.weighty = 0;
    c.gridwidth = GridBagConstraints.REMAINDER;

    final Label top_label = new Label ("Select by:");
    gridbag.setConstraints (top_label, c);
    add (top_label);


    by_key_button = new Checkbox ("Key: ", false);
    final Panel by_key_panel = new Panel ();
    by_key_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    by_key_panel.add (by_key_button);
    c.gridwidth = 2;
    gridbag.setConstraints (by_key_panel, c);
    add (by_key_panel);

    final EntryInformation default_entry_information =
      Options.getArtemisEntryInformation ();

    key_selector = new KeyChoice (default_entry_information);
    c.gridwidth = GridBagConstraints.REMAINDER;
    gridbag.setConstraints (key_selector, c);
    add (key_selector);

    key_selector.addItemListener (new ItemListener () {
      public void itemStateChanged (ItemEvent _) {
        by_key_button.setState (true);
      }
    });

    by_key_button.addItemListener (new ItemListener () {
      public void itemStateChanged (ItemEvent e) {
        if (!by_key_button.getState ()) {
          by_qualifier_button.setState (false);
        }
      }
    });


    by_qualifier_button = new Checkbox ("Qualifier: ", false);
    final Panel by_qualifier_panel = new Panel ();
    by_qualifier_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    by_qualifier_panel.add (by_qualifier_button);
    c.gridwidth = 2;
    gridbag.setConstraints (by_qualifier_panel, c);
    add (by_qualifier_panel);

    qualifier_selector = new QualifierChoice (default_entry_information,
                                              key_selector.getSelectedItem ());
    c.gridwidth = GridBagConstraints.REMAINDER;
    gridbag.setConstraints (qualifier_selector, c);
    add (qualifier_selector);

    qualifier_selector.addItemListener (new ItemListener () {
      public void itemStateChanged (ItemEvent _) {
        by_qualifier_button.setState (true);
        by_key_button.setState (true);
      }
    });

    key_selector.addItemListener (new ItemListener () {
      public void itemStateChanged (ItemEvent _) {
        qualifier_selector.setKey (key_selector.getSelectedItem ());
      }
    });

    by_qualifier_button.addItemListener (new ItemListener () {
      public void itemStateChanged (ItemEvent e) {
        if (by_qualifier_button.getState ()) {
          if (!by_key_button.getState ()) {
            by_key_button.setState (true);
          }
        }
      }
    });


    final Label qualifier_text_label = new Label ("   Containing this text: ");
    c.gridwidth = 2;
    gridbag.setConstraints (qualifier_text_label, c);
    add (qualifier_text_label);

    qualifier_text = new TextField ("", 18);

    c.gridwidth = GridBagConstraints.REMAINDER;
    gridbag.setConstraints (qualifier_text, c);
    add (qualifier_text);


    ignore_case_checkbox = new Checkbox ("Ignore Case", true);

    final Panel ignore_case_panel = new Panel ();

    ignore_case_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    ignore_case_panel.add (ignore_case_checkbox);

    c.gridwidth = 2;
    gridbag.setConstraints (ignore_case_panel, c);
    add (ignore_case_panel);


    partial_match_checkbox = new Checkbox ("Allow Partial Match", true);

    final Panel partial_match_panel = new Panel ();

    partial_match_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    partial_match_panel.add (partial_match_checkbox);

    c.gridwidth = GridBagConstraints.REMAINDER;
    gridbag.setConstraints (partial_match_panel, c);
    add (partial_match_panel);


    // leave a blank line to give the user a visual clue
    final Label blank_label1 = new Label ("");

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


    final Label and_label1 = new Label ("And:");

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



    less_than_button = new Checkbox ("Up to: ", false);
    final Panel less_than_panel = new Panel ();
    less_than_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    less_than_panel.add (less_than_button);
    c.gridwidth = 3;
    gridbag.setConstraints (less_than_panel, c);
    add (less_than_panel);

    less_than_text = new TextField ("", 18);

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

    final Label less_than_label = new Label (" bases long");

    c.gridwidth = GridBagConstraints.REMAINDER;
    gridbag.setConstraints (less_than_label, c);
    add (less_than_label);

    // leave a blank line to give the user a visual clue
    final Label blank_label2 = new Label ("");

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

    final Label and_label2 = new Label ("And:");

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



    greater_than_button = new Checkbox ("At least: ", false);
    final Panel greater_than_panel = new Panel ();
    greater_than_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    greater_than_panel.add (greater_than_button);
    c.gridwidth = 2;
    gridbag.setConstraints (greater_than_panel, c);
    add (greater_than_panel);

    greater_than_text = new TextField ("", 18);

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

    final Label greater_than_label = new Label (" bases long");

    c.gridwidth = GridBagConstraints.REMAINDER;
    gridbag.setConstraints (greater_than_label, c);
    add (greater_than_label);

    // leave a blank line to give the user a visual clue
    final Label blank_label3 = new Label ("");

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


    final Label and_label3 = new Label ("And by:");

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



    by_motif_button = new Checkbox ("Amino acid motif: ", false);
    final Panel by_motif_panel = new Panel ();
    by_motif_panel.setLayout (new FlowLayout (FlowLayout.LEFT));
    by_motif_panel.add (by_motif_button);
    c.gridwidth = 2;
    gridbag.setConstraints (by_motif_panel, c);
    add (by_motif_panel);

    motif_text = new TextField ("", 18);

    c.gridwidth = GridBagConstraints.REMAINDER;
    gridbag.setConstraints (motif_text, c);
    add (motif_text);

    // leave a blank line to give the user a visual clue
    final Label blank_label4 = new Label ("");

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


    final Panel strand_panel = new Panel ();

    strand_panel.setLayout (new FlowLayout (FlowLayout.LEFT));

    forward_strand_checkbox = new Checkbox ("Forward Strand Features", true);
    strand_panel.add (forward_strand_checkbox);
    forward_strand_checkbox.addItemListener (new ItemListener () {
      public void itemStateChanged (ItemEvent _) {
        if (!forward_strand_checkbox.getState () &&
            !reverse_strand_checkbox.getState ()) {
          // make sure one of the strand is always selected
          reverse_strand_checkbox.setState (true);
        }
      }
    });


    reverse_strand_checkbox = new Checkbox ("Reverse Strand Features", true);
    strand_panel.add (reverse_strand_checkbox);
    reverse_strand_checkbox.addItemListener (new ItemListener () {
      public void itemStateChanged (ItemEvent _) {
        if (!reverse_strand_checkbox.getState () &&
            !forward_strand_checkbox.getState ()) {
          // make sure one of the strand is always selected
          forward_strand_checkbox.setState (true);
        }
      }
    });


    c.gridwidth = GridBagConstraints.REMAINDER;
    gridbag.setConstraints (strand_panel, c);
    add (strand_panel);





    final Button select_button = new Button ("Select");

    select_button.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent e) {
        selection.set (getSelected ());
      }
    });

    final Button view_button = new Button ("View");

    view_button.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent e) {
        final FeaturePredicate predicate =
          new FeatureFromVectorPredicate (getSelected ());

        final FilteredEntryGroup filtered_entry_group =
          new FilteredEntryGroup (entry_group, predicate);

        String title = "All features";

        if (by_key_button.getState ()) {
          title += " with key \"" + key_selector.getSelectedItem () + "\"";
        }

        if (by_qualifier_button.getState ()) {
          if (qualifier_text.getText ().trim ().length () > 0) {
            title +=
              " with qualifier \"" + qualifier_selector.getSelectedItem () +
              "\" containing text \"" + qualifier_text.getText () + "\"";
          } else {
            title +=
              " with qualifier \"" +
              qualifier_selector.getSelectedItem () + "\"";
          }
        }

        if (forward_strand_checkbox.getState () &&
            !reverse_strand_checkbox.getState ()) {
          title += " on the forward strand";
        }

        if (!forward_strand_checkbox.getState () &&
            reverse_strand_checkbox.getState ()) {
          title += " on the reverse strand";
        }

        if (by_motif_button.getState ()) {
          title +=
            " with motif: " + motif_text.getText ().trim ().toUpperCase ();
        }
        
        if (less_than_button.getState ()) {
          title +=
            " at most: " + less_than_text.getText ().trim () +
            " bases long";
        }
        
        if (greater_than_button.getState ()) {
          title +=
            " at least: " + greater_than_text.getText ().trim () +
            " bases long";
        }
        
        final FeatureListFrame feature_list_frame =
          new FeatureListFrame (title,
                                getSelection (),
                                goto_event_source, filtered_entry_group,
                                base_plot_group);

        feature_list_frame.setVisible (true);
      }
    });


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

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

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

    final Panel bottom_button_panel = new Panel (flow_layout);

    bottom_button_panel.add (select_button);
    bottom_button_panel.add (view_button);
    bottom_button_panel.add (close_button);

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

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

    getEntryGroup ().addEntryGroupChangeListener (this);

    pack ();

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

    setVisible (true);
  }

  /**
   *  Implementation of the EntryGroupChangeListener interface.  We listen to
   *  EntryGroupChange events so that we can get rid of the Navigator when the
   *  EntryGroup is no longer in use (for example when the EntryEdit is
   *  closed).
   **/
  public void entryGroupChanged (final EntryGroupChangeEvent event) {
    switch (event.getType ()) {
    case EntryGroupChangeEvent.DONE_GONE:
      getEntryGroup ().removeEntryGroupChangeListener (this);
      dispose ();
      break;
    }
  }

  /**
   *  Return those features that match the current setting of the Selector.
   **/
  private FeatureVector getSelected () {
    final FeaturePredicate key_and_qualifier_predicate;
    final FeaturePredicate motif_predicate;

    if (by_key_button.getState ()) {
      if (by_qualifier_button.getState ()) {
        final String search_text = qualifier_text.getText ().trim ();
        final String qualifier_name = qualifier_selector.getSelectedItem ();

        if (search_text.length () == 0) {
          key_and_qualifier_predicate =
            new FeatureKeyQualifierPredicate (key_selector.getSelectedItem (),
                                              qualifier_name,
                                              true);
        } else {
          key_and_qualifier_predicate =
            new FeatureKeyQualifierPredicate (key_selector.getSelectedItem (),
                                              qualifier_name,
                                              search_text,
                                              partial_match_checkbox.getState (),
                                              ignore_case_checkbox.getState ());
        }
      } else {
        final String search_text = qualifier_text.getText ().trim ();
        if (search_text.length () == 0) {
          key_and_qualifier_predicate =
            new FeatureKeyPredicate (key_selector.getSelectedItem ());
        } else {
          key_and_qualifier_predicate =
            new FeatureKeyQualifierPredicate (key_selector.getSelectedItem (),
                                              null, // match any qialifier
                                              search_text,
                                              partial_match_checkbox.getState (),
                                              ignore_case_checkbox.getState ());
        }
      }
    } else {
      key_and_qualifier_predicate = null;
    }

    if (by_motif_button.getState ()) {
      final AminoAcidSequence amino_acid_sequence =
        new AminoAcidSequence (motif_text.getText ().trim ());

      motif_predicate =
        new FeaturePatternPredicate (amino_acid_sequence);
    } else {
      motif_predicate = null;
    }

    FeaturePredicate less_than_predicate = null;

    if (less_than_button.getState () &&
        less_than_text.getText ().trim ().length () > 0) {
      try {
        final int less_than_int =
          Integer.valueOf (less_than_text.getText ().trim ()).intValue ();

        less_than_predicate = new FeaturePredicate () {
          public boolean testPredicate (final Feature feature) {
            if (feature.getBaseCount () <= less_than_int) {
              return true;
            } else {
              return false;
            }
          }
        };
      } catch (NumberFormatException e) {
        new MessageDialog (this,
                           "warning this is not a number: " +
                           less_than_text.getText ());
        less_than_predicate = null;
      }
    } else {
      less_than_predicate = null;
    }

    FeaturePredicate greater_than_predicate = null;

    if (greater_than_button.getState () &&
        greater_than_text.getText ().trim ().length () > 0) {
      try {
        final int greater_than_int =
          Integer.valueOf (greater_than_text.getText ().trim ()).intValue ();

        greater_than_predicate = new FeaturePredicate () {
          public boolean testPredicate (final Feature feature) {
            if (feature.getBaseCount () >= greater_than_int) {
              return true;
            } else {
              return false;
            }
          }
        };
      } catch (NumberFormatException e) {
        new MessageDialog (this,
                           "warning this is not a number: " +
                           greater_than_text.getText ());
        greater_than_predicate = null;
      }
    } else {
      greater_than_predicate = null;
    }

    FeaturePredicate predicate = null;

    if (!by_key_button.getState () && !by_motif_button.getState ()) {
      // default to selecting all features
      predicate = new FeaturePredicate () {
        public boolean testPredicate (final Feature feature) {
          return true;
        }
      };
    } else {
      if (motif_predicate != null && key_and_qualifier_predicate != null) {
        predicate =
          new FeaturePredicateConjunction (key_and_qualifier_predicate,
                                           motif_predicate,
                                           FeaturePredicateConjunction.AND);
      } else {
        if (motif_predicate != null) {
          predicate = motif_predicate;
        } else {
          predicate = key_and_qualifier_predicate;
        }
      }
    }

    if (less_than_predicate != null) {
      predicate =
        new FeaturePredicateConjunction (predicate,
                                         less_than_predicate,
                                         FeaturePredicateConjunction.AND);
    }

    if (greater_than_predicate != null) {
      predicate =
        new FeaturePredicateConjunction (predicate,
                                         greater_than_predicate,
                                         FeaturePredicateConjunction.AND);
    }

    final FeatureEnumeration test_enumerator = entry_group.features ();

    final FeatureVector return_features = new FeatureVector ();

    while (test_enumerator.hasMoreFeatures ()) {
      final Feature this_feature = test_enumerator.nextFeature ();

      if (predicate.testPredicate (this_feature)) {
        if (this_feature.isForwardFeature ()) {
          if (forward_strand_checkbox.getState ()) {
            return_features.add (this_feature);
          }
        } else {
          if (reverse_strand_checkbox.getState ()) {
            return_features.add (this_feature);
          }
        }
      }
    }

    return return_features;
  }

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

  private Checkbox by_key_button;
  private Checkbox by_qualifier_button;
  private Checkbox by_motif_button;
  private Checkbox less_than_button;
  private Checkbox greater_than_button;
  private Checkbox ignore_case_checkbox;
  private Checkbox forward_strand_checkbox;
  private Checkbox reverse_strand_checkbox;
  private KeyChoice key_selector;
  private QualifierChoice qualifier_selector;
  private TextField qualifier_text;
  private TextField motif_text;
  private TextField less_than_text;
  private TextField greater_than_text;

  /**
   *  If checked the search text is allowed to match a substring of a
   *  qualifier value.
   **/
  final Checkbox partial_match_checkbox;

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