text: // Copyright (c) 2012-2017 VideoStitch SAS
  // Copyright (c) 2018 stitchEm
  
  #pragma once
  
  #include "buffer.hpp"
  #include <cassert>
  
  namespace VideoStitch {
  namespace GPU {
  
  /**
   * A UniqueBuffer is a smart wrapper for GPU Buffers that retains sole ownership
   * of GPU resources and frees the GPU memory when the UniqueBuffer goes out of scope.
   * No two UniqueBuffer instances can manage the same GPU memory.
   *
   * Reminder: the GPU Buffer is a dumb reference around GPU memory which requires manual memory
   * management (with alloc and release).
   *
   * The UniqueBuffer facilitates the memory management by automating the destruction,
   * similar to unique_ptr.
   */
  template <typename T>
  class UniqueBuffer {
   public:
    UniqueBuffer() : buf() {}
  
    /**
     * Takes ownership of the GPU Buffer, to release its memory when UniqueBuffer is destroyed
     */
    explicit UniqueBuffer(Buffer<T> buf) : buf(buf) {}
  
    UniqueBuffer(const UniqueBuffer& other) = delete;
  
    UniqueBuffer(UniqueBuffer&& other) {
      if (hasValidBuffer) {
        buf.release();
      }
  
      buf = other.buf;
      hasValidBuffer = other.hasValidBuffer;
  
      other.buf = Buffer<T>();
      other.hasValidBuffer = false;
    }
  
    /** Move assignment operator */
    UniqueBuffer& operator=(UniqueBuffer&& other) {
      if (hasValidBuffer) {
        buf.release();
      }
  
      this->buf = other.buf;
      this->hasValidBuffer = other.hasValidBuffer;
  
      other.buf = Buffer<T>();
      other.hasValidBuffer = false;
  
      return *this;
    }
  
    operator bool() const { return buf.byteSize() > 0; }
  
    /**
     * Returns the GPU Buffer but keeps ownership
     */
    Buffer<T> borrow() const { return buf; }
  
    /**
     * Returns the const GPU Buffer but keeps ownership
     */
    Buffer<const T> borrow_const() const { return buf; }
  
    /**
     * Releases ownership
     */
    Buffer<T> releaseOwnership() {
      Buffer<T> res = buf;
      buf = Buffer<T>();
      hasValidBuffer = false;
      return res;
    }
  
    /**
     * Allocates a GPU Buffer for the first time after creating the object.
     * Allocating a UniqueBuffer that is already allocated is an error, use .recreate()
     * @param size Size (in elements of type T).
     * @param name pool name.
     */
    Status alloc(size_t numElements, const char* name) {
      if (hasValidBuffer) {
        return {Origin::GPU, ErrType::ImplementationError,
                "Attempting to allocate a UniqueBuffer that is already allocated"};
      }
  
      PotentialValue<Buffer<T>> potBuf = Buffer<T>::allocate(numElements, name);
      if (potBuf.ok()) {
        buf = potBuf.value();
        hasValidBuffer = true;
      }
      return potBuf.status();
    }
  
    /**
     * Replaces current buffer with a new GPU Buffer of different size
     * Calling recreate on an uninitialized unique buffer is a valid operation.
     *
     * Note: the content of the GPU memory is not preserved, a new buffer is created
     *
     * @param size Size (in elements of type T).
     * @param name pool name.
     */
    Status recreate(size_t numElements, const char* name) {
      if (hasValidBuffer) {
        buf.release();
        hasValidBuffer = false;
      }
  
      return this->alloc(numElements, name);
    }
  
    ~UniqueBuffer() {
      if (hasValidBuffer) {
        buf.release();
      }
    }
  
   private:
    Buffer<T> buf;
    bool hasValidBuffer = false;
  };
  
  template <typename T>
  class PotentialUniqueBuffer : public PotentialValue<UniqueBuffer<T>> {
   public:
    PotentialUniqueBuffer(const Status& status) : PotentialValue<UniqueBuffer<T>>(status) {}
    PotentialUniqueBuffer(UniqueBuffer<T>&& value) : PotentialValue<UniqueBuffer<T>>(std::move(value)) {}
    PotentialUniqueBuffer(const PotentialUniqueBuffer&& other) = delete;
    PotentialUniqueBuffer(PotentialUniqueBuffer&& other) : PotentialValue<UniqueBuffer<T>>(std::move(other)) {}
  
    Buffer<T> borrow() const {
      const UniqueBuffer<T>& ub = this->value_;
      return ub.borrow();
    }
  
    Buffer<const T> borrow_const() const {
      const UniqueBuffer<T>& ub = this->value_;
      return ub.borrow_const();
    }
  
    Buffer<T> releaseOwnership() {
      UniqueBuffer<T>& ub = this->value_;
      return ub.releaseOwnership();
    }
  };
  
  /**
   * Helper function to create UniqueBuffers with a single call
   * Name doesn't start with 'allocate' to make it stand out in the source code
   * as it has a different lifetime
   */
  template <typename T>
  PotentialUniqueBuffer<T> uniqueBuffer(size_t numElements, const char* name) {
    UniqueBuffer<T> buf;
    const Status allocationStatus = buf.alloc(numElements, name);
    if (!allocationStatus.ok()) {
      return PotentialUniqueBuffer<T>(allocationStatus);
    }
    return PotentialUniqueBuffer<T>(std::move(buf));
  }
  
  }  // namespace GPU
  }  // namespace VideoStitch
  