#ifndef GPU_COMMAND_BUFFER_CLIENT_SHARED_IMAGE_POOL_H_
#define GPU_COMMAND_BUFFER_CLIENT_SHARED_IMAGE_POOL_H_

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

#include <memory>
#include <optional>
#include <vector>

#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/gpu_command_buffer_client_export.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/shared_image_pool_id.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/common/sync_token.h"
#include "gpu/ipc/common/shared_image_pool_client_interface.mojom.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "ui/gfx/buffer_types.h"
#include "ui/gfx/geometry/size.h"

namespace gpu {
class SharedImageInterface;

// Structure holding the necessary information to create shared images and
// describe its characteristics in the SharedImagePool. It will be constant for
// all the shared images in the pool.
struct GPU_COMMAND_BUFFER_CLIENT_EXPORT ImageInfo {
  gfx::Size size;
  viz::SharedImageFormat format;
  SharedImageUsageSet usage;
  gfx::ColorSpace color_space;
  GrSurfaceOrigin surface_origin = kTopLeft_GrSurfaceOrigin;
  SkAlphaType alpha_type = kPremul_SkAlphaType;
  std::optional<gfx::BufferUsage> buffer_usage = std::nullopt;
  bool is_software = false;

  ImageInfo(gfx::Size size,
            viz::SharedImageFormat format,
            SharedImageUsageSet usage,
            std::optional<gfx::BufferUsage> buffer_usage = std::nullopt,
            bool is_software = false)
      : size(size),
        format(format),
        usage(usage),
        buffer_usage(std::move(buffer_usage)),
        is_software(is_software) {}

  ImageInfo(gfx::Size size,
            viz::SharedImageFormat format,
            SharedImageUsageSet usage,
            gfx::ColorSpace color_space,
            GrSurfaceOrigin surface_origin,
            SkAlphaType alpha_type,
            std::optional<gfx::BufferUsage> buffer_usage = std::nullopt,
            bool is_software = false)
      : size(size),
        format(format),
        usage(usage),
        color_space(color_space),
        surface_origin(surface_origin),
        alpha_type(alpha_type),
        buffer_usage(std::move(buffer_usage)),
        is_software(is_software) {}

  bool operator==(const ImageInfo& other) const {
    return size == other.size && format == other.format &&
           usage == other.usage && color_space == other.color_space &&
           surface_origin == other.surface_origin &&
           alpha_type == other.alpha_type &&
           buffer_usage == other.buffer_usage &&
           is_software == other.is_software;
  }
};

// A reference-counted image class that wraps a GPU ClientSharedImage. Clients
// can optionally extend this class to add its own metadata and logic in
// addition to the shared image it wraps. This allow clients to create its own
// custom pool of images of ClientImage type and are not limited to creating
// pool of only ClientSharedImage. See unittests for example.
class GPU_COMMAND_BUFFER_CLIENT_EXPORT ClientImage
    : public base::RefCountedThreadSafe<ClientImage> {
 public:
  REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();
  explicit ClientImage(scoped_refptr<ClientSharedImage> shared_image);

  // Returns the reference on the underlying shared image. Note that clients
  // using it should ensure that the returned reference does not outlive the
  // ClientImage.
  const scoped_refptr<ClientSharedImage>& GetSharedImage() const;

  // Returns a sync token which should be waited upon before using this image.
  const SyncToken& GetSyncToken() const;

  // Sets the sync token which will be waited upon before releasing this image
  // for re-use or destruction.
  void SetReleaseSyncToken(SyncToken release_sync_token);

  // Only used for testing purposes.
  const SharedImagePoolId& GetPoolIdForTesting() const;

  // Dumps memoy allocation. Caller specified `parent_path` cannot be empty.
  virtual void OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd,
                            const std::string& parent_path) const;

 protected:
  friend class base::RefCountedThreadSafe<ClientImage>;
  friend class SharedImagePoolBase;

  // Allow each instantiation of SharedImagePool to access `pool_id_`.
  template <typename ClientImageType>
  friend class SharedImagePool;
  virtual ~ClientImage();

  // Subclasses can set this to true if they have more complex logic if managing
  // destruction sync token.
  bool subclass_manages_destruction_sync_token_ = false;

 private:
  scoped_refptr<ClientSharedImage> shared_image_;

  // This token has to be waited upon before using/re-using the |shared_image_|.
  // This will be also used internally as a destruction sync token for the
  // shared image.
  SyncToken sync_token_;

  // The time when this image was last used. This can be used to purge the
  // recycled images in the pool based on the optional expiration time set by
  // the client.
  base::TimeTicks last_used_time_ = base::TimeTicks::Now();

  // Unique unguessable identifier to identify the pool this image belongs to.
  SharedImagePoolId pool_id_;
};

// This class is designed to handle bulk of functionality of the image pool.
// This also allows to have a template subclass with minimum functionality.
// Since all definitions of templated subclass with be in this header file, we
// want it to be as thin as possible as it will also code generate for all
// possible params and this will increase binary size. Clients will not use this
// class directly.
class GPU_COMMAND_BUFFER_CLIENT_EXPORT SharedImagePoolBase {
 public:
  virtual ~SharedImagePoolBase();

  size_t GetPoolSizeForTesting() const;
  bool IsReclaimTimerRunningForTesting() const;

 protected:
  SharedImagePoolBase(
      const SharedImagePoolId& pool_id,
      const ImageInfo& image_info,
      std::string_view debug_label,
      const scoped_refptr<SharedImageInterface> sii,
      std::optional<uint8_t> max_pool_size,
      std::optional<base::TimeDelta> unused_resource_expiration_time);

  scoped_refptr<ClientSharedImage> CreateSharedImageInternal();
  scoped_refptr<ClientImage> GetImageFromPoolInternal();
  void ReleaseImageInternal(scoped_refptr<ClientImage> image);
  void ClearInternal();
  void ReconfigureInternal(const ImageInfo& image_info);

  // Unique identifier to identify this pool and all images generated from it.
  const SharedImagePoolId pool_id_;

  // Information used to create new ClientSharedImage.
  ImageInfo image_info_;

  std::string debug_label_;

  // Interface to the GPU process for creating shared images.
  const scoped_refptr<SharedImageInterface> sii_;

  // Optional maximum size of the pool. If unset, there is no limit on the size
  // of the pool.
  const std::optional<uint8_t> max_pool_size_;

  const std::optional<base::TimeDelta> unused_resource_expiration_time_;

  // Pool of available images.
  std::vector<scoped_refptr<ClientImage>> image_pool_;

 private:
  void MaybePostUnusedResourcesReclaimTask();
  void ClearOldUnusedResources();

  base::OneShotTimer unused_resources_reclaim_timer_;
};

// Templated class for managing a pool of ClientImageType objects which wraps
// shared images. ClientImageType should be a subclass of ClientImage if
// extended functionality is needed. By default, ClientImageType is ClientImage
// which will result in pool of ClientSharedImage if a client does not need
// additional functionality.
// Clients will use this class and its apis for desired functionality.
template <typename ClientImageType = ClientImage>
class GPU_COMMAND_BUFFER_CLIENT_EXPORT SharedImagePool
    : public SharedImagePoolBase,
      public mojom::SharedImagePoolClientInterface {
 public:
  static std::unique_ptr<SharedImagePool<ClientImageType>> Create(
      const ImageInfo& image_info,
      const scoped_refptr<SharedImageInterface> sii,
      std::string_view debug_label,
      std::optional<uint8_t> max_pool_size = std::nullopt,
      std::optional<base::TimeDelta> unused_resource_expiration_time =
          std::nullopt) {
    CHECK(sii);
    return base::WrapUnique<SharedImagePool<ClientImageType>>(
        new SharedImagePool(image_info, debug_label, std::move(sii),
                            std::move(max_pool_size),
                            std::move(unused_resource_expiration_time)));
  }

  // Clears the pool, deleting all contained images. Also sends an IPC to
  // destroy the corresponding service side pool.
  ~SharedImagePool() override {
    if (sii_) {
      sii_->DestroySharedImagePool(pool_id_);
    }
  }

  // Retrieves an image from the pool or creates a new one if the pool is empty.
  scoped_refptr<ClientImageType> GetImage() {
    // Try to get an image from the pool.
    auto image = GetImageFromPoolInternal();
    if (image) {
      return static_cast<ClientImageType*>(image.get());
    }

    // If the pool is empty, create a new image.
    auto shared_image = CreateSharedImageInternal();
    if (!shared_image) {
      LOG(ERROR) << "Unable to create a shared image.";
      return nullptr;
    }
    auto new_image =
        base::MakeRefCounted<ClientImageType>(std::move(shared_image));
    new_image->pool_id_ = pool_id_;
    return new_image;
  }

  // Releases an |image| to the Pool. The |image| will be released/destroyed if
  // the pool is full or will be recycled back into the pool for re-use. Clients
  // can optionally set an release sync token via ::SetReleaseSyncToken() which
  // will be waited upon before releasing or re-using this |image|.
  void ReleaseImage(scoped_refptr<ClientImageType> image) {
    ReleaseImageInternal(std::move(image));
  }

  // Clears the whole pool and destroys all the images.
  void Clear() { ClearInternal(); }

  // Used to reconfigure the pool with new |image_info|. If this info is same as
  // previous one, this operation will be no-op. Note that the max size of the
  // pool will not be reconfigured and remains same.
  void Reconfigure(const ImageInfo& image_info) {
    ReconfigureInternal(image_info);
  }

  // Returns the |image_info_| to the client. Based on this info, clients can
  // decide to continue using this pool or recreate new pool with updated
  // |image_info_|.
  const ImageInfo& GetImageInfo() { return image_info_; }

  // mojom::SharedImagePoolClientInterface implementation.
  void OnClearPool() override { Clear(); }

  base::ByteSize EstimatedSizeInBytes() const {
    base::ByteSize result;
    for (const auto& image : image_pool_) {
      result += image->GetSharedImage()->EstimatedSizeInBytes();
    }
    return result;
  }

  void OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd,
                    const std::string& parent_path) const {
    for (const auto& image : image_pool_) {
      image->OnMemoryDump(pmd, parent_path);
    }
  }

  // Returns a weak pointer to this pool, allowing for safe reference without
  // ownership.
  base::WeakPtr<SharedImagePool<ClientImageType>> GetWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

 private:
  SharedImagePool(
      const ImageInfo& image_info,
      std::string_view debug_label,
      scoped_refptr<SharedImageInterface> sii,
      std::optional<uint8_t> max_pool_size,
      std::optional<base::TimeDelta> unused_resource_expiration_time)
      : SharedImagePoolBase(SharedImagePoolId::Create(),
                            image_info,
                            debug_label,
                            sii,
                            std::move(max_pool_size),
                            std::move(unused_resource_expiration_time)) {
    mojo::PendingReceiver<gpu::mojom::SharedImagePoolClientInterface>
        client_receiver;
    auto client_remote = client_receiver.InitWithNewPipeAndPassRemote();
    receiver_.Bind(std::move(client_receiver));
    receiver_.set_disconnect_handler(base::BindOnce(
        &SharedImagePool::OnDisconnectedSharedImagePoolClientInterface,
        base::Unretained(this)));
    sii->CreateSharedImagePool(pool_id_, std::move(client_remote));
  }

  void OnDisconnectedSharedImagePoolClientInterface() { ClearInternal(); }

  mojo::Receiver<mojom::SharedImagePoolClientInterface> receiver_{this};

  base::WeakPtrFactory<SharedImagePool<ClientImageType>> weak_ptr_factory_{
      this};
};

}  // namespace gpu

#endif  // GPU_COMMAND_BUFFER_CLIENT_SHARED_IMAGE_POOL_H_
