package simple_math_interpreter

import std.convert.*
import simple_math_interpreter.ext.*

enum Token {
    | Value(Int)
    | LParen
    | RParen
    | Plus
    | Minus
    | Multiply
    | Divide
}

extend Token {
    static func fromRunes(runes: List<Rune>): Result<List<Token>, String> {
        match (tokensFunc(runes).map {t => t[0]}) {
            case Some(l) => Ok(l)
            case None => Err("Invalid Input!")
        }
    }
}

extend Token <: ToString {
    public func toString() {
        match (this) {
            case Value(n) => n.toString()
            case LParen => "("
            case RParen => ")"
            case Plus => "+"
            case Minus => "-"
            case Multiply => "*"
            case Divide => "/"
        }
    }
}

let symbol = Combinator<Rune, Rune>.make {
    ch => match (ch) {
        case r'+' | r'-' | r'*' | r'/' | r'(' | r')' => true
        case _ => false
    }
}.map {
    ch: Rune => match (ch) {
        case '+' => Plus
        case '-' => Minus
        case '*' => Multiply
        case '/' => Divide
        case '(' => LParen
        case ')' => RParen
        case _ => throw Exception()
    }
}
let whiteSpace = Combinator<Rune, Rune>.make {ch => ch == r' '}
let number = Combinator<Rune, Rune>.make {
    ch => match (Int64.tryParse(ch.toString())) {
        case Some(n) where n >= 0 && n <= 9 => true
        case _ => false
    }
}.map {ch => Int64.parse(ch.toString())}
let value = number.many(1).map {
    l => l.reduce<Int64>({acc, x => acc * 10 + x}, 0)
}.map {v => Value(v)}
let tokenAndSpace = value.or(symbol).and(whiteSpace.many(1)).map {s: (Token, List<Rune>) => s[0]}
let token = value.or(symbol)
let tokens = tokenAndSpace.or(token).many(1)

func tokensFunc(input: List<Rune>): Option<(List<Token>, List<Rune>)> {
    tokens.parseFunc(input)
}

enum Expression {
    | Number(Int)
    | Plus(Expression, Expression)
    | Minus(Expression, Expression)
    | Multiply(Expression, Expression)
    | Divide(Expression, Expression)
}

extend Expression {
    static func fromTokens(tokens: List<Token>): Result<Expression, String> {
        match (expressionFunc(tokens).map {t => t[0]}) {
            case Some(e) => Ok(e)
            case None => Err("Invalid Expression!")
        }
    }
}

extend Expression {
    func eval(): Int64 {
        match (this) {
            case Number(n) => n
            case Plus(e1, e2) => e1.eval() + e2.eval()
            case Minus(e1, e2) => e1.eval() - e2.eval()
            case Multiply(e1, e2) => e1.eval() * e2.eval()
            case Divide(e1, e2) => e1.eval() / e2.eval()
        }
    }
}

extend Expression <: ToString {
    public func toString(): String {
        match (this) {
            case Number(n) => "${n}"
            case Plus(e1, e2) => "(+ ${e1} ${e2})"
            case Minus(e1, e2) => "(- ${e1} ${e2})"
            case Multiply(e1, e2) => "(* ${e1} ${e2})"
            case Divide(e1, e2) => "(/ ${e1} ${e2})"
        }
    }
}

let eNumber = Combinator<Token, Token>.make {
    token => match (token) {
        case Value(_) => true
        case _ => false
    }
}.map {
    token => match (token) {
        case Value(i) => Number(i)
        case _ => throw Exception()
    }
}
let lparen = Combinator<Token, Token>.make {
    token => match (token) {
        case LParen => true
        case _ => false
    }
}
let rparen = Combinator<Token, Token>.make {
    token => match (token) {
        case RParen => true
        case _ => false
    }
}
let plus = Combinator<Token, Token>.make {
    token => match (token) {
        case Plus => true
        case _ => false
    }
}
let minus = Combinator<Token, Token>.make {
    token => match (token) {
        case Minus => true
        case _ => false
    }
}
let multiply = Combinator<Token, Token>.make {
    token => match (token) {
        case Multiply => true
        case _ => false
    }
}
let divide = Combinator<Token, Token>.make {
    token => match (token) {
        case Divide => true
        case _ => false
    }
}

func atomicFunc(input: List<Token>): Option<(Expression, List<Token>)> {
    let expression = Combinator(expressionFunc)
    lparen.and(expression).and(rparen).map {t => t[0][1]}.or(eNumber).parseFunc(input)
}

func combineFunc(input: List<Token>): Option<(Expression, List<Token>)> {
    let atomic = Combinator(atomicFunc)
    atomic.and(multiply.or(divide).and(atomic).many(0)).map {
        t => t[1].reduce(
            {
                acc, x => match ((acc, x)) {
                    case (e1, (Multiply, e2)) => Multiply(e1, e2)
                    case (e1, (_, e2)) => Divide(e1, e2)
                }
            },
            t[0]
        )
    }.parseFunc(input)
}

func expressionFunc(input: List<Token>): Option<(Expression, List<Token>)> {
    let combine = Combinator(combineFunc)
    combine.and(plus.or(minus).and(combine).many(0)).map {
        t => t[1].reduce(
            {
                acc, x => match ((acc, x)) {
                    case (e1, (Plus, e2)) => Plus(e1, e2)
                    case (e1, (_, e2)) => Minus(e1, e2)
                }
            },
            t[0]
        )
    }.parseFunc(input)
}

public func eval(str: String): Result<Int64, String> {
    let runes = List<Rune>.fromArray(str.trimAscii().toRuneArray())
    Token.fromRunes(runes).andThen(Expression.fromTokens).map {e => e.eval()}
}