# SPDX-License-Identifier: GPL-2.0-or-later

import bpy

header = '''
digraph ancestors           {
graph [fontsize=30 labelloc="t" label="" splines=false overlap=true, rankdir=BT];
ratio = "auto" ;
'''

footer = '''
}
'''


def compat_str(text, line_length=0):

    if line_length:
        text_ls = []
        while len(text) > line_length:
            text_ls.append(text[:line_length])
            text = text[line_length:]

        if text:
            text_ls.append(text)
        text = '\n  '.join(text_ls)

    #text = text.replace('.', '.\n')
    #text = text.replace(']', ']\n')
    text = text.replace("\n", "\\n")
    text = text.replace('"', '\\"')
    return text


def graph_armature(obj, filepath, FAKE_PARENT=True, CONSTRAINTS=True, DRIVERS=True, XTRA_INFO=True):
    CONSTRAINTS = DRIVERS = True

    fileobject = open(filepath, "w")
    fw = fileobject.write
    fw(header)
    fw('label = "%s::%s" ;' % (bpy.data.filepath.split("/")[-1].split("\\")[-1], obj.name))

    arm = obj.data

    bones = [bone.name for bone in arm.bones]
    bones.sort()
    print("")
    for bone in bones:
        b = arm.bones[bone]
        print(">>", bone, ["*>", "->"][b.use_connect], getattr(getattr(b, "parent", ""), "name", ""))
        label = [bone]
        bone = arm.bones[bone]

        for key, value in obj.pose.bones[bone.name].items():
            if key.startswith("_"):
                continue

            if type(value) == float:
                value = "%.3f" % value
            elif type(value) == str:
                value = compat_str(value)

            label.append("%s = %s" % (key, value))

        opts = [
            "shape=box",
            "regular=1",
            "style=filled",
            "fixedsize=false",
            'label="%s"' % compat_str('\n'.join(label)),
        ]

        if bone.name.startswith('ORG'):
            opts.append("fillcolor=yellow")
        else:
            opts.append("fillcolor=white")

        fw('"%s" [%s];\n' % (bone.name, ','.join(opts)))

    fw('\n\n# Hierarchy:\n')

    # Root node.
    if FAKE_PARENT:
        fw('"Object::%s" [];\n' % obj.name)

    for bone in bones:
        bone = arm.bones[bone]

        parent = bone.parent
        if parent:
            parent_name = parent.name
            connected = bone.use_connect
        elif FAKE_PARENT:
            parent_name = 'Object::%s' % obj.name
            connected = False
        else:
            continue

        opts = ["dir=forward", "weight=2", "arrowhead=normal"]
        if not connected:
            opts.append("style=dotted")

        fw('"%s" -> "%s" [%s] ;\n' % (bone.name, parent_name, ','.join(opts)))
    del bone

    # constraints
    if CONSTRAINTS:
        fw('\n\n# Constraints:\n')
        for bone in bones:
            pbone = obj.pose.bones[bone]
            # must be ordered
            for constraint in pbone.constraints:
                subtarget = getattr(constraint, "subtarget", "")
                if subtarget:
                    # TODO, not internal links
                    opts = [
                        'dir=forward',
                        "weight=1",
                        "arrowhead=normal",
                        "arrowtail=none",
                        "constraint=false",
                        'color="red"',
                        'labelfontsize=4',
                    ]
                    if XTRA_INFO:
                        label = "%s\n%s" % (constraint.type, constraint.name)
                        opts.append('label="%s"' % compat_str(label))
                    fw('"%s" -> "%s" [%s] ;\n' % (pbone.name, subtarget, ','.join(opts)))

    # Drivers
    if DRIVERS:
        fw('\n\n# Drivers:\n')

        def rna_path_as_pbone(rna_path):
            if not rna_path.startswith("pose.bones["):
                return None

            #rna_path_bone = rna_path[:rna_path.index("]") + 1]
            # return obj.path_resolve(rna_path_bone)
            bone_name = rna_path.split("[")[1].split("]")[0]
            return obj.pose.bones[bone_name[1:-1]]

        animation_data = obj.animation_data
        if animation_data:

            fcurve_drivers = [fcurve_driver for fcurve_driver in animation_data.drivers]
            fcurve_drivers.sort(key=lambda fcurve_driver: fcurve_driver.data_path)

            for fcurve_driver in fcurve_drivers:
                rna_path = fcurve_driver.data_path
                pbone = rna_path_as_pbone(rna_path)

                if pbone:
                    for var in fcurve_driver.driver.variables:
                        for target in var.targets:
                            pbone_target = rna_path_as_pbone(target.data_path)
                            rna_path_target = target.data_path
                            if pbone_target:
                                opts = [
                                    'dir=forward',
                                    "weight=1",
                                    "arrowhead=normal",
                                    "arrowtail=none",
                                    "constraint=false",
                                    'color="blue"',
                                    "labelfontsize=4",
                                ]
                                display_source = rna_path.replace("pose.bones", "")
                                display_target = rna_path_target.replace("pose.bones", "")
                                if XTRA_INFO:
                                    label = "%s\\n%s" % (display_source, display_target)
                                    opts.append('label="%s"' % compat_str(label))
                                fw('"%s" -> "%s" [%s] ;\n' % (pbone_target.name, pbone.name, ','.join(opts)))

    fw(footer)
    fileobject.close()

    '''
    print(".", end="")
    import sys
    sys.stdout.flush()
    '''
    print("\nSaved:", filepath)
    return True


if __name__ == "__main__":
    import os
    tmppath = "/tmp/test.dot"
    graph_armature(bpy.context.object, tmppath, CONSTRAINTS=True, DRIVERS=True)
    os.system("dot -Tpng %s > %s; eog %s &" % (tmppath, tmppath + '.png', tmppath + '.png'))
