#include <stdbool.h>

#include "interrupts.h"
#include "interface.h"
#include "events.h"
#include "mem.h"
#include "scheduler.h"
#include "communication_interface.h"

static uint64_t enabled_interrupts[2] = { 0, 0 };
static volatile uint64_t triggered_interrupts[2] = { 0, 0 };

static volatile uint32_t pin_status = 0;

void real_isr(uint16_t pin, enum InterruptMode mode)
{
	switch (mode) {
		case Change_c: ;
			bool val = read_dpin(pin);
			pin_status = pin_status | (uint32_t) val << pin;
			break;
		case Rising_c:
			pin_status = pin_status | 1u << pin;
			break;
		case Falling_c:
			pin_status = pin_status | 0u << pin;
			break;
		case Low_c:
			pin_status = pin_status | 0u << pin;
			break;
		case High_c:
			pin_status = pin_status | 1u << pin;
			break;
	}

	SET_BIT(triggered_interrupts, pin, mode);

	force_wake_up();
}

void enable_interrupt(uint16_t pin, enum InterruptMode mode)
{
	// Check if the interrupt was already enabled
	if (GET_BIT(enabled_interrupts, pin, mode) != 0) {
		return;
	}

	// Check if there is another type of interrupt attached to this pin
	bool overlap = false;
	for (enum InterruptMode m = Change_c; m <= High_c; ++m) {
		if (m == mode) {
			continue;
		}

		if (GET_BIT(enabled_interrupts, pin, m) != 0) {
			overlap = true;
			break;
		}
	}

	void (*isr)() = get_isr(pin, mode);

	if (isr == NULL || overlap) {
		send_message((struct MTMessageFro) {
			.cons = MTFException_c,
			.data = {
				.MTFException = {
					.cons = MTEUnsupportedInterrupt_c,
					.data = { .MTEUnsupportedInterrupt = {
							  .f0 = pin,
							  .f1 = mode }
						}
					}
				}
			});
		return;
	}

	attach_interrupt(isr, pin, mode);
	SET_BIT(enabled_interrupts, pin, mode)
}

void disable_interrupt(uint16_t pin, enum InterruptMode mode)
{
	// Check if it is safe to disable the interrupt
	struct TaskTree *e = event_list_head();
	uint16_t event_type = pin * 5 + mode;
	while (e != NULL) {
		if (e->data.event.event_type == event_type) {
			return;
		}
		e = event_list_next(e);
	}

	detach_interrupt(pin);
	CLEAR_BIT(enabled_interrupts, pin, mode)
}

bool pending_interrupts()
{
	return triggered_interrupts[0] != 0 || triggered_interrupts[1] != 0;
}

bool active_interrupts()
{
	return enabled_interrupts[0] != 0 || enabled_interrupts[1] != 0;
}

static void store_in_task_tree(struct TaskTree *t, uint16_t pin,
			       enum InterruptMode mode)
{
	uint16_t *stack = mem_stack();
	*stack++ = MT_NULL;
	*stack++ = mem_rptr(t);
	while (*--stack != MT_NULL) {
		struct TaskTree *ctree = mem_cast_tree(*stack);
		switch (ctree->task_type) {
			case BCStep_c:
				*stack++ = mem_rptr(ctree->data.step.lhs);
				break;
			case BCStepStable_c:
				*stack++ = mem_rptr(ctree->data.steps.lhs);
				break;
			case BCStepUnstable_c:
				*stack++ = mem_rptr(ctree->data.stepu.lhs);
				break;
			case BCSeqStable_c:
				*stack++ = mem_rptr(ctree->data.seqs.lhs);
				*stack++ = mem_rptr(ctree->data.seqs.rhs);
				break;
			case BCSeqUnstable_c:
				*stack++ = mem_rptr(ctree->data.sequ.lhs);
				*stack++ = mem_rptr(ctree->data.sequ.rhs);
				break;
			case BCRepeat_c:
				if (ctree->data.repeat.tree != NULL)
					*stack++ = mem_rptr(
						ctree->data.repeat.tree);
				break;
			case BCTOr_c:
				*stack++ = mem_rptr(ctree->data.tor.lhs);
				*stack++ = mem_rptr(ctree->data.tor.rhs);
				break;
			case BCTAnd_c:
				*stack++ = mem_rptr(ctree->data.tand.lhs);
				*stack++ = mem_rptr(ctree->data.tand.rhs);
				break;
			case BCSdsSet_c:
				*stack++ = mem_rptr(ctree->data.sdsset.data);
				break;
			case BCSdsUpd_c:
				*stack++ = mem_rptr(ctree->data.sdsupd.ctx);
				break;
			case BCInterrupt_c:
				if (ctree->data.interrupt.mode == mode &&
					ctree->data.interrupt.pin == pin) {
					ctree->data.interrupt.triggered = true;
					ctree->data.interrupt.status = pin_status & 1u << pin;
				}
				break;
			default:
				break;
		}
	}
}

static struct MTask *update_task(uint16_t id, uint16_t pin,
				 enum InterruptMode mode)
{
	struct MTask *t = mem_task_head();
	while (t != NULL) {
		if (t->taskid != id) {
			t = mem_task_next(t);
			continue;
		}

		t->execution_max = 0;
		t->execution_min = 0;
		store_in_task_tree(t->tree, pin, mode);
		return t;
	}

	return NULL;
}

void process_interrupts()
{
	if (!pending_interrupts()) {
		return;
	}

	for (uint16_t pin = 0; pin <= MAX_INTERRUPT_PIN; pin++) {
		for (enum InterruptMode mode = Change_c; mode <= High_c;
		     mode++) {
			if (GET_BIT(triggered_interrupts, pin, mode)) {
				CLEAR_BIT(triggered_interrupts, pin, mode)

				msg_debug(
					SC("interrupt triggered on pin: %u with mode: %u\n"),
					pin, mode);

				struct TaskTree *event = event_list_head();

				while (event != NULL) {
					if (event->data.event.event_type !=
					    pin * 5 + mode) {
						event = event_list_next(event);
						continue;
					}

					struct MTask *t = update_task(
						event->data.event.task_id, pin,
						mode);
					queue_remove_task(
						event->data.event.task_id);
					queue_insert_task(t);

					remove_event(event);
					event = event_list_next(event);
				}

				disable_interrupt(pin, mode);
			}
		}
	}
}
