# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/

import lldb

from lldbutils import utils


def summarize_string(valobj, internal_dict):
    data = valobj.GetChildMemberWithName("mData")
    length = valobj.GetChildMemberWithName("mLength").GetValueAsUnsigned(0)
    return utils.format_string(data, length)


def summarize_atom(valobj, internal_dict):
    target = lldb.debugger.GetSelectedTarget()
    length = valobj.GetChildMemberWithName("mLength").GetValueAsUnsigned()
    string = target.EvaluateExpression(
        "(char16_t*)%s.GetUTF16String()" % valobj.GetName()
    )
    return utils.format_string(string, length)


class TArraySyntheticChildrenProvider:
    def __init__(self, valobj, internal_dict):
        self.valobj = valobj
        self.header = self.valobj.GetChildMemberWithName("mHdr")
        self.element_type = self.valobj.GetType().GetTemplateArgumentType(0)
        self.element_size = self.element_type.GetByteSize()
        header_size = self.header.GetType().GetPointeeType().GetByteSize()
        self.element_base_addr = self.header.GetValueAsUnsigned(0) + header_size

    def num_children(self):
        return (
            self.header
            .Dereference()
            .GetChildMemberWithName("mLength")
            .GetValueAsUnsigned(0)
        )

    def get_child_index(self, name):
        try:
            index = int(name)
            if index >= self.num_children():
                return None
        # Ideally we'd use the exception type, but it's unclear what that is
        # without knowing how to trigger the original exception.
        except:  # NOQA: E501, E722
            pass
        return None

    def get_child_at_index(self, index):
        if index >= self.num_children():
            return None
        addr = self.element_base_addr + index * self.element_size
        return self.valobj.CreateValueFromAddress(
            "[%d]" % index, addr, self.element_type
        )


def prefcnt(debugger, command, result, dict):
    """Displays the refcount of an object."""
    # We handled regular nsISupports-like refcounted objects and cycle collected
    # objects.
    target = debugger.GetSelectedTarget()
    process = target.GetProcess()
    thread = process.GetSelectedThread()
    frame = thread.GetSelectedFrame()
    obj = frame.EvaluateExpression(command)
    if obj.GetError().Fail():
        print("could not evaluate expression")
        return
    obj = utils.dereference(obj)
    field = obj.GetChildMemberWithName("mRefCnt")
    if field.GetError().Fail():
        field = obj.GetChildMemberWithName("refCnt")
    if field.GetError().Fail():
        print("not a refcounted object")
        return
    refcnt_type = field.GetType().GetCanonicalType().GetName()
    if refcnt_type == "nsAutoRefCnt":
        print(field.GetChildMemberWithName("mValue").GetValueAsUnsigned(0))
    elif refcnt_type == "nsCycleCollectingAutoRefCnt":
        print(
            field.GetChildMemberWithName("mRefCntAndFlags").GetValueAsUnsigned(0) >> 2
        )
    elif refcnt_type == "mozilla::ThreadSafeAutoRefCnt":
        print(
            field
            .GetChildMemberWithName("mValue")
            .GetChildMemberWithName("mValue")
            .GetValueAsUnsigned(0)
        )
    elif refcnt_type == "int":  # non-atomic mozilla::RefCounted object
        print(field.GetValueAsUnsigned(0))
    elif refcnt_type == "mozilla::Atomic<int>":  # atomic mozilla::RefCounted object
        print(field.GetChildMemberWithName("mValue").GetValueAsUnsigned(0))
    else:
        print("unknown mRefCnt type " + refcnt_type)


# Used to work around http://llvm.org/bugs/show_bug.cgi?id=22211
def callfunc(debugger, command, result, dict):
    """Calls a function for which debugger information is unavailable by getting its address
    from the symbol table.  The function is assumed to return void."""

    if "(" not in command:
        print("Usage: callfunc your_function(args)")
        return

    command_parts = command.split("(")
    funcname = command_parts[0].strip()
    args = command_parts[1]

    target = debugger.GetSelectedTarget()
    symbols = target.FindFunctions(funcname).symbols
    if not symbols:
        print('Could not find a function symbol for a function called "%s"' % funcname)
        return

    sym = symbols[0]
    arg_types = "()"
    if sym.name and sym.name.startswith(funcname + "("):
        arg_types = sym.name[len(funcname) :]
    debugger.HandleCommand(
        "print ((void(*)%s)0x%0x)(%s"
        % (arg_types, sym.addr.GetLoadAddress(target), args)
    )


def init(debugger):
    debugger.HandleCommand(
        "type summary add nsAString -F lldbutils.general.summarize_string"
    )
    debugger.HandleCommand(
        "type summary add nsACString -F lldbutils.general.summarize_string"
    )
    debugger.HandleCommand(
        "type summary add nsFixedString -F lldbutils.general.summarize_string"
    )
    debugger.HandleCommand(
        "type summary add nsFixedCString -F lldbutils.general.summarize_string"
    )
    debugger.HandleCommand(
        "type summary add nsAutoString -F lldbutils.general.summarize_string"
    )
    debugger.HandleCommand(
        "type summary add nsAutoCString -F lldbutils.general.summarize_string"
    )
    debugger.HandleCommand(
        "type summary add nsAtom -F lldbutils.general.summarize_atom"
    )
    debugger.HandleCommand(
        'type synthetic add -x "nsTArray<" -l lldbutils.general.TArraySyntheticChildrenProvider'
    )
    debugger.HandleCommand(
        'type synthetic add -x "AutoTArray<" -l lldbutils.general.TArraySyntheticChildrenProvider'  # NOQA: E501
    )
    debugger.HandleCommand(
        'type synthetic add -x "FallibleTArray<" -l lldbutils.general.TArraySyntheticChildrenProvider'  # NOQA: E501
    )
    debugger.HandleCommand(
        "command script add -f lldbutils.general.prefcnt -f lldbutils.general.prefcnt prefcnt"
    )
    debugger.HandleCommand(
        "command script add -f lldbutils.general.callfunc -f lldbutils.general.callfunc callfunc"
    )
