package net.vulkanmod.vulkan.texture;

import java.nio.ByteBuffer;
import java.nio.LongBuffer;
import java.util.Arrays;
import net.vulkanmod.render.texture.ImageUploadHelper;
import net.vulkanmod.vulkan.Renderer;
import net.vulkanmod.vulkan.Vulkan;
import net.vulkanmod.vulkan.memory.MemoryManager;
import net.vulkanmod.vulkan.memory.buffer.StagingBuffer;
import net.vulkanmod.vulkan.queue.CommandPool;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.vulkan.VK10;
import org.lwjgl.vulkan.VkAllocationCallbacks;
import org.lwjgl.vulkan.VkBufferMemoryBarrier;
import org.lwjgl.vulkan.VkCommandBuffer;
import org.lwjgl.vulkan.VkDevice;
import org.lwjgl.vulkan.VkImageMemoryBarrier;
import org.lwjgl.vulkan.VkImageViewCreateInfo;
import org.lwjgl.vulkan.VkMemoryBarrier;

public class VulkanImage {
   public static int DefaultFormat = 37;
   private static final VkDevice DEVICE = Vulkan.getVkDevice();
   public final String name;
   public final int format;
   public final int aspect;
   public final int arrayLayers;
   public final int mipLevels;
   public final int width;
   public final int height;
   public final int formatSize;
   public final int usage;
   public final int viewType;
   public final int size;
   private long id;
   private long allocation;
   private long mainImageView;
   private final long[] levelImageViews;
   private long sampler;
   private int currentLayout;

   public VulkanImage(String name, long id, int format, int mipLevels, int width, int height, int formatSize, int usage, long imageView) {
      this.id = id;
      this.mainImageView = imageView;
      this.name = name;
      this.arrayLayers = 1;
      this.mipLevels = mipLevels;
      this.width = width;
      this.height = height;
      this.formatSize = formatSize;
      this.format = format;
      this.usage = usage;
      this.aspect = getAspect(this.format);
      this.viewType = 1;
      this.size = width * height * formatSize;
      this.levelImageViews = new long[mipLevels];
      this.sampler = SamplerManager.getDefaultSampler();
   }

   private VulkanImage(Builder builder) {
      this.name = builder.name;
      this.mipLevels = builder.mipLevels;
      this.width = builder.width;
      this.height = builder.height;
      this.arrayLayers = builder.arrayLayers;
      this.formatSize = builder.formatSize;
      this.format = builder.format;
      this.usage = builder.usage;
      this.aspect = getAspect(this.format);
      this.viewType = builder.viewType;
      this.size = this.width * this.height * this.formatSize;
      this.levelImageViews = new long[builder.mipLevels];
   }

   public static VulkanImage createTextureImage(Builder builder) {
      VulkanImage image = new VulkanImage(builder);
      image.createImage();
      image.mainImageView = createImageView(image.id, image.viewType, image.format, image.aspect, image.arrayLayers, 0, image.mipLevels);
      image.sampler = SamplerManager.getSampler(builder.clamp, builder.linearFiltering, builder.mipLevels - 1);
      return image;
   }

   public static VulkanImage createDepthImage(int format, int width, int height, int usage, boolean blur, boolean clamp) {
      VulkanImage image = builder(width, height).setFormat(format).setUsage(usage).setLinearFiltering(blur).setClamp(clamp).createVulkanImage();
      return image;
   }

   public static VulkanImage createWhiteTexture() {
      MemoryStack stack = MemoryStack.stackPush();

      VulkanImage var4;
      try {
         int i = -1;
         ByteBuffer buffer = stack.malloc(4);
         buffer.putInt(0, i);
         VulkanImage image = builder(1, 1).setFormat(DefaultFormat).setUsage(6).setLinearFiltering(false).setClamp(false).createVulkanImage();
         image.uploadSubTextureAsync(0, 0, image.width, image.height, 0, 0, 0, 0, 0, buffer);
         var4 = image;
      } catch (Throwable var6) {
         if (stack != null) {
            try {
               stack.close();
            } catch (Throwable var5) {
               var6.addSuppressed(var5);
            }
         }

         throw var6;
      }

      if (stack != null) {
         stack.close();
      }

      return var4;
   }

   private void createImage() {
      MemoryStack stack = MemoryStack.stackPush();

      try {
         LongBuffer pTextureImage = stack.mallocLong(1);
         PointerBuffer pAllocation = stack.pointers(0L);
         int flags = this.viewType == 3 ? 16 : 0;
         MemoryManager.getInstance().createImage(this.width, this.height, this.arrayLayers, this.mipLevels, this.format, 0, this.usage, flags, 1, pTextureImage, pAllocation);
         this.id = pTextureImage.get(0);
         this.allocation = pAllocation.get(0);
         MemoryManager.addImage(this);
         if (this.name != null) {
            Vulkan.setDebugLabel(stack, 10, pTextureImage.get(), this.name);
         }
      } catch (Throwable var6) {
         if (stack != null) {
            try {
               stack.close();
            } catch (Throwable var5) {
               var6.addSuppressed(var5);
            }
         }

         throw var6;
      }

      if (stack != null) {
         stack.close();
      }

   }

   public static int getAspect(int format) {
      byte var10000;
      switch (format) {
         case 124:
         case 125:
         case 126:
            var10000 = 2;
            break;
         case 127:
         case 128:
         default:
            var10000 = 1;
            break;
         case 129:
         case 130:
            var10000 = 6;
      }

      return var10000;
   }

   public static boolean isDepthFormat(int format) {
      boolean var10000;
      switch (format) {
         case 124:
         case 125:
         case 126:
         case 129:
         case 130:
            var10000 = true;
            break;
         case 127:
         case 128:
         default:
            var10000 = false;
      }

      return var10000;
   }

   public static long createImageView(long image, int format, int aspectFlags, int arrayLayers, int mipLevels) {
      return createImageView(image, 1, format, aspectFlags, arrayLayers, 0, mipLevels);
   }

   public static long createImageView(long image, int viewType, int format, int aspectFlags, int arrayLayers, int baseMipLevel, int mipLevels) {
      MemoryStack stack = MemoryStack.stackPush();

      long var11;
      try {
         VkImageViewCreateInfo viewInfo = VkImageViewCreateInfo.calloc(stack);
         viewInfo.sType(15);
         viewInfo.image(image);
         viewInfo.viewType(viewType);
         viewInfo.format(format);
         viewInfo.subresourceRange().aspectMask(aspectFlags);
         viewInfo.subresourceRange().baseMipLevel(baseMipLevel);
         viewInfo.subresourceRange().levelCount(mipLevels);
         viewInfo.subresourceRange().baseArrayLayer(0);
         viewInfo.subresourceRange().layerCount(arrayLayers);
         LongBuffer pImageView = stack.mallocLong(1);
         if (VK10.vkCreateImageView(DEVICE, viewInfo, (VkAllocationCallbacks)null, pImageView) != 0) {
            throw new RuntimeException("Failed to create texture image view");
         }

         var11 = pImageView.get(0);
      } catch (Throwable var14) {
         if (stack != null) {
            try {
               stack.close();
            } catch (Throwable var13) {
               var14.addSuppressed(var13);
            }
         }

         throw var14;
      }

      if (stack != null) {
         stack.close();
      }

      return var11;
   }

   public void uploadSubTextureAsync(int mipLevel, int width, int height, int xOffset, int yOffset, int unpackSkipRows, int unpackSkipPixels, int unpackRowLength, ByteBuffer buffer) {
      this.uploadSubTextureAsync(mipLevel, 0, width, height, xOffset, yOffset, unpackSkipRows, unpackSkipPixels, unpackRowLength, MemoryUtil.memAddress(buffer));
   }

   public void uploadSubTextureAsync(int mipLevel, int arrayLayer, int width, int height, int xOffset, int yOffset, int unpackSkipRows, int unpackSkipPixels, int unpackRowLength, ByteBuffer buffer) {
      this.uploadSubTextureAsync(mipLevel, arrayLayer, width, height, xOffset, yOffset, unpackSkipRows, unpackSkipPixels, unpackRowLength, MemoryUtil.memAddress(buffer));
   }

   public void uploadSubTextureAsync(int mipLevel, int arrayLayer, int width, int height, int xOffset, int yOffset, int unpackSkipRows, int unpackSkipPixels, int unpackRowLength, long srcPtr) {
      long uploadSize = (long)(unpackRowLength * height - unpackSkipPixels) * (long)this.formatSize;
      StagingBuffer stagingBuffer = Vulkan.getStagingBuffer();
      if (uploadSize > stagingBuffer.getBufferSize()) {
         stagingBuffer = new StagingBuffer(uploadSize);
         stagingBuffer.scheduleFree();
      }

      srcPtr += ((long)unpackRowLength * (long)unpackSkipRows + (long)unpackSkipPixels) * (long)this.formatSize;
      stagingBuffer.align(this.formatSize);
      stagingBuffer.copyBuffer((int)uploadSize, srcPtr);
      long bufferId = stagingBuffer.getId();
      VkCommandBuffer commandBuffer = ImageUploadHelper.INSTANCE.getOrStartCommandBuffer().getHandle();
      MemoryStack stack = MemoryStack.stackPush();

      try {
         this.transferDstLayout(stack, commandBuffer);
         int srcOffset = (int)stagingBuffer.getOffset();
         ImageUtil.copyBufferToImageCmd(stack, commandBuffer, bufferId, this.id, arrayLayer, mipLevel, width, height, xOffset, yOffset, srcOffset, unpackRowLength, height);
      } catch (Throwable var22) {
         if (stack != null) {
            try {
               stack.close();
            } catch (Throwable var21) {
               var22.addSuppressed(var21);
            }
         }

         throw var22;
      }

      if (stack != null) {
         stack.close();
      }

   }

   private void transferDstLayout(MemoryStack stack, VkCommandBuffer commandBuffer) {
      this.transitionImageLayout(stack, commandBuffer, 7);
   }

   public void readOnlyLayout() {
      if (this.currentLayout != 5) {
         MemoryStack stack = MemoryStack.stackPush();

         try {
            if (Renderer.getInstance().getBoundRenderPass() != null) {
               CommandPool.CommandBuffer commandBuffer = ImageUploadHelper.INSTANCE.getOrStartCommandBuffer();
               VkCommandBuffer vkCommandBuffer = commandBuffer.getHandle();
               this.readOnlyLayout(stack, vkCommandBuffer);
            } else {
               this.readOnlyLayout(stack, Renderer.getCommandBuffer());
            }
         } catch (Throwable var5) {
            if (stack != null) {
               try {
                  stack.close();
               } catch (Throwable var4) {
                  var5.addSuppressed(var4);
               }
            }

            throw var5;
         }

         if (stack != null) {
            stack.close();
         }

      }
   }

   public void readOnlyLayout(MemoryStack stack, VkCommandBuffer commandBuffer) {
      this.transitionImageLayout(stack, commandBuffer, 5);
   }

   public void setSampler(long sampler) {
      this.sampler = sampler;
   }

   public void transitionImageLayout(MemoryStack stack, VkCommandBuffer commandBuffer, int newLayout) {
      transitionImageLayout(stack, commandBuffer, this, newLayout);
   }

   public static void transitionImageLayout(MemoryStack stack, VkCommandBuffer commandBuffer, VulkanImage image, int newLayout) {
      if (image.currentLayout != newLayout) {
         int dstAccessMask = 0;
         int sourceStage;
         int srcAccessMask;
         switch (image.currentLayout) {
            case 0:
            case 1000001002:
               srcAccessMask = 0;
               sourceStage = 8192;
               break;
            case 2:
               srcAccessMask = 256;
               sourceStage = 1024;
               break;
            case 3:
               srcAccessMask = 1536;
               sourceStage = 512;
               break;
            case 4:
               srcAccessMask = 544;
               sourceStage = 384;
               break;
            case 5:
               srcAccessMask = 32;
               sourceStage = 128;
               break;
            case 6:
               srcAccessMask = 2048;
               sourceStage = 4096;
               break;
            case 7:
               srcAccessMask = 4096;
               sourceStage = 4096;
               break;
            default:
               throw new RuntimeException("Unexpected value:" + image.currentLayout);
         }

         int destinationStage;
         switch (newLayout) {
            case 2:
               dstAccessMask = 384;
               destinationStage = 1024;
               break;
            case 3:
               dstAccessMask = 1536;
               destinationStage = 256;
               break;
            case 5:
               dstAccessMask = 32;
               destinationStage = 128;
               break;
            case 6:
               dstAccessMask = 2048;
               destinationStage = 4096;
               break;
            case 7:
               dstAccessMask = 4096;
               destinationStage = 4096;
               break;
            case 1000001002:
               destinationStage = 8192;
               break;
            default:
               throw new RuntimeException("Unexpected value:" + newLayout);
         }

         transitionLayout(stack, commandBuffer, image, image.currentLayout, newLayout, sourceStage, srcAccessMask, destinationStage, dstAccessMask);
      }
   }

   public static void transitionLayout(MemoryStack stack, VkCommandBuffer commandBuffer, VulkanImage image, int oldLayout, int newLayout, int sourceStage, int srcAccessMask, int destinationStage, int dstAccessMask) {
      transitionLayout(stack, commandBuffer, image, 0, oldLayout, newLayout, sourceStage, srcAccessMask, destinationStage, dstAccessMask);
   }

   public static void transitionLayout(MemoryStack stack, VkCommandBuffer commandBuffer, VulkanImage image, int baseLevel, int oldLayout, int newLayout, int sourceStage, int srcAccessMask, int destinationStage, int dstAccessMask) {
      VkImageMemoryBarrier.Buffer barrier = VkImageMemoryBarrier.calloc(1, stack);
      barrier.sType(45);
      barrier.oldLayout(image.currentLayout);
      barrier.newLayout(newLayout);
      barrier.srcQueueFamilyIndex(-1);
      barrier.dstQueueFamilyIndex(-1);
      barrier.image(image.getId());
      barrier.subresourceRange().baseMipLevel(baseLevel);
      barrier.subresourceRange().levelCount(-1);
      barrier.subresourceRange().baseArrayLayer(0);
      barrier.subresourceRange().layerCount(-1);
      barrier.subresourceRange().aspectMask(image.aspect);
      barrier.srcAccessMask(srcAccessMask);
      barrier.dstAccessMask(dstAccessMask);
      VK10.vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, (VkMemoryBarrier.Buffer)null, (VkBufferMemoryBarrier.Buffer)null, barrier);
      image.currentLayout = newLayout;
   }

   private static boolean hasStencilComponent(int format) {
      return format == 130 || format == 129;
   }

   public void free() {
      MemoryManager.getInstance().addToFreeable(this);
   }

   public void doFree() {
      if (this.id != 0L) {
         MemoryManager.freeImage(this.id, this.allocation);
         VK10.vkDestroyImageView(Vulkan.getVkDevice(), this.mainImageView, (VkAllocationCallbacks)null);
         if (this.levelImageViews != null) {
            Arrays.stream(this.levelImageViews).forEach((imageView) -> {
               if (imageView != 0L) {
                  VK10.vkDestroyImageView(Vulkan.getVkDevice(), imageView, (VkAllocationCallbacks)null);
               }

            });
         }

         this.id = 0L;
      }
   }

   public int getCurrentLayout() {
      return this.currentLayout;
   }

   public void setCurrentLayout(int currentLayout) {
      this.currentLayout = currentLayout;
   }

   public long getId() {
      return this.id;
   }

   public long getAllocation() {
      return this.allocation;
   }

   public long getImageView() {
      return this.mainImageView;
   }

   public long getLevelImageView(int i) {
      if (this.levelImageViews[i] == 0L) {
         this.levelImageViews[i] = createImageView(this.id, 1, this.format, this.aspect, this.arrayLayers, i, 1);
      }

      return this.levelImageViews[i];
   }

   public long[] getLevelImageViews() {
      return this.levelImageViews;
   }

   public long getSampler() {
      return this.sampler;
   }

   public static Builder builder(int width, int height) {
      return new Builder(width, height);
   }

   public static class Builder {
      final int width;
      final int height;
      String name;
      int format;
      int formatSize;
      int arrayLayers;
      byte mipLevels;
      int usage;
      int viewType;
      boolean linearFiltering;
      boolean clamp;
      int reductionMode;

      public Builder(int width, int height) {
         this.format = VulkanImage.DefaultFormat;
         this.arrayLayers = 1;
         this.mipLevels = 1;
         this.usage = 7;
         this.viewType = 1;
         this.linearFiltering = false;
         this.clamp = false;
         this.reductionMode = -1;
         this.width = width;
         this.height = height;
      }

      public Builder setName(String name) {
         this.name = name;
         return this;
      }

      public Builder setFormat(int format) {
         this.format = format;
         return this;
      }

      public Builder setArrayLayers(int n) {
         this.arrayLayers = (byte)n;
         return this;
      }

      public Builder setMipLevels(int n) {
         this.mipLevels = (byte)n;
         return this;
      }

      public Builder setUsage(int usage) {
         this.usage = usage;
         return this;
      }

      public Builder addUsage(int usage) {
         this.usage |= usage;
         return this;
      }

      public Builder setViewType(int viewType) {
         this.viewType = viewType;
         return this;
      }

      public Builder setLinearFiltering(boolean b) {
         this.linearFiltering = b;
         return this;
      }

      public Builder setClamp(boolean b) {
         this.clamp = b;
         return this;
      }

      public Builder setSamplerReductionMode(int reductionMode) {
         this.reductionMode = reductionMode;
         return this;
      }

      public VulkanImage createVulkanImage() {
         this.formatSize = formatSize(this.format);
         return VulkanImage.createTextureImage(this);
      }

      private static int formatSize(int format) {
         byte var10000;
         switch (format) {
            case 9:
               var10000 = 1;
               break;
            case 37:
            case 41:
            case 42:
            case 43:
            case 44:
            case 50:
            case 100:
            case 126:
            case 129:
               var10000 = 4;
               break;
            case 76:
               var10000 = 2;
               break;
            case 97:
               var10000 = 8;
               break;
            default:
               throw new IllegalArgumentException(String.format("Unxepcted format: %s", format));
         }

         return var10000;
      }
   }
}
