#include <stdint.h>
#include <stm32g0xx.h>
#include "task.h"


#define TASK_INITIAL_XPSR       (0x21000000UL)
#define TASK_CONTEXT_SWITCH()   { SCB->ICSR = (1 << SCB_ICSR_PENDSVSET_Pos); }
#define TASK_STACK_FILL_VALUE   (0xDEADBEEF)


typedef enum {
    TASK_FLAG_NONE  = 0,
    TASK_FLAG_READY = (1 << 0),
    TASK_FLAG_RESET = (1 << 1),
} task_flags_t;


typedef struct {
    uint32_t initial_pc;
    uint32_t initial_sp;
    uint32_t sp;
    task_flags_t flags;
} task_t;


static task_t task_table[__TASK_ID_MAX];
static volatile task_id_t task_current = 0;


static void task_exit (void) {
    task_table[task_current].flags = TASK_FLAG_NONE;
    task_yield();
    while (1);
}

static void task_initialize (task_id_t id) {
    task_t *task = &task_table[id];
    uint32_t *sp = ((uint32_t *) (task->initial_sp));
    *--sp = TASK_INITIAL_XPSR;
    *--sp = task->initial_pc;
    *--sp = ((uint32_t) (task_exit));
    for (int i = 0; i < 13; i++) {
        *--sp = 0;
    }
    task->sp = ((uint32_t) (sp));
}

static void task_reset (task_id_t id) {
    task_table[id].flags &= ~(TASK_FLAG_RESET);
    task_initialize(id);
}

static uint32_t task_switch_context (uint32_t sp) {
    task_table[task_current].sp = sp;

    for (task_id_t id = 0; id < __TASK_ID_MAX; id++) {
        if (task_table[id].flags & TASK_FLAG_READY) {
            task_current = id;
            break;
        }
    }

    if (task_table[task_current].flags & TASK_FLAG_RESET) {
        task_reset(task_current);
    }

    return task_table[task_current].sp;
}


void task_create (task_id_t id, void (*code)(void), void *stack, size_t stack_size) {
    if (id < __TASK_ID_MAX) {
        for (size_t i = 0; i < stack_size; i += sizeof(uint32_t)) {
            (*(uint32_t *) (stack + i)) = TASK_STACK_FILL_VALUE;
        }
        task_t *task = &task_table[id];
        task->initial_pc = (uint32_t) (code);
        task->initial_sp = (((uint32_t) (stack)) + stack_size);
        task->flags = TASK_FLAG_READY;
        task_initialize(id);
    }
}

void task_yield (void) {
    task_table[task_current].flags &= ~(TASK_FLAG_READY);
    TASK_CONTEXT_SWITCH();
}

void task_set_ready (task_id_t id) {
    task_table[id].flags |= TASK_FLAG_READY;
    TASK_CONTEXT_SWITCH();
}

void task_set_ready_and_reset (task_id_t id) {
    task_table[id].flags |= (TASK_FLAG_RESET | TASK_FLAG_READY);
    TASK_CONTEXT_SWITCH();
}

size_t task_get_stack_usage (void *stack, size_t stack_size) {
    for (size_t i = 0; i < stack_size; i += sizeof(uint32_t)) {
        if ((*(uint32_t *) (stack + i)) != TASK_STACK_FILL_VALUE) {
            return (stack_size - i);
        }
    }
    return 0;
}

__attribute__((naked)) void task_scheduler_start (void) {
    uint32_t sp = task_table[task_current].sp;

    NVIC_SetPriority(PendSV_IRQn, 3);

    asm volatile (
        "add %[sp], #32 \n"
        "msr psp, %[sp] \n"
        "movs r0, #2 \n"
        "msr CONTROL, r0 \n"
        "isb \n"
        "pop {r0-r5} \n"
        "mov lr, r5 \n"
        "pop {r3} \n"
        "pop {r2} \n"
        "cpsie i \n"
        "bx r3 \n"
        :: [sp] "r" (sp)
    );

    while (1);
}


__attribute__((naked)) void PendSV_Handler (void) {
    asm volatile (
        "mrs r1, psp \n"
        "sub r1, r1, #32 \n"
        "mov r0, r1 \n"
        "stmia r1!, {r4-r7} \n"
        "mov r4, r8 \n"
        "mov r5, r9 \n"
        "mov r6, r10 \n"
        "mov r7, r11 \n"
        "stmia r1!, {r4-r7} \n"
        "push {lr} \n"
        "cpsid i \n"
        "blx %[task_switch_context] \n"
        "cpsie i \n"
        "pop {r2} \n"
        "add r0, #16 \n"
        "ldmia r0!, {r4-r7} \n"
        "mov r8, r4 \n"
        "mov r9, r5 \n"
        "mov r10, r6 \n"
        "mov r11, r7 \n"
        "msr psp, r0 \n"
        "sub r0, #32 \n"
        "ldmia r0!, {r4-r7} \n"
        "bx r2 \n"
        :: [task_switch_context] "r" (task_switch_context)
    );
}
