#
# TODO: Add support for vector.
#

import contextlib
import llvm.core as lc
import llvm.ee as le
import llvm.passes as lp
from llvm import LLVMException
from . import shortnames as types

###
#  Utilities
###

class FunctionAlreadyExists(NameError):
    pass

def _is_int(ty):
    return isinstance(ty, lc.IntegerType)

def _is_real(ty):
    tys = [ lc.Type.float(),
            lc.Type.double(),
            lc.Type.x86_fp80(),
            lc.Type.fp128(),
            lc.Type.ppc_fp128() ]
    return any(ty == x for x in tys)

def _is_vector(ty, of=None):
    if isinstance(ty, lc.VectorType):
        if of is not None:
            return of(ty.element)
        return True
    else:
        return False

def _is_pointer(ty):
    return isinstance(ty, lc.PointerType)

def _is_block_terminated(bb):
    instrs = bb.instructions
    return len(instrs) > 0 and instrs[-1].is_terminator

def _is_struct(ty):
    return isinstance(ty, lc.StructType)

def _is_cstruct(ty):
    try:
        return issubclass(ty, CStruct)
    except TypeError:
        return False

def _list_values(iterable):
    return [i.value for i in iterable]

def _auto_coerce_index(cbldr, idx):
    if not isinstance(idx, CValue):
        idx = cbldr.constant(types.int, idx)
    return idx

@contextlib.contextmanager
def _change_block_temporarily(builder, bb):
    origbb = builder.basic_block
    builder.position_at_end(bb)
    yield
    builder.position_at_end(origbb)

@contextlib.contextmanager
def _change_block_temporarily_dummy(*args):
    yield

class CastError(TypeError):
    def __init__(self, orig, to):
        super(CastError, self).__init__("Cannot cast from %s to %s" % (orig, to))

class _IfElse(object):
    '''if-else construct.

    Example
    -------
    with cbuilder.ifelse(cond) as ifelse:
        with ifelse.then():
            # code when cond is true
            # this block is mandatory
        with ifelse.otherwise():
            # code when cond is false
            # this block is optional
    '''

    def __init__(self, parent, cond):
        self.parent = parent
        self.cond = cond
        self._to_close = []

    @contextlib.contextmanager
    def then(self):
        self._bbif = self.parent.function.append_basic_block('if.then')
        self._bbelse = self.parent.function.append_basic_block('if.else')

        builder = self.parent.builder
        builder.cbranch(self.cond.value, self._bbif, self._bbelse)

        builder.position_at_end(self._bbif)

        yield

        self._to_close.extend([self._bbif, self._bbelse, builder.basic_block])

    @contextlib.contextmanager
    def otherwise(self):
        builder = self.parent.builder
        builder.position_at_end(self._bbelse)
        yield
        self._to_close.append(builder.basic_block)

    def close(self):
        self._to_close.append(self.parent.builder.basic_block)
        bbend = self.parent.function.append_basic_block('if.end')
        builder = self.parent.builder
        closed_count = 0
        for bb in self._to_close:
            if not _is_block_terminated(bb):
                with _change_block_temporarily(builder, bb):
                    builder.branch(bbend)
                    closed_count += 1
        builder.position_at_end(bbend)
        if not closed_count:
            self.parent.unreachable()

class _Loop(object):
    '''while...do loop.

    Example
    -------
    with cbuilder.loop() as loop:
        with loop.condition() as setcond:
            # Put the condition evaluation here
            setcond( cond )         # set loop condition
            # Do not put code after setcond(...)
        with loop.body():
            # Put the code of the loop body here

    Use loop.break_loop() to break out of the loop.
    Use loop.continue_loop() to jump the condition evaulation.
    '''

    def __init__(self, parent):
        self.parent = parent

    @contextlib.contextmanager
    def condition(self):
        builder = self.parent.builder
        self._bbcond = self.parent.function.append_basic_block('loop.cond')
        self._bbbody = self.parent.function.append_basic_block('loop.body')
        self._bbend = self.parent.function.append_basic_block('loop.end')

        builder.branch(self._bbcond)

        builder.position_at_end(self._bbcond)

        def setcond(cond):
            builder.cbranch(cond.value, self._bbbody, self._bbend)

        yield setcond

    @contextlib.contextmanager
    def body(self):
        builder = self.parent.builder
        builder.position_at_end(self._bbbody)
        yield self
        # close last block
        if not _is_block_terminated(builder.basic_block):
            builder.branch(self._bbcond)

    def break_loop(self):
        self.parent.builder.branch(self._bbend)

    def continue_loop(self):
        self.parent.builder.branch(self._bbcond)

    def close(self):
        builder = self.parent.builder
        builder.position_at_end(self._bbend)

class CBuilder(object):
    '''
    A wrapper class for features in llvm-py package
    to allow user to use C-like high-level language contruct easily.
    '''

    def __init__(self, function):
        '''constructor

        function : is an empty function to be populating.
        '''
        self.function = function
        self.declare_block = self.function.append_basic_block('decl')
        self.first_body_block = self.function.append_basic_block('body')
        self.builder = lc.Builder.new(self.first_body_block)
        self.target_data = le.TargetData.new(self.function.module.data_layout)
        self._auto_inline_list = []
        # Prepare arguments. Make all function arguments behave like variables.
        self.args = []
        for arg in function.args:
            var = self.var(arg.type, arg, name=arg.name)
            self.args.append(var)

    @staticmethod
    def new_function(mod, name, ret, args):
        '''factory method

        Create a new function in the module and return a CBuilder instance.
        '''
        functype = lc.Type.function(ret, args)
        func = mod.add_function(functype, name=name)
        return CBuilder(func)

    def depends(self, fndecl):
        '''add function dependency

        Returns a CFunc instance and define the function if it is not defined.

        fndecl : is a callable that takes a `llvm.core.Module` and returns
                 a function pointer.
        '''
        return CFunc(self, fndecl(self.function.module))

    def printf(self, fmt, *args):
        '''printf() from libc

        fmt : a character string holding printf format string.
        *args : additional variable arguments.
        '''
        from .libc import LibC
        libc = LibC(self)
        ret = libc.printf(fmt, *args)
        return CTemp(self, ret)

    def debug(self, *args):
        '''debug print

        Use printf to dump the values of all arguments.
        '''
        type_mapper = {
            'i8' : '%c',
            'i16': '%hd',
            'i32': '%d',
            'i64': '%ld',
            'double': '%e',
        }
        itemsfmt = []
        items = []
        for i in args:
            if isinstance(i, str):
                itemsfmt.append(i.replace('%', '%%'))
            elif isinstance(i.type, lc.PointerType):
                itemsfmt.append("%p")
                items.append(i)
            else:
                tyname = str(i.type)
                if tyname == 'float':
                    # auto convert float to double
                    ty = '%e'
                    i = i.cast(types.double)
                else:
                    ty = type_mapper[tyname]
                itemsfmt.append(ty)
                items.append(i)
        fmt = ' '.join(itemsfmt) + '\n'
        return self.printf(self.constant_string(fmt), *items)

    def sizeof(self, ty):
        bldr = self.builder
        ptrty = types.pointer(ty)
        first = lc.Constant.null(ptrty)
        second = bldr.gep(first, [lc.Constant.int(types.intp, 1)])

        firstint = bldr.ptrtoint(first, types.intp)
        secondint = bldr.ptrtoint(second, types.intp)
        diff = bldr.sub(secondint, firstint)
        return CTemp(self, diff)

    def min(self, x, y):
        z = self.var(x.type)
        with self.ifelse( x < y ) as ifelse:
            with ifelse.then():
                z.assign(x)
            with ifelse.otherwise():
                z.assign(y)
        return z

    def var(self, ty, value=None, name=''):
        '''allocate variable on the stack

        ty : variable type
        value : [optional] initializer value
        name : [optional] name used in LLVM IR
        '''
        with _change_block_temporarily(self.builder, self.declare_block):
            # goto the first block
            is_cstruct = _is_cstruct(ty)
            if is_cstruct:
                cstruct = ty
                ty = ty.llvm_type()
            ptr = self.builder.alloca(ty, name=name)
        # back to the body
        if value is not None:
            if isinstance(value, CValue):
                value = value.value
            elif not isinstance(value, lc.Value):
                value = self.constant(ty, value).value
            self.builder.store(value, ptr)
        if is_cstruct:
            return cstruct(self, ptr)
        else:
            return CVar(self, ptr)

    def var_copy(self, val, name=''):
        '''allocate a new variable by copying another value

        The new variable has the same type and value of `val`.
        '''
        return self.var(val.type, val, name=name)

    def array(self, ty, count, name=''):
        '''allocate an array on the stack

        ty : array element type
        count : array size; can be python int, llvm.core.Constant, or CValue
        name : [optional] name used in LLVM IR
        '''
        if isinstance(count, int) or isinstance(count, lc.Constant):
            # Only go to the first block if array size is fixed.
            contexthelper = _change_block_temporarily
        else:
            # Do not go to the first block if the array size is dynamic.
            contexthelper = _change_block_temporarily_dummy

        with contexthelper(self.builder, self.declare_block):
            if _is_cstruct(ty): # array of struct?
                cstruct = ty
                ty = ty.llvm_type()

            if isinstance(count, CValue):
                count = count.value
            elif not isinstance(count, lc.Value):
                count = self.constant(types.int, count).value

            ptr = self.builder.alloca_array(ty, count, name=name)
            return CArray(self, ptr)

    def ret(self, val=None):
        '''insert return statement

        val : if is `None`, insert return-void
              else, return `val`
        '''
        retty = self.function.type.pointee.return_type
        if val is not None:
            if val.type != retty:
                errmsg = "Return type mismatch"
                raise TypeError(errmsg)
            self.builder.ret(val.value)
        else:
            if retty != types.void:
                # errmsg = "Cannot return void"
                # raise TypeError(errmsg, retty)
                bb = self.function.append_basic_block('unreachable_bb')
                self.builder.position_at_end(bb)
                self.builder.ret(self.builder.alloca(retty, name='undef'))
            else:
                self.builder.ret_void()

    @contextlib.contextmanager
    def ifelse(self, cond):
        '''start a if-else block

        cond : branch condition
        '''
        cb = _IfElse(self, cond)
        yield cb
        cb.close()

    @contextlib.contextmanager
    def loop(self):
        '''start a loop block
        '''
        cb = _Loop(self)
        yield cb
        cb.close()

    @contextlib.contextmanager
    def forever(self):
        '''start a forever loop block
        '''
        with self.loop() as loop:
            with loop.condition() as setcond:
                NULL = self.constant_null(types.int)
                setcond( NULL == NULL )
            with loop.body():
                yield loop

    @contextlib.contextmanager
    def for_range(self, *args):
        '''start a for-range block.

        *args : same as arguments of builtin `range()`
        '''
        def check_arg(x):
            if isinstance(x, int):
                return self.constant(types.int, x)
            if not isinstance(x, IntegerValue):
                raise TypeError(x, "All args must be of integer type.")
            return x

        if len(args) == 3:
            start, stop, step = map(check_arg, args)
        elif len(args) == 2:
            start, stop = map(check_arg, args)
            step = self.constant(start.type, 1)
        elif len(args) == 1:
            stop = check_arg(args[0])
            start = self.constant(stop.type, 0)
            step = self.constant(stop.type, 1)
        else:
            raise TypeError("Invalid # of arguments: 1, 2 or 3")

        idx = self.var_copy(start)
        with self.loop() as loop:
            with loop.condition() as setcond:
                setcond( idx < stop )
            with loop.body():
                yield loop, idx
                idx += step

    def position_at_end(self, bb):
        '''reposition inserter to the end of basic-block

        bb : a basic block
        '''
        self.basic_block = bb
        self.builder.position_at_end(bb)

    def close(self):
        '''end code generation
        '''
        # Close declaration block
        with _change_block_temporarily(self.builder, self.declare_block):
            self.builder.branch(self.first_body_block)

        # Do the auto inlining
        for callinst in self._auto_inline_list:
            lc.inline_function(callinst)

    def constant(self, ty, val):
        '''create a constant

        ty : data type
        val : initializer
        '''
        if isinstance(ty, lc.IntegerType):
            res = lc.Constant.int(ty, val)
        elif ty == types.float or ty == types.double:
            res = lc.Constant.real(ty, val)
        else:
            raise TypeError("Cannot auto build constant "
                            "from %s and value %s" % (ty, val))
        return CTemp(self, res)

    def constant_null(self, ty):
        '''create a zero filled constant

        ty : data type
        '''
        res = lc.Constant.null(ty)
        return CTemp(self, res)

    def constant_string(self, string):
        '''create a constant string

        This will de-duplication string of same content to minimize memory use.
        '''
        mod = self.function.module
        collision = 0
        name_fmt = '.conststr.%x.%x'
        content = lc.Constant.stringz(string)
        while True:
            name = name_fmt % (hash(string), collision)
            try:
                # check if the name already exists
                globalstr = mod.get_global_variable_named(name)
            except LLVMException:
                # new constant string
                globalstr = mod.add_global_variable(content.type, name=name)
                globalstr.initializer = content
                globalstr.linkage = lc.LINKAGE_LINKONCE_ODR
                globalstr.global_constant = True
            else:
                # compare existing content
                existed = str(globalstr.initializer)
                if existed != str(content):
                    collision += 1
                    continue # loop until we resolve the name collision

            return CTemp(self, globalstr.bitcast(
                                           types.pointer(content.type.element)))


    def get_intrinsic(self, intrinsic_id, tys):
        '''get intrinsic function

        intrinsic_id : numerical ID of target intrinsic
        tys : type argument for the intrinsic
        '''
        lfunc = lc.Function.intrinsic(self.function.module, intrinsic_id, tys)
        return CFunc(self, lfunc)

    def get_function_named(self, name):
        '''get function by name
        '''
        m = self.function.module
        func = m.get_function_named(name)
        return CFunc(self, func)

    def is_terminated(self):
        '''is the current basic-block terminated?
        '''
        return _is_block_terminated(self.builder.basic_block)

    def atomic_cmpxchg(self, ptr, old, val, ordering, crossthread=True):
        '''atomic compare-exchange

        ptr : pointer to data
        old : old value to compare to
        val : new value
        ordering : memory ordering as a string
        crossthread : set to `False` for single-thread code

        Returns the old value on success.
        '''
        res = self.builder.atomic_cmpxchg(ptr.value, old.value, val.value,
                                          ordering, crossthread)
        return CTemp(self, res)

    def atomic_xchg(self, ptr, val, ordering, crossthread=True):
        '''atomic exchange

        ptr : pointer to data
        val : new value
        ordering : memory ordering as a string
        crossthread : set to `False` for single-thread code

        Returns the old value
        '''

        res = self.builder.atomic_xchg(ptr.value, val.value,
                                       ordering, crossthread)
        return CTemp(self, res)

    def atomic_add(self, ptr, val, ordering, crossthread=True):
        '''atomic add

        ptr : pointer to data
        val : new value
        ordering : memory ordering as a string
        crossthread : set to `False` for single-thread code

        Returns the computation result of the operation
        '''

        res = self.builder.atomic_add(ptr.value, val.value,
                                      ordering, crossthread)
        return CTemp(self, res)

    def atomic_sub(self, ptr, val, ordering, crossthread=True):
        '''atomic sub

        See `atomic_add` for parameters documentation
        '''
        res = self.builder.atomic_sub(ptr.value, val.value,
                                      ordering, crossthread)
        return CTemp(self, res)

    def atomic_and(self, ptr, val, ordering, crossthread=True):
        '''atomic bitwise and

        See `atomic_add` for parameters documentation
        '''
        res = self.builder.atomic_and(ptr.value, val.value,
                                      ordering, crossthread)
        return CTemp(self, res)

    def atomic_nand(self, ptr, val, ordering, crossthread=True):
        '''atomic bitwise nand

        See `atomic_add` for parameters documentation
        '''
        res = self.builder.atomic_nand(ptr.value, val.value,
                                       ordering, crossthread)
        return CTemp(self, res)

    def atomic_or(self, ptr, val, ordering, crossthread=True):
        '''atomic bitwise or

        See `atomic_add` for parameters documentation
        '''
        res = self.builder.atomic_or(ptr.value, val.value,
                                     ordering, crossthread)
        return CTemp(self, res)

    def atomic_xor(self, ptr, val, ordering, crossthread=True):
        '''atomic bitwise xor

        See `atomic_add` for parameters documentation
        '''

        res = self.builder.atomic_xor(ptr.value, val.value,
                                      ordering, crossthread)
        return CTemp(self, res)

    def atomic_max(self, ptr, val, ordering, crossthread=True):
        '''atomic signed maximum between value at `ptr` and `val`

        See `atomic_add` for parameters documentation
        '''
        res = self.builder.atomic_max(ptr.value, val.value,
                                      ordering, crossthread)
        return CTemp(self, res)

    def atomic_min(self, ptr, val, ordering, crossthread=True):
        '''atomic signed minimum between value at `ptr` and `val`

        See `atomic_add` for parameters documentation
        '''
        res = self.builder.atomic_min(ptr.value, val.value,
                                      ordering, crossthread)
        return CTemp(self, res)

    def atomic_umax(self, ptr, val, ordering, crossthread=True):
        '''atomic unsigned maximum between value at `ptr` and `val`

        See `atomic_add` for parameters documentation
        '''
        res = self.builder.atomic_umax(ptr.value, val.value,
                                       ordering, crossthread)
        return CTemp(self, res)

    def atomic_umin(self, ptr, val, ordering, crossthread=True):
        '''atomic unsigned minimum between value at `ptr` and `val`

        See `atomic_add` for parameters documentation
        '''
        res = self.builder.atomic_umin(ptr.value, val.value,
                                       ordering, crossthread)
        return CTemp(self, res)

    def atomic_load(self, ptr, ordering, align=1, crossthread=True):
        '''atomic load

        ptr : pointer to the value to load
        align : memory alignment in bytes
        See `atomic_add` for other documentation of other parameters
        '''
        res = self.builder.atomic_load(ptr.value, ordering, align, crossthread)
        return CTemp(self, res)

    def atomic_store(self, val, ptr, ordering, align=1, crossthread=True):
        '''atomic store

        ptr : pointer to where to store
        val : value to store
        align : memory alignment in bytes
        See `atomic_add` for other documentation of other parameters
        '''

        res = self.builder.atomic_store(val.value, ptr.value, ordering,
                                        align, crossthread)
        return CTemp(self, res)

    def fence(self, ordering, crossthread=True):
        '''insert memory fence
        '''
        res = self.builder.fence(ordering, crossthread)
        return CTemp(self, res)

    def alignment(self, ty):
        '''get minimum alignment of `ty`
        '''
        return self.abi.abi_alignment(ty)

    @property
    def abi(self):
        return self.target_data

    def unreachable(self):
        '''insert instruction that causes segfault some platform (Intel),
        or no-op on others.

        It has no defined semantic.
        '''
        self.builder.unreachable()

    def add_auto_inline(self, callinst):
        self._auto_inline_list.append(callinst)


    def set_memop_non_temporal(self, ldst):
        const_one = self.constant(types.int, 1).value
        md = lc.MetaData.get(self.function.module, [const_one])
        ldst.set_metadata('nontemporal', md)


class CFuncRef(object):
    '''create a function reference to use with `CBuilder.depends`

    Either from name, type and pointer,
    Or from  a llvm.core.FunctionType instance
    '''
    def __init__(self, *args, **kwargs):
        def one_arg(fn):
            self._fn = fn
            self._name = fn.name

        def three_arg(name, ty, ptr):
            self._name = name
            self._type = ty
            self._ptr = ptr

        try:
            three_arg(*args, **kwargs)
            self._meth = self._from_pointer
        except TypeError:
            one_arg(*args, **kwargs)
            self._meth = self._from_func

    def __call__(self, module):
        return self._meth()

    def _from_func(self):
        return self._fn

    def _from_pointer(self):
        fnptr = types.pointer(self._type)
        ptr = lc.Constant.int(types.intp, self._ptr)
        ptr = ptr.inttoptr(fnptr)
        return ptr

    def __str__(self):
        return self._name


class CDefinition(object):
    '''represents function definition

    Inherit from this class to create a new function definition.

    Class Members
    -------------
    _name_ : name of the function
    _retty_ : return type
    _argtys_ : argument names and types as list of tuples;
               e.g. [ ( 'myarg', lc.Type.int() ), ... ]
    '''
    _name_ = ''             # name of the function; should overide in subclass
    _retty_  = types.void # return type; can overide in subclass
    _argtys_ = []       # a list of tuple(name, type, [attributes]); can overide in subclass

    def __init__(self, *args, **kwargs):
        self.specialize(*args, **kwargs)
        self.cbuilder = None

    def specialize(self, *args, **kwargs):
        """
        Override in subclasses
        """

    def specialize_name(self):
        """
        Specialize the class name to enable multiple function definitions
        """
        cls = type(self)

        counter = getattr(cls, 'counter', 0)
        cls._name_ = "%s_%d" % (cls._name_, counter)
        cls.counter = counter + 1

    def define(self, module, optimize=True):
        '''define the function in the module.

        Raises NameError if a function of the same name has already been
        defined.
        '''
        functype = lc.Type.function(self._retty_, [arg[1] for arg in self._argtys_])
        name = self._name_
        if not name:
            raise AttributeError("Function name cannot be empty.")

        func = module.get_or_insert_function(functype, name=name)

        if not func.is_declaration: # already defined?
            raise FunctionAlreadyExists(func)

        # Name all arguments
        for i, arginfo in enumerate(self._argtys_):
            name = arginfo[0]
            func.args[i].name = name
            if len(arginfo) > 2:
                for attr in arginfo[2]:
                    func.args[i].add_attribute(attr)

        # Create builder and populate body
        self.cbuilder = CBuilder(func)
        self.body(*self.cbuilder.args)
        self.cbuilder.close()
        self.cbuilder = None

        if optimize:
            fpm = lp.FunctionPassManager.new(module)
            pmb = lp.PassManagerBuilder.new()
            pmb.opt_level = 2
            pmb.populate(fpm)
            fpm.run(func)

        return func

    def __call__(self, module):
        # We don't really have to overload __call__ to do things like
        # defining functions...
        try:
            func = self.define(module)
        except FunctionAlreadyExists as e:
            (func,) = e
        return func

    def __getattr__(self, attr):
        return getattr(self.cbuilder, attr)

    def body(self):
        '''overide this function to define the body.
        '''
        raise NotImplementedError

    def __str__(self):
        return self._name_

class CValue(object):
    def __init__(self, parent, handle):
        self.__parent = parent
        self.__handle = handle

    @property
    def handle(self):
        return self.__handle

    @property
    def parent(self):
        return self.__parent

    def _temp(self, val):
        return CTemp(self.parent, val)

def _get_operator_provider(ty):
    if _is_pointer(ty):
        return PointerValue
    elif _is_int(ty):
        return IntegerValue
    elif _is_real(ty):
        return RealValue
    elif _is_vector(ty):
        inner = _get_operator_provider(ty.element)
        return type(str(ty), (inner, VectorIndexing), {})
    elif _is_struct(ty):
        return StructValue
    else:
        assert False, (str(ty), type(ty))

class CTemp(CValue):
    def __new__(cls, parent, handle):
        meta = _get_operator_provider(handle.type)
        base = type(str('%s_%s' % (cls.__name__, handle.type)), (cls, meta), {})
        return object.__new__(base)

    def __init__(self, *args, **kws):
        super(CTemp, self).__init__(*args, **kws)
        self._init_mixin()

    @property
    def value(self):
        return self.handle

    @property
    def type(self):
        return self.value.type

class CVar(CValue):
    def __new__(cls, parent, ptr):
        meta = _get_operator_provider(ptr.type.pointee)
        base = type(str('%s_%s' % (cls.__name__, ptr.type.pointee)), (cls, meta), {})
        return object.__new__(base)

    def __init__(self, parent, ptr):
        super(CVar, self).__init__(parent, ptr)
        self._init_mixin()
        self.invariant = False

    def reference(self):
        return self._temp(self.handle)

    @property
    def ref(self):
        return self.reference()

    @property
    def value(self):
        return self.parent.builder.load(self.ref.value)

    @property
    def type(self):
        return self.ref.type.pointee

    def assign(self, val, **kws):
        if self.invariant:
            raise TypeError("Storing to invariant variable.")
        self.parent.builder.store(val.value, self.ref.value, **kws)
        return self

    def __iadd__(self, rhs):
        return self.assign(self.__add__(rhs))

    def __isub__(self, rhs):
        return self.assign(self.__sub__(rhs))

    def __imul__(self, rhs):
        return self.assign(self.__mul__(rhs))

    def __idiv__(self, rhs):
        return self.assign(self.__div__(rhs))

    def __imod__(self, rhs):
        return self.assign(self.__mod__(rhs))

    def __ilshift__(self, rhs):
        return self.assign(self.__lshift__(rhs))

    def __irshift__(self, rhs):
        return self.assign(self.__rshift__(rhs))

    def __iand__(self, rhs):
        return self.assign(self.__and__(rhs))

    def __ior__(self, rhs):
        return self.assign(self.__or__(rhs))

    def __ixor__(self, rhs):
        return self.assign(self.__xor__(rhs))


class OperatorMixin(object):
    def _init_mixin(self):
        pass

class IntegerValue(OperatorMixin):

    def _init_mixin(self):
        self._unsigned = False

    def _get_unsigned(self):
        return self._unsigned

    def _set_unsigned(self, unsigned):
        self._unsigned = bool(unsigned)

    unsigned = property(_get_unsigned, _set_unsigned)

    def __add__(self, rhs):
        return self._temp(self.parent.builder.add(self.value, rhs.value))

    def __sub__(self, rhs):
        return self._temp(self.parent.builder.sub(self.value, rhs.value))

    def __mul__(self, rhs):
        return self._temp(self.parent.builder.mul(self.value, rhs.value))

    def __div__(self, rhs):
        if self.unsigned:
            return self._temp(self.parent.builder.udiv(self.value, rhs.value))
        else:
            return self._temp(self.parent.builder.sdiv(self.value, rhs.value))

    def __mod__(self, rhs):
        if self.unsigned:
            return self._temp(self.parent.builder.urem(self.value, rhs.value))
        else:
            return self._temp(self.parent.builder.srem(self.value, rhs.value))

    def __ilshift__(self, rhs):
        return self._temp(self.parent.builder.shl(self.value, rhs.value))

    def __irshift__(self, rhs):
        if self.unsigned:
            return self._temp(self.self.parent.builder.lshr(self.value, rhs.value))
        else:
            return self._temp(self.parent.builder.ashr(self.value, rhs.value))

    def __iand__(self, rhs):
        return self._temp(self.parent.builder.and_(self.value, rhs.value))

    def __ior__(self, rhs):
        return self._temp(self.parent.builder.or_(self.value, rhs.value))

    def __ixor__(self, rhs):
        return self._temp(self.parent.builder.xor(self.value, rhs.value))

    def __lt__(self, rhs):
        if self.unsigned:
            return self._temp(self.parent.builder.icmp(lc.ICMP_ULT, self.value, rhs.value))
        else:
            return self._temp(self.parent.builder.icmp(lc.ICMP_SLT, self.value, rhs.value))

    def __le__(self, rhs):
        if self.unsigned:
            return self._temp(self.parent.builder.icmp(lc.ICMP_ULE, self.value, rhs.value))
        else:
            return self._temp(self.parent.builder.icmp(lc.ICMP_SLE, self.value, rhs.value))

    def __eq__(self, rhs):
        return self._temp(self.parent.builder.icmp(lc.ICMP_EQ, self.value, rhs.value))

    def __ne__(self, rhs):
        return self._temp(self.parent.builder.icmp(lc.ICMP_NE, self.value, rhs.value))

    def __gt__(self, rhs):
        if self.unsigned:
            return self._temp(self.parent.builder.icmp(lc.ICMP_UGT, self.value, rhs.value))
        else:
            return self._temp(self.parent.builder.icmp(lc.ICMP_SGT, self.value, rhs.value))

    def __ge__(self, rhs):
        if self.unsigned:
            return self._temp(self.parent.builder.icmp(lc.ICMP_UGE, self.value, rhs.value))
        else:
            return self._temp(self.parent.builder.icmp(lc.ICMP_SGE, self.value, rhs.value))

    def __neg__(self):
        return self._temp(self.parent.builder.neg(self.value))

    def cast(self, ty, unsigned=False):
        if ty == self.type:
            return self._temp(self.value)

        if _is_real(ty):
            if self.unsigned or unsigned:
                return self._temp(self.parent.builder.uitofp(self.value, ty))
            else:
                return self._temp(self.parent.builder.sitofp(self.value, ty))
        elif _is_int(ty):
            if self.parent.abi.size(self.type) < self.parent.abi.size(ty):
                if self.unsigned or unsigned:
                    return self._temp(self.parent.builder.zext(self.value, ty))
                else:
                    return self._temp(self.parent.builder.sext(self.value, ty))
            else:
                return self._temp(self.parent.builder.trunc(self.value, ty))
        elif _is_pointer(ty) and _is_int(self.type):
            return self._temp(self.parent.builder.inttoptr(self.value, ty))
        elif _is_int(ty) and _is_pointer(self.type):
            return self._temp(self.parent.builder.ptrtoint(self.value, ty))

        raise CastError(self.type, ty)

class RealValue(OperatorMixin):
    def __add__(self, rhs):
        return self._temp(self.parent.builder.fadd(self.value, rhs.value))

    def __sub__(self, rhs):
        return self._temp(self.parent.builder.fsub(self.value, rhs.value))

    def __mul__(self, rhs):
        return self._temp(self.parent.builder.fmul(self.value, rhs.value))

    def __div__(self, rhs):
        return self._temp(self.parent.builder.fdiv(self.value, rhs.value))

    def __mod__(self, rhs):
        return self._temp(self.parent.builder.frem(self.value, rhs.value))

    def __lt__(self, rhs):
        return self._temp(self.parent.builder.fcmp(lc.FCMP_OLT, self.value, rhs.value))

    def __le__(self, rhs):
        return self._temp(self.parent.builder.fcmp(lc.FCMP_OLE, self.value, rhs.value))

    def __eq__(self, rhs):
        return self._temp(self.parent.builder.fcmp(lc.FCMP_OEQ, self.value, rhs.value))

    def __ne__(self, rhs):
        return self._temp(self.parent.builder.fcmp(lc.FCMP_ONE, self.value, rhs.value))

    def __gt__(self, rhs):
        return self._temp(self.parent.builder.fcmp(lc.FCMP_OGT, self.value, rhs.value))

    def __ge__(self, rhs):
        return self._temp(self.parent.builder.fcmp(lc.FCMP_OGE, self.value, rhs.value))

    def cast(self, ty, unsigned=False):
        if ty == self.type:
            return self._temp(self.value)

        if _is_int(ty):
            if unsigned:
                return self._temp(self.parent.builder.fptoui(self.value, ty))
            else:
                return self._temp(self.parent.builder.fptosi(self.value, ty))

        if _is_real(ty):
            if self.parent.abi.size(self.type) > self.parent.abi.size(ty):
                return self._temp(self.parent.builder.fptrunc(self.value, ty))
            else:
                return self._temp(self.parent.builder.fpext(self.value, ty))

        raise CastError(self.type, ty)


class PointerIndexing(OperatorMixin):
    def __getitem__(self, idx):
        '''implement access indexing

        Uses GEP.
        '''
        bldr = self.parent.builder
        if type(idx) is slice:
            # just handle case by case
            # Case #1: A[idx:] get pointer offset by idx
            if not idx.step and not idx.stop:
                idx = _auto_coerce_index(self.parent, idx.start)
                ptr = bldr.gep(self.value, [idx.value], inbounds=True)
                return CArray(self.parent, ptr)
        else: # return an variable at idx
            idx = _auto_coerce_index(self.parent, idx)
            ptr = bldr.gep(self.value, [idx.value], inbounds=True)
            return CVar(self.parent, ptr)

    def __setitem__(self, idx, val):
        idx = _auto_coerce_index(self.parent, idx)
        bldr = self.parent.builder
        self[idx].assign(val)

class PointerCasting(OperatorMixin):
    def cast(self, ty):
        if ty == self.type:
            return self._temp(self.value)

        if _is_pointer(ty):
            return self._temp(self.parent.builder.bitcast(self.value, ty))
        raise CastError(self.type, ty)



class VectorIndexing(OperatorMixin):
    def __getitem__(self, idx):
        '''implement access indexing

        Uses GEP.
        '''
        bldr = self.parent.builder
        idx = _auto_coerce_index(self.parent, idx)
        val = bldr.extract_element(self.value, idx.value)
        return CTemp(self.parent, val)

    def __setitem__(self, idx, val):
        idx = _auto_coerce_index(self.parent, idx)
        bldr = self.parent.builder
        vec = bldr.insert_element(self.value, val.value, idx.value)
        bldr.store(vec, self.ref.value)

class PointerValue(PointerIndexing, PointerCasting):

    def load(self, **kws):
        return self._temp(self.parent.builder.load(self.value, **kws))

    def store(self, val, nontemporal=False, **kws):
        inst = self.parent.builder.store(val.value, self.value, **kws)
        if nontemporal:
            self.parent.set_memop_non_temporal(inst)
        return inst

    def atomic_load(self, ordering, align=None, crossthread=True):
        '''atomic load memory for pointer types

        align : overide to control memory alignment; otherwise the default
                alignment of the type is used.

        Other parameters are the same as `CBuilder.atomic_load`
        '''
        if align is None:
            align = self.parent.alignment(self.type.pointee)
        inst = self.parent.builder.atomic_load(self.value, ordering, align,
                                               crossthread=crossthread)
        return self._temp(inst)

    def atomic_store(self, value, ordering, align=None,  crossthread=True):
        '''atomic memory store for pointer types

        align : overide to control memory alignment; otherwise the default
                alignment of the type is used.

        Other parameters are the same as `CBuilder.atomic_store`
        '''
        if align is None:
            align = self.parent.alignment(self.type.pointee)
        self.parent.builder.atomic_store(value.ptr, self.value, ordering,
                                         align=align, crossthread=crossthread)

    def atomic_cmpxchg(self, old, new, ordering, crossthread=True):
        '''atomic compare-exchange for pointer types

        Other parameters are the same as `CBuilder.atomic_cmpxchg`
        '''
        inst = self.parent.builder.atomic_cmpxchg(self.value, old.value,
                                                  new.value, ordering,
                                                  crossthread=crossthread)
        return self._temp(inst)

    def as_struct(self, cstruct_class, volatile=False):
        '''load a pointer to a structure and assume a structure interface
        '''
        ptr = self.parent.builder.load(self.value, volatile=volatile)
        return cstruct_class(self.parent, self.value)

class StructValue(OperatorMixin):

    def as_struct(self, cstruct_class):
        '''assume a structure interface
        '''
        return cstruct_class(self.parent, self.ref.value)


class CFunc(CValue, PointerCasting):
    '''Wraps function pointer
    '''
    def __init__(self, parent, func):
        super(CFunc, self).__init__(parent, func)

    @property
    def function(self):
        return self.handle

    def __call__(self, *args, **opts):
        '''Call the function with the given arguments

        *args : variable arguments of CValue instances
        '''
        arg_values = _list_values(args)
        ftype = self.function.type.pointee
        for i, (exp, got) in enumerate(zip(ftype.args, arg_values)):
            if exp != got.type:
                raise TypeError("At call to %s, "
                                "argument %d mismatch: %s != %s"
                                % (self.function.name, i, exp, got.type))
        res = self.parent.builder.call(self.function, arg_values)

        if hasattr(self.function, 'calling_convention'):
            res.calling_convention = self.function.calling_convention

        if opts.get('inline'):
            self.parent.add_auto_inline(res)

        if ftype.return_type != lc.Type.void():
            return CTemp(self.parent, res)

    @property
    def value(self):
        return self.function

    @property
    def type(self):
        return self.function.type


class CArray(CValue, PointerIndexing, PointerCasting):
    '''wraps a array

    Similar to C arrays
    '''
    def __init__(self, parent, base):
        super(CArray, self).__init__(parent, base)

    @property
    def value(self):
        return self.handle

    def reference(self):
        return self._temp(self.value)

    @property
    def type(self):
        return self.value.type

    def vector_load(self, count, align=0):
        parent = self.parent
        builder = parent.builder
        values = [self[i] for i in range(count)]

        vecty = types.vector(self.type.pointee, count)
        vec = builder.load(builder.bitcast(self.value, types.pointer(vecty)),
                           align=align)
        return self._temp(vec)

    def vector_store(self, vec, align=0):
        if vec.type.element != self.type.pointee:
            raise TypeError("Type mismatch; expect %s but got %s" % \
                            (vec.type.element, self.type.pointee))
        parent = self.parent
        builder = parent.builder
        vecptr = builder.bitcast(self.value, types.pointer(vec.type))
        builder.store(vec.value, vecptr, align=align)
        return self


class CStruct(CValue):
    '''Wraps a structure

    Structure in LLVM can be identified by name of layout.

    Subclass to define a new structure. All fields are defined in the
    `_fields_` class attribute as a list of tuple (name, type).

    Can define new methods which gets inlined to the parent CBuilder.
    '''

    def __init__(self, parent, ptr):
        super(CStruct, self).__init__(parent, ptr)
        makeind = lambda x: self.parent.constant(types.int, x).value

        for i, (fd, _) in enumerate(self._fields_):
            gep = self.parent.builder.gep(ptr, [makeind(0), makeind(i)])
            gep.name = "%s.%s" % (type(self).__name__, fd)
            if hasattr(self, fd):
                raise AttributeError("Field name shadows another attribute")
            setattr(self, fd, CVar(self.parent, gep))

        self.type = self.llvm_type()

    def reference(self):
        return self._temp(self.handle)

    @classmethod
    def llvm_type(cls):
        return lc.Type.struct([v for k, v in cls._fields_])

    @classmethod
    def from_numba_struct(cls, context, struct_type):
        class Struct(cls):
            _fields_ = [(name, type.to_llvm(context))
            for name, type in struct_type.fields]
        return Struct


class CExternal(object):
    '''subclass to define external interface

    All class attributes that are `llvm.core.FunctionType` are converted
    to `CFunc` instance during instantiation.
    '''

    _calling_convention_ = None # default

    def __init__(self, cbuilder):
        is_func = lambda x: isinstance(x, lc.FunctionType)
        non_magic = lambda s: not ( s.startswith('__') and s.endswith('__') )

        to_declare = []
        for fname in filter(non_magic, vars(type(self))):
            ftype = getattr(self, fname)
            if is_func(ftype):
                to_declare.append((fname, ftype))

        mod = cbuilder.function.module
        for fname, ftype in to_declare:
            func = mod.get_or_insert_function(ftype, name=fname)
            if self._calling_convention_:
                func.calling_convention = self._calling_convention_

            if func.type.pointee != ftype:
                raise NameError("Function has already been declared "
                                "with a different type: %s != %s"
                                % (func.type, ftype) )
            setattr(self, fname, CFunc(cbuilder, func))


