package org.sc3d.apt.sss.v3;

import java.io.*;
import java.math.BigInteger;

/** This class is a worked example of how to use SSS in general, and the tools provided in this Java package in particular. It is not itself a particularly useful tool. To get the most out of this example, read its <a href="doc-files/Calculator.java">source code</a>.
 * <p>This class uses an SSS syntax to implement an interactive arbitrary precision integer calculator, using the java.math.BigInteger class as the back-end. If you really wanted to write a calculator, there are easier ways of doing it than using SSS. Having said that:<ul>
 * <li>this class only took a few hours to write (it's about 200 lines),
 * <li>it was trivial to debug and I'm confident it works correctly,
 * <li>it produces excellent error messages, and
 * <li>it is good, maintainable, flexible, future-proof code.
 * </ul>If you wanted to do something slightly more complicated, the balance tips firmly in favour of using something like SSS, and you might well want to imitate this code.
 */
public class Calculator extends Parser {
  /** Constructs a Calculator. */
  public Calculator() { super(SUM); }
  
  /* New API. */
  
  /** The grammar of arithmetic expressions that this calculator accepts. Here is an SSS grammar specification for this Grammar:<pre>
   * sign ::= {Minus {"-"}}
   * atom ::= {Number {sign* NUMBER} Bracket {sign* Round(sum)}}
   * multiplicand ::= {Multiply {"*" atom} Divide {"/" atom}}
   * product ::= {Product {atom multiplicand*}}
   * summand ::= {Add {"+" product} Subtract {"-" product}}
   * sum ::= {Sum {product summand*}}
   * ROOT sum
   * </pre> */
  public static final Grammar SUM = GrammarParser.fromString(
    "sign ::= {Minus {\"-\"}}\n"+
    "atom ::= {Number {sign* NUMBER} Bracket {sign* ROUND(sum)}}\n"+
    "multiplicand ::= {Multiply {\"*\" atom} Divide {\"/\" atom}}\n"+
    "product ::= {Product {atom multiplicand*}}\n"+
    "summand ::= {Add {\"+\" product} Subtract {\"-\" product}}\n"+
    "sum ::= {Sum {product summand*}}\n"+
    "ROOT sum"
  );
  
  /** Evaluates an arithmetic expression. If the expression is not syntactically correct, this method attaches appropriate error messages to 'expression' and returns 'null'. */
  public BigInteger evaluate(Sentence expression) {
    final Expression expr = (Expression)this.parse(expression);
    return expr==null ? null : expr.evaluate();
  }
  
  /** Converts a Tree representing the parse-tree of an 'atom', 'product' or 'sum' into an Expression. If the Tree contains errors, this method annotates the Tokens it contains with error messages and returns 'null'.*/
  public Expression postProcessExpression(Tree.Production raw) {
    if (raw.name.equals("Sum") || raw.name.equals("Product")) {
      Expression ans = this.postProcessExpression(raw.getP(0));
      final Tree.NonTerminal summands = raw.getNT(1);
      for (int i=0; i<summands.length; i++) {
        final Tree.Production summand = summands.get(i);
        final String op = summand.getT(0).t.toString();
        final Expression e2 = this.postProcessExpression(summand.getP(1));
        if (ans!=null) ans = e2==null ? null : new Operation(ans, e2, op);
      }
      return ans;
    } else if (raw.name.equals("Bracket")) {
      final Tree.NonTerminal signs = raw.getNT(0);
      final Tree.NonTerminal contents = (Tree.NonTerminal)raw.getT(1).parse();
      if (contents==null) return null;
      Expression ans = this.postProcessExpression(contents.get());
      if (ans==null) return null;
      for (int i=0; i<signs.length; i++) ans = new Negation(ans);
      return ans;
    } else if (raw.name.equals("Number")) {
      final Tree.NonTerminal signs = raw.getNT(0);
      final SSSNumber t = (SSSNumber)raw.getT(1).t;
      final int shift = t.getShift();
      if (shift<0) {
        t.addError("Sorry, fractions are not implemented.");
        return null;
      }
      final StringBuffer mant = new StringBuffer(t.getMantissa());
      for (int i=0; i<shift; i++) mant.append('0');
      Expression ans;
      try { ans = new Number(new BigInteger(mant.toString(), t.getRadix())); }
      catch (NumberFormatException e) { return null; }
      for (int i=0; i<signs.length; i++) ans = new Negation(ans);
      return ans;
    } else throw new RuntimeException("Unknown production");
  }
  
  ////////////////////////////////////////////////////////////////////////////
  
  /** The data structure that represents an arithmetic expression. Instances are immutable. */
  public static abstract class Expression {
    /** Calculates and returns the value of this Expression. */
    public abstract BigInteger evaluate();
  }
  
  ////////////////////////////////////////////////////////////////////////////
  
  /** An Expression that consists only of a number. */
  public static class Number extends Expression {
    /** Constructs a Number. */
    public Number(BigInteger n) { this.n = n; }
    /** The number. */
    public final BigInteger n;
    /** Returns 'n'. */
    public BigInteger evaluate() { return this.n; }
  }

  ////////////////////////////////////////////////////////////////////////////
  
  /** An Expression whose outermost operator is a negation. */
  public static class Negation extends Expression {
    /** Constructs the negation of 'e'. */
    public Negation(Expression e) { this.e = e; }
    /** The Expression of which this is the Negation. */
    public final Expression e;
    /** Returns the negation of 'e.evaluate()'. */
    public BigInteger evaluate() { return this.e.evaluate().negate(); }
  }
  
  ////////////////////////////////////////////////////////////////////////////
  
  /** An Expression whose outermost operator is an addition, subtraction, multiplication of division. */
  public static class Operation extends Expression {
    /** Constructs an Operation, given values for its fields. */
    public Operation(Expression e1, Expression e2, String operator) {
      this.e1 = e1; this.e2 = e2; this.operator = operator;
    }
    /** One of the operands. */
    public final Expression e1, e2;
    /** "+", "-", "*" or "/". */
    public final String operator;
    /** Evaluates the operands and performs the operation. */
    public BigInteger evaluate() {
      final BigInteger op1 = this.e1.evaluate(), op2 = this.e2.evaluate();
      if (this.operator.equals("+")) return op1.add(op2);
      if (this.operator.equals("-")) return op1.subtract(op2);
      if (this.operator.equals("*")) return op1.multiply(op2);
      if (this.operator.equals("/")) return op1.divide(op2);
      throw new RuntimeException("Unknown operation: "+this.operator);
    }
  }
  
  ////////////////////////////////////////////////////////////////////////////
  
  /* Override things in Parser. */
  
  /** Returns an Expression, given a Tree representing a parse-tree obeying 'Grammar.SUM'. If 'raw' contains errors, this method annotates the Tokens it contains with error messages and returns 'null'. */
  public Object postProcess(Tree raw) {
    // Comment in the following line to see the raw parse-tree.
    // System.out.println(raw);
    return this.postProcessExpression(((Tree.NonTerminal)raw).get());
  }
  
  /* Test code. */
  
  /** Repeatedly accepts an arithmetic expression from the console and evaluates it. */
  public static void main(String[] args) throws IOException {
    final BufferedReader in =
      new BufferedReader(new InputStreamReader(System.in));
    final Calculator me = new Calculator();
    while (true) {
      System.out.println("\nPlease enter an arithmetic expression:");
      final String line = in.readLine();
      if (line==null) break;
      final Sentence input = new Sentence(line.toCharArray());
      final BigInteger ans = me.evaluate(input);
      if (ans!=null) System.out.println("Answer = "+ans);
      else input.printErrorReport(System.out, 5);
    }
  }
}
