from functools import reduce

import six

from ..interface import RValue
from ..syntax import isnumber, W


class Binary(RValue):
    operations = {
        '+': lambda x, y: x + y,
        '-': lambda x, y: x - y,
        '*': lambda x, y: x * y,
        '^': lambda x, y: x ** y,
    }

    precedence = {
        '+': 0,
        '-': 0,
        '*': 1,
        '^': 2,
    }

    def __init__(self, exprs, glyph, where=None):
        assert glyph in  Binary.operations
        for e in exprs:
            assert isinstance(e, RValue)

        self.where = where
        self.exprs = exprs
        self.glyph = glyph
        self.precedence = Binary.precedence[glyph]

    def eval(self, context):  # @ReservedAssignment
        vals = []
        for expr in self.exprs:
            val = expr.eval(context)
            if not isnumber(val):
                raise ValueError('I can only do math with numbers, not %r.' %
                                 val.__class__.__name__)
            vals.append(val)
        operation = Binary.operations[self.glyph]
        return reduce(operation, vals)

    def __repr__(self):
        s = 'Binary(%r,%r)' % (self.exprs, self.glyph)
        return s

    def __str__(self):
        def convert(x):
            if isinstance(x, Binary) and x.precedence < self.precedence:
                return '(%s)' % x
            else:
                return '%s' % x

        s = self.glyph.join(convert(x) for x in self.exprs)
        return s

    @staticmethod
    def parse_action(s, loc, tokens):
        where = W(s, loc)
        l = list(tokens[0])
        exprs = [l.pop(0)]
        while l:
            glyph = l.pop(0)
            assert isinstance(glyph, six.string_types)
            expr = l.pop(0)
            assert isinstance(expr, RValue)
            exprs.append(expr)

        # noinspection PyUnboundLocalVariable
        return Binary(exprs, glyph, where=where)


class Unary(RValue):

    operations = {
        '-': lambda x: (-x),
    }

    def __init__(self, glyph, expr, where=None):
        assert glyph in Unary.operations
        assert isinstance(expr, RValue)

        self.where = where
        self.expr = expr
        self.glyph = glyph

    def eval(self, context):  # @ReservedAssignment
        val = self.expr.eval(context)
        if not isnumber(val):
            raise ValueError('I can only do math with numbers, not with %r.' %
                   val.__class__.__name__)

        operation = Unary.operations[self.glyph]
        return operation(val)

    def __repr__(self):
        s = 'Unary(%r,%r)' % (self.glyph, self.expr)
        return s

    def __str__(self):
        # XXX: precedence
        return '%s%s' % (self.glyph, self.expr)

    @staticmethod
    def parse_action(s, loc, tokens):
        where = W(s, loc)
        glyph = tokens[0][0]
        assert isinstance(glyph, six.string_types)
        expr = tokens[0][1]
        assert isinstance(expr, RValue)
        return Unary(glyph, expr, where=where)

