// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_REPORTING_RESOURCES_RESOURCE_MANAGER_H_
#define COMPONENTS_REPORTING_RESOURCES_RESOURCE_MANAGER_H_

#include <atomic>
#include <cstdint>
#include <optional>
#include <queue>
#include <utility>

#include "base/functional/callback_forward.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/thread_annotations.h"

namespace reporting {

// Resource management class. The class is thread-safe.
// Each resource instance is created with its own total size; the rest of the
// functionality is identical. All APIs are non-blocking.
class ResourceManager : public base::RefCountedThreadSafe<ResourceManager> {
 public:
  explicit ResourceManager(uint64_t total_size);

  // Needs to be called before attempting to allocate specified size.
  // Returns true if requested amount can be allocated.
  // After that the caller can actually allocate it or must call
  // |Discard| if decided not to allocate.
  bool Reserve(uint64_t size);

  // Reverts reservation, arranges for callbacks calls as necessary.
  // Must be called after the specified amount is released.
  void Discard(uint64_t size);

  // Returns total amount.
  uint64_t GetTotal() const;

  // Returns current used amount.
  uint64_t GetUsed() const;

  // Registers a callback to be invoked once there is specified amount
  // of resource available (does not reserve it, so once called back
  // the respective code must attempt to reserve again, and if unsuccessful,
  // may need ot re-register the callback).
  // Callbacks will be invoked in the context of the sequenced task runner
  // it was registered in.
  void RegisterCallback(uint64_t size, base::OnceClosure cb);

  // Test only: Sets non-default usage limit.
  void Test_SetTotal(uint64_t test_total);

 private:
  friend class base::RefCountedThreadSafe<ResourceManager>;

  ~ResourceManager();

  // Flushes as many callbacks as possible given the current resource
  // availability. Callbacks only signal that resource may be available,
  // the resumed task must try to actually reserve it after that.
  void FlushCallbacks();

  uint64_t total_;  // Remains constant in prod code, changes only in tests.
  std::atomic<uint64_t> used_{0};

  // Sequenced task runner for callbacks handling (not for calling callbacks!)
  const scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_;
  SEQUENCE_CHECKER(sequence_checker_);

  // Queue of pairs [size, callback].
  // When `Discard` leaves enough space available (even momentarily),
  // calls as many of the callbacks as fit in that size, in the queue order.
  // Note that in a meantime reservation may change - the called back code
  // must attempt reservation before using it.
  std::queue<std::pair<uint64_t, base::OnceClosure>> resource_callbacks_
      GUARDED_BY_CONTEXT(sequence_checker_);
};

// Moveable RAII class used for scoped Reserve-Discard.
//
// Usage:
//  {
//    ScopedReservation reservation(1024u, options.memory_resource());
//    if (!reservation.reserved()) {
//      // Allocation failed.
//      return;
//    }
//    // Allocation succeeded.
//    ...
//  }   // Automatically discarded.
//
// Can be handed over to another owner by move-constructor or using HandOver
// method:
// {
//   ScopedReservation summary;
//   for (const uint64_t size : sizes) {
//     ScopedReservation single_reservation(size, resource);
//     ...
//     summary.HandOver(single_reservation);
//   }
// }
class ScopedReservation {
 public:
  // Zero-size reservation with no resource interface attached.
  // reserved() returns false.
  ScopedReservation() noexcept;
  // Specified reservation, must have resource interface attached.
  ScopedReservation(uint64_t size,
                    scoped_refptr<ResourceManager> resource_manager) noexcept;
  // New reservation on the same resource interface as |other_reservation|.
  ScopedReservation(uint64_t size,
                    const ScopedReservation& other_reservation) noexcept;
  // Move constructor.
  ScopedReservation(ScopedReservation&& other) noexcept;
  ScopedReservation(const ScopedReservation& other) = delete;
  ScopedReservation& operator=(ScopedReservation&& other) = delete;
  ScopedReservation& operator=(const ScopedReservation& other) = delete;
  ~ScopedReservation();

  bool reserved() const;

  // Reduces reservation to |new_size|.
  bool Reduce(uint64_t new_size);

  // Adds |other| to |this| without assigning or releasing any reservation.
  // Used for seamless transition from one reservation to another (more generic
  // than std::move). Resets |other| to non-reserved state upon return from this
  // method.
  void HandOver(ScopedReservation& other);

 private:
  scoped_refptr<ResourceManager> resource_manager_;
  std::optional<uint64_t> size_;
};

}  // namespace reporting

#endif  // COMPONENTS_REPORTING_RESOURCES_RESOURCE_MANAGER_H_
