package net.vulkanmod.render.sky;

import com.mojang.blaze3d.opengl.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.VertexFormat.class_5596;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import net.minecraft.class_1011;
import net.minecraft.class_243;
import net.minecraft.class_287;
import net.minecraft.class_289;
import net.minecraft.class_290;
import net.minecraft.class_2960;
import net.minecraft.class_310;
import net.minecraft.class_3298;
import net.minecraft.class_3300;
import net.minecraft.class_3532;
import net.minecraft.class_4063;
import net.minecraft.class_638;
import net.minecraft.class_9801;
import net.vulkanmod.render.PipelineManager;
import net.vulkanmod.render.VBO;
import net.vulkanmod.vulkan.Renderer;
import net.vulkanmod.vulkan.VRenderSystem;
import net.vulkanmod.vulkan.shader.GraphicsPipeline;
import net.vulkanmod.vulkan.util.ColorUtil;
import org.apache.commons.lang3.Validate;
import org.joml.Matrix4fStack;

public class CloudRenderer {
   private static final class_2960 TEXTURE_LOCATION = class_2960.method_60656("textures/environment/clouds.png");
   private static final int DIR_NEG_Y_BIT = 1;
   private static final int DIR_POS_Y_BIT = 2;
   private static final int DIR_NEG_X_BIT = 4;
   private static final int DIR_POS_X_BIT = 8;
   private static final int DIR_NEG_Z_BIT = 16;
   private static final int DIR_POS_Z_BIT = 32;
   private static final byte Y_BELOW_CLOUDS = 0;
   private static final byte Y_ABOVE_CLOUDS = 1;
   private static final byte Y_INSIDE_CLOUDS = 2;
   private static final int CELL_WIDTH = 12;
   private static final int CELL_HEIGHT = 4;
   private CloudGrid cloudGrid;
   private int prevCloudX;
   private int prevCloudZ;
   private byte prevCloudY;
   private class_4063 prevCloudsType;
   private boolean generateClouds;
   private VBO cloudBuffer;

   public CloudRenderer() {
      this.loadTexture();
   }

   public void loadTexture() {
      this.cloudGrid = createCloudGrid(TEXTURE_LOCATION);
   }

   public void renderClouds(class_638 level, float ticks, float partialTicks, double camX, double camY, double camZ) {
      Optional<Integer> optional = level.method_8597().comp_4037();
      if (!optional.isEmpty()) {
         class_310 minecraft = class_310.method_1551();
         int cloudHeight = (Integer)optional.get();
         double timeOffset = (double)((ticks + partialTicks) * 0.03F);
         double centerX = camX + timeOffset;
         double centerZ = camZ + (double)3.96F;
         double centerY = (double)((float)cloudHeight - (float)camY + 0.33F);
         int centerCellX = (int)Math.floor(centerX / (double)12.0F);
         int centerCellZ = (int)Math.floor(centerZ / (double)12.0F);
         byte yState;
         if (centerY < (double)-4.0F) {
            yState = 0;
         } else if (centerY > (double)0.0F) {
            yState = 1;
         } else {
            yState = 2;
         }

         if (centerCellX != this.prevCloudX || centerCellZ != this.prevCloudZ || minecraft.field_1690.method_1632() != this.prevCloudsType || this.prevCloudY != yState || this.cloudBuffer == null) {
            this.prevCloudX = centerCellX;
            this.prevCloudZ = centerCellZ;
            this.prevCloudsType = minecraft.field_1690.method_1632();
            this.prevCloudY = yState;
            this.generateClouds = true;
         }

         if (this.generateClouds) {
            this.generateClouds = false;
            if (this.cloudBuffer != null) {
               this.cloudBuffer.close();
            }

            this.resetBuffer();
            class_9801 cloudsMesh = this.buildClouds(class_289.method_1348(), centerCellX, centerCellZ, centerY);
            if (cloudsMesh == null) {
               return;
            }

            this.cloudBuffer = new VBO(true);
            this.cloudBuffer.upload(cloudsMesh);
         }

         if (this.cloudBuffer != null) {
            float xTranslation = (float)(centerX - (double)(centerCellX * 12));
            float yTranslation = (float)centerY;
            float zTranslation = (float)(centerZ - (double)(centerCellZ * 12));
            Renderer.getInstance().getMainPass().rebindMainTarget();
            Matrix4fStack poseStack = RenderSystem.getModelViewStack();
            poseStack.pushMatrix();
            poseStack.translate(-xTranslation, yTranslation, -zTranslation);
            VRenderSystem.applyModelViewMatrix(poseStack);
            VRenderSystem.calculateMVP();
            VRenderSystem.setModelOffset(-xTranslation, 0.0F, -zTranslation);
            class_243 cloudColor = class_243.method_24457(level.method_23785(partialTicks));
            VRenderSystem.setShaderColor((float)cloudColor.field_1352, (float)cloudColor.field_1351, (float)cloudColor.field_1350, 0.8F);
            GraphicsPipeline pipeline = PipelineManager.getCloudsPipeline();
            VRenderSystem.enableBlend();
            VRenderSystem.blendFuncSeparate(770, 771, 1, 0);
            VRenderSystem.enableDepthTest();
            VRenderSystem.depthFunc(515);
            GlStateManager._enableDepthTest();
            GlStateManager._depthMask(true);
            GlStateManager._colorMask(true, true, true, true);
            GlStateManager._disablePolygonOffset();
            VRenderSystem.setPolygonModeGL(6914);
            VRenderSystem.setPrimitiveTopologyGL(4);
            boolean fastClouds = this.prevCloudsType == class_4063.field_18163;
            boolean insideClouds = yState == 2;
            boolean disableCull = insideClouds || fastClouds && centerY <= (double)0.0F;
            if (disableCull) {
               VRenderSystem.disableCull();
            } else {
               VRenderSystem.enableCull();
            }

            if (!fastClouds) {
               VRenderSystem.colorMask(false, false, false, false);
               this.cloudBuffer.bind(pipeline);
               this.cloudBuffer.draw();
               VRenderSystem.colorMask(true, true, true, true);
            }

            this.cloudBuffer.bind(pipeline);
            this.cloudBuffer.draw();
            poseStack.popMatrix();
            VRenderSystem.enableCull();
            VRenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
            VRenderSystem.setModelOffset(0.0F, 0.0F, 0.0F);
         }
      }
   }

   public void resetBuffer() {
      if (this.cloudBuffer != null) {
         this.cloudBuffer.close();
         this.cloudBuffer = null;
      }

   }

   private class_9801 buildClouds(class_289 tesselator, int centerCellX, int centerCellZ, double cloudY) {
      float upFaceBrightness = 1.0F;
      float xDirBrightness = 0.9F;
      float downFaceBrightness = 0.7F;
      float zDirBrightness = 0.8F;
      class_287 bufferBuilder = tesselator.method_60827(class_5596.field_27382, class_290.field_1576);
      int cloudRange = Math.min((Integer)class_310.method_1551().field_1690.method_71270().method_41753(), 128) * 16;
      int renderDistance = class_3532.method_15386((float)cloudRange / 12.0F);
      boolean insideClouds = this.prevCloudY == 2;
      if (this.prevCloudsType == class_4063.field_18164) {
         for(int cellX = -renderDistance; cellX < renderDistance; ++cellX) {
            for(int cellZ = -renderDistance; cellZ < renderDistance; ++cellZ) {
               int cellIdx = this.cloudGrid.getWrappedIdx(centerCellX + cellX, centerCellZ + cellZ);
               byte renderFaces = this.cloudGrid.renderFaces[cellIdx];
               int baseColor = this.cloudGrid.pixels[cellIdx];
               float x = (float)(cellX * 12);
               float z = (float)(cellZ * 12);
               if ((renderFaces & 2) != 0 && cloudY <= (double)0.0F) {
                  int color = ColorUtil.ARGB.multiplyRGB(baseColor, 1.0F);
                  putVertex(bufferBuilder, x + 12.0F, 4.0F, z + 12.0F, color);
                  putVertex(bufferBuilder, x + 12.0F, 4.0F, z + 0.0F, color);
                  putVertex(bufferBuilder, x + 0.0F, 4.0F, z + 0.0F, color);
                  putVertex(bufferBuilder, x + 0.0F, 4.0F, z + 12.0F, color);
               }

               if ((renderFaces & 1) != 0 && cloudY >= (double)-4.0F) {
                  int color = ColorUtil.ARGB.multiplyRGB(baseColor, 0.7F);
                  putVertex(bufferBuilder, x + 0.0F, 0.0F, z + 12.0F, color);
                  putVertex(bufferBuilder, x + 0.0F, 0.0F, z + 0.0F, color);
                  putVertex(bufferBuilder, x + 12.0F, 0.0F, z + 0.0F, color);
                  putVertex(bufferBuilder, x + 12.0F, 0.0F, z + 12.0F, color);
               }

               if ((renderFaces & 8) != 0 && (x < 1.0F || insideClouds)) {
                  int color = ColorUtil.ARGB.multiplyRGB(baseColor, 0.9F);
                  putVertex(bufferBuilder, x + 12.0F, 4.0F, z + 12.0F, color);
                  putVertex(bufferBuilder, x + 12.0F, 0.0F, z + 12.0F, color);
                  putVertex(bufferBuilder, x + 12.0F, 0.0F, z + 0.0F, color);
                  putVertex(bufferBuilder, x + 12.0F, 4.0F, z + 0.0F, color);
               }

               if ((renderFaces & 4) != 0 && (x > -1.0F || insideClouds)) {
                  int color = ColorUtil.ARGB.multiplyRGB(baseColor, 0.9F);
                  putVertex(bufferBuilder, x + 0.0F, 4.0F, z + 0.0F, color);
                  putVertex(bufferBuilder, x + 0.0F, 0.0F, z + 0.0F, color);
                  putVertex(bufferBuilder, x + 0.0F, 0.0F, z + 12.0F, color);
                  putVertex(bufferBuilder, x + 0.0F, 4.0F, z + 12.0F, color);
               }

               if ((renderFaces & 32) != 0 && (z < 1.0F || insideClouds)) {
                  int color = ColorUtil.ARGB.multiplyRGB(baseColor, 0.8F);
                  putVertex(bufferBuilder, x + 0.0F, 4.0F, z + 12.0F, color);
                  putVertex(bufferBuilder, x + 0.0F, 0.0F, z + 12.0F, color);
                  putVertex(bufferBuilder, x + 12.0F, 0.0F, z + 12.0F, color);
                  putVertex(bufferBuilder, x + 12.0F, 4.0F, z + 12.0F, color);
               }

               if ((renderFaces & 16) != 0 && (z > -1.0F || insideClouds)) {
                  int color = ColorUtil.ARGB.multiplyRGB(baseColor, 0.8F);
                  putVertex(bufferBuilder, x + 12.0F, 4.0F, z + 0.0F, color);
                  putVertex(bufferBuilder, x + 12.0F, 0.0F, z + 0.0F, color);
                  putVertex(bufferBuilder, x + 0.0F, 0.0F, z + 0.0F, color);
                  putVertex(bufferBuilder, x + 0.0F, 4.0F, z + 0.0F, color);
               }
            }
         }
      } else {
         for(int cellX = -renderDistance; cellX < renderDistance; ++cellX) {
            for(int cellZ = -renderDistance; cellZ < renderDistance; ++cellZ) {
               int cellIdx = this.cloudGrid.getWrappedIdx(centerCellX + cellX, centerCellZ + cellZ);
               byte renderFaces = this.cloudGrid.renderFaces[cellIdx];
               int baseColor = this.cloudGrid.pixels[cellIdx];
               float x = (float)(cellX * 12);
               float z = (float)(cellZ * 12);
               if ((renderFaces & 1) != 0) {
                  int color = ColorUtil.ARGB.multiplyRGB(baseColor, 1.0F);
                  putVertex(bufferBuilder, x + 0.0F, 0.0F, z + 12.0F, color);
                  putVertex(bufferBuilder, x + 0.0F, 0.0F, z + 0.0F, color);
                  putVertex(bufferBuilder, x + 12.0F, 0.0F, z + 0.0F, color);
                  putVertex(bufferBuilder, x + 12.0F, 0.0F, z + 12.0F, color);
               }
            }
         }
      }

      return bufferBuilder.method_60794();
   }

   private static void putVertex(class_287 bufferBuilder, float x, float y, float z, int color) {
      bufferBuilder.method_22912(x, y, z).method_39415(color);
   }

   private static CloudGrid createCloudGrid(class_2960 textureLocation) {
      class_3300 resourceManager = class_310.method_1551().method_1478();

      try {
         class_3298 resource = resourceManager.getResourceOrThrow(textureLocation);
         InputStream inputStream = resource.method_14482();

         CloudGrid var8;
         try {
            class_1011 image = class_1011.method_4309(inputStream);
            int width = image.method_4307();
            int height = image.method_4323();
            Validate.isTrue(width == height, "Image width and height must be the same", new Object[0]);
            int[] pixels = image.method_48463();
            var8 = new CloudGrid(pixels, width);
         } catch (Throwable var10) {
            if (inputStream != null) {
               try {
                  inputStream.close();
               } catch (Throwable var9) {
                  var10.addSuppressed(var9);
               }
            }

            throw var10;
         }

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

         return var8;
      } catch (IOException e) {
         throw new RuntimeException(e);
      }
   }

   static class CloudGrid {
      final int width;
      final int[] pixels;
      final byte[] renderFaces;

      CloudGrid(int[] pixels, int width) {
         this.pixels = pixels;
         this.width = width;
         this.renderFaces = this.computeRenderFaces();
      }

      byte[] computeRenderFaces() {
         byte[] renderFaces = new byte[this.pixels.length];

         for(int z = 0; z < this.width; ++z) {
            for(int x = 0; x < this.width; ++x) {
               int idx = this.getIdx(x, z);
               int pixel = this.pixels[idx];
               if (hasColor(pixel)) {
                  byte faces = 3;
                  int adjPixel = this.getTexelWrapped(x - 1, z);
                  if (pixel != adjPixel) {
                     faces = (byte)(faces | 4);
                  }

                  adjPixel = this.getTexelWrapped(x + 1, z);
                  if (pixel != adjPixel) {
                     faces = (byte)(faces | 8);
                  }

                  adjPixel = this.getTexelWrapped(x, z - 1);
                  if (pixel != adjPixel) {
                     faces = (byte)(faces | 16);
                  }

                  adjPixel = this.getTexelWrapped(x, z + 1);
                  if (pixel != adjPixel) {
                     faces = (byte)(faces | 32);
                  }

                  renderFaces[idx] = faces;
               }
            }
         }

         return renderFaces;
      }

      int getTexelWrapped(int x, int z) {
         if (x < 0) {
            x = this.width - 1;
         }

         if (x > this.width - 1) {
            x = 0;
         }

         if (z < 0) {
            z = this.width - 1;
         }

         if (z > this.width - 1) {
            z = 0;
         }

         return this.pixels[this.getIdx(x, z)];
      }

      int getWrappedIdx(int x, int z) {
         x = Math.floorMod(x, this.width);
         z = Math.floorMod(z, this.width);
         return this.getIdx(x, z);
      }

      int getIdx(int x, int z) {
         return z * this.width + x;
      }

      private static boolean hasColor(int pixel) {
         return (pixel >> 24 & 255) > 1;
      }
   }
}
