"""Tests related to the AttributeTemplate python APIs"""
import tempfile
from pathlib import Path
from typing import Tuple

import carb
import numpy as np
import omni.graph.core as og
import omni.graph.core._unstable as ogu
import omni.graph.core.tests as ogts
import omni.graph.tools.ogn as ogn
import omni.kit.test
import omni.usd
from pxr import Gf, Sdf, Vt

# test data for metadata on attribute templates
VALID_ATTRIBUTE_OGN_KEYS_AND_SAMPLES = {
    ogn.MetadataKeys.ALLOW_MULTI_INPUTS: "1",
    ogn.MetadataKeys.ALLOWED_TOKENS: "A,B,C",
    ogn.MetadataKeys.ALLOWED_TOKENS_RAW: '{"A": "1", "B": "2", "C": "3"}',
    ogn.MetadataKeys.DESCRIPTION: "TestDescription",
    ogn.MetadataKeys.HIDDEN: "1",
    ogn.MetadataKeys.OPTIONAL: "1",
    ogn.MetadataKeys.OUTPUT_ONLY: "1",
    ogn.MetadataKeys.LITERAL_ONLY: "1",
    ogn.MetadataKeys.UI_NAME: "DisplayName",
}


class TestAttributeTemplateAPI(ogts.OmniGraphTestCase):
    """Tests that validate the attribute template API"""

    _simple_test_file = "TestCompoundGraph.usda"

    @classmethod
    def setUpClass(cls):
        # this is required when running a single test to make expectedError work correctly...not sure why
        carb.log_warn("This class expects error. This preflushes to avoid problems with ExpectedError")

    def validate_connections_match_usd(self, attribute: ogu.AttributeTemplate, path_to_rel: str):
        """
        Helper that tests whether the connection list on the node type
        matches the corresponding USD relationship
        """

        base_path = "/World/Compounds/TestType/Graph"
        stage = omni.usd.get_context().get_stage()
        relationship = stage.GetRelationshipAtPath(f"/World/Compounds/TestType.{path_to_rel}")
        self.assertTrue(relationship.IsValid())
        # compare sets as the order isn't guaranteed
        attr_set = {Sdf.Path(f"{base_path}/{x}") for x in attribute.get_connections()}
        rel_set = set(relationship.GetTargets())
        self.assertEqual(attr_set, rel_set)

    async def test_port_type_property(self):
        """
        Tests the port type attribute on AttributeTemplates
        """
        compound_node_type = ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        for i in range(1, 20):
            compound_node_type.add_input(f"input_{i}", "int", True)
            compound_node_type.add_output(f"output_{i}", "float", True)

        for i in range(1, 20):
            input_type = compound_node_type.find_input(f"input_{i}")
            output_type = compound_node_type.find_output(f"output_{i}")
            self.assertEquals(input_type.port_type, og.AttributePortType.ATTRIBUTE_PORT_TYPE_INPUT)
            self.assertEquals(output_type.port_type, og.AttributePortType.ATTRIBUTE_PORT_TYPE_OUTPUT)

    async def test_extended_type_property(self):
        """
        Test the attribute extended type property
        """
        compound_node_type = ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        regular_input = compound_node_type.add_input("input_0", "int", True, 1)
        union_input = compound_node_type.add_extended_input(
            "input_1", "int, float", True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_UNION
        )
        any_input = compound_node_type.add_extended_input(
            "input_2", None, True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_ANY
        )
        regular_output = compound_node_type.add_output("output_0", "int", True, 1)
        union_output = compound_node_type.add_extended_output(
            "output_1", "int, float", True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_UNION
        )
        any_output = compound_node_type.add_extended_output(
            "output_2", None, True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_ANY
        )

        self.assertTrue(regular_input.is_valid())
        self.assertTrue(union_input.is_valid())
        self.assertTrue(any_input.is_valid())
        self.assertTrue(regular_output.is_valid())
        self.assertTrue(union_output.is_valid())
        self.assertTrue(any_output.is_valid())

        self.assertEquals(regular_input.extended_type, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_REGULAR)
        self.assertEquals(union_input.extended_type, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_UNION)
        self.assertEquals(any_input.extended_type, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_ANY)
        self.assertEquals(regular_output.extended_type, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_REGULAR)
        self.assertEquals(union_output.extended_type, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_UNION)
        self.assertEquals(any_output.extended_type, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_ANY)

    async def test_get_types_method(self):
        """Test the get types methods returns the expected list values"""

        compound_node_type = ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        regular_input = compound_node_type.add_input("input_0", "int", True, 1)
        union_input = compound_node_type.add_extended_input(
            "input_1", "int, float", True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_UNION
        )
        any_input = compound_node_type.add_extended_input(
            "input_2", None, True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_ANY
        )
        regular_output = compound_node_type.add_output("output_0", "float", True, 1)
        union_output = compound_node_type.add_extended_output(
            "output_1", "double[2], int[], normalf[3]", True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_UNION
        )
        any_output = compound_node_type.add_extended_output(
            "output_2", None, True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_ANY
        )

        self.assertTrue(regular_input.is_valid())
        self.assertTrue(union_input.is_valid())
        self.assertTrue(any_input.is_valid())
        self.assertTrue(regular_output.is_valid())
        self.assertTrue(union_output.is_valid())
        self.assertTrue(any_output.is_valid())

        self.assertEqual(regular_input.get_types(), [og.Type(og.BaseDataType.INT)])
        self.assertEqual(regular_output.get_types(), [og.Type(og.BaseDataType.FLOAT)])
        self.assertEqual(union_input.get_types(), [og.Type(og.BaseDataType.INT), og.Type(og.BaseDataType.FLOAT)])
        self.assertEqual(
            union_output.get_types(),
            [
                og.Type(og.BaseDataType.DOUBLE, 2),
                og.Type(og.BaseDataType.INT, 1, 1),
                og.Type(og.BaseDataType.FLOAT, 3, 0, og.AttributeRole.NORMAL),
            ],
        )
        self.assertEqual(any_input.get_types(), [])
        self.assertEqual(any_output.get_types(), [])

    async def test_attribute_template_set_type(self):
        """Tests the set type and set extended type calls"""

        stage = omni.usd.get_context().get_stage()
        compound_node_type = ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        prim = stage.GetPrimAtPath("/World/Compounds/TestType")

        regular_input = compound_node_type.add_input("input_0", "int", True, 1)
        regular_output = compound_node_type.add_output("output_0", "int", True, 2)
        input_rel = prim.GetRelationship("omni:graph:input:input_0")
        output_rel = prim.GetRelationship("omni:graph:output:output_0")

        self.assertTrue(regular_input.get_types(), [og.Type(og.BaseDataType.INT)])
        self.assertTrue(regular_output.get_types(), [og.Type(og.BaseDataType.INT)])
        self.assertTrue(input_rel.IsValid())
        self.assertTrue(output_rel.IsValid())
        self.assertEqual(input_rel.GetCustomDataByKey("omni:graph:default"), 1)
        self.assertEqual(output_rel.GetCustomDataByKey("omni:graph:default"), 2)

        # set to union or any type and validate the key is removed
        regular_input.set_extended_type(og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_UNION, "numerics")
        regular_output.set_extended_type(og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_ANY)
        self.assertEqual(regular_input.extended_type, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_UNION)
        self.assertEqual(regular_output.extended_type, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_ANY)
        self.assertGreater(len(regular_input.get_types()), 5)
        self.assertEqual(regular_output.get_types(), [])
        self.assertIsNone(input_rel.GetCustomDataByKey("omni:graph:default"))
        self.assertIsNone(output_rel.GetCustomDataByKey("omni:graph:default"))
        self.assertEqual(input_rel.GetCustomDataByKey("omni:graph:type"), "numerics")
        self.assertIsNone(output_rel.GetCustomDataByKey("omni:graph:type"))

        # validate setting a regular input with the wrong method has no effect
        with ogts.ExpectedError():
            regular_input.set_extended_type(og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_REGULAR, "int")
        self.assertEqual(regular_input.extended_type, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_UNION)

        regular_input.set_type(og.Type(og.BaseDataType.FLOAT, 3, 1), [(1, 2, 3), (4, 5, 6)])
        self.assertEqual(regular_input.extended_type, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_REGULAR)
        self.assertEqual(
            input_rel.GetCustomDataByKey("omni:graph:default"), Vt.Vec3fArray([Gf.Vec3f(1, 2, 3), Gf.Vec3f(4, 5, 6)])
        )
        self.assertEqual(input_rel.GetCustomDataByKey("omni:graph:type"), "float[3][]")

        regular_output.set_type(og.Type(og.BaseDataType.FLOAT, 2))
        self.assertEqual(output_rel.GetCustomDataByKey("omni:graph:default"), None)
        self.assertEqual(output_rel.GetCustomDataByKey("omni:graph:type"), "float[2]")

    async def test_connect_by_path(self):
        """Test the connect by path API call"""
        stage = omni.usd.get_context().get_stage()
        compound_node_type = ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        prim = stage.GetPrimAtPath("/World/Compounds/TestType")

        graph_path = prim.GetPath().AppendChild("Graph")
        (_, (_add_node1, _add_node2), _, _) = og.Controller.edit(
            str(graph_path),
            {og.Controller.Keys.CREATE_NODES: [("Add", "omni.graph.nodes.Add"), ("Add2", "omni.graph.nodes.Add")]},
        )
        node_path = graph_path.AppendChild("add_node")

        input_1 = compound_node_type.add_extended_input(
            "input_1", None, True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_ANY
        )
        # invalid Paths will throw a value error
        with self.assertRaises(ValueError):
            input_1.connect_by_path(None)
        with self.assertRaises(ValueError):
            input_1.connect_by_path(2.0)
        with self.assertRaises(ValueError):
            input_1.connect_by_path("PATH WITH INVALID IDENTIFIERS - !@#$%^")

        # valid paths that are not part of the CNT graph produces errors and return false.
        with ogts.ExpectedError():
            self.assertFalse(input_1.connect_by_path("/World"))  # valid path, not part of the graph
        with ogts.ExpectedError():
            self.assertFalse(input_1.connect_by_path(graph_path))  # valid SDFPath, not child of the graph

        full_attr_path = node_path.AppendProperty("inputs:a")
        rel_attr_path = "add.inputs:b"

        self.assertTrue(input_1.connect_by_path(full_attr_path))
        self.assertTrue(input_1.connect_by_path(rel_attr_path))
        self.assertEqual(len(input_1.get_connections()), 2)

        # existing connections don't append
        self.assertFalse(input_1.connect_by_path(node_path.AppendProperty("inputs:a")))
        self.assertEqual(len(input_1.get_connections()), 2)

        output_1 = compound_node_type.add_extended_output(
            "output_1", None, True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_ANY
        )
        self.assertTrue(output_1.connect_by_path(node_path.AppendProperty("outputs:sum")))
        self.assertEqual(len(output_1.get_connections()), 1)

        self.assertTrue(output_1.connect_by_path("Add2.outputs:sum"))
        self.assertEqual(len(output_1.get_connections()), 1)

        self.validate_connections_match_usd(input_1, "omni:graph:input:input_1")
        self.validate_connections_match_usd(output_1, "omni:graph:output:output_1")

    def create_test_node_with_input_and_output(self) -> Tuple[ogu.AttributeTemplate, ogu.AttributeTemplate]:
        """Creates a comound_node_type with two attributes with connections"""
        compound_node_type = ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        graph_path = "/World/Compounds/TestType/Graph"
        (_, (_add_node1, _add_node2), _, _) = og.Controller.edit(
            graph_path,
            {og.Controller.Keys.CREATE_NODES: [("Add", "omni.graph.nodes.Add"), ("Add2", "omni.graph.nodes.Add")]},
        )

        input_1 = compound_node_type.add_extended_input(
            "input_1", None, True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_ANY
        )
        self.assertEqual(input_1.get_connections(), [])
        input_1.connect_by_path("/World/Compounds/TestType/Graph/Add.inputs:a")
        input_1.connect_by_path("Add.inputs:b")
        input_1.connect_by_path("Add2.inputs:a")
        input_1.connect_by_path("Add2.inputs:b")

        output_1 = compound_node_type.add_extended_output(
            "output_1", None, True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_ANY
        )
        self.assertEqual(output_1.get_connections(), [])
        output_1.connect_by_path("Add.outputs:sum")
        return (input_1, output_1)

    async def test_get_connections(self):
        """
        Test the get_connection member function returns the expected connection values
        """
        (input_1, output_1) = self.create_test_node_with_input_and_output()
        self.validate_connections_match_usd(input_1, "omni:graph:input:input_1")
        self.validate_connections_match_usd(output_1, "omni:graph:output:output_1")

        self.assertEqual(output_1.get_connections(), ["Add.outputs:sum"])

        output_1.connect_by_path("Add2.outputs:sum")

        self.assertEqual(input_1.get_connections(), ["Add.inputs:a", "Add.inputs:b", "Add2.inputs:a", "Add2.inputs:b"])
        self.assertEqual(output_1.get_connections(), ["Add2.outputs:sum"])
        self.validate_connections_match_usd(input_1, "omni:graph:input:input_1")
        self.validate_connections_match_usd(output_1, "omni:graph:output:output_1")

    async def test_disconnect_all(self):
        """Test the disconnect_all member function"""

        (input_1, output_1) = self.create_test_node_with_input_and_output()
        self.assertNotEqual(input_1.get_connections(), [])
        self.assertNotEqual(output_1.get_connections(), [])

        input_1.disconnect_all()
        self.assertEqual(input_1.get_connections(), [])
        input_1.disconnect_all()
        self.assertEqual(input_1.get_connections(), [])
        output_1.disconnect_all()
        self.assertEqual(output_1.get_connections(), [])
        self.validate_connections_match_usd(input_1, "omni:graph:input:input_1")
        self.validate_connections_match_usd(output_1, "omni:graph:output:output_1")

    async def test_disconnect_by_path(self):
        """Tests disconnect_by_path API call"""
        (input_1, _) = self.create_test_node_with_input_and_output()

        self.assertEqual(len(input_1.get_connections()), 4)
        # non existing path
        self.assertFalse(input_1.disconnect_by_path("Add.omni:graph:input:not_valid"))
        # existing absolute path
        self.assertTrue(input_1.disconnect_by_path("/World/Compounds/TestType/Graph/Add.inputs:b"))
        self.assertTrue(input_1.disconnect_by_path("Add2.inputs:b"))
        self.assertEqual(input_1.get_connections(), ["Add.inputs:a", "Add2.inputs:a"])
        self.validate_connections_match_usd(input_1, "omni:graph:input:input_1")

    async def test_connections_are_set_when_loaded(self):
        """Tests that connections that are loaded from USD can be retrieved through the API"""

        (result, error) = await ogts.load_test_file(self._simple_test_file, use_caller_subdirectory=True)
        self.assertTrue(result, error)

        compound_node_type = ogu.find_compound_node_type_by_path("/World/Compounds/Compound")
        self.assertTrue(compound_node_type.is_valid())
        input_a = compound_node_type.find_input("A")
        input_b = compound_node_type.find_input("B")
        output = compound_node_type.find_output("Value")

        self.assertTrue(input_a.is_valid())
        self.assertTrue(input_b.is_valid())
        self.assertTrue(output.is_valid())

        self.assertEqual(input_a.get_connections(), ["Add_Node.inputs:a"])
        self.assertEqual(input_b.get_connections(), ["Add_Node.inputs:b"])
        self.assertEqual(output.get_connections(), ["Add_Node.outputs:sum"])

    async def test_set_get_metadata(self):
        """Tests that validate the calls to set and retrieve metadata work as expected"""
        (input_1, output_1) = self.create_test_node_with_input_and_output()

        entries = VALID_ATTRIBUTE_OGN_KEYS_AND_SAMPLES
        for (key, value) in entries.items():
            input_1.set_metadata(key, value)
            output_1.set_metadata(key, value)

        for (key, value) in entries.items():
            self.assertTrue(input_1.get_metadata(key), value)
            self.assertTrue(output_1.get_metadata(key), value)

        # create a graph with the compound node and validate the values transfer
        (_, (node,), _, _) = og.Controller.edit(
            "/World/PushGraph", {og.Controller.Keys.CREATE_NODES: [("Node", "test.nodes.TestType")]}
        )

        node_input = node.get_attribute("inputs:input_1")
        node_output = node.get_attribute("outputs:output_1")

        for (key, value) in entries.items():
            self.assertEqual(node_input.get_metadata(key), value)
            self.assertEqual(node_output.get_metadata(key), value)

    async def test_set_get_metadata_reloads_from_file(self):
        """Tests that creating and setting metadata persists when the file reloads"""
        (input_1, output_1) = self.create_test_node_with_input_and_output()
        entries = VALID_ATTRIBUTE_OGN_KEYS_AND_SAMPLES
        for (key, value) in entries.items():
            input_1.set_metadata(key, value)
            output_1.set_metadata(key, value)

        with tempfile.TemporaryDirectory() as tmpdirname:
            # save the file
            tmp_file_path = Path(tmpdirname) / "tmp.usda"
            result = omni.usd.get_context().save_as_stage(str(tmp_file_path))
            self.assertTrue(result)

            # refresh the stage
            await omni.usd.get_context().new_stage_async()

            # reload the file back
            (result, error) = await ogts.load_test_file(str(tmp_file_path))
            self.assertTrue(result, error)

        compound_node_type = ogu.find_compound_node_type_by_path("/World/Compounds/TestType")
        self.assertTrue(compound_node_type.is_valid())

        input_1 = compound_node_type.find_input("input_1")
        output_1 = compound_node_type.find_output("output_1")
        self.assertTrue(input_1.is_valid())
        self.assertTrue(output_1.is_valid())

        for (key, value) in entries.items():
            input_1.set_metadata(key, value)
            output_1.set_metadata(key, value)

    async def test_stress_test_add_input_output(self):
        """
        Stress tests having many inputs and outputs on compound nodes.
        Validates that 'handles' maintain their correct object when
        internal movement of vectors is happening
        """
        num_inputs = 1000
        num_outputs = 1000

        ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        node_type = og.get_node_type("test.nodes.TestType")
        self.assertTrue(node_type.is_valid())

        compound_node_type = ogu.get_compound_node_type(node_type)
        self.assertTrue(compound_node_type.is_valid())

        # build up a large number of inputs and outputs
        for i in range(0, num_inputs):
            compound_node_type.add_input(f"in_{i}", "int", True)
        for j in range(0, num_outputs):
            compound_node_type.add_output(f"out_{j}", "int", True)

        # validate the inputs and outputs return the correct object
        for i in range(0, num_inputs):
            input_attr = compound_node_type.find_input(f"in_{i}")
            self.assertTrue(input_attr.is_valid(), f"Failed at index {i}")
            self.assertEquals(input_attr.name, f"inputs:in_{i}")

        for j in range(0, num_outputs):
            output = compound_node_type.find_output(f"out_{j}")
            self.assertTrue(output.is_valid(), f"Failed at index {j}")
            self.assertEquals(output.name, f"outputs:out_{j}")

        # remove alternate inputs and outputs
        for i in range(1, num_inputs, 2):
            compound_node_type.remove_input_by_name(f"in_{i}")

        for j in range(1, num_outputs, 2):
            compound_node_type.remove_output_by_name(f"out_{j}")

        # verify everything is as expected.
        for i in range(0, num_inputs):
            input_attr = compound_node_type.find_input(f"in_{i}")
            if (i % 2) == 0:
                self.assertTrue(input_attr.is_valid(), f"Failed at index {i}")
                self.assertEquals(input_attr.name, f"inputs:in_{i}")
            else:
                self.assertFalse(input_attr.is_valid())

        for j in range(0, num_outputs):
            output = compound_node_type.find_output(f"out_{j}")
            if (j % 2) == 0:
                self.assertTrue(output.is_valid(), f"Failed at index{j}")
                self.assertEquals(output.name, f"outputs:out_{j}")
            else:
                self.assertFalse(output.is_valid())

    # ----------------------------------------------------------------------
    async def test_default_data(self):
        """
        Tests setting and getting default data through the attributetemplate ABI
        """
        compound_node_type = ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        name_index = 0

        def validate_type(og_type, data):
            nonlocal name_index
            input_name = f"input_{name_index}"
            name_index = name_index + 1
            attr_input = compound_node_type.add_input(input_name, og_type.get_ogn_type_name(), True, data)
            actual_data = attr_input.get_default_data()
            expected_data = data

            # special case for half floats where the precision can fail verify_values due to loss of precision
            if og_type.base_type == og.BaseDataType.HALF:
                if og_type.array_depth > 0 or og_type.tuple_count > 1:
                    expected_data = np.array(np.array(data, dtype=np.float16), dtype=np.float32)
                else:
                    expected_data = float(np.half(data))

            ogts.verify_values(
                expected_data, actual_data, f"Comparison failed {str(actual_data)} vs {str(expected_data)}"
            )

        validate_type(og.Type(og.BaseDataType.BOOL), True)
        validate_type(og.Type(og.BaseDataType.BOOL), False)
        validate_type(og.Type(og.BaseDataType.BOOL, 1, 1), [True, False])
        validate_type(og.Type(og.BaseDataType.DOUBLE, 3, 0, og.AttributeRole.COLOR), (1, 0, 0))
        validate_type(og.Type(og.BaseDataType.DOUBLE, 3, 1, og.AttributeRole.COLOR), [(1, 0, 0), (0, 1, 0)])
        validate_type(og.Type(og.BaseDataType.FLOAT, 3, 0, og.AttributeRole.COLOR), (1, 0, 0))
        validate_type(og.Type(og.BaseDataType.FLOAT, 3, 1, og.AttributeRole.COLOR), [(1, 0, 0), (0, 1, 0)])
        validate_type(og.Type(og.BaseDataType.HALF, 3, 0, og.AttributeRole.COLOR), (1, 0, 0))
        validate_type(og.Type(og.BaseDataType.HALF, 3, 1, og.AttributeRole.COLOR), [(1, 0, 0), (0, 1, 0)])
        validate_type(og.Type(og.BaseDataType.DOUBLE, 4, 0, og.AttributeRole.COLOR), (1, 0, 0, 1))
        validate_type(og.Type(og.BaseDataType.DOUBLE, 4, 1, og.AttributeRole.COLOR), [(1, 0, 0, 1), (0, 1, 1, 0)])
        validate_type(og.Type(og.BaseDataType.FLOAT, 4, 0, og.AttributeRole.COLOR), (1, 0, 0, 1))
        validate_type(og.Type(og.BaseDataType.FLOAT, 4, 1, og.AttributeRole.COLOR), [(1, 0, 0, 1), (0, 1, 1, 0)])
        validate_type(og.Type(og.BaseDataType.HALF, 4, 0, og.AttributeRole.COLOR), (1, 0, 0, 1))
        validate_type(og.Type(og.BaseDataType.HALF, 4, 1, og.AttributeRole.COLOR), [(1, 0, 0, 1), (0, 1, 1, 0)])
        validate_type(og.Type(og.BaseDataType.DOUBLE), 1.5)
        validate_type(og.Type(og.BaseDataType.DOUBLE, 2), (1.5, 1.6))
        validate_type(og.Type(og.BaseDataType.DOUBLE, 2, 1), [(1.5, 1.6), (2.5, 2.6)])
        validate_type(og.Type(og.BaseDataType.DOUBLE, 3), (1.5, 1.6, 1.7))
        validate_type(og.Type(og.BaseDataType.DOUBLE, 3, 1), [(1.5, 1.6, 1.7), (2.5, 2.6, 2.7)])
        validate_type(og.Type(og.BaseDataType.DOUBLE, 4), (1.5, 1.6, 1.7, 1.8))
        validate_type(og.Type(og.BaseDataType.DOUBLE, 4, 1), [(1.5, 1.6, 1.7, 1.8), (2.5, 2.6, 2.7, 2.8)])
        validate_type(og.Type(og.BaseDataType.DOUBLE, 1, 1), [1.5, 1.6, 1.7])
        validate_type(og.Type(og.BaseDataType.FLOAT), 1.5)
        validate_type(og.Type(og.BaseDataType.FLOAT, 2), (1.5, 1.6))
        validate_type(og.Type(og.BaseDataType.FLOAT, 2, 1), [(1.5, 1.6), (2.5, 2.6)])
        validate_type(og.Type(og.BaseDataType.FLOAT, 3), (1.5, 1.6, 1.7))
        validate_type(og.Type(og.BaseDataType.FLOAT, 3, 1), [(1.5, 1.6, 1.7), (2.5, 2.6, 2.7)])
        validate_type(og.Type(og.BaseDataType.FLOAT, 4), (1.5, 1.6, 1.7, 1.8))
        validate_type(og.Type(og.BaseDataType.FLOAT, 4, 1), [(1.5, 1.6, 1.7, 1.8), (2.5, 2.6, 2.7, 2.8)])
        validate_type(og.Type(og.BaseDataType.FLOAT, 1, 1), [1.5, 1.6, 1.7])
        validate_type(
            og.Type(og.BaseDataType.DOUBLE, 16, 0, og.AttributeRole.FRAME),
            ((1, 0, 0, 1), (2, 0, 0, 2), (3, 0, 0, 3), (4, 0, 0, 4)),
        )
        validate_type(
            og.Type(og.BaseDataType.DOUBLE, 16, 1, og.AttributeRole.FRAME),
            [
                ((1, 0, 0, 1), (2, 0, 0, 2), (3, 0, 0, 3), (4, 0, 0, 4)),
                ((5, 0, 0, 5), (6, 0, 0, 6), (7, 0, 0, 7), (8, 0, 0, 8)),
            ],
        )
        validate_type(og.Type(og.BaseDataType.HALF), 1.5)
        validate_type(og.Type(og.BaseDataType.HALF, 2), (1.5, 1.6))
        validate_type(og.Type(og.BaseDataType.HALF, 2, 1), [(1.5, 1.6), (2.5, 2.6)])
        validate_type(og.Type(og.BaseDataType.HALF, 3), (1.5, 1.6, 1.7))
        validate_type(og.Type(og.BaseDataType.HALF, 3, 1), [(1.5, 1.6, 1.7), (2.5, 2.6, 2.7)])
        validate_type(og.Type(og.BaseDataType.HALF, 4), (1.5, 1.6, 1.7, 1.8))
        validate_type(og.Type(og.BaseDataType.HALF, 4, 1), [(1.5, 1.6, 1.7, 1.8), (2.5, 2.6, 2.7, 2.8)])
        validate_type(og.Type(og.BaseDataType.HALF, 1, 1), [1.5, 1.6, 1.7])
        validate_type(og.Type(og.BaseDataType.INT), 1)
        validate_type(og.Type(og.BaseDataType.INT, 2), (1, 2))
        validate_type(og.Type(og.BaseDataType.INT, 2, 1), [(1, 2), (3, 4)])
        validate_type(og.Type(og.BaseDataType.INT, 3), (1, 2, 3))
        validate_type(og.Type(og.BaseDataType.INT, 3, 1), [(1, 2, 3), (3, 4, 5)])
        validate_type(og.Type(og.BaseDataType.INT, 4), (1, 2, 3, 4))
        validate_type(og.Type(og.BaseDataType.INT, 4, 1), [(1, 2, 3, 4), (3, 4, 5, 6)])
        validate_type(og.Type(og.BaseDataType.INT64), 123456789012)
        validate_type(og.Type(og.BaseDataType.INT64, 1, 1), [123456789012, 345678901234])
        validate_type(og.Type(og.BaseDataType.INT, 1, 1), [1, 2, 3, 4, 5, 6])
        validate_type(og.Type(og.BaseDataType.DOUBLE, 4, 0, og.AttributeRole.MATRIX), ((1, 2), (3, 4)))
        validate_type(
            og.Type(og.BaseDataType.DOUBLE, 4, 1, og.AttributeRole.MATRIX), [((1, 2), (3, 4)), ((5, 6), (7, 8))]
        )
        validate_type(og.Type(og.BaseDataType.DOUBLE, 9, 0, og.AttributeRole.MATRIX), ((1, 2, 3), (4, 5, 6), (7, 8, 9)))
        validate_type(
            og.Type(og.BaseDataType.DOUBLE, 9, 1, og.AttributeRole.MATRIX),
            [((1, 2, 3), (4, 5, 6), (7, 8, 9)), ((0, 1, 2), (3, 4, 5), (6, 7, 8))],
        )
        validate_type(
            og.Type(og.BaseDataType.DOUBLE, 16, 0, og.AttributeRole.MATRIX),
            ((1, 0, 0, 1), (2, 0, 0, 2), (3, 0, 0, 3), (4, 0, 0, 4)),
        )
        validate_type(
            og.Type(og.BaseDataType.DOUBLE, 16, 1, og.AttributeRole.MATRIX),
            [
                ((1, 0, 0, 1), (2, 0, 0, 2), (3, 0, 0, 3), (4, 0, 0, 4)),
                ((5, 0, 0, 5), (6, 0, 0, 6), (7, 0, 0, 7), (8, 0, 0, 8)),
            ],
        )
        validate_type(og.Type(og.BaseDataType.DOUBLE, 3, 0, og.AttributeRole.NORMAL), (0, 1, 0))
        validate_type(og.Type(og.BaseDataType.DOUBLE, 3, 1, og.AttributeRole.NORMAL), [(1, 0.0, 0.0), (0, 1, 0.0)])
        validate_type(og.Type(og.BaseDataType.FLOAT, 3, 0, og.AttributeRole.NORMAL), (1.5, 1.6, 1.7))
        validate_type(og.Type(og.BaseDataType.FLOAT, 3, 1, og.AttributeRole.NORMAL), [(1.5, 1.6, 1.7), (2.5, 2.6, 2.7)])
        validate_type(og.Type(og.BaseDataType.HALF, 3, 0, og.AttributeRole.NORMAL), (1.5, 1.6, 1.7))
        validate_type(og.Type(og.BaseDataType.HALF, 3, 1, og.AttributeRole.NORMAL), [(1.5, 1.6, 1.7), (2.5, 2.6, 2.7)])
        validate_type(og.Type(og.BaseDataType.DOUBLE, 3, 0, og.AttributeRole.POSITION), (1.5, 1.6, 1.7))
        validate_type(
            og.Type(og.BaseDataType.DOUBLE, 3, 1, og.AttributeRole.POSITION), [(1.5, 1.6, 1.7), (2.5, 2.6, 2.7)]
        )
        validate_type(og.Type(og.BaseDataType.FLOAT, 3, 0, og.AttributeRole.POSITION), (1.5, 1.6, 1.7))
        validate_type(
            og.Type(og.BaseDataType.FLOAT, 3, 1, og.AttributeRole.POSITION), [(1.5, 1.6, 1.7), (2.5, 2.6, 2.7)]
        )
        validate_type(og.Type(og.BaseDataType.HALF, 3, 0, og.AttributeRole.POSITION), (1.5, 1.6, 1.7))
        validate_type(
            og.Type(og.BaseDataType.HALF, 3, 1, og.AttributeRole.POSITION), [(1.5, 1.6, 1.7), (2.5, 2.6, 2.7)]
        )
        validate_type(og.Type(og.BaseDataType.DOUBLE, 4, 0, og.AttributeRole.QUATERNION), (1, 0, 0, 1))
        validate_type(og.Type(og.BaseDataType.DOUBLE, 4, 1, og.AttributeRole.QUATERNION), [(1, 0, 0, 1), (0, 1, 0, 1)])
        validate_type(og.Type(og.BaseDataType.FLOAT, 4, 0, og.AttributeRole.QUATERNION), (1, 0, 0, 1))
        validate_type(og.Type(og.BaseDataType.FLOAT, 4, 1, og.AttributeRole.QUATERNION), [(1, 0, 0, 1), (0, 1, 0, 1)])
        validate_type(og.Type(og.BaseDataType.HALF, 4, 0, og.AttributeRole.QUATERNION), (1, 0, 0, 1))
        validate_type(og.Type(og.BaseDataType.HALF, 4, 1, og.AttributeRole.QUATERNION), [(1, 0, 0, 1), (0, 1, 0, 1)])
        validate_type(og.Type(og.BaseDataType.UCHAR, 1, 1, og.AttributeRole.TEXT), "text")
        validate_type(og.Type(og.BaseDataType.DOUBLE, 2, 0, og.AttributeRole.TEXCOORD), (1.5, 1.6))
        validate_type(og.Type(og.BaseDataType.DOUBLE, 2, 1, og.AttributeRole.TEXCOORD), [(1.5, 1.6), (2.5, 2.6)])
        validate_type(og.Type(og.BaseDataType.FLOAT, 2, 0, og.AttributeRole.TEXCOORD), (1.5, 1.6))
        validate_type(og.Type(og.BaseDataType.FLOAT, 2, 1, og.AttributeRole.TEXCOORD), [(1.5, 1.6), (2.5, 2.6)])
