import os
import unittest

import llvm.ee
from llvm.core import *
from llvm import _dwarf, debuginfo
from llvm.test_llvmpy import TestCase

class TestDebugInfo(TestCase):

    def test_dwarf_constants(self):
        dwarf_constants = vars(_dwarf)

        # Version numbers
        self.assertIn("LLVMDebugVersion", dwarf_constants)
        self.assertIn("DWARF_VERSION", dwarf_constants)

        # Tags
        self.assertIn("DW_TAG_compile_unit", dwarf_constants)

        # Language identifiers
        self.assertIn("DW_LANG_Python", dwarf_constants)
        self.assertIn("DW_LANG_C89", dwarf_constants)
        self.assertIn("DW_LANG_C99", dwarf_constants)

        # print sorted([constname for constname in dwarf_constants
        #                             if constname.startswith("DW_LANG_")])

    def test_debug_info_compile_unit(self):
        mod = Module.new('test_debug_info')
        fty = Type.function(Type.float(), [Type.float()])
        square = mod.add_function(fty, 'square')
        bb = square.append_basic_block('entry')
        bldr = Builder.new(bb)

        source_filename = "test_debug_info.py"
        source_filedir = os.path.expanduser("~")

        # Debug info for our file
        filedesc = debuginfo.FileDescriptor(source_filename, source_filedir)

        # Debug info for our function
        subprogram = debuginfo.SubprogramDescriptor(
            filedesc,
            "some_function",
            "some_function",
            "some_function",
            filedesc,
            1,                          # line number
            debuginfo.empty,            # Type descriptor
            llvm_func=square,
        )
        subprograms = debuginfo.MDList([subprogram])

        # Debug info for our basic blocks
        blockdescr1 = debuginfo.BlockDescriptor(subprogram, 2, 4) # lineno, col
        blockdescr2 = debuginfo.BlockDescriptor(blockdescr1, 3, 4) # lineno, col

        # Debug info for our instructions
        posinfo1 = debuginfo.PositionInfoDescriptor(2, 4, blockdescr1)
        posinfo2 = debuginfo.PositionInfoDescriptor(2, 4, blockdescr2)

        # Debug info for our module
        compile_unit = debuginfo.CompileUnitDescriptor(
            # DW_LANG_Python segfaults:
            # llvm/ADT/StringRef.h:79: llvm::StringRef::StringRef(const char*):
            # Assertion `Str && "StringRef cannot be built from a NULL argument"'
            # _dwarf.DW_LANG_Python,
            _dwarf.DW_LANG_C89,
            source_filename,
            source_filedir,
            "my_cool_compiler",
            subprograms=subprograms,
        )

        # Define our module debug data
        compile_unit.define(mod)

        # Build some instructions
        value = square.args[0]
        # result = bldr.fmul(value, value)

        # Generate an instruction that will result in a signal
        result = bldr.fdiv(value, llvm.core.Constant.real(value.type, 0))
        ltrap = llvm.core.Function.intrinsic(mod, INTR_TRAP, [])
        lcall = bldr.call(ltrap, [])
        ret = bldr.ret(result)

        # Annotate instructions with source position
        result.set_metadata("dbg", posinfo1.get_metadata(mod))
        lcall.set_metadata("dbg", posinfo1.get_metadata(mod))
        ret.set_metadata("dbg", posinfo2.get_metadata(mod))

        # ... Aaaand, test...
        # print mod

        modstr = str(mod)

        # Test compile unit
        self.assertIn("my_cool_compiler", modstr)
        self.assertIn(source_filename, modstr)
        self.assertIn(source_filedir, modstr)
        self.assertIn("my_cool_compiler", modstr)

        # Test subprogram
        self.assertIn("some_function", modstr)
        self.assertIn("float (float)* @square", modstr)

        return square


def debug_in_gdb(lfunc):
    # Create an execution engine object. This will create a JIT compiler
    # on platforms that support it, or an interpreter otherwise.
    module = lfunc.module
    ee = llvm.ee.ExecutionEngine.new(module)
    float_type = lfunc.args[0].type

    # The arguments needs to be passed as "GenericValue" objects.
    arg1_value = 5.0
    arg1 = llvm.ee.GenericValue.real(float_type, arg1_value)

    # Now let's compile and run!
    retval = ee.run_function(lfunc, [arg1])
    print retval.as_real(float_type)


if __name__ == '__main__':
#    TestDebugInfo("test_dwarf_constants").debug()
#    TestDebugInfo("test_debug_info_compile_unit").debug()
#    tester = TestDebugInfo("test_debug_info_compile_unit")
#    debug_in_gdb(tester.test_debug_info_compile_unit())
    unittest.main()