/*
 *    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.
 */

/*
 *    EnsembleLibraryEditor.java
 *    Copyright (C) 2006 Robert Jung
 *
 */

package weka.gui;

import weka.classifiers.EnsembleLibrary;
import weka.gui.ensembleLibraryEditor.AddModelsPanel;
import weka.gui.ensembleLibraryEditor.ListModelsPanel;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyEditor;

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

/**
 * Class for editing Library objects. Brings up a custom editing panel for the
 * user to edit the library model list, as well as save load libraries from
 * files.
 * <p/>
 * A model list file is simply a flat file with a single classifier on each
 * line. Each of these classifier is represented by the command line string that
 * would be used to create that specific model with the specified set of
 * paramters.
 * <p/>
 * This code in class is based on other custom editors in weka.gui such as the
 * CostMatrixEditor to try and maintain some consistency throughout the package.
 * 
 * @author  Robert Jung (mrbobjung@gmail.com)
 * @version $Revision: 1.1 $
 */
public class EnsembleLibraryEditor
  implements PropertyEditor {
  
  /** The library being edited */
  protected EnsembleLibrary m_Library;
  
  /** A helper class for notifying listeners */
  protected transient PropertyChangeSupport m_PropSupport = new PropertyChangeSupport(
      this);
  
  /** An instance of the custom editor */
  protected transient CustomEditor m_CustomEditor = new CustomEditor();
  
  /**
   * the main panel of the editor that will let the user switch between the
   * different editing panels.
   */
  protected transient JTabbedPane m_ModelOptionsPane = new JTabbedPane();
  
  /**
   * the main panel that will display the current set of models in the library
   */
  protected transient ListModelsPanel m_ListModelsPanel = new ListModelsPanel(
      m_Library);
  
  /**
   * This class presents a GUI for editing the library, and saving and loading
   * model list from files.
   */
  private class CustomEditor
    extends JPanel {
    
    /** for serialization */
    private static final long serialVersionUID = -1553490570313707008L;

    /**
     * Constructs a new editor.
     */
    public CustomEditor() {
      
      m_ModelOptionsPane = new JTabbedPane();
      
      m_ListModelsPanel = new ListModelsPanel(m_Library);
      
      m_ModelOptionsPane.addTab("Current Library", m_ListModelsPanel);
      
      m_ModelOptionsPane.addTab("Add Models", new AddModelsPanel(
	  m_ListModelsPanel));
      
      // All added panels needs a reference to the main one so that
      // they can add models to it.
      setLayout(new BorderLayout());
      add(m_ModelOptionsPane, BorderLayout.CENTER);
    }
    
  }
  
  /**
   * Constructs a new LibraryEditor.
   */
  public EnsembleLibraryEditor() {
    m_PropSupport = new PropertyChangeSupport(this);
    m_CustomEditor = new CustomEditor();
  }
  
  /**
   * Sets the value of the Library to be edited.
   * 
   * @param value
   *            a Library object to be edited
   */
  public void setValue(Object value) {
    m_Library = (EnsembleLibrary) value;
    m_ListModelsPanel.setLibrary(m_Library);
  }
  
  /**
   * Gets the cost matrix that is being edited.
   * 
   * @return the edited CostMatrix object
   */
  public Object getValue() {
    return m_Library;
  }
  
  /**
   * Indicates whether the object can be represented graphically. In this case
   * it can.
   * 
   * @return true
   */
  public boolean isPaintable() {
    return true;
  }
  
  /**
   * Paints a graphical representation of the Object. For the ensemble library
   * it prints out the working directory as well as the number of models in
   * the library
   * 
   * @param gfx
   *            the graphics context to draw the representation to
   * @param box
   *            the bounds within which the representation should fit.
   */
  public void paintValue(Graphics gfx, Rectangle box) {
    gfx.drawString(m_Library.size() + " models selected", box.x, box.y
	+ box.height);
  }
  
  /**
   * Returns the Java code that generates an object the same as the one being
   * edited. Unfortunately this can't be done in a single line of code, so the
   * code returned will only build a default cost matrix of the same size.
   * 
   * @return the initialization string
   */
  public String getJavaInitializationString() {
    return ("new Library(" + m_Library.size() + ")");
  }
  
  /**
   * Some objects can be represented as text, but a library cannot.
   * 
   * @return null
   */
  public String getAsText() {
    return null;
  }
  
  /**
   * Some objects can be represented as text, but a library cannot.
   * 
   * @param text
   *            ignored
   * @throws always throws an IllegalArgumentException
   */
  public void setAsText(String text) {
    throw new IllegalArgumentException("LibraryEditor: "
	+ "Library properties cannot be " + "expressed as text");
  }
  
  /**
   * Some objects can return tags, but a cost matrix cannot.
   * 
   * @return null
   */
  public String[] getTags() {
    return null;
  }
  
  /**
   * Gets a GUI component with which the user can edit the cost matrix.
   * 
   * @return an editor GUI component
   */
  public Component getCustomEditor() {
    return m_CustomEditor;
  }
  
  /**
   * Indicates whether the library can be edited in a GUI, which it can.
   * 
   * @return true
   */
  public boolean supportsCustomEditor() {
    return true;
  }
  
  /**
   * Adds an object to the list of those that wish to be informed when the
   * library changes.
   * 
   * @param listener
   *            a new listener to add to the list
   */
  public void addPropertyChangeListener(PropertyChangeListener listener) {
    m_Library.addPropertyChangeListener(listener);
  }
  
  /**
   * Removes an object from the list of those that wish to be informed when
   * the cost matrix changes.
   * 
   * @param listener
   *            the listener to remove from the list
   */
  public void removePropertyChangeListener(PropertyChangeListener listener) {
    m_PropSupport.removePropertyChangeListener(listener);
  }
  
  // ***************************************************************
  // The following three methods seem sort of out of place here.
  // Basically they are helper methods for the various classes that
  // constitute the AddModelsPanel. These static methods needed to
  // go here because the objects (PropertyText, PropertyPanel, etc)
  // involved are not visible from the weka.gui.libraryEditor
  // package where the AddModelsPanel classes reside.  If these 
  // objects ever became public then these three methods should be
  // moved within the gui.LibraryEditor package
  // ***************************************************************
  
  /**
   * This method handles the different object editor types in weka to obtain
   * their current values.
   * 
   * @param source	an Editor
   * @return		the value of the editor
   */
  public static Object getEditorValue(Object source) {
    
    Object value = null;
    
    if (source instanceof GenericArrayEditor) {
      value = ((GenericArrayEditor) source).getValue();
    } else if (source instanceof CostMatrixEditor) {
      value = ((CostMatrixEditor) source).getValue();
    } else if (source instanceof PropertyText) {
      value = ((PropertyText) source).getText();
    } else if (source instanceof PropertyEditor) {
      value = ((PropertyEditor) source).getValue();
    }
    
    return value;
  }
  
  /**
   * This is a helper function that creates a renderer for Default Objects.
   * These are basically objects that are not numeric, nominal, or generic
   * objects. These are objects that we don't want to do anything special with
   * and just display their values as normal. We simply create the editor the
   * same way that they would have been created in the PropertySheetPanel
   * class.
   * 
   * @param nodeEditor
   *            the editor created for the defaultNode
   * @return the Component to dispaly the defaultNode
   */
  public static Component getDefaultRenderer(PropertyEditor nodeEditor) {
    
    Component genericRenderer = null;
    
    if (nodeEditor.isPaintable() && nodeEditor.supportsCustomEditor()) {
      genericRenderer = new PropertyPanel(nodeEditor);
    } else if (nodeEditor.getTags() != null) {
      genericRenderer = new PropertyValueSelector(nodeEditor);
    } else if (nodeEditor.getAsText() != null) {
      // String init = editor.getAsText();
      genericRenderer = new PropertyText(nodeEditor);
      ((PropertyText) genericRenderer).setColumns(20);
    } else {
      System.err.println("Warning: Property \""
	  + nodeEditor.getClass().toString()
	  + "\" has non-displayabale editor.  Skipping.");
    }
    
    return genericRenderer;
  }
  
  /**
   * This is a helper function that creates a renderer for GenericObjects
   * 
   * @param classifierEditor
   *            the editor created for this
   * @return object renderer
   */
  public static Component createGenericObjectRenderer(
      GenericObjectEditor classifierEditor) {
    
    PropertyPanel propertyPanel = new PropertyPanel(classifierEditor);
    
    return propertyPanel;
  }
  
  
  /**
   * This is a simple main method that lets you run a LibraryEditor
   * on its own without having to deal with the Explorer, etc... 
   * This is useful only for building model lists.
   * 
   * @param args	the commandline arguments
   */
  public static void main(String[] args) {
    
    EnsembleLibrary value = new EnsembleLibrary();
    EnsembleLibraryEditor libraryEditor = new EnsembleLibraryEditor();
    libraryEditor.setValue(value);
    
    JPanel editor = (JPanel) libraryEditor.getCustomEditor();
    
    JFrame frame = new JFrame();
    frame.getContentPane().add(editor);
    
    frame.pack();
    frame.setVisible(true);
  }
  
}
