/* Comparator.java
 *
 * created: Mon Jul 12 1999,2000
 *
 * This file is part of Artemis
 *
 * Copyright (C) 1999  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/Comparator.java,v 1.19 2000/09/20 09:27:39 kmr Exp $
 */

package diana.components;

import AppGlobal;

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

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

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.Vector;
import java.util.EventObject;

/**
 *  This Frame component contains a AlignmentViewer component, two
 *  FeatureDisplay components and the event listeners that keep them
 *  synchronized.
 *
 *  @author Kim Rutherford
 *  @version $Id: Comparator.java,v 1.19 2000/09/20 09:27:39 kmr Exp $
 **/

public class Comparator extends Frame
    implements FeatureDisplayOwner {
  /**
   *  Create a new Comparator component using the given data.
   **/
  public Comparator (final EntryGroup subject_entry_group,
                     final EntryGroup query_entry_group,
                     final ComparisonData comparison_data) {
    this.subject_entry_group = subject_entry_group;
    this.query_entry_group = query_entry_group;
    this.comparison_data = comparison_data;

    // XXX add a InputStreamProgressListener
    this.entry_sources = AppGlobal.getEntrySources (this, null);

    button_panel = new Panel ();

    final FlowLayout flow_layout = new FlowLayout (FlowLayout.LEFT);

    button_panel.setLayout (flow_layout);

    lock_checkbox = new Checkbox ("lock", true);

    button_panel.add (lock_checkbox);

    makeSelections ();

    makeGotoEventSources ();

    subject_entry_display =
      new FeatureDisplay (getSubjectEntryGroup (), getSubjectSelection (),
                          getSubjectGotoEventSource (), this, true);

    query_entry_display =
      new FeatureDisplay (getQueryEntryGroup (), getQuerySelection (),
                          getQueryGotoEventSource (), this, false);

    alignment_viewer = new AlignmentViewer (this, comparison_data);

    subject_entry_display.setShowLabels (false);
    query_entry_display.setShowLabels (false);

    getSubjectEntryGroup ().addFeatureChangeListener (getSubjectSelection ());
    getQueryEntryGroup ().addEntryChangeListener (getQuerySelection ());

    addDisplayListeners (subject_entry_display, query_entry_display);

    setLayout(new BorderLayout ());

    setTitle ("Artemis Comparison Tool");

    if (getSubjectEntryGroup ().getDefaultEntry () != null &&
        getQueryEntryGroup ().getDefaultEntry () != null) {
      final String first_name =
        getSubjectEntryGroup ().getDefaultEntry ().getName ();
      final String second_name =
        getQueryEntryGroup ().getDefaultEntry ().getName ();

      if (first_name != null && second_name != null) {
        setTitle ("Artemis Comparison Tool: " + first_name + " vs " +
                  second_name);
      }
    }

    subject_plots =
      new BasePlotGroup (getSubjectEntryGroup (), this,
                         getSubjectSelection (), getSubjectGotoEventSource ());
    query_plots =
      new BasePlotGroup (getQueryEntryGroup (), this, getQuerySelection (),
                         getQueryGotoEventSource ());

    subject_entry_display.addDisplayAdjustmentListener (subject_plots);
    query_entry_display.addDisplayAdjustmentListener (query_plots);

    final Font default_font = getDefaultFont ();

    setFont (default_font);

    makeMenus ();

    addWindowListener (new WindowAdapter () {
      public void windowClosing (WindowEvent event) {
        // XXX
      }
    });

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

    GridBagConstraints c = new GridBagConstraints();

    c.gridwidth  = GridBagConstraints.REMAINDER;
    c.fill       = GridBagConstraints.HORIZONTAL;
    c.anchor     = GridBagConstraints.NORTH;
    c.gridheight = 1;
    c.weightx    = 1;
    c.weighty    = 0;

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

    c.weighty = 0;
    gridbag.setConstraints (subject_plots, c);
    add (subject_plots);

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

    c.fill = GridBagConstraints.BOTH;
    c.weighty = 1;
    gridbag.setConstraints (alignment_viewer, c);
    add (alignment_viewer);

    c.weighty = 0;
    gridbag.setConstraints (query_entry_display, c);
    add (query_entry_display);

    c.weighty = 0;
    gridbag.setConstraints (query_plots, c);
    add (query_plots);

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

    packme ();

    setSize (900,700);

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



  /**
   *  This method will call pack () and then move the Frame to the centre of
   *  the screen.  (Implementation of the FeatureDisplayOwner interface).
   **/
  public void packme () {
    pack ();

    final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();

    final int x_position = (screen.width - getSize ().width) / 2;
    int y_position = (screen.height - getSize ().height) / 2;

    if (y_position < 10) {
      y_position = 10;
    }

    setLocation (new Point (x_position, y_position));
  }

  /**
   *  Arrange for the two FeatureDisplay objects to scroll in parallel.
   **/
  public void lockDisplays () {
    lock_checkbox.setState (true);
  }

  /**
   *  Arrange for the two FeatureDisplay objects to scroll independently.
   **/
  public void unlockDisplays () {
    lock_checkbox.setState (false);
  }

  /**
   *  Return true if and only if the displays are currently locked.
   **/
  public boolean displaysAreLocked () {
    return lock_checkbox.getState ();
  }

  /**
   *  Toggle whether the two displays are locked.
   **/
  public void toggleDisplayLock () {
    if (displaysAreLocked ()) {
      unlockDisplays ();
    } else {
      lockDisplays ();
    }
  }

  /**
   *  Return the reference of the subject FeatureDisplay.
   **/
  public FeatureDisplay getSubjectDisplay () {
    return subject_entry_display;
  }

  /**
   *  Return the reference of the query FeatureDisplay.
   **/
  public FeatureDisplay getQueryDisplay () {
    return query_entry_display;
  }

  /**
   *  Return the reference of the EntryGroup in view in the subject
   *  FeatureDisplay.
   **/
  public EntryGroup getSubjectEntryGroup () {
    return subject_entry_group;
  }

  /**
   *  Return the reference of the EntryGroup in view in the query
   *  FeatureDisplay.
   **/
  public EntryGroup getQueryEntryGroup () {
    return query_entry_group;
  }

  /**
   *  Return the Selection object used by the subject FeatureDisplay.
   **/
  public Selection getSubjectSelection () {
    return subject_selection;
  }

  /**
   *  Return the Selection object used by the query FeatureDisplay.
   **/
  public Selection getQuerySelection () {
    return query_selection;
  }

  /**
   *  Return the GotoEventSource object used by the subject FeatureDisplay.
   **/
  public GotoEventSource getSubjectGotoEventSource () {
    return subject_goto_event_source;
  }

  /**
   *  Return the GotoEventSource object used by the query FeatureDisplay.
   **/
  public GotoEventSource getQueryGotoEventSource () {
    return query_goto_event_source;
  }

  /**
   *  Wire-up the two FeatureDisplay objects with DisplayAdjustmentListeners.
   **/
  private void addDisplayListeners (final FeatureDisplay subject_display,
                                    final FeatureDisplay query_display) {

    subject_listener = new DisplayAdjustmentListener () {
      public void displayAdjustmentValueChanged (DisplayAdjustmentEvent e) {
        subject_display.removeDisplayAdjustmentListener (subject_listener);
        query_display.removeDisplayAdjustmentListener (query_listener);

        final int current_start = query_display.getFirstVisibleForwardBase ();

        query_display.setScaleFactor (e.getScaleFactor ());

        if (lock_checkbox.getState () && !dont_move_query) {
          final int difference;

          if (last_subject_event == null) {
            difference = 0;
          } else {
            difference = e.getStart () - last_subject_event.getStart ();
          }

          final int new_start = current_start + difference;

          query_display.setFirstBase (new_start);
        }

        subject_display.addDisplayAdjustmentListener (subject_listener);
        query_display.addDisplayAdjustmentListener (query_listener);

        last_subject_event = e;
      }
    };

    query_listener = new DisplayAdjustmentListener () {
      public void displayAdjustmentValueChanged (DisplayAdjustmentEvent e) {
        subject_display.removeDisplayAdjustmentListener (subject_listener);
        query_display.removeDisplayAdjustmentListener (query_listener);

        final int current_start =
        subject_display.getFirstVisibleForwardBase ();

        subject_display.setScaleFactor (e.getScaleFactor ());

        if (lock_checkbox.getState () && !dont_move_subject) {
          final int difference;

          if (last_query_event == null) {
            difference = 0;
          } else {
            difference = e.getStart () - last_query_event.getStart ();
          }

          final int new_start = current_start + difference;

          subject_display.setFirstBase (new_start);
        }

        subject_display.addDisplayAdjustmentListener (subject_listener);
        query_display.addDisplayAdjustmentListener (query_listener);

        last_query_event = e;
      }
    };

    subject_display.addDisplayAdjustmentListener (subject_listener);
    query_display.addDisplayAdjustmentListener (query_listener);

    final DisplayAdjustmentListener subject_align_listener =
      new DisplayAdjustmentListener () {
        public void displayAdjustmentValueChanged (DisplayAdjustmentEvent e) {
          alignment_viewer.setSubjectSequencePosition (e);
        }
      };

    subject_display.addDisplayAdjustmentListener (subject_align_listener);

    final DisplayAdjustmentListener query_align_listener =
      new DisplayAdjustmentListener () {
        public void displayAdjustmentValueChanged (DisplayAdjustmentEvent e) {
          alignment_viewer.setQuerySequencePosition (e);
        }
      };

    query_display.addDisplayAdjustmentListener (query_align_listener);
  }

  private boolean dont_move_subject = false;
  private boolean dont_move_query = false;

  /**
   *  Make subject_goto_event_source and query_goto_event_source and wire them
   *  togeather so that goto events do the right thing.
   **/
  private void makeGotoEventSources () {
    subject_goto_event_source =
      new SimpleGotoEventSource (getSubjectEntryGroup ()) {
        /**
         *  Send the given event to all the GotoListeners.
         **/
        public void sendGotoEvent (final GotoEvent goto_event) {
          // temporarily disable moving so that the other FeatureDisplay
          // doesn't start moving (because of the listeners set up in
          // addDisplayListeners ()
          dont_move_query = true;
          super.sendGotoEvent (goto_event);
          dont_move_query = false;
        }
      };

    query_goto_event_source =
      new SimpleGotoEventSource (getQueryEntryGroup ()) {
        /**
         *  Send the given event to all the GotoListeners.
         **/
        public void sendGotoEvent (final GotoEvent goto_event) {
          // temporarily disable moving so that the other FeatureDisplay
          // doesn't start moving (because of the listeners set up in
          // addDisplayListeners ()
          dont_move_subject = true;
          super.sendGotoEvent (goto_event);
          dont_move_subject = false;
        }
      };
  }

  /**
   *  Make a Selection object for the subject and one for the query.  Add
   *  SelectionChangeListeners for each that update the selected matches in
   *  the AlignmentViewer.
   **/
  private void makeSelections () {
//    subject_selection = new Selection (getToolkit().getSystemClipboard());
    subject_selection = new Selection (null);
//    query_selection = new Selection (getToolkit().getSystemClipboard());
    query_selection = new Selection (null);

    final SelectionChangeListener subject_listener =
      new SelectionChangeListener () {
        public void selectionChanged (SelectionChangeEvent event) {
          final Range range = subject_selection.getSelectionRange ();
          alignment_viewer.selectFromSubjectRange (range);
        }
      };

    final SelectionChangeListener query_listener =
      new SelectionChangeListener () {
        public void selectionChanged (SelectionChangeEvent event) {
          final Range range = query_selection.getSelectionRange ();
          alignment_viewer.selectFromQueryRange (range);
        }
      };

    subject_selection.addSelectionChangeListener (subject_listener);
    query_selection.addSelectionChangeListener (query_listener);
  }

  /**
   *  Send an event to those object listening for it.
   *  @param listeners A Vector of the objects that the event should be sent
   *    to.
   *  @param event The event to send
   **/
  private void fireAction (final Vector listeners, final EventObject event) {
    final Vector targets;
    // copied from a book - synchronising the whole method might cause a
    // deadlock
    synchronized (this) {
      targets = (Vector) listeners.clone ();
    }

    for (int i = 0 ; i < targets.size () ; ++i) {
      GotoListener target = (GotoListener) targets.elementAt (i);

      if (event instanceof GotoEvent) {
        final GotoListener goto_listener = (GotoListener) target;
        goto_listener.performGoto ((GotoEvent) event);
      } else {
        throw new Error ("EntryEdit.fireAction () - unknown event");
      }
    }
  }

  /**
   *  Return the current default font (from Diana.options).
   **/
  private Font getDefaultFont () {
    return Options.getOptions ().getFont ();
  }

  /**
   *  Make and add the menus for this component.
   **/
  private void makeMenus () {
    final Font default_font = getDefaultFont ();

    setMenuBar (menu_bar);

    makeFileMenu ();

    menu_bar.add (file_menu);

    final BaseAlgorithm [] subject_algorithms =
      subject_plots.getPlotAlgorithms ();

    for (int i = 0 ; i < subject_algorithms.length ; ++i) {
      final BaseAlgorithm this_algorithm = subject_algorithms[i];

      addDisplayMenuAlgorithm (subject_plots, this_algorithm,
                               subject_graphs_menu);
    }

    final BaseAlgorithm [] query_algorithms =
      query_plots.getPlotAlgorithms ();

    for (int i = 0 ; i < query_algorithms.length ; ++i) {
      final BaseAlgorithm this_algorithm = query_algorithms[i];

      addDisplayMenuAlgorithm (query_plots, this_algorithm,
                               query_graphs_menu);
    }

    menu_bar.add (subject_graphs_menu);
    menu_bar.add (query_graphs_menu);

    final Menu display_menu = new Menu ("Display");


    final MenuItem hide_on_frame_lines_item =
      new MenuItem ("Hide Frame Lines");
    hide_on_frame_lines_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        getSubjectDisplay ().setShowForwardFrameLines (false);
        getSubjectDisplay ().setShowReverseFrameLines (false);
        getQueryDisplay ().setShowForwardFrameLines (false);
        getQueryDisplay ().setShowReverseFrameLines (false);
      }
    });

    display_menu.add (hide_on_frame_lines_item);


    final MenuItem show_on_frame_lines_item =
      new MenuItem ("Show Frame Lines");
    show_on_frame_lines_item.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        getSubjectDisplay ().setShowForwardFrameLines (true);
        getSubjectDisplay ().setShowReverseFrameLines (true);
        getQueryDisplay ().setShowForwardFrameLines (true);
        getQueryDisplay ().setShowReverseFrameLines (true);
      }
    });

    display_menu.add (show_on_frame_lines_item);


    menu_bar.add (display_menu);
  }

  /**
   *  Add a menu item for the given algorithm to the given menu.
   **/
  private void addDisplayMenuAlgorithm (final BasePlotGroup base_plot_group,
                                        final BaseAlgorithm algorithm,
                                        final Menu menu) {
    final CheckboxMenuItem new_item =
      new CheckboxMenuItem (algorithm.getAlgorithmName ());

    new_item.setState (base_plot_group.basePlotIsVisible (algorithm));

    new_item.addItemListener (new ItemListener () {
      public void itemStateChanged(ItemEvent event) {
        base_plot_group.setVisibleByAlgorithm (algorithm,
                                               new_item.getState ());
        packme ();
      }
    });

    menu.add (new_item);
  }

  /**
   *  Make a new File menu replacing the current one (if any).
   **/
  private void makeFileMenu () {
    file_menu.removeAll ();

    for (int subject_or_query = 0 ;
         subject_or_query <= 1 ;
         ++ subject_or_query) {
      // subject_or_query == 0 means subject otherwise query

      final String subject_or_query_string;
      final EntryGroup entry_group;

      if (subject_or_query == 0) {
        subject_or_query_string = "Top";
        entry_group = getSubjectEntryGroup ();
      } else {
        subject_or_query_string = "Bottom";
        entry_group = getQueryEntryGroup ();
      }

      for (int source_index = 0 ;
           source_index < entry_sources.size () ;
           ++source_index) {
        final EntrySource this_source =
          entry_sources.elementAt (source_index);

        String entry_source_name = this_source.getSourceName ();

        String menu_item_name = null;

        if (entry_source_name.equals ("Filesystem")) {
          menu_item_name =
            "Read An Entry Into " + subject_or_query_string + " ...";
        } else {
          menu_item_name =
            "Read " + entry_source_name + " Entry Into " +
            subject_or_query_string + " ...";
        }

        final MenuItem read_entry = new MenuItem (menu_item_name);

        read_entry.addActionListener (new ActionListener () {
          public void actionPerformed (ActionEvent event) {
            try {
              final Entry new_entry =
                this_source.getEntry (entry_group.getBases ());

              if (new_entry != null) {
                entry_group.add (new_entry);
              }
            } catch (OutOfRangeException e) {
              new MessageDialog (Comparator.this,
                                 "read failed: one of the features in the" +
                                 " entry has an out of range location: " +
                                 e.getMessage ());
              return;
            } catch (IOException e) {
              new MessageDialog (Comparator.this,
                                 "read failed due to an IO error: " +
                                 e.getMessage ());
              return;
            }
          }
        });

        file_menu.add (read_entry);
      }
    }


    file_menu.addSeparator ();

    final MenuItem edit_subject = new MenuItem ("Edit Subject in Artemis");
    edit_subject.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        final EntryEdit entry_edit = new EntryEdit (getSubjectEntryGroup ());

        entry_edit.setVisible (true);
      }
    });

    file_menu.add (edit_subject);


    final MenuItem edit_query = new MenuItem ("Edit Query in Artemis");
    edit_query.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        final EntryEdit entry_edit = new EntryEdit (getQueryEntryGroup ());

        entry_edit.setVisible (true);
      }
    });

    file_menu.add (edit_query);


    file_menu.addSeparator ();

    final MenuItem close = new MenuItem ("Close");
    close.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        Comparator.this.dispose ();
      }
    });

    file_menu.add (close);
  }

  /**
   *  A vector of those objects listening for selection change events.
   **/
  final private Vector goto_listener_list = new Vector ();

  /**
   *  One of the two Entry objects that we are comparing.
   **/
  final private EntryGroup subject_entry_group;

  /**
   *  One of the two Entry objects that we are comparing.
   **/
  final private EntryGroup query_entry_group;

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

  private final MenuBar menu_bar = new MenuBar ();
  private final Menu file_menu   = new Menu ("File");
  private final Menu subject_graphs_menu = new Menu ("Graphs(top)");
  private final Menu query_graphs_menu  = new Menu ("Graphs(bottom)");

  final private BasePlotGroup subject_plots;
  final private BasePlotGroup query_plots;

  final private Panel button_panel;
  final private Checkbox lock_checkbox;
  final private FeatureDisplay subject_entry_display;
  final private FeatureDisplay query_entry_display;
  final private AlignmentViewer alignment_viewer;

  private DisplayAdjustmentListener subject_listener = null;
  private DisplayAdjustmentListener query_listener = null;

  private DisplayAdjustmentEvent last_subject_event;
  private DisplayAdjustmentEvent last_query_event;

  /**
   *  The Selection used by the subject FeatureDisplay.
   **/
  private Selection subject_selection;

  /**
   *  The Selection used by the query FeatureDisplay.
   **/
  private Selection query_selection;

  /**
   *  The GotoEventSource used by the subject FeatureDisplay.
   **/
  private GotoEventSource subject_goto_event_source;

  /**
   *  The GotoEventSource used by the query FeatureDisplay.
   **/
  private GotoEventSource query_goto_event_source;

  /**
   *  The EntrySourceVector reference that is created in the constructor.
   **/
  final EntrySourceVector entry_sources;
}
