#pragma once

#include "cesium-async++.h"

#include <spdlog/spdlog.h>

namespace CesiumAsync {
namespace CesiumImpl {

template <typename TScheduler> class ImmediateScheduler {
public:
  explicit ImmediateScheduler(TScheduler* pScheduler) noexcept
      : _pScheduler(pScheduler) {}

  void schedule(async::task_run_handle t) {
    // Are we already in a suitable thread?
    std::vector<TScheduler*>& inSuitable =
        ImmediateScheduler<TScheduler>::getSchedulersCurrentlyDispatching();
    if (std::find(inSuitable.begin(), inSuitable.end(), this->_pScheduler) !=
        inSuitable.end()) {
      // Yes, run this task directly.
      t.run();
    } else {
      // No, schedule this task with the deferred scheduler.
      this->_pScheduler->schedule(std::move(t));
    }
  }

  class SchedulerScope {
  public:
    SchedulerScope(TScheduler* pScheduler = nullptr) : _pScheduler(pScheduler) {
      if (this->_pScheduler) {
        std::vector<TScheduler*>& inSuitable =
            ImmediateScheduler<TScheduler>::getSchedulersCurrentlyDispatching();
        inSuitable.push_back(this->_pScheduler);
      }
    }

    ~SchedulerScope() noexcept { this->reset(); }

    SchedulerScope(SchedulerScope&& rhs) noexcept
        : _pScheduler(rhs._pScheduler) {
      rhs._pScheduler = nullptr;
    }

    SchedulerScope& operator=(SchedulerScope&& rhs) noexcept {
      std::swap(this->_pScheduler, rhs._pScheduler);
      return *this;
    }

    void reset() noexcept {
      if (this->_pScheduler) {
        std::vector<TScheduler*>& inSuitable =
            ImmediateScheduler<TScheduler>::getSchedulersCurrentlyDispatching();
        assert(!inSuitable.empty());
        assert(inSuitable.back() == this->_pScheduler);
        inSuitable.pop_back();

        this->_pScheduler = nullptr;
      }
    }

    SchedulerScope(const SchedulerScope&) = delete;
    SchedulerScope& operator=(const SchedulerScope&) = delete;

  private:
    TScheduler* _pScheduler;
  };

  SchedulerScope scope() { return SchedulerScope(this->_pScheduler); }

private:
  TScheduler* _pScheduler;

  // If a TScheduler instance is found in this thread-local vector, then the
  // current thread has been dispatched by this scheduler and therefore we can
  // dispatch immediately.
  static std::vector<TScheduler*>&
  getSchedulersCurrentlyDispatching() noexcept {
    // We're using a static local here rather than a static field because, on
    // at least some Linux systems (mine), with Clang 12, in a Debug build, a
    // thread_local static field causes a SEGFAULT on access.
    // I don't understand why (despite hours trying), but making it a static
    // local instead solves the problem and is arguably cleaner, anyway.
    static thread_local std::vector<TScheduler*> schedulersCurrentlyDispatching;
    return schedulersCurrentlyDispatching;
  }
};

} // namespace CesiumImpl
} // namespace CesiumAsync
