#undef UFBXT_TEST_GROUP
#define UFBXT_TEST_GROUP "transform"

UFBXT_FILE_TEST(maya_pivots)
#if UFBXT_IMPL
{
	ufbx_node *node = ufbx_find_node(scene, "pCube1");
	ufbxt_assert(node);

	ufbx_vec3 origin_ref = { 0.7211236250, 1.8317762500, -0.6038020000 };
	ufbxt_assert_close_vec3(err, node->local_transform.translation, origin_ref);
}
#endif

#if UFBXT_IMPL
static void ufbxt_check_rotation_order(ufbx_scene *scene, const char *name, ufbx_rotation_order order)
{
	ufbx_node *node = ufbx_find_node(scene, name);
	ufbxt_assert(node);
	ufbx_prop *prop = ufbx_find_prop(&node->props, "RotationOrder");
	ufbxt_assert(prop);
	ufbxt_assert((ufbx_rotation_order)prop->value_int == order);
}
#endif

UFBXT_FILE_TEST(maya_rotation_order)
#if UFBXT_IMPL
{
	ufbxt_check_rotation_order(scene, "XYZ", UFBX_ROTATION_ORDER_XYZ);
	ufbxt_check_rotation_order(scene, "XZY", UFBX_ROTATION_ORDER_XZY);
	ufbxt_check_rotation_order(scene, "YZX", UFBX_ROTATION_ORDER_YZX);
	ufbxt_check_rotation_order(scene, "YXZ", UFBX_ROTATION_ORDER_YXZ);
	ufbxt_check_rotation_order(scene, "ZXY", UFBX_ROTATION_ORDER_ZXY);
	ufbxt_check_rotation_order(scene, "ZYX", UFBX_ROTATION_ORDER_ZYX);
}
#endif

UFBXT_FILE_TEST(maya_post_rotate_order)
#if UFBXT_IMPL
{
	ufbxt_check_rotation_order(scene, "pCube1", UFBX_ROTATION_ORDER_XYZ);
	ufbxt_check_rotation_order(scene, "pCube2", UFBX_ROTATION_ORDER_ZYX);
}
#endif

UFBXT_FILE_TEST(synthetic_pre_post_rotate)
#if UFBXT_IMPL
{
	ufbxt_check_rotation_order(scene, "pCube1", UFBX_ROTATION_ORDER_XYZ);
	ufbxt_check_rotation_order(scene, "pCube2", UFBX_ROTATION_ORDER_ZYX);
}
#endif

UFBXT_FILE_TEST(maya_parented_cubes)
#if UFBXT_IMPL
{
}
#endif

UFBXT_FILE_TEST_FLAGS(synthetic_geometric_squish, UFBXT_FILE_TEST_FLAG_OPT_HANDLING_IGNORE_NORMALS_IN_DIFF)
#if UFBXT_IMPL
{
	ufbx_node *node = ufbx_find_node(scene, "pSphere1");
	ufbxt_assert(node);
	ufbxt_assert_close_real(err, node->geometry_transform.scale.y, 0.01f);
}
#endif

UFBXT_FILE_TEST_FLAGS(synthetic_geometric_transform, UFBXT_FILE_TEST_FLAG_OPT_HANDLING_IGNORE_NORMALS_IN_DIFF)
#if UFBXT_IMPL
{
	ufbx_node *node = ufbx_find_node(scene, "Parent");
	ufbxt_assert(node);
	ufbx_vec3 euler = ufbx_quat_to_euler(node->geometry_transform.rotation, UFBX_ROTATION_ORDER_XYZ);
	ufbxt_assert_close_real(err, node->geometry_transform.translation.x, -0.5f);
	ufbxt_assert_close_real(err, node->geometry_transform.translation.y, -1.0f);
	ufbxt_assert_close_real(err, node->geometry_transform.translation.z, -1.5f);
	ufbxt_assert_close_real(err, euler.x, 20.0f);
	ufbxt_assert_close_real(err, euler.y, 40.0f);
	ufbxt_assert_close_real(err, euler.z, 60.0f);
	ufbxt_assert_close_real(err, node->geometry_transform.scale.x, 2.0f);
	ufbxt_assert_close_real(err, node->geometry_transform.scale.y, 3.0f);
	ufbxt_assert_close_real(err, node->geometry_transform.scale.z, 4.0f);
}
#endif

UFBXT_FILE_TEST(maya_cube_hidden)
#if UFBXT_IMPL
{
	ufbx_node *node = ufbx_find_node(scene, "pCube1");
	ufbxt_assert(node);
	ufbxt_assert(!node->visible);
}
#endif

UFBXT_TEST(root_transform)
#if UFBXT_IMPL
{

	ufbxt_diff_error err = { 0 };

	char path[512];
	ufbxt_file_iterator iter = { "maya_cube" };
	while (ufbxt_next_file(&iter, path, sizeof(path))) {
		ufbx_load_opts opts = { 0 };

		ufbx_vec3 euler = { { 90.0f, 0.0f, 0.0f } };

		opts.use_root_transform = true;
		opts.root_transform.translation.x = -1.0f;
		opts.root_transform.translation.y = -2.0f;
		opts.root_transform.translation.z = -3.0f;
		opts.root_transform.rotation = ufbx_euler_to_quat(euler, UFBX_ROTATION_ORDER_XYZ);
		opts.root_transform.scale.x = 2.0f;
		opts.root_transform.scale.y = 3.0f;
		opts.root_transform.scale.z = 4.0f;

		ufbx_scene *scene = ufbx_load_file(path, &opts, NULL);
		ufbxt_assert(scene);

		ufbxt_check_scene(scene);

		ufbxt_assert_close_vec3(&err, scene->root_node->local_transform.translation, opts.root_transform.translation);
		ufbxt_assert_close_quat(&err, scene->root_node->local_transform.rotation, opts.root_transform.rotation);
		ufbxt_assert_close_vec3(&err, scene->root_node->local_transform.scale, opts.root_transform.scale);

		ufbx_transform eval = ufbx_evaluate_transform(&scene->anim, scene->root_node, 0.1);
		ufbxt_assert_close_vec3(&err, eval.translation, opts.root_transform.translation);
		ufbxt_assert_close_quat(&err, eval.rotation, opts.root_transform.rotation);
		ufbxt_assert_close_vec3(&err, eval.scale, opts.root_transform.scale);

		ufbx_scene *state = ufbx_evaluate_scene(scene, &scene->anim, 0.1, NULL, NULL);
		ufbxt_assert(state);

		ufbxt_assert_close_vec3(&err, state->root_node->local_transform.translation, opts.root_transform.translation);
		ufbxt_assert_close_quat(&err, state->root_node->local_transform.rotation, opts.root_transform.rotation);
		ufbxt_assert_close_vec3(&err, state->root_node->local_transform.scale, opts.root_transform.scale);
		
		ufbx_free_scene(state);
		ufbx_free_scene(scene);
	}

	ufbxt_assert(iter.num_found >= 8);

	ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", err.sum / (ufbx_real)err.num, err.max, err.num);
}
#endif

UFBXT_TEST(blender_axes)
#if UFBXT_IMPL
{
	char path[512], name[512];

	static const char *axis_names[] = {
		"px", "nx", "py", "ny", "pz", "nz",
	};

	for (uint32_t fwd_ix = 0; fwd_ix < 6; fwd_ix++) {
		for (uint32_t up_ix = 0; up_ix < 6; up_ix++) {

			// Don't allow collinear axes
			if ((fwd_ix >> 1) == (up_ix >> 1)) continue;

			ufbx_coordinate_axis axis_fwd = (ufbx_coordinate_axis)fwd_ix;
			ufbx_coordinate_axis axis_up = (ufbx_coordinate_axis)up_ix;

			snprintf(name, sizeof(name), "blender_axes/axes_%s%s", axis_names[fwd_ix], axis_names[up_ix]);

			ufbxt_file_iterator iter = { name };
			while (ufbxt_next_file(&iter, path, sizeof(path))) {
				ufbxt_diff_error err = { 0 };

				// Load normally and check axes
				{
					ufbx_scene *scene = ufbx_load_file(path, NULL, NULL);
					ufbxt_assert(scene);
					ufbxt_check_scene(scene);

					ufbx_coordinate_axis axis_front = axis_fwd ^ 1;

					ufbxt_assert(scene->settings.axes.front == axis_front);
					ufbxt_assert(scene->settings.axes.up == axis_up);

					ufbx_free_scene(scene);
				}

				// Axis conversion
				for (int mode = 0; mode < 4; mode++) {
					ufbxt_hintf("mode = %d", mode);
					ufbx_load_opts opts = { 0 };

					bool transform_root = (mode % 2) != 0;
					bool use_adjust = (mode / 2) != 0;

					opts.target_axes = ufbx_axes_right_handed_z_up;
					opts.target_unit_meters = 1.0f;

					if (transform_root) {
						opts.use_root_transform = true;
						opts.root_transform = ufbx_identity_transform;
						opts.root_transform.translation.z = 1.0f;
					}

					if (use_adjust) {
						opts.space_conversion = UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS;
					} else {
						opts.space_conversion = UFBX_SPACE_CONVERSION_TRANSFORM_ROOT;
					}

					ufbx_scene *scene = ufbx_load_file(path, &opts, NULL);
					ufbxt_assert(scene);
					ufbxt_check_scene(scene);

					ufbx_node *plane = ufbx_find_node(scene, "Plane");
					ufbxt_assert(plane && plane->mesh);
					ufbx_mesh *mesh = plane->mesh;

					ufbxt_assert(mesh->num_faces == 1);
					ufbx_face face = mesh->faces.data[0];
					ufbxt_assert(face.num_indices == 3);

					if (use_adjust && !transform_root) {
						ufbx_node *root = scene->root_node;

						ufbx_vec3 identity_scale = { 1.0f, 1.0f, 1.0f };
						ufbxt_assert_close_quat(&err, root->local_transform.rotation, ufbx_identity_quat);
						ufbxt_assert_close_vec3(&err, root->local_transform.scale, identity_scale);
					}

					for (uint32_t i = 0; i < face.num_indices; i++) {
						ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, face.index_begin + i);
						ufbx_vec2 uv = ufbx_get_vertex_vec2(&mesh->vertex_uv, face.index_begin + i);

						pos = ufbx_transform_position(&plane->geometry_to_world, pos);

						ufbx_vec3 ref;
						if (uv.x < 0.5f && uv.y < 0.5f) {
							ref.x = 1.0f;
							ref.y = 1.0f;
							ref.z = 3.0f;
						} else if (uv.x > 0.5f && uv.y < 0.5f) {
							ref.x = 2.0f;
							ref.y = 2.0f;
							ref.z = 3.0f;
						} else if (uv.x < 0.5f && uv.y > 0.5f) {
							ref.x = 1.0f;
							ref.y = 2.0f;
							ref.z = 4.0f;
						} else {
							ufbxt_assert(0 && "Shouldn't exist");
						}

						if (transform_root) {
							ref.z += 1.0f;
						}

						ufbxt_assert_close_vec3(&err, pos, ref);
					}

					ufbx_free_scene(scene);
				}

				ufbxt_logf(".. Absolute diff: avg %.3g, max %.3g (%zu tests)", err.sum / (ufbx_real)err.num, err.max, err.num);
			}
		}
	}
}
#endif

#if UFBXT_IMPL
static ufbx_load_opts ufbxt_scale_to_cm_opts()
{
	ufbx_load_opts opts = { 0 };
	opts.target_unit_meters = 0.01f;
	return opts;
}
#endif

UFBXT_FILE_TEST_OPTS(maya_scale_no_inherit, ufbxt_scale_to_cm_opts)
#if UFBXT_IMPL
{
	{
		ufbx_node *node = ufbx_find_node(scene, "joint1");
		ufbxt_assert(node);
		ufbxt_assert(node->inherit_type == UFBX_INHERIT_NORMAL);
		ufbxt_assert_close_real(err, node->local_transform.scale.x, 0.02f);
		ufbxt_assert_close_real(err, node->local_transform.scale.y, 0.03f);
		ufbxt_assert_close_real(err, node->local_transform.scale.z, 0.04f);
		ufbxt_assert_close_real(err, node->world_transform.scale.x, 2.0f);
		ufbxt_assert_close_real(err, node->world_transform.scale.y, 3.0f);
		ufbxt_assert_close_real(err, node->world_transform.scale.z, 4.0f);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "joint2");
		ufbxt_assert(node);
		ufbxt_assert(node->inherit_type == UFBX_INHERIT_NO_SCALE);
		ufbxt_assert_close_real(err, node->local_transform.scale.x, 100.0f);
		ufbxt_assert_close_real(err, node->local_transform.scale.y, 100.0f);
		ufbxt_assert_close_real(err, node->local_transform.scale.z, 100.0f);
		ufbxt_assert_close_real(err, node->world_transform.scale.x, 100.0f);
		ufbxt_assert_close_real(err, node->world_transform.scale.y, 100.0f);
		ufbxt_assert_close_real(err, node->world_transform.scale.z, 100.0f);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "joint3");
		ufbxt_assert(node);
		ufbxt_assert(node->inherit_type == UFBX_INHERIT_NO_SCALE);
		ufbxt_assert_close_real(err, node->local_transform.scale.x, 1.0f);
		ufbxt_assert_close_real(err, node->local_transform.scale.y, 1.0f);
		ufbxt_assert_close_real(err, node->local_transform.scale.z, 1.0f);
		ufbxt_assert_close_real(err, node->world_transform.scale.x, 1.0f);
		ufbxt_assert_close_real(err, node->world_transform.scale.y, 1.0f);
		ufbxt_assert_close_real(err, node->world_transform.scale.z, 1.0f);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "joint4");
		ufbxt_assert(node);
		ufbxt_assert(node->inherit_type == UFBX_INHERIT_NO_SCALE);
		ufbxt_assert_close_real(err, node->local_transform.scale.x, 1.5f);
		ufbxt_assert_close_real(err, node->local_transform.scale.y, 2.5f);
		ufbxt_assert_close_real(err, node->local_transform.scale.z, 3.5f);
		ufbxt_assert_close_real(err, node->world_transform.scale.x, 1.5f);
		ufbxt_assert_close_real(err, node->world_transform.scale.y, 2.5f);
		ufbxt_assert_close_real(err, node->world_transform.scale.z, 3.5f);
	}

	{
		ufbx_node *node = ufbx_find_node(scene, "joint3");

		{
			ufbx_transform transform = ufbx_evaluate_transform(&scene->anim, node, 1.0);
			ufbxt_assert_close_real(err, transform.scale.x, 0.3f);
			ufbxt_assert_close_real(err, transform.scale.y, 0.6f);
			ufbxt_assert_close_real(err, transform.scale.z, 0.9f);
		}

		{
			ufbx_transform transform = ufbx_evaluate_transform(&scene->anim, node, 0.5);
			ufbxt_assert_close_real(err, transform.scale.x, 0.67281f);
			ufbxt_assert_close_real(err, transform.scale.y, 0.81304f);
			ufbxt_assert_close_real(err, transform.scale.z, 0.95326f);
		}
	}
}
#endif

UFBXT_FILE_TEST(synthetic_node_dag)
#if UFBXT_IMPL
{
	ufbx_node *root = scene->root_node;
	ufbx_node *a = ufbx_find_node(scene, "A");
	ufbx_node *b = ufbx_find_node(scene, "B");
	ufbx_node *c = ufbx_find_node(scene, "C");
	ufbx_node *d = ufbx_find_node(scene, "D");

	ufbxt_assert(root && a && b && c && d);
	ufbxt_assert(root->children.count == 1);
	ufbxt_assert(root->children.data[0] == a);

	ufbxt_assert(a->parent == root);
	ufbxt_assert(a->children.count == 1);
	ufbxt_assert(a->children.data[0] == b);

	ufbxt_assert(b->parent == a);
	ufbxt_assert(b->children.count == 1);
	ufbxt_assert(b->children.data[0] == c);

	ufbxt_assert(c->parent == b);
	ufbxt_assert(c->children.count == 1);
	ufbxt_assert(c->children.data[0] == d);

	ufbxt_assert(d->parent == c);
	ufbxt_assert(d->children.count == 0);
}
#endif

UFBXT_FILE_TEST_FLAGS(synthetic_node_cycle_fail, UFBXT_FILE_TEST_FLAG_ALLOW_ERROR)
#if UFBXT_IMPL
{
	ufbxt_assert(!scene);
}
#endif
