package net.vulkanmod.gl;

import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import net.vulkanmod.Initializer;
import net.vulkanmod.vulkan.memory.MemoryManager;
import net.vulkanmod.vulkan.texture.ImageUtil;
import net.vulkanmod.vulkan.texture.SamplerManager;
import net.vulkanmod.vulkan.texture.VTextureSelector;
import net.vulkanmod.vulkan.texture.VulkanImage;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;

public class VkGlTexture {
   private static int ID_COUNTER = 1;
   private static final Int2ReferenceOpenHashMap<VkGlTexture> map = new Int2ReferenceOpenHashMap();
   private static int boundTextureId = 0;
   private static VkGlTexture boundTexture;
   private static int activeTexture = 0;
   private static int unpackRowLength;
   private static int unpackSkipRows;
   private static int unpackSkipPixels;
   public final int id;
   VulkanImage vulkanImage;
   int width;
   int height;
   int vkFormat;
   boolean needsUpdate = false;
   int maxLevel = 0;
   int maxLod = 0;
   int minFilter = 9728;
   int magFilter = 9728;
   boolean clamp = true;

   public static void bindIdToImage(int id, VulkanImage vulkanImage) {
      VkGlTexture texture = (VkGlTexture)map.get(id);
      texture.vulkanImage = vulkanImage;
   }

   public static int genTextureId() {
      int id = ID_COUNTER;
      map.put(id, new VkGlTexture(id));
      ++ID_COUNTER;
      return id;
   }

   public static void bindTexture(int id) {
      boundTextureId = id;
      boundTexture = (VkGlTexture)map.get(id);
      if (id > 0) {
         if (boundTexture == null) {
            Initializer.LOGGER.error("Invalid id({}) value", id);
         } else {
            VulkanImage vulkanImage = boundTexture.vulkanImage;
            if (vulkanImage != null) {
               VTextureSelector.bindTexture(activeTexture, vulkanImage);
            }

         }
      }
   }

   public static void glDeleteTextures(IntBuffer intBuffer) {
      for(int i = intBuffer.position(); i < intBuffer.limit(); ++i) {
         glDeleteTextures(intBuffer.get(i));
      }

   }

   public static void glDeleteTextures(int i) {
      VkGlTexture glTexture = (VkGlTexture)map.remove(i);
      VulkanImage image = glTexture != null ? glTexture.vulkanImage : null;
      if (image != null) {
         MemoryManager.getInstance().addToFreeable(image);
      }

   }

   public static VkGlTexture getTexture(int id) {
      return id == 0 ? null : (VkGlTexture)map.get(id);
   }

   public static void activeTexture(int i) {
      activeTexture = i - '蓀';
      VTextureSelector.setActiveTexture(activeTexture);
   }

   public static void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, long pixels) {
      if (!checkParams(level, width, height)) {
         boundTexture.updateParams(level, width, height, internalFormat, type);
         boundTexture.allocateIfNeeded();
         VTextureSelector.bindTexture(activeTexture, boundTexture.vulkanImage);
         texSubImage2D(target, level, 0, 0, width, height, format, type, pixels);
      }
   }

   public static void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
      if (!checkParams(level, width, height)) {
         boundTexture.updateParams(level, width, height, internalFormat, type);
         boundTexture.allocateIfNeeded();
         VTextureSelector.bindTexture(activeTexture, boundTexture.vulkanImage);
         texSubImage2D(target, level, 0, 0, width, height, format, type, pixels);
      }
   }

   private static boolean checkParams(int level, int width, int height) {
      return width == 0 || height == 0;
   }

   public static void texSubImage2D(int target, int level, int xOffset, int yOffset, int width, int height, int format, int type, long pixels) {
      if (width != 0 && height != 0) {
         VkGlBuffer glBuffer = VkGlBuffer.getPixelUnpackBufferBound();
         ByteBuffer src;
         if (glBuffer != null) {
            glBuffer.data.position((int)pixels);
            src = glBuffer.data;
         } else if (pixels != 0L) {
            src = getByteBuffer(width, height, pixels);
         } else {
            src = null;
         }

         if (src != null) {
            boundTexture.uploadSubImage(level, xOffset, yOffset, width, height, format, src);
         }

      }
   }

   private static ByteBuffer getByteBuffer(int width, int height, long pixels) {
      int formatSize = 4;
      int rowLength = unpackRowLength != 0 ? unpackRowLength : width;
      int offset = (unpackSkipRows * rowLength + unpackSkipPixels) * formatSize;
      ByteBuffer src = MemoryUtil.memByteBuffer(pixels + (long)offset, (rowLength * height - unpackSkipPixels) * formatSize);
      return src;
   }

   public static void texSubImage2D(int target, int level, int xOffset, int yOffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels) {
      if (width != 0 && height != 0) {
         VkGlBuffer glBuffer = VkGlBuffer.getPixelUnpackBufferBound();
         ByteBuffer src;
         if (glBuffer != null) {
            if (pixels != null) {
               throw new IllegalStateException("Trying to use pixel buffer when there is a Pixel Unpack Buffer bound.");
            }

            glBuffer.data.position(0);
            src = glBuffer.data;
         } else {
            src = pixels;
         }

         if (src != null) {
            boundTexture.uploadSubImage(level, xOffset, yOffset, width, height, format, src);
         }

      }
   }

   public static void texParameteri(int target, int pName, int param) {
      if (target != 3553) {
         throw new UnsupportedOperationException("target != GL_TEXTURE_2D not supported");
      } else if (boundTexture != null) {
         switch (pName) {
            case 10240:
               boundTexture.setMagFilter(param);
               break;
            case 10241:
               boundTexture.setMinFilter(param);
               break;
            case 10242:
            case 10243:
               boundTexture.setClamp(param);
            case 33082:
            case 34049:
            default:
               break;
            case 33083:
               boundTexture.setMaxLod(param);
               break;
            case 33085:
               boundTexture.setMaxLevel(param);
         }

      }
   }

   public static int getTexParameteri(int target, int pName) {
      if (target != 3553) {
         throw new UnsupportedOperationException("target != GL_TEXTURE_2D not supported");
      } else if (boundTexture == null) {
         return -1;
      } else {
         int var10000;
         switch (pName) {
            case 4096 -> var10000 = boundTexture.vulkanImage.width;
            case 4097 -> var10000 = boundTexture.vulkanImage.height;
            case 4099 -> var10000 = GlUtil.getGlFormat(boundTexture.vulkanImage.format);
            case 10240 -> var10000 = boundTexture.magFilter;
            case 10241 -> var10000 = boundTexture.minFilter;
            case 33083 -> var10000 = boundTexture.maxLod;
            case 33085 -> var10000 = boundTexture.maxLevel;
            default -> var10000 = -1;
         }

         return var10000;
      }
   }

   public static int getTexLevelParameter(int target, int level, int pName) {
      if (target != 3553) {
         throw new UnsupportedOperationException("target != GL_TEXTURE_2D not supported");
      } else if (boundTexture == null) {
         return -1;
      } else {
         int var10000;
         switch (pName) {
            case 4096:
               var10000 = boundTexture.vulkanImage.width;
               break;
            case 4097:
               var10000 = boundTexture.vulkanImage.height;
               break;
            case 4098:
            default:
               var10000 = -1;
               break;
            case 4099:
               var10000 = GlUtil.getGlFormat(boundTexture.vulkanImage.format);
         }

         return var10000;
      }
   }

   public static void pixelStoreI(int pName, int value) {
      switch (pName) {
         case 3314 -> unpackRowLength = value;
         case 3315 -> unpackSkipRows = value;
         case 3316 -> unpackSkipPixels = value;
      }

   }

   public static void generateMipmap(int target) {
      if (target != 3553) {
         throw new UnsupportedOperationException("target != GL_TEXTURE_2D not supported");
      }
   }

   public static void getTexImage(int tex, int level, int format, int type, long pixels) {
      VulkanImage image = boundTexture.vulkanImage;
      VkGlBuffer buffer = VkGlBuffer.getPixelPackBufferBound();
      long ptr;
      if (buffer != null) {
         buffer.data.position((int)pixels);
         ptr = MemoryUtil.memAddress(buffer.data);
      } else {
         ptr = pixels;
      }

      ImageUtil.downloadTexture(image, ptr);
   }

   public static void setVulkanImage(int id, VulkanImage vulkanImage) {
      VkGlTexture texture = (VkGlTexture)map.get(id);
      texture.vulkanImage = vulkanImage;
   }

   public static VkGlTexture getBoundTexture() {
      return boundTexture;
   }

   public VkGlTexture(int id) {
      this.id = id;
   }

   void updateParams(int level, int width, int height, int internalFormat, int type) {
      if (level > this.maxLevel) {
         this.maxLevel = level;
         this.needsUpdate = true;
      }

      if (level == 0) {
         int vkFormat = GlUtil.vulkanFormat(internalFormat, type);
         if (this.vulkanImage == null || this.width != width || this.height != height || vkFormat != this.vulkanImage.format) {
            this.width = width;
            this.height = height;
            this.vkFormat = vkFormat;
            this.needsUpdate = true;
         }
      }

   }

   void allocateIfNeeded() {
      if (this.needsUpdate) {
         this.allocateImage(this.width, this.height, this.vkFormat);
         this.updateSampler();
         this.needsUpdate = false;
      }

   }

   void allocateImage(int width, int height, int vkFormat) {
      if (this.vulkanImage != null) {
         this.vulkanImage.free();
      }

      if (VulkanImage.isDepthFormat(vkFormat)) {
         this.vulkanImage = VulkanImage.createDepthImage(vkFormat, width, height, 37, false, true);
      } else {
         this.vulkanImage = (new VulkanImage.Builder(width, height)).setName(String.format("GlTexture %d", this.id)).setMipLevels(this.maxLevel + 1).setFormat(vkFormat).addUsage(16).createVulkanImage();
      }

   }

   void updateSampler() {
      if (this.vulkanImage != null) {
         int addressMode = this.clamp ? 2 : 0;
         int vkMinFilter;
         int mipmapMode;
         switch (this.minFilter) {
            case 9728:
            case 9984:
               vkMinFilter = 0;
               mipmapMode = 0;
               break;
            case 9729:
            case 9987:
               vkMinFilter = 1;
               mipmapMode = 1;
               break;
            case 9985:
               vkMinFilter = 1;
               mipmapMode = 0;
               break;
            case 9986:
               vkMinFilter = 0;
               mipmapMode = 1;
               break;
            default:
               throw new IllegalStateException("Unexpected min filter value: %d".formatted(this.minFilter));
         }

         byte var10000;
         switch (this.magFilter) {
            case 9728 -> var10000 = 0;
            case 9729 -> var10000 = 1;
            default -> throw new IllegalStateException("Unexpected mag filter value: %d".formatted(this.magFilter));
         }

         int vkMagFilter = var10000;
         long sampler = SamplerManager.getSampler(addressMode, addressMode, vkMinFilter, vkMagFilter, mipmapMode, (float)this.maxLod, false, 0.0F, -1);
         this.vulkanImage.setSampler(sampler);
      }
   }

   private void uploadSubImage(int level, int xOffset, int yOffset, int width, int height, int format, ByteBuffer pixels) {
      ByteBuffer src;
      if (format == 6407 && this.vulkanImage.format == 37) {
         src = GlUtil.RGBtoRGBA_buffer(pixels);
      } else if (format == 32993 && this.vulkanImage.format == 37) {
         src = GlUtil.BGRAtoRGBA_buffer(pixels);
      } else {
         src = pixels;
      }

      this.vulkanImage.uploadSubTextureAsync(level, width, height, xOffset, yOffset, 0, 0, unpackRowLength, src);
      if (src != pixels) {
         MemoryUtil.memFree(src);
      }

   }

   void generateMipmaps() {
      ImageUtil.generateMipmaps(this.vulkanImage);
   }

   void setMaxLevel(int l) {
      if (l < 0) {
         throw new IllegalStateException("max level cannot be < 0.");
      } else {
         if (this.maxLevel != l) {
            this.maxLevel = l;
            this.needsUpdate = true;
         }

      }
   }

   void setMaxLod(int l) {
      if (l < 0) {
         throw new IllegalStateException("max level cannot be < 0.");
      } else {
         if (this.maxLod != l) {
            this.maxLod = l;
            this.updateSampler();
         }

      }
   }

   void setMagFilter(int v) {
      switch (v) {
         case 9728:
         case 9729:
            this.magFilter = v;
            this.updateSampler();
            return;
         default:
            throw new IllegalArgumentException("illegal mag filter value: " + v);
      }
   }

   void setMinFilter(int v) {
      switch (v) {
         case 9728:
         case 9729:
         case 9984:
         case 9985:
         case 9986:
         case 9987:
            this.minFilter = v;
            this.updateSampler();
            return;
         default:
            throw new IllegalArgumentException("illegal min filter value: " + v);
      }
   }

   void setClamp(int v) {
      if (v == 33071) {
         this.clamp = true;
      } else {
         this.clamp = false;
      }

      this.updateSampler();
   }

   public VulkanImage getVulkanImage() {
      return this.vulkanImage;
   }

   public void setVulkanImage(VulkanImage vulkanImage) {
      this.vulkanImage = vulkanImage;
      this.width = vulkanImage.width;
      this.height = vulkanImage.height;
      this.maxLevel = vulkanImage.mipLevels;
      this.vkFormat = vulkanImage.format;
   }
}
