/* Copyright 2011,2012 Bernhard R. Link <brlink@debian.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * SOFTWARE IN THE PUBLIC INTEREST, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
#include "config.h"

#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <getopt.h>
#include <endian.h>

#include "globals.h"
#include "statetable.h"
#include "translate.h"
#include "merge.h"
#define EFF_INTERNAL 1
#include "eff.h"
#include "tags.h"

static void printstatetable(struct statetable *t) {
	int i;
	unsigned char w, s;

	for ( i = 0 ; i < t->count ; i++) {
		printf("state %d: { ", i);
		if (t->transits[i].next['\n'] != -1) {
			printf("'\\n'  -> %d, ", t->transits[i].next['\n']);
		}
		for (w = ' ' ; w < 127 ; w++) {
			int ns = t->transits[i].next[w];
			if (ns != -1) {
				s = w;
				while (w < 127 && t->transits[i].next[w+1] == ns) {
					w++;
				}
				if (w != s)
				printf("'%c' - '%c' -> %d, ", s, w,
						t->transits[i].next[w]);
				else
				printf("'%c' -> %d, ", w,
						t->transits[i].next[w]);
			}
		}
		printf(" }\n");
	}
}

static void storestatetable(struct effwriter *f, struct statetable *t, int goalcount) {
	unsigned char buffer[6 * 256];
	uint32_t u;
	int i;

	eff_writestart(f, "states");
	eff_writeuint32(f, t->count);

	/* store the state table in some run-length-encoded format: */
	for (i = 0 ; i < t->count ; i++) {
		int lasts = -1;
		int w, o = 0;

		for (w = 0 ; w <= 255 ; w++) {
			int ns = t->transits[i].next[w];

			if (ns != lasts) {
				if (lasts != -1) {
					buffer[6 * o + 1] = w - 1;
					if (lasts < 0) {
						u = -2 - lasts;
						assert (u >= 0);
						assert (u < goalcount);
					} else
						u = goalcount + 1 + lasts;
					u = u32trans(u);
					memcpy(buffer + 6 * o + 2, &u, 4);
					o++;
				}
				buffer[6 * o + 0] = w;
			}
			lasts = ns;
		}
		if (lasts != -1) {
			buffer[6 * o + 1] = w - 1;
			if (lasts < 0) {
				u = -2 - lasts;
				assert (u >= 0);
				assert (u < goalcount);
			} else
				u = goalcount + 1 + lasts;
			u = u32trans(u);
			memcpy(buffer + 6 * o + 2, &u, 4);
			o++;
		}
		eff_writearray(f, 6, o, buffer);
	}
	eff_writeend(f);
}


int main(int argc, char * const argv[]) {
	struct tag *tag;
	struct statetable *a = NULL, *b;
	const char *outfile = NULL;
	int opt, tagcount;
	bool debug = false, quiet = false;
	static const struct option long_options[] = {
		{"debug", no_argument, NULL, 'd'},
		{"quiet", no_argument, NULL, 'q'},
		{"output", required_argument, NULL, 'o'},
		{0, 0, 0, 0}
	};

	while ((opt = getopt_long(argc, argv, "+o:qd", long_options, NULL)) != -1) {
		switch (opt) {
			case 'q':
				quiet = true;
				break;
			case 'd':
				debug = true;
				break;
			case 'o':
				outfile = optarg;
				break;
			case '?':
				fail("Unknown command line options");
		}
	}

	for ( ; optind < argc ; optind++) {
		FILE *f = fopen(argv[optind], "r");
		if (f == NULL) {
			fail("Error %d opening '%s': %s",
					errno, argv[optind], strerror(errno));
		}
		if (!parse_tag_description(f, argv[optind]))
			fail("Could not parse '%s'", argv[optind]);
		fclose(f);
	}

	tagcount = 0;
	for (tag = tags ; tag != NULL ; tag = tag->next) {
		tag->id = tagcount++;
	}

	for (tag = tags ; tag != NULL ; tag = tag->next) {
		if (tag->mergemode) {
			struct tag *tt;

			tag->mergeid1 = -1;
			for (tt = tags ; tt != NULL ; tt = tt->next) {
				if (strcmp(tag->mergename1, tt->name) == 0) {
					tag->mergeid1 = tt->id;
					break;
				}
			}
			if (tag->mergeid1 < 0)
				fail("Cannot find tag '%s' to merge into '%s'",
						tag->name, tag->mergename1);
			tag->mergeid2 = -1;
			for (tt = tags ; tt != NULL ; tt = tt->next) {
				if (strcmp(tag->mergename2, tt->name) == 0) {
					tag->mergeid2 = tt->id;
					break;
				}
			}
			if (tag->mergeid2 < 0)
				fail("Cannot find tag '%s' to merge into '%s'",
						tag->name, tag->mergename2);
			tag->mergeid3 = -1;
			if (tag->mergename3[0] != '\0') {
				for (tt = tags ; tt != NULL ; tt = tt->next) {
					if (strcmp(tag->mergename3, tt->name) == 0) {
						tag->mergeid3 = tt->id;
						break;
					}
				}
				if (tag->mergeid3 < 0)
					fail("Cannot find tag '%s' to merge into '%s'",
							tag->name, tag->mergename3);
			}
		}
	}

	for (tag = tags ; tag != NULL ; tag = tag->next) {
		const struct pattern *p;
		for (p = tag->patterns ; p != NULL ; p = p->next) {
			b = parse_pattern(p->pattern, tag->id);
			if (a != NULL)
				a = merge_statetables(a, b);
			else
				a = b;
		}
	}
	if (a == NULL)
		fail("Nothing to look for specified!");
	if (!quiet)
		printf("%d states\n", a->count);
	if (debug)
		printstatetable(a);
	if (outfile != NULL) {
		struct effwriter *f;

		f = eff_create(outfile, "builocheck");
		if (variable_count > 0) {
			eff_writestart(f, "variable_count");
			eff_writeuint32(f, variable_count);
			eff_writeend(f);
		}
		eff_writestart(f, "goals");
		eff_writeuint32(f, tagcount);
		for (tag = tags ; tag != NULL ; tag = tag->next) {
			tag_write(tag, f);
		}
		eff_writeend(f);
		storestatetable(f, a, tagcount);
		eff_done(f);
	}
	return 0;
}
