import java.io.PrintStream;

/**
 * This class performs exaustiv tests on the main class of the Vektoren
 * project. Call <tt>test_All</tt> to perform all tests. This class is
 * used only for debugging perposes. I didn't take care about overlong
 * lines in this modul.
 */
public class TestVektoren
{
  public static boolean test_All( PrintStream log )
  {
    boolean bOk = true;

    try
    {
      bOk &= test_Value( log );
      bOk &= test_Scanner( log );
      bOk &= test_Parser( log );
    }
    catch( Exception e )
    {
      log.println( e.getMessage() );
      e.printStackTrace( log );
      bOk = false;
    }

    log.println((bOk ? "[OK]": "[ERR]") + "test_All");
    return bOk;
  }

  public static boolean test_Value( PrintStream log )
  {
    boolean bOk = true;
    final double epsilon = 1e-5;

    try
    {
      {
        Value val = new Value( 1.23456 );
        bOk &= Math.abs( val.getX() - 1.23456 ) <= epsilon;
        bOk &= val.getType() == Value.typeScalar;
      }

      {
        Value val = new Value( 1, 2, 3 );
        bOk &= val.getX() == 1;
        bOk &= val.getY() == 2;
        bOk &= val.getZ() == 3;
        bOk &= val.getType() == Value.typeVector;
      }

      bOk &= new Value( 1 ).add( new Value( 2 ) ).equals( 3, epsilon );
      bOk &= new Value( 1, 2, 3 ).add( new Value( 4, 5, 6 ) ).equals( 5, 7, 9 , epsilon );

      bOk &= new Value( 1 ).subtract( new Value( 2 )).equals( -1 , epsilon );
      bOk &= new Value( 1, 2, 3).subtract( new Value( -4, -5, -6) ).equals( 5, 7, 9, epsilon );

      bOk &= new Value( 1 ).negate().equals( -1, epsilon );
      bOk &= new Value( 1, 2, 3 ).negate().equals( -1, -2, -3, epsilon );

      bOk &= new Value( 1.5 ).multiply( new Value( 2.3 )).equals(  3.45 , epsilon );
      bOk &= new Value( 1.2, 2.3, 4.5 ).multiply( new Value( 2 ) ).equals( 2.4, 4.6, 9.0, epsilon );
      bOk &= new Value( 2 ).multiply( new Value( 1.2, 2.3, 4.5 ) ).equals( 2.4, 4.6, 9.0, epsilon );

      bOk &= new Value( 1, 2, 3).multiply( new Value( 4, 5, 6 )).equals( -3, 6, -3, epsilon );

      bOk &= new Value( 42.45 ).toString().equals("42.450");
      bOk &= new Value( 1, 2, 3 ).toString().equals("[ 1.000 , 2.000 , 3.000 ]");
    }
    catch( Exception e )
    {
      bOk = false;
    }

    try
    {
      // exception expected!
      new Value( 1 ).add( new Value( 1, 2, 3 ) );
      bOk = false; // this statement shouln't be reached
    }
    catch( Exception e )
    {
    }

    try
    {
      // exception expected!
      new Value( 1 ).subtract( new Value( 1, 2, 3 ) );
      bOk = false; // this statement shouln't be reached
    }
    catch( Exception e )
    {
    }

    log.println((bOk ? "[OK]": "[ERR]") + "test_Value");
    return bOk;
  }


  public static boolean test_Scanner_hfn( PrintStream log
                                        , String expr
                                        , int[] tokens
                                        , Value[] values
                                        , boolean exceptionExpected
                                        )
  {
    boolean bOk = true;
    boolean bException = false;

    try
    {
      Scanner scanner = new Scanner( expr, true );

      int token=Scanner.EOF;
      int tokenCount=0;
      int valueCount=0;

      do
      {
        token=scanner.nextToken();
        tokenCount++;

        if ( tokenCount <= tokens.length)
        {
          if ( tokens[tokenCount-1] != token )
          {
            // wrong token
            bOk = false;
            break;
          }
          else
          {
            if ( token==Scanner.VECTOR || token==Scanner.SCALAR )
            {
              valueCount++;

              if ( valueCount <= values.length )
              {
                if ( !scanner.val.equals( values[valueCount-1], 1e-5) )
                {
                  bOk = false;
                  break;
                }
              }
              else
              {
                // more values found that supplied in values
                bOk = false;
                break;
              }
            }
          }
        }
        else
        {
          // more tokens found that exptected
          bOk = false;
          break;
        }
      } while ( token != Scanner.EOF );

      bOk &= tokenCount == tokens.length && valueCount == values.length;

    }
    catch( Exception e )
    {
      log.println("exception: " + e.getMessage() );
      bException = true;
    }

    bOk &= bException == exceptionExpected;

    log.println((bOk ? "[OK]": "[ERR]") + "test_Scanner for \"" + expr + "\"");
    return bOk;
  }

  public static boolean test_Scanner( PrintStream log )
  {
    boolean bOk = true;

    bOk &= test_Scanner_hfn( log, "", new int[] { Scanner.EOF }, new Value[] {}, false );
    bOk &= test_Scanner_hfn( log, "(42)", new int[] { Scanner.LPARAN, Scanner.SCALAR, Scanner.RPARAN, Scanner.EOF }, new Value[] { new Value( 42 ) }, false );
    bOk &= test_Scanner_hfn( log, "(((42)))", new int[] { Scanner.LPARAN, Scanner.LPARAN, Scanner.LPARAN, Scanner.SCALAR, Scanner.RPARAN, Scanner.RPARAN, Scanner.RPARAN, Scanner.EOF }, new Value[] { new Value( 42 ) }, true ); // nesting level error
    bOk &= test_Scanner_hfn( log, "(([1,2,3]))", new int[] { Scanner.LPARAN, Scanner.LPARAN, Scanner.VECTOR, Scanner.RPARAN, Scanner.RPARAN, Scanner.EOF }, new Value[] { new Value( 1,2,3 ) }, true ); // nesting level error
    bOk &= test_Scanner_hfn( log, "(-42)", new int[] { Scanner.LPARAN, Scanner.MINUS, Scanner.SCALAR, Scanner.RPARAN, Scanner.EOF }, new Value[] { new Value( 42 ) }, false );
    bOk &= test_Scanner_hfn( log, "(-(-42))", new int[] { Scanner.LPARAN, Scanner.MINUS, Scanner.LPARAN, Scanner.MINUS, Scanner.SCALAR, Scanner.RPARAN, Scanner.RPARAN, Scanner.EOF }, new Value[] { new Value( 42 ) }, false );
    bOk &= test_Scanner_hfn( log, "[-1,2,+3.5]", new int[] { Scanner.VECTOR, Scanner.EOF }, new Value[] { new Value( -1,2,3.5 ) }, false );
    bOk &= test_Scanner_hfn( log, "[1,2,3", new int[] { Scanner.VECTOR, Scanner.EOF }, new Value[] { new Value( 1,2,3 ) }, true );
    bOk &= test_Scanner_hfn( log, "[1,23]", new int[] { Scanner.VECTOR, Scanner.EOF }, new Value[] { new Value( 1,2,3 ) }, true );
    bOk &= test_Scanner_hfn( log, "[42]", new int[] { Scanner.VECTOR, Scanner.EOF }, new Value[] { new Value( 1,2,3 ) }, true );
    bOk &= test_Scanner_hfn( log, ".", new int[] { Scanner.VECTOR, Scanner.EOF }, new Value[] { }, true );
    bOk &= test_Scanner_hfn( log, "a", new int[] { Scanner.VECTOR, Scanner.EOF }, new Value[] { }, true );
    bOk &= test_Scanner_hfn( log, "(200)", new int[] { Scanner.LPARAN, Scanner.SCALAR, Scanner.RPARAN, Scanner.EOF }, new Value[] { }, true ); // value out of range
    bOk &= test_Scanner_hfn( log, "[200,1,0]", new int[] { Scanner.VECTOR, Scanner.EOF }, new Value[] { }, true ); // value out of range
    bOk &= test_Scanner_hfn( log, "((42))+([1,2,3]*[4,5,6])-[3,2,1]", new int[] { Scanner.LPARAN, Scanner.LPARAN, Scanner.SCALAR, Scanner.RPARAN, Scanner.RPARAN, Scanner.PLUS, Scanner.LPARAN,  Scanner.VECTOR, Scanner.MULT, Scanner.VECTOR, Scanner.RPARAN, Scanner.MINUS, Scanner.VECTOR, Scanner.EOF }, new Value[] { new Value( 42 ), new Value( 1, 2, 3 ), new Value(4,5,6), new Value(3,2,1) }, false );
    bOk &= test_Scanner_hfn( log, "42", new int[] { Scanner.SCALAR, Scanner.EOF }, new Value[] { new Value( 42 ) }, true ); // scalar must be inbetween ()
    bOk &= test_Scanner_hfn( log, "(42*42)", new int[] { Scanner.LPARAN,  Scanner.SCALAR, Scanner.MULT, Scanner.SCALAR, Scanner.RPARAN, Scanner.EOF }, new Value[] { new Value( 42 ), new Value( 42 ) }, true ); // scalar must be inbetween ()

    log.println((bOk ? "[OK]": "[ERR]") + "test_Scanner");
    return bOk;
  }

  public static boolean test_Parser_hfn( PrintStream log, String expr, double result, boolean exceptionExpected )
  {
    boolean bException=false;
    boolean bOk = true;
    try
    {
      // not not check with nasty scanner
      bOk &= new Parser( true ).run(new Scanner( expr, false )).equals( result, 1e-5 );
    }
    catch( Exception e )
    {
      log.println("exception: " + e.getMessage() );
      bException=true;
    }
    bOk &= bException == exceptionExpected;

    log.println((bOk ? "[OK]": "[ERR]") + "test_Parser_hfn for \"" + expr + "\"");
    return bOk;
  }

  public static boolean test_Parser_hfn( PrintStream log, String expr, double x, double y, double z, boolean exceptionExpected )
  {
    boolean bException=false;
    boolean bOk = true;
    try
    {
      // not not check with nasty scanner
      bOk &= new Parser( true ).run(new Scanner( expr, false )).equals( x, y, z, 1e-5 );
    }
    catch( Exception e )
    {
      log.println("exception: " + e.getMessage() );
      bException=true;
    }
    bOk &= bException == exceptionExpected;

    log.println((bOk ? "[OK]": "[ERR]") + "test_Parser_hfn for \"" + expr + "\"");
    return bOk;
  }


  public static boolean test_Parser( PrintStream log )
  {
    boolean bOk = true;

    bOk &= test_Parser_hfn( log, "42", 42, false );
    bOk &= test_Parser_hfn( log, "-42", -42, false );
    bOk &= test_Parser_hfn( log, "--42", 42, true );
    bOk &= test_Parser_hfn( log, "1--2", 3, false );
    bOk &= test_Parser_hfn( log, "1+-2", -1, false );
    bOk &= test_Parser_hfn( log, "-1*-1", 1, false );
    bOk &= test_Parser_hfn( log, "1*+3+4", 7, false );
    bOk &= test_Parser_hfn( log, "-(1)", -1, false );
    bOk &= test_Parser_hfn( log, "-(-1)", 1, false );
    bOk &= test_Parser_hfn( log, "1+2", 3, false );
    bOk &= test_Parser_hfn( log, "1+2+3", 6, false );
    bOk &= test_Parser_hfn( log, "1+2*3", 7, false );
    bOk &= test_Parser_hfn( log, "1+2*3*4+5", 30, false );
    bOk &= test_Parser_hfn( log, "(1+2)*3", 9, false );
    bOk &= test_Parser_hfn( log, "[1,2,3]", 1, 2, 3, false );
    bOk &= test_Parser_hfn( log, "2*[1,2,3]", 2, 4, 6, false );
    bOk &= test_Parser_hfn( log, "(2.3)*[3.2,-12,5]+[0.64,7.6,-1.5]", 8, -20, 10, false );

    bOk &= test_Parser_hfn( log, "(1", 1, true );
    bOk &= test_Parser_hfn( log, "1+", 1, true );
    bOk &= test_Parser_hfn( log, "(1))", 1, true );
    bOk &= test_Parser_hfn( log, "1*2*3*4*5*6", 720, true ); // more that 4 operators

    log.println((bOk ? "[OK]": "[ERR]") + "test_Parser");
    return bOk;
  }
}