// See - http://www.ibm.com/developerworks/jp/java/library/j-scala11218.html
//       > 多忙な Java 開発者のための Scala ガイド: 電卓を作る、第 3 回

package calc.dsl2

import java.math.BigDecimal
import scala.util.parsing.combinator._

private[dsl2] abstract class Expr
private[dsl2] case class Number(value : BigDecimal) extends Expr
private[dsl2] case class UnaryOp(operator : String, arg : Expr) extends Expr
private[dsl2] case class BinaryOp(operator : String, left : Expr, right : Expr) extends Expr
private[dsl2] case class OpTerm(operator : String, term : Expr) extends Expr
private[dsl2] case class OpTermexp(operator : String, termexp : Expr) extends Expr

object Calc2 {
    val MC = java.math.MathContext.DECIMAL128
    val MINUS_1 = Number(new BigDecimal(-1))
    val PLUS_1 = Number(new BigDecimal(1))
    val ZERO = Number(new BigDecimal(0))

    def genExprTerms(lhs: Expr, terms: List[OpTerm]) :Expr = {
        var len = terms.length
        len match {
            case 0 => lhs
            case _ => BinaryOp(terms(len-1).operator,
                               genExprTerms(lhs, terms.dropRight(1)), terms(len-1).term)
        }
    }

    def genExprTermexps(lhs: Expr, termexps: List[OpTermexp]) :Expr = {
        var len = termexps.length
        len match {
            case 0 => lhs
            case _ => BinaryOp(termexps(len-1).operator,
                               genExprTermexps(lhs, termexps.dropRight(1)), termexps(len-1).termexp)
        }
    }

    def simplify(e: Expr): Expr = {
        // first simplify the subexpressions
        val simpSubs = e match {
            // Ask each side to simplify
            case BinaryOp(op, left, right) => BinaryOp(op, simplify(left), simplify(right))
                // Ask the operand to simplify
            case UnaryOp(op, operand) => UnaryOp(op, simplify(operand))
                // Anything else doesn't have complexity (no operands to simplify)
            case _ => e
        }

        // now simplify at the top, assuming the components are already simplified
        def simplifyTop(x: Expr) = x match {
            // Double negation returns the original value
            case UnaryOp("-", UnaryOp("-", x)) => x
                // Positive returns the original value
            case UnaryOp("+", x) => x
                // Multiplying x by 1 returns the original value
            case BinaryOp("*", x, PLUS_1) => x
                // Multiplying 1 by x returns the original value
            case BinaryOp("*", PLUS_1, x) => x
                // Multiplying x by 0 returns zero
            case BinaryOp("*", x, ZERO) => ZERO
                // Multiplying 0 by x returns zero
            case BinaryOp("*", ZERO, x) => ZERO
                // Dividing x by 1 returns the original value
            case BinaryOp("/", x, PLUS_1) => x
                // Dividing x by x returns 1
            case BinaryOp("/", x1, x2) if x1 == x2 => PLUS_1
                // Adding x to 0 returns the original value
            case BinaryOp("+", x, ZERO) => x
                // Adding 0 to x returns the original value
            case BinaryOp("+", ZERO, x) => x
                // Anything else cannot (yet) be simplified
            case e => e
        }
        simplifyTop(simpSubs)
    }

    def exponentiate(base : BigDecimal, exponent : BigDecimal) =
    new BigDecimal(Math.pow(base.floatValue, exponent.floatValue))

    def evaluate(e : Expr) : BigDecimal = {
        simplify(e) match {
            case Number(x) => x
            case UnaryOp("-", x) => (evaluate(x).multiply(MINUS_1.value))
            case UnaryOp("+", x) => (evaluate(x))
            case BinaryOp("+", x1, x2) => (evaluate(x1).add(evaluate(x2)))
            case BinaryOp("-", x1, x2) => (evaluate(x1).subtract(evaluate(x2), MC))
            case BinaryOp("*", x1, x2) => (evaluate(x1).multiply(evaluate(x2), MC))
            case BinaryOp("/", x1, x2) => (evaluate(x1).divide(evaluate(x2), MC))
            case BinaryOp("^", x1, x2) => exponentiate(evaluate(x1), evaluate(x2))
        }
    }

    def evaluate(text : String) : BigDecimal = evaluate(parse(text))
    def parse(text : String) = ArithParser.parse(text).get

    object ArithParser extends JavaTokenParsers {

        def expr: Parser[Expr] =
        (headterm ~ rep(opterm)) ^^ { case lhs~terms => genExprTerms(lhs, terms)} |
        headterm

        //式の先頭の項には +,- を付加できる。
        def headterm: Parser[Expr] =
        ("+" ~ term) ^^ { case plus~rhs => rhs } |
        ("-" ~ term) ^^ { case minus~rhs => UnaryOp("-", rhs) } |
        term

        def opterm: Parser[OpTerm] =
        ("+" | "-")~term ^^ {case op~rhs => OpTerm(op, rhs)}

        // *, /  は +, - より優先度を高く
        def term: Parser[Expr] =
        (termexp ~ rep(optermexp)) ^^ { case lhs~termexps => genExprTermexps(lhs, termexps) } |
        termexp

        def optermexp: Parser[OpTermexp] =
        ("*" | "/")~termexp ^^ {case op~rhs => OpTermexp(op, rhs)}

        // ^ は *, / より優先度を高く
        def termexp: Parser[Expr] =
        (factor ~ "^" ~ factor) ^^ { case lhs~times~rhs => BinaryOp("^", lhs, rhs) } |
        factor

        def factor : Parser[Expr] =
        "(" ~> expr <~ ")" |
        ("-" ~ "(" ~ expr ~ ")" ) ^^ { case minus~lp~expr~rp => UnaryOp("-", expr) } |
        ("+" ~ "(" ~ expr ~ ")" ) ^^ { case plus~lp~expr~rp => UnaryOp("+", expr) } |
        unsignedFloatingNumber ^^ {x => Number(new BigDecimal(x.toString))}

        def parse(text : String) = parseAll(expr, text)
    }

    // scala で定義している FloatingNumber から先頭の符号を削除
    trait JavaTokenParsers extends RegexParsers {
        def unsignedFloatingNumber: Parser[String] =
        """(\d+(\.\d*)?|\d*\.\d+)([eE][+-]?\d+)?[fFdD]?""".r
    }
}