#ifndef UFBXT_CHECK_MATERIAL_H_INCLUDED
#define UFBXT_CHECK_MATERIAL_H_INCLUDED

#include "../ufbx.h"
#include <stdlib.h>

static const char *ufbxt_fbx_map_name(ufbx_material_fbx_map map)
{
	switch (map) {
	case UFBX_MATERIAL_FBX_DIFFUSE_FACTOR: return "diffuse_factor";
	case UFBX_MATERIAL_FBX_DIFFUSE_COLOR: return "diffuse_color";
	case UFBX_MATERIAL_FBX_SPECULAR_FACTOR: return "specular_factor";
	case UFBX_MATERIAL_FBX_SPECULAR_COLOR: return "specular_color";
	case UFBX_MATERIAL_FBX_SPECULAR_EXPONENT: return "specular_exponent";
	case UFBX_MATERIAL_FBX_REFLECTION_FACTOR: return "reflection_factor";
	case UFBX_MATERIAL_FBX_REFLECTION_COLOR: return "reflection_color";
	case UFBX_MATERIAL_FBX_TRANSPARENCY_FACTOR: return "transparency_factor";
	case UFBX_MATERIAL_FBX_TRANSPARENCY_COLOR: return "transparency_color";
	case UFBX_MATERIAL_FBX_EMISSION_FACTOR: return "emission_factor";
	case UFBX_MATERIAL_FBX_EMISSION_COLOR: return "emission_color";
	case UFBX_MATERIAL_FBX_AMBIENT_FACTOR: return "ambient_factor";
	case UFBX_MATERIAL_FBX_AMBIENT_COLOR: return "ambient_color";
	case UFBX_MATERIAL_FBX_NORMAL_MAP: return "normal_map";
	case UFBX_MATERIAL_FBX_BUMP: return "bump";
	case UFBX_MATERIAL_FBX_BUMP_FACTOR: return "bump_factor";
	case UFBX_MATERIAL_FBX_DISPLACEMENT_FACTOR: return "displacement_factor";
	case UFBX_MATERIAL_FBX_DISPLACEMENT: return "displacement";
	case UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT_FACTOR: return "vector_displacement_factor";
	case UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT: return "vector_displacement";
	}

	ufbxt_assert(0 && "Unhandled PBR map name");
	return NULL;
}

static const char *ufbxt_pbr_map_name(ufbx_material_pbr_map map)
{
	switch (map) {
	case UFBX_MATERIAL_PBR_BASE_FACTOR: return "base_factor";
	case UFBX_MATERIAL_PBR_BASE_COLOR: return "base_color";
	case UFBX_MATERIAL_PBR_ROUGHNESS: return "roughness";
	case UFBX_MATERIAL_PBR_METALNESS: return "metalness";
	case UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS: return "diffuse_roughness";
	case UFBX_MATERIAL_PBR_SPECULAR_FACTOR: return "specular_factor";
	case UFBX_MATERIAL_PBR_SPECULAR_COLOR: return "specular_color";
	case UFBX_MATERIAL_PBR_SPECULAR_IOR: return "specular_ior";
	case UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY: return "specular_anisotropy";
	case UFBX_MATERIAL_PBR_SPECULAR_ROTATION: return "specular_rotation";
	case UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR: return "transmission_factor";
	case UFBX_MATERIAL_PBR_TRANSMISSION_COLOR: return "transmission_color";
	case UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH: return "transmission_depth";
	case UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER: return "transmission_scatter";
	case UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER_ANISOTROPY: return "transmission_scatter_anisotropy";
	case UFBX_MATERIAL_PBR_TRANSMISSION_DISPERSION: return "transmission_dispersion";
	case UFBX_MATERIAL_PBR_TRANSMISSION_ROUGHNESS: return "transmission_roughness";
	case UFBX_MATERIAL_PBR_TRANSMISSION_EXTRA_ROUGHNESS: return "transmission_extra_roughness";
	case UFBX_MATERIAL_PBR_TRANSMISSION_PRIORITY: return "transmission_priority";
	case UFBX_MATERIAL_PBR_TRANSMISSION_ENABLE_IN_AOV: return "transmission_enable_in_aov";
	case UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR: return "subsurface_factor";
	case UFBX_MATERIAL_PBR_SUBSURFACE_COLOR: return "subsurface_color";
	case UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS: return "subsurface_radius";
	case UFBX_MATERIAL_PBR_SUBSURFACE_SCALE: return "subsurface_scale";
	case UFBX_MATERIAL_PBR_SUBSURFACE_ANISOTROPY: return "subsurface_anisotropy";
	case UFBX_MATERIAL_PBR_SUBSURFACE_TINT_COLOR: return "subsurface_tint_color";
	case UFBX_MATERIAL_PBR_SUBSURFACE_TYPE: return "subsurface_type";
	case UFBX_MATERIAL_PBR_SHEEN_FACTOR: return "sheen_factor";
	case UFBX_MATERIAL_PBR_SHEEN_COLOR: return "sheen_color";
	case UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS: return "sheen_roughness";
	case UFBX_MATERIAL_PBR_COAT_FACTOR: return "coat_factor";
	case UFBX_MATERIAL_PBR_COAT_COLOR: return "coat_color";
	case UFBX_MATERIAL_PBR_COAT_ROUGHNESS: return "coat_roughness";
	case UFBX_MATERIAL_PBR_COAT_IOR: return "coat_ior";
	case UFBX_MATERIAL_PBR_COAT_ANISOTROPY: return "coat_anisotropy";
	case UFBX_MATERIAL_PBR_COAT_ROTATION: return "coat_rotation";
	case UFBX_MATERIAL_PBR_COAT_NORMAL: return "coat_normal";
	case UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_COLOR: return "coat_affect_base_color";
	case UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_ROUGHNESS: return "coat_affect_base_roughness";
	case UFBX_MATERIAL_PBR_THIN_FILM_THICKNESS: return "thin_film_thickness";
	case UFBX_MATERIAL_PBR_THIN_FILM_IOR: return "thin_film_ior";
	case UFBX_MATERIAL_PBR_EMISSION_FACTOR: return "emission_factor";
	case UFBX_MATERIAL_PBR_EMISSION_COLOR: return "emission_color";
	case UFBX_MATERIAL_PBR_OPACITY: return "opacity";
	case UFBX_MATERIAL_PBR_INDIRECT_DIFFUSE: return "indirect_diffuse";
	case UFBX_MATERIAL_PBR_INDIRECT_SPECULAR: return "indirect_specular";
	case UFBX_MATERIAL_PBR_NORMAL_MAP: return "normal_map";
	case UFBX_MATERIAL_PBR_TANGENT_MAP: return "tangent_map";
	case UFBX_MATERIAL_PBR_DISPLACEMENT_MAP: return "displacement_map";
	case UFBX_MATERIAL_PBR_MATTE_FACTOR: return "matte_factor";
	case UFBX_MATERIAL_PBR_MATTE_COLOR: return "matte_color";
	case UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION: return "ambient_occlusion";
	case UFBX_MATERIAL_PBR_GLOSSINESS: return "glossiness";
	case UFBX_MATERIAL_PBR_COAT_GLOSSINESS: return "coat_glossiness";
	case UFBX_MATERIAL_PBR_TRANSMISSION_GLOSSINESS: return "transmission_glossiness";
	}

	ufbxt_assert(0 && "Unhandled PBR map name");
	return 0;
}

static const char *ufbxt_material_feature_name(ufbx_material_pbr_map map)
{
	switch (map) {
	case UFBX_MATERIAL_FEATURE_PBR: return "pbr";
	case UFBX_MATERIAL_FEATURE_METALNESS: return "metalness";
	case UFBX_MATERIAL_FEATURE_DIFFUSE: return "diffuse";
	case UFBX_MATERIAL_FEATURE_SPECULAR: return "specular";
	case UFBX_MATERIAL_FEATURE_EMISSION: return "emission";
	case UFBX_MATERIAL_FEATURE_TRANSMISSION: return "transmission";
	case UFBX_MATERIAL_FEATURE_COAT: return "coat";
	case UFBX_MATERIAL_FEATURE_SHEEN: return "sheen";
	case UFBX_MATERIAL_FEATURE_OPACITY: return "opacity";
	case UFBX_MATERIAL_FEATURE_AMBIENT_OCCLUSION: return "ambient_occlusion";
	case UFBX_MATERIAL_FEATURE_MATTE: return "matte";
	case UFBX_MATERIAL_FEATURE_UNLIT: return "unlit";
	case UFBX_MATERIAL_FEATURE_IOR: return "ior";
	case UFBX_MATERIAL_FEATURE_DIFFUSE_ROUGHNESS: return "diffuse_roughness";
	case UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS: return "transmission_roughness";
	case UFBX_MATERIAL_FEATURE_THIN_WALLED: return "thin_walled";
	case UFBX_MATERIAL_FEATURE_CAUSTICS: return "caustics";
	case UFBX_MATERIAL_FEATURE_EXIT_TO_BACKGROUND: return "exit_to_background";
	case UFBX_MATERIAL_FEATURE_INTERNAL_REFLECTIONS: return "internal_reflections";
	case UFBX_MATERIAL_FEATURE_DOUBLE_SIDED: return "double_sided";
	case UFBX_MATERIAL_FEATURE_ROUGHNESS_AS_GLOSSINESS: return "roughness_as_glossiness";
	case UFBX_MATERIAL_FEATURE_COAT_ROUGHNESS_AS_GLOSSINESS: return "coat_roughness_as_glossiness";
	case UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS_AS_GLOSSINESS: return "transmission_roughness_as_glossiness";
	}

	ufbxt_assert(0 && "Unhandled material feature name");
	return 0;
}

static const char *ufbxt_shader_type_name(ufbx_shader_type map)
{
	switch (map) {
	case UFBX_SHADER_UNKNOWN: return "unknown";
	case UFBX_SHADER_FBX_LAMBERT: return "fbx_lambert";
	case UFBX_SHADER_FBX_PHONG: return "fbx_phong";
	case UFBX_SHADER_OSL_STANDARD_SURFACE: return "osl_standard_surface";
	case UFBX_SHADER_ARNOLD_STANDARD_SURFACE: return "arnold_standard_surface";
	case UFBX_SHADER_3DS_MAX_PHYSICAL_MATERIAL: return "3ds_max_physical_material";
	case UFBX_SHADER_3DS_MAX_PBR_METAL_ROUGH: return "3ds_max_pbr_metal_rough";
	case UFBX_SHADER_3DS_MAX_PBR_SPEC_GLOSS: return "3ds_max_pbr_spec_gloss";
	case UFBX_SHADER_GLTF_MATERIAL: return "gltf_material";
	case UFBX_SHADER_SHADERFX_GRAPH: return "shaderfx_graph";
	case UFBX_SHADER_BLENDER_PHONG: return "blender_phong";
	case UFBX_SHADER_WAVEFRONT_MTL: return "wavefront_mtl";
	}

	ufbxt_assert(0 && "Unhandled material feature name");
	return 0;
}

static size_t ufbxt_tokenize_line(char *buf, size_t buf_len, const char **tokens, size_t max_tokens, const char **p_line, const char *sep_chars)
{
	bool prev_sep = true;

	size_t num_tokens = 0;
	size_t buf_pos = 0;

	const char *p = *p_line;
	for (;;) {
		char c = *p;
		if (c == '\0' || c == '\n') break;

		bool sep = false;
		for (const char *s = sep_chars; *s; s++) {
			if (*s == c) {
				sep = true;
				break;
			}
		}

		if (!sep) {
			if (prev_sep) {
				ufbxt_assert(buf_pos < buf_len);
				buf[buf_pos++] = '\0';

				ufbxt_assert(num_tokens < max_tokens);
				tokens[num_tokens++] = buf + buf_pos;
			}

			if (c == '"') {
				p++;
				while (*p != '"') {
					c = *p;
					if (c == '\\') {
						p++;
						switch (*p) {
						case 'n': c = '\n'; break;
						case 'r': c = '\r'; break;
						case 't': c = '\t'; break;
						case '\\': c = '\\'; break;
						case '"': c = '"'; break;
						default:
							fprintf(stderr, "Bad escape '\\%c'\n", *p);
							ufbxt_assert(0 && "Bad escape");
							return 0;
						}
					}
					ufbxt_assert(buf_pos < buf_len);
					buf[buf_pos++] = c;
					p++;
				}
			} else {
				ufbxt_assert(buf_pos < buf_len);
				buf[buf_pos++] = c;
			}
		}
		prev_sep = sep;
		p++;
	}

	ufbxt_assert(buf_pos < buf_len);
	buf[buf_pos++] = '\0';

	for (size_t i = num_tokens; i < max_tokens; i++) {
		tokens[i] = "";
	}

	if (*p == '\n') p += 1;
	*p_line = p;

	return num_tokens;
}

static bool ufbxt_check_materials(ufbx_scene *scene, const char *spec, const char *filename)
{
	bool ok = true;

	char line_buf[512];
	const char *tokens[8];

	char dot_buf[512];
	const char *dots[8];

	bool seen_materials[256];

	ufbx_material *material = NULL;
	size_t num_materials = 0;
	size_t num_props = 0;

	double err_sum = 0.0;
	double err_max = 0.0;
	size_t err_num = 0;

	bool material_error = false;
	bool require_all = false;

	long version = 0;

	int line = 0;
	while (*spec != '\0') {
		size_t num_tokens = ufbxt_tokenize_line(line_buf, sizeof(line_buf), tokens, ufbxt_arraycount(tokens), &spec, " \t\r");
		line++;

		if (num_tokens == 0) continue;

		const char *first_token = tokens[0];
		size_t num_dots = ufbxt_tokenize_line(dot_buf, sizeof(dot_buf), dots, ufbxt_arraycount(dots), &first_token, ".");

		if (!strcmp(dots[0], "version")) {
			char *end = NULL;
			version = strtol(tokens[1], &end, 10);
			if (!end || *end != '\0') {
				fprintf(stderr, "%s:%d: Bad value in '%s': '%s'\n", filename, line, tokens[0], tokens[1]);
				ok = false;
				continue;
			}

			continue;
		} else if (!strcmp(dots[0], "require")) {
			if (!strcmp(tokens[1], "all")) {
				if (scene->materials.count > ufbxt_arraycount(seen_materials)) {
					fprintf(stderr, "%s:%d: Too many materials in file for 'reqiure all': %zu (max %zu)\n", filename, line,
						scene->materials.count, (size_t)ufbxt_arraycount(seen_materials));
					ok = false;
					continue;
				}
				require_all = true;
				memset(seen_materials, 0, scene->materials.count * sizeof(bool));
			} else {
				fprintf(stderr, "%s:%d: Bad require directive: '%s'\n", filename, line, tokens[1]);
				ok = false;
				continue;
			}
			continue;
		} else if (!strcmp(dots[0], "material")) {
			if (*tokens[1] == '\0') {
				fprintf(stderr, "%s:%d: Expected material name for 'material'\n", filename, line);
				ok = false;
			}

			material = ufbx_find_material(scene, tokens[1]);
			if (!material) {
				fprintf(stderr, "%s:%d: Material not found: '%s'\n", filename, line, tokens[1]);
				ok = false;
			}
			material_error = !material;

			if (material) {
				seen_materials[material->typed_id] = true;
			}

			num_materials++;
			continue;
		}

		if (!material) {
			if (!material_error) {
				fprintf(stderr, "%s:%d: Statement '%s' needs to have a material defined\n", filename, line, tokens[0]);
			}
			ok = false;
			continue;
		}

		num_props++;

		if (!strcmp(dots[0], "fbx") || !strcmp(dots[0], "pbr")) {
			ufbx_material_map *map = NULL;
			if (!strcmp(dots[0], "fbx")) {
				ufbx_material_fbx_map name = (ufbx_material_fbx_map)UFBX_MATERIAL_FBX_MAP_COUNT;
				for (size_t i = 0; i < UFBX_MATERIAL_FBX_MAP_COUNT; i++) {
					const char *str = ufbxt_fbx_map_name((ufbx_material_fbx_map)i);
					if (!strcmp(dots[1], str)) {
						name = (ufbx_material_fbx_map)i;
						break;
					}
				}
				if (name == (ufbx_material_fbx_map)UFBX_MATERIAL_FBX_MAP_COUNT) {
					fprintf(stderr, "%s:%d: Unknown FBX material map '%s' in '%s'\n", filename, line, dots[1], tokens[0]);
					ok = false;
				} else {
					map = &material->fbx.maps[name];
				}
			} else if (!strcmp(dots[0], "pbr")) {
				ufbx_material_pbr_map name = (ufbx_material_pbr_map)UFBX_MATERIAL_PBR_MAP_COUNT;
				for (size_t i = 0; i < UFBX_MATERIAL_PBR_MAP_COUNT; i++) {
					const char *str = ufbxt_pbr_map_name((ufbx_material_pbr_map)i);
					if (!strcmp(dots[1], str)) {
						name = (ufbx_material_pbr_map)i;
						break;
					}
				}
				if (name == (ufbx_material_pbr_map)UFBX_MATERIAL_PBR_MAP_COUNT) {
					fprintf(stderr, "%s:%d: Unknown PBR material map '%s' in '%s'\n", filename, line, dots[1], tokens[0]);
					ok = false;
				} else {
					map = &material->pbr.maps[name];
				}
			} else {
				ufbxt_assert(0 && "Unhandled branch");
			}

			// Errors reported above, but set ok to false just to be sure..
			if (!map) {
				ok = false;
				continue;
			}

			if (!strcmp(dots[2], "texture")) {
				size_t tok_start = 1;
				bool content = false;
				for (;;) {
					const char *tok = tokens[tok_start];
					if (!strcmp(tok, "content")) {
						content = true;
					} else {
						break;
					}
					tok_start++;
				}

				const char *tex_name = tokens[tok_start];
				if (map->texture) {
					if (strcmp(map->texture->relative_filename.data, tex_name) != 0) {
						fprintf(stderr, "%s:%d: Material '%s' %s.%s is different: got '%s', expected '%s'\n", filename, line,
							material->name.data, dots[0], dots[1], map->texture->relative_filename.data, tex_name);
						ok = false;
					}
					if (content && map->texture->content.size == 0) {
						fprintf(stderr, "%s:%d: Material '%s' %s.%s expected content for the texture %s\n", filename, line,
							material->name.data, dots[0], dots[1], map->texture->relative_filename.data);
						ok = false;
					}
				} else {
					fprintf(stderr, "%s:%d: Material '%s' %s.%s missing texture, expected '%s'\n", filename, line,
						material->name.data, dots[0], dots[1], tex_name);
					ok = false;
				}
			} else {
				size_t tok_start = 1;
				bool implicit = false;
				bool widen = false;
				for (;;) {
					const char *tok = tokens[tok_start];
					if (!strcmp(tok, "implicit")) {
						implicit = true;
					} else if (!strcmp(tok, "widen")) {
						widen = true;
					} else {
						break;
					}
					tok_start++;
				}

				double ref[4];
				size_t num_ref = 0;
				for (; num_ref < 4; num_ref++) {
					const char *tok = tokens[tok_start + num_ref];
					if (!*tok) break;

					char *end = NULL;
					ref[num_ref] = strtod(tok, &end);
					if (!end || *end != '\0') {
						fprintf(stderr, "%s:%d: Bad number in '%s': '%s'\n", filename, line, tokens[0], tok);
						ok = false;
						num_ref = 0;
						break;
					}
				}

				if (num_ref == 0) continue;

				if (!implicit && !map->has_value) {
					fprintf(stderr, "%s:%d: Material '%s' %s.%s not defined in material\n", filename, line,
						material->name.data, dots[0], dots[1]);
					ok = false;
					continue;
				} else if (implicit && map->has_value) {
					fprintf(stderr, "%s:%d: Material '%s' %s.%s defined in material, expected implicit\n", filename, line,
						material->name.data, dots[0], dots[1]);
					ok = false;
					continue;
				}

				if (!implicit && !widen && (size_t)map->value_components != num_ref) {
					fprintf(stderr, "%s:%d: Material '%s' %s.%s has wrong number of components: got %zu, expected %zu\n", filename, line,
						material->name.data, dots[0], dots[1], (size_t)map->value_components, num_ref);
					ok = false;
					continue;
				}

				if (widen && map->value_components >= num_ref) {
					fprintf(stderr, "%s:%d: Material '%s' %s.%s exected to be widened got %zu components, expected %zu\n", filename, line,
						material->name.data, dots[0], dots[1], (size_t)map->value_components, num_ref);
					ok = false;
					continue;
				}

				char mat_str[128];
				char ref_str[128];
				size_t mat_pos = 0;
				size_t ref_pos = 0;

				bool equal = true;
				for (size_t i = 0; i < num_ref; i++) {
					double mat_value = map->value_vec4.v[i];
					double ref_value = ref[i];
					double err = fabs(mat_value - ref_value);
					if (err > 0.002) {
						equal = false;
					}

					err_sum += err;
					if (err > err_max) err_max = err;
					err_num += 1;

					if (i > 0) {
						mat_pos += (size_t)snprintf(mat_str + mat_pos, sizeof(mat_str) - mat_pos, ", ");
						ref_pos += (size_t)snprintf(ref_str + ref_pos, sizeof(ref_str) - ref_pos, ", ");
					}

					mat_pos += (size_t)snprintf(mat_str + mat_pos, sizeof(mat_str) - mat_pos, "%.3f", mat_value);
					ref_pos += (size_t)snprintf(ref_str + ref_pos, sizeof(ref_str) - ref_pos, "%.3f", ref_value);
				}

				if (!equal) {
					fprintf(stderr, "%s:%d: Material '%s' %s.%s has wrong value: got (%s), expected (%s)\n", filename, line,
						material->name.data, dots[0], dots[1], mat_str, ref_str);
					ok = false;
				}
			}

		} else if (!strcmp(dots[0], "features")) {
			ufbx_material_feature name = (ufbx_material_feature)UFBX_MATERIAL_FEATURE_COUNT;
			for (size_t i = 0; i < UFBX_MATERIAL_FEATURE_COUNT; i++) {
				const char *str = ufbxt_material_feature_name((ufbx_material_feature)i);
				if (!strcmp(dots[1], str)) {
					name = (ufbx_material_feature)i;
					break;
				}
			}
			if (name == (ufbx_material_feature)UFBX_MATERIAL_FEATURE_COUNT) {
				fprintf(stderr, "%s:%d: Unknown material feature '%s' in '%s'\n", filename, line, dots[1], tokens[0]);
				ok = false;
			}

			ufbx_material_feature_info *feature = &material->features.features[name];

			char *end = NULL;
			long enabled = strtol(tokens[1], &end, 10);
			if (!end || *end != '\0') {
				fprintf(stderr, "%s:%d: Bad value in '%s': '%s'\n", filename, line, tokens[0], tokens[1]);
				ok = false;
				continue;
			}

			if (enabled != (long)feature->enabled) {
				fprintf(stderr, "%s:%d: Material '%s' features.%s mismatch: got %ld, expected %ld\n", filename, line,
					material->name.data, dots[1], (long)feature->enabled, enabled);
				ok = false;
				continue;
			}
		} else if (!strcmp(dots[0], "shader_type")) {
			ufbx_shader_type name = (ufbx_shader_type)UFBX_SHADER_TYPE_COUNT;
			for (size_t i = 0; i < UFBX_SHADER_TYPE_COUNT; i++) {
				const char *str = ufbxt_shader_type_name((ufbx_shader_type)i);
				if (!strcmp(tokens[1], str)) {
					name = (ufbx_shader_type)i;
					break;
				}
			}
			if (name == (ufbx_shader_type)UFBX_SHADER_TYPE_COUNT) {
				fprintf(stderr, "%s:%d: Unknown shader type '%s' in '%s'\n", filename, line, tokens[1], tokens[0]);
				ok = false;
			}

			if (material->shader_type != name) {
				const char *mat_name = ufbxt_shader_type_name(material->shader_type);
				fprintf(stderr, "%s:%d: Material '%s' shader type mismatch: got '%s', expected '%s'\n", filename, line,
					material->name.data, mat_name, tokens[1]);
			}
		} else {
			fprintf(stderr, "%s:%d: Invalid token '%s'\n", filename, line, tokens[0]);
			ok = false;
		}
	}

	if (require_all) {
		for (size_t i = 0; i < scene->materials.count; i++) {
			if (!seen_materials[i]) {
				fprintf(stderr, "%s: 'require all': Material in FBX not described: '%s'\n", filename,
					scene->materials.data[i]->name.data);
				ok = false;
			}
		}
	}

	if (ok) {
		double avg = err_num > 0 ? err_sum / (double)err_num : 0.0;
		printf("Checked %zu materials, %zu properties (error avg %.3g, max %.3g, %zu tests)\n", num_materials, num_props, avg, err_max, err_num);
	}

	return ok;
}

#endif
