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

/*
 * Copyright (C) 2002 University of Waikato 
 */

package weka.test;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Properties;

/**
 * <code>Regression</code> provides support for performing regression
 * testing.
 *
 * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
 * @version $Revision: 1.4 $
 */
public class Regression {

  /** Reference files have this extension. */
  private static final String FILE_EXTENSION = ".ref";

  /**
   * The name of the system property that can be used to override the
   * location of the reference root.
   */
  private static final String ROOT_PROPERTY = "weka.test.Regression.root";

  /** Default root location, relative to the users home direcory. */
  private static final String DEFAULT_ROOT = "wekarefs";

  /** Stores the root location under which reference files are stored. */
  private static File ROOT;

  /** Stores the output to be compared against the reference. */
  private StringBuffer m_Output;

  /** The file where the reference output is (or will be) located */
  private File m_RefFile;

  /**
   * Returns a <code>File</code> corresponding to the root of the
   * reference tree.
   *
   * @return a <code>File</code> giving the root of the reference tree. 
   */
  public static File getRoot() {

    if (ROOT == null) {
      String root = System.getProperty(ROOT_PROPERTY);
      if (root == null) {
        root = System.getProperty("user.dir");
        ROOT = new File(root, DEFAULT_ROOT);
      } else {
        ROOT = new File(root);
      }
    }
    return ROOT;
  }

  /**
   * Creates a new <code>Regression</code> instance for the supplied class.
   *
   * @param theClass a <code>Class</code> value. The name of the class is
   * used to determine the reference file to compare output with.
   */
  public Regression(Class theClass) {

    if (theClass == null) {
      throw new NullPointerException();
    }
    String relative = theClass.getName().replace('.', File.separatorChar) 
      + FILE_EXTENSION;
    m_RefFile = new File(getRoot(), relative);
    m_Output = new StringBuffer();
  }

  /**
   * Adds some output to the current regression output. The accumulated output
   * will provide the material for the regression comparison.
   *
   * @param output a <code>String</code> that forms part of the regression test.
   */
  public void println(String output) {

    m_Output.append(output).append('\n');
  }

  /**
   * Returns the difference between the current output and the reference
   * version.
   *
   * @return a <code>String</code> value illustrating the differences. If this
   * string is empty, there are no differences. If null is returned, there was
   * no reference output found, and the current output has been written as the
   * reference.
   * @exception IOException if an error occurs during reading or writing of
   * the reference file.
   */
  public String diff() throws IOException {

    try {
      String reference = readReference();
      return diff(reference, m_Output.toString());
    } catch (FileNotFoundException fnf) {
      // No, write out the current output
      writeAsReference();
      return null;
    }
  }

  /**
   * Returns the difference between two strings, Will be the empty string
   * if there are no difference.
   *
   * @param reference a <code>String</code> value
   * @param current a <code>String</code> value
   * @return a <code>String</code> value describing the differences between 
   * the two input strings. This will be the empty string if there are no
   * differences.
   */
  protected String diff(String reference, String current) {

    if (reference.equals(current)) {
      return "";
    } else {
      // Should do something more cunning here, like try to isolate the
      // actual differences. We could also try calling unix diff utility 
      // if it exists.
      StringBuffer diff = new StringBuffer();
      diff.append("+++ Reference: ").append(m_RefFile).append(" +++\n")
        .append(reference)
        .append("+++ Current +++\n")
        .append(current)
        .append("+++\n");
      return diff.toString();
    }
  }

  /**
   * Reads the reference output from a file and returns it as a String
   *
   * @return a <code>String</code> value containing the reference output
   * @exception IOException if an error occurs.
   */
  protected String readReference() throws IOException {

    Reader r = new BufferedReader(new FileReader(m_RefFile));
    StringBuffer ref = new StringBuffer();
    char [] buf = new char[5];
    for(int read = r.read(buf); read > 0; read = r.read(buf)) {
      ref.append(new String(buf, 0, read));
    }
    r.close();
    return ref.toString();    
  }
  
  /**
   * Writes the current output as the new reference. Normally this method is
   * called automatically if diff() is called with no existing reference
   * version. You may wish to call it explicitly if you know you want to 
   * create the reference version.
   *
   * @exception IOException if an error occurs.
   */
  public void writeAsReference() throws IOException {

    File parent = m_RefFile.getParentFile();
    if (!parent.exists()) {
      parent.mkdirs();
    }
    Writer w = new BufferedWriter(new FileWriter(m_RefFile));
    w.write(m_Output.toString());
    w.close();
  }

  /**
   * Tests Regression from the command line
   *
   * @param args a <code>String</code> array containing values to
   * send to println().
   */
  public static void main(String []args) {

    try {
      if (args.length == 0) {
        throw new Exception("Usage: Regression \"some text\"");
      }
      Properties props = System.getProperties();
      props.setProperty(ROOT_PROPERTY, props.getProperty("java.io.tmpdir"));

      Regression reg = new Regression(Regression.class);
      for (int i = 0; i < args.length; i++) {
        reg.println(args[i]);
      }
      String diff = reg.diff();
      if (diff == null) {
        System.err.println("Created reference output");
      } else if (diff.equals("")) {
        System.err.println("Passed");
      } else {
        System.err.println("Failed: " + diff);
      }
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}
