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

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <atomic>
#include <chrono>
#include <semaphore>
#include <thread>

using namespace ::testing;

namespace {
std::atomic<bool> terminate;
std::counting_semaphore<12345> got_stuff(0);

buffer rx;

using namespace std::literals::chrono_literals;
}

std::string drain_n(size_t n, std::chrono::system_clock::duration timeout) {
    std::string txtout;
    auto begin = std::chrono::system_clock::now();

    while (std::chrono::system_clock::now() < begin + timeout) {
        auto out = FakeUart_Drain(n);
        if (out.data.empty()) {
            continue;
        }

        auto strout = std::string_view(reinterpret_cast<char*>(out.data.data()),
                                       out.data.size());
        n -= out.data.size();
        txtout.append(strout);
        if (n < 1) {
            return txtout;
        }
    }
    return "";
}

TEST(Uart, BasicSend) {
    FakeUart_Reset();
    terminate = false;

    constexpr std::string_view kTestData = "blarg";
    auto buff = std::as_bytes(std::span{kTestData});

    async::schedule(UartWrite(buff).h);
    std::thread t(
        []() { async::main_loop([]() -> bool { return terminate; }); });

    std::string strout = drain_n(kTestData.size(), 1s);
    EXPECT_EQ(strout, kTestData);

    terminate = true;
    t.join();
}

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

TEST(Uart, Echo) {
    FakeUart_Reset();
    terminate = false;

    constexpr std::string_view kTestData = "blargblaektrkblalasrjkh1!!";
    auto buff = std::as_bytes(std::span{kTestData});

    async::schedule(test_echo().h);
    std::thread t(
        []() { async::main_loop([]() -> bool { return terminate; }); });

    for (int j = 0; j < 100; j++) {
        FakeUart_Feed(buff);
        std::string txtout = drain_n(kTestData.size(), 1s);

        ASSERT_EQ(txtout, kTestData);
    }

    terminate = true;
    t.join();
}