/*
 *    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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    Explorer.java
 *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.gui.explorer;

import java.awt.BorderLayout;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.EventListener;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Vector;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;

import weka.core.Capabilities;
import weka.core.Copyright;
import weka.core.Instances;
import weka.core.Memory;
import weka.core.converters.AbstractFileLoader;
import weka.core.converters.ConverterUtils;
import weka.gui.LogPanel;
import weka.gui.Logger;
import weka.gui.LookAndFeel;
import weka.gui.WekaTaskMonitor;

/**
 * The main class for the Weka explorer. Lets the user create, open, save,
 * configure, datasets, and perform ML analysis.
 * 
 * @author Len Trigg (trigg@cs.waikato.ac.nz)
 * @version $Revision: 10438 $
 */
public class Explorer extends JPanel {

  /** for serialization */
  private static final long serialVersionUID = -7674003708867909578L;

  /**
   * Interface for classes that listen for filter changes.
   * 
   * @author FracPete (fracpete at waikato dot ac dot nz)
   * @version $Revision: 10438 $
   */
  public static interface CapabilitiesFilterChangeListener extends
    EventListener {

    /**
     * method gets called in case of a change event
     * 
     * @param e the associated change event
     */
    public void capabilitiesFilterChanged(CapabilitiesFilterChangeEvent e);
  }

  /**
   * This event can be fired in case the capabilities filter got changed
   * 
   * @author FracPete (fracpete at waikato dot ac dot nz)
   * @version $Revision: 10438 $
   */
  public static class CapabilitiesFilterChangeEvent extends ChangeEvent {

    /** for serialization */
    private static final long serialVersionUID = 1194260517270385559L;

    /** the capabilities filter */
    protected Capabilities m_Filter;

    /**
     * Constructs a GOECapabilitiesFilterChangeEvent object.
     * 
     * @param source the Object that is the source of the event
     * @param filter the responsible capabilities filter
     */
    public CapabilitiesFilterChangeEvent(Object source, Capabilities filter) {
      super(source);
      m_Filter = filter;
    }

    /**
     * returns the associated Capabilities filter
     * 
     * @return the filter
     */
    public Capabilities getFilter() {
      return m_Filter;
    }
  }

  /**
   * A common interface for panels to be displayed in the Explorer
   * 
   * @author FracPete (fracpete at waikato dot ac dot nz)
   * @version $Revision: 10438 $
   */
  public static interface ExplorerPanel {

    /**
     * Sets the Explorer to use as parent frame (used for sending notifications
     * about changes in the data)
     * 
     * @param parent the parent frame
     */
    public void setExplorer(Explorer parent);

    /**
     * returns the parent Explorer frame
     * 
     * @return the parent
     */
    public Explorer getExplorer();

    /**
     * Tells the panel to use a new set of instances.
     * 
     * @param inst a set of Instances
     */
    public void setInstances(Instances inst);

    /**
     * Returns the title for the tab in the Explorer
     * 
     * @return the title of this tab
     */
    public String getTabTitle();

    /**
     * Returns the tooltip for the tab in the Explorer
     * 
     * @return the tooltip of this tab
     */
    public String getTabTitleToolTip();
  }

  /**
   * A common interface for panels in the explorer that can handle logs
   * 
   * @author FracPete (fracpete at waikato dot ac dot nz)
   * @version $Revision: 10438 $
   */
  public static interface LogHandler {

    /**
     * Sets the Logger to receive informational messages
     * 
     * @param newLog the Logger that will now get info messages
     */
    public void setLog(Logger newLog);
  }

  /** The panel for preprocessing instances */
  protected PreprocessPanel m_PreprocessPanel = new PreprocessPanel();

  /** Contains all the additional panels apart from the pre-processing panel */
  protected Vector<ExplorerPanel> m_Panels = new Vector<ExplorerPanel>();

  /** The tabbed pane that controls which sub-pane we are working with */
  protected JTabbedPane m_TabbedPane = new JTabbedPane();

  /** The panel for log and status messages */
  protected LogPanel m_LogPanel = new LogPanel(new WekaTaskMonitor());

  /** the listeners that listen to filter changes */
  protected HashSet<CapabilitiesFilterChangeListener> m_CapabilitiesFilterChangeListeners = new HashSet<CapabilitiesFilterChangeListener>();

  /**
   * Creates the experiment environment gui with no initial experiment
   */
  public Explorer() {

    String date = (new SimpleDateFormat("EEEE, d MMMM yyyy"))
      .format(new Date());
    m_LogPanel.logMessage(Messages.getInstance().getString(
      "Explorer_LogPanel_LogMessage_Text_First"));
    m_LogPanel.logMessage(Messages.getInstance().getString(
      "Explorer_LogPanel_LogMessage_Text_Second")
      + Copyright.getFromYear()
      + Messages.getInstance().getString(
        "Explorer_LogPanel_LogMessage_Text_Third")
      + Copyright.getToYear()
      + " " + Copyright.getOwner() + ", " + Copyright.getAddress());
    m_LogPanel.logMessage(Messages.getInstance().getString(
      "Explorer_LogPanel_LogMessage_Text_Fourth")
      + Copyright.getURL());
    m_LogPanel.logMessage(Messages.getInstance().getString(
      "Explorer_LogPanel_LogMessage_Text_Fifth")
      + date);
    m_LogPanel.statusMessage(Messages.getInstance().getString(
      "Explorer_LogPanel_StatusMessage_Text_First"));

    // intialize pre-processpanel
    m_PreprocessPanel.setLog(m_LogPanel);
    m_TabbedPane.addTab(m_PreprocessPanel.getTabTitle(), null,
      m_PreprocessPanel, m_PreprocessPanel.getTabTitleToolTip());

    // initialize additional panels
    String[] tabs = ExplorerDefaults.getTabs();
    Hashtable<String, HashSet> tabOptions = new Hashtable<String, HashSet>();
    for (String tab : tabs) {
      try {
        // determine classname and additional options
        String[] optionsStr = tab.split(":");
        String classname = optionsStr[0];
        HashSet options = new HashSet();
        tabOptions.put(classname, options);
        for (int n = 1; n < optionsStr.length; n++) {
          options.add(optionsStr[n]);
        }

        // setup panel
        ExplorerPanel panel = (ExplorerPanel) Class.forName(classname)
          .newInstance();
        panel.setExplorer(this);
        m_Panels.add(panel);
        if (panel instanceof LogHandler) {
          ((LogHandler) panel).setLog(m_LogPanel);
        }
        m_TabbedPane.addTab(panel.getTabTitle(), null, (JPanel) panel,
          panel.getTabTitleToolTip());
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    // setup tabbed pane
    m_TabbedPane.setSelectedIndex(0);
    for (int i = 0; i < m_Panels.size(); i++) {
      HashSet options = tabOptions.get(m_Panels.get(i).getClass().getName());
      m_TabbedPane.setEnabledAt(i + 1, options.contains("standalone"));
    }

    // setup notification for dataset changes
    m_PreprocessPanel.addPropertyChangeListener(new PropertyChangeListener() {
      @Override
      public void propertyChange(PropertyChangeEvent e) {
        for (int i = 0; i < m_Panels.size(); i++) {
          m_Panels.get(i).setInstances(m_PreprocessPanel.getInstances());
          m_TabbedPane.setEnabledAt(i + 1, true);
        }
      }
    });

    // add listeners for changes in the capabilities
    m_PreprocessPanel.setExplorer(this);
    addCapabilitiesFilterListener(m_PreprocessPanel);
    for (int i = 0; i < m_Panels.size(); i++) {
      if (m_Panels.get(i) instanceof CapabilitiesFilterChangeListener) {
        addCapabilitiesFilterListener((CapabilitiesFilterChangeListener) m_Panels
          .get(i));
      }
    }

    // add components to layout
    setLayout(new BorderLayout());
    add(m_TabbedPane, BorderLayout.CENTER);
    add(m_LogPanel, BorderLayout.SOUTH);
  }

  /**
   * returns all the panels, apart from the PreprocessPanel
   * 
   * @return the currently displayed panels w/o PreprocessPanel
   */
  public Vector<ExplorerPanel> getPanels() {
    return m_Panels;
  }

  /**
   * returns the instance of the PreprocessPanel being used in this instance of
   * the Explorer
   * 
   * @return the panel
   */
  public PreprocessPanel getPreprocessPanel() {
    return m_PreprocessPanel;
  }

  /**
   * returns the tabbed pane of the Explorer
   * 
   * @return the tabbed pane
   */
  public JTabbedPane getTabbedPane() {
    return m_TabbedPane;
  }

  /**
   * adds the listener to the list of objects that listen for changes of the
   * CapabilitiesFilter
   * 
   * @param l the listener to add
   * @see #m_CapabilitiesFilterChangeListeners
   */
  public void addCapabilitiesFilterListener(CapabilitiesFilterChangeListener l) {
    m_CapabilitiesFilterChangeListeners.add(l);
  }

  /**
   * Removes the specified listener from the set of listeners if it is present.
   * 
   * @param l the listener to remove
   * @return true if the listener was registered
   */
  public boolean removeCapabilitiesFilterListener(
    CapabilitiesFilterChangeListener l) {
    return m_CapabilitiesFilterChangeListeners.remove(l);
  }

  /**
   * notifies all the listeners of a change
   * 
   * @param filter the affected filter
   */
  public void notifyCapabilitiesFilterListener(Capabilities filter) {
    for (CapabilitiesFilterChangeListener l : m_CapabilitiesFilterChangeListeners) {
      if (l == this) {
        continue;
      }
      l.capabilitiesFilterChanged(new CapabilitiesFilterChangeEvent(this,
        filter));
    }
  }

  /**
   * variable for the Explorer class which would be set to null by the memory
   * monitoring thread to free up some memory if we running out of memory
   */
  private static Explorer m_explorer;

  /** for monitoring the Memory consumption */
  protected static Memory m_Memory = new Memory(true);

  /**
   * Tests out the explorer environment.
   * 
   * @param args ignored.
   */
  public static void main(String[] args) {

    weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, Messages
      .getInstance().getString("Explorer_Main_Logger_Text"));

    LookAndFeel.setLookAndFeel();

    try {
      // uncomment to disable the memory management:
      // m_Memory.setEnabled(false);

      m_explorer = new Explorer();
      final JFrame jf = new JFrame(Messages.getInstance().getString(
        "Explorer_Main_JFrame_Text"));
      jf.getContentPane().setLayout(new BorderLayout());
      jf.getContentPane().add(m_explorer, BorderLayout.CENTER);
      jf.addWindowListener(new WindowAdapter() {
        @Override
        public void windowClosing(WindowEvent e) {
          jf.dispose();
          System.exit(0);
        }
      });
      jf.pack();
      jf.setSize(800, 600);
      jf.setVisible(true);
      Image icon = Toolkit.getDefaultToolkit().getImage(
        m_explorer.getClass().getClassLoader()
          .getResource("weka/gui/weka_icon_new_48.png"));
      jf.setIconImage(icon);

      if (args.length == 1) {
        System.err.println(Messages.getInstance().getString(
          "Explorer_Main_Run_Error_Text")
          + args[0]);
        AbstractFileLoader loader = ConverterUtils.getLoaderForFile(args[0]);
        loader.setFile(new File(args[0]));
        m_explorer.m_PreprocessPanel.setInstancesFromFile(loader);
      }

      Thread memMonitor = new Thread() {
        @Override
        public void run() {
          while (true) {
            // try {
            // //System.out.println("Before sleeping.");
            // this.sleep(4000);

            // System.gc();

            if (m_Memory.isOutOfMemory()) {
              // clean up
              jf.dispose();
              m_explorer = null;
              System.gc();

              // display error
              System.err.println(Messages.getInstance().getString(
                "Explorer_Main_Run_Error_Text_First"));
              m_Memory.showOutOfMemory();
              System.err.println(Messages.getInstance().getString(
                "Explorer_Main_Run_Error_Text_Second"));
              System.exit(-1);
            }

            // } catch(InterruptedException ex) { ex.printStackTrace(); }
          }
        }
      };

      memMonitor.setPriority(Thread.MAX_PRIORITY);
      memMonitor.start();
    } catch (Exception ex) {
      ex.printStackTrace();
      System.err.println(ex.getMessage());
    }
  }
}
