// Copyright 2021 gRPC 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 GRPC_CORE_LIB_PROMISE_LATCH_H
#define GRPC_CORE_LIB_PROMISE_LATCH_H

#include <grpc/support/port_platform.h>

#include <grpc/support/log.h>

#include "src/core/lib/promise/activity.h"
#include "src/core/lib/promise/intra_activity_waiter.h"
#include "src/core/lib/promise/poll.h"

namespace grpc_core {

// Latch provides a single set waitable object.
// Initially the Latch is unset.
// It can be waited upon by the Wait method, which produces a Promise that
// resolves when the Latch is Set to a value of type T.
template <typename T>
class Latch {
 public:
  // This is the type of the promise returned by Wait.
  class WaitPromise {
   public:
    Poll<T*> operator()() const {
      if (latch_->has_value_) {
        return &latch_->value_;
      } else {
        return latch_->waiter_.pending();
      }
    }

   private:
    friend class Latch;
    explicit WaitPromise(Latch* latch) : latch_(latch) {}
    Latch* latch_;
  };

  Latch() = default;
  Latch(const Latch&) = delete;
  Latch& operator=(const Latch&) = delete;
  Latch(Latch&& other) noexcept
      : value_(std::move(other.value_)), has_value_(other.has_value_) {
#ifndef NDEBUG
    GPR_DEBUG_ASSERT(!other.has_had_waiters_);
#endif
  }
  Latch& operator=(Latch&& other) noexcept {
#ifndef NDEBUG
    GPR_DEBUG_ASSERT(!other.has_had_waiters_);
#endif
    value_ = std::move(other.value_);
    has_value_ = other.has_value_;
    return *this;
  }

  // Produce a promise to wait for a value from this latch.
  WaitPromise Wait() {
#ifndef NDEBUG
    has_had_waiters_ = true;
#endif
    return WaitPromise(this);
  }

  // Set the value of the latch. Can only be called once.
  void Set(T value) {
    GPR_DEBUG_ASSERT(!has_value_);
    value_ = std::move(value);
    has_value_ = true;
    waiter_.Wake();
  }

 private:
  // The value stored (if has_value_ is true), otherwise some random value, we
  // don't care.
  // Why not absl::optional<>? Writing things this way lets us compress
  // has_value_ with waiter_ and leads to some significant memory savings for
  // some scenarios.
  GPR_NO_UNIQUE_ADDRESS T value_;
  // True if we have a value set, false otherwise.
  bool has_value_ = false;
#ifndef NDEBUG
  // Has this latch ever had waiters.
  bool has_had_waiters_ = false;
#endif
  IntraActivityWaiter waiter_;
};

}  // namespace grpc_core

#endif  // GRPC_CORE_LIB_PROMISE_LATCH_H
