#include <coroutine>
#include <cstdint>
#include <exception>
#include <fmt/core.h>


template<typename T>
struct Generator {
  struct promise_type;
  using handle_type = std::coroutine_handle<promise_type>;

  struct promise_type {
    T value_;
    std::exception_ptr exception_;

    Generator get_return_object() {
      return Generator(handle_type::from_promise(*this));
    }
    std::suspend_always initial_suspend() { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    void unhandled_exception() { exception_ = std::current_exception(); }

    template<std::convertible_to<T> From>
    std::suspend_always yield_value(From&& from) {
      value_ = std::forward<From>(from);
      return {};
    }
    void return_void() {}
  };

  handle_type h_;

  Generator(handle_type h) : h_(h) {}
  ~Generator() { h_.destroy(); }
  explicit operator bool() {
    fill();
    return !h_.done();
  }

  T operator()() {
    fill();
    full_ = false;
    return std::move(h_.promise().value_);
  }

private:
  bool full_ = false;

  void fill() {
    if(!full_) {
      h_();
      if(h_.promise().exception_) {
        std::rethrow_exception(h_.promise().exception_);
      }

      full_ = true;
    }
  }
};

Generator<std::uint64_t>
fib(unsigned n) {
  if(n == 0) co_return;
  if(n > 94) {
    throw std::runtime_error("Too big");
  }

  if(n == 1) co_return;

  co_yield 1;

  if(n == 2) co_return;

  std::uint64_t a = 0;
  std::uint64_t b = 1;
  for(unsigned i = 2; i < n; ++i) {
    std::uint64_t s = a + b;
    co_yield s;
    a = b;
    b = s;
  }
}

int main() {
  try {
    auto gen = fib(50);
    for(int j = 0; gen; ++j) {
      fmt::println("fib({})={}", j, gen());
    }
  } catch(const std::exception& ex) {
    fmt::println("Exception: {}", ex.what());
  } catch(...) {
    fmt::println("Unknown exception");
  }

  return 0;
}
