package dev.crucifixbeta.mod.gui;

import com.mojang.blaze3d.systems.RenderSystem;
import dev.crucifixbeta.api.utils.render.Render2DUtil;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.concurrent.atomic.AtomicReference;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.BufferRenderer;
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.texture.NativeImage;
import net.minecraft.client.texture.NativeImageBackedTexture;
import net.minecraft.client.texture.NativeImage.Format;
import net.minecraft.client.util.Window;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.resource.Resource;
import net.minecraft.util.Identifier;
import net.minecraft.util.Util;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.bytedeco.javacv.FFmpegFrameGrabber.Exception;
import org.joml.Matrix4f;

@Environment(EnvType.CLIENT)
public final class EpsilonMenuBackgroundRenderer {
   private static final Identifier WALLPAPER_VIDEO;
   private static final String DYNAMIC_TEXTURE_NAME;
   private static final long MIN_TEXTURE_UPLOAD_INTERVAL_MS;
   private static final long TARGET_FRAME_INTERVAL_MS;
   private static final int MAX_DECODE_WIDTH;
   private static final int MAX_DECODE_HEIGHT;
   private static final EpsilonMenuBackgroundRenderer INSTANCE;
   private final MinecraftClient client = MinecraftClient.getInstance();
   private long startTimeMs = Util.getMeasuringTimeMs();
   private EpsilonMenuBackgroundRenderer.VideoDecoder decoder;
   private NativeImageBackedTexture texture;
   private Identifier textureId;
   private boolean loadAttempted;
   private int videoWidth;
   private int videoHeight;
   private long lastTextureUploadMs;

   private EpsilonMenuBackgroundRenderer() {
   }

   public static void resetTime() {
      INSTANCE.startTimeMs = Util.getMeasuringTimeMs();
   }

   public static void render(DrawContext context, int mouseX, int mouseY) {
      INSTANCE.renderInternal(context);
   }

   public static void close() {
      INSTANCE.closeInternal();
   }

   private void renderInternal(DrawContext context) {
      Window var2 = this.client.getWindow();
      if (var2 != null) {
         int var3 = var2.getScaledWidth();
         int var4 = var2.getScaledHeight();
         if (var3 > 0 && var4 > 0) {
            if (this.updateVideoTexture()) {
               this.drawCover(context.getMatrices(), this.textureId, var3, var4);
            } else {
               context.fill(0, 0, var3, var4, -16250868);
            }

            Render2DUtil.drawRect(context.getMatrices(), 0.0F, 0.0F, (float)var3, (float)var4, 905969664);
            Render2DUtil.verticalGradient(context.getMatrices(), 0.0F, 0.0F, (float)var3, (float)var4, 134217728, 1879048192);
         }
      }
   }

   private boolean updateVideoTexture() {
      if (!this.ensureDecoder()) {
         return false;
      } else {
         long var1 = Util.getMeasuringTimeMs();
         if (this.textureId != null && var1 - this.lastTextureUploadMs < 50L) {
            return true;
         } else {
            EpsilonMenuBackgroundRenderer.DecodedFrame var3 = this.decoder.takeLatestFrame();
            if (var3 == null) {
               return this.textureId != null;
            } else {
               if (this.texture != null && this.videoWidth == var3.width() && this.videoHeight == var3.height()) {
                  this.texture.setImage(var3.image());
                  this.texture.upload();
               } else {
                  this.recreateTexture(var3);
               }

               this.lastTextureUploadMs = var1;
               return this.textureId != null;
            }
         }
      }
   }

   private boolean ensureDecoder() {
      if (this.decoder != null) {
         return true;
      } else if (this.loadAttempted) {
         return false;
      } else {
         this.loadAttempted = true;

         try {
            Path var1 = this.extractVideoToTempFile();
            if (var1 == null) {
               return false;
            } else {
               this.decoder = new EpsilonMenuBackgroundRenderer.VideoDecoder(var1);
               this.decoder.start();
               return true;
            }
         } catch (Throwable var2) {
            System.out.println("[MainMenu] Failed to load MP4 wallpaper: " + var2.getMessage());
            var2.printStackTrace();
            this.closeInternal();
            this.loadAttempted = true;
            return false;
         }
      }
   }

   private Path extractVideoToTempFile() throws IOException {
      Resource var1 = this.client.getResourceManager().getResource(WALLPAPER_VIDEO).orElse(null);
      if (var1 == null) {
         System.out.println("[MainMenu] Wallpaper video not found: " + WALLPAPER_VIDEO);
         return null;
      } else {
         Path var2 = Files.createTempFile("crucifix-main-menu-wallpaper-", ".mp4");
         var2.toFile().deleteOnExit();

         try (InputStream var3 = var1.getInputStream()) {
            Files.copy(var3, var2, StandardCopyOption.REPLACE_EXISTING);
         }

         return var2;
      }
   }

   private void recreateTexture(EpsilonMenuBackgroundRenderer.DecodedFrame frame) {
      if (this.textureId != null) {
         this.client.getTextureManager().destroyTexture(this.textureId);
         this.textureId = null;
         this.texture = null;
      }

      this.videoWidth = frame.width();
      this.videoHeight = frame.height();
      this.texture = new NativeImageBackedTexture(frame.image());
      this.texture.setFilter(true, false);
      this.texture.upload();
      this.textureId = this.client.getTextureManager().registerDynamicTexture("epsilon_main_menu_video", this.texture);
   }

   private void drawCover(MatrixStack matrices, Identifier texture, int width, int height) {
      if (texture != null && this.videoWidth > 0 && this.videoHeight > 0) {
         float var5 = (float)width / (float)height;
         float var6 = (float)this.videoWidth / (float)this.videoHeight;
         float var7 = 0.0F;
         float var8 = 0.0F;
         float var9 = 1.0F;
         float var10 = 1.0F;
         if (var5 > var6) {
            float var11 = var6 / var5;
            var8 = (1.0F - var11) * 0.5F;
            var10 = var8 + var11;
         } else {
            float var13 = var5 / var6;
            var7 = (1.0F - var13) * 0.5F;
            var9 = var7 + var13;
         }

         Matrix4f var14 = matrices.peek().getPositionMatrix();
         RenderSystem.disableDepthTest();
         RenderSystem.depthMask(false);
         RenderSystem.disableCull();
         RenderSystem.enableBlend();
         RenderSystem.defaultBlendFunc();
         RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
         RenderSystem.setShader(GameRenderer::getPositionTexColorProgram);
         RenderSystem.setShaderTexture(0, texture);
         BufferBuilder var12 = Tessellator.getInstance().begin(DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR);
         var12.vertex(var14, 0.0F, (float)height, 0.0F).texture(var7, var10).color(-1);
         var12.vertex(var14, (float)width, (float)height, 0.0F).texture(var9, var10).color(-1);
         var12.vertex(var14, (float)width, 0.0F, 0.0F).texture(var9, var8).color(-1);
         var12.vertex(var14, 0.0F, 0.0F, 0.0F).texture(var7, var8).color(-1);
         BufferRenderer.drawWithGlobalProgram(var12.end());
         RenderSystem.depthMask(true);
         RenderSystem.enableDepthTest();
         RenderSystem.enableCull();
         RenderSystem.disableBlend();
      }
   }

   private void closeInternal() {
      if (this.textureId != null) {
         this.client.getTextureManager().destroyTexture(this.textureId);
         this.textureId = null;
         this.texture = null;
      }

      if (this.decoder != null) {
         this.decoder.close();
         this.decoder = null;
      }

      this.videoWidth = 0;
      this.videoHeight = 0;
      this.loadAttempted = false;
      this.lastTextureUploadMs = 0L;
   }

   private static NativeImage toNativeImage(BufferedImage bufferedImage) {
      NativeImage var1 = new NativeImage(Format.RGBA, bufferedImage.getWidth(), bufferedImage.getHeight(), false);

      for (int var2 = 0; var2 < bufferedImage.getHeight(); var2++) {
         for (int var3 = 0; var3 < bufferedImage.getWidth(); var3++) {
            int var4 = bufferedImage.getRGB(var3, var2);
            int var5 = var4 >>> 24 & 0xFF;
            int var6 = var4 >>> 16 & 0xFF;
            int var7 = var4 >>> 8 & 0xFF;
            int var8 = var4 & 0xFF;
            var1.setColor(var3, var2, var5 << 24 | var8 << 16 | var7 << 8 | var6);
         }
      }

      return var1;
   }

   private static BufferedImage scaleFrame(BufferedImage source) {
      int var1 = source.getWidth();
      int var2 = source.getHeight();
      if (var1 <= 960 && var2 <= 540) {
         return source;
      } else {
         float var3 = Math.min(960.0F / (float)var1, 540.0F / (float)var2);
         int var4 = Math.max(1, Math.round((float)var1 * var3));
         int var5 = Math.max(1, Math.round((float)var2 * var3));
         BufferedImage var6 = new BufferedImage(var4, var5, 2);
         Graphics2D var7 = var6.createGraphics();

         try {
            var7.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            var7.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
            var7.drawImage(source, 0, 0, var4, var5, null);
         } finally {
            var7.dispose();
         }

         return var6;
      }
   }

   static {

      MAX_DECODE_HEIGHT = 540;
      MAX_DECODE_WIDTH = 960;
      TARGET_FRAME_INTERVAL_MS = 50L;
      MIN_TEXTURE_UPLOAD_INTERVAL_MS = 50L;
      DYNAMIC_TEXTURE_NAME = "epsilon_main_menu_video";
      WALLPAPER_VIDEO = Identifier.of("epsilon", "textures/gui/main_menu_wallpaper.mp4");
      INSTANCE = new EpsilonMenuBackgroundRenderer();
   }
@Environment(EnvType.CLIENT)
   private static record DecodedFrame(NativeImage image, int width, int height) {
      private void close() {
         this.image.close();
      }
   }

   @Environment(EnvType.CLIENT)
   private static final class VideoDecoder implements AutoCloseable, Runnable {
      private final Path videoPath;
      private final AtomicReference<EpsilonMenuBackgroundRenderer.DecodedFrame> latestFrame = new AtomicReference<>();
      private FFmpegFrameGrabber grabber;
      private Java2DFrameConverter converter;
      private long frameIntervalMs;
      private volatile boolean running;
      private Thread thread;

      private VideoDecoder(Path videoPath) {
         this.videoPath = videoPath;
      }

      private void start() {
         this.running = true;
         this.thread = new Thread(this, "CrucifiX Main Menu Video");
         this.thread.setDaemon(true);
         this.thread.start();
      }

      @Override
      public void run() {
         try {
            this.open();
            this.decodeLoop();
         } catch (Throwable var5) {
            if (this.running) {
               System.out.println("[MainMenu] Video decoder stopped: " + var5.getMessage());
               var5.printStackTrace();
            }
         } finally {
            this.releaseGrabber();
         }
      }

      private void open() throws Exception {
         this.converter = new Java2DFrameConverter();
         this.grabber = new FFmpegFrameGrabber(this.videoPath.toFile());
         this.grabber.setAudioStream(-1);
         this.grabber.start();
         double var1 = this.grabber.getVideoFrameRate();
         if (var1 <= 0.0 || Double.isNaN(var1) || Double.isInfinite(var1)) {
            var1 = this.grabber.getFrameRate();
         }

         this.frameIntervalMs = Math.max(50L, Math.round(1000.0 / Math.max(1.0, var1)));
      }

      private void decodeLoop() {
         while (this.running) {
            long var1 = Util.getMeasuringTimeMs();

            try {
               Frame var3 = this.grabber.grabImage();
               if (var3 == null) {
                  this.restart();
                  var3 = this.grabber.grabImage();
               }

               if (var3 != null) {
                  BufferedImage var4 = this.converter.convert(var3);
                  if (var4 != null) {
                     BufferedImage var5 = EpsilonMenuBackgroundRenderer.scaleFrame(var4);
                     this.publishFrame(EpsilonMenuBackgroundRenderer.toNativeImage(var5), var5.getWidth(), var5.getHeight());
                  }
               }
            } catch (java.lang.Exception var12) {
               if (this.running) {
                  System.out.println("[MainMenu] Video frame decode failed: " + var12.getMessage());
                  this.restartQuietly();
               }
            }

            long var6 = Util.getMeasuringTimeMs() - var1;
            long var8 = Math.max(1L, this.frameIntervalMs - var6);

            try {
               Thread.sleep(var8);
            } catch (InterruptedException var11) {
               Thread.currentThread().interrupt();
               break;
            }
         }
      }

      private EpsilonMenuBackgroundRenderer.DecodedFrame takeLatestFrame() {
         return this.latestFrame.getAndSet(null);
      }

      private void publishFrame(NativeImage image, int width, int height) {
         EpsilonMenuBackgroundRenderer.DecodedFrame var4 = new EpsilonMenuBackgroundRenderer.DecodedFrame(image, width, height);
         if (!this.running) {
            var4.close();
         } else {
            EpsilonMenuBackgroundRenderer.DecodedFrame var5 = this.latestFrame.getAndSet(var4);
            if (var5 != null) {
               var5.close();
            }
         }
      }

      private void restart() throws Exception {
         this.grabber.setVideoTimestamp(0L);
      }

      private void restartQuietly() {
         try {
            this.restart();
         } catch (java.lang.Exception var2) {
         }
      }

      @Override
      public void close() {
         this.running = false;
         if (this.thread != null) {
            this.thread.interrupt();
         }

         if (this.thread != null && Thread.currentThread() != this.thread) {
            try {
               this.thread.join(250L);
            } catch (InterruptedException var4) {
               Thread.currentThread().interrupt();
            }

            this.thread = null;
         }

         this.releaseGrabber();
         EpsilonMenuBackgroundRenderer.DecodedFrame var1 = this.latestFrame.getAndSet(null);
         if (var1 != null) {
            var1.close();
         }

         this.converter = null;

         try {
            Files.deleteIfExists(this.videoPath);
         } catch (IOException var3) {
         }
      }

      private synchronized void releaseGrabber() {
         if (this.grabber != null) {
            try {
               this.grabber.stop();
            } catch (java.lang.Exception var3) {
            }

            try {
               this.grabber.release();
            } catch (java.lang.Exception var2) {
            }

            this.grabber = null;
         }
      }
}
}
