/**
 * @file vma_cxx.cc
 * @author Supakorn "Jamie" Rassameemasmuang (jamievlin [at] outlook.com)
 * @brief Implementation for vma_cxx.h
 */

#include "vma_cxx.h"

#include <utility>

namespace vma
{
namespace cxx
{

UniqueAllocator::UniqueAllocator(VmaAllocatorCreateInfo const& vmaAllocatorCreateInfo)
{
  if (vmaCreateAllocator(&vmaAllocatorCreateInfo, &_allocator) != VK_SUCCESS)
  {
    throw vk::OutOfDeviceMemoryError("Cannot create Vulkan memory allocator");
  }
}

UniqueAllocator::~UniqueAllocator()
{
  if (_allocator != VK_NULL_HANDLE)
  {
    vmaDestroyAllocator(_allocator);
  }
}

UniqueAllocator::UniqueAllocator(UniqueAllocator&& other) noexcept
  : _allocator(std::exchange(other._allocator, VK_NULL_HANDLE))
{
}

UniqueAllocator& UniqueAllocator::operator=(UniqueAllocator&& other) noexcept
{
  if (this != &other)
  {
    std::swap(_allocator, other._allocator);
  }
  return *this;
}

VmaAllocator UniqueAllocator::getAllocator() const
{
  return _allocator;
}

UniqueBuffer
UniqueAllocator::createBuffer(VkBufferCreateInfo const& bufferCreateInfo, VmaAllocationCreateInfo const& allocInfo, VkDeviceSize alignment)
{
  VkBuffer buf;
  VmaAllocation alloc;
  // Use vmaCreateBufferWithAlignment instead of vmaCreateBuffer
  if (vmaCreateBufferWithAlignment(
          _allocator,
          &bufferCreateInfo,
          &allocInfo,
          alignment,
          &buf,
          &alloc,
          nullptr) != VK_SUCCESS)

    if (vmaCreateBuffer(_allocator, &bufferCreateInfo, &allocInfo, &buf, &alloc, nullptr) != VK_SUCCESS)
  {
    throw vk::OutOfDeviceMemoryError("Cannot create Vulkan memory buffer");
  }

  return {_allocator, alloc, buf};
}
UniqueImage UniqueAllocator::createImage(
  VkImageCreateInfo const& imgCreateInfo,
  VmaAllocationCreateInfo const& allocInfo
)
{
  VkImage resultImage;
  VmaAllocation resultAllocation;

  if (vmaCreateImage(_allocator, &imgCreateInfo, &allocInfo, &resultImage, &resultAllocation, nullptr) != VK_SUCCESS)
  {
    throw vk::OutOfDeviceMemoryError("Cannot create Vulkan image");
  }

  return {_allocator, resultImage, resultAllocation};
}

// unique buffer

UniqueBuffer::UniqueBuffer(
        VmaAllocator const& allocator,
        VmaAllocation const& alloc,
        VkBuffer const& buf
        )
    : _allocator(allocator), _buffer(buf), _allocation(alloc)
{

}
UniqueBuffer::~UniqueBuffer()
{
  if (_allocation != VK_NULL_HANDLE)
  {
    vmaDestroyBuffer(_allocator, _buffer, _allocation);
  }
}
UniqueBuffer::UniqueBuffer(UniqueBuffer&& other) noexcept
    : _allocator(std::exchange(other._allocator, VK_NULL_HANDLE)),
      _buffer(std::exchange(other._buffer, VK_NULL_HANDLE)),
      _allocation(std::exchange(other._allocation, VK_NULL_HANDLE))
{

}
UniqueBuffer& UniqueBuffer::operator=(UniqueBuffer&& other) noexcept
{
  if (this != &other)
  {
    std::swap(_allocator, other._allocator);
    std::swap(_buffer, other._buffer);
    std::swap(_allocation, other._allocation);
  }
  return *this;
}

VkBuffer UniqueBuffer::getBuffer() const
{
  return _buffer;
}
VmaAllocation UniqueBuffer::getAllocation() const
{
  return _allocation;
}
VmaAllocator UniqueBuffer::getAllocator() const
{
  return _allocator;
}


MemoryMapperLock::MemoryMapperLock(UniqueBuffer const& buffer) : sourceBuffer(&buffer)
{
  if (vmaMapMemory(buffer.getAllocator(), buffer.getAllocation(), &copyPtr) != VK_SUCCESS)
  {
    throw vk::OutOfDeviceMemoryError("Cannot map memory");
  }
}
MemoryMapperLock::~MemoryMapperLock()
{
  if (copyPtr != nullptr)
  {
    vmaUnmapMemory(sourceBuffer->getAllocator(), sourceBuffer->getAllocation());
  }
}
UniqueImage::UniqueImage(VmaAllocator const& allocator, VkImage const& image, VmaAllocation const& allocation)
    : _allocator(allocator), _image(image), _allocation(allocation)
{

}
UniqueImage::~UniqueImage()
{
  if (_allocation != VK_NULL_HANDLE)
  {
    vmaDestroyImage(_allocator, _image, _allocation);
  }
}
UniqueImage::UniqueImage(UniqueImage&& other) noexcept
    : _allocator(std::exchange(other._allocator, VK_NULL_HANDLE)), _image(std::exchange(other._image, VK_NULL_HANDLE)),
      _allocation(std::exchange(other._allocation, VK_NULL_HANDLE))
{

}
UniqueImage& UniqueImage::operator=(UniqueImage&& other) noexcept
{
  if (this != &other)
  {
    std::swap(_allocator, other._allocator);
    std::swap(_image, other._image);
    std::swap(_allocation, other._allocation);
  }
  return *this;
}
VkImage UniqueImage::getImage() const
{
  return _image;
}
}// namespace cxx
}
