#pragma once

namespace Emulator {

struct Scheduler {
  enum class Mode : uint {
    Run,
    SynchronizeMaster,
    SynchronizeSlave,
  };

  enum class Event : uint {
    Step,
    Frame,
    Synchronize,
  };

  inline auto synchronizing() const -> bool { return _mode == Mode::SynchronizeSlave; }

  auto reset() -> void {
    _host = co_active();
    _threads.reset();
  }

  auto primary(Thread& thread) -> void {
    _master = _resume = thread.handle();
  }

  auto append(Thread& thread) -> bool {
    if(_threads.find(&thread)) return false;
    thread._clock += _threads.size();  //this bias prioritizes threads appended earlier first
    return _threads.append(&thread), true;
  }

  auto remove(Thread& thread) -> bool {
    if(auto offset = _threads.find(&thread)) return _threads.remove(*offset), true;
    return false;
  }

  auto enter(Mode mode = Mode::Run) -> Event {
    _mode = mode;
    _host = co_active();
    co_switch(_resume);
    return _event;
  }

  inline auto resume(Thread& thread) -> void {
    if(_mode != Mode::SynchronizeSlave) co_switch(thread.handle());
  }

  auto exit(Event event) -> void {
    uintmax minimum = -1;
    for(auto thread : _threads) {
      if(thread->_clock < minimum) minimum = thread->_clock;
    }
    for(auto thread : _threads) {
      thread->_clock -= minimum;
    }

    _event = event;
    _resume = co_active();
    co_switch(_host);
  }

  inline auto synchronize(Thread& thread) -> void {
    if(thread.handle() == _master) {
      while(enter(Mode::SynchronizeMaster) != Event::Synchronize);
    } else {
      _resume = thread.handle();
      while(enter(Mode::SynchronizeSlave) != Event::Synchronize);
    }
  }

  inline auto synchronize() -> void {
    if(co_active() == _master) {
      if(_mode == Mode::SynchronizeMaster) return exit(Event::Synchronize);
    } else {
      if(_mode == Mode::SynchronizeSlave) return exit(Event::Synchronize);
    }
  }

private:
  cothread_t _host = nullptr;    //program thread (used to exit scheduler)
  cothread_t _resume = nullptr;  //resume thread (used to enter scheduler)
  cothread_t _master = nullptr;  //primary thread (used to synchronize components)
  Mode _mode = Mode::Run;
  Event _event = Event::Step;
  vector<Thread*> _threads;
};

}
