/* ArtemisMain.java
 *
 * created: Wed Feb 23 2000
 *
 * This file is part of Artemis
 *
 * Copyright (C) 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/ArtemisMain.java,v 1.23 2000/11/01 14:57:31 kmr Exp $
 */

package diana.components;

import Diana;
import AppGlobal;

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

import uk.ac.sanger.pathogens.*;
import uk.ac.sanger.pathogens.embl.Key;
import uk.ac.sanger.pathogens.embl.KeyVector;
import uk.ac.sanger.pathogens.embl.EntryInformation;
import uk.ac.sanger.pathogens.embl.SimpleEntryInformation;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.Properties;
import java.util.Enumeration;

/**
 *  The main window for the Artemis sequence editor.
 *
 *  @author Kim Rutherford <kmr@sanger.ac.uk>
 *  @version $Id: ArtemisMain.java,v 1.23 2000/11/01 14:57:31 kmr Exp $
 **/

public class ArtemisMain extends Splash {
  /**
   *  The constructor creates all the components for the main Artemis window
   *  and sets up all the menu callbacks.
   **/
  public ArtemisMain () {
    super ("Artemis", Diana.version);
  }

  /**
   *  Read the entries named in args and in the diana.ini file.
   **/
  public void readArgsAndOptions (final String [] args) {
    if (args.length == 0) {
      // open the entries given in the options file (diana.ini)
      readDefaultEntries ();
    }

    final EntryInformation artemis_entry_information =
      Options.getArtemisEntryInformation ();

    EntryEdit last_entry_edit = null;

    for (int i = 0 ; i < args.length ; ++i) {
      final String new_entry_name = args[i];

      if (new_entry_name.length () == 0) {
        continue;
      }

      if (new_entry_name.startsWith ("+") && last_entry_edit != null) {
        final File entry_file = new File (new_entry_name.substring (1));

        final uk.ac.sanger.pathogens.embl.Entry new_embl_entry =
          EntryFileDialog.getEntryFromFile (this,
                                            entry_file,
                                            artemis_entry_information,
                                            getInputStreamProgressListener ());

        if (new_embl_entry == null) {
          // the read failed
          break;
        }

        try {
          final Entry new_entry =
            new Entry (last_entry_edit.getEntryGroup ().getBases (),
                       new_embl_entry);

          last_entry_edit.getEntryGroup ().add (new_entry);
        } catch (OutOfRangeException e) {
          new MessageDialog (this, "read failed: one of the features in " +
                             new_entry_name + " has an out of range " +
                             "location: " + e.getMessage ());
        }

      } else {
        final File entry_file = new File (new_entry_name);

        final uk.ac.sanger.pathogens.embl.Entry new_embl_entry =
          EntryFileDialog.getEntryFromFile (this,
                                            entry_file,
                                            artemis_entry_information,
                                            getInputStreamProgressListener ());

        if (new_embl_entry == null) {
          // the read failed
          break;
        }

        try {
          final Entry entry = new Entry (new_embl_entry);

          last_entry_edit = makeEntryEdit (entry);

          getStatusLabel ().setText ("");
        } catch (OutOfRangeException e) {
          new MessageDialog (this, "read failed: one of the features in " +
                             new_entry_name + " has an out of range " +
                             "location: " + e.getMessage ());
          break;
        } catch (NoSequenceException e) {
          new MessageDialog (this, "read failed: " +
                             new_entry_name + " contains no sequence");
          break;
        }
      }
    }

    for (int entry_index = 0 ;
         entry_index < entry_edit_objects.size () ;
         ++entry_index) {
      entry_edit_objects.elementAt (entry_index).show ();
    }
  }

  /**
   *  Read the entries given in the diana.ini file.
   **/
  private void readDefaultEntries () {
    final EntryInformation artemis_entry_information =
      Options.getArtemisEntryInformation ();

    final String default_sequence_file_name =
      Options.getOptions ().getDefaultSequenceFileName ();

    final String default_feature_file_name =
      Options.getOptions ().getDefaultFeatureFileName ();

    if (default_sequence_file_name != null) {
      final String default_sequence_file_name_embl =
        default_sequence_file_name + "_embl";

      uk.ac.sanger.pathogens.embl.Entry new_embl_entry = null;

      // try opening the default sequence file with "_embl" added to the name
      // if that fails try the plain sequence file name

      final File entry_file_embl = new File (default_sequence_file_name_embl);

      if (entry_file_embl.exists ()) {
        new_embl_entry =
          EntryFileDialog.getEntryFromFile (this,
                                            entry_file_embl,
                                            artemis_entry_information,
                                            getInputStreamProgressListener ());
      }

      if (new_embl_entry == null || new_embl_entry.getSequence () == null ||
          new_embl_entry.getSequence ().length () == 0) {
        final File entry_file = new File (default_sequence_file_name);

        if (entry_file.exists ()) {
          final InputStreamProgressListener progress_listener =
            getInputStreamProgressListener ();

          new_embl_entry =
            EntryFileDialog.getEntryFromFile (this,
                                              entry_file,
                                              artemis_entry_information,
                                              progress_listener);
        } else {
          // read failed
          System.err.println ("file does not exist: " +
                              default_sequence_file_name +
                              " (given in options files)");
          return;
        }
      }

      if (new_embl_entry == null || new_embl_entry.getSequence () == null ||
          new_embl_entry.getSequence ().length () == 0) {
        // read failed
        System.err.println ("failed to read " + default_sequence_file_name +
                            " (given in options files)");
        return;
      }

      getStatusLabel ().setText ("");

      try {
        final Entry entry = new Entry (new_embl_entry);

        final EntryEdit new_entry_edit = makeEntryEdit (entry);

        if (default_feature_file_name != null) {
          final File feature_file = new File (default_feature_file_name);

          final InputStreamProgressListener progress_listener =
            getInputStreamProgressListener ();

          final uk.ac.sanger.pathogens.embl.Entry new_embl_table_entry =
            EntryFileDialog.getEntryFromFile (this,
                                              feature_file,
                                              artemis_entry_information,
                                              progress_listener);

          if (new_embl_table_entry == null) {
            // open failed
            return;
          }

          final EntryGroup entry_group = new_entry_edit.getEntryGroup ();

          final Entry new_table_entry =
            new Entry (entry.getBases (), new_embl_table_entry);

          entry_group.add (new_table_entry);
        }
      } catch (OutOfRangeException e) {
        new MessageDialog (this, "read failed: one of the features in " +
                           default_feature_file_name +
                           " has an out of range location: " +
                           e.getMessage ());
      } catch (NoSequenceException e) {
        new MessageDialog (this, "read failed: " +
                           new_embl_entry.getName () +
                           " contains no sequence");
      }
    }
  }

  /**
   *  Return a Logger for warnings/errors/messages.
   **/
  public static Logger getLogger () {
    return logger;
  }

  /**
   *  Make an EntryEdit component from the given Entry.
   **/
  private EntryEdit makeEntryEdit (final Entry entry) {
    final Bases bases = entry.getBases ();

    final EntryGroup entry_group = new SimpleEntryGroup (bases);

    entry_group.add (entry);

    final InputStreamProgressListener listener =
      getInputStreamProgressListener ();

    final EntryEdit entry_edit = new EntryEdit (entry_group);

    entry_edit.setVisible (true);

    return entry_edit;
  }

  /**
   *  This method gets rid of an EntryEdit object and it's frame.  Each object
   *  removed with this method must have been added previously with
   *  addEntryEdit ().
   *  @param entry_edit The object to get rid of.
   **/
  public void entryEditFinished (EntryEdit entry_edit) {
    if (null == entry_edit) {
      throw new Error ("entryEditFinished () was passed a null object");
    }

    if (!entry_edit_objects.removeElement (entry_edit)) {
      throw new Error ("entryEditFinished () - could not remove a " +
                       "object from an empty vector");
    }
    entry_edit.setVisible (false);
    entry_edit.dispose ();
  }

  /**
   *  Add an EntryEdit object to our list of objects.
   *  @param entry_edit The object to add.
   **/
  public synchronized void addEntryEdit (EntryEdit entry_edit) {
    entry_edit_objects.addElement (entry_edit);
  }

  /**
   *  Return the reference of the global clipboard object for this program.
   **/
// XXX
//    static public ClipBoard getClipboard () {
//      return clipboard;
//    }

  /**
   *  Make a new menu item in the given menu, with its label given the
   *  String and add the given ActionListener to it.
   */
  private static void makeMenuItem (Menu menu, String name,
                                    ActionListener listener) {
    MenuItem new_item = new MenuItem (name);
    menu.add (new_item);
    new_item.addActionListener (listener);
  }

  /**
   *  Make all the menus and menu items for the main window.  Also sets up
   *  suitable ActionListener objects for each item.
   */
  protected void makeAllMenus () {
    final Menu file_menu = new Menu ("File");

    final Menu options_menu = new Menu ("Options");

    getMenuBar ().add (file_menu);
    getMenuBar ().add (options_menu);

    final EntrySourceVector entry_sources = getEntrySources (this);

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

      String entry_source_name = this_entry_source.getSourceName ();

      String menu_name = null;

      if (entry_source_name.equals ("Filesystem")) {
        menu_name = "Open ...";
      } else {
        menu_name = "Open from " + entry_source_name + " ...";
      }

      ActionListener menu_listener = menu_listener = new ActionListener () {
        public void actionPerformed (ActionEvent event) {
          getEntryEditFromEntrySource (this_entry_source);
        }
      };
      makeMenuItem (file_menu, menu_name, menu_listener);
    }

    ActionListener menu_listener = null;

    menu_listener = new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        exit ();
      }
    };
    makeMenuItem (file_menu, "Quit", menu_listener);

    menu_listener = new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        resetOptions ();
      }
    };
    makeMenuItem (options_menu, "Re-read Options", menu_listener);

    final CheckboxMenuItem enable_direct_edit_item =
      new CheckboxMenuItem ("Enable Direct Editing");
    enable_direct_edit_item.setState (Options.getOptions ().canDirectEdit ());
    enable_direct_edit_item.addItemListener (new ItemListener () {
      public void itemStateChanged(ItemEvent event) {
        final boolean item_state = enable_direct_edit_item.getState ();
        Options.getOptions ().setDirectEdit (item_state);
      }
    });
    options_menu.add (enable_direct_edit_item);

    if (Options.isUnixHost ()) {
      options_menu.addSeparator ();

      menu_listener = new ActionListener () {
        public void actionPerformed (ActionEvent event) {
          logger.setVisible (true);
        }
      };
      makeMenuItem (options_menu, "Show Log Window", menu_listener);

      menu_listener = new ActionListener () {
        public void actionPerformed (ActionEvent event) {
          logger.setVisible (false);
        }
      };
      makeMenuItem (options_menu, "Hide Log Window", menu_listener);
    }
  }

  /**
   *  Read an Entry from the given EntrySource and make a new EntryEdit
   *  component for the Entry.
   **/
  private void getEntryEditFromEntrySource (final EntrySource entry_source) {
    try {
      final Entry entry = entry_source.getEntry ();

      if (entry != null) {
        final EntryGroup entry_group =
          new SimpleEntryGroup (entry.getBases ());

        entry_group.add (entry);

        final EntryEdit entry_edit = new EntryEdit (entry_group);

        entry_edit.setVisible (true);
      }
    } catch (OutOfRangeException e) {
      new MessageDialog (this, "read failed: one of the features in " +
                         " the entry has an out of range " +
                         "location: " + e.getMessage ());
    } catch (NoSequenceException e) {
      new MessageDialog (this, "read failed: entry contains no sequence");
    } catch (IOException e) {
      new MessageDialog (this, "read failed due to IO error: " +
                         e.getMessage ());
    }
  }

  /**
   *  Force the options files to be re-read and the EntryEdit components to be
   *  redisplayed.
   **/
  private void resetOptions () {
    Options.getOptions ().reset ();
  }

  /**
   *  Force all the EntryEdit components to be redisplayed.
   **/
  private void redisplayAll () {
    for (int i = 0 ; i < entry_edit_objects.size () ; ++i) {
      entry_edit_objects.elementAt (i).redisplay ();
    }
  }

  /**
   *  Close the main frame and all EntryEdit frames and then this frame,
   *  then exit.
   **/
  protected void exit () {
    for (int i = 0 ; i < entry_edit_objects.size () ; ++i) {
      entryEditFinished (entry_edit_objects.elementAt (i));
    }
    setVisible (false);
    dispose ();
    System.gc ();
    System.exit (0);
  }

  /**
   *  The global clip board object.  Used by the stand alone program and the
   *  applet.
   **/
// XXX
//  private ClipBoard clipboard = new ClipBoard ();

  /**
   *  A vector containing all the EntryEdit object we have created.
   **/
  private EntryEditVector entry_edit_objects = new EntryEditVector ();

  /**
   *  The Logger that is returned by getLogger().
   **/
  private final static LogViewer logger = new LogViewer ();
}
