#include <cstdint>

#include "xuartlite.h"

struct Gpio {
    volatile uint32_t data;
};

#define gpio0 ((Gpio*)0x40000000)

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

XUartLite* uart0 = &uart0_inst;

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

uint8_t UartRead() {
    uint8_t c;
    while (XUartLite_Recv(uart0, &c, 1) < 1) {
    }
    return c;
}

void UartWrite(uint8_t c) { XUartLite_Send(uart0, &c, 1); }

uint32_t UartRead32() {
    uint32_t val = 0;

    // little endian
    val |= (UartRead() << 0);
    val |= (UartRead() << 8);
    val |= (UartRead() << 16);
    val |= (UartRead() << 24);

    return val;
}
}  // namespace

int main() {
    gpio0->data = 1;

    InitUarts();

    while (1) {
        uint8_t c = UartRead();
        if (c == 'c') {
            uint32_t addr = UartRead32();
            gpio0->data = 0xb0;
            uint32_t bytes = UartRead32();
            gpio0->data = 0x30;

            uint8_t* start = reinterpret_cast<uint8_t*>(addr);
            uint8_t* end = reinterpret_cast<uint8_t*>(addr + bytes);
            for (uint8_t* ptr = start; ptr < end; ptr++) {
                *ptr = UartRead();
            }

            UartWrite('a');
            UartWrite(bytes);
            gpio0->data = 0xf0;
        } else if (c == 'j') {
            uint32_t addr = UartRead32();

            gpio0->data = 0x55;

            auto jump = reinterpret_cast<void (*)()>(addr);
            jump();
        }
    }
}