/*
 *   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 3 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, see <http://www.gnu.org/licenses/>.
 */

/*
 * SerializedClassifier.java
 * Copyright (C) 2007-2012 University of Waikato, Hamilton, New Zealand
 */

package weka.classifiers.misc;

import java.io.File;
import java.util.Enumeration;
import java.util.Vector;

import weka.classifiers.AbstractClassifier;
import weka.classifiers.Classifier;
import weka.core.Capabilities;
import weka.core.Capabilities.Capability;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Option;
import weka.core.RevisionUtils;
import weka.core.SerializationHelper;
import weka.core.Utils;

/**
 <!-- globalinfo-start -->
 * A wrapper around a serialized classifier model. This classifier loads a serialized models and uses it to make predictions.<br/>
 * <br/>
 * Warning: since the serialized model doesn't get changed, cross-validation cannot bet used with this classifier.
 * <p/>
 <!-- globalinfo-end -->
 * 
 <!-- options-start -->
 * Valid options are: <p/>
 * 
 * <pre> -D
 *  If set, classifier is run in debug mode and
 *  may output additional info to the console</pre>
 * 
 * <pre> -model &lt;filename&gt;
 *  The file containing the serialized model.
 *  (required)</pre>
 * 
 <!-- options-end -->
 *
 * @author  fracpete (fracpete at waikato dot ac dot nz)
 * @version $Revision: 8034 $
 */
public class SerializedClassifier
  extends AbstractClassifier {

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

  /** the serialized classifier model used for making predictions */
  protected transient Classifier m_Model = null;
  
  /** the file where the serialized model is stored */
  protected File m_ModelFile = new File(System.getProperty("user.dir"));
  
  /**
   * Returns a string describing classifier
   * 
   * @return 		a description suitable for displaying in the
   *         		explorer/experimenter gui
   */
  public String globalInfo() {
    return 
        "A wrapper around a serialized classifier model. This classifier loads "
      + "a serialized models and uses it to make predictions.\n\n"
      + "Warning: since the serialized model doesn't get changed, cross-validation "
      + "cannot bet used with this classifier.";
  }

  /**
   * Gets an enumeration describing the available options.
   *
   * @return an enumeration of all the available options.
   */
  public Enumeration listOptions(){
    Vector        	result;
    Enumeration   	enm;

    result = new Vector();

    enm = super.listOptions();
    while (enm.hasMoreElements())
      result.addElement(enm.nextElement());

    result.addElement(new Option(
	"\tThe file containing the serialized model.\n"
	+ "\t(required)",
	"model", 1, "-model <filename>"));

    return result.elements();
  }
  
  /**
   * returns the options of the current setup
   *
   * @return		the current options
   */
  public String[] getOptions(){
    int       	i;
    Vector    	result;
    String[]  	options;

    result = new Vector();

    options = super.getOptions();
    for (i = 0; i < options.length; i++)
      result.add(options[i]);

    result.add("-model");
    result.add("" + getModelFile());

    return (String[]) result.toArray(new String[result.size()]);	  
  }

  /**
   * Parses the options for this object. <p/>
   *
   <!-- options-start -->
   * Valid options are: <p/>
   * 
   * <pre> -D
   *  If set, classifier is run in debug mode and
   *  may output additional info to the console</pre>
   * 
   * <pre> -model &lt;filename&gt;
   *  The file containing the serialized model.
   *  (required)</pre>
   * 
   <!-- options-end -->
   *
   * @param options	the options to use
   * @throws Exception	if setting of options fails
   */
  public void setOptions(String[] options) throws Exception {
    String	tmpStr;
    
    super.setOptions(options);
    
    tmpStr = Utils.getOption("model", options);
    if (tmpStr.length() != 0)
      setModelFile(new File(tmpStr));
    else
      setModelFile(new File(System.getProperty("user.dir")));
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return 		tip text for this property suitable for
   * 			displaying in the explorer/experimenter gui
   */
  public String modelFileTipText() {
    return "The serialized classifier model to use for predictions.";
  }

  /**
   * Gets the file containing the serialized model.
   *
   * @return 		the file.
   */
  public File getModelFile() {
    return m_ModelFile;
  }
  
  /**
   * Sets the file containing the serialized model.
   *
   * @param value 	the file.
   */
  public void setModelFile(File value) {
    m_ModelFile = value;
    
    if (value.exists() && value.isFile()) {
      try {
	initModel();
      }
      catch (Exception e) {
	throw new IllegalArgumentException("Cannot load model from file '" + value + "': " + e);
      }
    }
  }

  /**
   * Sets the fully built model to use, if one doesn't want to load a model
   * from a file or already deserialized a model from somewhere else.
   * 
   * @param value	the built model
   * @see		#getCurrentModel()
   */
  public void setModel(Classifier value) {
    m_Model = value;
  }
  
  /**
   * Gets the currently loaded model (can be null). Call buildClassifier method
   * to load model from file.
   * 
   * @return		the current model
   * @see		#setModel(Classifier)
   */
  public Classifier getCurrentModel() {
    return m_Model;
  }
  
  /**
   * loads the serialized model if necessary, throws an Exception if the
   * derserialization fails.
   * 
   * @throws Exception	if deserialization fails
   */
  protected void initModel() throws Exception {
    if (m_Model == null)
      m_Model = (Classifier) SerializationHelper.read(m_ModelFile.getAbsolutePath());
  }

  /**
   * Returns default capabilities of the base classifier.
   *
   * @return      the capabilities of the base classifier
   */
  public Capabilities getCapabilities() {
    Capabilities        result;

    // init model if necessary
    if (m_ModelFile != null && m_ModelFile.exists() && m_ModelFile.isFile()) {
      try {
        initModel();
      }
      catch (Exception e) {
        System.err.println(e);
      }
    }

    if (m_Model != null) {
      result = m_Model.getCapabilities();
    } else {
      result = new Capabilities(this);
      result.disableAll();
    }
    
    // set dependencies
    for (Capability cap: Capability.values())
      result.enableDependency(cap);
    
    result.setOwner(this);
    
    return result;
  }
  
  /**
   * Calculates the class membership probabilities for the given test
   * instance.
   *
   * @param instance the instance to be classified
   * @return preedicted class probability distribution
   * @throws Exception if distribution can't be computed successfully 
   */
  public double[] distributionForInstance(Instance instance) throws Exception {
    double[]	result;

    // init model if necessary
    initModel();
    
    result = m_Model.distributionForInstance(instance);
    
    return result;
  }
  
  /**
   * loads only the serialized classifier
   * 
   * @param data        the training instances
   * @throws Exception  if something goes wrong
   */
  public void buildClassifier(Instances data) throws Exception {
    // init model if necessary
    initModel();

    // can classifier handle the data?
    getCapabilities().testWithFail(data);
  }

  /**
   * Returns a string representation of the classifier
   * 
   * @return		the string representation of the classifier
   */
  public String toString() {
    StringBuffer	result;
    
    if (m_Model == null) {
      result = new StringBuffer("No model loaded yet.");
    }
    else {
      result = new StringBuffer();
      result.append("SerializedClassifier\n");
      result.append("====================\n\n");
      result.append("File: " + getModelFile() + "\n\n");
      result.append(m_Model.toString());
    }
    
    return result.toString();
  }
  
  /**
   * Returns the revision string.
   * 
   * @return		the revision
   */
  public String getRevision() {
    return RevisionUtils.extract("$Revision: 8034 $");
  }
  
  /**
   * Runs the classifier with the given options
   * 
   * @param args	the commandline options
   */
  public static void main(String[] args) {
    runClassifier(new SerializedClassifier(), args);
  }
}
