/*
 * Copyright (C) 2015 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#include <assert.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "xml.h"
#include "xml-schem.h"

char *progname;
char *outname;
char **inname;

static void
merge(
	struct xml_schem *sch,
	struct xml_schem_signal *s0,
	struct xml_schem_signal *s1
)
{
	struct xml_schem_port *port;
	struct xml_schem_component *comp;
	struct xml_schem_name *n0;
	struct xml_schem_name *n1;

	if (s0 == s1) {
		return;
	}

	/* Rename all connections. */
	for (port = sch->port_first; port; port = port->next) {
		if (strcmp(port->signal, s1->id) == 0) {
			// free(port->signal);
			port->signal = strdup(s0->id);
			assert(port->signal);
		}
	}
	for (comp = sch->comp_first; comp; comp = comp->next) {
		for (port = comp->port_first; port; port = port->next) {
			if (strcmp(port->signal, s1->id) == 0) {
				// free(port->signal);
				port->signal = strdup(s0->id);
				assert(port->signal);
			}
		}
	}

	/* Move s1's names to s0. */
	while (s1->name_first) {
		n1 = s1->name_first;

		/* Remove name from s1. */
		s1->name_first = n1->next;
		if (n1->next) {
			n1->next->prev = NULL;
		} else {
			s1->name_last = NULL;
		}

		/* Add name to s0. */
		for (n0 = s0->name_first; ; n0 = n0->next) {
			if (! n0) {
				/* Not found. */
				n1->prev = s0->name_last;
				n1->next = NULL;
				if (n1->prev) {
					n1->prev->next = n1;
				} else {
					s0->name_first = n1;
				}
				s0->name_last = n1;
				break;
			}
			if (strcmp(n0->name, n1->name) == 0) {
				/* Found. */
				assert(n1->name);
				// free(n1->name);
				free(n1);
				break;
			}
		}
	}

	/* Move s1's graphical elements to s0. */
	while (s1->ge_first) {
		struct xml_schem_ge *ge;

		ge = s1->ge_first;

		/* Remove graphical element from s1. */
		s1->ge_first = ge->next;
		if (ge->next) {
			ge->next->prev = NULL;
		} else {
			s1->ge_last = NULL;
		}

		/* Add graphical element to s0. */
		ge->prev = s0->ge_last;
		ge->next = NULL;
		if (ge->prev) {
			ge->prev->next = ge;
		} else {
			s0->ge_first = ge;
		}
		s0->ge_last = ge;
	}

	/* Remove signal s1 from schem. */
	if (s1->prev) {
		s1->prev->next = s1->next;
	} else {
		sch->sig_first = s1->next;
	}
	if (s1->next) {
		s1->next->prev = s1->prev;
	} else {
		sch->sig_last = s1->prev;
	}
	assert(s1->id);
	// free(s1->id);
	assert(s1->type);
	// free(s1->type);
	assert(! s1->name_first);
	assert(! s1->name_last);
	assert(! s1->ge_first);
	assert(! s1->ge_last);
}

static int
port_merge(
	struct xml_schem *sch,
	struct xml_schem_port *p0,
	struct xml_schem_port *p1
)
{
	if (strcmp(p0->name, p1->name) != 0) {
		return 0;

	} else {
		struct xml_schem_signal *s0;
		struct xml_schem_signal *s1;
		struct xml_schem_ge *ge;

#if 0
		fprintf(stderr, "Merging ports %s.\n", p0->name);
#endif

		/* Lookup signals. */
		for (s0 = sch->sig_first; ; s0 = s0->next) {
			assert(s0);
			if (strcmp(s0->id, p0->signal) == 0) {
				break;
			}
		}
		for (s1 = sch->sig_first; ; s1 = s1->next) {
			assert(s1);
			if (strcmp(s1->id, p1->signal) == 0) {
				break;
			}
		}
		assert(s0);
		assert(s1);

		/* Merge the two signals. */
		merge(sch, s0, s1);

		/* Move p1's graphic elements to p0. */
		while (p1->ge_first) {
			ge = p1->ge_first;

			/* Remove graphic element from p1. */
			p1->ge_first = ge->next;
			if (ge->next) {
				ge->next->prev = NULL;
			} else {
				p1->ge_last = NULL;
			}

			/* Add graphic element to p0. */
			ge->prev = p0->ge_last;
			ge->next = NULL;
			if (ge->prev) {
				ge->prev->next = ge;
			} else {
				p0->ge_first = ge;
			}
			p0->ge_last = ge;
		}

		/* Remove port p1 from schem. */
		if (p1->prev) {
			p1->prev->next = p1->next;
		} else {
			sch->port_first = p1->next;
		}
		if (p1->next) {
			p1->next->prev = p1->prev;
		} else {
			sch->port_last = p1->prev;
		}
		assert(p1->name);
		// free(p1->name);
		assert(p1->signal);
		// free(p1->signal);
		assert(p1->inout);
		// free(p1->inout);
		return 1;
	}
}

static int
sig_same(struct xml_schem_signal *s0, struct xml_schem_signal *s1)
{
	struct xml_schem_name *n0;
	struct xml_schem_name *n1;

	for (n0 = s0->name_first; n0; n0 = n0->next) {
		for (n1 = s1->name_first; n1; n1 = n1->next) {
			if (strcmp(n0->name, n1->name) == 0) {
				return 1;
			}
		}
	}

	return 0;
}

#if 0
static const char *
sig_merge(struct xml_schem *plan, struct xml_schem_signal *sig)
{
	struct xml_schem_signal *s;
	struct xml_schem_name *n0;
	struct xml_schem_name *n1;

	for (s = plan->sig_first; ; s = s->next) {
		if (! s) {
			/* New signal. */
			sig->prev = plan->sig_last;
			sig->next = NULL;
			if (sig->prev) {
				sig->prev->next = sig;
			} else {
				plan->sig_first = sig;
			}
			plan->sig_last = sig;
			return sig->id;
		}
		if (sig_same(s, sig)) {
			/* Already known signal. */
			struct xml_schem_ge *ge;

			while ((n0 = sig->name_first) != NULL) {
				/* Remove name from new signal. */
				sig->name_first = n0->next;
				if (n0->next) {
					n0->next->prev = n0->prev;
				} else {
					sig->name_last = n0->prev;
				}

				/* Add name to old signal. */
				for (n1 = s->name_first; ; n1 = n1->next) {
					if (! n1) {
						n0->prev = s->name_last;
						n0->next = NULL;
						if (n0->prev) {
							n0->prev->next = n0;
						} else {
							s->name_first = n0;
						}
						s->name_last = n0;
						break;
					}
					if (strcmp(n0->name, n1->name) == 0) {
						break;
					}
				}
			}
			while ((ge = sig->ge_first) != NULL) {
				/* Remove graphic element from new signal. */
				sig->ge_first = ge->next;
				if (ge->next) {
					ge->next->prev = ge->prev;
				} else {
					sig->ge_last = ge->prev;
				}

				/* Add graphic element to old signal. */
				ge->prev = s->ge_last;
				ge->next = NULL;
				if (ge->prev) {
					ge->prev->next = ge;
				} else {
					s->ge_first = ge;
				}
				s->ge_last = ge;
			}
			return s->id;
		}
	}
}
#else
static int
sig_merge(
	struct xml_schem *sch,
	struct xml_schem_signal *s0,
	struct xml_schem_signal *s1
)
{
	if (! sig_same(s0, s1)) {
		return 0;
	} else {
#if 0
		fprintf(stderr, "Merging signals %s and %s.\n", s0->id, s1->id);
#endif

		merge(sch, s0, s1);
		return 1;
	}
}
#endif

static struct xml_schem *
plan_merge(unsigned int nplans, struct xml_schem *inplan[])
{
	struct xml_schem *outplan;
	double minx[32];
	double miny[32];
	double maxx[32];
	double maxy[32];
	double x;
	double y;
	unsigned int i;
	struct xml_schem_generic *gen;
	struct xml_schem_port *port;
	struct xml_schem_signal *sig;
	struct xml_schem_component *comp;
	struct xml_schem_port *p0;
	struct xml_schem_port *p1;
	struct xml_schem_signal *s0;
	struct xml_schem_signal *s1;

	outplan = malloc(sizeof(*outplan));
	assert(outplan);
	memset(outplan, 0, sizeof(*outplan));

	/* Translate coordinates. */
	for (i = 0; i < nplans; i++) {
		xml_schem_bbox(inplan[i], &minx[i], &miny[i], &maxx[i], &maxy[i]);
	}
	y = 0.0;
	x = 0.0;
	for (i = 0; i < nplans; i++) {
		xml_schem_translate(inplan[i], x - minx[i], y - miny[i]);
		x += maxx[i] - minx[i] + 100.0;
	}

	/* Rename local signals. */
	for (i = 0; i < nplans; i++) {
		char buf[50];

		for (port = inplan[i]->port_first; port; port = port->next) {
			for (sig = inplan[i]->sig_first; ; sig = sig->next) {
				assert(sig);
				if (strcmp(sig->id, port->signal) == 0) {
					sprintf(buf, "%p", sig);
					port->signal = strdup(buf);
					assert(port->signal);
					break;
				}
			}
		}
		for (comp = inplan[i]->comp_first; comp; comp = comp->next) {
			for (port = comp->port_first; port; port = port->next) {
				for (sig = inplan[i]->sig_first; ; sig = sig->next) {
					assert(sig);
					if (strcmp(sig->id, port->signal) == 0) {
						sprintf(buf, "%p", sig);
						port->signal = strdup(buf);
						assert(port->signal);
						break;
					}
				}
			}
		}
		/* Must be last. */
		for (sig = inplan[i]->sig_first; sig; sig = sig->next) {
			sprintf(buf, "%p", sig);
			sig->id = strdup(buf);
			assert(sig->id);
		}
	}

	/* Merge signals. Must be first! */
	for (i = 0; i < nplans; i++) {
		while (inplan[i]->sig_first) {
#if 0
			const char *old;
			const char *new;
#endif

			sig = inplan[i]->sig_first;

			/* Remove from input plan. */
			inplan[i]->sig_first = sig->next;
			if (sig->next) {
				sig->next->prev = sig->prev;
			} else {
				inplan[i]->sig_last = sig->next;
			}

			/* Add to output plan. */
#if 0
			old = sig->id;
			new = sig_merge(outplan, sig);

			for (port = inplan[i]->port_first; port; port = port->next) {
				if (strcmp(port->signal, old) == 0) {
					port->signal = new;
				}
			}
			for (comp = inplan[i]->comp_first; comp; comp = comp->next) {
				for (port = comp->port_first; port; port = port->next) {
					if (strcmp(port->signal, old) == 0) {
						port->signal = new;
					}
				}
			}
#else
			sig->prev = outplan->sig_last;
			sig->next = NULL;
			if (sig->prev) {
				sig->prev->next = sig;
			} else {
				outplan->sig_first = sig;
			}
			outplan->sig_last = sig;
#endif
		}
	}

	/* Merge generics. */
	for (i = 0; i < nplans; i++) {
		while (inplan[i]->gen_first) {
			gen = inplan[i]->gen_first;

			/* Remove from input plan. */
			inplan[i]->gen_first = gen->next;
			if (gen->next) {
				gen->next->prev = gen->prev;
			} else {
				inplan[i]->gen_last = gen->next;
			}

			/* Add to output plan. */
			gen->prev = outplan->gen_last;
			gen->next = NULL;
			if (gen->prev) {
				gen->prev->next = gen;
			} else {
				outplan->gen_first = gen;
			}
			outplan->gen_last = gen;
		}
	}

	/* Merge ports. */
	for (i = 0; i < nplans; i++) {
		while (inplan[i]->port_first) {
			port = inplan[i]->port_first;

			/* Remove from input plan. */
			inplan[i]->port_first = port->next;
			if (port->next) {
				port->next->prev = port->prev;
			} else {
				inplan[i]->port_last = port->next;
			}

			/* Add to output plan. */
			port->prev = outplan->port_last;
			port->next = NULL;
			if (port->prev) {
				port->prev->next = port;
			} else {
				outplan->port_first = port;
			}
			outplan->port_last = port;
		}
	}

	/* Merge components. */
	for (i = 0; i < nplans; i++) {
		while (inplan[i]->comp_first) {
			comp = inplan[i]->comp_first;

			/* Remove from input plan. */
			inplan[i]->comp_first = comp->next;
			if (comp->next) {
				comp->next->prev = comp->prev;
			} else {
				inplan[i]->comp_last = comp->next;
			}

			/* Add to output plan. */
			comp->prev = outplan->comp_last;
			comp->next = NULL;
			if (comp->prev) {
				comp->prev->next = comp;
			} else {
				outplan->comp_first = comp;
			}
			outplan->comp_last = comp;
		}
	}

	/* Connect ports with equal names. */
again_port:;
	for (p0 = outplan->port_first; p0; p0 = p0->next) {
		for (p1 = p0->next; p1; p1 = p1->next) {
			if (port_merge(outplan, p0, p1)) {
				goto again_port;
			}
		}
	}

	/* Connect signals with equal names. */
again_sig:;
	for (s0 = outplan->sig_first; s0; s0 = s0->next) {
		for (s1 = s0->next; s1; s1 = s1->next) {
			if (sig_merge(outplan, s0, s1)) {
				goto again_sig;
			}
		}
	}

	/* Sort ports. */
again_sort:;
	for (p0 = outplan->port_first; p0; p0 = p0->next) {
		for (p1 = p0->next; p1; p1 = p1->next) {
			if (p0->seq
			 && p1->seq
			 && atoi(p1->seq) < atoi(p0->seq)) {
				if (p1->prev) {
					p1->prev->next = p1->next;
				} else {
					outplan->port_first = p1->next;
				}
				if (p1->next) {
					p1->next->prev = p1->prev;
				} else {
					outplan->port_last = p1->prev;
				}

				p1->prev = p0->prev;
				p1->next = p0;
				if (p1->prev) {
					p1->prev->next = p1;
				} else {
					outplan->port_first = p1;
				}
				p1->next->prev = p1;
				goto again_sort;
			}
		}
	}

	return outplan;
}

static void
do_work(void)
{
	unsigned int nins;
	struct xml_schem *inplan[32];
	struct xml_schem *outplan;
	FILE *fp;
	int ret;

	/*
	 * Read xml files.
	 */
	nins = 0;
	while (inname[nins]) {
		assert(nins < sizeof(inplan) / sizeof(inplan[0]));

		fp = fopen(inname[nins], "r");
		assert(fp);

		inplan[nins] = xml_schem_read(fp);

		ret = fclose(fp);
		assert(0 <= ret);

		nins++;
	}

	/*
	 * Merge files.
	 */
	outplan = plan_merge(nins, inplan);

	/*
	 * Write xml file.
	 */
	fp = fopen(outname, "w");
	assert(fp);

	xml_schem_write(fp, outplan);

	ret = fclose(fp);
	assert(0 <= ret);
}

static void __attribute__((__noreturn__))
usage(int retval)
{
	fprintf(stderr, "Usage: %s <outname> <inname> ...\n", progname);
	exit(retval);
}

int
main(int argc, char **argv)
{
	int c;

	/*
	 * Get program name.
	 */
	progname = *argv;

	/*
	 * Get options.
	 */
	while ((c = getopt(argc, argv, "")) != -1) {
		switch (c) {
		default:
			usage(1);
			/*NOTREACHED*/
		}
	}
	argc -= optind;
	argv += optind;

	/*
	 * Get parameter.
	 */
	if (0 < argc) {
		outname = *argv;
		argv++;
		argc--;
	} else {
		usage(1);
		/*NOTREACHED*/
	}
	if (0 < argc) {
		inname = argv;
		argv += argc;
		argc -= argc;
	} else {
		usage(1);
		/*NOTREACHED*/
	}
	if (argc != 0) {
		usage(1);
		/*NOTREACHED*/
	}

	/*
	 * Do work.
	 */
	do_work();

	return 0;
}
