template <typename T, std::size_t N>
struct queue {
    std::array<T, N> data;
    volatile std::size_t put_idx;
    volatile std::size_t get_idx;

    async_flag put_flag;
    async_flag get_flag;

    constexpr std::size_t _inc(std::size_t n) {
        return n + 1 < N ? n + 1 : 0;
    }

    bool empty() {
        return put_idx == get_idx;
    }

    bool full() {
        return _inc(put_idx) == get_idx;
    }

    bool put(T v) {
        if(full()) {
            return false;
        }

        data[put_idx] = v;
        put_idx = _inc(put_idx);
        put_flag.set();
        return true;
    }

    bool get(T& v) {
        if(empty()) {
            return false;
        }

        v = data[get_idx];
        get_idx = _inc(get_idx);
        get_flag.set();
        return true;
    }

    async<> async_put(T v) {
        while(!put(v)) {
            co_await get_flag;
        }
    }

    async<T> async_get() {
        T v;
        while(!get(v)) {
            co_await put_flag;
        }
        co_return v;
    }
};

queue<char, 8> uart1_rx;
queue<char, 8> uart1_tx;

template <>
void interrupt::handler<interrupt::irq::USART1>() {
    if(USART1.reg.SR & (1 << 5)) { // RXNE
        char c = USART1.reg.DR;
        //printf("USART1 ISR RX: %c\n", c);
        uart1_rx.put(c);
    }

    if(USART1.reg.SR & (1 << 7)) { // TXE
        char c;
        if(uart1_tx.get(c)) {
            USART1.reg.DR = c;
        } else {
            USART1.reg.CR1 &= ~(1 << 7); // TXEIE
        }
    }
}

struct shell_io_t {
    async<char> getchar() {
        return uart1_rx.async_get();
    }

    async<> putchar(char c) {
        co_await uart1_tx.async_put(c);
        USART1.reg.CR1 |= 1 << 7; // TXEIE
    }

    async<> print(std::string_view s) {
        for(auto c : s) {
            co_await putchar(c);
        }
    }

    template<typename ...T>
    async<> print(T... args) {
        auto s = std::format(args...);
        co_await print(std::string_view(s));
    }
};

task shell_task(shell_io_t& io, std::span<shell_cmd> cmds) {
    std::array<char, 80> buf;
    std::size_t idx = 0;

    while(1) {
        char c = co_await io.getchar();

        if(c >= 0x20) {
            if(idx >= buf.size()) {
                continue;
            }
            buf[idx++] = c;
            co_await io.putchar(c);
        }

        if(c == '\r') {
            if(!idx) {
                continue;
            }

            co_await io.putchar('\n');

            auto [name, args] = split_first(std::string_view(buf.data(), idx));

            bool found = false;

            for(auto cmd : cmds) {
                if(name != cmd.name) {
                    continue;
                }

                co_await cmd.cmd(io);
                found = true;
                break;
            }

            if(!found) {
                buf[name.size()] = 0;
                co_await io.print("No such command: {}\n", name);
            }

            idx = 0;
        }
    }
}

shell_cmd shell_cmds[] = {
    {
        "foo",
        [](auto io) -> async<> {
            co_await io.print("Hello world!\n");
        },
    },
};