package dev.crucifixbeta.mod.modules.impl.render;

import com.mojang.blaze3d.platform.GlStateManager.DstFactor;
import com.mojang.blaze3d.platform.GlStateManager.SrcFactor;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.systems.VertexSorter;
import dev.crucifixbeta.mod.modules.Module;
import dev.crucifixbeta.mod.modules.settings.impl.BooleanSetting;
import dev.crucifixbeta.mod.modules.settings.impl.SliderSetting;
import dev.crucifixbeta.mod.modules.settings.impl.StringSetting;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.math.RotationAxis;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL30C;

@Environment(EnvType.CLIENT)
public class SkyShader extends Module {
   public static SkyShader INSTANCE;
   public final BooleanSetting enabled = this.add(new BooleanSetting("Enabled", true));
   public final SliderSetting speed = this.add(new SliderSetting("Speed", 1.0, 0.1, 5.0));
   public final SliderSetting opacity = this.add(new SliderSetting("Opacity", 0.8, 0.0, 1.0));
   public final StringSetting customShaderPath = this.add(new StringSetting("ShaderPath", "sky.frag"));
   private float time;
   private int customProgram = -1;
   private int vao = -1;
   private int vbo = -1;
   private String lastShaderPath = "";

   public SkyShader() {
      super("SkyShader", Module.Category.Render);
      this.setChinese("\u5929\u7a7a\u7740\u8272\u5668");
      INSTANCE = this;
      this.ensureDefaultShader();
   }

   public static SkyShader getInstance() {
      return INSTANCE;
   }

   @Override
   public void onDisable() {
      this.deleteShader();
   }

   private void ensureDefaultShader() {
      File var1 = new File(MinecraftClient.getInstance().runDirectory, "crucifixbeta/shaders");
      if (var1.exists() || var1.mkdirs()) {
         File var2 = new File(var1, "sky.frag");
         if (!var2.exists()) {
            String var3 = "#version 150\nuniform vec2 u_Resolution;\nuniform float u_Time;\nuniform float u_Opacity;\nin vec3 vPos;\nout vec4 fragColor;\n\nvoid main() {\n    vec2 uv = normalize(vPos).xy;\n    float t = u_Time * 0.5;\n    vec3 color = vec3(0.0);\n    color.r = 0.5 + 0.5 * cos(t + uv.x * 2.0);\n    color.g = 0.5 + 0.5 * cos(t + uv.y * 2.0 + 2.0);\n    color.b = 0.5 + 0.5 * cos(t + (uv.x + uv.y) * 2.0 + 4.0);\n    float stars = fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453);\n    if (stars > 0.99) color += vec3(1.0);\n    fragColor = vec4(color, u_Opacity);\n}\n";

            try {
               Files.writeString(var2.toPath(), var3, StandardCharsets.UTF_8);
            } catch (Exception var5) {
               var5.printStackTrace();
            }
         }
      }
   }

   private void initShader() {
      String var1 = this.customShaderPath.getValue();
      if (!var1.equals(this.lastShaderPath) || this.customProgram == -1) {
         this.deleteShader();
         this.lastShaderPath = var1;
         File var2 = new File(mc.runDirectory, "crucifixbeta/shaders/" + var1);
         if (!var2.exists()) {
            var2 = new File(var1);
         }

         if (!var2.exists()) {
            System.out.println("[SkyShader] Shader file not found: " + var1);
         } else {
            try {
               String var3 = Files.readString(var2.toPath(), StandardCharsets.UTF_8);
               String var4 = "#version 150\nin vec3 Position;\nuniform mat4 ProjMat;\nuniform mat4 ModelViewMat;\nout vec3 vPos;\nvoid main() {\n    gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0);\n    vPos = Position;\n}\n";
               int var5 = this.compileShader(35633, var4, "Vertex");
               if (var5 < 0) {
                  return;
               }

               int var6 = this.compileShader(35632, var3, "Fragment");
               if (var6 < 0) {
                  GL30C.glDeleteShader(var5);
                  return;
               }

               this.customProgram = GL30C.glCreateProgram();
               GL30C.glAttachShader(this.customProgram, var5);
               GL30C.glAttachShader(this.customProgram, var6);
               GL30C.glBindAttribLocation(this.customProgram, 0, "Position");
               GL30C.glLinkProgram(this.customProgram);
               GL30C.glDeleteShader(var5);
               GL30C.glDeleteShader(var6);
               if (GL30C.glGetProgrami(this.customProgram, 35714) == 0) {
                  System.out.println("[SkyShader] Program link error: " + GL30C.glGetProgramInfoLog(this.customProgram, 1024));
                  this.deleteShader();
                  return;
               }

               this.initBuffers();
            } catch (Exception var7) {
               var7.printStackTrace();
               this.deleteShader();
            }
         }
      }
   }

   private int compileShader(int type, String source, String label) {
      int var4 = GL30C.glCreateShader(type);
      GL30C.glShaderSource(var4, source);
      GL30C.glCompileShader(var4);
      if (GL30C.glGetShaderi(var4, 35713) == 0) {
         System.out.println("[SkyShader] " + label + " shader compile error: " + GL30C.glGetShaderInfoLog(var4, 1024));
         GL30C.glDeleteShader(var4);
         return -1;
      } else {
         return var4;
      }
   }

   private void initBuffers() {
      if (this.vao == -1) {
         this.vao = GL30C.glGenVertexArrays();
         this.vbo = GL30C.glGenBuffers();
         GL30C.glBindVertexArray(this.vao);
         GL30C.glBindBuffer(34962, this.vbo);
         float[] var1 = new float[]{
            -100.0F,
            -100.0F,
            -100.0F,
            100.0F,
            -100.0F,
            -100.0F,
            -100.0F,
            100.0F,
            -100.0F,
            100.0F,
            100.0F,
            -100.0F,
            100.0F,
            -100.0F,
            100.0F,
            -100.0F,
            -100.0F,
            100.0F,
            100.0F,
            100.0F,
            100.0F,
            -100.0F,
            100.0F,
            100.0F,
            -100.0F,
            -100.0F,
            100.0F,
            -100.0F,
            -100.0F,
            -100.0F,
            -100.0F,
            100.0F,
            100.0F,
            -100.0F,
            100.0F,
            -100.0F,
            100.0F,
            -100.0F,
            -100.0F,
            100.0F,
            -100.0F,
            100.0F,
            100.0F,
            100.0F,
            -100.0F,
            100.0F,
            100.0F,
            100.0F,
            -100.0F,
            100.0F,
            -100.0F,
            100.0F,
            100.0F,
            -100.0F,
            -100.0F,
            100.0F,
            100.0F,
            100.0F,
            100.0F,
            100.0F,
            -100.0F,
            -100.0F,
            100.0F,
            100.0F,
            -100.0F,
            100.0F,
            -100.0F,
            -100.0F,
            -100.0F,
            100.0F,
            -100.0F,
            -100.0F
         };
         GL30C.glBufferData(34962, var1, 35044);
         GL30C.glVertexAttribPointer(0, 3, 5126, false, 0, 0L);
         GL30C.glEnableVertexAttribArray(0);
         GL30C.glBindBuffer(34962, 0);
         GL30C.glBindVertexArray(0);
      }
   }

   public void renderSky(MatrixStack matrices, Matrix4f projectionMatrix, float tickDelta) {
      if (this.isOn() && this.enabled.getValue() && mc.world != null && mc.getWindow() != null) {
         this.initShader();
         if (this.customProgram != -1 && this.vao != -1) {
            this.time = this.time + this.speed.getValueFloat() * 0.01F * tickDelta;
            RenderSystem.enableBlend();
            RenderSystem.blendFunc(SrcFactor.SRC_ALPHA, DstFactor.ONE_MINUS_SRC_ALPHA);
            RenderSystem.disableCull();
            RenderSystem.depthMask(false);
            RenderSystem.backupProjectionMatrix();
            RenderSystem.setProjectionMatrix(projectionMatrix, VertexSorter.BY_Z);
            GL30C.glUseProgram(this.customProgram);
            this.setUniform1f("u_Time", this.time);
            this.setUniform2f("u_Resolution", (float)mc.getWindow().getScaledWidth(), (float)mc.getWindow().getScaledHeight());
            this.setUniform1f("u_Opacity", this.opacity.getValueFloat());
            this.setUniformMatrix("ProjMat", projectionMatrix);
            MatrixStack var4 = new MatrixStack();
            var4.multiplyPositionMatrix(matrices.peek().getPositionMatrix());
            var4.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(-90.0F));
            var4.multiply(RotationAxis.POSITIVE_X.rotationDegrees(mc.world.getSkyAngle(tickDelta) * 360.0F));
            this.setUniformMatrix("ModelViewMat", var4.peek().getPositionMatrix());
            GL30C.glBindVertexArray(this.vao);

            for (int var5 = 0; var5 < 6; var5++) {
               GL30C.glDrawArrays(5, var5 * 4, 4);
            }

            GL30C.glBindVertexArray(0);
            GL30C.glUseProgram(0);
            RenderSystem.restoreProjectionMatrix();
            RenderSystem.depthMask(true);
            RenderSystem.enableCull();
            RenderSystem.disableBlend();
            RenderSystem.defaultBlendFunc();
         }
      }
   }

   private void setUniform1f(String name, float value) {
      int var3 = GL30C.glGetUniformLocation(this.customProgram, name);
      if (var3 != -1) {
         GL30C.glUniform1f(var3, value);
      }
   }

   private void setUniform2f(String name, float x, float y) {
      int var4 = GL30C.glGetUniformLocation(this.customProgram, name);
      if (var4 != -1) {
         GL30C.glUniform2f(var4, x, y);
      }
   }

   private void setUniformMatrix(String name, Matrix4f matrix) {
      int var3 = GL30C.glGetUniformLocation(this.customProgram, name);
      if (var3 != -1) {
         float[] var4 = new float[16];
         matrix.get(var4);
         GL30C.glUniformMatrix4fv(var3, false, var4);
      }
   }

   private void deleteShader() {
      if (this.customProgram != -1) {
         GL30C.glDeleteProgram(this.customProgram);
         this.customProgram = -1;
      }
   }

   public float getTime() {
      return this.time;
   }
}
