package asm.mixins;

import com.mojang.blaze3d.systems.RenderSystem;
import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.utils.render.TextUtil;
import dev.crucifixbeta.api.utils.world.InteractUtil;
import dev.crucifixbeta.mod.modules.Module;
import dev.crucifixbeta.mod.modules.impl.player.Freecam;
import dev.crucifixbeta.mod.modules.impl.player.InteractTweaks;
import dev.crucifixbeta.mod.modules.impl.render.AspectRatio;
import dev.crucifixbeta.mod.modules.impl.render.Chams;
import dev.crucifixbeta.mod.modules.impl.render.Fov;
import dev.crucifixbeta.mod.modules.impl.render.HighLight;
import dev.crucifixbeta.mod.modules.impl.render.NoRender;
import dev.crucifixbeta.mod.modules.impl.render.Zoom;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.block.enums.CameraSubmersionType;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.render.RenderTickCounter;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.entity.projectile.ProjectileUtil;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.hit.HitResult.Type;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RotationAxis;
import net.minecraft.util.math.Vec3d;
import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.At.Shift;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Environment(EnvType.CLIENT)
@Mixin({GameRenderer.class})
public class MixinGameRenderer {
   @Shadow
   @Final
   MinecraftClient client;
   @Shadow
   private float fovMultiplier;
   @Shadow
   private float lastFovMultiplier;
   @Shadow
   private boolean renderingPanorama;
   @Shadow
   private float zoom;
   @Shadow
   private float zoomX;
   @Shadow
   private float zoomY;
   @Shadow
   private float viewDistance;
   @Unique
   private Entity cameraEntity;
   @Unique
   private float originalYaw;
   @Unique
   private float originalPitch;

   @Shadow
   private static HitResult ensureTargetInRange(HitResult hitResult, Vec3d cameraPos, double interactionRange) {
      Vec3d var4 = hitResult.getPos();
      if (!var4.isInRange(cameraPos, interactionRange)) {
         Vec3d var5 = hitResult.getPos();
         Direction var6 = Direction.getFacing(var5.x - cameraPos.x, var5.y - cameraPos.y, var5.z - cameraPos.z);
         return BlockHitResult.createMissed(var5, var6, BlockPos.ofFloored(var5));
      } else {
         return hitResult;
      }
   }

   @Inject(
      method = {"showFloatingItem"},
      at = {@At("HEAD")},
      cancellable = true
   )
   private void onShowFloatingItem(ItemStack floatingItem, CallbackInfo info) {
      if (floatingItem.getItem() == Items.TOTEM_OF_UNDYING && NoRender.INSTANCE.isOn() && NoRender.INSTANCE.totem.getValue()) {
         info.cancel();
      }
   }

   @Redirect(
      method = {"renderWorld"},
      at = @At(
         value = "INVOKE",
         target = "Lnet/minecraft/util/math/MathHelper;lerp(FFF)F"
      ),
      require = 0
   )
   private float applyCameraTransformationsMathHelperLerpProxy(float delta, float first, float second) {
      return NoRender.INSTANCE.isOn() && NoRender.INSTANCE.nausea.getValue() ? 0.0F : MathHelper.lerp(delta, first, second);
   }

   @Inject(
      method = {"tiltViewWhenHurt"},
      at = {@At("HEAD")},
      cancellable = true
   )
   private void tiltViewWhenHurtHook(MatrixStack matrices, float tickDelta, CallbackInfo ci) {
      if (NoRender.INSTANCE.isOn() && NoRender.INSTANCE.hurtCam.getValue()) {
         ci.cancel();
      }
   }

   @Inject(
      method = {"shouldRenderBlockOutline"},
      at = {@At("HEAD")},
      cancellable = true
   )
   public void hookOutline(CallbackInfoReturnable<Boolean> cir) {
      if (HighLight.INSTANCE.isOn()) {
         cir.setReturnValue(false);
      }
   }

   @Inject(
      at = {@At(
         value = "FIELD",
         target = "Lnet/minecraft/client/render/GameRenderer;renderHand:Z",
         opcode = 180,
         ordinal = 0
      )},
      method = {"renderWorld"}
   )
   void render3dHook(RenderTickCounter tickCounter, CallbackInfo ci) {
      if (!Module.nullCheck()) {
         Camera var3 = MinecraftClient.getInstance().gameRenderer.getCamera();
         MatrixStack var4 = new MatrixStack();
         var4.multiply(RotationAxis.POSITIVE_X.rotationDegrees(var3.getPitch()));
         var4.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(var3.getYaw() + 180.0F));
         TextUtil.lastProjMat.set(RenderSystem.getProjectionMatrix());
         TextUtil.lastModMat.set(RenderSystem.getModelViewMatrix());
         TextUtil.lastWorldSpaceMatrix.set(var4.peek().getPositionMatrix());
         CrucifixBeta.FPS.record();
         CrucifixBeta.MODULE.render3D(var4);
      }
   }

   @Inject(
      at = {@At("TAIL")},
      method = {"renderWorld"}
   )
   void render3dTail(RenderTickCounter tickCounter, CallbackInfo ci) {
      if (Chams.INSTANCE != null && Chams.INSTANCE.isOn() && Chams.INSTANCE.hand.booleanValue) {
         RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
      }
   }

   @Inject(
      method = {"getFov(Lnet/minecraft/client/render/Camera;FZ)D"},
      at = {@At("HEAD")},
      cancellable = true
   )
   public void getFov(Camera camera, float tickDelta, boolean changingFov, CallbackInfoReturnable<Double> ci) {
      if (!this.renderingPanorama && (Fov.INSTANCE.isOn() || Zoom.on)) {
         double var5 = 70.0;
         if (changingFov) {
            if (Fov.INSTANCE.isOn()) {
               double var7 = Fov.INSTANCE.fov.getValue();
               if (Zoom.on) {
                  ci.setReturnValue(Math.min(Math.max(var7 - Zoom.INSTANCE.currentFov, 1.0), 177.0));
               } else {
                  ci.setReturnValue(var7);
               }

               return;
            }

            var5 = (double)this.client.options.getFov().getValue().intValue();
            var5 *= (double)MathHelper.lerp(tickDelta, this.lastFovMultiplier, this.fovMultiplier);
            if (Zoom.on) {
               var5 = Math.min(Math.max(var5 - Zoom.INSTANCE.currentFov, 1.0), 177.0);
            }
         } else if (Fov.INSTANCE.isOn()) {
            ci.setReturnValue(Fov.INSTANCE.itemFov.getValue());
            return;
         }

         if (camera.getFocusedEntity() instanceof LivingEntity && ((LivingEntity)camera.getFocusedEntity()).isDead()) {
            float var9 = Math.min((float)((LivingEntity)camera.getFocusedEntity()).deathTime + tickDelta, 20.0F);
            var5 /= (double)((1.0F - 500.0F / (var9 + 500.0F)) * 2.0F + 1.0F);
         }

         CameraSubmersionType var10;
         if ((var10 = camera.getSubmersionType()) == CameraSubmersionType.LAVA || var10 == CameraSubmersionType.WATER) {
            var5 *= MathHelper.lerp(this.client.options.getFovEffectScale().getValue(), 1.0, 0.85714287F);
         }

         ci.setReturnValue(var5);
      }
   }

   @Inject(
      method = {"getNightVisionStrength"},
      at = {@At("HEAD")},
      cancellable = true
   )
   private static void getNightVisionStrengthHook(LivingEntity entity, float tickDelta, CallbackInfoReturnable<Float> cir) {
      StatusEffectInstance var3 = entity.getStatusEffect(StatusEffects.NIGHT_VISION);
      if (var3 == null) {
         cir.setReturnValue(1.0F);
      }
   }

   @Inject(
      method = {"renderWorld"},
      at = {@At(
         value = "INVOKE",
         target = "Lnet/minecraft/client/render/GameRenderer;renderHand(Lnet/minecraft/client/render/Camera;FLorg/joml/Matrix4f;)V",
         shift = Shift.AFTER
      )}
   )
   public void postRender3dHook(RenderTickCounter tickCounter, CallbackInfo ci) {
      CrucifixBeta.SHADER.renderShaders();
   }

   @Inject(
      method = {"getBasicProjectionMatrix"},
      at = {@At("TAIL")},
      cancellable = true
   )
   public void getBasicProjectionMatrixHook(double fov, CallbackInfoReturnable<Matrix4f> cir) {
      if (AspectRatio.INSTANCE.isOn()) {
         MatrixStack var4 = new MatrixStack();
         var4.peek().getPositionMatrix().identity();
         if (this.zoom != 1.0F) {
            var4.translate(this.zoomX, -this.zoomY, 0.0F);
            var4.scale(this.zoom, this.zoom, 1.0F);
         }

         var4.peek()
            .getPositionMatrix()
            .mul(
               new Matrix4f()
                  .setPerspective((float)(fov * (float) (Math.PI / 180.0)), AspectRatio.INSTANCE.ratio.getValueFloat(), 0.05F, this.viewDistance * 4.0F)
            );
         cir.setReturnValue(var4.peek().getPositionMatrix());
      }
   }

   @Inject(
      method = {"findCrosshairTarget"},
      at = {@At("HEAD")},
      cancellable = true
   )
   private void findCrosshairTarget(
      Entity camera, double blockInteractionRange, double entityInteractionRange, float tickDelta, CallbackInfoReturnable<HitResult> cir
   ) {
      if (Freecam.INSTANCE.isOn()) {
         cir.setReturnValue(
            InteractUtil.getRtxTarget(
               Freecam.INSTANCE.getFakeYaw(),
               Freecam.INSTANCE.getFakePitch(),
               Freecam.INSTANCE.getFakeX(),
               Freecam.INSTANCE.getFakeY(),
               Freecam.INSTANCE.getFakeZ()
            )
         );
      } else {
         double var8 = Math.max(blockInteractionRange, entityInteractionRange);
         double var10 = MathHelper.square(var8);
         Vec3d var12 = camera.getCameraPosVec(tickDelta);
         InteractTweaks.INSTANCE.isActive = InteractTweaks.INSTANCE.ghostHand();
         HitResult var13 = camera.raycast(var8, tickDelta, false);
         InteractTweaks.INSTANCE.isActive = false;
         double var14 = var13.getPos().squaredDistanceTo(var12);
         if (var13.getType() != Type.MISS) {
            var10 = var14;
            var8 = Math.sqrt(var14);
         }

         Vec3d var16 = camera.getRotationVec(tickDelta);
         Vec3d var17 = var12.add(var16.x * var8, var16.y * var8, var16.z * var8);
         Box var18 = camera.getBoundingBox().stretch(var16.multiply(var8)).expand(1.0, 1.0, 1.0);
         if (!InteractTweaks.INSTANCE.noEntityTrace()) {
            EntityHitResult var19 = ProjectileUtil.raycast(camera, var12, var17, var18, entity -> !entity.isSpectator() && entity.canHit(), var10);
            cir.setReturnValue(
               var19 != null && var19.getPos().squaredDistanceTo(var12) < var14
                  ? ensureTargetInRange(var19, var12, entityInteractionRange)
                  : ensureTargetInRange(var13, var12, blockInteractionRange)
            );
         } else {
            cir.setReturnValue(ensureTargetInRange(var13, var12, blockInteractionRange));
         }
      }
   }
}
