#include <assert.h>
#include <inttypes.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>

#include <libdisplay-info/displayid.h>
#include <libdisplay-info/edid.h>
#include <libdisplay-info/info.h>

#include "di-edid-decode.h"

struct uncommon_features uncommon_features = {0};

static const struct option long_options[] = {
	{ "help", no_argument, 0, 'h' },
	{ 0, 0, 0, 0 }
};

static void usage(void)
{
	fprintf(stderr, "Usage:		di-edid-decode  <options> [in]\n"
			" [in]:		EDID file to parse. Read from standard input (stdin),\n"
			"		if none given.\n"
			"Example :	di-edid-decode /sys/class/drm/card0-DP-2/edid \n"
			"\nOptions:\n"
			"-h, --help	display the help message\n");
}

static const char *
ext_tag_name(enum di_edid_ext_tag tag)
{
	switch (tag) {
	case DI_EDID_EXT_CEA:
		return "CTA-861 Extension Block";
	case DI_EDID_EXT_VTB:
		return "Video Timing Extension Block";
	case DI_EDID_EXT_DI:
		return "Display Information Extension Block";
	case DI_EDID_EXT_LS:
		return "Localized String Extension Block";
	case DI_EDID_EXT_DPVL:
		return "Digital Packet Video Link Extension";
	case DI_EDID_EXT_BLOCK_MAP:
		return "Block Map Extension Block";
	case DI_EDID_EXT_VENDOR:
		return "Manufacturer-Specific Extension Block";
	case DI_EDID_EXT_DISPLAYID:
		return "DisplayID Extension Block";
	}
	abort();
}

static void
print_ext(const struct di_edid_ext *ext, size_t ext_index)
{
	const char *tag_name;

	tag_name = ext_tag_name(di_edid_ext_get_tag(ext));
	printf("\n----------------\n\n");
	printf("Block %zu, %s:\n", ext_index + 1, tag_name);

	switch (di_edid_ext_get_tag(ext)) {
	case DI_EDID_EXT_CEA:
		print_cta(di_edid_ext_get_cta(ext));
		break;
	case DI_EDID_EXT_DISPLAYID:
		print_displayid(di_edid_ext_get_displayid(ext));
		break;
	default:
		break; /* Ignore */
	}
}

static size_t
edid_checksum_index(size_t block_index)
{
	return 128 * (block_index + 1) - 1;
}

int
main(int argc, char *argv[])
{
	FILE *in;
	static uint8_t raw[32 * 1024];
	size_t size = 0;
	const struct di_edid *edid;
	struct di_info *info;
	const struct di_edid_ext *const *exts;
	const char *failure_msg;
	size_t i;
	int opt;

	in = stdin;
	while (1) {
		int option_index = 0;
		opt = getopt_long(argc, argv, "h", long_options, &option_index);
		if (opt == -1)
			break;

		switch (opt) {
		case 'h':
			usage();
			return -1;
		default:
			usage();
			return -1;
		}
	}

	if (argc > 1) {
		in = fopen(argv[1], "r");
		if (!in) {
			perror("failed to open input file");
			return 1;
		}
	}

	while (!feof(in)) {
		size += fread(&raw[size], 1, sizeof(raw) - size, in);
		if (ferror(in)) {
			perror("fread failed");
			return 1;
		} else if (size >= sizeof(raw)) {
			fprintf(stderr, "input too large\n");
			return 1;
		}
	}

	fclose(in);

	info = di_info_parse_edid(raw, size);
	if (!info) {
		perror("di_edid_parse failed");
		return 1;
	}

	edid = di_info_get_edid(info);
	print_edid(edid);

	exts = di_edid_get_extensions(edid);

	for (i = 0; exts[i] != NULL; i++);
	if (i > 0) {
		printf("  Extension blocks: %zu\n", i);
	}

	printf("Checksum: 0x%02hhx\n", raw[edid_checksum_index(0)]);

	for (i = 0; exts[i] != NULL; i++) {
		print_ext(exts[i], i);
		printf("Checksum: 0x%02hhx\n", raw[edid_checksum_index(i + 1)]);
	}

	printf("\n----------------\n\n");

	failure_msg = di_info_get_failure_msg(info);
	if (failure_msg) {
		printf("Failures:\n\n%s", failure_msg);
		printf("EDID conformity: FAIL\n");
	} else {
		printf("EDID conformity: PASS\n");
	}

	if (uncommon_features.color_point_descriptor) {
		fprintf(stderr, "The EDID blob contains an uncommon Color "
				"Point Descriptor. Please share the EDID blob "
				"with upstream!\n");
	}
	if (uncommon_features.color_management_data) {
		fprintf(stderr, "The EDID blob contains an uncommon Color "
				"Management Data Descriptor. Please share the "
				"EDID blob with upstream!\n");
	}
	if (uncommon_features.cta_transfer_characteristics) {
		fprintf(stderr, "The EDID blob contains an uncommon CTA VESA "
				"Display Transfer Characteristic data block. "
				"Please share the EDID blob with upstream!\n");
	}

	di_info_destroy(info);
	return failure_msg ? 254 : 0;
}
