package dev.crucifixbeta.mod.gui.fonts;

import com.mojang.blaze3d.systems.RenderSystem;
import dev.crucifixbeta.api.utils.Wrapper;
import dev.crucifixbeta.api.utils.render.Render3DUtil;
import dev.crucifixbeta.mod.gui.clickgui.ClickGuiScreen;
import dev.crucifixbeta.mod.modules.impl.client.ClickGui;
import dev.crucifixbeta.mod.modules.impl.client.ColorsModule;
import it.unimi.dsi.fastutil.chars.Char2IntArrayMap;
import it.unimi.dsi.fastutil.chars.Char2ObjectArrayMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectList;
import it.unimi.dsi.fastutil.objects.ObjectListIterator;
import java.awt.Color;
import java.awt.Font;
import java.io.Closeable;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.render.VertexFormat.DrawMode;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;

@Environment(EnvType.CLIENT)
public class FontRenderer implements Closeable {
   private static final Char2IntArrayMap colorCodes;
   private static final ExecutorService ASYNC_WORKER;
   private final Object2ObjectMap<Identifier, ObjectList<FontRenderer.DrawEntry>> GLYPH_PAGE_CACHE = new Object2ObjectOpenHashMap();
   private final float originalSize;
   private final ObjectList<GlyphMap> maps = new ObjectArrayList();
   private final Char2ObjectArrayMap<Glyph> allGlyphs = new Char2ObjectArrayMap();
   private final int charsPerPage;
   private final int padding;
   private final String prebakeGlyphs;
   private int scaleMul = 0;
   private Font font;
   private Font secondFont;
   private int previousGameScale = -1;
   private Future<Void> prebakeGlyphsFuture;
   private boolean initialized;

   public FontRenderer(Font font, float sizePx, int charactersPerPage, int paddingBetweenCharacters, @Nullable String prebakeCharacters) {
      this.originalSize = sizePx;
      this.charsPerPage = charactersPerPage;
      this.padding = paddingBetweenCharacters;
      this.prebakeGlyphs = prebakeCharacters;
      this.init(font, sizePx);
   }

   public FontRenderer(Font font, Font secondFont, float sizePx, int charactersPerPage, int paddingBetweenCharacters, @Nullable String prebakeCharacters) {
      this(font, sizePx, charactersPerPage, paddingBetweenCharacters, prebakeCharacters);
      this.secondFont = secondFont.deriveFont(sizePx * (float)this.scaleMul);
   }

   public FontRenderer(Font font, float sizePx) {
      this(font, sizePx, 256, 5, null);
   }

   public FontRenderer(Font font, Font secondFont, float sizePx) {
      this(font, secondFont, sizePx, 256, 5, null);
   }

   private static int floorNearestMulN(int x, int n) {
      return n * (int)Math.floor((double)x / (double)n);
   }

   public static String stripControlCodes(String text) {
      char[] var1 = text.toCharArray();
      StringBuilder var2 = new StringBuilder();

      for (int var3 = 0; var3 < var1.length; var3++) {
         char var4 = var1[var3];
         if (var4 == 167) {
            var3++;
         } else {
            var2.append(var4);
         }
      }

      return var2.toString();
   }

   @Contract(
      value = "-> new",
      pure = true
   )
   @NotNull
   public static Identifier randomIdentifier() {
      return Identifier.of("frogclient", "temp/" + randomString());
   }

   private static String randomString() {
      return IntStream.range(0, 32).mapToObj(operand -> String.valueOf((char)new Random().nextInt(97, 123))).collect(Collectors.joining());
   }

   @Contract(
      value = "_ -> new",
      pure = true
   )
   @NotNull
   public static int[] RGBIntToRGB(int in) {
      int var1 = in >> 16 & 0xFF;
      int var2 = in >> 8 & 0xFF;
      int var3 = in & 0xFF;
      return new int[]{var1, var2, var3};
   }

   public static double roundToDecimal(double n, int point) {
      if (point == 0) {
         return Math.floor(n);
      } else {
         double var3 = Math.pow(10.0, (double)point);
         return (double)Math.round(n * var3) / var3;
      }
   }

   private void sizeCheck() {
      int var1 = (int)Wrapper.mc.getWindow().getScaleFactor();
      if (var1 != this.previousGameScale) {
         this.close();
         this.init(this.font, this.originalSize);
         if (this.secondFont != null) {
            this.secondFont = this.secondFont.deriveFont(this.originalSize * (float)this.scaleMul);
         }
      }
   }

   private void init(Font font, float sizePx) {
      if (this.initialized) {
         throw new IllegalStateException("Double call to init()");
      } else {
         this.initialized = true;
         this.scaleMul = this.previousGameScale = (int)Wrapper.mc.getWindow().getScaleFactor();
         this.font = font.deriveFont(sizePx * (float)this.scaleMul);
         if (this.prebakeGlyphs != null && !this.prebakeGlyphs.isEmpty()) {
            this.prebakeGlyphsFuture = this.prebake();
         }
      }
   }

   private Future<Void> prebake() {
      return ASYNC_WORKER.submit(() -> {
         for (char var4 : this.prebakeGlyphs.toCharArray()) {
            if (Thread.interrupted()) {
               break;
            }

            this.locateGlyph1(var4);
         }

         return null;
      });
   }

   private GlyphMap generateMap(char from, char to) {
      GlyphMap var3 = this.secondFont != null
         ? new GlyphMap(from, to, this.font, this.secondFont, randomIdentifier(), this.padding)
         : new GlyphMap(from, to, this.font, randomIdentifier(), this.padding);
      this.maps.add(var3);
      return var3;
   }

   private Glyph locateGlyph0(char glyph) {
      ObjectListIterator var2 = this.maps.iterator();

      while (var2.hasNext()) {
         GlyphMap var3 = (GlyphMap)var2.next();
         if (var3.contains(glyph)) {
            return var3.getGlyph(glyph);
         }
      }

      int var4 = floorNearestMulN(glyph, this.charsPerPage);
      GlyphMap var5 = this.generateMap((char)var4, (char)(var4 + this.charsPerPage));
      return var5.getGlyph(glyph);
   }

   @Nullable
   private Glyph locateGlyph1(char glyph) {
      return (Glyph)this.allGlyphs.computeIfAbsent(glyph, this::locateGlyph0);
   }

   public void drawString(MatrixStack stack, String s, double x, double y, int color) {
      float var8 = (float)(color >> 16 & 0xFF) / 255.0F;
      float var9 = (float)(color >> 8 & 0xFF) / 255.0F;
      float var10 = (float)(color & 0xFF) / 255.0F;
      float var11 = (float)(color >> 24 & 0xFF) / 255.0F;
      this.drawString(stack, s, (float)x, (float)y, var8, var9, var10, var11, false);
   }

   public void drawString(MatrixStack stack, String s, double x, double y, Color color) {
      this.drawString(
         stack,
         s,
         (float)x,
         (float)y,
         (float)color.getRed() / 255.0F,
         (float)color.getGreen() / 255.0F,
         (float)color.getBlue() / 255.0F,
         (float)color.getAlpha() / 255.0F,
         false
      );
   }

   public void drawString(MatrixStack stack, String s, float x, float y, float r, float g, float b, float a) {
      this.drawString(stack, s, x, y, r, g, b, a, false);
   }

   public void drawString(MatrixStack stack, String s, double x, double y, int color, boolean shadow) {
      float var9 = (float)(color >> 16 & 0xFF) / 255.0F;
      float var10 = (float)(color >> 8 & 0xFF) / 255.0F;
      float var11 = (float)(color & 0xFF) / 255.0F;
      float var12 = (float)(color >> 24 & 0xFF) / 255.0F;
      this.drawString(stack, s, (float)x, (float)y, var9, var10, var11, var12, shadow);
   }

   public void drawString(MatrixStack stack, String s, double x, double y, Color color, boolean shadow) {
      this.drawString(
         stack,
         s,
         (float)x,
         (float)y,
         (float)color.getRed() / 255.0F,
         (float)color.getGreen() / 255.0F,
         (float)color.getBlue() / 255.0F,
         (float)color.getAlpha() / 255.0F,
         shadow
      );
   }

   public void drawStringWithShadow(MatrixStack stack, String s, double x, double y, int color) {
      float var8 = (float)(color >> 16 & 0xFF) / 255.0F;
      float var9 = (float)(color >> 8 & 0xFF) / 255.0F;
      float var10 = (float)(color & 0xFF) / 255.0F;
      float var11 = (float)(color >> 24 & 0xFF) / 255.0F;
      this.drawString(stack, s, (float)x, (float)y, var8, var9, var10, var11, false);
   }

   public void drawStringWithShadow(MatrixStack stack, String s, double x, double y, Color color) {
      this.drawString(
         stack,
         s,
         (float)x,
         (float)y,
         (float)color.getRed() / 255.0F,
         (float)color.getGreen() / 255.0F,
         (float)color.getBlue() / 255.0F,
         (float)color.getAlpha() / 255.0F,
         false
      );
   }

   public void drawStringWithShadow(MatrixStack stack, String s, float x, float y, float r, float g, float b, float a) {
      this.drawString(stack, s, x, y, r, g, b, a, false);
   }

   public void drawString(MatrixStack stack, String s, float x, float y, float r, float g, float b, float a, boolean shadow) {
      if (this.prebakeGlyphsFuture != null && !this.prebakeGlyphsFuture.isDone()) {
         try {
            this.prebakeGlyphsFuture.get();
         } catch (ExecutionException | InterruptedException var38) {
         }
      }

      this.sizeCheck();
      float var10 = r;
      float var11 = g;
      float var12 = b;
      float var13 = r;
      float var14 = g;
      float var15 = b;
      boolean var16 = this.useClickGuiRainbowByY();
      if (var16) {
         Color var17 = ClickGui.getInstance().getActiveColor((double)y / 10.0);
         var10 = (float)var17.getRed() / 255.0F;
         var11 = (float)var17.getGreen() / 255.0F;
         var12 = (float)var17.getBlue() / 255.0F;
         var13 = var10;
         var14 = var11;
         var15 = var12;
      }

      stack.push();
      stack.translate(roundToDecimal((double)x, 1), roundToDecimal((double)y, 1), 0.0);
      stack.scale(1.0F / (float)this.scaleMul, 1.0F / (float)this.scaleMul, 1.0F);
      RenderSystem.enableBlend();
      RenderSystem.disableCull();
      GL11.glTexParameteri(3553, 10241, 9729);
      RenderSystem.setShader(GameRenderer::getPositionTexColorProgram);
      Matrix4f var39 = stack.peek().getPositionMatrix();
      char[] var18 = s.toCharArray();
      float var19 = 0.0F;
      float var20 = 0.0F;
      boolean var21 = false;
      int var22 = 0;
      Identifier var23 = null;
      BufferBuilder var24 = null;

      for (int var25 = 0; var25 < var18.length; var25++) {
         char var26 = var18[var25];
         if (var21) {
            var21 = false;
            char var27 = Character.toUpperCase(var26);
            if (colorCodes.containsKey(var27)) {
               int[] var28 = RGBIntToRGB(colorCodes.get(var27));
               var13 = (float)var28[0] / 255.0F;
               var14 = (float)var28[1] / 255.0F;
               var15 = (float)var28[2] / 255.0F;
            } else if (var27 == 'R') {
               var13 = var10;
               var14 = var11;
               var15 = var12;
            }
         } else if (var26 == 167) {
            var21 = true;
         } else if (var26 == '\n') {
            var20 += this.getStringHeight(s.substring(var22, var25)) * (float)this.scaleMul;
            var19 = 0.0F;
            var22 = var25 + 1;
            if (var16) {
               Color var40 = ClickGui.getInstance().getActiveColor((double)(y + var20) / 10.0);
               var10 = (float)var40.getRed() / 255.0F;
               var11 = (float)var40.getGreen() / 255.0F;
               var12 = (float)var40.getBlue() / 255.0F;
               var13 = var10;
               var14 = var11;
               var15 = var12;
            }
         } else {
            Glyph var41 = this.locateGlyph1(var26);
            if (var41 != null && var41.value() != ' ') {
               Identifier var42 = var41.owner().bindToTexture;
               if (var23 != var42) {
                  if (var24 != null) {
                     Render3DUtil.endBuilding(var24);
                  }

                  var23 = var42;
                  RenderSystem.setShaderTexture(0, var42);
                  var24 = Tessellator.getInstance().begin(DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR);
               }

               GlyphMap var29 = var41.owner();
               float var32 = (float)var41.width();
               float var33 = (float)var41.height();
               float var34 = (float)var41.u() / (float)var29.width;
               float var35 = (float)var41.v() / (float)var29.height;
               float var36 = ((float)var41.u() + var32) / (float)var29.width;
               float var37 = ((float)var41.v() + var33) / (float)var29.height;
               var24.vertex(var39, var19, var20 + var33, 0.0F).texture(var34, var37).color(var13, var14, var15, a);
               var24.vertex(var39, var19 + var32, var20 + var33, 0.0F).texture(var36, var37).color(var13, var14, var15, a);
               var24.vertex(var39, var19 + var32, var20, 0.0F).texture(var36, var35).color(var13, var14, var15, a);
               var24.vertex(var39, var19, var20, 0.0F).texture(var34, var35).color(var13, var14, var15, a);
               var19 += (float)var41.width();
            } else if (var41 != null) {
               var19 += (float)var41.width();
            }
         }
      }

      if (var24 != null) {
         Render3DUtil.endBuilding(var24);
      }

      stack.pop();
   }

   public void drawCenteredString(MatrixStack stack, String s, double x, double y, int color) {
      float var8 = (float)(color >> 16 & 0xFF) / 255.0F;
      float var9 = (float)(color >> 8 & 0xFF) / 255.0F;
      float var10 = (float)(color & 0xFF) / 255.0F;
      float var11 = (float)(color >> 24 & 0xFF) / 255.0F;
      this.drawStringWithShadow(stack, s, (float)(x - (double)(this.getWidth(s) / 2.0F)), (float)y, var8, var9, var10, var11);
   }

   public void drawCenteredString(MatrixStack stack, String s, double x, double y, Color color) {
      this.drawStringWithShadow(
         stack,
         s,
         (float)(x - (double)(this.getWidth(s) / 2.0F)),
         (float)y,
         (float)color.getRed() / 255.0F,
         (float)color.getGreen() / 255.0F,
         (float)color.getBlue() / 255.0F,
         (float)color.getAlpha() / 255.0F
      );
   }

   public void drawCenteredString(MatrixStack stack, String s, float x, float y, float r, float g, float b, float a) {
      this.drawStringWithShadow(stack, s, x - this.getWidth(s) / 2.0F, y, r, g, b, a);
   }

   public float getWidth(String text) {
      char[] var2 = stripControlCodes(text).toCharArray();
      float var3 = 0.0F;
      float var4 = 0.0F;

      for (char var8 : var2) {
         if (var8 == '\n') {
            var4 = Math.max(var3, var4);
            var3 = 0.0F;
         } else {
            Glyph var9 = this.locateGlyph1(var8);
            var3 += var9 == null ? 0.0F : (float)var9.width() / (float)this.scaleMul;
         }
      }

      return Math.max(var3, var4);
   }

   public float getStringHeight(String text) {
      char[] var2 = stripControlCodes(text).toCharArray();
      if (var2.length == 0) {
         var2 = new char[]{' '};
      }

      float var3 = 0.0F;
      float var4 = 0.0F;

      for (char var8 : var2) {
         if (var8 == '\n') {
            if (var3 == 0.0F) {
               var3 = this.locateGlyph1(' ') == null ? 0.0F : (float)Objects.requireNonNull(this.locateGlyph1(' ')).height() / (float)this.scaleMul;
            }

            var4 += var3;
            var3 = 0.0F;
         } else {
            Glyph var9 = this.locateGlyph1(var8);
            var3 = Math.max(var9 == null ? 0.0F : (float)var9.height() / (float)this.scaleMul, var3);
         }
      }

      return var3 + var4;
   }

   @Override
   public void close() {
      try {
         if (this.prebakeGlyphsFuture != null && !this.prebakeGlyphsFuture.isDone() && !this.prebakeGlyphsFuture.isCancelled()) {
            this.prebakeGlyphsFuture.cancel(true);
            this.prebakeGlyphsFuture.get();
            this.prebakeGlyphsFuture = null;
         }

         ObjectListIterator var1 = this.maps.iterator();

         while (var1.hasNext()) {
            GlyphMap var2 = (GlyphMap)var1.next();
            var2.destroy();
         }

         this.maps.clear();
         this.allGlyphs.clear();
         this.initialized = false;
      } catch (Exception var3) {
      }
   }

   public float getFontHeight(String str) {
      return this.getStringHeight(str);
   }

   public float getFontHeight() {
      return this.getStringHeight("A");
   }

   public void drawGradientString(MatrixStack stack, String s, float x, float y) {
      this.drawString(stack, s, x, y, 255.0F, 255.0F, 255.0F, 255.0F);
   }

   public void drawGradientCenteredString(MatrixStack matrices, String s, float x, float y) {
      this.drawGradientString(matrices, s, x - this.getWidth(s) / 2.0F, y);
   }

   private boolean useClickGuiRainbowByY() {
      if (Wrapper.mc != null && Wrapper.mc.currentScreen instanceof ClickGuiScreen) {
         ClickGui var1 = ClickGui.getInstance();
         if (var1 == null) {
            return false;
         } else {
            ColorsModule var2 = ColorsModule.INSTANCE;
            return var2 != null && var2.colorMode.getValue() != ColorsModule.ColorMode.RainbowDelay && var1.colorMode.getValue() == ClickGui.ColorMode.Rainbow;
         }
      } else {
         return false;
      }
   }

   static {

      colorCodes = new Char2IntArrayMap() {
         {
            this.put('0', 0);
            this.put('1', 170);
            this.put('2', 43520);
            this.put('3', 43690);
            this.put('4', 11141120);
            this.put('5', 11141290);
            this.put('6', 16755200);
            this.put('7', 11184810);
            this.put('8', 5592405);
            this.put('9', 5592575);
            this.put('A', 5635925);
            this.put('B', 5636095);
            this.put('C', 16733525);
            this.put('D', 16733695);
            this.put('E', 16777045);
            this.put('F', 16777215);
         }
      };
      ASYNC_WORKER = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
   }
@Environment(EnvType.CLIENT)
   private static record DrawEntry(float atX, float atY, float r, float g, float elementCodec, Glyph toDraw) {
   }
}
