/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#pragma once

#include "cmConfigure.h" // IWYU pragma: keep

#include <functional>
#include <list>
#include <vector>

#include <cm/optional>
#include <cm/string_view>

#include <cm3p/uv.h>
#include <stddef.h>

#include "cmCTestJobServerClient.h"

// POSIX jobserver client implementation
// https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html
class cmCTestJobServerPosix : public cmCTestJobServerClient
{
private:
  union
  {
    struct
    {
      uv_pipe_t read_pipe;
      uv_pipe_t write_pipe;
    };
    uv_pipe_t fifo;
  };

  enum class ConnectionType
  {
    // read_pipe and write_pipe are valid
    Pipe,
    // fifo is valid
    Fifo
  };
  ConnectionType connection_type;

  // min(local number of processes, upstream number of processes)
  size_t maxSlots;

  uv_loop_t* Loop = nullptr;
  uv_stream_t* _get_writer();
  uv_stream_t* _get_reader();
  static void _on_read(uv_stream_t* stream, ssize_t nread,
                       const uv_buf_t* buf);

  // A callback to be called if the jobserver client encounters a fatal error
  cm::optional<std::function<void()>> FatalCallback;

  // Stack of tokens that have been acquired from the jobserver
  std::vector<char> tokens = std::vector<char>();
  size_t usedTokens = 0;
  bool freeTokenAvailable = true;

  std::list<cmCTestJobServerClient::Task> queue =
    std::list<cmCTestJobServerClient::Task>();

  void _acquire(size_t slots);
  void _write(size_t slots, bool noRetry);

  void _process_queue();

public:
  cmCTestJobServerPosix(size_t maxJobs)
    : maxSlots(maxJobs)
  {
  }
  ~cmCTestJobServerPosix() override = default;

  cm::string_view Name() const override { return cm::string_view("posix"); }

  /**
   * @brief Connect to jobserver using a pair of file descriptors
   * @return true if the connection seems successful
   */
  bool Connect(uv_loop_t* loop, int rfd, int wfd);
  /**
   * @brief Connect to jobserver using a path to a fifo
   * @return true if the connection seems successful
   */
  bool Connect(uv_loop_t* loop, const char* path);
  /**
   * @brief Connect to jobserver using the environment variables
   * @return true if the connection seems successful
   */
  bool Connect(uv_loop_t* loop);

  void Enqueue(std::function<void(size_t)> task, size_t slots) override;
  void Release(size_t slots) override;
  void Close(bool force) override;
  void SetFatalCallback(std::function<void()> f) override;
};
