/* label.c */

#include <stdlib.h>
#include <string.h>
#include "project.h"

static label_t* labels = NULL;		/* List of all known labels */
static unsigned capacity = 0;		/* Amount of memory allocated */
static unsigned count = 0;		/* Number of known labels */
static unsigned max_addr = 0;		/* Highest labeled address */
static int virgin = 1;			/* Installed atexit yet? */

/* Cleanup function. */
static void cleanup(void)
{
	free(labels);
}

/* Adds a new label to the database. */
char* label_add(char* name, unsigned addr)
{
	int i;

	/* Bail on duplicates */
	for (i = 0; i < count; i++)
		if (strcmp(labels[i].name, name) == 0) return "duplicate label";

	/* Grow label list if required */
	if (capacity == count) {
		capacity = capacity == 0 ? 64 : capacity * 2;
		labels = realloc(labels, sizeof(label_t) * capacity);
		if (labels == NULL) die("Out of memory\n");
		if (virgin) {
			virgin = 0;
			if (atexit(cleanup) != 0) {
				perror("atexit");
				exit(EXIT_FAILURE);
			}
		}
	}

	/* Add a new entry to the label list */
	labels[count].name = strdup(name);
	labels[count].addr = addr & 0xffff;
	count++;
	if (max_addr < addr) max_addr = addr;
	return NULL;
}

/* Returns a label's label_t* given its name.  Returns NULL on failure. */
label_t* label_by_name(char* name)
{
	int i;

	/* Scan the list */
	for (i = 0; i < count; i++)
		if (strcmp(labels[i].name, name) == 0)
			return &labels[i];
	return NULL;
}

/* Returns a label's label_t* given its address. */
label_t* label_by_addr(unsigned addr)
{
	int i;

	/* Scan the list */
	for (i = 0; i < count; i++)
		if (labels[i].addr == addr) return &labels[i];
	return NULL;
}

/* Returns the label_t* for the first label _before_or_at_ the given address. */
label_t* label_before_addr(unsigned addr)
{
	label_t* candidate = NULL;
	int i;

	/* Scan the list */
	for (i = 0; i < count; i++)
		if (labels[i].addr <= addr &&
		(candidate == NULL || candidate->addr < labels[i].addr))
			candidate = &labels[i];
	return candidate;
}

/* Returns the label_t* for the first label _after_ the given address. */
label_t* label_after_addr(unsigned addr)
{
	label_t* candidate = NULL;
	int i;

	if (addr >= max_addr) return NULL;

	/* Scan the list */
	for (i = 0; i < count; i++)
		if (labels[i].addr > addr &&
		(candidate == NULL || candidate->addr > labels[i].addr))
			candidate = &labels[i];
	return candidate;
}

/* Empties the label table. */
void label_reset(void)
{
	count = 0;
}

/* Loads a label map from a file. */
void label_load_map(char* filename)
{
	char* buf = NULL;
	int buf_capacity = 0;
	FILE* handle;
	char delims[] = " :\t\n";
	char* name, *addr, *dummy;

	/* First lose any previous label list */
	label_reset();

	/* Open the file */
	handle = fopen(filename, "r");
	if (handle == NULL) {
		perror("fopen");
		exit(EXIT_FAILURE);
	}

	/* Read the file line-by-line */
	while (1) {
		char* ret;

		if (getline(&buf, &buf_capacity, handle) == -1) {
			if (ferror(handle)) {
				perror("getline");
				exit(EXIT_FAILURE);
			} else if (feof(handle)) break;
		}

		name = strtok(buf, delims);
		if (name == NULL) continue;	/* Blank line */

		addr = strtok(NULL, delims);
		if (addr == NULL) die("Invalid map file\n");

		dummy = strtok(NULL, delims);
		if (dummy != NULL) die("Invalid map file\n");

		ret = label_add(name, strtoul(addr, NULL, 16));
		if (ret != NULL) die("%s\n", ret);
	}

	/* Done */
	free(buf);
	if (fclose(handle) == EOF) {
		perror("fclose");
		exit(EXIT_FAILURE);
	}
}

/* EOF */

