/* 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 <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>

#include "globals.h"
#include "statetable.h"
#include "translate.h"

struct posinfo {
	/* next to try is thisofs + 1 + nextdelta,
	 * if special is true or match has the bit set */
	int nextdelta;
	/* characters to match, one  bit per char value */
	uint32_t match[8];
	/* or go directly to one of those */
	struct alternate {
		struct alternate *next;
		int what;
	} *alternate;
	bool special;
	/* no need to keep earlier states when this is reached */
	bool consumes;
	/* emit a exclusive state if that is reached */
	bool exclusive;
};

static void setchar(struct posinfo *p, unsigned char c) {
	p->match[c >> 5] |= 1U << (c & 31);
}
static void unsetchar(struct posinfo *p, unsigned char c) {
	p->match[c >> 5] &= ~(1U << (c & 31));
}
static bool ischarset(struct posinfo *p, unsigned char c) {
	return (p->match[c >> 5] & (1U << (c & 31)) ) != 0;
}
static void negatecharset(struct posinfo *p) {
	int i;

	for (i = 0 ; i < 8 ; i++) {
		p->match[i] ^= 0xFFFFFFFFul;
	}
}
static void addalternate(struct posinfo *list, int addfrom, int addto) {
	struct alternate *a = NEW(1, struct alternate);

	a->what = addto;
	a->next = list[addfrom].alternate;
	list[addfrom].alternate = a;
}

static void findinstant(struct posinfo *list, int fromstate, size_t len, bool *directly) {
	if (directly[fromstate])
		return;
	directly[fromstate] = true;
	if (list[fromstate].special)
		findinstant(list,
				fromstate + 1 + list[fromstate].nextdelta,
				len, directly);

	struct alternate *a;
	for (a = list[fromstate].alternate ; a != NULL ; a = a->next) {
		findinstant(list, a->what, len, directly);
	}
}

bool **create_instant_lists(struct posinfo *list, size_t len) {
	bool **lists;
	int i;

	lists = NEW(len + 1, bool*);
	for (i = 0 ; i < len ; i++) {
		lists[i] = zNEW(len + 1 , bool);
		findinstant(list, i, len, lists[i]);
	}
	lists[len] = zNEW(len + 1, bool);
	lists[len][len] = true;
	return lists;
}

static int newstate(size_t len, const bool *state, struct statetable **st_p, bool ***ps_p) {
	int count = (*st_p == NULL)?0:(*st_p)->count;
	struct statetable *st;
	bool **ps = *ps_p;
	int i;

	for (i = 0 ; i < count ; i++) {
		if (memcmp(ps[i], state, sizeof(bool)*(len+1)) == 0 )
			return i;
	}

	if ((count&31) == 0) {
		st = realloc(*st_p, sizeof(struct statetable)
				+ (count+32) * sizeof(struct statematrix));
		if (st == NULL)
			fail("Out of memory");
		*st_p = st;
		ps = realloc(*ps_p, (count+32) * sizeof(bool *));
		if (ps == NULL)
			fail("Out of memory");
		*ps_p = ps;
	} else
		st = *st_p;
	ps[count] = NEW(len + 1 , bool);
	memcpy(ps[count], state, (len+1)*sizeof(bool));
	memset(&st->transits[count], 0, sizeof(struct statematrix));
	st->count = count +1;
	return count;
}

void can_union(int len, bool *t, const bool *o) {
	int i;

	for (i = 0 ; i <= len ; i++) {
		t[i]  = t[i] || o[i];
	}
}

static void wheretogo(struct posinfo *list, int fromstate, unsigned char w, size_t len, bool *cangoto, const bool * const *instants) {
	if (ischarset(list + fromstate, w)) {
		can_union(len, cangoto,
			instants[fromstate + 1+ list[fromstate].nextdelta]);
	}
}

struct statetable *parse_pattern(const char *pattern, int resultcode) {
	size_t len;
	int lasttoken = -1;
	int thisofs;
	struct posinfo *i;
	bool **instants;
	struct group {
		struct group *parent;
		struct groupaltend {
			struct groupaltend *next;
			int sep;
		} *altends;
		int start;
	} *groups = NULL;
	bool hadtoplevelalternate = false;
	static const uint32_t all[8] = { 0xFFFFeFFFul, 0xFFFFFFFFul, 0xFFFFFFFFul, 0xFFFFFFFFul, 0xFFFFFFFFul, 0xFFFFFFFFul, 0xFFFFFFFFul, 0xFFFFFFFFul};

	len = strlen(pattern);
	i = zNEW(len + 1, struct posinfo);
	thisofs = 0;
	while (thisofs < len) {
		if (pattern[thisofs] == '$' && thisofs == len - 1) {
			lasttoken = thisofs;
			setchar(i + thisofs, '\n');
		} else if (pattern[thisofs] == '.') {
			lasttoken = thisofs;
			memcpy(i[thisofs].match, all, sizeof(all));
		} else if (pattern[thisofs] == '*') {
			if( lasttoken < 0 )
				fail("'*' at start of pattern");
			if (!hadtoplevelalternate && groups == NULL) {
				/* TODO: extend this optimisation
				 * to also work in groups and with top level alternates */
				if (memcmp(i[lasttoken].match, all, sizeof(all)) == 0) {
					i[lasttoken].consumes = true;
				}
			}
			i[thisofs].special = true;
			addalternate(i, lasttoken, thisofs);
			addalternate(i, thisofs, lasttoken);
		} else if (pattern[thisofs] == '[') {
			int startofs = thisofs;
			struct posinfo *ti = i + thisofs;
			bool negate = false;

			lasttoken = thisofs;
			thisofs++;
			if (pattern[thisofs] == '^') {
				negate = true;
				thisofs++;
			}
			if (pattern[thisofs] == ']') {
				setchar(ti, ']');
				thisofs++;
			}
			while (thisofs < len && pattern[thisofs] != ']') {
				if (pattern[thisofs+1] == '-' &&
						pattern[thisofs+2] != ']' &&
						pattern[thisofs+2] != '\0') {
					unsigned char u;

					if (pattern[thisofs+2] <= pattern[thisofs])
						fail("Invalid [] range from 0x%02hhx' to '%02hhx'", pattern[thisofs], pattern[thisofs+2]);
					for (u = pattern[thisofs] ; u <= (unsigned char)pattern[thisofs+2] ; u++) {
						setchar(ti, u);
					}
					thisofs += 2;
				} else
						setchar(ti, pattern[thisofs]);
				thisofs ++;
			}
			if (pattern[thisofs] != ']')
				fail("Unterminated '['");
			if (negate)
				negatecharset(ti);
			unsetchar(ti, '\n');
			ti->nextdelta = (thisofs + 1) - (startofs + 1);
		} else if (pattern[thisofs] != '\\') {
			lasttoken = thisofs;
			setchar(i + thisofs, pattern[thisofs]);
		/* escaped . * \\ is normal character */
		} else if (pattern[thisofs+1] == '*') {
			lasttoken = thisofs;
			setchar(i + thisofs, '*');
			i[thisofs].nextdelta = 1;
			thisofs ++;
		} else if (pattern[thisofs+1] == '.') {
			lasttoken = thisofs;
			setchar(i + thisofs, '.');
			i[thisofs].nextdelta = 1;
			thisofs ++;
		} else if (pattern[thisofs+1] == '\\') {
			lasttoken = thisofs;
			setchar(i + thisofs, '\\');
			i[thisofs].nextdelta = 1;
			thisofs ++;
		/* other things can special meaning by \\ */
		} else if (pattern[thisofs+1] == '+') {
			if( lasttoken < 0 )
				fail("'\\+' at start of pattern");
			i[thisofs].special = true;
			i[thisofs].nextdelta = 1;
			addalternate(i, thisofs, lasttoken);
			thisofs ++;
		} else if (pattern[thisofs+1] == '?') {
			if( lasttoken < 0 )
				fail("'\\?' at start of pattern");
			i[thisofs].special = true;
			i[thisofs].nextdelta = 1;
			addalternate(i, lasttoken, thisofs);
			thisofs ++;
		} else if (pattern[thisofs+1] == '(') {
			struct group *g = alloca(sizeof(struct group));
			g->parent = groups;
			g->altends = NULL;
			g->start = thisofs;
			groups = g;
			i[thisofs].nextdelta = 1;
			i[thisofs].special = true;
			lasttoken = -1;
			thisofs ++;
		} else if (pattern[thisofs+1] == ')') {
			if (groups == NULL)
				fail("'\\)' without '\\('!");
			lasttoken = groups->start;
			while (groups->altends != NULL) {
				i[groups->altends->sep].nextdelta =
					thisofs - (groups->altends->sep + 1);
				i[groups->altends->sep].special = true;
				groups->altends = groups->altends->next;
			}
			groups = groups->parent;
			i[thisofs].nextdelta = 1;
			i[thisofs].special = true;
			thisofs ++;
		} else if (pattern[thisofs+1] == '|') {
			if (groups == NULL) {
				/* global "or", why not multiple regexps? */
				addalternate(i, 0, thisofs+2);
				i[thisofs].special = true;
				i[thisofs].nextdelta = len - (thisofs + 1);
				hadtoplevelalternate = true;
			} else {
				struct groupaltend *a = alloca(sizeof(struct groupaltend));
				a->next = groups->altends;
				a->sep = thisofs;
				groups->altends = a;
				addalternate(i, groups->start, thisofs+2);
				/* the | belongs to the end of the above, not the next one */
				i[thisofs].special = false;
			}
			lasttoken = -1;
			thisofs ++;
		} else if (pattern[thisofs+1] == '/') {
			if (groups != NULL) {
				fail("\\/ not set supported within \\( \\)");
			}
			if (hadtoplevelalternate)
				fail("\\/ cannot be combined with top-level \\|");
			i[thisofs].nextdelta = 1;
			i[thisofs].special = true;
			i[thisofs].consumes = true;
			i[thisofs].exclusive = true;
			lasttoken = -1;
			thisofs ++;
		} else {
			fail("Unexpected backslash escape sequence '\\%c'", pattern[thisofs+1]);
		}
		thisofs ++;
	}
	if (groups != NULL) {
		fail("Unterminated '\\('!");
	}
	instants = create_instant_lists(i, len);
	struct statetable *statetable = NULL;
	bool **positionsets = NULL;
	int processed = 0;

	processed = newstate(len, instants[0], &statetable, &positionsets);
	assert (processed == 0);

	if (instants[0][len]) {
		fail("Trivial expression '%s'", pattern);
	}

	while (processed < statetable->count) {
		int w;

		for (w = 0 ; w < ' ' ; w++) {
			statetable->transits[processed].next[w] = -1;
		}
		for (w = '\n' ; w <= 255 ; w=(w == '\n')?' ':w+1) {
			bool *thisstate = positionsets[processed];
			bool nextstate[len+1];
			int j, n;
			bool dead, exclusive;

			memset(nextstate, 0, sizeof(bool)*(len+1));
			for (j = 0 ; j < len ; j++) {
				if (thisstate[j])
					wheretogo(i, j, w,
							len, nextstate,
							(const bool * const*)instants);
			}
			if (nextstate[len])
				/* found */
				n = -2 - resultcode;
			else {
				dead = true; exclusive = false;
				for (j = len - 1 ; j >= 0 ; j--) {
					if (nextstate[j]) {
						dead = false;
						/* \/ makes the state exclusive
						 * if it is the most advance we got */
						if (i[j].exclusive) {
							int k;
							for (k = len - 1 ; k > j ; k--) {
								if (nextstate[k] && !instants[j][k])
									break;
							}
							if (k == j)
								exclusive = true;
						}
						/* a ".*" not in an alternate consumes everything before */
						if (i[j].consumes) {
							while (j > 0) {
								nextstate[--j] = false;
							}
							break;
						}
					}
				}
				if (dead)
					/* dead end */
					n = -1;
				else {
					n = newstate(len, nextstate, &statetable, &positionsets);
					statetable->transits[n].exclusive = exclusive;
				}
			}
			statetable->transits[processed].next[w] = n;
		}
		processed++;
	}
	free(i);
	int j;
	for (j = 0 ; j <= len ; j++) {
//		int k;
//
//		if (j < 5) {
//		printf("%-7d:  ", j);
//		for (k = 0 ; k <= len ; k++) {
//			putchar(instants[j][k]?'X':' ');
//		}
//		printf(" \n");
//		}
		free(instants[j]);
	}
	free(instants);
//	printf("pattern: '%s'\n", pattern);
	for (j = 0 ; j < statetable->count ; j++) {
//		int k;
//		printf("%-7d:  ", j);
//		for (k = 0 ; k <= len ; k++) {
//			putchar(positionsets[j][k]?'X':' ');
//		}
//		printf(" \n");
		free(positionsets[j]);
	}
	free(positionsets);
	return statetable;
}
