/*
 * Calc.java
 *
 * Created on 29. prosinec 2004, 16:09
 */

package calculatorall;

import java.util.Stack;
import java.util.StringTokenizer;

/**
 * @author ;-)
 */
public class Calculate {
  private Stack opStack;
  private Stack postfixStack;
  private StringTokenizer str;

  private static final int EOL = 0;
  private static final int VALUE = 1;
  private static final int OPAREN = 2;
  private static final int CPAREN = 3;
  private static final int EXP = 4;
  private static final int MULT = 5;
  private static final int DIV = 6;
  private static final int MOD = 7;
  private static final int PLUS = 8;
  private static final int MINUS = 9;
  private static final int GTHAN = 10;
  private static final int LTHAN = 11;
  private static final int EQUALS = 12;
  private static final int AND = 13;
  private static final int OR = 14;
  private static final int NOT = 15;

  private static Precedence[] precTable = {
    new Precedence(0, -1), //EOL
    new Precedence(0, 0), //VALUE
    new Precedence(100, 0), //OPAREN
    new Precedence(0, 99), //CPAREN
    new Precedence(10, 9), //EXP
    new Precedence(7, 8), //MULT
    new Precedence(7, 8), //DIV
    new Precedence(7, 8), //MOD
    new Precedence(5, 6), //PLUS
    new Precedence(5, 6), //MINUS
    new Precedence(3, 4), //GTHAN
    new Precedence(3, 4), //LTHAN
    new Precedence(3, 4), //EQUALS
    new Precedence(1, 2), //AND
    new Precedence(1, 2), //OR
    new Precedence(1, 2)   //NOT
  };

  public Calculate() {
    opStack = new Stack();
    postfixStack = new Stack();
    opStack.push(new Integer(EOL));
  }

  public String getValue(String expr) {
    str = new StringTokenizer(expr, "+*-/^()><=&|! ", true);
    EvalTokenizer tok = new EvalTokenizer(str);
    Token lastToken;
    double theResult;
    Double result;
    String resultStr;

    do {
      lastToken = tok.getToken();
      processToken(lastToken);
    } while (lastToken.getType() != EOL);

    if (postfixStack.isEmpty()) {
      System.err.println("Missing Operand! (1)");
      return null;
    }

    theResult = postFixTopAndPop();
    result = new Double(theResult);
    resultStr = new String(result.toString());

    if (!postfixStack.isEmpty())
      System.err.println("Warning: missing operators!");
    return resultStr;
  }


  // ---------------------------------------------------------------------------
  // ----------------------------- private methods -----------------------------
  // --------------------------------------------------------------------------
  private double postFixTopAndPop() {
    return ((Double) (postfixStack.pop())).doubleValue();
  }

  private int opStackTop() {
    return ((Integer) (opStack.peek())).intValue();
  }

  private void processToken(Token lastToken) {
    int topOp;
    int lastType = lastToken.getType();

    switch (lastType) {
      case VALUE:
        postfixStack.push(new Double(lastToken.getValue()));
        return;
      case CPAREN:
        while ((topOp = opStackTop()) != OPAREN && topOp != EOL)
          binaryOp(topOp);
        if (topOp == OPAREN)
          opStack.pop();
        else
          System.err.println("Missing Open Parens...");
        break;
      default:
        while (precTable[lastType].inputSymbol <= precTable[topOp = opStackTop()].topOfStack) {
          binaryOp(topOp);
        }
        if (lastType != EOL)
          opStack.push(new Integer(lastType));
        break;
    }
  }

  private double getTop() {
    if (postfixStack.isEmpty()) {
      return 0;
    }
    return postFixTopAndPop();
  }

  private void binaryOp(int topOp) {
    boolean lhsPopped;
    double rhs;
    double lhs;

    if (topOp == OPAREN) {
      System.err.println("Unbalanced Parentheses");
      opStack.pop();
      return;
    }

    lhsPopped = false;
    rhs = getTop();
    if (!postfixStack.isEmpty())
      lhsPopped = true;
    lhs = getTop();
    if (topOp == EXP)
      postfixStack.push(new Double((long) (Math.pow(lhs, rhs))));
    else if (topOp == PLUS)
      postfixStack.push(new Double(lhs + rhs));
    else if (topOp == MINUS)
      postfixStack.push(new Double(lhs - rhs));
    else if (topOp == MULT)
      postfixStack.push(new Double(lhs * rhs));
    else if (topOp == DIV) {
      if (rhs != 0)
        postfixStack.push(new Double(lhs / rhs));
      else {
        System.err.println("Can't divide by zero...");
        postfixStack.push(new Double(lhs));
      }
    } else if (topOp == MOD)
      postfixStack.push(new Double(lhs % rhs));
    
    //Zero is false, One is true
    else if (topOp == GTHAN)
      if (lhs > rhs)
        postfixStack.push(new Double(1));
      else
        postfixStack.push(new Double(0));
    else if (topOp == LTHAN)
      if (lhs < rhs)
        postfixStack.push(new Double(1));
      else
        postfixStack.push(new Double(0));
    else if (topOp == EQUALS)
      if (lhs == rhs)
        postfixStack.push(new Double(1));
      else
        postfixStack.push(new Double(0));
    else if (topOp == AND)
      if (lhs == 0 || rhs == 0)
        postfixStack.push(new Double(0));
      else
        postfixStack.push(new Double(1));
    else if (topOp == OR)
      if (lhs != 0 || rhs != 0)
        postfixStack.push(new Double(1));
      else
        postfixStack.push(new Double(0));
    else if (topOp == NOT) {
      if (lhsPopped)
        postfixStack.push(new Double(lhs));
      if (rhs == 0)
        postfixStack.push(new Double(1));
      else
        postfixStack.push(new Double(0));
    }
    opStack.pop();
  }

  // ---------------------------------------------------------------------------
  // ------------------------------ inner classes ------------------------------
  // ---------------------------------------------------------------------------
  private static class Precedence {
    public int inputSymbol;
    public int topOfStack;

    public Precedence(int inputSymbol, int topOfSymbol) {
      this.inputSymbol = inputSymbol;
      this.topOfStack = topOfSymbol;
    }
  }

  private static class Token {
    private int type = EOL;
    private double value = 0;

    public Token() {
      this(EOL);
    }

    public Token(int t) {
      this(t, 0);
    }

    public Token(int type, double value) {
      this.type = type;
      this.value = value;
    }

    public int getType() {
      return type;
    }

    public double getValue() {
      return value;
    }
  }

  private static class EvalTokenizer {
    private StringTokenizer strToken;

    public EvalTokenizer(StringTokenizer strToken) {
      this.strToken = strToken;
    }

    public Token getToken() {
      double theValue;
      String s;

      if (!strToken.hasMoreTokens())
        return new Token();
      s = strToken.nextToken();
      if (s.equals(" "))
        return getToken();
      if (s.equals("^"))
        return new Token(EXP);
      if (s.equals("/"))
        return new Token(DIV);
      if (s.equals("*"))
        return new Token(MULT);
      if (s.equals("%"))
        return new Token(MOD);
      if (s.equals("("))
        return new Token(OPAREN);
      if (s.equals(")"))
        return new Token(CPAREN);
      if (s.equals("+"))
        return new Token(PLUS);
      if (s.equals("-"))
        return new Token(MINUS);
      if (s.equals(">"))
        return new Token(GTHAN);
      if (s.equals("<"))
        return new Token(LTHAN);
      if (s.equals("="))
        return new Token(EQUALS);
      if (s.equals("&"))
        return new Token(AND);
      if (s.equals("|"))
        return new Token(OR);
      if (s.equals("!"))
        return new Token(NOT);
      theValue = Double.parseDouble(s);

      return new Token(VALUE, theValue);
    }
  }
}
