from lldb_rb.lldb_interface import LLDBInterface
from lldb_rb.rb_heap_structs import HeapPage, RbObject
from lldb_rb.constants import *

class RbInspector(LLDBInterface):
    def __init__(self, debugger, result, ruby_globals):
        self.build_environment(debugger)
        self.result = result
        self.ruby_globals = ruby_globals

    def _append_command_output(self, command):
        output1 = self.result.GetOutput()
        self.debugger.GetCommandInterpreter().HandleCommand(command, self.result)
        output2 = self.result.GetOutput()
        self.result.Clear()
        self.result.write(output1)
        self.result.write(output2)

    def string2cstr(self, rstring):
        """Returns the pointer to the C-string in the given String object"""
        if rstring.TypeIsPointerType():
            rstring = rstring.Dereference()

        flags = rstring.GetValueForExpressionPath(".basic->flags").unsigned
        clen = int(rstring.GetValueForExpressionPath(".len").value, 0)
        if flags & self.ruby_globals["RUBY_FL_USER1"]:
            cptr = int(rstring.GetValueForExpressionPath(".as.heap.ptr").value, 0)
        else:
            cptr = int(rstring.GetValueForExpressionPath(".as.embed.ary").location, 0)

        return cptr, clen

    def output_string(self, rstring):
        cptr, clen = self.string2cstr(rstring)
        expr = "print *(const char (*)[%d])%0#x" % (clen, cptr)
        self._append_command_output(expr)

    def fixnum_p(self, x):
        return x & self.ruby_globals["RUBY_FIXNUM_FLAG"] != 0

    def flonum_p(self, x):
        return (x & self.ruby_globals["RUBY_FLONUM_MASK"]) == self.ruby_globals["RUBY_FLONUM_FLAG"]

    def static_sym_p(self, x):
        special_shift = self.ruby_globals["RUBY_SPECIAL_SHIFT"]
        symbol_flag = self.ruby_globals["RUBY_SYMBOL_FLAG"]
        return (x & ~(~0 << special_shift)) == symbol_flag

    def generic_inspect(self, val, rtype):
        tRType = self.target.FindFirstType("struct %s" % rtype).GetPointerType()
        val = val.Cast(tRType)
        self._append_command_output("p *(struct %s *) %0#x" % (rtype, val.GetValueAsUnsigned()))

    def inspect(self, val):
        rbTrue  = self.ruby_globals["RUBY_Qtrue"]
        rbFalse = self.ruby_globals["RUBY_Qfalse"]
        rbNil   = self.ruby_globals["RUBY_Qnil"]
        rbUndef = self.ruby_globals["RUBY_Qundef"]
        rbImmediateMask = self.ruby_globals["RUBY_IMMEDIATE_MASK"]

        num = val.GetValueAsSigned()
        if num == rbFalse:
            print('false', file=self.result)
        elif num == rbTrue:
            print('true', file=self.result)
        elif num == rbNil:
            print('nil', file=self.result)
        elif num == rbUndef:
            print('undef', file=self.result)
        elif self.fixnum_p(num):
            print(num >> 1, file=self.result)
        elif self.flonum_p(num):
            self._append_command_output("print rb_float_value(%0#x)" % val.GetValueAsUnsigned())
        elif self.static_sym_p(num):
            if num < 128:
                print("T_SYMBOL: %c" % num, file=self.result)
            else:
                print("T_SYMBOL: (%x)" % num, file=self.result)
                self._append_command_output("p rb_id2name(%0#x)" % (num >> 8))

        elif num & rbImmediateMask:
            print('immediate(%x)' % num, file=self.result)
        else:
            rval = RbObject(val, self.debugger, self.ruby_globals)
            rval.dump_bits(self.result)

            flaginfo = ""
            if rval.promoted_p():
                flaginfo += "[PROMOTED] "
            if rval.frozen_p():
                flaginfo += "[FROZEN] "

            if rval.is_type("RUBY_T_NONE"):
                print('T_NONE: %s%s' % (flaginfo, val.Dereference()), file=self.result)

            elif rval.is_type("RUBY_T_NIL"):
                print('T_NIL: %s%s' % (flaginfo, val.Dereference()), file=self.result)

            elif rval.is_type("RUBY_T_OBJECT"):
                self.result.write('T_OBJECT: %s' % flaginfo)
                self._append_command_output("print *(struct RObject*)%0#x" % val.GetValueAsUnsigned())

            elif (rval.is_type("RUBY_T_CLASS") or
                  rval.is_type("RUBY_T_MODULE") or
                  rval.is_type("RUBY_T_ICLASS")):
                self.result.write('T_%s: %s' % (rval.type_name.split('_')[-1], flaginfo))
                tRClass = self.target.FindFirstType("struct RClass")

                self._append_command_output("print *(struct RClass*)%0#x" % val.GetValueAsUnsigned())
                if not val.Cast(tRClass).GetChildMemberWithName("ptr").IsValid():
                    self._append_command_output(
                        "print *(struct rb_classext_struct*)%0#x" %
                        (val.GetValueAsUnsigned() + tRClass.GetByteSize())
                    )

            elif rval.is_type("RUBY_T_STRING"):
                self.result.write('T_STRING: %s' % flaginfo)
                tRString = self.target.FindFirstType("struct RString").GetPointerType()

                rb_enc_mask = self.ruby_globals["RUBY_ENCODING_MASK"]
                rb_enc_shift = self.ruby_globals["RUBY_ENCODING_SHIFT"]
                encidx = ((rval.flags & rb_enc_mask) >> rb_enc_shift)
                encname = self.target.FindFirstType("enum ruby_preserved_encindex") \
                        .GetEnumMembers().GetTypeEnumMemberAtIndex(encidx) \
                        .GetName()

                if encname is not None:
                    self.result.write('[%s] ' % encname[14:])
                else:
                    self.result.write('[enc=%d] ' % encidx)

                ptr, len = self.string2cstr(val.Cast(tRString))
                if len == 0:
                    self.result.write("(empty)\n")
                else:
                    self._append_command_output("print *(const char (*)[%d])%0#x" % (len, ptr))

            elif rval.is_type("RUBY_T_SYMBOL"):
                self.result.write('T_SYMBOL: %s' % flaginfo)
                tRSymbol = self.target.FindFirstType("struct RSymbol").GetPointerType()
                tRString = self.target.FindFirstType("struct RString").GetPointerType()

                val = val.Cast(tRSymbol)
                self._append_command_output(
                        'print (ID)%0#x ' % val.GetValueForExpressionPath("->id").GetValueAsUnsigned())
                self.output_string(val.GetValueForExpressionPath("->fstr").Cast(tRString))

            elif rval.is_type("RUBY_T_ARRAY"):
                tRArray = self.target.FindFirstType("struct RArray").GetPointerType()
                len = rval.ary_len()
                ptr = rval.ary_ptr()

                self.result.write("T_ARRAY: %slen=%d" % (flaginfo, len))

                if rval.flags & self.ruby_globals["RUBY_FL_USER1"]:
                    self.result.write(" (embed)")
                elif rval.flags & self.ruby_globals["RUBY_FL_USER2"]:
                    shared = val.GetValueForExpressionPath("->as.heap.aux.shared").GetValueAsUnsigned()
                    self.result.write(" (shared) shared=%016x" % shared)
                else:
                    capa = val.GetValueForExpressionPath("->as.heap.aux.capa").GetValueAsSigned()
                    self.result.write(" (ownership) capa=%d" % capa)
                if len == 0:
                    self.result.write(" {(empty)}\n")
                else:
                    self.result.write("\n")
                    if ptr.GetValueAsSigned() == 0:
                        self._append_command_output(
                                "expression -fx -- ((struct RArray*)%0#x)->as.ary" % val.GetValueAsUnsigned())
                    else:
                        self._append_command_output(
                                "expression -Z %d -fx -- (const VALUE*)%0#x" % (len, ptr.GetValueAsUnsigned()))

            elif rval.is_type("RUBY_T_HASH"):
                self.result.write("T_HASH: %s" % flaginfo)
                self._append_command_output("p *(struct RHash *) %0#x" % val.GetValueAsUnsigned())

            elif rval.is_type("RUBY_T_BIGNUM"):
                tRBignum = self.target.FindFirstType("struct RBignum").GetPointerType()

                sign = '-'
                if (rval.flags & self.ruby_globals["RUBY_FL_USER1"]) != 0:
                    sign = '+'
                len = rval.bignum_len()

                if rval.flags & self.ruby_globals["RUBY_FL_USER2"]:
                    print("T_BIGNUM: sign=%s len=%d (embed)" % (sign, len), file=self.result)
                    self._append_command_output("print ((struct RBignum *) %0#x)->as.ary"
                                                % val.GetValueAsUnsigned())
                else:
                    print("T_BIGNUM: sign=%s len=%d" % (sign, len), file=self.result)
                    print(val.Dereference(), file=self.result)
                    self._append_command_output(
                            "expression -Z %x -fx -- (const BDIGIT*)((struct RBignum*)%d)->as.heap.digits" %
                            (len, val.GetValueAsUnsigned()))

            elif rval.is_type("RUBY_T_FLOAT"):
                self._append_command_output("print ((struct RFloat *)%d)->float_value"
                                            % val.GetValueAsUnsigned())

            elif rval.is_type("RUBY_T_RATIONAL"):
                tRRational = self.target.FindFirstType("struct RRational").GetPointerType()
                val = val.Cast(tRRational)
                self.inspect(val.GetValueForExpressionPath("->num"))
                output = self.result.GetOutput()
                self.result.Clear()
                self.result.write("(Rational) " + output.rstrip() + " / ")
                self.inspect(val.GetValueForExpressionPath("->den"))

            elif rval.is_type("RUBY_T_COMPLEX"):
                tRComplex = self.target.FindFirstType("struct RComplex").GetPointerType()
                val = val.Cast(tRComplex)
                self.inspect(val.GetValueForExpressionPath("->real"))
                real = self.result.GetOutput().rstrip()
                self.result.Clear()
                self.inspect(val.GetValueForExpressionPath("->imag"))
                imag = self.result.GetOutput().rstrip()
                self.result.Clear()
                if not imag.startswith("-"):
                    imag = "+" + imag
                print("(Complex) " + real + imag + "i", file=self.result)

            elif rval.is_type("RUBY_T_REGEXP"):
                tRRegex = self.target.FindFirstType("struct RRegexp").GetPointerType()
                val = val.Cast(tRRegex)
                print("(Regex) ->src {", file=self.result)
                self.inspect(val.GetValueForExpressionPath("->src"))
                print("}", file=self.result)

            elif rval.is_type("RUBY_T_DATA"):
                tRTypedData = self.target.FindFirstType("struct RTypedData").GetPointerType()
                val = val.Cast(tRTypedData)
                flag = val.GetValueForExpressionPath("->typed_flag")

                if flag.GetValueAsUnsigned() == 1:
                    print("T_DATA: %s" %
                          val.GetValueForExpressionPath("->type->wrap_struct_name"),
                          file=self.result)
                    self._append_command_output(
                            "p *(struct RTypedData *) %0#x" % val.GetValueAsUnsigned())
                else:
                    print("T_DATA:", file=self.result)
                    self._append_command_output(
                            "p *(struct RData *) %0#x" % val.GetValueAsUnsigned())

            elif rval.is_type("RUBY_T_NODE"):
                tRNode = self.target.FindFirstType("struct RNode").GetPointerType()
                rbNodeTypeMask = self.ruby_globals["RUBY_NODE_TYPEMASK"]
                rbNodeTypeShift = self.ruby_globals["RUBY_NODE_TYPESHIFT"]

                nd_type = (rval.flags & rbNodeTypeMask) >> rbNodeTypeShift
                val = val.Cast(tRNode)

                self._append_command_output("p (node_type) %d" % nd_type)
                self._append_command_output("p *(struct RNode *) %0#x" % val.GetValueAsUnsigned())

            elif rval.is_type("RUBY_T_IMEMO"):
                imemo_type = ((rval.flags >> self.ruby_globals["RUBY_FL_USHIFT"])
                              & IMEMO_MASK)
                print("T_IMEMO: ", file=self.result)

                self._append_command_output("p (enum imemo_type) %d" % imemo_type)
                self._append_command_output("p *(struct MEMO *) %0#x" % val.GetValueAsUnsigned())

            elif rval.is_type("RUBY_T_FILE"):
                self.generic_inspect(val, "RFile")

            elif rval.is_type("RUBY_T_MOVED"):
                self.generic_inspect(val, "RMoved")

            elif rval.is_type("RUBY_T_MATCH"):
                self.generic_inspect(val, "RMatch")

            elif rval.is_type("RUBY_T_STRUCT"):
                self.generic_inspect(val, "RStruct")

            elif rval.is_type("RUBY_T_ZOMBIE"):
                self.generic_inspect(val, "RZombie")

            else:
                print("Not-handled type %0#x" % rval.type, file=self.result)
                print(val, file=self.result)

