#===- core.py - Python LLVM Bindings -------------------------*- python -*--===#
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
#===------------------------------------------------------------------------===#
from __future__ import print_function

from .common import LLVMObject
from .common import c_object_p
from .common import get_library

from . import enumerations

from ctypes import POINTER
from ctypes import byref
from ctypes import c_char_p
from ctypes import c_uint

import sys

__all__ = [
    "lib",
    "Enums",
    "OpCode",
    "MemoryBuffer",
    "Module",
    "Value",
    "Function",
    "BasicBlock",
    "Instruction",
    "Context",
    "PassRegistry"
]

lib = get_library()
Enums = []

class LLVMEnumeration(object):
    """Represents an individual LLVM enumeration."""

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

    def __repr__(self):
        return '%s.%s' % (self.__class__.__name__,
                          self.name)

    @classmethod
    def from_value(cls, value):
        """Obtain an enumeration instance from a numeric value."""
        result = cls._value_map.get(value, None)

        if result is None:
            raise ValueError('Unknown %s: %d' % (cls.__name__,
                                                 value))

        return result

    @classmethod
    def register(cls, name, value):
        """Registers a new enumeration.

        This is called by this module for each enumeration defined in
        enumerations. You should not need to call this outside this module.
        """
        if value in cls._value_map:
            raise ValueError('%s value already registered: %d' % (cls.__name__,
                                                                  value))
        enum = cls(name, value)
        cls._value_map[value] = enum
        setattr(cls, name, enum)

class Attribute(LLVMEnumeration):
    """Represents an individual Attribute enumeration."""

    _value_map = {}

    def __init__(self, name, value):
        super(Attribute, self).__init__(name, value)

class OpCode(LLVMEnumeration):
    """Represents an individual OpCode enumeration."""

    _value_map = {}

    def __init__(self, name, value):
        super(OpCode, self).__init__(name, value)

class TypeKind(LLVMEnumeration):
    """Represents an individual TypeKind enumeration."""

    _value_map = {}

    def __init__(self, name, value):
        super(TypeKind, self).__init__(name, value)

class Linkage(LLVMEnumeration):
    """Represents an individual Linkage enumeration."""

    _value_map = {}

    def __init__(self, name, value):
        super(Linkage, self).__init__(name, value)

class Visibility(LLVMEnumeration):
    """Represents an individual visibility enumeration."""

    _value_map = {}

    def __init__(self, name, value):
        super(Visibility, self).__init__(name, value)

class CallConv(LLVMEnumeration):
    """Represents an individual calling convention enumeration."""

    _value_map = {}

    def __init__(self, name, value):
        super(CallConv, self).__init__(name, value)

class IntPredicate(LLVMEnumeration):
    """Represents an individual IntPredicate enumeration."""

    _value_map = {}

    def __init__(self, name, value):
        super(IntPredicate, self).__init__(name, value)

class RealPredicate(LLVMEnumeration):
    """Represents an individual RealPredicate enumeration."""

    _value_map = {}

    def __init__(self, name, value):
        super(RealPredicate, self).__init__(name, value)

class LandingPadClauseTy(LLVMEnumeration):
    """Represents an individual LandingPadClauseTy enumeration."""

    _value_map = {}

    def __init__(self, name, value):
        super(LandingPadClauseTy, self).__init__(name, value)

class MemoryBuffer(LLVMObject):
    """Represents an opaque memory buffer."""

    def __init__(self, filename=None):
        """Create a new memory buffer.

        Currently, we support creating from the contents of a file at the
        specified filename.
        """
        if filename is None:
            raise Exception("filename argument must be defined")

        memory = c_object_p()
        out = c_char_p(None)

        result = lib.LLVMCreateMemoryBufferWithContentsOfFile(filename,
                byref(memory), byref(out))

        if result:
            raise Exception("Could not create memory buffer: %s" % out.value)

        LLVMObject.__init__(self, memory, disposer=lib.LLVMDisposeMemoryBuffer)

    def __len__(self):
        return lib.LLVMGetBufferSize(self)

class Value(LLVMObject):
    
    def __init__(self, value):
        LLVMObject.__init__(self, value)

    @property
    def name(self):
        return lib.LLVMGetValueName(self)

    def dump(self):
        lib.LLVMDumpValue(self)
    
    def get_operand(self, i):
        return Value(lib.LLVMGetOperand(self, i))
    
    def set_operand(self, i, v):
        return lib.LLVMSetOperand(self, i, v)
    
    def __len__(self):
        return lib.LLVMGetNumOperands(self)

class Module(LLVMObject):
    """Represents the top-level structure of an llvm program in an opaque object."""

    def __init__(self, module, name=None, context=None):
        LLVMObject.__init__(self, module, disposer=lib.LLVMDisposeModule)

    @classmethod
    def CreateWithName(cls, module_id):
        m = Module(lib.LLVMModuleCreateWithName(module_id))
        Context.GetGlobalContext().take_ownership(m)
        return m

    @property
    def datalayout(self):
        return lib.LLVMGetDataLayout(self)

    @datalayout.setter
    def datalayout(self, new_data_layout):
        """new_data_layout is a string."""
        lib.LLVMSetDataLayout(self, new_data_layout)

    @property
    def target(self):
        return lib.LLVMGetTarget(self)

    @target.setter
    def target(self, new_target):
        """new_target is a string."""
        lib.LLVMSetTarget(self, new_target)

    def dump(self):
        lib.LLVMDumpModule(self)

    class __function_iterator(object):
        def __init__(self, module, reverse=False):
            self.module = module
            self.reverse = reverse
            if self.reverse:
                self.function = self.module.last
            else:
                self.function = self.module.first
        
        def __iter__(self):
            return self
        
        def __next__(self):
            if not isinstance(self.function, Function):
                raise StopIteration("")
            result = self.function
            if self.reverse:
                self.function = self.function.prev
            else:
                self.function = self.function.next
            return result

        if sys.version_info.major == 2:
            next = __next__

    def __iter__(self):
        return Module.__function_iterator(self)

    def __reversed__(self):
        return Module.__function_iterator(self, reverse=True)

    @property
    def first(self):
        return Function(lib.LLVMGetFirstFunction(self))

    @property
    def last(self):
        return Function(lib.LLVMGetLastFunction(self))

    def print_module_to_file(self, filename):
        out = c_char_p(None)
        # Result is inverted so 0 means everything was ok.
        result = lib.LLVMPrintModuleToFile(self, filename, byref(out))        
        if result:
            raise RuntimeError("LLVM Error: %s" % out.value)

class Function(Value):

    def __init__(self, value):
        Value.__init__(self, value)
    
    @property
    def next(self):
        f = lib.LLVMGetNextFunction(self)
        return f and Function(f)
    
    @property
    def prev(self):
        f = lib.LLVMGetPreviousFunction(self)
        return f and Function(f)
    
    @property
    def first(self):
        b = lib.LLVMGetFirstBasicBlock(self)
        return b and BasicBlock(b)

    @property
    def last(self):
        b = lib.LLVMGetLastBasicBlock(self)
        return b and BasicBlock(b)

    class __bb_iterator(object):
        def __init__(self, function, reverse=False):
            self.function = function
            self.reverse = reverse
            if self.reverse:
                self.bb = function.last
            else:
                self.bb = function.first
        
        def __iter__(self):
            return self
        
        def __next__(self):
            if not isinstance(self.bb, BasicBlock):
                raise StopIteration("")
            result = self.bb
            if self.reverse:
                self.bb = self.bb.prev
            else:
                self.bb = self.bb.next
            return result

        if sys.version_info.major == 2:
            next = __next__
    
    def __iter__(self):
        return Function.__bb_iterator(self)

    def __reversed__(self):
        return Function.__bb_iterator(self, reverse=True)
    
    def __len__(self):
        return lib.LLVMCountBasicBlocks(self)

class BasicBlock(LLVMObject):
    
    def __init__(self, value):
        LLVMObject.__init__(self, value)

    @property
    def next(self):
        b = lib.LLVMGetNextBasicBlock(self)
        return b and BasicBlock(b)

    @property
    def prev(self):
        b = lib.LLVMGetPreviousBasicBlock(self)
        return b and BasicBlock(b)
    
    @property
    def first(self):
        i = lib.LLVMGetFirstInstruction(self)
        return i and Instruction(i)

    @property
    def last(self):
        i = lib.LLVMGetLastInstruction(self)
        return i and Instruction(i)

    def __as_value(self):
        return Value(lib.LLVMBasicBlockAsValue(self))
    
    @property
    def name(self):
        return lib.LLVMGetValueName(self.__as_value())

    def dump(self):
        lib.LLVMDumpValue(self.__as_value())

    def get_operand(self, i):
        return Value(lib.LLVMGetOperand(self.__as_value(),
                                        i))
    
    def set_operand(self, i, v):
        return lib.LLVMSetOperand(self.__as_value(),
                                  i, v)
    
    def __len__(self):
        return lib.LLVMGetNumOperands(self.__as_value())

    class __inst_iterator(object):
        def __init__(self, bb, reverse=False):            
            self.bb = bb
            self.reverse = reverse
            if self.reverse:
                self.inst = self.bb.last
            else:
                self.inst = self.bb.first
        
        def __iter__(self):
            return self
        
        def __next__(self):
            if not isinstance(self.inst, Instruction):
                raise StopIteration("")
            result = self.inst
            if self.reverse:
                self.inst = self.inst.prev
            else:
                self.inst = self.inst.next
            return result

        if sys.version_info.major == 2:
            next = __next__

    def __iter__(self):
        return BasicBlock.__inst_iterator(self)

    def __reversed__(self):
        return BasicBlock.__inst_iterator(self, reverse=True)


class Instruction(Value):

    def __init__(self, value):
        Value.__init__(self, value)

    @property
    def next(self):
        i = lib.LLVMGetNextInstruction(self)
        return i and Instruction(i)

    @property
    def prev(self):
        i = lib.LLVMGetPreviousInstruction(self)
        return i and Instruction(i)

    @property
    def opcode(self):
        return OpCode.from_value(lib.LLVMGetInstructionOpcode(self))

class Context(LLVMObject):

    def __init__(self, context=None):
        if context is None:
            context = lib.LLVMContextCreate()
            LLVMObject.__init__(self, context, disposer=lib.LLVMContextDispose)
        else:
            LLVMObject.__init__(self, context)

    @classmethod
    def GetGlobalContext(cls):
        return Context(lib.LLVMGetGlobalContext())

class PassRegistry(LLVMObject):
    """Represents an opaque pass registry object."""

    def __init__(self):
        LLVMObject.__init__(self,
                            lib.LLVMGetGlobalPassRegistry())

def register_library(library):
    # Initialization/Shutdown declarations.
    library.LLVMInitializeCore.argtypes = [PassRegistry]
    library.LLVMInitializeCore.restype = None

    library.LLVMInitializeTransformUtils.argtypes = [PassRegistry]
    library.LLVMInitializeTransformUtils.restype = None

    library.LLVMInitializeScalarOpts.argtypes = [PassRegistry]
    library.LLVMInitializeScalarOpts.restype = None

    library.LLVMInitializeObjCARCOpts.argtypes = [PassRegistry]
    library.LLVMInitializeObjCARCOpts.restype = None

    library.LLVMInitializeVectorization.argtypes = [PassRegistry]
    library.LLVMInitializeVectorization.restype = None

    library.LLVMInitializeInstCombine.argtypes = [PassRegistry]
    library.LLVMInitializeInstCombine.restype = None

    library.LLVMInitializeAggressiveInstCombiner.argtypes = [PassRegistry]
    library.LLVMInitializeAggressiveInstCombiner.restype = None

    library.LLVMInitializeIPO.argtypes = [PassRegistry]
    library.LLVMInitializeIPO.restype = None

    library.LLVMInitializeInstrumentation.argtypes = [PassRegistry]
    library.LLVMInitializeInstrumentation.restype = None

    library.LLVMInitializeAnalysis.argtypes = [PassRegistry]
    library.LLVMInitializeAnalysis.restype = None

    library.LLVMInitializeCodeGen.argtypes = [PassRegistry]
    library.LLVMInitializeCodeGen.restype = None

    library.LLVMInitializeTarget.argtypes = [PassRegistry]
    library.LLVMInitializeTarget.restype = None

    library.LLVMShutdown.argtypes = []
    library.LLVMShutdown.restype = None

    # Pass Registry declarations.
    library.LLVMGetGlobalPassRegistry.argtypes = []
    library.LLVMGetGlobalPassRegistry.restype = c_object_p

    # Context declarations.
    library.LLVMContextCreate.argtypes = []
    library.LLVMContextCreate.restype = c_object_p

    library.LLVMContextDispose.argtypes = [Context]
    library.LLVMContextDispose.restype = None

    library.LLVMGetGlobalContext.argtypes = []
    library.LLVMGetGlobalContext.restype = c_object_p

    # Memory buffer declarations
    library.LLVMCreateMemoryBufferWithContentsOfFile.argtypes = [c_char_p,
            POINTER(c_object_p), POINTER(c_char_p)]
    library.LLVMCreateMemoryBufferWithContentsOfFile.restype = bool

    library.LLVMGetBufferSize.argtypes = [MemoryBuffer]

    library.LLVMDisposeMemoryBuffer.argtypes = [MemoryBuffer]

    # Module declarations
    library.LLVMModuleCreateWithName.argtypes = [c_char_p]
    library.LLVMModuleCreateWithName.restype = c_object_p

    library.LLVMDisposeModule.argtypes = [Module]
    library.LLVMDisposeModule.restype = None

    library.LLVMGetDataLayout.argtypes = [Module]
    library.LLVMGetDataLayout.restype = c_char_p

    library.LLVMSetDataLayout.argtypes = [Module, c_char_p]
    library.LLVMSetDataLayout.restype = None

    library.LLVMGetTarget.argtypes = [Module]
    library.LLVMGetTarget.restype = c_char_p

    library.LLVMSetTarget.argtypes = [Module, c_char_p]
    library.LLVMSetTarget.restype = None

    library.LLVMDumpModule.argtypes = [Module]
    library.LLVMDumpModule.restype = None

    library.LLVMPrintModuleToFile.argtypes = [Module, c_char_p,
                                              POINTER(c_char_p)]
    library.LLVMPrintModuleToFile.restype = bool

    library.LLVMGetFirstFunction.argtypes = [Module]
    library.LLVMGetFirstFunction.restype = c_object_p

    library.LLVMGetLastFunction.argtypes = [Module]
    library.LLVMGetLastFunction.restype = c_object_p

    library.LLVMGetNextFunction.argtypes = [Function]
    library.LLVMGetNextFunction.restype = c_object_p

    library.LLVMGetPreviousFunction.argtypes = [Function]
    library.LLVMGetPreviousFunction.restype = c_object_p

    # Value declarations.
    library.LLVMGetValueName.argtypes = [Value]
    library.LLVMGetValueName.restype = c_char_p

    library.LLVMDumpValue.argtypes = [Value]
    library.LLVMDumpValue.restype = None

    library.LLVMGetOperand.argtypes = [Value, c_uint]
    library.LLVMGetOperand.restype = c_object_p

    library.LLVMSetOperand.argtypes = [Value, Value, c_uint]
    library.LLVMSetOperand.restype = None

    library.LLVMGetNumOperands.argtypes = [Value]
    library.LLVMGetNumOperands.restype = c_uint

    # Basic Block Declarations.
    library.LLVMGetFirstBasicBlock.argtypes = [Function]
    library.LLVMGetFirstBasicBlock.restype = c_object_p

    library.LLVMGetLastBasicBlock.argtypes = [Function]
    library.LLVMGetLastBasicBlock.restype = c_object_p

    library.LLVMGetNextBasicBlock.argtypes = [BasicBlock]
    library.LLVMGetNextBasicBlock.restype = c_object_p

    library.LLVMGetPreviousBasicBlock.argtypes = [BasicBlock]
    library.LLVMGetPreviousBasicBlock.restype = c_object_p

    library.LLVMGetFirstInstruction.argtypes = [BasicBlock]
    library.LLVMGetFirstInstruction.restype = c_object_p

    library.LLVMGetLastInstruction.argtypes = [BasicBlock]
    library.LLVMGetLastInstruction.restype = c_object_p

    library.LLVMBasicBlockAsValue.argtypes = [BasicBlock]
    library.LLVMBasicBlockAsValue.restype = c_object_p

    library.LLVMCountBasicBlocks.argtypes = [Function]
    library.LLVMCountBasicBlocks.restype = c_uint

    # Instruction Declarations.
    library.LLVMGetNextInstruction.argtypes = [Instruction]
    library.LLVMGetNextInstruction.restype = c_object_p

    library.LLVMGetPreviousInstruction.argtypes = [Instruction]
    library.LLVMGetPreviousInstruction.restype = c_object_p

    library.LLVMGetInstructionOpcode.argtypes = [Instruction]
    library.LLVMGetInstructionOpcode.restype = c_uint

def register_enumerations():
    if Enums:
        return None
    enums = [
        (Attribute, enumerations.Attributes),
        (OpCode, enumerations.OpCodes),
        (TypeKind, enumerations.TypeKinds),
        (Linkage, enumerations.Linkages),
        (Visibility, enumerations.Visibility),
        (CallConv, enumerations.CallConv),
        (IntPredicate, enumerations.IntPredicate),
        (RealPredicate, enumerations.RealPredicate),
        (LandingPadClauseTy, enumerations.LandingPadClauseTy),
    ]
    for enum_class, enum_spec in enums:
        for name, value in enum_spec:
            print(name, value)
            enum_class.register(name, value)
    return enums

def initialize_llvm():
    Context.GetGlobalContext()
    p = PassRegistry()
    lib.LLVMInitializeCore(p)
    lib.LLVMInitializeTransformUtils(p)
    lib.LLVMInitializeScalarOpts(p)
    lib.LLVMInitializeObjCARCOpts(p)
    lib.LLVMInitializeVectorization(p)
    lib.LLVMInitializeInstCombine(p)
    lib.LLVMInitializeIPO(p)
    lib.LLVMInitializeInstrumentation(p)
    lib.LLVMInitializeAnalysis(p)
    lib.LLVMInitializeCodeGen(p)
    lib.LLVMInitializeTarget(p)

register_library(lib)
Enums = register_enumerations()
initialize_llvm()
