
import sys

from pypy.translator.llvm.log import log 
from pypy.translator.llvm.funcnode import FuncNode, FuncTypeNode
from pypy.translator.llvm.extfuncnode import ExternalFuncNode, SimplerExternalFuncNode
from pypy.translator.llvm.structnode import StructNode, StructVarsizeNode, \
     StructTypeNode, StructVarsizeTypeNode, getindexhelper, \
     FixedSizeArrayTypeNode, FixedSizeArrayNode
from pypy.translator.llvm.arraynode import ArrayNode, StrArrayNode, \
     VoidArrayNode, ArrayTypeNode, VoidArrayTypeNode
from pypy.translator.llvm.opaquenode import OpaqueNode, ExtOpaqueNode, \
     OpaqueTypeNode, ExtOpaqueTypeNode
from pypy.rpython.lltypesystem import lltype, llmemory
from pypy.objspace.flow.model import Constant, Variable
from pypy.rpython.memory.lladdress import NULL
from pypy.rlib.objectmodel import Symbolic, ComputedIntSymbolic
from pypy.rlib.objectmodel import CDefinedIntSymbolic
from pypy.rlib import objectmodel

log = log.database 

class Database(object): 
    def __init__(self, genllvm, translator): 
        self.genllvm = genllvm
        self.translator = translator
        self.obj2node = {}
        self._pendingsetup = []
        self._tmpcount = 1
        self.helper2ptr = {}

        self.primitives = Primitives(self)
    
    #_______debuggging______________________________________

    def dump_pbcs(self):
        r = ""
        for k, v in self.obj2node.iteritems():
            
            if isinstance(k, lltype.LowLevelType):
                continue

            assert isinstance(lltype.typeOf(k), lltype.ContainerType)
            # Only dump top levels
            p, _ = lltype.parentlink(k)
            if p is None:
                ref = v.get_ref()
                pbc_ref = v.get_ref()
                
                r += "\ndump_pbcs %s (%s)\n" \
                     "getref -> %s \n" \
                     "pbcref -> %s \n" % (v, k, ref, pbc_ref)
        return r
    
    #_______setting up and preperation______________________________

    def create_constant_node(self, type_, value):
        node = None
        if isinstance(type_, lltype.FuncType):
            if getattr(value._callable, "suggested_primitive", False):
                node = ExternalFuncNode(self, value)
            elif hasattr(value, '_external_name'):
                node = ExternalFuncNode(self, value, value._external_name)

            elif getattr(value, 'external', None) == 'C':
                node = SimplerExternalFuncNode(self, value)
            else:
                node = FuncNode(self, value)

        elif isinstance(type_, lltype.FixedSizeArray):
            node = FixedSizeArrayNode(self, value)

        elif isinstance(type_, lltype.Struct):
            if type_._arrayfld:
                node = StructVarsizeNode(self, value)
            else:
                node = StructNode(self, value)
                    
        elif isinstance(type_, lltype.Array):
            if type_.OF is lltype.Char:
                node = StrArrayNode(self, value)
            elif type_.OF is lltype.Void:
                node = VoidArrayNode(self, value)
            else:
                node = ArrayNode(self, value)

        elif isinstance(type_, lltype.OpaqueType):
            if hasattr(type_, '_exttypeinfo'):
                node = ExtOpaqueNode(self, value)
            else:
                node = OpaqueNode(self, value)

        assert node is not None, "%s not supported" % (type_)
        return node

    def addpending(self, key, node):
        # santity check we at least have a key of the right type
        assert (isinstance(key, lltype.LowLevelType) or
                isinstance(lltype.typeOf(key), lltype.ContainerType))

        assert key not in self.obj2node, (
            "node with key %r already known!" %(key,))
        
        #log("added to pending nodes:", type(key), node)        

        self.obj2node[key] = node 
        self._pendingsetup.append(node)

    def prepare_type(self, type_):
        if type_ in self.obj2node:
            return

        if isinstance(type_, lltype.Primitive):
            pass

        elif isinstance(type_, lltype.Ptr):
            self.prepare_type(type_.TO)

        elif isinstance(type_, lltype.FixedSizeArray):
            self.addpending(type_, FixedSizeArrayTypeNode(self, type_))

        elif isinstance(type_, lltype.Struct):
            if type_._arrayfld:
                self.addpending(type_, StructVarsizeTypeNode(self, type_))
            else:
                self.addpending(type_, StructTypeNode(self, type_))

        elif isinstance(type_, lltype.FuncType):
            self.addpending(type_, FuncTypeNode(self, type_))

        elif isinstance(type_, lltype.Array):
            if type_.OF is lltype.Void:
                self.addpending(type_, VoidArrayTypeNode(self, type_))
            else:
                self.addpending(type_, ArrayTypeNode(self, type_))

        elif isinstance(type_, lltype.OpaqueType):
            if hasattr(type_, '_exttypeinfo'):
                self.addpending(type_, ExtOpaqueTypeNode(self, type_))
            else:
                self.addpending(type_, OpaqueTypeNode(self, type_))

        else:
            assert False, "need to prepare typerepr %s %s" % (type_, type(type_))

    def prepare_type_multi(self, types):
        for type_ in types:
            self.prepare_type(type_)

    def prepare_constant(self, type_, value):
        if isinstance(type_, lltype.Primitive):
            #log.prepareconstant(value, "(is primitive)")
            if type_ is llmemory.Address:
                # prepare the constant data which this address references
                assert isinstance(value, llmemory.fakeaddress)
                if value:
                    self.prepare_constant(lltype.typeOf(value.ptr), value.ptr)
            return

        if isinstance(type_, lltype.Ptr) and isinstance(value._obj, int):
            return
        
        if isinstance(type_, lltype.Ptr):
            type_ = type_.TO
            value = value._obj

            #log.prepareconstant("preparing ptr", value)

            # we dont need a node for nulls
            if value is None:
                return

        # we can share data via pointers
        if value not in self.obj2node: 
            self.addpending(value, self.create_constant_node(type_, value))

        # always add type (it is safe)
        self.prepare_type(type_)
        
    def prepare_arg_value(self, const_or_var):
        """if const_or_var is not already in a dictionary self.obj2node,
        the appropriate node gets constructed and gets added to
        self._pendingsetup and to self.obj2node"""
        if isinstance(const_or_var, Constant):
            ct = const_or_var.concretetype
            if isinstance(ct, lltype.Primitive):
                # special cases for address
                if ct is llmemory.Address:
                    fakedaddress = const_or_var.value
                    if fakedaddress:
                        ptrvalue = fakedaddress.ptr
                        ct = lltype.typeOf(ptrvalue)
                    else:
                        return                        
                elif ct is llmemory.WeakGcAddress:
                    return # XXX sometime soon
                else:
                    return
            else:
                assert isinstance(ct, lltype.Ptr), "Preparation of non primitive and non pointer" 
                ptrvalue = const_or_var.value
                
            value = ptrvalue._obj

            if isinstance(value, int):
                return

            # Only prepare root values at this point 
            if isinstance(ct, lltype.Array) or isinstance(ct, lltype.Struct):
                p, c = lltype.parentlink(value)
                if p is None:
                    #log.prepareargvalue("skipping preparing non root", value)
                    return

            if value is not None and value not in self.obj2node:
                self.addpending(value, self.create_constant_node(ct.TO, value))
        else:
            assert isinstance(const_or_var, Variable)


    def prepare_arg(self, const_or_var):
        #log.prepare(const_or_var)
        self.prepare_type(const_or_var.concretetype)
        self.prepare_arg_value(const_or_var)


    def setup_all(self):
        while self._pendingsetup: 
            node = self._pendingsetup.pop(0)
            #log.settingup(node)
            node.setup()

    def set_entrynode(self, key):
        self.entrynode = self.obj2node[key]    
        return self.entrynode

    def getnodes(self):
        return self.obj2node.itervalues()
        
    # __________________________________________________________
    # Representing variables and constants in LLVM source code 

    def repr_arg(self, arg):
        if isinstance(arg, Constant):
            if isinstance(arg.concretetype, lltype.Primitive):
                return self.primitives.repr(arg.concretetype, arg.value)
            else:
                assert isinstance(arg.value, lltype._ptr)
                if isinstance(arg.value._obj, int):
                    rt = self.repr_type(arg.concretetype)
                    v = repr(arg.value._obj)
                    return 'cast (int %s to %s)'%(v, rt)
                elif not arg.value:
                    return 'null'
                else:
                    node = self.obj2node[arg.value._obj]
                    return node.get_ref()
        else:
            assert isinstance(arg, Variable)
            return "%" + str(arg)

    def repr_arg_type(self, arg):
        assert isinstance(arg, (Constant, Variable))
        ct = arg.concretetype 
        return self.repr_type(ct)
    
    def repr_type(self, type_):
        try:
            return self.obj2node[type_].ref 
        except KeyError: 
            if isinstance(type_, lltype.Primitive):
                return self.primitives[type_]
            elif isinstance(type_, lltype.Ptr):
                return self.repr_type(type_.TO) + '*'
            else: 
                raise TypeError("cannot represent %r" %(type_,))
            
    def repr_argwithtype(self, arg):
        return self.repr_arg(arg), self.repr_arg_type(arg)
            
    def repr_arg_multi(self, args):
        return [self.repr_arg(arg) for arg in args]

    def repr_arg_type_multi(self, args):
        return [self.repr_arg_type(arg) for arg in args]

    def repr_constant(self, value):
        " returns node and repr as tuple "
        type_ = lltype.typeOf(value)
        if isinstance(type_, lltype.Primitive):
            repr = self.primitives.repr(type_, value)
            return None, "%s %s" % (self.repr_type(type_), repr)

        elif isinstance(type_, lltype.Ptr):
            toptr = self.repr_type(type_)
            value = value._obj

            # special case, null pointer
            if value is None:
                return None, "%s null" % toptr

            if isinstance(value, int):
                return None, '%s cast (int %s to %s)'%(toptr, value, toptr)

            node = self.obj2node[value]
            ref = node.get_pbcref(toptr)
            return node, "%s %s" % (toptr, ref)

        elif isinstance(type_, (lltype.Array, lltype.Struct)):
            node = self.obj2node[value]
            return node, node.constantvalue()

        elif isinstance(type_, lltype.OpaqueType):
            node = self.obj2node[value]
            if isinstance(node, ExtOpaqueNode):
                return node, node.constantvalue()
                
        assert False, "%s not supported" % (type(value))

    def repr_tmpvar(self): 
        count = self._tmpcount 
        self._tmpcount += 1
        return "%tmp_" + str(count) 

    def repr_constructor(self, type_):
        return self.obj2node[type_].constructor_ref

    def repr_name(self, obj):
        " simply returns a reference to constant value "
        return self.obj2node[obj].ref

    def repr_value(self, value):
        # XXX Testing
        return self.obj2node[value].get_ref()

    # __________________________________________________________
    # Other helpers

    def get_machine_word(self):
        return self.primitives[lltype.Signed]

    def get_machine_uword(self):
        return self.primitives[lltype.Unsigned]

    def is_function_ptr(self, arg):
        if isinstance(arg, (Constant, Variable)): 
            arg = arg.concretetype 
            if isinstance(arg, lltype.Ptr):
                if isinstance(arg.TO, lltype.FuncType):
                    return True
        return False

    def get_childref(self, parent, child):
        node = self.obj2node[parent]
        return node.get_childref(child)


class Primitives(object):
    def __init__(self, database):        
        self.database = database
        self.types = {
            lltype.Char: "sbyte",
            lltype.Bool: "bool",
            lltype.Float: "double",
            lltype.UniChar: "uint",
            lltype.Void: "void",
            lltype.UnsignedLongLong: "ulong",
            lltype.SignedLongLong: "long",
            llmemory.Address: "sbyte*",
            llmemory.WeakGcAddress: "sbyte*",
            }

        # 32 bit platform
        if sys.maxint == 2**31-1:
            self.types.update({
                lltype.Signed: "int",
                lltype.Unsigned: "uint" })
            
        # 64 bit platform
        elif sys.maxint == 2**63-1:        
            self.types.update({
                lltype.Signed: "long",
                lltype.Unsigned: "ulong" })            
        else:
            raise Exception("Unsupported platform - unknown word size")

        self.reprs = {
            lltype.SignedLongLong : self.repr_signed,
            lltype.Signed : self.repr_signed,
            lltype.UnsignedLongLong : self.repr_default,
            lltype.Unsigned : self.repr_default,
            lltype.Float : self.repr_float,
            lltype.Char : self.repr_char,
            lltype.UniChar : self.repr_unichar,
            lltype.Bool : self.repr_bool,
            lltype.Void : self.repr_void,
            llmemory.Address : self.repr_address,
            llmemory.WeakGcAddress : self.repr_weakgcaddress,
            }        

        try:
            import ctypes
        except ImportError:
            pass
        else:
            from pypy.rpython.rctypes import rcarithmetic as rcarith

            def update(from_, type):
                if from_ not in self.types:
                    self.types[from_] = type
                if from_ not in self.reprs:
                    self.reprs[from_] = self.repr_default
            
            for k, v in [
                (rcarith.CByte, self.types[lltype.Char]),
                (rcarith.CUByte, 'ubyte'),
                (rcarith.CShort, 'short'),
                (rcarith.CUShort, 'ushort'),
                (rcarith.CInt, 'int'),
                (rcarith.CUInt, 'uint'),
                (rcarith.CLong, self.types[lltype.Signed]),
                (rcarith.CULong, self.types[lltype.Unsigned]),
                (rcarith.CLonglong, self.types[lltype.SignedLongLong]),
                (rcarith.CULonglong, self.types[lltype.UnsignedLongLong])]:
                update(k, v)
        
    def __getitem__(self, key):
        return self.types[key]
        
    def repr(self, type_, value):
        try:
            reprfn = self.reprs[type_]
        except KeyError:
            raise Exception, "unsupported primitive type %r, value %r" % (type_, value)
        else:
            return reprfn(type_, value)
        
    def repr_default(self, type_, value):
        return str(value)

    def repr_bool(self, type_, value):
        return str(value).lower() #False --> false

    def repr_void(self, type_, value):
        return 'void'
  
    def repr_char(self, type_, value):
        x = ord(value)
        if x >= 128:
            r = "cast (ubyte %s to sbyte)" % x
        else:
            r = str(x)
        return r

    def repr_unichar(self, type_, value):
        return str(ord(value))

    def repr_float(self, type_, value):
        repr = "%f" % value
        # llvm requires a . when using e notation
        if "e" in repr and "." not in repr:
            repr = repr.replace("e", ".0e")
        elif repr in ["inf", "nan"]:
            # Need hex repr
            import struct
            packed = struct.pack("d", value)
            if sys.byteorder == 'little':
                packed = packed[::-1]
            
            repr = "0x" + "".join([("%02x" % ord(ii)) for ii in packed])
        return repr

    def repr_address(self, type_, value):
        if not value:
            return 'null'
        ptr = value.ptr
        node, ref = self.database.repr_constant(ptr)
        res = "cast(%s to sbyte*)" % (ref,)
        return res

    def repr_weakgcaddress(self, type_, value):
        assert isinstance(value, llmemory.fakeweakaddress)
        log.WARNING("XXX weakgcaddress completely ignored...")
        return 'null'

    def repr_signed(self, type_, value):
        if isinstance(value, Symbolic):
            return self.repr_symbolic(type_, value)
        return str(value)
    
    def repr_symbolic(self, type_, value):
        """ returns an int value for pointer arithmetic - not sure this is the
        llvm way, but well XXX need to fix adr_xxx operations  """
        if (type(value) == llmemory.GCHeaderOffset or
            type(value) == llmemory.AddressOffset):
            repr = 0
        
        elif isinstance(value, llmemory.AddressOffset):
            from_, indices, to = self.get_offset(value)
            indices_as_str = ", ".join("%s %s" % (w, i) for w, i in indices)
            r = self.database.repr_type
            repr = "cast(%s* getelementptr(%s* null, %s) to int)" % (r(to),
                                                                     r(from_),
                                                                     indices_as_str)
        elif isinstance(value, ComputedIntSymbolic):
            # force the ComputedIntSymbolic to become a real integer value now
            repr = '%d' % value.compute_fn()
        elif isinstance(value, CDefinedIntSymbolic):
            if value is objectmodel.malloc_zero_filled:
                repr = '1'
            elif value is objectmodel._we_are_jitted:
                repr = '0'
            else:
                raise NotImplementedError("CDefinedIntSymbolic: %r" % (value,))
        else:
            raise NotImplementedError("symbolic: %r" % (value,))
        
        return repr
    
    def get_offset(self, value, initialindices=None):
        " return (from_type, (indices, ...), to_type) "        
        word = self.database.get_machine_word()
        uword = self.database.get_machine_uword()
        indices = initialindices or [(word, 0)]

        if isinstance(value, llmemory.ItemOffset):
            # skips over a fixed size item (eg array access)
            from_ = value.TYPE
            lasttype, lastvalue = indices[-1]
            assert lasttype == word
            indices[-1] = (word, lastvalue + value.repeat)
            to = value.TYPE
        
        elif isinstance(value, llmemory.FieldOffset):
            # jumps to a field position in a struct
            from_ = value.TYPE
            pos = getindexhelper(value.fldname, value.TYPE)
            indices.append((uword, pos))
            to = getattr(value.TYPE, value.fldname)            

        elif isinstance(value, llmemory.ArrayLengthOffset):
            # jumps to the place where the array length is stored
            from_ = value.TYPE     # <Array of T> or <GcArray of T>
            assert isinstance(value.TYPE, lltype.Array)
            indices.append((uword, 0))
            to = lltype.Signed

        elif isinstance(value, llmemory.ArrayItemsOffset):
            # jumps to the beginning of array area
            from_ = value.TYPE
            if not isinstance(value.TYPE, lltype.FixedSizeArray):
                indices.append((uword, 1))
            indices.append((word, 0))    # go to the 1st item
            to = value.TYPE.OF

        elif isinstance(value, llmemory.CompositeOffset):
            from_, indices, to = self.get_offset(value.offsets[0], indices)
            for item in value.offsets[1:]:
                _, indices, to = self.get_offset(item, indices)

        else:
            raise Exception("unsupported offset")

        return from_, indices, to    
