#include "hal/gpio.h"
#include "hal/intc.h"
#include "hal/interrupts.h"
#include "hal/pol0.h"
#include "hal/timer.h"
#include "lib/async.h"
#include "lib/buffer.h"

#include "uart.h"
#include "uart_async.h"

namespace {

constexpr uint32_t kTicksPerSecond = 100'000'000;
constexpr uint32_t kTicksPerMicrosecond = 100;
constexpr uint32_t kOverflowSeconds = (1ULL << 32) / kTicksPerSecond;
constexpr uint32_t kOverflowMicroseconds =
    ((1ULL << 32) % kTicksPerSecond) / kTicksPerMicrosecond;

Timer* timer0;
volatile uint32_t t1overflowsecs = 0;
volatile uint32_t t1overflowusecs = 0;

Gpio* gpio0;

void ToggleLed(int which) {
    uint8_t data = gpio0->data;
    data ^= (0x1 << which);
    gpio0->data = data;
}

void SetLed(int which) {
    uint8_t data = gpio0->data;
    data |= (0x1 << which);
    gpio0->data = data;
}

void ClearLed(int which) {
    uint8_t data = gpio0->data;
    data &= ~(0x1 << which);
    gpio0->data = data;
}

void Uart0Isr() { HandleUartIsr(); }

void Timer0Isr() {
    if (timer0->HasT1Overflowed()) {
        UartWriteCrash("t1 overflow\r\n");
        t1overflowsecs += kOverflowSeconds;
        t1overflowusecs += kOverflowMicroseconds;
        timer0->ClearT1Overflow();
        return;
    }
    SetLed(6);
    UartWriteCrash("blarg\r\n");
    __builtin_trap();
}

void SetupUart() {
    InitUarts();

    intc::SetIsr(UART0_IRQN, Uart0Isr);
    intc::SetIrqEnabled(UART0_IRQN, true);
}

void SetupTimer() {
    timer0 = Timer::Instance(TIMER0_BASE);
    timer0->SetupAsWdt(100'000 * 1000);
    timer0->EnableT1();

    intc::SetIsr(TIMER0_IRQN, Timer0Isr);
    intc::SetIrqEnabled(TIMER0_IRQN, true);
}

void SetupInterrupts() {
    intc::EnableInterrupts();
    SetExternalInterruptHandler(intc::InterruptHandler);
    EnableExternalInterrupts();
    EnableInterrupts(true);
}

async::task<> echo() {
    async::task<std::byte> reader = UartReadLoop();
    async::gimme<std::span<const std::byte>> feeder;
    async::task<> writer = UartWriteLoop(feeder);
    writer.h.resume();  // advance to first yield
    while (1) {
        SetLed(1);
        std::byte c = co_await reader;
        ClearLed(1);
        co_await feeder.feed(std::span{&c, 1});
    }
}

async::task<> blink() {
    while (1) {
        co_await async::delay(500);
        ToggleLed(0);
        timer0->Pet();

        co_await UartWrite(".");
    }
}

}  // namespace

int main() {
    gpio0 = Gpio::Instance(GPIO0_BASE);
    gpio0->data = 0;

    SetupUart();
    UartWriteBlocking("uart setup done\r\n");
    SetupTimer();
    UartWriteBlocking("timer setup done\r\n");

    SetupInterrupts();

    auto e = echo();
    auto b = blink();
    async::schedule(e.h);
    async::schedule(b.h);

    UartWriteBlocking("init done. starting main loop\r\n");

    async::main_loop([]() {
        return false;
    });
    // should never get here
}

/// stdlib stuff

#include <sys/time.h>

#include <cstdint>

#include "lib/lock.h"

extern unsigned char _heap_begin, _heap_end;

extern "C" void* _sbrk(int increment) {
    static unsigned char* heap = &_heap_begin;
    unsigned char* prev_heap = heap;
    if (heap + increment >= &_heap_end) {
        UartWriteCrash("Heap overflow!\r\n");
        return reinterpret_cast<void*>(-1);
    }
    heap += increment;
    return prev_heap;
}

extern "C" int _gettimeofday(struct timeval* tv, void* tzvp) {
    (void)tzvp;
    uint32_t ticks = timer0->GetT1Ticks();
    tv->tv_sec = ticks / kTicksPerSecond + t1overflowsecs;
    tv->tv_usec =
        (ticks % kTicksPerSecond) / kTicksPerMicrosecond + t1overflowusecs;

    return 0;
}

extern "C" uint8_t __atomic_exchange_1(volatile void* ptr, uint8_t val,
                                       int memorder) {
    (void)memorder;
    auto* dest = reinterpret_cast<volatile uint8_t*>(ptr);
    bool ret;

    {
        InterruptLock lock;
        ret = *dest;
        *dest = val;
    }

    return ret;
}
