// FILE: BasicCalc.java
// This program reads a reads and evaluates a fully parenthesized arithmetic expression.
// The purpose is to illustrate a fundamental use of stacks.

//import edu.colorado.collections.CharStack;
//import edu.colorado.collections.DoubleStack;
//import edu.colorado.io.EasyReader;  // From Appendix B

public class BasicCalc
{                                                                                  
   public static void main(String[ ] args)
   {   
      EasyReader stdin = new EasyReader(System.in);
      double answer;
      
      System.out.println("Type a fully parenthesized arithmetic expression:");
      answer = readAndEvaluate(stdin);
      System.out.println("That evaluates to " + answer);
   }
   
   
   public static double readAndEvaluate(EasyReader input)   
   // Precondition: The next line of characters in the EasyReader is a fully
   // parenthesized arithmetic expression formed from non-negative numbers,
   // parentheses, and the four operations +, -, *, and /.
   // Postcondition: A line has been read from the EasyReader, including the
   // newline character. This line has been evaluated and the value returned.
   // Exceptions: Can throw an IllegalArgumentException if the input line is an
   // illegal expression, such as unbalanced parentheses or a division by zero.
   // However, some illegal expressions are not caught by this implementation. 
   {
      final char DECIMAL           = '.';
      final char RIGHT_PARENTHESIS = ')';
      final String SYMBOLS         = "+-*/";
      
      DoubleStack numbers = new DoubleStack( );
      CharStack operations = new CharStack( );
    
      while (!input.isEOLN( ))
      {
         if (Character.isDigit(input.peek( )) || (input.peek( ) == DECIMAL))
         {  // Read a number and push it on the numbers stack.
            numbers.push(input.doubleInput( ));
         }
         else if (SYMBOLS.indexOf(input.peek( )) >= 0)
         {  // Read the + - * or / symbol and push it on the operations stack.
            operations.push(input.charInput( ));
         }
         else if (input.peek( ) == RIGHT_PARENTHESIS)
         {  // Evaluate the stuff on top of the stacks.
            input.ignore( );
            evaluateStackTops(numbers, operations);
         }
         else
         {  // Just read and ignore all other characters.
            input.ignore( );
         }
      } 
      input.skipLine( ); // Read and ignore the newline character.

      if (numbers.size( ) != 1)
         throw new IllegalArgumentException("Illegal input expression");    
      return numbers.pop( );
   }
   
   
   public static void evaluateStackTops(DoubleStack numbers, CharStack operations)     
   // Precondition: The top of the operations stack contains +, -, *, or /, and
   // the numbers stack contains at least two numbers. 
   // Postcondition: The top two numbers have been popped from the numbers stack, and the
   // top operation has been popped from the operations stack. The two numbers have been
   // combined using the operation (with the second number popped as the left operand). 
   // The result of the operation has then been pushed back onto the numbers stack.
   // Exceptions: Throws an IllegalArgumentException if the stacks are illegal or if the 
   // operation results in a division by zero.
   {
      double operand1, operand2;
      
      // Check that the stacks have enough items, and get the two operands.
      if ((numbers.size( ) < 2) || (operations.isEmpty( )))
         throw new IllegalArgumentException("Illegal expression");        
      operand2 = numbers.pop( );
      operand1 = numbers.pop( );
      
      // Carry out an action based on the operation on the top of the stack.
      switch (operations.pop( ))
      {
         case '+': numbers.push(operand1 + operand2);
                   break;
         case '-': numbers.push(operand1 - operand2);
                   break;
         case '*': numbers.push(operand1 * operand2);
                   break;
         case '/': if (operand2 == 0)
                      throw new IllegalArgumentException("Division by zero");
                   numbers.push(operand1 / operand2);
                   break;
         default:  throw new IllegalArgumentException("Illegal operation");
      }
   }
}
