"""Parse an tokenized expression into an AST."""
import codecs
from runtime import ast, lexer, env, lib, flags

class ParseException(Exception):
    def __init__(self, msg):
        super().__init__("ParseException: " + msg)

class MissingOperand(ParseException):
    def __init__(self, op):
        super().__init__("%s is missing operands" % op)

class UnknownOperator(ParseException):
    def __init__(self, op):
        super().__init__("Unknown operator %s" % op)

class BadStatement(ParseException):
    """A statement exception."""
    def __init__(self, msg="Bad statement without semicolon"):
        super().__init__(msg)

class NotImplemented(ParseException):
    """A parse exception."""
    def __init__(self, msg="Functionality not implemented"):
        super().__init__(msg)

class InvalidStatement(ParseException):
    def __init__(self, msg):
        super().__init__("Invalid statement: Unexpected %s" % str(msg))

class InvalidDeclaration(ParseException):
    def __init__(self, msg):
        super().__init__("Invalid declaration: Unexpected %s" % str(msg))

class InvalidDefinition(ParseException):
    def __init__(self, msg):
        super().__init__("Invalid definition: Unexpected %s" % str(msg))

class InvalidAssignment(ParseException):
    def __init__(self, msg="Invalid assignment"):
        super().__init__(msg)

class InvalidBlock(ParseException):
    def __init__(self, msg="Missing block borders"):
        super().__init__(msg)

class InvalidExpression(ParseException):
    def __init__(self, msg="Invalid expression"):
        super().__init__(msg)

class InvalidCondition(ParseException):
    def __init__(self, msg="Invalid condition"):
        super().__init__(msg)

class InvalidLoop(ParseException):
    def __init__(self, msg):
        super().__init__("Invalid loop: Unexpected %s" % str(msg))

def is_assignment(token):
    return token != None and (token.kind is lexer.OPERATOR) and (token.value in ["=", "+=", "-=", "*=", "/=", "%=", "^="])

def find_matching_block(stream, start):
    level = 1
    max = len(stream)
    for i in range(start, max):
        if stream[i].kind == lexer.LBLOCK:
            level += 1
        elif stream[i].kind == lexer.RBLOCK:
            level -= 1
            if level == 0:
                return i
    return -1

def find_matching_prt(stream, start):
    level = 1
    max = len(stream)
    for i in range(start, max):
        if flags.debug:
            print("scanned", str(stream[i]), ":", level)
        if stream[i].kind == lexer.LPRT:
            level += 1
        elif stream[i].kind == lexer.RPRT:
            level -= 1
            if level == 0:
                return i
    return -1

def get_arg_count(operator, last_token):
    if operator in ["+", "-"] and (last_token == None or last_token.kind not in [lexer.NUMBER, lexer.IDENTIFIER, lexer.STRING, lexer.RPRT]):
        if flags.debug:
            print("unary operator because of", last_token)
        return 1
    elif operator in ["!"]:
        return 1
    if flags.debug:
        print("binary because of", last_token)
    return 2

def is_left_associative(operator, last_token):
    if operator in ["+", "-"] and (last_token == None or last_token.kind not in [lexer.NUMBER, lexer.IDENTIFIER, lexer.STRING, lexer.RPRT]):
        if flags.debug:
            print("right associative because of", last_token)
        return False
    elif operator in ["!", "^"]:
        return False
    if flags.debug:
        print("left associative because of", last_token)
    return True

def get_precedence(operator, last_token):
    if operator in ["+", "-"] and (last_token == None or last_token.kind not in [lexer.NUMBER, lexer.IDENTIFIER, lexer.STRING, lexer.RPRT]):
        return 7
    elif operator in ["!"]:
        return 7
    elif operator in ["^"]:
        return 6
    elif operator in ["*", "/"]:
        return 5
    elif operator in ["+", "-", ":"]:
        return 4
    elif operator in ["%"]:
        return 3
    elif operator in ["<", ">", "<=", ">=", "!=", "=="]:
        return 2
    elif operator in ["&&", "||", "^|"]:
        return 1
    return 0

def generate_expression(stream):
    if flags.debug:
        print("Starting generate expression")
    operand_stack = []
    operator_stack = []
    max = len(stream) - 1

    last_token = None
    token = None

    def pop_off_operator():
        if len(operator_stack) < 1:
            raise ParseException("Empty operator stack, could not pop off operator")

        operator = operator_stack.pop()
        if flags.debug:
            print("popping of", operator)

        arg_count = operator.tag("arg_count")
        if len(operand_stack) < arg_count:
            raise MissingOperand(operator.symbol)

        for j in range(arg_count):
            operator.add_front(operand_stack.pop())
        operand_stack.append(operator)

    for i in range(max + 1):
        last_token = token
        token = stream[i]

        if flags.debug:
            print(">>> Parsing next token:", token)
            print("Operands: ", ', '.join(str(e) for e in operand_stack))
            print("Operators:", ', '.join(str(e) for e in operator_stack))

        if token.kind == lexer.NUMBER:
            value = None
            if '.' in token.value:
                value = env.Value(lib.FLOAT, data=float(token.value))
            else:
                value = env.Value(lib.INTEGER, data=float(token.value))
            operand_stack.append(ast.Literal(value))
        elif token.kind == lexer.STRING:
            stripped = token.value.strip("\"")
            decoded = codecs.decode(stripped, "unicode_escape")
            value = env.Value(lib.STRING, data=decoded)
            operand_stack.append(ast.Literal(value))
        elif token.kind == lexer.SEPARATOR:
            while len(operator_stack) > 0 and operator_stack[-1] != "(":
                pop_off_operator()
        elif token.kind == lexer.IDENTIFIER:
            if i < max and stream[i+1].kind == lexer.LPRT:
                operator_stack.append(ast.Call(token.value))
            else:
                if token.value == "false":
                    operand_stack.append(ast.Literal(env.Value(lib.BOOLEAN, data=False)))
                elif token.value == "true":
                    operand_stack.append(ast.Literal(env.Value(lib.BOOLEAN, data=True)))
                elif token.value == "null":
                    operand_stack.append(ast.Literal(env.Value(lib.NULL)))
                else:
                    operand_stack.append(ast.Identifier(token.value))
        elif token.kind == lexer.OPERATOR:
            new_operator = ast.Operation(token.value)

            prec = get_precedence(token.value, last_token)
            arg_count = get_arg_count(token.value, last_token)
            left_associative = is_left_associative(token.value, last_token)

            new_operator.tag("precedence", prec)
            new_operator.tag("arg_count", arg_count)
            new_operator.tag("left_associative", left_associative)

            if flags.debug:
                print("adding operator", new_operator.symbol, "to", len(operator_stack))

            while len(operator_stack) > 0 and (type(operator_stack[-1]) is ast.Operation):
                other = operator_stack[-1]
                other_prec = operator_stack[-1].tag("precedence")

                if flags.debug:
                    print("comparing precedence of ", new_operator.symbol, prec, "to", other.symbol, other_prec)

                if left_associative:
                    if prec > other_prec:
                        break
                else:
                    if prec >= other_prec:
                        break
                pop_off_operator()

            operator_stack.append(new_operator)
            if flags.debug:
                print("pushed operator on stack")
        elif token.kind == lexer.LPRT:
            operand_stack.append(token.value)
            operator_stack.append(token.value)
        elif token.kind == lexer.RPRT:
            while len(operator_stack) > 0 and operator_stack[-1] != "(":
                pop_off_operator()

            if len(operator_stack) < 1:
                raise ParseException("Mismatched parentheses")

            operator_stack.pop()

            if len(operator_stack) > 0 and type(operator_stack[-1]) is ast.Call:
                function = operator_stack.pop()
                while len(operand_stack) > 0 and operand_stack[-1] != "(":
                    function.add_front(operand_stack.pop())
                operand_stack.pop()
                operand_stack.append(function)
            else:
                j = len(operand_stack) - 1
                while j >= 0 and operand_stack[j] != "(":
                    j -= 1
                del operand_stack[j]
        elif token.kind == lexer.STATEMENT:
            while len(operator_stack) > 0:
                pop_off_operator()
            if len(operand_stack) > 1:
                raise InvalidExpression()

            if len(operand_stack) != 1:
                raise InvalidExpression("Empty expression")

            return operand_stack[0], i

    last_token = token

    while len(operator_stack) > 0:
        pop_off_operator()

    if flags.debug:
        print("Operands: ", ', '.join(str(e) for e in operand_stack))
        print("Operators:", ', '.join(str(e) for e in operator_stack))

    if len(operand_stack) != 1:
        raise InvalidExpression("Empty expression")

    if flags.debug:
        print("Parsed expression with length %d" % (max + 1))

    return operand_stack[0], max + 1

def generate_declaration(stream):
    if flags.debug:
        print("Starting generating declaration")

    end = len(stream)
    for j in range(len(stream)):
        if stream[j].kind is lexer.STATEMENT:
            end = j
            break

    if end < 3:
        raise ParseException("Declaration too short")

    if not (stream[0].kind is lexer.IDENTIFIER and stream[0].value == "var"):