#include "asn1c_internal.h"
#include "asn1c_fdeps.h"

static asn1c_fdeps_t *asn1c_new_dep(const char *filename);
static int asn1c_dep_add(asn1c_fdeps_t *deps, asn1c_fdeps_t *d);

int
asn1c_activate_dependency(asn1c_fdeps_t *deps, asn1c_fdeps_t *cur, const char *data) {
	const char *fname;
	int i;

	if(!deps || !data || !*data)
		return 0;
	if(!cur) cur = deps;

	if(cur->usage > FDEP_NOTUSED)
		return 1;	/* Already activated */

	fname = data;
	if(*data == '#') {
		const char *start = data;
		const char *end = 0;

		start = strchr(data, '<');
		if(start) {
			start++;
			end = strchr(start, '>');
		} else if((start = strchr(data, '\"'))) {
			start++;
			end = strchr(start, '\"');
		}
		if(end) {
			char *p = alloca((end - start) + 1);
			memcpy(p, start, end - start);
			p[end-start] = '\0';
			fname = p;
		} else {
			return 0;
		}
	}

	if(cur->filename && strcmp(cur->filename, fname) == 0) {
		cur->usage = FDEP_REFERRED;

		/* Activate subdependencies */
		for(i = 0; i < cur->el_count; i++) {
			asn1c_activate_dependency(deps,
				cur->elements[i],
				cur->elements[i]->filename);
		}

		/*
		 * This might be a link to someplace else.
		 */
		return asn1c_activate_dependency(deps, NULL, fname);
	} else {
		for(i = 0; i < cur->el_count; i++) {
			asn1c_activate_dependency(deps,
				cur->elements[i], fname);
		}
	}

	return 0;
}

asn1c_fdeps_t *
asn1c_read_file_dependencies(arg_t *arg, const char *datadir) {
	char buf[4096];
	asn1c_fdeps_t *deps;
	asn1c_fdeps_t *cur;
	FILE *f;
	enum fdep_usage special_section = FDEP_NOTUSED;

	(void)arg;

	if(!datadir || strlen(datadir) > sizeof(buf) / 2) {
		errno = EINVAL;
		return NULL;
	} else {
		sprintf(buf, "%s/file-dependencies", datadir);
	}

	f = fopen(buf, "r");
	if(!f) return NULL;

	deps = asn1c_new_dep(0);
	assert(deps);

	while(fgets(buf, sizeof(buf), f)) {
		char *p = strchr(buf, '#');
		if(p) *p = '\0';	/* Remove comments */

		cur = deps;
		for(p = strtok(buf, " \t\r\n"); p;
				p = strtok(NULL, " \t\r\n")) {
			asn1c_fdeps_t *d;

			/*
			 * Special "prefix" section.
			 */
			if(strchr(p, ':')) {
				special_section = FDEP_IGNORE;
				if(strcmp(p, "COMMON-FILES:") == 0) {
					special_section = FDEP_COMMON_FILES;
				} else if(strcmp(p, "CONVERTER:") == 0) {
					special_section = FDEP_CONVERTER;
				} else if((arg->flags & A1C_GEN_OER)
					  && strcmp(p, "CODEC-OER:") == 0) {
					special_section = FDEP_CODEC_OER;
				} else if((arg->flags & A1C_GEN_PER)
					  && strcmp(p, "CODEC-PER:") == 0) {
					special_section = FDEP_CODEC_PER;
				}
				break;
			}

			if(special_section == FDEP_IGNORE)
				continue;

			d = asn1c_new_dep(p);
			d->usage = special_section;

			if(asn1c_dep_add(cur, d) == 1)
				cur = d;
		}
	}

	fclose(f);

	return deps;
}

static asn1c_fdeps_t *
asn1c_new_dep(const char *filename) {
	asn1c_fdeps_t *d;

	d = calloc(1, sizeof(*d));
	if(filename) {
		d->filename = strdup(filename);
		if(!d->filename) return NULL;
	}

	return d;
}

static void
asn1c_free_dep(asn1c_fdeps_t *d) {

	if(d) {
		if(d->filename) free(d->filename);
		d->filename = 0;
		free(d);
	}
}

static int
asn1c_dep_add(asn1c_fdeps_t *deps, asn1c_fdeps_t *d) {
	int n;

	/* Check for duplicates */
	for(n = 0; n < deps->el_count; n++) {
		if(strcmp(deps->elements[n]->filename, d->filename) == 0)
			return 0;
	}

	if(deps->el_count == deps->el_size) {
		void *p;
		n = deps->el_size?deps->el_size << 2:16;
		p = realloc(deps->elements,
			n * sizeof(deps->elements[0]));
		assert(p);
		deps->elements = p;
		deps->el_size = n;
	}

	deps->elements[deps->el_count++] = d;
	return 1;
}

asn1c_fdeps_t *
asn1c_deps_makelist(asn1c_fdeps_t *deps) {
	asn1c_fdeps_t *dlist;
	asn1c_fdeps_t *d;
	int i;

	if(!deps) {
		errno = EINVAL;
		return 0;
	}

	dlist = asn1c_new_dep(0);

	if(deps->filename && deps->usage != FDEP_NOTUSED) {
		d = asn1c_new_dep(deps->filename);
		d->usage = deps->usage;
		if(!asn1c_dep_add(dlist, d)) {
			asn1c_free_dep(d);
		}
	}

	for(i = 0; i < deps->el_count; i++) {
		int j;
		d = asn1c_deps_makelist(deps->elements[i]);
		assert(!d->filename);
		for(j = 0; j < d->el_count; j++) {
			if(asn1c_dep_add(dlist, d->elements[j])) {
				d->elements[j] = 0;
			}
		}
		asn1c_deps_freelist(d);
	}

	return dlist;
}

void
asn1c_deps_freelist(asn1c_fdeps_t *deps) {
	if(deps) {
		int i;
		if(deps->elements) {
			for(i = 0; i < deps->el_count; i++) {
				asn1c_deps_freelist(deps->elements[i]);
				deps->elements[i] = 0;
			}
			free(deps->elements);
			deps->elements = 0;			
		}
		asn1c_free_dep(deps);
	}
}
