/*
Autor: Ada Jacyna
Temat: std::coroutine

Współprogramy (ang. coroutine) to funkcje, które mogą zostać zawieszone w trakcie wykonywania a potem kontynuowane
bez blokowania wątku (nie posługują się stackiem). Służą do np. odciążania pamięci.

Funkcja jest korutyną jeżeli stosuje jedną z poniższych funkcjonalności:
    - co_await  -   wstrzymanie wykonania aż do wznowienia
    - co_yield  -   wstrzymanie wykonania aby zwrócić daną wartość
    - co_return -   zakończenie wykonania aby zwrócić wartość

Każda korytuna wymaga użycia objektu *promise*, którym manipuluje wewnątrz siebie. Za jego pomocą podaje swoje
wyniki. W standardzie C++20 utworzenie tej funkcjonalności jest konieczne.

Działanie przykładu:
W funkcji main() wołana jest funkcja split_string typu StringGenerator. Tworzony jest obiekt tego typu,
initial_suspend zapewnia, że kod jeszcze nie rusza.
Pętla while woła kolejno next(), które pozwala na wycinanie kolejnych słów.
Póki uchwyt do korutyny nie ma stanu done(), słowa są wypisywane

Ścieżka działania: Main -> next() -> Splitter -> Main -> next() -> (resume) Splitter ...


Źródła:
    https://en.cppreference.com/w/cpp/language/coroutines
    https://stackoverflow.com/questions/71153205/c-coroutine-when-how-to-use
    https://docs.microsoft.com/pl-pl/cpp/cpp/coroutines-cpp

    Kompilacja (GCC/Clang): g++ -std=c++20 -fcoroutines coroutine_example.cpp

*/

#include <iostream>
#include <coroutine>

class StringGenerator
{
public:
    // Definicja 'promise' jest wymagana przez kompilator (odniesienie do linku 1)
    struct promise_type
    {
        std::string current_word;
        StringGenerator get_return_object()
        {
            return StringGenerator{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        // Upewnienie się, że wstrzymujemy pracę przed startem,
        std::suspend_always initial_suspend() { return {}; }

        // Zatrzymanie na końcu programu by sprzątnąć
        std::suspend_always final_suspend() noexcept { return {}; }

        // Funkcja wywoływana przy wołaniu co_yield ...
        std::suspend_always yield_value(std::string value)
        {
            current_word = value;
            return {};
        }

        void return_void() {}
        void unhandled_exception()
        {
            std::terminate();
        }
    };
    using handle_type = std::coroutine_handle<promise_type>;

    explicit StringGenerator(handle_type handle) : coroutine_handle_{handle} {}
    ~StringGenerator()
    {
        if (coroutine_handle_)
            coroutine_handle_.destroy();
    }
    StringGenerator(const StringGenerator &) = delete;
    StringGenerator &operator=(const StringGenerator &) = delete;

    StringGenerator(StringGenerator &&other) noexcept : coroutine_handle_{other.coroutine_handle_}
    {
        other.coroutine_handle_ = nullptr;
    }

    bool next()
    {
        if (!coroutine_handle_ || coroutine_handle_.done())
            return false;
        coroutine_handle_.resume();
        return !coroutine_handle_.done();
    }

    std::string value() const
    {
        return coroutine_handle_.promise().current_word;
    }

private:
    handle_type coroutine_handle_;
};

StringGenerator split_string(std::string text, char delimiter)
{
    size_t start = 0;
    size_t end = text.find(delimiter);

    while (end != std::string::npos)
    {
        // Wycinamy słowa po kolei
        std::string word = text.substr(start, end - start);

        // Tu wywołana będzie funkcja yield_value, funkcja zostaje zatrzymana
        co_yield word;

        // Po wznowieniu, wywołany będzie next() i zaczyna kod od tego momentu
        start = end + 1;
        end = text.find(delimiter, start);
    }
    // Obsługa ostatniego słowa
    co_yield text.substr(start);
}

int main()
{
    std::string sentence = "Coroutine example is written right here\n";
    char separator = ' ';
    std::cout << "Splitting text...\n";

    StringGenerator splitter = split_string(sentence, separator);

    int count = 1;
    // Jeżeli byłby break, to kolejne słowa nigdy nie zostaną sparsowane --> oszczędność zasobów
    while (splitter.next())
    {
        std::cout << "Word " << count++ << ": " << splitter.value() << "\n";
        // break;
    }

    std::cout << "End\n";
}