package org.gregorie.runtest;

import java.util.*;
import java.io.*;

/**
 * <p><b>Custom</b> is a template subclassed to create a customised set of
 * class instantiatins and method calls to exercise the constructors and
 * methods in the the class under test. It contains public customisable
 * methods which are called by the <b>RunTest</b> class to exercise
 * functionality implemented by the class under test. These are:</p>
 * <dl>
 *  <dt>link()</dt>
 *      <dd>Never normally modified</dd>
 *  <dt>showHelp()</dt>
 *      <dd>Must supply help text specific to the class under test.</dd>
 *  <dt>initialiseTest()</dt>
 *      <dd>Called at the start of a test script to set up any
 *          data or preconditions required by the class under test.
 *          Must be modified to suit the classes and methods being
 *          tested.</dd>
 *  <dt>callAdapter()</dt>
 *      <dd>Called each time the test script requests a method
 *          in the class under test to be called or some other action
 *          to be carried out. Will me modified to suit the classes and
 *          methods being tested.</dd>
 *  <dt>finishTest</dt>
 *      <dd>Called at the end of a test script to tidy up before another
 *          script is run. Should be modified if tidying up after a run
 *          is needed by the classes amd methods being tested.</dd>
 * </dl>
 * <p>In addition a set of protected methods are defined that may be called
 *    from <i>callAdapter()</i> to perform commonly used actions.
 *    They are:</p>
 *    <ul>
 *      <li>collectArguments()</li>
 *      <li>defaultToSpace()</li>
 *      <li>strToDouble()</li>
 *      <li>trimQuotes()</li>
 *    </ul>
 *
 * <pre>
 *             Written by M.C.Gregorie.
 *
 *             Version history
 *             
 *             1.0   06/11/11   Initial release
 *             2.1   26/07/20   environ package dependencies removed
 * </pre>
 * <PRE>
 * <b>Licensing</b>
 * 
 * Java support classes
 * Copyright (C) 2020  Martin C Gregorie
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Lesser 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
 * Lesser GNU General Public License for more details.
 * 
 * You should have received a copy of the Lesser GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * </PRE>
 * <p>Contact Martin Gregorie at
 * <a href="mailto:martin@gregorie.org">martin@gregorie.org</a>.<br>
 * Read the <a href="http://www.gnu.org/licenses/lgpl.html">Lesser GNU
 * General Public License</a>.</p>
 */

public class Custom
{
   private static final String COPYRIGHT = "(c) Martin Gregorie, 2020"; 
   private static final String PROGNAME  = "Custom"; 
   private static final String VERSION   = "2.1";

   private RunTest      rt = null;
   private int          debug = 0;

   /**
    * <p><i>link()</i> accepts and stores a reference to the <b>RunTest</b>
    * instance and the tracing and diagnostic reporting level being used
    * by it.</p>
    *
    * <p>This is the first method called by <b>RunTest</b> and should not
    * normally be modified. If a class under test uses the
    * <b>ReportError</b> class for  debugging and/or reporting runtime
    * errors you must create a local <b>ReportError</b> instance for use
    * by the class(es) that require it and immediately call</p>
    *
    * <p><i>ReportError.setDebug(debug);</i></p>
    *
    * <p>to synchronise debug levels used in the <b>Custom</b> subclass with
    * the <b>RunTest</b> debug levels. This is needed because, in the
    * interest of portability, neither <b>RunTest</b> or <b>Custom</b> use
    * any classes that are not in the Java standard class library.
    * <b>ReportError</b> provides error reporting and process tracing
    * diagnostic methods used by classes in the <b>org.gregorie.environ</b>
    * support library as well as more generally to applications that use
    * this class library.</p>
    * 
    * @param rt retrieves a reference to the controlling RunTest instance
    *           and stores it in the local RunTest 'rt' variable to allow
    *           code in the Custom to report test results and errors
    *           to the RunTest instance for inclusion in the test run
    *           report.
    */ 
   public void link(RunTest rt)
   {
      this.rt = rt;
      debug = rt.getDebugLevel();
   }
   
   /**
    * <p><i>callAdapter()</i> is called to exercise the method or action
    * identified by <i>fn</i> using arguments supplied as <i>p</i>.
    * For convenience, their integer equivalents are supplied as <i>n</i>.
    * If the code that sets up arguments for a class or method test
    * references more arguments than were provided by the script line
    * an Exception will be thrown. This is caught by the code in
    * <b>RunTest</b> instance that called this <i>callAdapter()</i>
    * method. An error reporting this scripting error is output.</p>
    *
    * <p>Selection of the class or method to be tested is best implemented
    * as an if-then-else chain using <i>fn</i> as the selector, as is shown
    * in the <b>CustomTest</b> subclass. You can declare variables in the
    * method as needed but be aware that they are re-instantiated for each
    * action line in the script, so if you need script-wide data permanence
    * for the class under test, you must declare suitable class variables in
    * your subclass.</p>
    *
    * <p>Any setup that must be carried out before the script is run should be
    * placed in the <i>initialiseTest()</i> method and any cleanup or
    * post-script processing goes in the <i>finishTest()</i> method.</p> 
    *
    * <p>Results returned by a method call should be passed to the test
    * manager by calling <i>rt.setResult()</i> or <i>rt.setResultWithHex</i>.
    * This will cause a 'result' line to be output after the action line.
    * <i>setResult()</i> is overloaded to allow direct output of
    * boolean, char, int, double and String results. There is an additional
    * method, <i>setResultWithHex()</i>, which should be called if a String
    * result may contain non-printable characters: these are replaced by
    * two hex digits enclosed in angle brackets.</p>
    *
    * <p>If you need to annotate a test result, calling <i>rt.setNote()</i>
    * will add a 'note' line after the action line. It follows any
    * 'result' line in the test report.</p>
    *
    * <p>Errors returned by a method call should be passed to the test
    * manager by calling <i>rt.setError()</i>. This will cause an
    * 'Error:' line to be output after the action and 'result' lines
    * and will increment the error count. Error detection is dependent
    * on the class design, but will typically be via methods returning
    * true/false or null/not-null replies or by throwing exceptions.
    * The text of the error message may be extracted from the exception
    * or by calling the classes' <i>getError()</i> method if it has one:
    * it doesn't matter if this is done on a per-call basis or by setting
    * a 'success' flag (typically a local boolean variable) that causes
    * <i>getError()</i> to be called after all method-specific code and the
    * the if-then-else action selection chain has finished.</p>
    *
    * <p>If you need to pause the test for any reason, call <i>rt.wait(n)</i>
    * where 'n' is the number of seconds to delay.</p>
    *
    * <p><b>Note:</b> that using the <b>RunTest -n</b> option will cancel
    * this delay, so if the test requires that the delay must always occur,
    * you should implement a private delay method in your <i>Custom</i>
    * extension. A convenient way to do this is to use the
    * <b>org.gregorie.environ.Wait</b> class.</p>
    *
    * @param fn Name of the function to be tested.
    * @param p  List of function arguments as Strings
    * @param n  List of function arguments as integers
    */ 
   public void callAdapter(String fn,
                           ArrayList<String> p,
                           ArrayList<Integer> n)
   {      
      if (fn.equals("example"))
      {
         /*
            Do something whose name is 'example', e.g.
            
            Example ex = new Example(p.get(0));

            or

            rt.setResult(example.method(p.get(0), n.get(1));
         */
      }
      else
         rt.setError("Unknown action");
   }

   /**
    * This method carries out test-specific cleanup.
    * If it is not needed by the target set of tests, remove the
    * trace statement.
    */
   public void finishTest()
   {
      rt.trace(1, "finishTest() called");
   }
         
   /**
    * This method carries out test-specific initialisation
    * If it is not needed by the target set of tests, remove the
    * trace statement.
    */
   public void initialiseTest()
   {
      rt.trace(1, "initialiseTest() called");
   }
         
   /**
    * This method is run when the '-?' option is present.
    * It should be overridden by class-specific text in your extension.
    */
   public void showHelp()
   {
      rt.trace("\n" + PROGNAME + " - " + VERSION + " " + COPYRIGHT + "\n");
      rt.trace("Syntax  : java testprogs/RunTest " + PROGNAME +
               " [<opts>] script...");
      rt.trace("Function: " + PROGNAME + " provides the facilities");
      rt.trace("          needed to interface the class under test");
      rt.trace("          to the RunTest scripted test system.");
   }

   /**
    * <p>This is a convenience method for use in callAdapter().</p>
    *
    * <p>It concatenates the elements in <i>p</i>, working from
    * left to right until a zero length element is encountered
    * or all have been processed. A single space is used to
    * separate each argument from the next. As the resulting
    * string starts with a space, the workround is to
    * enclose the list of elements in double quotes:
    * "A quoted string" and wrap this method within trimQuotes():</p>
    *
    * <blockquote>trimQuotes(collectArguments(p))</blockquote>
    *
    * <p>which will return the required string without quotes
    * or the leading space.</p>
    *
    * @param p The list of function arguments as Strings
    * @return  The list as a String with single spaces between the arguments
    */
   protected String collectArguments(ArrayList<String> p)
   {
      String str = "";
      String w = null;
      for (int i = 0; i < p.size(); i++)
      {
         w = p.get(i);
         if (w.length() == 0)
            break;

         str = str + " " + w;
      }

      return str;
   }

   /**
    * <p>This is a convenience method for use in callAdapter().</p>
    *
    * <p>It returns a space if its supplied parameter is of zero length
    * or if the parameter is off the end of the ArrayList p.</p>
    * 
    * <p>Otherwise it returns the first character of its argument.</p>
    *
    * @param p The ArrayList containing the parameter to be examined.
    * @param i The index of the parameter.
    * @return  The first character or a space if the string is empty
    *          or non-existent. 
    */ 
   protected String defaultToSpace(ArrayList<String> p, int  i)
   {
      String c = new String(" ");
      
      if (i < p.size())
      {
         String s = p.get(i);
         if (s.length() > 0)
            c = s;
      }
         
      return (c);
   }

   /**
    * <p>This is a convenience method for use in callAdapter().</p>
    *
    * <p>It makes a double from String value.</p>
    *
    * @param s The String value
    * @return  the double equivalent of <i>value</i> or 0.0 if it
    *          didn't hold a valid double 
    */
   protected double strToDouble(String s)
   {
      double r;

      try
      {
         Double d = new Double(s);
         r = d.doubleValue();
      }
      catch (NumberFormatException e)
      {
         r = 0.0;
      }

      return r;
   }
                 
   /**
    * <p>This is a convenience method for use in callAdapter().</p>
    *
    * <p>It removes leading and trailing spaces in s. If the string
    * contains at least two double quotes, only the text between
    * the first and last double quotes is retained.</p>
    *
    * @param s The String to be trimmed
    * @return  The trimmed and dequoted String content
    */
   protected String trimQuotes(String s)
   { 
      String astr = s.trim();
      int j = astr.indexOf('"');
      int k = astr.lastIndexOf('"');
      if (j >= 0 && k >= 0 && j < k)
      {
         astr = astr.substring(0, j) + astr.substring(j + 1);
         k = astr.lastIndexOf('"');
         astr = astr.substring(0, k) + astr.substring(k + 1);
      }

      return astr;
   }
}
