#include "uart.h"

#include "async.h"
#include "gpio.h"
#include "lock.h"
#include "ring_buffer.h"
#include "trace.h"
#include "uart_async.h"
#include "xuartlite.h"

namespace {
using async::AwaitableType;

constexpr uintptr_t kUart0BaseAddress = 0x40001000;
XUartLite uart0_inst;
XUartLite_Config uart0_config = {
    .DeviceId = 0,
    .RegBaseAddr = kUart0BaseAddress,
    .BaudRate = 115200,
    .UseParity = false,
    .DataBits = 8,
};

constexpr size_t kUartTxBufferSize = 256;
std::array<std::byte, kUartTxBufferSize> tx_buffer = {};
RingBuffer tx_ring_buffer{.buffer = tx_buffer};

XUartLite* uart0 = &uart0_inst;
}  // namespace

void InitUarts() {
    XUartLite_CfgInitialize(uart0, &uart0_config, uart0_config.RegBaseAddr);

    XUartLite_SetSendHandler(uart0, HandleUartTxFromIsr, nullptr);
    XUartLite_SetRecvHandler(uart0, HandleUartRxFromIsr, nullptr);
    XUartLite_EnableInterrupt(uart0);
}

// xuartlite private header stuff
extern "C" uint8_t XUartLite_GetSR(XUartLite* InstancePtr);
#define XUL_SR_TX_FIFO_EMPTY 0x04 /* transmit FIFO empty */

void UartWriteCrash(std::span<const std::byte> data) {
    while (data.size() > 0) {
        while ((XUartLite_GetSR(uart0) & XUL_SR_TX_FIFO_EMPTY) == 0) {
        }
        auto* dat =
            reinterpret_cast<uint8_t*>(const_cast<std::byte*>(data.data()));
        uint8_t sent = XUartLite_Send(uart0, dat, data.size());
        data = data.subspan(sent);
    }
    while ((XUartLite_GetSR(uart0) & XUL_SR_TX_FIFO_EMPTY) == 0) {
    }
}

async::task<> UartWrite(std::span<const std::byte> data) {
    while (!tx_ring_buffer.Store(data)) {
        tracing::trace(tracing::TraceEvent::kUartTxBufferFull);
        co_await async::await(AwaitableType::kUartTx);
        tracing::trace(tracing::TraceEvent::kUartTxBufferNotFull);
    }

    {
        InterruptLock lock;
        if (!XUartLite_IsSending(uart0)) {
            tracing::trace(tracing::TraceEvent::kUartSend);
            XUartLite_Send(uart0, tx_ring_buffer.RawReadPointer(),
                           tx_ring_buffer.ContiguousAvailableData());
        }
    }
}

#define GCC_HAS_BUG_101133 0  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101133
#if !GCC_HAS_BUG_101133
async::task<> UartWriteLoop(async::gimme<std::span<const std::byte>>& data_gen) {
    while (1) {
        auto data = co_await data_gen;
        while (!tx_ring_buffer.Store(data)) {
            tracing::trace(tracing::TraceEvent::kUartTxBufferFull);
            co_await async::await(AwaitableType::kUartTx);
            tracing::trace(tracing::TraceEvent::kUartTxBufferNotFull);
        }

        {
            InterruptLock lock;
            if (!XUartLite_IsSending(uart0)) {
                tracing::trace(tracing::TraceEvent::kUartSend);
                XUartLite_Send(uart0, tx_ring_buffer.RawReadPointer(),
                        tx_ring_buffer.ContiguousAvailableData());
            }
        }
    }
}
#endif  // !GCC_HAS_BUG_101133

void UartReadBlocking(std::span<std::byte> data) {
    size_t bytes_received = 0;
    while (bytes_received < data.size()) {
        auto* buffer = reinterpret_cast<uint8_t*>(data.data() + bytes_received);
        tracing::trace(tracing::TraceEvent::kUartRecv);
        bytes_received +=
            XUartLite_Recv(uart0, buffer, data.size() - bytes_received);
    }
}

void UartWriteBlocking(std::span<const std::byte> data) {
    if (__get_PRIMASK() != 0) {
        UartWriteCrash("\r\nUartWriteBlocking called with interupts disabled!\r\n");
        __builtin_trap();
    }

    while (!tx_ring_buffer.Store(data)) {
    }

    {
        InterruptLock lock;
        if (!XUartLite_IsSending(uart0)) {
            tracing::trace(tracing::TraceEvent::kUartSend);
            XUartLite_Send(uart0, tx_ring_buffer.RawReadPointer(),
                           tx_ring_buffer.ContiguousAvailableData());
        }
    }
}

async::task<uint8_t> UartReadLoop() {
    uint8_t c;
    while (1) {
        tracing::trace(tracing::TraceEvent::kUartRecv);
        size_t received = XUartLite_Recv(uart0, &c, 1);
        // some data may already be in the fifo, but if not, wait:
        if (received < 1) {
            co_await async::await(AwaitableType::kUartRx);
        }

        co_yield c;
    }
}

async::task<buffer> UartRead(int size) {
    auto buff = buffer::make(size);
    auto* data = reinterpret_cast<uint8_t*>(buff.data.data());
    tracing::trace(tracing::TraceEvent::kUartRecv);
    size_t received = XUartLite_Recv(uart0, data, buff.data.size());
    // some data may already be in the fifo, but if not, wait:
    if (received < buff.data.size()) {
        co_await async::await(AwaitableType::kUartRx);
    }
    co_return buff;
}

void HandleUartTxFromIsr(void*, unsigned int transmitted) {
    tx_ring_buffer.Pop(transmitted);
    if (tx_ring_buffer.AvailableData() > 0) {
        tracing::trace(tracing::TraceEvent::kUartSend);
        XUartLite_Send(uart0, tx_ring_buffer.RawReadPointer(),
                       tx_ring_buffer.ContiguousAvailableData());
    }
    async::resume(AwaitableType::kUartTx);
}

void HandleUartRxFromIsr(void*, unsigned int) {
    async::resume(AwaitableType::kUartRx);
}

void HandleUartIsr() { XUartLite_InterruptHandler(uart0); }

extern "C" uint8_t XUartLite_GetSR(XUartLite*);

uint8_t UartStatus() { return XUartLite_GetSR(uart0); }

void LogStuff() {
    uint8_t data = gpio0->data;
    data |= (uart0->ReceiveBuffer.RemainingBytes & 0xf) << 4;
    gpio0->data = data;
}
