package dev.crucifixbeta.core.impl;

import asm.accessors.IClientPlayerEntity;
import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.events.eventbus.EventListener;
import dev.crucifixbeta.api.events.impl.DoAttackEvent;
import dev.crucifixbeta.api.events.impl.FireworkShooterRotationEvent;
import dev.crucifixbeta.api.events.impl.InteractBlockEvent;
import dev.crucifixbeta.api.events.impl.InteractItemEvent;
import dev.crucifixbeta.api.events.impl.JumpEvent;
import dev.crucifixbeta.api.events.impl.KeyboardInputEvent;
import dev.crucifixbeta.api.events.impl.PacketEvent;
import dev.crucifixbeta.api.events.impl.RotationEvent;
import dev.crucifixbeta.api.events.impl.SendMovementPacketsEvent;
import dev.crucifixbeta.api.events.impl.TickEvent;
import dev.crucifixbeta.api.events.impl.TickMovementEvent;
import dev.crucifixbeta.api.events.impl.TravelEvent;
import dev.crucifixbeta.api.events.impl.UpdateRotateEvent;
import dev.crucifixbeta.api.utils.Wrapper;
import dev.crucifixbeta.api.utils.math.MathUtil;
import dev.crucifixbeta.api.utils.math.Timer;
import dev.crucifixbeta.api.utils.path.BaritoneUtil;
import dev.crucifixbeta.api.utils.rotation.Rotation;
import dev.crucifixbeta.mod.modules.impl.client.AntiCheat;
import dev.crucifixbeta.mod.modules.impl.client.ClientSetting;
import dev.crucifixbeta.mod.modules.impl.movement.HoleSnap;
import dev.crucifixbeta.mod.modules.impl.player.Freecam;
import dev.crucifixbeta.mod.modules.settings.enums.SnapBack;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.Full;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.LookAndOnGround;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;

@Environment(EnvType.CLIENT)
public class RotationManager implements Wrapper {
   public static Rotation lastRotations = new Rotation(0.0F, 0.0F);
   public static final Timer ROTATE_TIMER = new Timer();
   public static Vec3d directionVec = null;
   public static boolean snapBack = false;
   private static float renderPitch;
   private static float renderYawOffset;
   private static float prevRenderPitch;
   private static float prevRenderYawOffset;
   private static float prevRotationYawHead;
   private static float rotationYawHead;
   public float nextYaw;
   public float nextPitch;
   public float rotationYaw;
   public float rotationPitch;
   public float lastYaw;
   public float lastPitch;
   public Vec3d crossHairUpdatePos;
   private int ticksExisted;
   public static float fixYaw;
   public static float fixPitch;
   private float prevYaw;
   private float prevPitch;

   public RotationManager() {
      CrucifixBeta.EVENT_BUS.subscribe(this);
   }

   @EventListener
   public void onInteract(InteractItemEvent event) {
      if (AntiCheat.INSTANCE.interactRotation.getValue() && mc.player != null) {
         if (event.isPre()) {
            this.snapAt(mc.player.getYaw(), mc.player.getPitch());
         } else {
            this.snapBack();
         }
      }
   }

   @EventListener
   public void onInteract(InteractBlockEvent event) {
      if (AntiCheat.INSTANCE.interactRotation.getValue() && mc.player != null) {
         if (event.isPre()) {
            this.snapAt(mc.player.getYaw(), mc.player.getPitch());
         } else {
            this.snapBack();
         }
      }
   }

   @EventListener
   public void doAttack(DoAttackEvent event) {
      if (AntiCheat.INSTANCE.interactRotation.getValue() && mc.player != null) {
         if (event.isPre()) {
            this.snapAt(mc.player.getYaw(), mc.player.getPitch());
         } else {
            this.snapBack();
         }
      }
   }

   public void snapBack() {
      if (AntiCheat.INSTANCE.snapBackEnum.is(SnapBack.Force)) {
         mc.getNetworkHandler()
            .sendPacket(new Full(mc.player.getX(), mc.player.getY(), mc.player.getZ(), this.rotationYaw, this.rotationPitch, mc.player.isOnGround()));
      } else if (AntiCheat.INSTANCE.snapBackEnum.is(SnapBack.Tick)) {
         snapBack = true;
      }
   }

   public void lookAt(Vec3d directionVec) {
      this.rotationTo(directionVec);
      this.snapAt(directionVec);
   }

   public void lookAt(BlockPos pos, Direction side) {
      Vec3d var3 = pos.toCenterPos()
         .add(new Vec3d((double)side.getVector().getX() * 0.5, (double)side.getVector().getY() * 0.5, (double)side.getVector().getZ() * 0.5));
      this.lookAt(var3);
   }

   public void snapAt(float yaw, float pitch) {
      this.setRenderRotation(yaw, pitch, true);
      if (AntiCheat.INSTANCE.grimRotation.getValue()) {
         mc.getNetworkHandler().sendPacket(new Full(mc.player.getX(), mc.player.getY(), mc.player.getZ(), yaw, pitch, mc.player.isOnGround()));
      } else {
         mc.getNetworkHandler().sendPacket(new LookAndOnGround(yaw, pitch, mc.player.isOnGround()));
      }
   }

   public void snapAt(Vec3d directionVec) {
      float[] var2 = getRotation(directionVec);
      this.snapAt(var2[0], var2[1]);
   }

   public void rotationTo(Vec3d vec3d) {
      ROTATE_TIMER.reset();
      directionVec = vec3d;
   }

   public boolean inFov(Vec3d directionVec, float fov) {
      float[] var3 = getRotation(
         this.crossHairUpdatePos != null
            ? this.crossHairUpdatePos
            : new Vec3d(mc.player.getX(), mc.player.getY() + (double)mc.player.getEyeHeight(mc.player.getPose()), mc.player.getZ()),
         directionVec
      );
      return this.inFov(var3[0], var3[1], fov);
   }

   public boolean inFov(float yaw, float pitch, float fov) {
      float var4 = MathHelper.angleBetween(yaw, this.rotationYaw);
      float var5;
      return var4 * var4 + (var5 = Math.abs(pitch - this.rotationPitch)) * var5 <= fov * fov;
   }

   @EventListener
   public void onTickMovement(TickMovementEvent event) {
      if (mc.player != null) {
         this.crossHairUpdatePos = new Vec3d(mc.player.getX(), mc.player.getY() + (double)mc.player.getEyeHeight(mc.player.getPose()), mc.player.getZ());
      }
   }

   @EventListener
   public void update(SendMovementPacketsEvent event) {
      if (AntiCheat.INSTANCE.movementSync() && !BaritoneUtil.isActive()) {
         event.setYaw(this.nextYaw);
         event.setPitch(this.nextPitch);
      } else {
         UpdateRotateEvent var2 = UpdateRotateEvent.get(event.getYaw(), event.getPitch());
         CrucifixBeta.EVENT_BUS.post(var2);
         event.setYaw(var2.getYaw());
         event.setPitch(var2.getPitch());
      }
   }

   @EventListener(
      priority = 999
   )
   public void update(TickMovementEvent event) {
      if (mc.player != null) {
         if (AntiCheat.INSTANCE.movementSync() && !BaritoneUtil.isActive()) {
            UpdateRotateEvent var2 = UpdateRotateEvent.get(mc.player.getYaw(), mc.player.getPitch());
            CrucifixBeta.EVENT_BUS.post(var2);
            this.nextYaw = var2.getYaw();
            this.nextPitch = var2.getPitch();
            fixYaw = this.nextYaw;
            fixPitch = this.nextPitch;
         }
      }
   }

   @EventListener(
      priority = -200
   )
   public void onLastRotation(UpdateRotateEvent event) {
      RotationEvent var2 = RotationEvent.get();
      CrucifixBeta.EVENT_BUS.post(var2);
      if (var2.getRotation()) {
         float[] var3 = this.injectStep(new float[]{var2.getYaw(), var2.getPitch()}, var2.getSpeed());
         event.setYaw(var3[0]);
         event.setPitch(var3[1]);
      } else if (var2.getTarget() != null) {
         float[] var4 = this.injectStep(var2.getTarget(), var2.getSpeed());
         event.setYaw(var4[0]);
         event.setPitch(var4[1]);
      } else if (!event.isModified()
         && AntiCheat.INSTANCE.look.getValue()
         && directionVec != null
         && !ROTATE_TIMER.passed((long)(AntiCheat.INSTANCE.rotateTime.getValue() * 1000.0))) {
         float[] var5 = this.injectStep(directionVec, AntiCheat.INSTANCE.steps.getValueFloat());
         event.setYaw(var5[0]);
         event.setPitch(var5[1]);
      }
   }

   @EventListener
   public void travel(TravelEvent e) {
      if (AntiCheat.INSTANCE.movementSync()) {
         if (!BaritoneUtil.isActive()) {
            if (!mc.player.isRiding()) {
               if (e.isPre()) {
                  this.prevYaw = mc.player.getYaw();
                  this.prevPitch = mc.player.getPitch();
                  mc.player.setYaw(fixYaw);
                  mc.player.setPitch(fixPitch);
               } else {
                  mc.player.setYaw(this.prevYaw);
                  mc.player.setPitch(this.prevPitch);
               }
            }
         }
      }
   }

   @EventListener
   public void onJump(JumpEvent e) {
      if (AntiCheat.INSTANCE.movementSync()) {
         if (!BaritoneUtil.isActive()) {
            if (!mc.player.isRiding()) {
               if (e.isPre()) {
                  this.prevYaw = mc.player.getYaw();
                  this.prevPitch = mc.player.getPitch();
                  mc.player.setYaw(fixYaw);
                  mc.player.setPitch(fixPitch);
               } else {
                  mc.player.setYaw(this.prevYaw);
                  mc.player.setPitch(this.prevPitch);
               }
            }
         }
      }
   }

   @EventListener
   public void onFirework(FireworkShooterRotationEvent event) {
      if (AntiCheat.INSTANCE.movementSync()) {
         if (!BaritoneUtil.isActive()) {
            if (event.shooter == mc.player) {
               event.yaw = fixYaw;
               event.pitch = fixPitch;
               event.cancel();
            }
         }
      }
   }

   @EventListener(
      priority = -999
   )
   public void onKeyInput(KeyboardInputEvent e) {
      if (AntiCheat.INSTANCE.movementSync()) {
         if (!BaritoneUtil.isActive()) {
            if (!HoleSnap.INSTANCE.isOn()) {
               if (!mc.player.isRiding() && !Freecam.INSTANCE.isOn()) {
                  float var2 = mc.player.input.movementForward;
                  float var3 = mc.player.input.movementSideways;
                  float var4 = (mc.player.getYaw() - fixYaw) * (float) (Math.PI / 180.0);
                  float var5 = MathHelper.cos(var4);
                  float var6 = MathHelper.sin(var4);
                  mc.player.input.movementSideways = (float)Math.round(var3 * var5 - var2 * var6);
                  mc.player.input.movementForward = (float)Math.round(var2 * var5 + var3 * var6);
               }
            }
         }
      }
   }

   public float[] injectStep(Vec3d vec, float steps) {
      float var3 = AntiCheat.INSTANCE.serverSide.getValue() ? this.getLastYaw() : this.rotationYaw;
      float var4 = AntiCheat.INSTANCE.serverSide.getValue() ? this.getLastPitch() : this.rotationPitch;
      float var5 = MathHelper.wrapDegrees(
         (float)MathHelper.wrapDegrees(Math.toDegrees(Math.atan2(vec.z - mc.player.getZ(), vec.x - mc.player.getX())) - 90.0) - var3
      );
      float var6 = (float)(
            -Math.toDegrees(
               Math.atan2(
                  vec.y - (mc.player.getPos().y + (double)mc.player.getEyeHeight(mc.player.getPose())),
                  Math.sqrt(Math.pow(vec.x - mc.player.getX(), 2.0) + Math.pow(vec.z - mc.player.getZ(), 2.0))
               )
            )
         )
         - var4;
      if (AntiCheat.INSTANCE.random.getValue()) {
         float var7 = (float)Math.toRadians((double)(27 * (mc.player.age % 30)));
         var5 = (float)((double)var5 + Math.sin((double)var7) * 3.0) + MathUtil.random(-1.0F, 1.0F);
         var6 += MathUtil.random(-0.6F, 0.6F);
      }

      if (var5 > 180.0F) {
         var5 -= 180.0F;
      }

      float var12 = 180.0F * steps;
      float var8 = MathHelper.clamp(MathHelper.abs(var5), -var12, var12);
      float var9 = MathHelper.clamp(var6, -45.0F, 45.0F);
      float var10 = var3 + (var5 > 0.0F ? var8 : -var8);
      float var11 = MathHelper.clamp(var4 + var9, -90.0F, 90.0F);
      return new float[]{var10, var11};
   }

   public float[] injectStep(float[] angle, float steps) {
      float var3 = AntiCheat.INSTANCE.serverSide.getValue() ? this.getLastYaw() : this.rotationYaw;
      float var4 = AntiCheat.INSTANCE.serverSide.getValue() ? this.getLastPitch() : this.rotationPitch;
      float var5 = MathHelper.wrapDegrees(angle[0] - var3);
      float var6 = angle[1] - var4;
      if (AntiCheat.INSTANCE.random.getValue()) {
         float var7 = (float)Math.toRadians((double)(27 * (mc.player.age % 30)));
         var5 = (float)((double)var5 + Math.sin((double)var7) * 3.0) + MathUtil.random(-1.0F, 1.0F);
         var6 += MathUtil.random(-0.6F, 0.6F);
      }

      if (var5 > 180.0F) {
         var5 -= 180.0F;
      }

      float var13 = 180.0F * steps;
      float var8 = 90.0F * steps;
      float var9 = MathHelper.clamp(MathHelper.abs(var5), -var13, var13);
      float var10 = MathHelper.clamp(var6, -var8, var8);
      float var11 = var3 + (var5 > 0.0F ? var9 : -var9);
      float var12 = MathHelper.clamp(var4 + var10, -90.0F, 90.0F);
      return new float[]{var11, var12};
   }

   @EventListener(
      priority = -999
   )
   public void onPacketSend(PacketEvent.Sent event) {
      if (mc.player != null) {
         Packet var2 = event.getPacket();
         PlayerMoveC2SPacket var3;
         if (var2 instanceof PlayerMoveC2SPacket && (var3 = (PlayerMoveC2SPacket)var2).changesLook()) {
            this.setLastYaw(var3.getYaw(this.getLastYaw()));
            this.setLastPitch(var3.getPitch(this.getLastPitch()));
            this.setRenderRotation(this.getLastYaw(), this.getLastPitch(), ClientSetting.INSTANCE.sync.getValue());
         }
      }
   }

   @EventListener
   public void onUpdateWalkingPost(TickEvent event) {
      if (event.isPost()) {
         this.setRenderRotation(this.getLastYaw(), this.getLastPitch(), false);
      }
   }

   public void setRenderRotation(float yaw, float pitch, boolean force) {
      if (mc.player != null) {
         if (mc.player.age != this.ticksExisted || force) {
            this.ticksExisted = mc.player.age;
            prevRenderPitch = renderPitch;
            prevRenderYawOffset = renderYawOffset;
            renderYawOffset = this.getRenderYawOffset(yaw, prevRenderYawOffset);
            prevRotationYawHead = rotationYawHead;
            rotationYawHead = yaw;
            renderPitch = pitch;
         }
      }
   }

   private float getRenderYawOffset(float yaw, float offsetIn) {
      float var3 = offsetIn;
      double var4 = mc.player.getX() - mc.player.prevX;
      double var6;
      if (var4 * var4 + (var6 = mc.player.getZ() - mc.player.prevZ) * var6 > 0.0025000002F) {
         float var8 = (float)MathHelper.atan2(var6, var4) * (180.0F / (float)Math.PI) - 90.0F;
         float var9 = MathHelper.abs(MathHelper.wrapDegrees(yaw) - var8);
         var3 = 95.0F < var9 && var9 < 265.0F ? var8 - 180.0F : var8;
      }

      if (mc.player.handSwingProgress > 0.0F) {
         var3 = yaw;
      }

      float var12;
      if ((var12 = MathHelper.wrapDegrees(yaw - (var3 = offsetIn + MathHelper.wrapDegrees(var3 - offsetIn) * 0.3F))) < -75.0F) {
         var12 = -75.0F;
      } else if (var12 >= 75.0F) {
         var12 = 75.0F;
      }

      var3 = yaw - var12;
      if (var12 * var12 > 2500.0F) {
         var3 += var12 * 0.2F;
      }

      return var3;
   }

   public static float[] getRotation(Vec3d eyesPos, Vec3d vec) {
      double var2 = vec.x - eyesPos.x;
      double var4 = vec.y - eyesPos.y;
      double var6 = vec.z - eyesPos.z;
      double var8 = Math.sqrt(var2 * var2 + var6 * var6);
      float var10 = (float)Math.toDegrees(Math.atan2(var6, var2)) - 90.0F;
      float var11 = (float)(-Math.toDegrees(Math.atan2(var4, var8)));
      return new float[]{MathHelper.wrapDegrees(var10), MathHelper.wrapDegrees(var11)};
   }

   public static float[] getRotation(Vec3d vec) {
      Vec3d var1 = mc.player.getEyePos();
      return getRotation(var1, vec);
   }

   public static Rotation getRotation() {
      if (mc != null && mc.player != null) {
         lastRotations = new Rotation(mc.player.getYaw(), mc.player.getPitch());
      }

      return lastRotations;
   }

   public static float getRenderPitch() {
      return renderPitch;
   }

   public static float getRotationYawHead() {
      return rotationYawHead;
   }

   public static float getRenderYawOffset() {
      return renderYawOffset;
   }

   public static float getPrevRenderPitch() {
      return prevRenderPitch;
   }

   public static float getPrevRotationYawHead() {
      return prevRotationYawHead;
   }

   public static float getPrevRenderYawOffset() {
      return prevRenderYawOffset;
   }

   public float getLastYaw() {
      return this.lastYaw;
   }

   public void setLastYaw(float lastYaw) {
      this.lastYaw = lastYaw;
      if (AntiCheat.INSTANCE.forceSync.getValue() && CrucifixBeta.SERVER.playerNull.passedS(0.15)) {
         ((IClientPlayerEntity)mc.player).setLastYaw(lastYaw);
      }
   }

   public float getLastPitch() {
      return this.lastPitch;
   }

   public void setLastPitch(float lastPitch) {
      this.lastPitch = lastPitch;
      if (AntiCheat.INSTANCE.forceSync.getValue() && CrucifixBeta.SERVER.playerNull.passedS(0.15)) {
         ((IClientPlayerEntity)mc.player).setLastPitch(lastPitch);
      }
   }
}
