// Copyright 2026 The Action Engine Authors.
//
// 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.

#ifndef THREAD_FIBER_CASES_H_
#define THREAD_FIBER_CASES_H_

#include <cstddef>
#include <type_traits>

#include <absl/container/inlined_vector.h>
#include <absl/log/check.h>
#include <absl/log/log.h>

#include "thread/boost_primitives.h"

namespace thread {
namespace internal {
template <typename T>
concept IsPointer = std::is_pointer_v<T>;

template <typename T>
concept IsNonConstPointer =
    IsPointer<T> && !std::is_const_v<std::remove_pointer_t<T>>;

template <typename T>
concept IsConstPointer =
    IsPointer<T> && std::is_const_v<std::remove_pointer_t<T>>;

struct Selector {
  static constexpr int kNonePicked = -1;

  bool TryPick(int case_index) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu) {
    if (picked_case_index != kNonePicked) {
      return false;  // Already picked.
    }

    picked_case_index = case_index;
    cv.Signal();
    return true;
  }

  // Returns true iff a case was picked before the deadline.
  bool WaitForPickUntil(absl::Time deadline) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu) {
    while (picked_case_index == internal::Selector::kNonePicked) {
      if (cv.WaitWithDeadline(&mu, deadline) &&
          picked_case_index == internal::Selector::kNonePicked) {
        return false;
      }
    }
    return true;
  }

  // kNonePicked until a case is picked, or the index of picked case
  thread::Mutex mu;
  int picked_case_index ABSL_GUARDED_BY(mu) = kNonePicked;
  thread::CondVar cv ABSL_GUARDED_BY(mu);
};

class Selectable;
}  // namespace internal

/**
 * @brief
 *   A Case represents a selectable case in a Select statement.
 *
 * A Case is used to represent a selectable condition in a Select statement,
 * allowing for passing arguments to the selectable. It contains a pointer to
 * the Selectable and an array of arguments that can be passed to the
 * Selectable when it is selected.
 */
struct [[nodiscard]] Case {
  internal::Selectable* absl_nonnull selectable;
  absl::InlinedVector<void* absl_nullable, 2> arguments;

  Case(internal::Selectable* absl_nonnull s) : selectable(s) {}

  Case(internal::Selectable* absl_nullable s, internal::IsPointer auto... args)
      : selectable(s) {
    (arguments.push_back(const_cast<void*>(static_cast<const void*>(args))),
     ...);
  }

  Case(const Case&) = default;
  Case& operator=(const Case&) = default;

  // // Disallow casting from bool
  // // ReSharper disable once CppNonExplicitConvertingConstructor
  template <typename... Args>
  Case(bool, Args... args) = delete;

  template <typename T>
  void AddArg(const T* absl_nonnull arg) {
    arguments.push_back(const_cast<void*>(static_cast<const void*>(arg)));
  }

  template <typename T>
  void AddArg(T* absl_nonnull arg) {
    arguments.push_back(static_cast<void*>(arg));
  }

  [[nodiscard]] void* absl_nonnull GetArgPtr(int index) const {
    if (index < 0 || index >= arguments.size()) {
      LOG(FATAL) << "Case::GetArgOrDie: index out of bounds: " << index
                 << ", arguments.size() = " << arguments.size();
      ABSL_ASSUME(false);
    }
    return arguments[index];
  }

  template <typename T>
  [[nodiscard]] T* absl_nonnull GetArgPtr(int index) const {
    return static_cast<T*>(GetArgPtr(index));
  }

  [[nodiscard]] size_t GetNumArgs() const { return arguments.size(); }
};

// An array of cases; the type supplied to Select. Must be initializer list
// compatible.
typedef absl::InlinedVector<Case, 4> CaseArray;

namespace internal {
struct CaseInSelectClause {
  const Case* absl_nullable case_ptr = nullptr;  // Initialized by Select().
  int index = -1;  // Provided by Select(): index in parameter list.
  internal::Selector* absl_nonnull selector =
      nullptr;  // Provided by Select(): owning selector.
  CaseInSelectClause* absl_nullable prev =
      nullptr;  // Initialized by Select(), nullptr -> not on list.
  CaseInSelectClause* absl_nullable next = nullptr;

  [[nodiscard]] const Case* absl_nonnull GetCase() const { return case_ptr; }

  // Attempt to cause the owning Selector to choose this case.
  bool TryPick() ABSL_EXCLUSIVE_LOCKS_REQUIRED(selector->mu) {
    return selector->TryPick(index);
  }

  bool Handle(bool enqueue);
  void Unregister();
};

using CaseStateArray = absl::InlinedVector<CaseInSelectClause, 4>;

// The interface implemented by objects that can be used with Select().
class Selectable {
 public:
  virtual ~Selectable() = default;

  // If this selectable is ready to be picked up by c's Select, call
  // c->TryPick() (which may or may not pick this selectable), and return true.
  virtual bool Handle(CaseInSelectClause* absl_nonnull case_state,
                      bool enqueue) = 0;

  // Unregister a case against future transitions for this Selectable.
  virtual void Unregister(CaseInSelectClause* absl_nonnull case_state) = 0;
};

inline bool CaseInSelectClause::Handle(bool enqueue) {
  return GetCase()->selectable->Handle(this, enqueue);
}

inline void CaseInSelectClause::Unregister() {
  GetCase()->selectable->Unregister(this);
}

// Shared linked list code. Implements a doubly-linked list where the list head
// is a pointer to the oldest element added to the list.
inline void PushBack(CaseInSelectClause* absl_nonnull* absl_nonnull head,
                     CaseInSelectClause* absl_nonnull element) {
  CHECK_EQ(element->prev, nullptr)
      << "Attempted to enqueue a CaseInSelectClause that is already on a list";

  if (*head == nullptr) {
    // Queue is empty; make singleton queue.
    element->next = element;
    element->prev = element;
    *head = element;
  } else {
    CHECK_NE((*head)->prev, nullptr)
        << "Waiter list head is corrupt: non-null head has null prev";

    // Add just before the oldest element (*head).
    element->next = *head;
    element->prev = element->next->prev;
    element->prev->next = element;
    element->next->prev = element;
  }
}

inline void UnlinkFromList(CaseInSelectClause* absl_nonnull* absl_nonnull head,
                           CaseInSelectClause* absl_nonnull element) {
  if (element->next == element) {
    // Single entry; clear list
    *head = nullptr;
  } else {
    // Remove from list
    element->next->prev = element->prev;
    element->prev->next = element->next;
    if (*head == element) {
      *head = element->next;
    }
  }
  // Maintaining this state in "prev" allows the safe removal of the current
  // element while iterating forwards.
  element->prev = nullptr;
}
}  // namespace internal
}  // namespace thread

#endif  // THREAD_FIBER_CASES_H_
