// Andrew Naplavkov

#ifndef STEP20_GENERATOR_HPP
#define STEP20_GENERATOR_HPP

#include <coroutine>
#include <iterator>
#include <memory>
#include <optional>

namespace step20 {

inline auto co_destroy = [](void* address) {
    std::coroutine_handle<>::from_address(address).destroy();
};

/// @see https://en.cppreference.com/w/cpp/header/generator
template <std::movable T>
struct generator : private std::unique_ptr<void, decltype(co_destroy)> {
    struct promise_type : std::optional<T> {
        std::suspend_never initial_suspend() { return {}; }
        void await_transform() = delete;
        void return_void() {}
        void unhandled_exception() { throw; }
        std::suspend_always final_suspend() noexcept { return {}; }

        std::suspend_always yield_value(T value)
        {
            this->emplace(std::move(value));
            return {};
        }

        generator get_return_object()
        {
            auto result = generator{};
            result.reset(handle::from_promise(*this).address());
            return result;
        }
    };

    using handle = std::coroutine_handle<promise_type>;

    struct iterator : private handle {
        using iterator_category = std::input_iterator_tag;
        using difference_type = std::ptrdiff_t;
        using value_type = T;
        using reference = const T&;
        explicit iterator(handle coro) : handle{coro} {}
        reference operator*() const { return *this->promise(); }
        bool operator==(std::default_sentinel_t) const { return this->done(); }
        void operator++(int) { this->resume(); }

        iterator& operator++()
        {
            this->resume();
            return *this;
        }
    };

    auto begin() const { return iterator{handle::from_address(get())}; }
    auto end() const { return std::default_sentinel; }
};

}  // namespace step20

#endif  // STEP20_GENERATOR_HPP