import java.io.Reader;
import java.io.StringReader;
import java.io.IOException;

/**
 * This class implements a simply one char lookahead scanner, with
 * performs the lexicalical analysis of the expression.<br>
 * The Scanner can be constructed using a String or a Stream (Reader),
 * to get the first/next token call <tt>nextToken</tt>. If <tt>nextToken</tt>
 * returns VECTOR or SCALAR, the value can be accessed via the public
 * member variable <tt>val</tt><br><br>
 * @see Scanner#nextToken
 * @see TestVektoren#test_Scanner
 */
public class Scanner
{
  // token types
  public static final int EOF=-1;
  public static final int SCALAR=0;
  public static final int VECTOR=1;
  public static final int PLUS=2;
  public static final int MINUS=3;
  public static final int MULT=4;
  public static final int DIV=5;
  public static final int LPARAN=6;
  public static final int RPARAN=7;

  private static final String[] tokenNames = new String[] { "Scalar", "Vector", "+", "-", "*", "/", "(", ")" };

  private Reader in=null;
  private int la=EOF;
  private int offset=0;
  private int tokenStart=0;

  // to check some restrictions
  private boolean nastyEprogRestrictions = false;
  private int[] backLog = new int[] { Scanner.EOF, Scanner.EOF, Scanner.EOF, Scanner.EOF };
  private int nestingLevel=0;

  /** contains the actual value of <tt>nextToken</tt> returns SCALAR | VECTOR */
  public Value val=Value.ZERO;

  public Scanner(String expr, boolean nastyEprogRestrictions) throws IOException
  {
    this( new StringReader( expr ), nastyEprogRestrictions );
  }

  public Scanner(Reader in, boolean nastyEprogRestrictions) throws IOException
  {
    this.in = in;
    this.nastyEprogRestrictions = nastyEprogRestrictions;
    nextCh();
  }

  /**
   * @return the start offset of the last token returned by <tt>nextToken</tt>,
   * can be used to construct error messages
   */
  public int getTokenStart()
  {
    return tokenStart;
  }

  private void nextCh() throws IOException
  {
    offset += (la!=EOF)?1:0;
    la=in.read();
  }

  private void match( char ch ) throws Exception
  {
    if ( la == ch )
      nextCh();
    else
      throw new ScannerException( (char)la, offset, ch );
  }

  private double scanDigit() throws Exception
  {
    // 20 characters should be enough, otherwise StringBuffer will grow
    // automatical
    StringBuffer strValue = new StringBuffer( 20 );
    int digits=0;

    if ( la == '+' || la == '-' )
    {
      strValue.append( (char)la );
      nextCh();
    }

    while ( ('0' <= la) && ( la <= '9' ) )
    {
      digits++;
      strValue.append( (char)la );
      nextCh();
    }

    if ( la == '.' )
    {
      strValue.append( (char)la );
      nextCh();

      while ( ('0' <= la) && ( la <= '9' ) )
      {
        digits++;
        strValue.append( (char)la );
        nextCh();
      }
    }

    if ( digits == 0 )
      throw new NumberFormatException("a scalar must have at least 1 digit");

    return Double.parseDouble( strValue.toString() );
  }

  private void checkRange( double x ) throws NastyEprogException
  {
    if ( x < -100.0 || x > 100.0 )
      throw new NastyEprogException("value not in range [-100.0,100.0] at offset " + tokenStart );
  }

  /**
   * @return the next token
   */
  public int nextToken() throws Exception
  {
    int iRet = EOF;
    tokenStart = offset;

    if ( la != EOF )
    {
      if ( la == '+' )
      {
        nextCh();
        iRet = PLUS;
      }
      else if ( la == '-' )
      {
        nextCh();
        iRet = MINUS;
      }
      else if ( la == '*' )
      {
        nextCh();
        iRet = MULT;
      }
      /* to also allow DIV uncomment this block
      else if ( la == '/' )
      {
        nextCh();
        iRet = DIV;
      }
      */
      else if ( la == '(' )
      {
        nextCh();
        iRet = LPARAN;

        nestingLevel++;
      }
      else if ( la == ')' )
      {
        nextCh();
        iRet = RPARAN;

        nestingLevel--;
      }
      else if ( ( ( '0' <= la ) && ( la <= '9' ) ) || la == '.' )
      {
        double x = scanDigit();

        iRet = SCALAR;
        if ( nastyEprogRestrictions )
        {
          checkRange( x );
        }

        val = new Value( x );
      }
      else if ( la == '[' )
      {
        nextCh();
        double x = scanDigit();
        match( ',' );
        double y = scanDigit();
        match( ',' );
        double z = scanDigit();
        match( ']' );

        if ( nastyEprogRestrictions )
        {
          checkRange( x );
          checkRange( y );
          checkRange( z );
        }

        iRet = VECTOR;
        val = new Value( x, y, z );
      }
      else
        throw new ScannerException( (char)la, offset );
    }

    if ( nastyEprogRestrictions )
    {
      // check if scalar is inbetween ( )
      for( int i=0; i<3; i++ )
        backLog[i] = backLog[i+1];

      backLog[3] = iRet;

      if ( backLog[2] == SCALAR )
      {
        if ( !
             (
               ( backLog[0] == LPARAN
               && ( backLog[1] == PLUS || backLog[1] == MINUS )
               && backLog[3] == RPARAN
               )
             ||
               ( backLog[1] == LPARAN && backLog[3] == RPARAN )
              )
            )
          throw new NastyEprogException("a scalar must be inbetween ( and ) at offset " + tokenStart );
      }

      // checking the nestinglevel, according to the specification only
      // 1 level of paranteses is allowed (accept if it is a scalar,
      // with must be inbetween ()
      if ( (nestingLevel>2) || ( nestingLevel>=2 && iRet == VECTOR ) )
        throw new NastyEprogException("too many ( ), nestinglevel to high at offset " + tokenStart );
    }

    return iRet;
  }

  /**
   * @return a human readable name for a given tokentype
   */
  public static String tokenName( int token )
  {
    if ( token == -1 )
      return "EOF";
    else if ( SCALAR <= token && token <= RPARAN )
      return tokenNames[ token ];
    else
      return "Unknown Token Type";
  }
}