# SPDX-FileCopyrightText: 2015-2023 Blender Authors
#
# SPDX-License-Identifier: Apache-2.0

# ./blender.bin --background --factory-startup --python tests/python/bl_rna_defaults.py

import bpy

DUMMY_NAME = "Untitled"
DUMMY_PATH = __file__
GLOBALS = {
    "error_num": 0,
}


def as_float_32(f):
    from struct import pack, unpack
    return unpack("f", pack("f", f))[0]


def repr_float_precision(f, round_fn):
    """
    Get's the value which was most likely entered by a human in C.

    Needed since Python will show trailing precision from a 32bit float.
    """
    f_round = round_fn(f)
    f_str = repr(f)
    f_str_frac = f_str.partition(".")[2]
    if not f_str_frac:
        return f_str
    for i in range(1, len(f_str_frac)):
        f_test = round(f, i)
        f_test_round = round_fn(f_test)
        if f_test_round == f_round:
            return "%.*f" % (i, f_test)
    return f_str


def repr_float_32(f):
    return repr_float_precision(f, as_float_32)


def validate_defaults(test_id, o):

    def warning(prop_id, val_real, val_default, *, repr_fn=repr):
        print("Error %s: '%s.%s' is:%s, expected:%s" %
              (test_id, o.__class__.__name__, prop_id,
               repr_fn(val_real), repr_fn(val_default)))
        GLOBALS["error_num"] += 1

    properties = type(o).bl_rna.properties.items()
    for prop_id, prop in properties:
        if prop_id == "rna_type":
            continue
        prop_type = prop.type
        if prop_type in {'STRING', 'COLLECTION'}:
            continue

        if prop_type == 'POINTER':
            # traverse down pointers if they're set
            val_real = getattr(o, prop_id)
            if (val_real is not None) and (not isinstance(val_real, bpy.types.ID)):
                validate_defaults("%s.%s" % (test_id, prop_id), val_real)
        elif prop_type in {'INT', 'BOOL'}:
            # array_length = prop.array_length
            if not prop.is_array:
                val_real = getattr(o, prop_id)
                val_default = prop.default
                if val_real != val_default:
                    warning(prop_id, val_real, val_default)
            else:
                pass  # TODO, array defaults
        elif prop_type == 'FLOAT':
            # array_length = prop.array_length
            if not prop.is_array:
                val_real = getattr(o, prop_id)
                val_default = prop.default
                if val_real != val_default:
                    warning(prop_id, val_real, val_default, repr_fn=repr_float_32)
            else:
                pass  # TODO, array defaults
        elif prop_type == 'ENUM':
            val_real = getattr(o, prop_id)
            if prop.is_enum_flag:
                val_default = prop.default_flag
            else:
                val_default = prop.default
            if val_real != val_default:
                warning(prop_id, val_real, val_default)

        # print(prop_id, prop_type)


def _test_id_gen(data_attr, args_create=(DUMMY_NAME,), create_method="new"):
    def test_gen(test_id):
        id_collection = getattr(bpy.data, data_attr)
        create_fn = getattr(id_collection, create_method)
        o = create_fn(*args_create)
        o.user_clear()
        validate_defaults(test_id, o)
        id_collection.remove(o)
    return test_gen


test_Action = _test_id_gen("actions")
test_Armature = _test_id_gen("armatures")
test_Brush = _test_id_gen("brushes")
test_Camera = _test_id_gen("cameras")
test_Collection = _test_id_gen("collections")
test_GreasePencil = _test_id_gen("grease_pencils")
test_HairCurves = _test_id_gen("hair_curves")
test_Lattice = _test_id_gen("lattices")
test_LineStyle = _test_id_gen("linestyles")
test_Mask = _test_id_gen("masks")
test_Material = _test_id_gen("materials")
test_Mesh = _test_id_gen("meshes")
test_MetaBall = _test_id_gen("metaballs")
test_MovieClip = _test_id_gen("movieclips", args_create=(DUMMY_PATH,), create_method="load")
test_Object = _test_id_gen("objects", args_create=(DUMMY_NAME, None))
test_Palette = _test_id_gen("palettes")
test_Particle = _test_id_gen("particles")
test_PointCloud = _test_id_gen("pointclouds")
test_Scene = _test_id_gen("scenes")
test_Sound = _test_id_gen("sounds", args_create=(DUMMY_PATH,), create_method="load")
test_Speaker = _test_id_gen("speakers")
test_Text = _test_id_gen("texts")
test_VectorFont = _test_id_gen("fonts", args_create=("<builtin>",), create_method="load")
test_Volume = _test_id_gen("volumes")
test_World = _test_id_gen("worlds")

ns = globals()
for t in bpy.data.curves.bl_rna.functions["new"].parameters["type"].enum_items.keys():
    ns["test_Curve_%s" % t] = _test_id_gen("curves", args_create=(DUMMY_NAME, t))
for t in bpy.data.lights.bl_rna.functions["new"].parameters["type"].enum_items.keys():
    ns["test_Light_%s" % t] = _test_id_gen("lights", args_create=(DUMMY_NAME, t))
for t in bpy.data.lightprobes.bl_rna.functions["new"].parameters["type"].enum_items.keys():
    ns["test_LightProbe_%s" % t] = _test_id_gen("lightprobes", args_create=(DUMMY_NAME, t))
# types are a dynamic enum, have to hard-code.
for t in "ShaderNodeTree", "CompositorNodeTree", "TextureNodeTree":
    ns["test_NodeGroup_%s" % t] = _test_id_gen("node_groups", args_create=(DUMMY_NAME, t))
for t in bpy.data.textures.bl_rna.functions["new"].parameters["type"].enum_items.keys():
    ns["test_Texture_%s" % t] = _test_id_gen("textures", args_create=(DUMMY_NAME, t))
del ns


def main():
    for fn_id, fn_val in sorted(globals().items()):
        if fn_id.startswith("test_") and callable(fn_val):
            fn_val(fn_id)

    print("Error (total): %d" % GLOBALS["error_num"])


if __name__ == "__main__":
    main()
