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

import com.mojang.blaze3d.systems.RenderSystem;
import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.events.eventbus.EventListener;
import dev.crucifixbeta.api.events.impl.KeyboardInputEvent;
import dev.crucifixbeta.api.events.impl.MoveEvent;
import dev.crucifixbeta.api.events.impl.PacketEvent;
import dev.crucifixbeta.api.events.impl.RotationEvent;
import dev.crucifixbeta.api.events.impl.TimerEvent;
import dev.crucifixbeta.api.events.impl.UpdateEvent;
import dev.crucifixbeta.api.utils.player.MovementUtil;
import dev.crucifixbeta.api.utils.render.ColorUtil;
import dev.crucifixbeta.mod.modules.Module;
import dev.crucifixbeta.mod.modules.impl.client.AntiCheat;
import dev.crucifixbeta.mod.modules.impl.player.Freecam;
import dev.crucifixbeta.mod.modules.settings.impl.BooleanSetting;
import dev.crucifixbeta.mod.modules.settings.impl.ColorSetting;
import dev.crucifixbeta.mod.modules.settings.impl.SliderSetting;
import java.awt.Color;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
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.util.math.MatrixStack;
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec2f;
import net.minecraft.util.math.Vec3d;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;

@Environment(EnvType.CLIENT)
public class HoleSnap extends Module {
   public static HoleSnap INSTANCE;
   public final BooleanSetting any = this.add(new BooleanSetting("AnyHole", true));
   public final SliderSetting timer = this.add(new SliderSetting("Timer", 1.0, 0.1, 8.0, 0.1));
   public final BooleanSetting up = this.add(new BooleanSetting("Up", true));
   public final BooleanSetting grim = this.add(new BooleanSetting("Grim", false));
   public final ColorSetting color = this.add(new ColorSetting("Color", new Color(255, 255, 255, 100)));
   public final SliderSetting circleSize = this.add(new SliderSetting("CircleSize", 1.0, 0.1F, 2.5));
   public final BooleanSetting fade = this.add(new BooleanSetting("Fade", true));
   public final SliderSetting segments = this.add(new SliderSetting("Segments", 180, 0, 360));
   private final SliderSetting range = this.add(new SliderSetting("Range", 5, 1, 50));
   private final SliderSetting timeoutTicks = this.add(new SliderSetting("TimeOut", 40, 0, 100));
   private final SliderSetting steps = this.add(new SliderSetting("Steps", 0.8, 0.0, 1.0, 0.01, this.grim::getValue));
   private final SliderSetting priority = this.add(new SliderSetting("Priority", 10, 0, 100, this.grim::getValue));
   boolean resetMove = false;
   boolean applyTimer = false;
   Vec3d targetPos;
   private BlockPos holePos;
   private int stuckTicks;
   private int enabledTicks;

   public HoleSnap() {
      super("HoleSnap", "HoleSnap", Module.Category.Movement);
      this.setChinese("\u62c9\u5751");
      INSTANCE = this;
   }

   public static Vec2f getRotationTo(Vec3d posFrom, Vec3d posTo) {
      Vec3d var2 = posTo.subtract(posFrom);
      return getRotationFromVec(var2);
   }

   public static void drawCircle(MatrixStack matrixStack, Color color, double circleSize, Vec3d pos, int segments) {
      Vec3d var6 = mc.getBlockEntityRenderDispatcher().camera.getPos();
      RenderSystem.disableDepthTest();
      Matrix4f var7 = matrixStack.peek().getPositionMatrix();
      RenderSystem.setShader(GameRenderer::getPositionColorProgram);
      float var8 = (float)color.getAlpha() / 255.0F;
      float var9 = (float)color.getRed() / 255.0F;
      float var10 = (float)color.getGreen() / 255.0F;
      float var11 = (float)color.getBlue() / 255.0F;
      BufferBuilder var12 = Tessellator.getInstance().begin(DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR);
      double var13 = 0.0;

      while (var13 < 360.0) {
         double var15 = Math.sin(Math.toRadians(var13)) * circleSize;
         double var17 = Math.cos(Math.toRadians(var13)) * circleSize;
         Vec3d var19 = new Vec3d(pos.x + var15, pos.y, pos.z + var17).add(-var6.x, -var6.y, -var6.z);
         var12.vertex(var7, (float)var19.x, (float)var19.y, (float)var19.z).color(var9, var10, var11, var8);
         var13 += 360.0 / (double)segments;
      }

      BufferRenderer.drawWithGlobalProgram(var12.end());
      RenderSystem.enableDepthTest();
   }

   private static Vec2f getRotationFromVec(Vec3d vec) {
      double var1 = vec.x;
      double var3 = vec.z;
      double var5 = Math.hypot(var1, var3);
      var3 = vec.z;
      double var7 = vec.x;
      double var9 = normalizeAngle(Math.toDegrees(Math.atan2(var3, var7)) - 90.0);
      double var11 = normalizeAngle(Math.toDegrees(-Math.atan2(vec.y, var5)));
      return new Vec2f((float)var9, (float)var11);
   }

   private static double normalizeAngle(double angleIn) {
      double var2 = angleIn % 360.0;
      if (var2 >= 180.0) {
         var2 -= 360.0;
      }

      if (var2 < -180.0) {
         var2 += 360.0;
      }

      return var2;
   }

   @EventListener(
      priority = -99
   )
   public void onTimer(TimerEvent event) {
      if (this.applyTimer) {
         event.set(this.timer.getValueFloat());
      }
   }

   @Override
   public void onEnable() {
      this.applyTimer = false;
      if (nullCheck()) {
         this.disable();
      } else {
         this.resetMove = false;
         this.holePos = CrucifixBeta.HOLE.getHole((float)this.range.getValue(), true, this.any.getValue(), this.up.getValue());
      }
   }

   @Override
   public void onDisable() {
      this.holePos = null;
      this.stuckTicks = 0;
      this.enabledTicks = 0;
      if (!nullCheck()) {
         if (this.resetMove && !this.grim.getValue()) {
            MovementUtil.setMotionX(0.0);
            MovementUtil.setMotionZ(0.0);
         }
      }
   }

   @EventListener
   public void onReceivePacket(PacketEvent.Receive event) {
      if (event.getPacket() instanceof PlayerPositionLookS2CPacket) {
         this.disable();
      }
   }

   @EventListener(
      priority = -999
   )
   public void onKeyInput(KeyboardInputEvent e) {
      if (this.grim.getValue()) {
         if (!AntiCheat.INSTANCE.movementSync()) {
            this.sendMessage("\u00a74HoleSnap require MovementSync.");
            this.disable();
         } else if (!mc.player.isRiding() && !Freecam.INSTANCE.isOn()) {
            mc.player.input.movementSideways = 0.0F;
            mc.player.input.movementForward = 1.0F;
         }
      }
   }

   @EventListener
   public void onUpdate(UpdateEvent event) {
      this.holePos = CrucifixBeta.HOLE.getHole((float)this.range.getValue(), true, this.any.getValue(), this.up.getValue());
      if (this.holePos == null) {
         this.disable();
      } else {
         this.enabledTicks++;
         if ((double)this.enabledTicks > this.timeoutTicks.getValue() - 1.0) {
            this.disable();
         } else {
            this.applyTimer = true;
            if (this.grim.getValue()) {
               if (!mc.player.isAlive() || mc.player.isFallFlying()) {
                  this.disable();
               } else if (this.stuckTicks > 8) {
                  this.disable();
               } else if (this.holePos == null) {
                  this.disable();
               } else {
                  Vec3d var2 = mc.player.getPos();
                  this.targetPos = new Vec3d((double)this.holePos.getX() + 0.5, mc.player.getY(), (double)this.holePos.getZ() + 0.5);
                  Direction var3;
                  if (CrucifixBeta.HOLE.isDoubleHole(this.holePos) && (var3 = CrucifixBeta.HOLE.is3Block(this.holePos)) != null) {
                     this.targetPos = this.targetPos
                        .add(new Vec3d((double)var3.getVector().getX() * 0.5, (double)var3.getVector().getY() * 0.5, (double)var3.getVector().getZ() * 0.5));
                  }

                  this.applyTimer = true;
                  this.resetMove = true;
                  float var4 = getRotationTo(var2, this.targetPos).x;
                  float var5 = var4 / 180.0F * (float) Math.PI;
                  double var6 = var2.distanceTo(this.targetPos);
                  double var8 = Math.min(0.2873, var6);
                  double var10 = (double)(-((float)Math.sin((double)var5))) * var8;
                  double var12 = (double)((float)Math.cos((double)var5)) * var8;
                  if (Math.abs(var10) < 0.25 && Math.abs(var12) < 0.25 && var2.y <= (double)this.holePos.getY() + 0.8) {
                     this.disable();
                  } else {
                     this.stuckTicks = mc.player.horizontalCollision ? ++this.stuckTicks : 0;
                  }
               }
            }
         }
      }
   }

   @EventListener
   public void onRotate(RotationEvent event) {
      if (this.grim.getValue() && this.holePos != null) {
         this.targetPos = new Vec3d((double)this.holePos.getX() + 0.5, mc.player.getY(), (double)this.holePos.getZ() + 0.5);
         Direction var2;
         if (CrucifixBeta.HOLE.isDoubleHole(this.holePos) && (var2 = CrucifixBeta.HOLE.is3Block(this.holePos)) != null) {
            this.targetPos = this.targetPos
               .add(new Vec3d((double)var2.getVector().getX() * 0.5, (double)var2.getVector().getY() * 0.5, (double)var2.getVector().getZ() * 0.5));
         }

         event.setTarget(this.targetPos, this.steps.getValueFloat(), this.priority.getValueFloat());
      }
   }

   @EventListener
   public void onMove(MoveEvent event) {
      if (!this.grim.getValue()) {
         if (!mc.player.isAlive() || mc.player.isFallFlying()) {
            this.disable();
         } else if (this.stuckTicks > 8) {
            this.disable();
         } else if (this.holePos == null) {
            this.disable();
         } else {
            Vec3d var2 = mc.player.getPos();
            this.targetPos = new Vec3d((double)this.holePos.getX() + 0.5, mc.player.getY(), (double)this.holePos.getZ() + 0.5);
            Direction var3;
            if (CrucifixBeta.HOLE.isDoubleHole(this.holePos) && (var3 = CrucifixBeta.HOLE.is3Block(this.holePos)) != null) {
               this.targetPos = this.targetPos
                  .add(new Vec3d((double)var3.getVector().getX() * 0.5, (double)var3.getVector().getY() * 0.5, (double)var3.getVector().getZ() * 0.5));
            }

            this.applyTimer = true;
            this.resetMove = true;
            float var4 = getRotationTo(var2, this.targetPos).x;
            float var5 = var4 / 180.0F * (float) Math.PI;
            double var6 = var2.distanceTo(this.targetPos);
            double var8 = Math.min(0.2873, var6);
            double var10 = (double)(-((float)Math.sin((double)var5))) * var8;
            double var12 = (double)((float)Math.cos((double)var5)) * var8;
            event.setX(var10);
            event.setZ(var12);
            if (Math.abs(var10) < 0.1 && Math.abs(var12) < 0.1 && var2.y <= (double)this.holePos.getY() + 0.5) {
               this.disable();
            }

            this.stuckTicks = mc.player.horizontalCollision ? ++this.stuckTicks : 0;
         }
      }
   }

   @Override
   public void onRender3D(MatrixStack matrixStack) {
      if (this.targetPos != null && this.holePos != null) {
         GL11.glEnable(3042);
         Color var2 = this.color.getValue();
         Vec3d var3 = new Vec3d(this.targetPos.x, (double)this.holePos.getY(), this.targetPos.getZ());
         if (this.fade.getValue()) {
            double var4 = 0.01;

            for (double var6 = 0.0; var6 < this.circleSize.getValue(); var6 += var4) {
               drawCircle(
                  matrixStack,
                  ColorUtil.injectAlpha(var2, (int)Math.min((double)(var2.getAlpha() * 2) / (this.circleSize.getValue() / var4), 255.0)),
                  var6,
                  var3,
                  this.segments.getValueInt()
               );
            }
         } else {
            drawCircle(matrixStack, var2, this.circleSize.getValue(), var3, this.segments.getValueInt());
         }

         GL11.glDisable(3042);
      }
   }
}
