//
//  KRTexture2D.cpp
//  Kraken Engine
//
//  Copyright 2026 Kearwood Gilbert. All rights reserved.
//  
//  Redistribution and use in source and binary forms, with or without modification, are
//  permitted provided that the following conditions are met:
//  
//  1. Redistributions of source code must retain the above copyright notice, this list of
//  conditions and the following disclaimer.
//  
//  2. Redistributions in binary form must reproduce the above copyright notice, this list
//  of conditions and the following disclaimer in the documentation and/or other materials
//  provided with the distribution.
//  
//  THIS SOFTWARE IS PROVIDED BY KEARWOOD GILBERT ''AS IS'' AND ANY EXPRESS OR IMPLIED
//  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
//  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KEARWOOD GILBERT OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
//  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
//  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//  
//  The views and conclusions contained in the software and documentation are those of the
//  authors and should not be interpreted as representing official policies, either expressed
//  or implied, of Kearwood Gilbert.
//

#include "KREngine-common.h"
#include "KRTexture2D.h"
#include "KRTextureManager.h"
using namespace hydra;

using namespace mimir;

KRTexture2D::KRTexture2D(KRContext& context, Block* data, std::string name) : KRTexture(context, name)
{
  m_pData = data;
}

KRTexture2D::~KRTexture2D()
{
  delete m_pData;
}

bool KRTexture2D::createGPUTexture(int targetLod)
{
  if (m_haveNewHandles) {
    return true;
  }

  Vector3i dimensions = getDimensions();
  size_t bufferSize = getMemRequiredForLodRange(targetLod);
  void* buffer = malloc(bufferSize);

  if (!getLodData(buffer, targetLod)) {
    free(buffer);
    return false;
  }

  bool success = true;
  m_new_lod = -1;

  KRDeviceManager* deviceManager = getContext().getDeviceManager();

  for (auto deviceItr = deviceManager->getDevices().begin(); deviceItr != deviceManager->getDevices().end(); deviceItr++) {
    KRDevice& device = *(*deviceItr).second;
    KrDeviceHandle deviceHandle = (*deviceItr).first;
    VmaAllocator allocator = device.getAllocator();
    KRTexture::TextureHandle& texture = m_newHandles.emplace_back();
    texture.device = deviceHandle;
    texture.allocation = VK_NULL_HANDLE;
    texture.image = VK_NULL_HANDLE;

    if (!allocate(device, targetLod, 0, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &texture.image, &texture.allocation
#if KRENGINE_DEBUG_GPU_LABELS
      , getName().c_str()
#endif
      )) {
      success = false;
      break;
    }

    int min_mip = std::min(targetLod, m_lod_count - 1);
    int mip_count = m_lod_count - min_mip;

    VkImageViewCreateInfo viewInfo{};
    viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    viewInfo.image = texture.image;
    viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
    viewInfo.format = getFormat();
    viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    viewInfo.subresourceRange.baseMipLevel = 0;
    viewInfo.subresourceRange.levelCount = mip_count;
    viewInfo.subresourceRange.baseArrayLayer = 0;
    viewInfo.subresourceRange.layerCount = 1;
    VkResult res = vkCreateImageView(device.m_logicalDevice, &viewInfo, nullptr, &texture.fullImageView);
    if (res != VK_SUCCESS) {
      success = false;
      break;
    }

	  std::vector<VkBufferImageCopy> regions;
	  regions.resize(mip_count, VkBufferImageCopy{});
    int bufferOffset = 0;
    for (int mip = min_mip; mip < min_mip + mip_count; mip++) {
        VkBufferImageCopy& region = regions[mip];
        region.bufferOffset = bufferOffset;
        region.bufferRowLength = 0;
        region.bufferImageHeight = 0;

        region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        region.imageSubresource.mipLevel = mip - min_mip;
        region.imageSubresource.baseArrayLayer = 0;
        region.imageSubresource.layerCount = 1;

        region.imageOffset = { 0, 0, 0 };
        region.imageExtent = {
            (unsigned int)dimensions.x,
            (unsigned int)dimensions.y,
            (unsigned int)dimensions.z
        };

		    bufferOffset += getMemRequiredForLod(mip);
    }

    device.streamUpload((void*)buffer, bufferSize, texture.image, regions.data(), regions.size());
  }

  free(buffer);

  if (success) {
    m_new_lod = targetLod;
    m_haveNewHandles = true;
  } else {
    destroyNewHandles();
  }

  return success;
}

bool KRTexture2D::save(const std::string& path)
{
  if (m_pData) {
    return m_pData->save(path);
  } else {
    return false;
  }
}

bool KRTexture2D::save(Block& data)
{
  if (m_pData) {
    data.append(*m_pData);
    return true;
  } else {
    return false;
  }
}
