#ifndef UFBXT_CHECK_SCENE_H_INCLUDED
#define UFBXT_CHECK_SCENE_H_INCLUDED

#ifndef ufbxt_assert
#include <assert.h>
#define ufbxt_assert(cond) assert(cond)
#endif

#ifndef ufbxt_arraycount
#define ufbxt_arraycount(arr) (sizeof(arr) / sizeof(*(arr)))
#endif

static uint32_t ufbxt_sink = 0;

#include <math.h>
#include <string.h>
#include "../ufbx.h"

static int ufbxt_is_utf8(const char *str, size_t length)
{
	// Table of valid UTF-8 byte sequences: {min,max} (inclusive)
	// Adapted from https://www.w3.org/International/questions/qa-forms-utf-8
	static const unsigned char utf8_forms[8][4][2] = { 
		{ {0xc2,0xdf}, {0x80,0xbf},                           }, // Non-overlong 2-byte
		{ {0xe0,0xe0}, {0xa0,0xbf}, {0x80,0xbf},              }, // Excluding overlongs
		{ {0xe1,0xec}, {0x80,0xbf}, {0x80,0xbf},              }, // Straight 3-byte
		{ {0xee,0xef}, {0x80,0xbf}, {0x80,0xbf},              }, // Straight 3-byte
		{ {0xed,0xed}, {0x80,0x9f}, {0x80,0xbf},              }, // Excluding surrogates
		{ {0xf0,0xf0}, {0x90,0xbf}, {0x80,0xbf}, {0x80,0xbf}, }, // Planes 1-3
		{ {0xf1,0xf3}, {0x80,0xbf}, {0x80,0xbf}, {0x80,0xbf}, }, // Planes 4-15
		{ {0xf4,0xf4}, {0x80,0x8f}, {0x80,0xbf}, {0x80,0xbf}, }, // Plane 16
	};

	size_t pos = 0, form, len;
	while (pos < length) {
		unsigned char c = (unsigned char)str[pos];

		// Fast path for ASCII (U+0000 excluded in ufbx)
		if (c >= 0x01 && c <= 0x7f) {
			pos += 1;
			continue;
		}

		// All forms can be distinguished from the leading byte
		for (form = 0; form < 8; form++) {
			const unsigned char *range = utf8_forms[form][0];
			// Check that the first byte is within range
			if (c >= range[0] && c <= range[1]) break;
		}

		// Invalid starting byte
		if (form == 8) return 0;

		// Validate the following 1-3 bytes
		for (len = 1; len < 4; len++) {
			const unsigned char *range = utf8_forms[form][len];
			// Forms end with max=0 (zero initialized)
			if (range[1] == 0) break;
			// Out of bounds
			if (pos + len >= length) return 0;
			// Check that the next byte is within range
			c = (unsigned char)str[pos + len];
			if (c < utf8_forms[form][len][0] || c > utf8_forms[form][len][1]) return 0;
		}

		// Advance to the next codepoint
		pos += len;
	}

	return 1;
}

static bool ufbxt_float_equal(double a, double b)
{
	if (isnan(a)) {
		return isnan(b);
	} else {
		return a == b;
	}
}

static void ufbxt_check_string(ufbx_string str)
{
	// Data may never be NULL, empty strings should have data = ""
	ufbxt_assert(str.data != NULL);
	ufbxt_assert(strlen(str.data) == str.length);

	// `ufbx_string` is always UTF-8
	ufbxt_assert(ufbxt_is_utf8(str.data, str.length));
}

static void ufbxt_check_memory(const void *data, size_t size)
{
	const uint8_t *d = (const uint8_t*)data;
	for (size_t i = 0; i < size; i++) {
		ufbxt_sink += (uint32_t)d[i];
	}
}

static void ufbxt_check_blob(ufbx_blob blob)
{
	ufbxt_check_memory(blob.data, blob.size);
}

#define ufbxt_check_list(list) ufbxt_check_memory((list).data, (list).count * sizeof(*(list).data))

static void ufbxt_check_element_ptr_any(ufbx_scene *scene, ufbx_element *element)
{
	if (!element) return;
	ufbxt_assert(scene->elements.data[element->element_id] == element);
	ufbxt_assert(scene->elements_by_type[element->type].data[element->typed_id] == element);
}

static void ufbxt_check_element_ptr(ufbx_scene *scene, void *v_element, ufbx_element_type type)
{
	if (!v_element) return;
	ufbx_element *element = (ufbx_element*)v_element;
	ufbxt_assert(element->type == type);
	ufbxt_assert(scene->elements.data[element->element_id] == element);
	ufbxt_assert(scene->elements_by_type[element->type].data[element->typed_id] == element);
}

static void ufbxt_check_vertex_element(ufbx_scene *scene, ufbx_mesh *mesh, void *void_elem, size_t elem_size)
{
	ufbx_vertex_attrib *elem = (ufbx_vertex_attrib*)void_elem;
	if (!elem->exists) {
		ufbxt_assert(elem->values.data == NULL);
		ufbxt_assert(elem->values.count == 0);
		ufbxt_assert(elem->indices.data == NULL);
		ufbxt_assert(elem->indices.count == 0);
		return;
	}

	ufbxt_assert(elem->values.count >= 0);
	ufbxt_assert(elem->values.count <= INT32_MAX);
	ufbxt_assert(elem->indices.count == mesh->num_indices);
	if (mesh->num_indices > 0) {
		ufbxt_assert(elem->indices.data != NULL);
	}

	// Check that the indices are in range
	for (size_t i = 0; i < mesh->num_indices; i++) {
		uint32_t ix = elem->indices.data[i];
		ufbxt_assert(ix < elem->values.count || (ix == UFBX_NO_INDEX && scene->metadata.may_contain_no_index));
	}

	// Check that the data at invalid index is valid and zero
	if (elem->indices.count > 0) {
		char zero[32] = { 0 };
		ufbxt_assert(elem_size <= 32);
		ufbxt_assert(!memcmp((char*)elem->values.data - elem_size, zero, elem_size));
	}
}

static void ufbxt_check_mesh_list_count(ufbx_scene *scene, ufbx_mesh *mesh, size_t count, size_t required_count, bool optional)
{
	if (count > 0 || !optional) {
		ufbxt_assert(count == required_count);
	}
}

#define ufbxt_check_mesh_list(scene, mesh, list, required_count, optional) do { \
		ufbxt_check_list(list); \
		ufbxt_check_mesh_list_count((scene), (mesh), (list).count, (required_count), (optional)); \
	} while (0)

static void ufbxt_check_props(ufbx_scene *scene, const ufbx_props *props, bool top)
{
	ufbx_prop *prev = NULL;
	for (size_t i = 0; i < props->props.count; i++) {
		ufbx_prop *prop = &props->props.data[i];

		ufbxt_assert(prop->type < UFBX_PROP_TYPE_COUNT);
		ufbxt_check_string(prop->name);
		ufbxt_check_string(prop->value_str);
		ufbxt_check_blob(prop->value_blob);

		// Properties should be sorted by name
		if (prev) {
			ufbxt_assert(prop->_internal_key >= prev->_internal_key);
			ufbxt_assert(strcmp(prop->name.data, prev->name.data) >= 0);
		}

		ufbx_prop *ref = ufbx_find_prop(props, prop->name.data);
		if (top) {
			ufbxt_assert(ref == prop);
		} else {
			ufbxt_assert(ref != NULL);
		}

		// `REAL/VEC2/VEC3/VEC4` are mutually exclusive
		uint32_t vec_flag = (uint32_t)prop->flags & (UFBX_PROP_FLAG_VALUE_REAL|UFBX_PROP_FLAG_VALUE_VEC2|UFBX_PROP_FLAG_VALUE_VEC3|UFBX_PROP_FLAG_VALUE_VEC4);
		ufbxt_assert((vec_flag & (vec_flag - 1)) == 0);

		prev = prop;
	}

	if (props->defaults) {
		ufbxt_check_props(scene, props->defaults, false);
	}
}

static void ufbxt_check_element(ufbx_scene *scene, ufbx_element *element)
{
	if (scene->dom_root && scene->metadata.file_format == UFBX_FILE_FORMAT_FBX) {
		bool requires_dom = true;
		uint32_t version = scene->metadata.version;

		if (element->type == UFBX_ELEMENT_NODE && ((ufbx_node*)element)->is_root) {
			requires_dom = false;
		} else if (version < 6000 && element->type == UFBX_ELEMENT_ANIM_STACK) {
			requires_dom = false;
		} else if (version < 6000 && element->type == UFBX_ELEMENT_ANIM_LAYER) {
			requires_dom = false;
		} else if (version < 6000 && element->type == UFBX_ELEMENT_SKIN_DEFORMER) {
			requires_dom = false;
		}

		if (requires_dom) {
			ufbxt_assert(element->dom_node);
		}
	}

	ufbxt_check_props(scene, &element->props, true);
	ufbxt_check_string(element->name);
	ufbxt_assert(scene->elements.data[element->element_id] == element);

	ufbxt_assert(scene->elements.data[element->element_id] == element);
	ufbxt_assert(scene->elements_by_type[element->type].data[element->typed_id] == element);

	for (size_t i = 0; i < element->connections_src.count; i++) {
		ufbx_connection *c = &element->connections_src.data[i];
		ufbxt_check_string(c->src_prop);
		ufbxt_check_string(c->dst_prop);
		ufbxt_assert(c->src == element);
		ufbxt_check_element_ptr_any(scene, c->dst);
		if (i > 0) {
			int cmp = strcmp(c[-1].src_prop.data, c[0].src_prop.data);
			ufbxt_assert(cmp <= 0);
			if (cmp == 0) {
				ufbxt_assert(strcmp(c[-1].dst_prop.data, c[0].dst_prop.data) <= 0);
			}
		}
	}

	for (size_t i = 0; i < element->connections_dst.count; i++) {
		ufbx_connection *c = &element->connections_dst.data[i];
		ufbxt_check_string(c->src_prop);
		ufbxt_check_string(c->dst_prop);
		ufbxt_assert(c->dst == element);
		ufbxt_check_element_ptr_any(scene, c->src);
		if (i > 0) {
			int cmp = strcmp(c[-1].dst_prop.data, c[0].dst_prop.data);
			ufbxt_assert(cmp <= 0);
			if (cmp == 0) {
				ufbxt_assert(strcmp(c[-1].src_prop.data, c[0].src_prop.data) <= 0);
			}
		}
	}

	ufbxt_assert(element->type >= 0);
	ufbxt_assert(element->type < UFBX_ELEMENT_TYPE_COUNT);
	if (element->type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && element->type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB) {
		for (size_t i = 0; i < element->instances.count; i++) {
			ufbx_node *node = element->instances.data[i];
			ufbxt_check_element_ptr(scene, node, UFBX_ELEMENT_NODE);
			bool found = false;
			for (size_t j = 0; j < node->all_attribs.count; j++) {
				if (node->all_attribs.data[j] == element) {
					found = true;
					break;
				}
			}
			ufbxt_assert(found);
		}
	} else {
		ufbxt_assert(element->instances.count == 0);
	}
}

static void ufbxt_check_unknown(ufbx_scene *scene, ufbx_unknown *unknown)
{
	ufbxt_check_string(unknown->type);
	ufbxt_check_string(unknown->sub_type);
	ufbxt_check_string(unknown->super_type);
}

static void ufbxt_check_node(ufbx_scene *scene, ufbx_node *node)
{
	ufbxt_check_element_ptr(scene, node->parent, UFBX_ELEMENT_NODE);
	if (node->parent) {
		bool found = false;
		for (size_t i = 0; i < node->parent->children.count; i++) {
			if (node->parent->children.data[i] == node) {
				found = true;
				break;
			}
		}
		ufbxt_assert(found);
	}

	for (size_t i = 0; i < node->children.count; i++) {
		ufbxt_assert(node->children.data[i]->parent == node);
	}

	for (size_t i = 0; i < node->all_attribs.count; i++) {
		ufbx_element *attrib = node->all_attribs.data[i];
		ufbxt_check_element_ptr_any(scene, attrib);
		bool found = false;
		for (size_t j = 0; j < attrib->instances.count; j++) {
			if (attrib->instances.data[j] == node) {
				found = true;
				break;
			}
		}
		ufbxt_assert(found);
	}

	if (node->all_attribs.count > 0) {
		ufbxt_assert(node->attrib == node->all_attribs.data[0]);
		if (node->all_attribs.count == 1) {
			ufbxt_assert(node->attrib_type == node->attrib->type);
		}
	}

	switch (node->attrib_type) {
	case UFBX_ELEMENT_MESH: ufbxt_assert(node->mesh); break;
	case UFBX_ELEMENT_LIGHT: ufbxt_assert(node->light); break;
	case UFBX_ELEMENT_CAMERA: ufbxt_assert(node->camera); break;
	case UFBX_ELEMENT_BONE: ufbxt_assert(node->bone); break;
	default: /* No shorthand */ break;
	}

	for (size_t i = 0; i < node->materials.count; i++) {
		ufbxt_assert(node->materials.data[i]);
		ufbxt_check_element_ptr(scene, node->materials.data[i], UFBX_ELEMENT_MATERIAL);
	}

	ufbxt_assert((uint32_t)node->attrib_type < (uint32_t)UFBX_ELEMENT_TYPE_COUNT);
	ufbxt_assert((uint32_t)node->inherit_type < (uint32_t)UFBX_INHERIT_TYPE_COUNT);

	if (node->is_root) {
		ufbxt_assert(!node->has_geometry_transform);
	}

	ufbxt_check_element_ptr(scene, node->geometry_transform_helper, UFBX_ELEMENT_NODE);
	if (node->geometry_transform_helper) {
		ufbxt_assert(node->geometry_transform_helper->is_geometry_transform_helper);

		if (scene->metadata.has_warning[UFBX_WARNING_DUPLICATE_OBJECT_ID]) {
			// In broken cases we may have multiple geometry transform helpers
			ufbxt_assert(node->children.count > 0);
			ufbxt_assert(node->children.data[0]->is_geometry_transform_helper);
		} else {
			// Geometry transform helper must always be the first child if the scene
			ufbxt_assert(node->geometry_transform_helper == node->children.data[0]);
		}
	}

	if (node->is_geometry_transform_helper) {
		ufbxt_assert(node->parent);
		if (scene->metadata.has_warning[UFBX_WARNING_DUPLICATE_OBJECT_ID]) {
			// In broken cases we may have multiple geometry transform helpers
			ufbxt_assert(node->parent->geometry_transform_helper);
		} else {
			ufbxt_assert(node->parent->geometry_transform_helper == node);
		}
	}
}

static void ufbxt_check_mesh(ufbx_scene *scene, ufbx_mesh *mesh)
{
	// ufbx_mesh *found = ufbx_find_mesh(scene, mesh->node.name.data);
	// ufbxt_assert(found && !strcmp(found->node.name.data, mesh->node.name.data));

	ufbxt_assert(mesh->vertices.data == mesh->vertex_position.values.data);
	ufbxt_assert(mesh->vertices.count == mesh->num_vertices);
	ufbxt_assert(mesh->vertex_indices.data == mesh->vertex_position.indices.data);
	ufbxt_assert(mesh->vertex_indices.count == mesh->num_indices);
	ufbxt_assert(mesh->vertex_first_index.count == mesh->num_vertices);

	for (size_t ii = 0; ii < mesh->num_indices; ii++) {
		uint32_t vi = mesh->vertex_indices.data[ii];
		if (vi != UFBX_NO_INDEX) {
			ufbxt_assert(vi < mesh->num_vertices);
			ufbxt_assert(mesh->vertex_first_index.data[vi] <= ii);
		}
	}

	for (size_t vi = 0; vi < mesh->num_vertices; vi++) {
		int32_t ii = (int32_t)mesh->vertex_first_index.data[vi];
		if (ii >= 0) {
			ufbxt_assert(mesh->vertex_indices.data[ii] == vi);
		} else {
			ufbxt_assert((uint32_t)ii == UFBX_NO_INDEX);
		}
	}

	ufbxt_check_vertex_element(scene, mesh, &mesh->vertex_position, sizeof(ufbx_vec3));
	ufbxt_check_vertex_element(scene, mesh, &mesh->vertex_normal, sizeof(ufbx_vec3));
	ufbxt_check_vertex_element(scene, mesh, &mesh->vertex_tangent, sizeof(ufbx_vec3));
	ufbxt_check_vertex_element(scene, mesh, &mesh->vertex_bitangent, sizeof(ufbx_vec3));
	ufbxt_check_vertex_element(scene, mesh, &mesh->vertex_uv, sizeof(ufbx_vec2));
	ufbxt_check_vertex_element(scene, mesh, &mesh->vertex_color, sizeof(ufbx_vec4));
	ufbxt_check_vertex_element(scene, mesh, &mesh->skinned_position, sizeof(ufbx_vec3));
	ufbxt_check_vertex_element(scene, mesh, &mesh->skinned_normal, sizeof(ufbx_vec3));

	ufbxt_assert(mesh->num_vertices == mesh->vertex_position.values.count);
	ufbxt_assert(mesh->num_triangles <= mesh->num_indices);

	ufbxt_assert(mesh->vertex_position.value_reals == 3);
	ufbxt_assert(mesh->vertex_normal.value_reals == 3);
	ufbxt_assert(mesh->vertex_tangent.value_reals == 3);
	ufbxt_assert(mesh->vertex_bitangent.value_reals == 3);
	ufbxt_assert(mesh->vertex_uv.value_reals == 2);
	ufbxt_assert(mesh->vertex_color.value_reals == 4);
	ufbxt_assert(mesh->vertex_crease.value_reals == 1);

	if (!scene->metadata.may_contain_missing_vertex_position) {
		ufbxt_assert(mesh->vertex_position.exists);
	}

	if (mesh->vertex_position.exists) {
		ufbxt_assert(mesh->vertex_position.unique_per_vertex);
	}

	ufbxt_check_mesh_list(scene, mesh, mesh->edges, mesh->num_edges, false);
	ufbxt_check_mesh_list(scene, mesh, mesh->edge_crease, mesh->num_edges, true);
	ufbxt_check_mesh_list(scene, mesh, mesh->edge_smoothing, mesh->num_edges, true);
	ufbxt_check_mesh_list(scene, mesh, mesh->edge_visibility, mesh->num_edges, true);

	ufbxt_check_mesh_list(scene, mesh, mesh->face_material, mesh->num_faces, true);
	ufbxt_check_mesh_list(scene, mesh, mesh->face_smoothing, mesh->num_faces, true);
	ufbxt_check_mesh_list(scene, mesh, mesh->face_group, mesh->num_faces, true);
	ufbxt_check_mesh_list(scene, mesh, mesh->face_hole, mesh->num_faces, true);

	size_t num_triangles = 0;
	size_t max_face_triangles = 0;
	size_t num_bad_faces[3] = { 0 };

	uint32_t prev_end = 0;
	ufbxt_assert(mesh->faces.count == mesh->num_faces);
	for (size_t i = 0; i < mesh->num_faces; i++) {
		ufbx_face face = mesh->faces.data[i];
		ufbxt_assert(face.index_begin == prev_end);
		prev_end = face.index_begin + face.num_indices;
		ufbxt_assert(prev_end <= mesh->num_indices);

		if (face.num_indices >= 3) {
			size_t tris = face.num_indices - 2;
			num_triangles += tris;
			if (tris > max_face_triangles) {
				max_face_triangles = tris;
			}
		} else {
			num_bad_faces[face.num_indices]++;
		}
	}

	ufbxt_assert(mesh->num_triangles == num_triangles);
	ufbxt_assert(mesh->max_face_triangles == max_face_triangles);
	ufbxt_assert(mesh->num_empty_faces == num_bad_faces[0]);
	ufbxt_assert(mesh->num_point_faces == num_bad_faces[1]);
	ufbxt_assert(mesh->num_line_faces == num_bad_faces[2]);

	if (!mesh->from_tessellated_nurbs && !mesh->subdivision_evaluated) {
		ufbxt_assert(scene->metadata.max_face_triangles >= max_face_triangles);
	}

	ufbxt_assert(mesh->edges.count == mesh->num_edges);
	for (size_t i = 0; i < mesh->num_edges; i++) {
		ufbx_edge edge = mesh->edges.data[i];
		ufbxt_assert(edge.a < mesh->num_indices);
		ufbxt_assert(edge.b < mesh->num_indices);
	}

	for (size_t i = 0; i < mesh->uv_sets.count; i++) {
		ufbx_uv_set *set = &mesh->uv_sets.data[i];
		ufbxt_assert(set->vertex_uv.value_reals == 2);
		ufbxt_assert(set->vertex_tangent.value_reals == 3);
		ufbxt_assert(set->vertex_bitangent.value_reals == 3);

		if (i == 0) {
			ufbxt_assert(mesh->vertex_uv.values.data == set->vertex_uv.values.data);
			ufbxt_assert(mesh->vertex_uv.values.count == set->vertex_uv.values.count);
			ufbxt_assert(mesh->vertex_uv.indices.data == set->vertex_uv.indices.data);
			ufbxt_assert(mesh->vertex_uv.indices.count == set->vertex_uv.indices.count);
		}
		ufbxt_check_string(set->name);
		ufbxt_check_vertex_element(scene, mesh, &set->vertex_uv, sizeof(ufbx_vec2));
		ufbxt_check_vertex_element(scene, mesh, &set->vertex_tangent, sizeof(ufbx_vec3));
		ufbxt_check_vertex_element(scene, mesh, &set->vertex_bitangent, sizeof(ufbx_vec3));
	}

	for (size_t i = 0; i < mesh->color_sets.count; i++) {
		ufbx_color_set *set = &mesh->color_sets.data[i];
		ufbxt_assert(set->vertex_color.value_reals == 4);

		if (i == 0) {
			ufbxt_assert(mesh->vertex_color.values.data == set->vertex_color.values.data);
			ufbxt_assert(mesh->vertex_color.values.count == set->vertex_color.values.count);
			ufbxt_assert(mesh->vertex_color.indices.data == set->vertex_color.indices.data);
			ufbxt_assert(mesh->vertex_color.indices.count == set->vertex_color.indices.count);
		}
		ufbxt_check_string(set->name);
		ufbxt_check_vertex_element(scene, mesh, &set->vertex_color, sizeof(ufbx_vec4));
	}

	for (size_t i = 0; i < mesh->num_edges; i++) {
		ufbx_edge edge = mesh->edges.data[i];
		ufbxt_assert(edge.a < mesh->num_indices);
		ufbxt_assert(edge.b < mesh->num_indices);
	}

	if (mesh->face_material.count) {
		ufbxt_assert(mesh->face_material.count == mesh->num_faces);
		for (size_t i = 0; i < mesh->num_faces; i++) {
			int32_t material = mesh->face_material.data[i];
			ufbxt_assert(material >= 0 && (size_t)material < mesh->materials.count);
		}
	} else {
		ufbxt_assert(mesh->face_material.count == 0);
	}

	for (size_t i = 0; i < mesh->materials.count; i++) {
		ufbx_mesh_material *mat = &mesh->materials.data[i];
		ufbxt_check_element_ptr(scene, mat->material, UFBX_ELEMENT_MATERIAL);

		if (!scene->metadata.may_contain_null_materials) {
			ufbxt_assert(mat->material);
		}

		ufbxt_assert(mat->face_indices.count == mat->num_faces);

		size_t mat_bad_faces[3] = { 0 };
		size_t mat_tris = 0;
		for (size_t j = 0; j < mat->num_faces; j++) {
			uint32_t ix = mat->face_indices.data[j];
			ufbx_face face = mesh->faces.data[ix];
			if (face.num_indices >= 3) {
				mat_tris += face.num_indices - 2;
			} else {
				mat_bad_faces[face.num_indices]++;
			}
			ufbxt_assert(mesh->face_material.data[ix] == (int32_t)i);
		}

		ufbxt_assert(mat->num_triangles == mat_tris);
		ufbxt_assert(mat->num_empty_faces == mat_bad_faces[0]);
		ufbxt_assert(mat->num_point_faces == mat_bad_faces[1]);
		ufbxt_assert(mat->num_line_faces == mat_bad_faces[2]);
	}

	if (mesh->face_group.count) {
		ufbxt_assert(mesh->face_group.count == mesh->num_faces);
		for (size_t i = 0; i < mesh->num_faces; i++) {
			uint32_t group = mesh->face_group.data[i];
			ufbxt_assert((size_t)group < mesh->face_groups.count);
		}
	} else {
		ufbxt_assert(mesh->face_groups.count == 0);
	}

	if (mesh->face_groups.count > 0) {
		size_t total_group_faces = 0;
		size_t total_group_tris = 0;
		for (size_t i = 0; i < mesh->face_groups.count; i++) {
			ufbx_face_group *group = &mesh->face_groups.data[i];

			if (i > 0) {
				ufbxt_assert(group->id >= mesh->face_groups.data[i - 1].id);
			}

			ufbxt_check_string(group->name);
			ufbxt_assert(group->face_indices.count == group->num_faces);

			size_t group_tris = 0;
			for (size_t j = 0; j < group->num_faces; j++) {
				uint32_t ix = group->face_indices.data[j];
				ufbx_face face = mesh->faces.data[ix];
				if (face.num_indices >= 3) {
					group_tris += face.num_indices - 2;
				}
				ufbxt_assert(mesh->face_group.data[ix] == (uint32_t)i);
			}
			ufbxt_assert(group->num_triangles == group_tris);

			total_group_faces += group->num_faces;
			total_group_tris += group->num_triangles;
		}
		ufbxt_assert(total_group_faces == mesh->num_faces);
		ufbxt_assert(total_group_tris == mesh->num_triangles);
	}

	for (size_t i = 0; i < mesh->skin_deformers.count; i++) {
		ufbxt_assert(mesh->skin_deformers.data[i]->vertices.count >= mesh->num_vertices);
		ufbxt_check_element_ptr(scene, mesh->skin_deformers.data[i], UFBX_ELEMENT_SKIN_DEFORMER);
	}
	for (size_t i = 0; i < mesh->blend_deformers.count; i++) {
		ufbxt_check_element_ptr(scene, mesh->blend_deformers.data[i], UFBX_ELEMENT_BLEND_DEFORMER);
	}
	for (size_t i = 0; i < mesh->cache_deformers.count; i++) {
		ufbxt_check_element_ptr(scene, mesh->cache_deformers.data[i], UFBX_ELEMENT_CACHE_DEFORMER);
	}
	for (size_t i = 0; i < mesh->all_deformers.count; i++) {
		ufbxt_check_element_ptr_any(scene, mesh->all_deformers.data[i]);
	}

	if (!scene->metadata.may_contain_null_materials) {
		for (size_t i = 0; i < mesh->instances.count; i++) {
			ufbxt_assert(mesh->instances.data[i]->materials.count >= mesh->materials.count);
		}
	}

	// No loose UV or color
	if (mesh->vertex_uv.exists) {
		ufbxt_assert(mesh->uv_sets.count > 0);
	}
	if (mesh->vertex_color.exists) {
		ufbxt_assert(mesh->color_sets.count > 0);
	}
}

static void ufbxt_check_light(ufbx_scene *scene, ufbx_light *light)
{
	ufbxt_assert((uint32_t)light->type < (uint32_t)UFBX_LIGHT_TYPE_COUNT);
	ufbxt_assert((uint32_t)light->decay < (uint32_t)UFBX_LIGHT_DECAY_COUNT);
	ufbxt_assert((uint32_t)light->area_shape < (uint32_t)UFBX_LIGHT_AREA_SHAPE_COUNT);
}

static void ufbxt_check_camera(ufbx_scene *scene, ufbx_camera *camera)
{
	ufbxt_assert((uint32_t)camera->projection_mode < (uint32_t)UFBX_PROJECTION_MODE_COUNT);
	ufbxt_assert((uint32_t)camera->aspect_mode < (uint32_t)UFBX_ASPECT_MODE_COUNT);
	ufbxt_assert((uint32_t)camera->aperture_mode < (uint32_t)UFBX_APERTURE_MODE_COUNT);
	ufbxt_assert((uint32_t)camera->gate_fit < (uint32_t)UFBX_GATE_FIT_COUNT);
	ufbxt_assert((uint32_t)camera->aperture_format < (uint32_t)UFBX_APERTURE_FORMAT_COUNT);

	if (camera->projection_mode == UFBX_PROJECTION_MODE_PERSPECTIVE) {
		ufbxt_assert(ufbxt_float_equal(camera->projection_plane.x, camera->field_of_view_tan.x));
		ufbxt_assert(ufbxt_float_equal(camera->projection_plane.y, camera->field_of_view_tan.y));
	} else if (camera->projection_mode == UFBX_PROJECTION_MODE_ORTHOGRAPHIC) {
		ufbxt_assert(ufbxt_float_equal(camera->projection_plane.x, camera->orthographic_size.x));
		ufbxt_assert(ufbxt_float_equal(camera->projection_plane.y, camera->orthographic_size.y));
	} else {
		ufbxt_assert(false && "Unhandled projection_mode");
	}
}

static void ufbxt_check_bone(ufbx_scene *scene, ufbx_bone *bone)
{
}

static void ufbxt_check_empty(ufbx_scene *scene, ufbx_empty *empty)
{
}

static void ufbxt_check_line_curve(ufbx_scene *scene, ufbx_line_curve *line)
{
	for (size_t i = 0; i < line->point_indices.count; i++) {
		int32_t ix = line->point_indices.data[i];
		ufbxt_assert(ix >= 0 && (uint32_t)ix < line->control_points.count);
	}

	for (size_t i = 0; i < line->segments.count; i++) {
		ufbx_line_segment seg = line->segments.data[i];
		ufbxt_assert(seg.num_indices <= line->point_indices.count);
		ufbxt_assert(seg.index_begin <= line->point_indices.count - seg.num_indices);
	}

	ufbxt_check_list(line->control_points);
}

static void ufbxt_check_nurbs_basis(ufbx_scene *scene, ufbx_nurbs_basis *basis)
{
	ufbxt_assert((uint32_t)basis->topology < (uint32_t)UFBX_NURBS_TOPOLOGY_COUNT);
	ufbxt_check_list(basis->knot_vector);
	ufbxt_check_list(basis->spans);
	if (basis->valid) {
		ufbxt_assert(basis->order > 1);
		ufbxt_assert(basis->knot_vector.count >= 2*(basis->order-1) + 1);
	}
}

static void ufbxt_check_nurbs_curve(ufbx_scene *scene, ufbx_nurbs_curve *curve)
{
	ufbxt_check_nurbs_basis(scene, &curve->basis);
	ufbxt_check_list(curve->control_points);
}

static void ufbxt_check_nurbs_surface(ufbx_scene *scene, ufbx_nurbs_surface *surface)
{
	ufbxt_check_nurbs_basis(scene, &surface->basis_u);
	ufbxt_check_nurbs_basis(scene, &surface->basis_v);
	ufbxt_check_list(surface->control_points);
}

static void ufbxt_check_nurbs_trim_surface(ufbx_scene *scene, ufbx_nurbs_trim_surface *surface)
{
}

static void ufbxt_check_nurbs_trim_boundary(ufbx_scene *scene, ufbx_nurbs_trim_boundary *boundary)
{
}

static void ufbxt_check_procedural_geometry(ufbx_scene *scene, ufbx_procedural_geometry *geometry)
{
}

static void ufbxt_check_stereo_camera(ufbx_scene *scene, ufbx_stereo_camera *camera)
{
	ufbxt_check_element_ptr(scene, camera->left, UFBX_ELEMENT_CAMERA);
	ufbxt_check_element_ptr(scene, camera->right, UFBX_ELEMENT_CAMERA);
}

static void ufbxt_check_camera_switcher(ufbx_scene *scene, ufbx_camera_switcher *switcher)
{
}

static void ufbxt_check_marker(ufbx_scene *scene, ufbx_marker *marker)
{
	ufbxt_assert((uint32_t)marker->type < (uint32_t)UFBX_MARKER_TYPE_COUNT);
}

static void ufbxt_check_lod_level(ufbx_scene *scene, ufbx_lod_group *lod_group, ufbx_lod_level *level)
{
	ufbxt_assert((uint32_t)level->display < (uint32_t)UFBX_LOD_DISPLAY_COUNT);
}

static void ufbxt_check_lod_group(ufbx_scene *scene, ufbx_lod_group *lod_group)
{
	for (size_t i = 0; i < lod_group->lod_levels.count; i++) {
		ufbxt_check_lod_level(scene, lod_group, &lod_group->lod_levels.data[i]);
	}
}

static void ufbxt_check_skin_deformer(ufbx_scene *scene, ufbx_skin_deformer *deformer)
{
	for (size_t i = 0; i < deformer->clusters.count; i++) {
		ufbxt_check_element_ptr(scene, deformer->clusters.data[i], UFBX_ELEMENT_SKIN_CLUSTER);
		if (!scene->metadata.may_contain_broken_elements) {
			ufbxt_assert(deformer->clusters.data[i]->bone_node);
		}
	}
	for (size_t i = 0; i < deformer->vertices.count; i++) {
		ufbx_skin_vertex vertex = deformer->vertices.data[i];
		ufbxt_assert(vertex.weight_begin <= deformer->weights.count);
		ufbxt_assert(deformer->weights.count - vertex.weight_begin >= vertex.num_weights);

		for (size_t i = 1; i < vertex.num_weights; i++) {
			size_t ix = vertex.weight_begin + i;
			ufbxt_assert(deformer->weights.data[ix - 1].weight >= deformer->weights.data[ix].weight);
		}
	}
	for (size_t i = 0; i < deformer->weights.count; i++) {
		ufbxt_assert(deformer->weights.data[i].cluster_index < deformer->clusters.count);
	}
}

static void ufbxt_check_skin_cluster(ufbx_scene *scene, ufbx_skin_cluster *cluster)
{
	ufbxt_check_element_ptr(scene, cluster->bone_node, UFBX_ELEMENT_NODE);
	ufbxt_check_list(cluster->vertices);
	ufbxt_check_list(cluster->weights);
}

static void ufbxt_check_blend_deformer(ufbx_scene *scene, ufbx_blend_deformer *deformer)
{
	for (size_t i = 0; i < deformer->channels.count; i++) {
		ufbxt_check_element_ptr(scene, deformer->channels.data[i], UFBX_ELEMENT_BLEND_CHANNEL);
	}
}

static void ufbxt_check_blend_channel(ufbx_scene *scene, ufbx_blend_channel *channel)
{
	for (size_t i = 0; i < channel->keyframes.count; i++) {
		if (i > 0) {
			ufbxt_assert(channel->keyframes.data[i - 1].target_weight <= channel->keyframes.data[i].target_weight);
		}
		ufbxt_check_element_ptr(scene, channel->keyframes.data[i].shape, UFBX_ELEMENT_BLEND_SHAPE);
	}
}

static void ufbxt_check_blend_shape(ufbx_scene *scene, ufbx_blend_shape *shape)
{
	ufbxt_check_list(shape->offset_vertices);
	ufbxt_check_list(shape->position_offsets);
	ufbxt_check_list(shape->normal_offsets);
	ufbxt_assert(shape->offset_vertices.count == shape->position_offsets.count);
	if (shape->normal_offsets.count > 0) {
		ufbxt_assert(shape->normal_offsets.count == shape->offset_vertices.count);
	}
}

static void ufbxt_check_cache_deformer(ufbx_scene *scene, ufbx_cache_deformer *deformer)
{
	ufbxt_check_string(deformer->channel);
	ufbxt_check_element_ptr(scene, deformer->file, UFBX_ELEMENT_CACHE_FILE);
}

static void ufbxt_check_cache_file(ufbx_scene *scene, ufbx_cache_file *file)
{
	ufbxt_check_string(file->filename);
	ufbxt_check_string(file->absolute_filename);
	ufbxt_check_string(file->relative_filename);
}

static void ufbxt_check_material_map(ufbx_scene *scene, ufbx_material *material, ufbx_material_map *map)
{
	ufbxt_check_element_ptr(scene, map->texture, UFBX_ELEMENT_TEXTURE);
	ufbxt_assert(map->value_components <= 4);
}

static void ufbxt_check_material(ufbx_scene *scene, ufbx_material *material)
{
	for (size_t i = 0; i < material->textures.count; i++) {
		ufbxt_check_string(material->textures.data[i].material_prop);
		ufbxt_check_string(material->textures.data[i].shader_prop);
		ufbxt_check_element_ptr(scene, material->textures.data[i].texture, UFBX_ELEMENT_TEXTURE);
	}

	for (size_t i = 0; i < UFBX_MATERIAL_FBX_MAP_COUNT; i++) {
		ufbxt_check_material_map(scene, material, &material->fbx.maps[i]);
	}

	for (size_t i = 0; i < UFBX_MATERIAL_PBR_MAP_COUNT; i++) {
		ufbxt_check_material_map(scene, material, &material->pbr.maps[i]);
	}

	ufbxt_check_string(material->shader_prop_prefix);
	ufbxt_check_element_ptr(scene, material->shader, UFBX_ELEMENT_SHADER);
}

static void ufbxt_check_shader_texture_input(ufbx_scene *scene, ufbx_texture *texture, ufbx_shader_texture *shader, ufbx_shader_texture_input *input)
{
	ufbxt_check_string(input->name);
	ufbxt_assert(input->prop);

	ufbxt_check_string(input->value_str);

	if (input->prop) {
		ufbx_prop *prop = input->prop;
		ufbxt_assert(prop == ufbx_find_prop(&texture->props, prop->name.data));
	}

	if (input->texture_prop) {
		ufbx_prop *prop = input->texture_prop;
		ufbxt_assert(prop == ufbx_find_prop(&texture->props, prop->name.data));
	}

	if (input->texture_enabled_prop) {
		ufbx_prop *prop = input->texture_enabled_prop;
		ufbxt_assert(prop == ufbx_find_prop(&texture->props, prop->name.data));
	}

	ufbxt_check_element_ptr(scene, input->texture, UFBX_ELEMENT_TEXTURE);
}

static void ufbxt_check_shader_texture(ufbx_scene *scene, ufbx_texture *texture, ufbx_shader_texture *shader)
{
	ufbxt_check_string(shader->shader_name);
	ufbxt_check_string(shader->shader_source);

	ufbxt_check_element_ptr(scene, shader->main_texture, UFBX_ELEMENT_TEXTURE);

	for (size_t i = 0; i < shader->inputs.count; i++) {
		ufbxt_check_shader_texture_input(scene, texture, shader, &shader->inputs.data[i]);
	}
}

static void ufbxt_check_texture(ufbx_scene *scene, ufbx_texture *texture)
{
	ufbxt_check_string(texture->filename);
	ufbxt_check_string(texture->relative_filename);
	ufbxt_check_string(texture->absolute_filename);

	ufbxt_check_blob(texture->raw_filename);
	ufbxt_check_blob(texture->raw_relative_filename);
	ufbxt_check_blob(texture->raw_absolute_filename);

	ufbxt_check_element_ptr(scene, texture->video, UFBX_ELEMENT_VIDEO);
	for (size_t i = 0; i < texture->layers.count; i++) {
		ufbxt_assert(texture->layers.data[i].texture);
		ufbxt_check_element_ptr(scene, texture->layers.data[i].texture, UFBX_ELEMENT_TEXTURE);
	}

	if (texture->shader) {
		ufbxt_check_shader_texture(scene, texture, texture->shader);
	}

	for (size_t i = 0; i < texture->file_textures.count; i++) {
		ufbx_texture *file = texture->file_textures.data[i];
		ufbxt_check_element_ptr(scene, file, UFBX_ELEMENT_TEXTURE);
		ufbxt_assert(file->type == UFBX_TEXTURE_FILE);
	}

	if (texture->type == UFBX_TEXTURE_FILE) {
		ufbxt_assert(texture->file_textures.count >= 1);
		ufbxt_assert(texture->file_textures.data[0] == texture);
	} else if (texture->type == UFBX_TEXTURE_SHADER) {
		ufbxt_assert(texture->shader);
	}

	if (texture->raw_absolute_filename.size > 0 || texture->raw_relative_filename.size > 0) {
		ufbxt_assert(texture->has_file);
	}

	ufbxt_assert(texture->file_index < scene->texture_files.count || texture->file_index == UFBX_NO_INDEX);
	if (texture->has_file) {
		ufbxt_assert(texture->file_index < scene->texture_files.count);
		ufbx_texture_file *file = &scene->texture_files.data[texture->file_index];
		ufbxt_assert(!strcmp(file->absolute_filename.data, texture->absolute_filename.data)
			|| !strcmp(file->relative_filename.data, texture->relative_filename.data));
	} else {
		ufbxt_assert(texture->file_index == UFBX_NO_INDEX);
	}
}

static void ufbxt_check_video(ufbx_scene *scene, ufbx_video *video)
{
	ufbxt_check_string(video->filename);
	ufbxt_check_string(video->relative_filename);
	ufbxt_check_string(video->absolute_filename);

	ufbxt_check_blob(video->raw_filename);
	ufbxt_check_blob(video->raw_relative_filename);
	ufbxt_check_blob(video->raw_absolute_filename);
}

static void ufbxt_check_shader(ufbx_scene *scene, ufbx_shader *shader)
{
	ufbxt_assert((uint32_t)shader->type < (uint32_t)UFBX_SHADER_TYPE_COUNT);
	for (size_t i = 0; i < shader->bindings.count; i++) {
		ufbxt_check_element_ptr(scene, shader->bindings.data[i], UFBX_ELEMENT_SHADER_BINDING);
	}
}

static void ufbxt_check_shader_binding(ufbx_scene *scene, ufbx_shader_binding *binding)
{
	for (size_t i = 0; i < binding->prop_bindings.count; i++) {
		ufbx_shader_prop_binding prop = binding->prop_bindings.data[i];
		ufbxt_check_string(prop.material_prop);
		ufbxt_check_string(prop.shader_prop);
	}
}

static void ufbxt_check_anim(ufbx_scene *scene, ufbx_anim *anim)
{
	for (size_t i = 0; i < anim->layers.count; i++) {
		ufbxt_check_element_ptr(scene, anim->layers.data[i].layer, UFBX_ELEMENT_ANIM_LAYER);
	}
	ufbxt_assert(anim->prop_overrides.count == 0);
}

static void ufbxt_check_anim_stack(ufbx_scene *scene, ufbx_anim_stack *anim_stack)
{
	for (size_t i = 0; i < anim_stack->layers.count; i++) {
		ufbxt_check_element_ptr(scene, anim_stack->layers.data[i], UFBX_ELEMENT_ANIM_LAYER);
	}
}

static void ufbxt_check_anim_layer(ufbx_scene *scene, ufbx_anim_layer *anim_layer)
{
	ufbxt_check_string(anim_layer->name);

	for (size_t i = 0; i < anim_layer->anim_values.count; i++) {
		ufbxt_check_element_ptr(scene, anim_layer->anim_values.data[i], UFBX_ELEMENT_ANIM_VALUE);
	}

	size_t props_left = 0;
	ufbx_element *prev_element = NULL;

	for (size_t i = 0; i < anim_layer->anim_props.count; i++) {
		ufbx_anim_prop *anim_prop = &anim_layer->anim_props.data[i];

		ufbxt_check_string(anim_prop->prop_name);
		ufbxt_check_element_ptr_any(scene, anim_prop->element);
		ufbxt_check_element_ptr(scene, anim_prop->anim_value, UFBX_ELEMENT_ANIM_VALUE);

		ufbx_anim_prop *ref = ufbx_find_anim_prop(anim_layer, anim_prop->element, anim_prop->prop_name.data);
		ufbxt_assert(ref);
		ufbxt_assert(!strcmp(ref->prop_name.data, anim_prop->prop_name.data));

		if (props_left > 0) {
			ufbxt_assert(anim_prop->element == prev_element);
			props_left--;
		} else {
			ufbx_anim_prop_list list = ufbx_find_anim_props(anim_layer, anim_prop->element);
			ufbxt_assert(list.count > 0);
			prev_element = anim_prop->element;
			props_left = list.count - 1;
		}
	}

	ufbxt_assert(props_left == 0);
}

static void ufbxt_check_anim_value(ufbx_scene *scene, ufbx_anim_value *anim_value)
{
	ufbxt_check_element_ptr(scene, anim_value->curves[0], UFBX_ELEMENT_ANIM_CURVE);
	ufbxt_check_element_ptr(scene, anim_value->curves[1], UFBX_ELEMENT_ANIM_CURVE);
	ufbxt_check_element_ptr(scene, anim_value->curves[2], UFBX_ELEMENT_ANIM_CURVE);
}

static void ufbxt_check_anim_curve(ufbx_scene *scene, ufbx_anim_curve *anim_curve)
{
	for (size_t i = 0; i < anim_curve->keyframes.count; i++) {
		ufbx_keyframe key = anim_curve->keyframes.data[i];
		ufbxt_assert((uint32_t)key.interpolation < (uint32_t)UFBX_INTERPOLATION_COUNT);
	}
}

static void ufbxt_check_display_layer(ufbx_scene *scene, ufbx_display_layer *layer)
{
	for (size_t i = 0; i < layer->nodes.count; i++) {
		ufbxt_assert(layer->nodes.data[i]);
		ufbxt_check_element_ptr(scene, layer->nodes.data[i], UFBX_ELEMENT_NODE);
	}
}

static void ufbxt_check_selection_set(ufbx_scene *scene, ufbx_selection_set *set)
{
	for (size_t i = 0; i < set->nodes.count; i++) {
		ufbxt_assert(set->nodes.data[i]);
		ufbxt_check_element_ptr(scene, set->nodes.data[i], UFBX_ELEMENT_SELECTION_NODE);
	}
}

static void ufbxt_check_selection_node(ufbx_scene *scene, ufbx_selection_node *node)
{
	ufbxt_check_element_ptr(scene, node->target_node, UFBX_ELEMENT_NODE);
	ufbxt_check_element_ptr(scene, node->target_mesh, UFBX_ELEMENT_MESH);
}

static void ufbxt_check_character(ufbx_scene *scene, ufbx_character *character)
{
}

static void ufbxt_check_constraint(ufbx_scene *scene, ufbx_constraint *constraint)
{
	ufbxt_check_element_ptr(scene, constraint->node, UFBX_ELEMENT_NODE);
	ufbxt_check_element_ptr(scene, constraint->aim_up_node, UFBX_ELEMENT_NODE);
	ufbxt_check_element_ptr(scene, constraint->ik_effector, UFBX_ELEMENT_NODE);
	ufbxt_check_element_ptr(scene, constraint->ik_end_node, UFBX_ELEMENT_NODE);
	for (size_t i = 0; i < constraint->targets.count; i++) {
		ufbxt_assert(constraint->targets.data[i].node);
		ufbxt_check_element_ptr(scene, constraint->targets.data[i].node, UFBX_ELEMENT_NODE);
	}
}

static void ufbxt_check_pose(ufbx_scene *scene, ufbx_pose *pose)
{
	for (size_t i = 0; i < pose->bone_poses.count; i++) {
		ufbx_bone_pose bone_pose = pose->bone_poses.data[i];
		ufbxt_assert(bone_pose.bone_node);
		ufbxt_check_element_ptr(scene, bone_pose.bone_node, UFBX_ELEMENT_NODE);
	}
}

static void ufbxt_check_metadata_object(ufbx_scene *scene, ufbx_metadata_object *metadata_object)
{
}

static void ufbxt_check_texture_file(ufbx_scene *scene, ufbx_texture_file *texture_file)
{
	ufbxt_check_string(texture_file->filename);
	ufbxt_check_string(texture_file->absolute_filename);
	ufbxt_check_string(texture_file->relative_filename);
	ufbxt_check_blob(texture_file->raw_filename);
	ufbxt_check_blob(texture_file->raw_absolute_filename);
	ufbxt_check_blob(texture_file->raw_relative_filename);
	ufbxt_check_blob(texture_file->content);
}

static void ufbxt_check_application(ufbx_scene *scene, ufbx_application *application)
{
	ufbxt_check_string(application->name);
	ufbxt_check_string(application->vendor);
	ufbxt_check_string(application->version);
}

static void ufbxt_check_metadata(ufbx_scene *scene, ufbx_metadata *metadata)
{
	ufbxt_check_string(metadata->creator);
	ufbxt_check_string(metadata->filename);
	ufbxt_check_string(metadata->relative_root);
	ufbxt_check_blob(metadata->raw_filename);
	ufbxt_check_blob(metadata->raw_relative_root);
	ufbxt_check_application(scene, &metadata->latest_application);
	ufbxt_check_application(scene, &metadata->original_application);

	if (metadata->file_format == UFBX_FILE_FORMAT_FBX) {
	} else if (metadata->file_format == UFBX_FILE_FORMAT_OBJ) {
		ufbxt_assert(metadata->ascii);
	} else if (metadata->file_format == UFBX_FILE_FORMAT_MTL) {
		ufbxt_assert(metadata->ascii);
	} else {
		ufbxt_assert(0 && "Invalid file format");
	}

	for (size_t i = 0; i < metadata->warnings.count; i++) {
		ufbx_warning *warning = &metadata->warnings.data[i];
		ufbxt_check_string(warning->description);
		ufbxt_assert(metadata->has_warning[warning->type]);
	}
}

static void ufbxt_check_dom_value(ufbx_scene *scene, ufbx_dom_value *value)
{
	ufbxt_check_string(value->value_str);
	ufbxt_check_blob(value->value_blob);
	size_t array_stride = 0;
	switch (value->type) {
	case UFBX_DOM_VALUE_NUMBER:
		break;
	case UFBX_DOM_VALUE_STRING:
		break;
	case UFBX_DOM_VALUE_ARRAY_I8:
		array_stride = 1;
		break;
	case UFBX_DOM_VALUE_ARRAY_I32:
		array_stride = 4;
		break;
	case UFBX_DOM_VALUE_ARRAY_I64:
		array_stride = 8;
		break;
	case UFBX_DOM_VALUE_ARRAY_F32:
		array_stride = 4;
		break;
	case UFBX_DOM_VALUE_ARRAY_F64:
		array_stride = 8;
		break;
	case UFBX_DOM_VALUE_ARRAY_RAW_STRING:
		array_stride = sizeof(ufbx_blob);
		break;
	case UFBX_DOM_VALUE_ARRAY_IGNORED:
		array_stride = 0;
		break;
	default:
		ufbxt_assert(0 && "Unhandled type");
		break;
	}

	if (array_stride) {
		ufbxt_assert(value->value_blob.size == (size_t)value->value_int * array_stride);
	}

	if (value->type == UFBX_DOM_VALUE_ARRAY_RAW_STRING) {
		const ufbx_blob *blobs = (const ufbx_blob*)value->value_blob.data;
		for (size_t i = 0; i < (size_t)value->value_int; i++) {
			ufbxt_check_blob(blobs[i]);
		}
	}
}

static void ufbxt_check_dom_node(ufbx_scene *scene, ufbx_dom_node *node)
{
	ufbxt_assert(node);
	ufbxt_check_string(node->name);
	for (size_t i = 0; i < node->children.count; i++) {
		ufbxt_check_dom_node(scene, node->children.data[i]);
	}
	for (size_t i = 0; i < node->values.count; i++) {
		ufbxt_check_dom_value(scene, &node->values.data[i]);
	}
}

static void ufbxt_check_scene(ufbx_scene *scene)
{
	// TODO: Partial safety validation?
	if (scene->metadata.is_unsafe) return;

	ufbxt_check_metadata(scene, &scene->metadata);

	for (size_t i = 0; i < scene->elements.count; i++) {
		ufbxt_assert(scene->elements.data[i]->element_id == (uint32_t)i);
		ufbxt_check_element(scene, scene->elements.data[i]);
	}

	for (size_t i = 0; i < scene->unknowns.count; i++) {
		ufbxt_check_unknown(scene, scene->unknowns.data[i]);
	}

	for (size_t i = 0; i < scene->nodes.count; i++) {
		ufbxt_check_node(scene, scene->nodes.data[i]);
	}

	for (size_t i = 0; i < scene->meshes.count; i++) {
		ufbxt_check_mesh(scene, scene->meshes.data[i]);
	}

	for (size_t i = 0; i < scene->lights.count; i++) {
		ufbxt_check_light(scene, scene->lights.data[i]);
	}

	for (size_t i = 0; i < scene->cameras.count; i++) {
		ufbxt_check_camera(scene, scene->cameras.data[i]);
	}

	for (size_t i = 0; i < scene->bones.count; i++) {
		ufbxt_check_bone(scene, scene->bones.data[i]);
	}

	for (size_t i = 0; i < scene->empties.count; i++) {
		ufbxt_check_empty(scene, scene->empties.data[i]);
	}

	for (size_t i = 0; i < scene->line_curves.count; i++) {
		ufbxt_check_line_curve(scene, scene->line_curves.data[i]);
	}

	for (size_t i = 0; i < scene->nurbs_curves.count; i++) {
		ufbxt_check_nurbs_curve(scene, scene->nurbs_curves.data[i]);
	}

	for (size_t i = 0; i < scene->nurbs_surfaces.count; i++) {
		ufbxt_check_nurbs_surface(scene, scene->nurbs_surfaces.data[i]);
	}

	for (size_t i = 0; i < scene->nurbs_trim_surfaces.count; i++) {
		ufbxt_check_nurbs_trim_surface(scene, scene->nurbs_trim_surfaces.data[i]);
	}

	for (size_t i = 0; i < scene->nurbs_trim_boundaries.count; i++) {
		ufbxt_check_nurbs_trim_boundary(scene, scene->nurbs_trim_boundaries.data[i]);
	}

	for (size_t i = 0; i < scene->procedural_geometries.count; i++) {
		ufbxt_check_procedural_geometry(scene, scene->procedural_geometries.data[i]);
	}

	for (size_t i = 0; i < scene->stereo_cameras.count; i++) {
		ufbxt_check_stereo_camera(scene, scene->stereo_cameras.data[i]);
	}

	for (size_t i = 0; i < scene->camera_switchers.count; i++) {
		ufbxt_check_camera_switcher(scene, scene->camera_switchers.data[i]);
	}

	for (size_t i = 0; i < scene->markers.count; i++) {
		ufbxt_check_marker(scene, scene->markers.data[i]);
	}

	for (size_t i = 0; i < scene->lod_groups.count; i++) {
		ufbxt_check_lod_group(scene, scene->lod_groups.data[i]);
	}

	for (size_t i = 0; i < scene->skin_deformers.count; i++) {
		ufbxt_check_skin_deformer(scene, scene->skin_deformers.data[i]);
	}

	for (size_t i = 0; i < scene->skin_clusters.count; i++) {
		ufbxt_check_skin_cluster(scene, scene->skin_clusters.data[i]);
	}

	for (size_t i = 0; i < scene->blend_deformers.count; i++) {
		ufbxt_check_blend_deformer(scene, scene->blend_deformers.data[i]);
	}

	for (size_t i = 0; i < scene->blend_channels.count; i++) {
		ufbxt_check_blend_channel(scene, scene->blend_channels.data[i]);
	}

	for (size_t i = 0; i < scene->blend_shapes.count; i++) {
		ufbxt_check_blend_shape(scene, scene->blend_shapes.data[i]);
	}

	for (size_t i = 0; i < scene->cache_deformers.count; i++) {
		ufbxt_check_cache_deformer(scene, scene->cache_deformers.data[i]);
	}

	for (size_t i = 0; i < scene->cache_files.count; i++) {
		ufbxt_check_cache_file(scene, scene->cache_files.data[i]);
	}

	for (size_t i = 0; i < scene->materials.count; i++) {
		ufbxt_check_material(scene, scene->materials.data[i]);
	}

	for (size_t i = 0; i < scene->textures.count; i++) {
		ufbxt_check_texture(scene, scene->textures.data[i]);
	}

	for (size_t i = 0; i < scene->videos.count; i++) {
		ufbxt_check_video(scene, scene->videos.data[i]);
	}

	for (size_t i = 0; i < scene->shaders.count; i++) {
		ufbxt_check_shader(scene, scene->shaders.data[i]);
	}

	for (size_t i = 0; i < scene->shader_bindings.count; i++) {
		ufbxt_check_shader_binding(scene, scene->shader_bindings.data[i]);
	}

	for (size_t i = 0; i < scene->anim_stacks.count; i++) {
		ufbxt_check_anim_stack(scene, scene->anim_stacks.data[i]);
	}

	for (size_t i = 0; i < scene->anim_layers.count; i++) {
		ufbxt_check_anim_layer(scene, scene->anim_layers.data[i]);
	}

	for (size_t i = 0; i < scene->anim_values.count; i++) {
		ufbxt_check_anim_value(scene, scene->anim_values.data[i]);
	}

	for (size_t i = 0; i < scene->anim_curves.count; i++) {
		ufbxt_check_anim_curve(scene, scene->anim_curves.data[i]);
	}

	for (size_t i = 0; i < scene->display_layers.count; i++) {
		ufbxt_check_display_layer(scene, scene->display_layers.data[i]);
	}

	for (size_t i = 0; i < scene->selection_sets.count; i++) {
		ufbxt_check_selection_set(scene, scene->selection_sets.data[i]);
	}

	for (size_t i = 0; i < scene->selection_nodes.count; i++) {
		ufbxt_check_selection_node(scene, scene->selection_nodes.data[i]);
	}

	for (size_t i = 0; i < scene->characters.count; i++) {
		ufbxt_check_character(scene, scene->characters.data[i]);
	}

	for (size_t i = 0; i < scene->constraints.count; i++) {
		ufbxt_check_constraint(scene, scene->constraints.data[i]);
	}

	for (size_t i = 0; i < scene->poses.count; i++) {
		ufbxt_check_pose(scene, scene->poses.data[i]);
	}

	for (size_t i = 0; i < scene->metadata_objects.count; i++) {
		ufbxt_check_metadata_object(scene, scene->metadata_objects.data[i]);
	}

	for (size_t i = 0; i < scene->texture_files.count; i++) {
		ufbxt_assert(scene->texture_files.data[i].index == i);
		ufbxt_check_texture_file(scene, &scene->texture_files.data[i]);
	}

	if (scene->dom_root) {
		ufbxt_check_dom_node(scene, scene->dom_root);
	}

	for (size_t type_ix = 0; type_ix < UFBX_ELEMENT_TYPE_COUNT; type_ix++) {
		size_t num_elements = scene->elements_by_type[type_ix].count;
		for (size_t i = 0; i < num_elements; i++) {
			ufbx_element *element = scene->elements_by_type[type_ix].data[i];
			ufbxt_assert(element->type == (ufbx_element_type)type_ix);
			ufbxt_assert(element->typed_id == (uint32_t)i);
		}
	}


	ufbxt_assert(scene->root_node);
	ufbxt_check_element_ptr(scene, scene->root_node, UFBX_ELEMENT_NODE);
	ufbxt_assert(scene->root_node->is_root);
}

#endif
