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

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

package weka.classifiers;

import weka.core.OptionHandler;
import weka.core.RevisionHandler;
import weka.core.RevisionUtils;
import weka.core.Utils;
import weka.gui.GenericObjectEditor;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.MethodDescriptor;
import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * This class is a light wrapper that is meant to represent a
 * classifier in a classifier library.  This simple base class
 * is intended only to store the class type and the parameters
 * for a specific "type" of classifier.  You will need to
 * extend this class to add functionality specific to models
 * that have been trained on data (as we have for Ensemble Selection)
 *
 * @author  Robert Jung (mrbobjung@gmail.com)
 * @version $Revision: 6041 $
 */
public class EnsembleLibraryModel
  implements Serializable, RevisionHandler {

  /** this stores the class of the classifier */
  //private Class m_ModelClass;

  /**
   * This is the serialVersionUID that SHOULD stay the same
   * so that future modified versions of this class will be
   * backwards compatible with older model versions.
   */
  private static final long serialVersionUID = 7932816660173443200L;

  /** this is an array of options*/
  protected Classifier m_Classifier;

  /** the description of this classifier*/
  protected String m_DescriptionText;

  /** this is also saved separately */
  protected String m_ErrorText;

  /** a boolean variable tracking whether or not this classifier was
   * able to be built successfully with the given set of options*/
  protected boolean m_OptionsWereValid;

  /** this is stores redundantly to speed up the many operations that
   frequently need to get the model string representations (like JList
   renderers) */
  protected String m_StringRepresentation;

  /**
   * Default Constructor
   */
  public EnsembleLibraryModel() {
    super();
  }

  /**
   * Initializes the class with the given classifier.
   *
   * @param classifier	the classifier to use
   */
  public EnsembleLibraryModel(Classifier classifier) {

    m_Classifier = classifier;

    //this may seem redundant and stupid, but it really is a good idea
    //to cache the stringRepresentation to minimize the amount of work
    //that needs to be done when these Models are rendered
    m_StringRepresentation = toString();

    updateDescriptionText();
  }

  /**
   * This method will attempt to instantiate this classifier with the given
   * options. If an exception is thrown from the setOptions method of the
   * classifier then the resulting exception text will be saved in the
   * description text string.
   */
  public void testOptions() {

    Classifier testClassifier = null;
    try {
      testClassifier = ((Classifier) m_Classifier.getClass().newInstance());
    } catch (InstantiationException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    }

    setOptionsWereValid(true);
    setErrorText(null);
    updateDescriptionText();

    try {
      ((OptionHandler)testClassifier).setOptions(((OptionHandler)m_Classifier).getOptions());
    } catch (Exception e) {

      setOptionsWereValid(false);
      setErrorText(e.getMessage());
    }

    updateDescriptionText();
  }

  /**
   * Returns the base classifier this library model represents.
   *
   * @return		the base classifier
   */
  public Classifier getClassifier() {
    return m_Classifier;
  }

  /**
   * getter for the string representation
   *
   * @return		the string representation
   */
  public String getStringRepresentation() {
    return m_StringRepresentation;
  }

  /**
   * setter for the description text
   *
   * @param descriptionText	the description
   */
  public void setDescriptionText(String descriptionText) {
    m_DescriptionText = descriptionText;
  }

  /**
   * getter for the string representation
   *
   * @return		the description
   */
  public String getDescriptionText() {
    return m_DescriptionText;
  }

  /**
   * setter for the error text
   *
   * @param errorText	the error text
   */
  public void setErrorText(String errorText) {
    this.m_ErrorText = errorText;
  }

  /**
   * getter for the error text
   *
   * @return		the error text
   */
  public String getErrorText() {
    return m_ErrorText;
  }

  /**
   * setter for the optionsWereValid member variable
   *
   * @param optionsWereValid	if true, the options were valid
   */
  public void setOptionsWereValid(boolean optionsWereValid) {
    this.m_OptionsWereValid = optionsWereValid;
  }

  /**
   * getter for the optionsWereValid member variable
   *
   * @return		true if the options were valid
   */
  public boolean getOptionsWereValid() {
    return m_OptionsWereValid;
  }

  /**
   * This method converts the current set of arguments and the
   * class name to a string value representing the command line
   * invocation
   *
   * @return		a string representation of classname and options
   */
  public String toString() {

    String str = m_Classifier.getClass().getName();

    str += " " + Utils.joinOptions(((OptionHandler) m_Classifier).getOptions());

    return str;

  }

  /**
   * getter for the modelClass
   *
   * @return		the model class
   */
  public Class getModelClass() {
    return m_Classifier.getClass();
  }

  /**
   * getter for the classifier options
   *
   * @return		the classifier options
   */
  public String[] getOptions() {
    return ((OptionHandler)m_Classifier).getOptions();
  }

  /**
   * Custom serialization method
   *
   * @param stream		the stream to write the object to
   * @throws IOException	if something goes wrong IO-wise
   */
  private void writeObject(ObjectOutputStream stream) throws IOException {
    //stream.defaultWriteObject();
    //stream.writeObject(b);

    //serialize the LibraryModel fields

    stream.writeObject(m_Classifier);

    stream.writeObject(m_DescriptionText);

    stream.writeObject(m_ErrorText);

    stream.writeObject(new Boolean(m_OptionsWereValid));

    stream.writeObject(m_StringRepresentation);
  }

  /**
   * Custom serialization method
   *
   * @param stream			the stream to write to
   * @throws IOException		if something goes wrong IO-wise
   * @throws ClassNotFoundException	if class couldn't be found
   */
  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {

    m_Classifier = (Classifier) stream.readObject();

    m_DescriptionText = (String) stream.readObject();

    m_ErrorText = (String) stream.readObject();

    m_OptionsWereValid = ((Boolean) stream.readObject()).booleanValue();

    m_StringRepresentation = (String) stream.readObject();
  }

  /**
   * This method loops through all of the properties of a classifier to
   * build the html toolTipText that will show all of the property values
   * for this model.
   *
   * Note that this code is copied+adapted from the PropertySheetPanel
   * class.
   */
  public void updateDescriptionText() {

    String toolTipText = new String("<html>");

    if (!m_OptionsWereValid) {

      toolTipText += "<font COLOR=\"#FF0000\">"
        + "<b>Invalid Model:</b><br>" + m_ErrorText + "<br></font>";
    }

    toolTipText += "<TABLE>";

    PropertyDescriptor properties[] = null;
    MethodDescriptor methods[] = null;

    try {
      BeanInfo bi = Introspector.getBeanInfo(m_Classifier.getClass());
      properties = bi.getPropertyDescriptors();
      methods = bi.getMethodDescriptors();
    } catch (IntrospectionException ex) {
      System.err.println("LibraryModel: Couldn't introspect");
      return;
    }

    for (int i = 0; i < properties.length; i++) {

      if (properties[i].isHidden() || properties[i].isExpert()) {
        continue;
      }

      String name = properties[i].getDisplayName();
      Class type = properties[i].getPropertyType();
      Method getter = properties[i].getReadMethod();
      Method setter = properties[i].getWriteMethod();

      // Only display read/write properties.
      if (getter == null || setter == null) {
        continue;
      }

      try {
        Object args[] = {};
        Object value = getter.invoke(m_Classifier, args);

        PropertyEditor editor = null;
        Class pec = properties[i].getPropertyEditorClass();
        if (pec != null) {
          try {
            editor = (PropertyEditor) pec.newInstance();
          } catch (Exception ex) {
            // Drop through.
          }
        }
        if (editor == null) {
          editor = PropertyEditorManager.findEditor(type);
        }
        //editors[i] = editor;

        // If we can't edit this component, skip it.
        if (editor == null) {
          continue;
        }
        if (editor instanceof GenericObjectEditor) {
          ((GenericObjectEditor) editor).setClassType(type);
        }

        // Don't try to set null values:
        if (value == null) {
          continue;
        }

        toolTipText += "<TR><TD>" + name + "</TD><TD>"
          + value.toString() + "</TD></TR>";

      } catch (InvocationTargetException ex) {
        System.err.println("Skipping property " + name
            + " ; exception on target: " + ex.getTargetException());
        ex.getTargetException().printStackTrace();
        continue;
      } catch (Exception ex) {
        System.err.println("Skipping property " + name
            + " ; exception: " + ex);
        ex.printStackTrace();
        continue;
      }
    }

    toolTipText += "</TABLE>";
    toolTipText += "</html>";
    m_DescriptionText = toolTipText;
  }

  /**
   * Returns the revision string.
   *
   * @return		the revision
   */
  public String getRevision() {
    return RevisionUtils.extract("$Revision: 6041 $");
  }
}
