/*
 * $Id: TestSuite.java,v 1.12 1999/01/23 16:41:49 metlov Exp $
 *
 * This file is part of the Java Expressions Library (JEL).
 *   For more information about JEL visit :
 *    http://galaxy.fzu.cz/JEL/
 *
 * (c) 1998 by Konstantin Metlov(metlov@fzu.cz);
 *
 * JEL is Distributed under the terms of GNU General Public License.
 *    This code comes with ABSOLUTELY NO WARRANTY.
 *  For license details see COPYING file in this directory.
 */



import gnu.jel.*;
import gnu.jel.generated.EC;
import gnu.jel.generated.ParseException;
import gnu.jel.generated.TokenMgrError;
import java.io.PrintStream;

public class TestSuite {
  
  static int successes=0;
  static int failures=0;
  static boolean abort_at_error=false;// Set if You want to abort at a failing
                                      // test, works only for testExpression().
  
  public static void main(String[] args) {

    // Set UP Library
    Class[] staticLib=new Class[3];
    try {
      staticLib[0]=Class.forName("java.lang.Math");
      VariableProvider tvp=new VariableProvider();
      staticLib[1]=tvp.getClass();
      staticLib[2]=Class.forName("gnu.jel.StringLib");
    } catch(ClassNotFoundException e) {
      System.out.println(e);
      System.exit(1);
    };
    Library lib=new Library(staticLib,null);
    try {
    lib.markStateDependent("random",null);
    } catch (NoSuchMethodException e) {
      // Can't be also
    };

    PrintStream o=System.out;
    
    o.println(); o.println("----- TESTING ERROR REPORTING ------");
    o.println();

    testError("",null,lib,0,o);
    testError(" ",null,lib,1,o);
    testError("tru",null,lib,1,o);
    testError("1=",null,lib,2,o);
    testError("1=2",null,lib,3,o);
    testError("1.0-+1.0",null,lib,5,o);
    testError("1.0&1.0",null,lib,4,o);
    testError("-",null,lib,1,o);
    testError("0x56+0xXX",null,lib,7,o);
    testError("Sin(x)",null,lib,5,o);
    testError("Sin(6)",null,lib,1,o);
    testError("sin(' ')",null,lib,1,o);
    //    testError("'a'+'b'",null,lib,4,o); // <- now perfectly legal 
    testError("1+sin(1,6)",null,lib,3,o);
    testError("2147483649L+2147483649",null,lib,13,o);
    testError("01234567+08+5",null,lib,11,o);
    testError("0.5+0#4",null,lib,6,o);
    testError("0.5+1",Integer.TYPE,lib,5,o);
    testError("0.5+(floatp)0.4D",null,lib,6,o);
    testError("0.5+(boolean)0.4D",null,lib,6,o);
    testError("23?1:2",null,lib,3,o);
    testError("true?\" \":' '",null,lib,10,o);
    testError("true?\" \":makeDoubleObject(1.0)",null,lib,30,o);
    
    o.println(); o.println("----- TESTING STATIC OPTIMIZATIONS ------");
    o.println("lines starting with <number>| denote optimization passes.");
    o.println("Expression on each line is evaluated 20 times to provoke some JIT compilers.");
    o.println();
    
    testExpression("2*2",new Integer(4),null,null,lib,o);
    testExpression("2L*2L",new Long(4),null,null,lib,o);
    testExpression("2.0*2.0",new Double(4.0),null,null,lib,o);
    testExpression("2.0F*2.0F",new Float(4.0F),null,null,lib,o);
    testExpression("sin(1)",new Double(Math.sin(1.0)),null,null,lib,o);
    testExpression("pow(sin(1),2)+pow(cos(1),2)",new Double(1.0),null,null,
		   lib,o);
    testExpression("min(1+2*2,(1+2)*2)",new Integer(5),null,null,lib,o);
    testExpression("7+4-6",new Double(5.0),Double.TYPE,null,lib,o);
    testExpression("true&&false||false&&true||false",Boolean.FALSE,
		   null,null,lib,o);

    testExpression("(1<<2L==4)&&(-1>>5==-1)&&(-1>>>1==0x7FFFFFFF)",
		   Boolean.TRUE,null,null,lib,o);

    testExpression("(-1>>>1==0x5FFFFFFF)",
		   Boolean.FALSE,null,null,lib,o);

    testExpression("(-1L>>>1L==0x7FFFFFFFFFFFFFFFL)",
		   Boolean.TRUE,null,null,lib,o);

    testExpression("1+2>2==true",
		   Boolean.TRUE,null,null,lib,o);

    testExpression("true?false:true?true:false",
		   Boolean.FALSE,null,null,lib,o);

    testExpression("false?true:true?false:true",
		   Boolean.FALSE,null,null,lib,o);

    
    testExpression("(1==1)&&(max(~bool2int(1<=2),~bool2int(2>=3))!=-1)",
		   Boolean.FALSE,null,null,lib,o);

    testExpression("(-1==-1)&&(max(~(1<=2?1:0),~(2>=3?1:0))!=-1)",
		   Boolean.FALSE,null,null,lib,o);

    testExpression("!((!true)&&(!true))",
		   Boolean.TRUE,null,null,lib,o);

    testExpression("!(!(false&&false&&false)&&!false)",
		   Boolean.FALSE,null,null,lib,o);

    testExpression("(!(5+1>5)?1:2)==2",
		   Boolean.TRUE,null,null,lib,o);

    testFullLogic("!(!(_a&&_b&&_c)&&!_d)",4,lib,o,false);
    
    testFullLogic("_a&&_b&&_c||_d",4,lib,o,false);

    testFullLogic("_a&&(_b&&(_c||_d))",4,lib,o,false);

    testFullLogic("_a&&((_b&&_c)||_d)",4,lib,o,false);
    
    testFullLogic("(_a&&_b)||!(_c&&_d)||_e",5,lib,o,false);

    testFullLogic("_a&&((_b&&(!(_c&&_d)||_e))||_f)",6,lib,o,false);

    testFullLogic("_a&&(!(_b&&(!(_c&&_d)||_e))||!_f)",6,lib,o,false);

    testFullLogic("_a&(!(_b&(!(_c&_d)|_e))|!_f)",6,lib,o,false);

    testFullLogic("_a?_b||_c||_d:_e&&_f",6,lib,o,false);

    testFullLogic("(_a==_b)&&(max(~bool2int(1<=2&&_c||_d),~bool2int(2>=3&&_e||_f))!=-1)",6,lib,o,false);

    testFullLogic("_a?_b:_c?_d:_e",5,lib,o,false);

    testExpression("\"aa\"+\"bb\"+\"cc\"",
		   "aabbcc",null,null,lib,o);

    testExpression("\"str\"+true+1+20L+6.0F+7.0D",
		   "strtrue1206.07.0",null,null,lib,o);

    testExpression("\"str\"+(2+3)","str5",null,null,lib,o);

    testExpression("((~(~((((1+2-2)*2/2)^55)^55)))|1&2)==1",
		   Boolean.TRUE,null,null,lib,o);

    testExpression("((~(~((((1L+2L-2L)*2L/2L)^55L)^55L)))|1L&2L)==1L",
		   Boolean.TRUE,null,null,lib,o);

    testExpression("((10/3)*3+10%3)==10",
		   Boolean.TRUE,null,null,lib,o);

    testExpression("((10L/3)*3+10%3L)==10L",
		   Boolean.TRUE,null,null,lib,o);

    testExpression("((1>5?0:10F)/3)*3+10F%3",
		   new Float(11.0F),null,null,lib,o);

    testExpression("((1>5?0:10D)/3)*3+10D%3",
		   new Double(11.0),null,null,lib,o);

    testExpression("round(((10-5)==5?(6.0-((8==8)?5.0:1))*2.0:1.0+"+
		   "2.0-1.0)/2.0)==1",
		   Boolean.TRUE,null,null,lib,o);


    // Tests if NaN (floating point "not a number") handling conforms to JLS
    testExpression("(1>NaNd)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1<NaNd)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1>=NaNd)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1<=NaNd)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1==NaNd)",Boolean.FALSE,null,null,lib,o);
    testExpression("!(1!=NaNd)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNd>1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNd<1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNd>=1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNd<=1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNd==1)",Boolean.FALSE,null,null,lib,o);
    testExpression("!(NaNd!=1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1>NaNf)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1<NaNf)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1>=NaNf)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1<=NaNf)",Boolean.FALSE,null,null,lib,o);
    testExpression("(1==NaNf)",Boolean.FALSE,null,null,lib,o);
    testExpression("!(1!=NaNf)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNf>1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNf<1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNf>=1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNf<=1)",Boolean.FALSE,null,null,lib,o);
    testExpression("(NaNf==1)",Boolean.FALSE,null,null,lib,o);
    testExpression("!(NaNf!=1)",Boolean.FALSE,null,null,lib,o);

    // In fact, next four tests are equivalent to the above ones,
    // but there was a problem with them and to solve it I had
    // to separate tests (making the above thing). I decided not
    // to remove the above since ... well... it makes testsuite runs
    // more impressive ;)))
    testExpression("(NaNd>1)||(NaNd<1)||(NaNd>=1)||(NaNd<=1)||"+
		   "(NaNd==1)||!(NaNd!=1)",
		   Boolean.FALSE,null,null,lib,o);

    testExpression("(1>NaNd)||(1<NaNd)||(1>=NaNd)||(1<=NaNd)||"+
		   "(1==NaNd)||!(1!=NaNd)",
		   Boolean.FALSE,null,null,lib,o);


    testExpression("(NaNf>1)||(NaNf<1)||(NaNf>=1)||(NaNf<=1)||"+
		   "(NaNf==1)||!(NaNf!=1)",
		   Boolean.FALSE,null,null,lib,o);

    testExpression("(1>NaNf)||(1<NaNf)||(1>=NaNf)||(1<=NaNf)||"+
		   "(1==NaNf)||!(1!=NaNf)",
		   Boolean.FALSE,null,null,lib,o);
    
    testExpression("indexOf(\"aaaca\",'c')+1==4 && "+
		   "indexOf(1>2?\"cc\":\"aaaca\",'c')+1==4",
		   Boolean.TRUE,null,null,lib,o);

    testExpression("round((float)4)",
		   new Integer(4),null,null,lib,o);

    testExpression("(int)4.0",
		   new Integer(4),null,null,lib,o);

    
    testExpression("7+(int)4-(int)6.0+1-(int)round((double)((float)((long)1+0)+0)+0)",
		   new Integer(5),null,null,lib,o);
    
    testExpression("~0",new Integer(-1),null,null,lib,o);
		   
    testExpression("~0",new Integer(-1),null,null,lib,o);

    testExpression("substring(\"abbb\"+\"ccc\"+'d'+(1>2?\"e\":\"d\"),1)",
		   "bbbcccdd",null,null,lib,o);

    testExpression("equals(substring(\"abbb\",1),\"bbb\")||false",
		   Boolean.TRUE,null,null,lib,o);

    o.println(); 
    o.println("----- TESTING DYNAMIC OPTIMIZATIONS (x=5.0D) ------");
    o.println();

    // Construct a new library
    Class[] dynamicLib=new Class[1];
    Object[] rtp=new Object[1];
    VariableProvider vp=new VariableProvider();
    Class oldmath=staticLib[0];
    staticLib=new Class[2];
    staticLib[0]=oldmath;
    // next line makes also static functions from VariablePrivider available
    staticLib[1]=vp.getClass();  
    
    vp.x=5.0;
    rtp[0]=vp;
    dynamicLib[0]=vp.getClass();
    lib=new Library(staticLib,dynamicLib);
    
    testExpression("sin(x/5)",new Double(Math.sin(1.0)),null,rtp,lib,o);
    testExpression("255+5+7+9-x",new Double(255+7+9),null,rtp,lib,o);
    testExpression("-x+255+5+7+9",new Double(255+7+9),null,rtp,lib,o);
    testExpression("-x+(255+5+7+9)",new Double(255+7+9),null,rtp,lib,o);
    testExpression("5*x-66",new Double(25-66),Double.TYPE,rtp,lib,o);
    testExpression("7+(int)4-(int)6.0+(int)x-(int)round((double)((float)((long)x+1)+2)+3)+6",
		   new Integer(5),null,rtp,lib,o);

    
    o.println(); o.println("----- TESTING COMPILED-in EXCEPTIONS (x=5)------");
    o.println();
    
    testExpression("(1+6)/(2+2-4)",null,null,rtp,lib,o);
    testExpression("throw_arg_eq_4(6-2)",null,null,rtp,lib,o);
    
    o.println(); o.println("^^^^^^^^^^^^ SCORE success/failure = "+
			   successes+"/"+failures+
			   " ^^^^^^^^^^^^" );

  };

  // Tests evaluation of logical expressions
  // The input is an expression of the form "a&b|c&d"
  // where there are n<=32 free parameters _a,_b,_c,_d (starting from a_ in 
  // alphabetical order) the expression should involve
  // only stateless functions otherwise this test has no sense.
  //
  // This function will evaluate the expression 2^n times for all possible
  // combinations of parameters and compare results of "interpreted" vs 
  // "compiled" evaluation. If in all 2^n cases results will coincide the test
  // is marked as PASSED.
  // This function does not analyze syntax of expression so be sure not to have
  // underscores ("_") in the names of functions.
  private static void testFullLogic(String expr,int bits,Library lib, 
				    PrintStream o,boolean showcases) {
    int cases=1<<bits;
    o.print("*** : FULL LOGIC TEST \""); o.print(expr); 
    o.println("\" . ( "+cases+" cases ).");    
    
    boolean vars[]=new boolean[bits];
    boolean testOK=true;
    for (int ccase=0;((ccase<cases)&&testOK);ccase++) {
      for(int i=0;i<bits;i++) vars[i]=((ccase>>>i & 0x00000001)>0?true:false);
      StringBuffer cexpr=new StringBuffer();
      for (int i=0;i<expr.length();i++) {
	char currchar=expr.charAt(i);
	if (currchar=='_') {
	  currchar=expr.charAt(++i);
	  int varnum=currchar-'a';
	  if (vars[varnum]) cexpr.append("true "); else  cexpr.append("false");
	} else cexpr.append(currchar);
      };

      // Now we need to calculate cexpr
      
      // First parse it
      OpenOptimizer op=null;
      try {
	op=fillOptimizer(cexpr.toString(),lib,null);
      } catch (CompilationException ce) {
	o.print("--- COMPILATION ERROR :");
	o.println(ce.getMessage());
	o.print("                       ");
	o.println(cexpr.toString());
	int column=ce.getColumn(); // Column, where error was found
	for(int i=0;i<column+23-1;i++) System.err.print(' ');
	o.println('^');
	o.println("Unexpected syntax error on supposingly correct"+
		  " expression.");
	FAIL(o);
	return;
      };
      
      // Make optimization iterations
      Object result=null;      
      boolean optimized=true;
      int iterations=0;
      while(optimized) {
	Object result1=null;      
	try {
	  CompiledExpression expr_c=op.compile();

	  // Execute several times to enable JIT compilation.
	  // Some JITs compile methods if they are run more than once
	  for(int acounter=0;acounter<20;acounter++) {
	    result1=expr_c.evaluate(null);
	  };
	} catch (Throwable e) {
	  o.println(cexpr.toString());
	  o.println("Exception emerged during compilation/evaluation.");
	  o.print("      ");e.printStackTrace();
	  FAIL(o);
	  return;
	};
	
	if (result!=null) {
	  if (!result.equals(result1)) {
	    o.println(cexpr.toString());
	    o.println("Expression gave inconsistent result "+result1+
		      " ( previous result was "+result+" ).");
	    o.println(op.toString());
	    FAIL(o);
	    return;
	  };
	};
	result=result1;

	optimized=op.optimizeIteration();
	iterations++;
      };
      
      if (iterations<=1) 
	o.println("WARNING : only one optimizing iteration was performed.");

      if (showcases) {
	o.print(cexpr.toString()); 
	o.print(" == "); 
	o.println(result.toString());
      };
    };
    OK(o);
  };

  private static void testExpression(String expr, Object tobe, Class fixType,
				     Object[] runtimeParameters,
				     Library lib,   PrintStream o ) {
    o.print("*** : \""); o.print(expr); 
    if (tobe != null) {
      o.print("\" = "); o.println(tobe);
    } else {
      o.println("\"   Should throw an exception at run time.");
    };
    OpenOptimizer op=null;
    try {
      op=fillOptimizer(expr,lib,fixType);
    } catch (CompilationException ce) {
      o.print("--- COMPILATION ERROR :");
      o.println(ce.getMessage());
      o.print("                       ");
      o.println(expr);
      int column=ce.getColumn(); // Column, where error was found
      for(int i=0;i<column+23-1;i++) System.err.print(' ');
      o.println('^');
      o.println("Unexpected syntax error on supposingly correct expression.");
      FAIL(o);
      return;
    };
    
    boolean testok=true;
    
    boolean optimized=true;
    int iteration=0;
    while(optimized) {
      String message=""+iteration+" |"+op.toString();
      o.print(message);
      for (int k=message.length();k<59;k++) o.print(' ');

      Object result=null;
      try {
	CompiledExpression expr_c=op.compile();
	
	// Execute several times to enable JIT compilation.
	// Some JITs compile methods if they are run more than once
	for(int acounter=0;acounter<20;acounter++) {
	  result=expr_c.evaluate(runtimeParameters);
	};
	
      } catch (Throwable e) {
	if (tobe==null) {
	  o.println("EXPECTED EXCEPTION.");
	  o.print("      ");o.println(e.getMessage());
	} else {
	  o.println("Exception emerged during compilation/evaluation.");
	  o.print("      ");e.printStackTrace();
	};
	testok=(tobe==null);
      };
      
      if (tobe!=null) {
	if (result!=null) {
	  o.print(" ="); o.println(result);
	  testok=testok && (result.equals(tobe));
	  if (!result.equals(tobe) && (abort_at_error)) System.exit(1);
	} else {
	  o.println("NO RESULT");
	  testok=false;
	};
      } else {
	testok=(result==null);
	if (result!=null) o.println(" ="+result.toString());
      };
      
      optimized=op.optimizeIteration();
      iteration++;
    };
    
    if (testok) OK(o); else FAIL(o);
    
  };
  
  private static void testError(String expr,Class fixType, Library lib, 
				int errcol, PrintStream o) {
    o.print("*** : \""); o.print(expr); o.println('"');

    CompilationException ce=null;
    try {
      OpenOptimizer op=fillOptimizer(expr,lib,fixType);
    } catch (CompilationException e) {
      ce=e;
    };
    if (ce==null) {
      o.println("No error detected."); 
      FAIL(o);
    } else {
      o.print("       ");
      int column=ce.getColumn(); // Column, where error was found
      for(int i=0;i<column-1;i++) o.print(' ');
      o.println('^');
      
      o.print("MESSAGE: "); o.println(ce.getMessage());
      
      if (ce.getColumn()!=errcol) {
	o.print("Error detected at column "); o.print(ce.getColumn());
	o.print(" while it should be at "); o.print(errcol); o.println(" .");
	FAIL(o);
      } else OK(o);

    };

  };

  private static void FAIL(PrintStream o) {
    failures++;
    o.println(" TEST FAILED !!!"); //o.println(); 
  };

  private static void OK(PrintStream o) {
    successes++;
    o.println(" TEST PASSED."); //o.println();
  };
  


  // Mostly copied from jel.Evaluator
  static OpenOptimizer fillOptimizer(String expression, Library lib, Class rt) 
       throws CompilationException {
	 EC ec=new EC(new java.io.StringReader(expression));
    OpenOptimizer o=new OpenOptimizer(lib);
    try {
      ec.expression(o,rt);
    } catch (ParseException pe) {
      throw new CompilationException(pe);
    } catch (TokenMgrError tme) {
      throw new CompilationException(tme);
    };
    return o;
  };

};

class OpenOptimizer extends Optimizer {
  
  OpenOptimizer(Library lib) {super(lib);};
  
  public boolean optimizeIteration() {
    try {
      return super.optimizeIteration(super.code);
    } catch (Throwable e) {
      return false;
    };
  };
  
};
