/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <atomic>

#include <folly/Function.h>
#include <folly/Likely.h>
#include <folly/fibers/FiberManager.h>
#include <folly/fibers/LoopController.h>

namespace folly {
namespace fibers {

class FiberManager;

class SimpleLoopController : public LoopController {
 public:
  SimpleLoopController();
  ~SimpleLoopController();

  /**
   * Run FiberManager loop; if no ready task are present,
   * run provided function. Stops after both stop() has been called
   * and no waiting tasks remain.
   */
  template <typename F>
  void loop(F&& func) {
    loopThread_.store(std::this_thread::get_id(), std::memory_order_release);

    bool waiting = false;
    stopRequested_ = false;

    while (FOLLY_LIKELY(waiting || !stopRequested_)) {
      func();
      runTimeouts();
      if (scheduled_) {
        scheduled_ = false;
        runLoop();
        waiting = fm_->hasTasks();
      }
    }

    loopThread_.store({}, std::memory_order_release);
  }

  /**
   * Requests exit from loop() as soon as all waiting tasks complete.
   */
  void stop() { stopRequested_ = true; }

  int remoteScheduleCalled() const { return remoteScheduleCalled_; }

  void runLoop() override {
    runLoopThread_.store(std::this_thread::get_id(), std::memory_order_release);
    do {
      if (remoteLoopRun_ < remoteScheduleCalled_) {
        for (; remoteLoopRun_ < remoteScheduleCalled_; ++remoteLoopRun_) {
          if (fm_->shouldRunLoopRemote()) {
            fm_->loopUntilNoReadyImpl();
          }
        }
      } else {
        fm_->loopUntilNoReadyImpl();
      }
    } while (remoteLoopRun_ < remoteScheduleCalled_);
    runLoopThread_.store({}, std::memory_order_release);
  }

  void runEagerFiber(Fiber* fiber) override { fm_->runEagerFiberImpl(fiber); }

  void schedule() override { scheduled_ = true; }

  HHWheelTimer* timer() override { return timer_.get(); }

  bool isInLoopThread() override {
    // One of the two will be set depending on how FiberManager is being looped
    // in tests.
    auto loopThread = loopThread_.load(std::memory_order_relaxed);
    auto runInLoopThread = runLoopThread_.load(std::memory_order_relaxed);
    auto thisThread = std::this_thread::get_id();
    return loopThread == thisThread || runInLoopThread == thisThread;
  }

 private:
  FiberManager* fm_;
  std::atomic<bool> scheduled_{false};
  bool stopRequested_;
  std::atomic<int> remoteScheduleCalled_{0};
  int remoteLoopRun_{0};
  std::atomic<std::thread::id> loopThread_;
  std::atomic<std::thread::id> runLoopThread_;

  class SimpleTimeoutManager;
  std::unique_ptr<SimpleTimeoutManager> timeoutManager_;
  std::shared_ptr<HHWheelTimer> timer_;

  /* LoopController interface */

  void setFiberManager(FiberManager* fm) override { fm_ = fm; }

  void scheduleThreadSafe() override {
    ++remoteScheduleCalled_;
    scheduled_ = true;
  }

  void runTimeouts();

  friend class FiberManager;
};
} // namespace fibers
} // namespace folly
