import inspect

from cached_property import cached_property


class DeferredExpression:

    def __init__(self, code=''):
        self.code = code
        binary_ops = (
            ('+', 'add'),
            ('-', 'sub'),
            ('*', 'mul'),
            ('/', 'truediv'),
            ('%', 'mod'),
            ('>>', 'rshift'),
            ('<<', 'lshift'),
            ('&', 'and'),
            ('^', 'xor'),
            ('|', 'or'),
            ('<', 'lt'),
            ('<=', 'le'),
            ('==', 'eq'),
            ('!=', 'ne'),
            ('>', 'gt'),
            ('>=', 'ge'),
        )
        unary_ops = (
            ('-', 'neg'),
            ('+', 'pos'),
            ('abs', 'abs'),
            ('~', 'invert'),
            ('int', 'int'),
            ('float', 'float'),
            ('round', 'round'),
        )
        for c_op, base_name in binary_ops:
            def binary_direct(op):
                def op_func(self_var, value):
                    code = "(%s %s %s)" % (self_var, op, value)
                    return DeferredExpression(code)
                return op_func

            def binary_reflected(op):
                def op_func(self_var, value):
                    code = "(%s %s %s)" % (value, op, self_var)
                    return DeferredExpression(code)
                return op_func

            def augmented_assign(op):
                def op_func(self_var, value):
                    if isinstance(self_var, Variable):
                        self_var._declare_once()
                    code = "%s %s= %s;\n" % (self_var.var_name, op, value)
                    self_var._bsca.append_code(code)
                    return self
                return op_func

            func_name = "__%s__" % base_name
            setattr(self.__class__, func_name, binary_direct(c_op))
            func_name = "__r%s__" % base_name
            setattr(self.__class__, func_name, binary_reflected(c_op))
            func_name = "__i%s__" % base_name
            setattr(self.__class__, func_name, augmented_assign(c_op))

        for c_op, base_name in unary_ops:
            def unary(op):
                def op_func(self_var):
                    code = "%s(%s)" % (op, self_var)
                    return DeferredExpression(code)
                return op_func

            func_name = "__%s__" % base_name
            setattr(self.__class__, func_name, unary(c_op))

    def __str__(self):
        return self.code


class Variable(DeferredExpression):
    """
    Base class for all variables.

    """
    def __init__(self, val=None):
        super(Variable, self).__init__()
        self._declared = False
        if val is not None:
            self._init_val = DeferredExpression(str(val))

    @property
    def _holder_frame(self):
        frame = inspect.currentframe().f_back.f_back.f_back
        while isinstance(frame.f_locals.get('self', ''), Variable):
            frame = frame.f_back
        return frame

    @cached_property
    def _bsca(self):
        return self._holder_frame.f_locals.get('self', '')

    @cached_property
    def var_name(self):
        all_vars = self._holder_frame.f_locals.items()
        for k, var in all_vars:
            if isinstance(var, self.__class__):
                if hash(self) == hash(var):
                    return k
        return ''

    def _declare_once(self):
        if not self._declared:
            c = "%s %s = %s;\n" % (
                self.var_type, self.var_name, self._init_val
            )
            self._bsca.append_code(c)
            self._declared = True
            setattr(self._bsca, self.var_name, self)

    def __str__(self):
        return self.var_name

    def __get__(self, obj, objtype):
        self._declare_once()
        return self

    def __set__(self, obj, value):
        self._declare_once()
        code = "%s = %s;\n" % (self.var_name, value)
        self._bsca.append_code(code)


class IntegerVariable(Variable):
    var_type = "unsigned int"

    def __init__(self, val=0):
        super(IntegerVariable, self).__init__(val)

@@ -55,16 +55,366 @@
 perty%0A%0A%0A
+class BscaDetector:%0A%0A    @property%0A    def _holder_frame(self):%0A        frame = inspect.currentframe().f_back.f_back.f_back%0A        while isinstance(frame.f_locals.get('self', ''), Constant):%0A            frame = frame.f_back%0A        return frame%0A%0A    @cached_property%0A    def _bsca(self):%0A        return self._holder_frame.f_locals.get('self', '')%0A%0A%0A
 class De
@@ -2814,16 +2814,505 @@
 .code%0A%0A%0A
+class Constant(BscaDetector):%0A%0A    def __init__(self, name, value):%0A        self._name = name%0A        self._value = value%0A        self._pattern_name = name%0A%0A    def get_define_code(self):%0A        code = %22#define %25s %7B%25s%7D%5Cn%22 %25 (self._name, self._pattern_name)%0A        return code%0A%0A    def replace_value(self, source):%0A        # WARNING: potentially dangerous%0A        val = %22self._bsca.%25s%22 %25 self._value.split()%5B0%5D%0A        return source.replace(%22%7B%25s%7D%22 %25 self._pattern_name, str(eval(val)))%0A%0A%0A
 class Va
@@ -3336,16 +3336,30 @@
 pression
+, BscaDetector
 ):%0A    %22

Add Constant class for defines generation (#20)