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

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

package weka.core;

import java.util.Enumeration;
import java.util.Vector;

/**
 * Simple command line checking of classes that implement OptionHandler.<p/>
 *
 * Usage: <p/>
 * <code>
 *     CheckOptionHandler -W optionHandlerClassName -- test options
 * </code> <p/>
 *
 <!-- options-start -->
 * Valid options are: <p/>
 * 
 * <pre> -D
 *  Turn on debugging output.</pre>
 * 
 * <pre> -S
 *  Silent mode - prints nothing to stdout.</pre>
 * 
 * <pre> -W
 *  Full name of the OptionHandler analysed.
 *  eg: weka.classifiers.rules.ZeroR
 *  (default weka.classifiers.rules.ZeroR)</pre>
 * 
 * <pre> 
 * Options specific to option handler weka.classifiers.rules.ZeroR:
 * </pre>
 * 
 * <pre> -D
 *  If set, classifier is run in debug mode and
 *  may output additional info to the console</pre>
 * 
 <!-- options-end -->
 *
 * Options after -- are used as user options in testing the
 * OptionHandler
 *
 * @author Len Trigg (trigg@cs.waikato.ac.nz)
 * @author FracPete (fracpete at waikato dot ac dot nz)
 * @version $Revision: 8034 $
 */
public class CheckOptionHandler
  extends Check {

  /** the optionhandler to test */
  protected OptionHandler m_OptionHandler = new weka.classifiers.rules.ZeroR();

  /** the user-supplied options */
  protected String[] m_UserOptions = new String[0];
  
  /** whether the tests were successful */
  protected boolean m_Success;
  
  /**
   * Returns an enumeration describing the available options.
   *
   * @return an enumeration of all the available options.
   */
  public Enumeration listOptions() {
    Vector<Option> result = new Vector<Option>();
    
    Enumeration en = super.listOptions();
    while (en.hasMoreElements())
      result.addElement((Option)en.nextElement());
    
    result.addElement(new Option(
        "\tFull name of the OptionHandler analysed.\n"
        +"\teg: weka.classifiers.rules.ZeroR\n"
        + "\t(default weka.classifiers.rules.ZeroR)",
        "W", 1, "-W"));
    
    if (m_OptionHandler != null) {
      result.addElement(new Option(
	  "", "", 0, 
	  "\nOptions specific to option handler " 
	  + m_OptionHandler.getClass().getName() + ":"));
      
      Enumeration enm = m_OptionHandler.listOptions();
      while (enm.hasMoreElements())
        result.addElement((Option)enm.nextElement());
    }
    
    return result.elements();
  }
  
  /**
   * Parses a given list of options. <p/>
   *
   <!-- options-start -->
   * Valid options are: <p/>
   * 
   * <pre> -D
   *  Turn on debugging output.</pre>
   * 
   * <pre> -S
   *  Silent mode - prints nothing to stdout.</pre>
   * 
   * <pre> -W
   *  Full name of the OptionHandler analysed.
   *  eg: weka.classifiers.rules.ZeroR
   *  (default weka.classifiers.rules.ZeroR)</pre>
   * 
   * <pre> 
   * Options specific to option handler weka.classifiers.rules.ZeroR:
   * </pre>
   * 
   * <pre> -D
   *  If set, classifier is run in debug mode and
   *  may output additional info to the console</pre>
   * 
   <!-- options-end -->
   *
   * @param options the list of options as an array of strings
   * @throws Exception if an option is not supported
   */
  public void setOptions(String[] options) throws Exception {
    String      tmpStr;
    
    super.setOptions(options);
    
    tmpStr = Utils.getOption('W', options);
    if (tmpStr.length() == 0)
      tmpStr = weka.classifiers.rules.ZeroR.class.getName();
    setUserOptions(Utils.partitionOptions(options));
    setOptionHandler(
	(OptionHandler) Utils.forName(
	    OptionHandler.class, tmpStr, null));
  }
  
  /**
   * Gets the current settings of the CheckClassifier.
   *
   * @return an array of strings suitable for passing to setOptions
   */
  public String[] getOptions() {
    Vector<String>        result;
    String[]      options;
    int           i;
    
    result = new Vector<String>();
    
    options = super.getOptions();
    for (i = 0; i < options.length; i++)
      result.add(options[i]);
    
    if (getOptionHandler() != null) {
      result.add("-W");
      result.add(getOptionHandler().getClass().getName());
    }
    
    if (m_OptionHandler != null) {
      options = m_OptionHandler.getOptions();
      result.add("--");
      for (i = 0; i < options.length; i++)
        result.add(options[i]);
    }
    
    return (String[]) result.toArray(new String[result.size()]);
  }
  
  /**
   * Set the OptionHandler to work on.. 
   *
   * @param value the OptionHandler to use.
   */
  public void setOptionHandler(OptionHandler value) {
    m_OptionHandler = value;
  }
  
  /**
   * Get the OptionHandler used in the tests.
   *
   * @return the OptionHandler used in the tests.
   */
  public OptionHandler getOptionHandler() {
    return m_OptionHandler;
  }

  /**
   * Sets the user-supplied options (creates a copy)
   * 
   * @param value	the user-supplied options to use
   */
  public void setUserOptions(String[] value) {
    m_UserOptions = getCopy(value);
  }
  
  /**
   * Gets the current user-supplied options (creates a copy)
   * 
   * @return		the user-supplied options
   */
  public String[] getUserOptions() {
    return getCopy(m_UserOptions);
  }
  
  /**
   * returns the success of the tests
   * 
   * @return		true if the tests were successful
   */
  public boolean getSuccess() {
    return m_Success;
  }
  
  /**
   * Prints the given options to a string.
   *
   * @param options the options to be joined
   * @return the options as one long string
   */
  protected String printOptions(String[] options) {
    if (options == null) {
      return("<null>");
    } else {
      return Utils.joinOptions(options);
    }
  }

  /**
   * Compares the two given sets of options.
   *
   * @param options1 the first set of options
   * @param options2 the second set of options
   * @throws Exception if the two sets of options differ
   */
  protected void compareOptions(String[] options1, String[] options2) 
    throws Exception {

    if (options1 == null) {
      throw new Exception("first set of options is null!");
    }
    if (options2 == null) {
      throw new Exception("second set of options is null!");
    }
    if (options1.length != options2.length) {
      throw new Exception("problem found!\n"
			    + "First set: " + printOptions(options1) + '\n'
			    + "Second set: " + printOptions(options2) + '\n'
			    + "options differ in length");
    }
    for (int i = 0; i < options1.length; i++) {
      if (!options1[i].equals(options2[i])) {
	
	throw new Exception("problem found!\n"
			    + "\tFirst set: " + printOptions(options1) + '\n'
			    + "\tSecond set: " + printOptions(options2) + '\n'
			    + '\t' + options1[i] + " != " + options2[i]);
      }
    }
  }

  /**
   * creates a copy of the given options
   * 
   * @param options	the options to copy
   * @return		the copy
   */
  protected String[] getCopy(String[] options) {
    String[]	result;
    
    result = new String[options.length];
    System.arraycopy(options, 0, result, 0, options.length);
    
    return result;
  }
  
  /**
   * returns a new instance of the OptionHandler's class
   * 
   * @return		a new instance
   */
  protected OptionHandler getDefaultHandler() {
    OptionHandler	result;
    
    try {
      result = (OptionHandler) m_OptionHandler.getClass().newInstance();
    }
    catch (Exception e) {
      e.printStackTrace();
      result = null;
    }
    
    return result;
  }

  /**
   * returns the default options the default OptionHandler will return
   * 
   * @return		the default options
   */
  protected String[] getDefaultOptions() {
    String[]		result;
    OptionHandler	o;
    
    o = getDefaultHandler();
    if (o == null) {
      println("WARNING: couldn't create default handler, cannot use default options!");
      result = new String[0];
    }
    else {
      result = o.getOptions();
    }
    
    return result;
  }
  
  /**
   * checks whether the listOptions method works
   * 
   * @return index 0 is true if the test was passed, index 1 is always false
   */
  public boolean checkListOptions() {
    boolean	result;
    
    print("ListOptions...");
    
    try {
      Enumeration enu = getOptionHandler().listOptions();
      if (getDebug() && enu.hasMoreElements())
	println("");
      while (enu.hasMoreElements()) {
	Option option = (Option) enu.nextElement();
	if (getDebug()) {
	  println(option.synopsis());
	  println(option.description());
	}
      }

      println("yes");
      result = true;
    }
    catch (Exception e) {
      println("no");
      result = false;

      if (getDebug())
	println(e);
    }
    
    return result;
  }
  
  /**
   * checks whether the user-supplied options can be processed at all
   * 
   * @return index 0 is true if the test was passed, index 1 is always false
   */
  public boolean checkSetOptions() {
    boolean	result;
    
    print("SetOptions...");
    
    try {
      getDefaultHandler().setOptions(getUserOptions());
      println("yes");
      result = true;
    }
    catch (Exception e) {
      println("no");
      result = false;

      if (getDebug())
	println(e);
    }
    
    return result;
  }
  
  /**
   * checks whether the default options can be processed completely
   * or some invalid options are returned by the getOptions() method.
   * 
   * @return index 0 is true if the test was passed, index 1 is always false
   */
  public boolean checkDefaultOptions() {
    boolean	result;
    String[]	options;
    
    print("Default options...");

    options = getDefaultOptions();
    
    try {
      getDefaultHandler().setOptions(options);
      Utils.checkForRemainingOptions(options);
      println("yes");
      result = true;
    }
    catch (Exception e) {
      println("no");
      result = false;

      if (getDebug())
	println(e);
    }
    
    return result;
  }
  
  /**
   * checks whether the user-supplied options can be processed completely
   * or some "left-over" options remain
   * 
   * @return index 0 is true if the test was passed, index 1 is always false
   */
  public boolean checkRemainingOptions() {
    boolean	result;
    String[]	options;
    
    print("Remaining options...");

    options = getUserOptions();
    
    try {
      getDefaultHandler().setOptions(options);
      if (getDebug())
	println("\n  remaining: " + printOptions(options));
      println("yes");
      result = true;
    }
    catch (Exception e) {
      println("no");
      result = false;

      if (getDebug())
	println(e);
    }
    
    return result;
  }
  
  /**
   * checks whether the user-supplied options stay the same after settting,
   * getting and re-setting again
   * 
   * @return index 0 is true if the test was passed, index 1 is always false
   */
  public boolean checkCanonicalUserOptions() {
    boolean		result;
    OptionHandler	handler;
    String[] 		userOptions;
    String[] 		userOptionsCheck;
    
    print("Canonical user options...");

    try {
      handler = getDefaultHandler();
      handler.setOptions(getUserOptions());
      if (getDebug())
	print("\n  Getting canonical user options: ");
      userOptions = handler.getOptions();
      if (getDebug())
	println(printOptions(userOptions));
      if (getDebug())
	println("  Setting canonical user options");
      handler.setOptions((String[])userOptions.clone());
      if (getDebug())
	println("  Checking canonical user options");
      userOptionsCheck = handler.getOptions();
      compareOptions(userOptions, userOptionsCheck);

      println("yes");
      result = true;
    }
    catch (Exception e) {
      println("no");
      result = false;

      if (getDebug())
	println(e);
    }
    
    return result;
  }

  /**
   * checks whether the optionhandler can be re-setted again to default
   * options after the user-supplied options have been set.
   * 
   * @return index 0 is true if the test was passed, index 1 is always false
   */
  public boolean checkResettingOptions() {
    boolean		result;
    String[]		defaultOptions;
    String[] 		defaultOptionsCheck;
    OptionHandler	handler;

    print("Resetting options...");
    
    try {
      if (getDebug())
	println("\n  Setting user options");
      handler = getDefaultHandler();
      handler.setOptions(getUserOptions());
      defaultOptions = getDefaultOptions();
      if (getDebug())
	println("  Resetting to default options");
      handler.setOptions(getCopy(defaultOptions));
      if (getDebug())
	println("  Checking default options match previous default");
      defaultOptionsCheck = handler.getOptions();
      compareOptions(defaultOptions, defaultOptionsCheck);
      
      println("yes");
      result = true;
    }
    catch (Exception e) {
      println("no");
      result = false;

      if (getDebug())
	println(e);
    }
    
    return result;
  }
  
  /**
   * Runs some diagnostic tests on an optionhandler object. Output is
   * printed to System.out (if not silent).
   */
  public void doTests() {
    println("OptionHandler: " + m_OptionHandler.getClass().getName() + "\n");

    if (getDebug()) {
      println("--> Info");
      print("Default options: ");
      println(printOptions(getDefaultOptions()));
      print("User options: ");
      println(printOptions(getUserOptions()));
    }

    println("--> Tests");
    m_Success = checkListOptions();

    if (m_Success)
      m_Success = checkSetOptions();
   
    if (m_Success)
      m_Success = checkDefaultOptions();
    
    if (m_Success)
      m_Success = checkRemainingOptions();

    if (m_Success)
      m_Success = checkCanonicalUserOptions();

    if (m_Success)
      m_Success = checkResettingOptions();
  }
  
  /**
   * Returns the revision string.
   * 
   * @return		the revision
   */
  public String getRevision() {
    return RevisionUtils.extract("$Revision: 8034 $");
  }
  
  /** 
   * Main method for using the CheckOptionHandler.
   *
   * @param args 	the options to the CheckOptionHandler
   */
  public static void main(String[] args) {
    CheckOptionHandler check = new CheckOptionHandler();
    runCheck(check, args);
    if (check.getSuccess())
      System.exit(0);
    else
      System.exit(1);
  }
}
