#!/usr/bin/env python

'''
The expressions module contains classes to represent an expression. The main
class is ExpressionNode. ExpressionNode's most useful method is py_string(),
which returns a Python string representing that expression.
'''

import keyword
import sys
from .ctypedescs import *


# Right now, the objects in this module are all oriented toward evaluation.
# However, they don't have to be, since ctypes objects are mutable. For example,
# shouldn't it be possible to translate the macro:
#
#   #define INCREMENT(x) ++x
#
# into Python? The resulting code should be:
#
#   def INCREMENT(x):
#       x.value+=1
#       return x.value
#
# On the other hand, this would be a challenge to write.


class EvaluationContext(object):
    '''Interface for evaluating expression nodes.
    '''

    def evaluate_identifier(self, name):
        warnings.warn('Attempt to evaluate identifier "%s" failed' % name)
        return 0

    def evaluate_sizeof(self, type):
        warnings.warn('Attempt to evaluate sizeof "%s" failed' % str(type))
        return 0

    def evaluate_sizeof(self, object):
        warnings.warn('Attempt to evaluate sizeof object "%s" failed' % str(object))
        return 0

    def evaluate_parameter(self, name):
        warnings.warn('Attempt to evaluate parameter "%s" failed' % name)
        return 0


class ExpressionNode(object):

    def __init__(self):
        self.errors = []

    def error(self, message, cls=None):
        self.errors.append((message, cls))

    def __repr__(self):
        try:
            string = repr(self.py_string(True))
        except ValueError:
            string = "<error in expression node>"
        return "<ExpressionNode: %s>" % string

    def visit(self, visitor):
        for error, cls in self.errors:
            visitor.visit_error(error, cls)


class ConstantExpressionNode(ExpressionNode):

    def __init__(self, value):
        ExpressionNode.__init__(self)
        self.value = value

    def evaluate(self, context):
        return self.value

    try:
        pos_inf = float('inf')
        neg_inf = float('-inf')
    except ValueError:
        pos_inf = ()
        neg_inf = ()

    def py_string(self, can_be_ctype):
        if (sys.platform != 'win32' or (sys.platform == 'win32' and
                                        sys.version_info >= (2, 6))):
            # Windows python did not get infinity support until 2.6
            if self.value == ConstantExpressionNode.pos_inf:
                return "float('inf')"
            elif self.value == ConstantExpressionNode.neg_inf:
                return "float('-inf')"
        return repr(self.value)


class IdentifierExpressionNode(ExpressionNode):

    def __init__(self, name):
        ExpressionNode.__init__(self)
        self.name = name

    def evaluate(self, context):
        return context.evaluate_identifier(self.name)

    def visit(self, visitor):
        visitor.visit_identifier(self.name)
        ExpressionNode.visit(self, visitor)

    def py_string(self, can_be_ctype):
        # Errors will be thrown in generated code if identifier evaluates
        # to a ctypes object, and can_be_ctype is False.
        return self.name


class ParameterExpressionNode(ExpressionNode):

    def __init__(self, name):
        ExpressionNode.__init__(self)
        self.name = name

    def evaluate(self, context):
        return context.evaluate_parameter(self.name)

    def visit(self, visitor):
        ExpressionNode.visit(self, visitor)

    def py_string(self, can_be_ctype):
        # Errors will be thrown in generated code if parameter is
        # a ctypes object, and can_be_ctype is False.
        return self.name


class UnaryExpressionNode(ExpressionNode):

    def __init__(self, name, op, format, child_can_be_ctype, child):
        ExpressionNode.__init__(self)
        self.name = name
        self.op = op
        self.format = format
        self.child_can_be_ctype = child_can_be_ctype
        self.child = child

    def visit(self, visitor):
        self.child.visit(visitor)
        ExpressionNode.visit(self, visitor)

    def evaluate(self, context):
        if self.op:
            return self.op(self.child.evaluate(context))
        else:
            raise ValueError("The C operator \"%s\" can't be evaluated right "
                             "now" % self.name)

    def py_string(self, can_be_ctype):
        return self.format % \
            self.child.py_string(self.child_can_be_ctype and can_be_ctype)


class SizeOfExpressionNode(ExpressionNode):

    def __init__(self, child):
        ExpressionNode.__init__(self)
        self.child = child

    def visit(self, visitor):
        self.child.visit(visitor)
        ExpressionNode.visit(self, visitor)

    def evaluate(self, context):
        if isinstance(self.child, CtypesType):
            return context.evaluate_sizeof(self.child)
        else:
            return context.evaluate_sizeof_object(self.child)

    def py_string(self, can_be_ctype):
        if isinstance(self.child, CtypesType):
            return 'sizeof(%s)' % self.child.py_string()
        else:
            return 'sizeof(%s)' % self.child.py_string(True)


class BinaryExpressionNode(ExpressionNode):

    def __init__(self, name, op, format, can_be_ctype, left, right):
        ExpressionNode.__init__(self)
        self.name = name
        self.op = op
        self.format = format
        self.can_be_ctype = can_be_ctype
        self.left = left
        self.right = right

    def visit(self, visitor):
        self.left.visit(visitor)
        self.right.visit(visitor)
        ExpressionNode.visit(self, visitor)

    def evaluate(self, context):
        if self.op:
            return self.op(self.left.evaluate(context),
                           self.right.evaluate(context))
        else:
            raise ValueError("The C operator \"%s\" can't be evaluated right "
                             "now" % self.name)

    def py_string(self, can_be_ctype):
        return self.format % \
            (self.left.py_string(self.can_be_ctype[0] and can_be_ctype),
             self.right.py_string(self.can_be_ctype[0] and can_be_ctype))


class ConditionalExpressionNode(ExpressionNode):

    def __init__(self, cond, yes, no):
        ExpressionNode.__init__(self)
        self.cond = cond
        self.yes = yes
        self.no = no

    def visit(self, visitor):
        self.cond.visit(visitor)
        self.yes.visit(visitor)
        self.no.visit(visitor)
        ExpressionNode.visit(self, visitor)

    def evaluate(self, context):
        if self.cond.evaluate(context):
            return self.yes.evaluate(context)
        else:
            return self.no.evaluate(context)

    def py_string(self, can_be_ctype):
        return "(%s if %s else %s)" % \
            (self.yes.py_string(can_be_ctype),
             self.cond.py_string(True),
             self.no.py_string(can_be_ctype))


class AttributeExpressionNode(ExpressionNode):

    def __init__(self, op, format, base, attribute):
        ExpressionNode.__init__(self)
        self.op = op
        self.format = format
        self.base = base
        self.attribute = attribute

        # Attribute access will raise parse errors if you don't do this.
        # Fortunately, the processor module does the same thing to
        # the struct member name.
        if self.attribute in keyword.kwlist:
            self.attribute = "_" + self.attribute

    def visit(self, visitor):
        self.base.visit(visitor)
        ExpressionNode.visit(self, visitor)

    def evaluate(self, context):
        return self.op(self.base.evaluate(context), self.attribute)

    def py_string(self, can_be_ctype):
        if can_be_ctype:
            return self.format % (self.base.py_string(can_be_ctype),
                                  self.attribute)
        else:
            return "(%s.value)" % (self.format %
                                   (self.base.py_string(can_be_ctype), self.attribute))


class CallExpressionNode(ExpressionNode):

    def __init__(self, function, arguments):
        ExpressionNode.__init__(self)
        self.function = function
        self.arguments = arguments

    def visit(self, visitor):
        self.function.visit(visitor)
        for arg in self.arguments:
            arg.visit(visitor)
        ExpressionNode.visit(self, visitor)

    def evaluate(self, context):
        arguments = [arg.evaluate(context) for arg in self.arguments]
        return self.function.evaluate(context)(*arguments)

    def py_string(self, can_be_ctype):
        function = self.function.py_string(can_be_ctype)
        arguments = [x.py_string(can_be_ctype) for x in self.arguments]
        if can_be_ctype:
            return '(%s (%s))' % (function, ", ".join(arguments))
        else:
            return '((%s (%s)).value)' % (function, ", ".join(arguments))

# There seems not to be any reasonable way to translate C typecasts
# into Python. Ctypesgen doesn't try, except for the special case of NULL.


class TypeCastExpressionNode(ExpressionNode):

    def __init__(self, base, ctype):
        ExpressionNode.__init__(self)
        self.base = base
        self.ctype = ctype
        self.isnull = isinstance(ctype, CtypesPointer) and \
            isinstance(base, ConstantExpressionNode) and \
            base.value == 0

    def visit(self, visitor):
        # No need to visit ctype because it isn't actually used
        self.base.visit(visitor)
        ExpressionNode.visit(self, visitor)

    def evaluate(self, context):
        if self.isnull:
            return None
        else:
            return self.base.evaluate(context)

    def py_string(self, can_be_ctype):
        if self.isnull:
            return "None"
        else:
            return self.base.py_string(can_be_ctype)


class UnsupportedExpressionNode(ExpressionNode):

    def __init__(self, message):
        ExpressionNode.__init__(self)
        self.message = message
        self.error(message, 'unsupported-type')

    def evaluate(self, context):
        raise ValueError("Tried to evaluate an unsupported expression "
                         "node: %s" % self.message)

    def __repr__(self):
        return "<UnsupportedExpressionNode>"

    def py_string(self, can_be_ctype):
        raise ValueError("Called py_string() an unsupported expression "
                         "node: %s" % self.message)
