/*
 * Copyright © 2013-2020 Inria.  All rights reserved.
 * See COPYING in top-level directory.
 */

#include "private/autogen/config.h"
#include "hwloc.h"
#include "hwloc/diff.h"
#include "misc.h"

void usage(const char *callname __hwloc_attribute_unused, FILE *where)
{
	fprintf(where, "Usage: hwloc-diff [options] <old.xml> <new.xml> [<output.diff.xml>]\n");
	fprintf(where, "Options:\n");
	fprintf(where, "  --refname <name>  Change the XML reference identifier to <name> in the output\n");
	fprintf(where, "                    (default is the filename of the first topology\n");
	fprintf(where, "  --version         Report version and exit\n");
}

int main(int argc, char *argv[])
{
	hwloc_topology_t topo1, topo2;
	hwloc_topology_diff_t firstdiff = NULL, diff;
	unsigned long flags = HWLOC_TOPOLOGY_FLAG_INCLUDE_DISALLOWED | HWLOC_TOPOLOGY_FLAG_IMPORT_SUPPORT;
	char *callname, *input1, *input2, *output, *outputname, *refname = NULL;
	char *xmlbuffer;
	int xmlbuflen;
	unsigned i, j;
	int err;

	callname = argv[0];
	/* skip argv[0], handle options */
	argc--;
	argv++;

	hwloc_utils_check_api_version(callname);

	if (!getenv("HWLOC_XML_VERBOSE"))
		putenv((char *) "HWLOC_XML_VERBOSE=1");

	while (argc && *argv[0] == '-') {
		if (!strcmp (argv[0], "--refname")) {
			if (argc < 2) {
				usage(callname, stderr);
				exit(EXIT_FAILURE);
			}
			refname = argv[1];
			argc--;
			argv++;
		} else if (!strcmp (argv[0], "--version")) {
			printf("%s %s\n", callname, HWLOC_VERSION);
			exit(EXIT_SUCCESS);
		} else if (!strcmp (argv[0], "-h") || !strcmp (argv[0], "--help")) {
			usage(callname, stdout);
			exit(EXIT_SUCCESS);
		} else {
			fprintf(stderr, "Unrecognized options: %s\n", argv[0]);
			usage(callname, stderr);
			exit(EXIT_FAILURE);
		}
		argc--;
		argv++;
	}

	if (argc < 2) {
		usage(callname, stderr);
		exit(EXIT_FAILURE);
	}
	input1 = argv[0];
	input2 = argv[1];
	argc -= 2;
	argv += 2;
	if (argc >= 1) {
		output = argv[0];
		outputname = argv[0];
		argc--;
		argv++;
	} else  {
		output = NULL;
		outputname = (char *) "stdout";
	}

	hwloc_topology_init(&topo1);
	hwloc_topology_set_all_types_filter(topo1, HWLOC_TYPE_FILTER_KEEP_ALL);
	hwloc_topology_set_flags(topo1, flags);
	err = hwloc_topology_set_xml(topo1, input1);
	if (err < 0) {
		fprintf(stderr, "Failed to load 1st XML topology %s\n", input1);
		goto out_with_topo1;
	}
	err = hwloc_topology_load(topo1);
	if (err < 0) {
		fprintf(stderr, "Failed to load 1st topology %s\n", input1);
		goto out_with_topo1;
	}

	hwloc_topology_init(&topo2);
	hwloc_topology_set_all_types_filter(topo2, HWLOC_TYPE_FILTER_KEEP_ALL);
	hwloc_topology_set_flags(topo2, flags);
	err = hwloc_topology_set_xml(topo2, input2);
	if (err < 0) {
		fprintf(stderr, "Failed to load 2nd XML topology %s\n", input2);
		goto out_with_topo2;
	}
	err = hwloc_topology_load(topo2);
	if (err < 0) {
		fprintf(stderr, "Failed to load 2nd topology %s\n", input2);
		goto out_with_topo2;
	}

	if (!refname) {
		refname = strrchr(input1, '/');
		if (refname)
			refname++;
		else
			refname = input1;
	}

	err = hwloc_topology_diff_build(topo1, topo2, 0, &firstdiff);
	if (err < 0) {
		fprintf(stderr, "Failed to compute the diff (%s)\n", strerror(errno));
		goto out_with_topo2;
	}

	diff = firstdiff;
	i = 0, j = 0;
	while (diff) {
		i++;
		if (diff->generic.type == HWLOC_TOPOLOGY_DIFF_TOO_COMPLEX)
			j++;
		diff = diff->generic.next;
	}
	if (!i) {
		fprintf(stderr, "Found no difference, exporting empty topology diff to %s\n", outputname);
	} else if (!j) {
		fprintf(stderr, "Found %u differences, exporting to %s\n", i, outputname);
	} else {
		fprintf(stderr, "Found %u differences, including %u too complex ones.\n", i, j);
		fprintf(stderr, "Cannot export differences to %s\n", outputname);
	}
	if (!j) {
		if (output) {
			err = hwloc_topology_diff_export_xml(firstdiff, refname, output);
		} else {
			err = hwloc_topology_diff_export_xmlbuffer(firstdiff, refname, &xmlbuffer, &xmlbuflen);
			if (!err) {
				printf("%s\n", xmlbuffer);
				hwloc_free_xmlbuffer(topo1, xmlbuffer);
			}
		}
		if (err < 0)
			fprintf(stderr, "Failed to export topology diff %s\n", output);
	}

	hwloc_topology_diff_destroy(firstdiff);

	hwloc_topology_destroy(topo2);
	hwloc_topology_destroy(topo1);

	if (j)
	  exit(EXIT_FAILURE);
	else
	  exit(EXIT_SUCCESS);

out_with_topo2:
	hwloc_topology_destroy(topo2);
out_with_topo1:
	hwloc_topology_destroy(topo1);
	exit(EXIT_FAILURE);
}
