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

#include "gate.h"
#include "error.h"

#define GATE_MAX   1024
#define INPUT_MAX  1024
#define OUTPUT_MAX 1024

static size_t gate_count;
static size_t input_count;
static struct gate gates[GATE_MAX];
static struct gate inputs[INPUT_MAX];

int
count_guard(int c, int max, char *desc) {
	if (c >= max) {
		emit_error("Internal: too many %s\n", desc);
		return 1;
	}
	return 0;
}

int
gate_add_generic(struct gate *array, size_t array_index, char *name, enum BINARY (*operation)(enum BINARY, enum BINARY), struct gate *in1, struct gate *in2) {
	struct gate g;

	if (name == NULL) {
		g.name = NULL;
	} else {
		g.name = strdup(name);
		if (g.name == NULL) {
			emit_error("strdup failed");
			return 1;
		}
	}
	g.operation = operation;
	g.in1 = in1;
	g.in2 = in2;

	array[array_index] = g;

	return 0;
}

struct gate*
gate_get_input_by_name(char *name) {
	struct gate *res = NULL;
	size_t i = 0;

	for (i = 0; i < input_count; i++) {
		if (inputs[i].name == NULL) {
			emit_error("input at index %zd has NULL name, ignoring", i);
			continue;
		}
		if (strcmp(inputs[i].name, name) == 0) {
			res = &inputs[i];
			break;
		}
	}

	return res;
}

int
gate_input_add(char *name) {
	int res = 0;
	if (count_guard(input_count, INPUT_MAX, "inputs")) {
		return 1;
	}

	if (gate_get_input_by_name(name) != NULL) {
		emit_error("Already an input called \"%s\"!\n", name);
		return 1;
	}

	res = gate_add_generic(inputs, input_count, name, logic_and, NULL, NULL);
	input_count++;
	return res;
}

int
gate_add(char *name, enum BINARY (*operation)(enum BINARY, enum BINARY), struct gate *in1, struct gate *in2) {
	if (count_guard(gate_count, GATE_MAX, "gates")) {
		return 1;
	}

	gate_add_generic(gates, gate_count, name, operation, in1, in2);
	gate_count++;

	return 0;
}

void
gate_free_all() {
	size_t i = 0;
	for (i = 0; i < gate_count; i++) {
		if (gates[i].name != NULL) {
			free(gates[i].name);
		}
	}
}

int
tick(void) {
	/* FIXME */
	return 1;
}

void
gate_init(void) {
	gate_count = 0;
	input_count = 0;
	memset(gates, 0, sizeof(gates));
	memset(inputs, 0, sizeof(inputs));
}

void
gate_dump(void) {
	size_t i = 0;

	emit_info("Gates:\n");
	for (i = 0; i < gate_count; i++) {
		emit_info("gate '%s': %s\n", gates[i].name, gates[i].output == LOGIC_HIGH ? "HIGH" : "LOW");
	}
}

int
gate_set_input(char *name, enum BINARY value) {
	struct gate *g = NULL;
	
	if (NULL == (g = gate_get_input_by_name(name))) {
		emit_error("No such input %s", name);
		return 1;
	}

	g->output = value;

	return 0;
}

void gate_update(void) {
	size_t i = 0;
	struct gate *g = NULL;
	enum BINARY in1 = LOGIC_LOW;
	enum BINARY in2 = LOGIC_LOW;

	/* FIXME should be input->output flow path */
	/* FIXME data flow doesn't matter yet because gate chaining isn't allowed */

	for (i = 0; i < gate_count; i++) {
		g = &gates[i];
		emit_error("Name: '%s'\n", g->name);
		in1 = g->in1->output;
		in2 = g->in2->output;
		g->output = (g->operation)(in1, in2);
	}
}