package asm.mixins;

import asm.accessors.IClientPlayerEntity;
import com.mojang.authlib.GameProfile;
import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.events.Event;
import dev.crucifixbeta.api.events.impl.MoveEvent;
import dev.crucifixbeta.api.events.impl.MovedEvent;
import dev.crucifixbeta.api.events.impl.SendMovementPacketsEvent;
import dev.crucifixbeta.api.events.impl.TickEvent;
import dev.crucifixbeta.api.events.impl.TickMovementEvent;
import dev.crucifixbeta.core.impl.CommandManager;
import dev.crucifixbeta.core.impl.RotationManager;
import dev.crucifixbeta.mod.modules.impl.client.AntiCheat;
import dev.crucifixbeta.mod.modules.impl.client.ClientSetting;
import dev.crucifixbeta.mod.modules.impl.exploit.PacketControl;
import dev.crucifixbeta.mod.modules.impl.movement.NoSlow;
import dev.crucifixbeta.mod.modules.impl.movement.Velocity;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.input.Input;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.MovementType;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
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;

@Environment(EnvType.CLIENT)
@Mixin({ClientPlayerEntity.class})
public abstract class MixinClientPlayerEntity extends AbstractClientPlayerEntity {
   @Shadow
   public Input input;
   @Final
   @Shadow
   protected MinecraftClient client;
   @Unique
   private float preYaw;
   @Unique
   private float prePitch;
   @Unique
   private boolean rotation = false;
   @Shadow
   private double lastX;
   @Shadow
   private double lastBaseY;
   @Shadow
   private double lastZ;
   @Shadow
   private int ticksSinceLastPositionPacketSent;
   @Shadow
   private float lastYaw;
   @Shadow
   private float lastPitch;

   public MixinClientPlayerEntity(ClientWorld world, GameProfile profile) {
      super(world, profile);
   }

   @Inject(
      method = {"pushOutOfBlocks"},
      at = {@At("HEAD")},
      cancellable = true
   )
   private void onPushOutOfBlocksHook(double x, double d, CallbackInfo info) {
      if (Velocity.INSTANCE.isOn() && Velocity.INSTANCE.blockPush.getValue()) {
         info.cancel();
      }
   }

   @Redirect(
      method = {"tickMovement"},
      at = @At(
         value = "INVOKE",
         target = "Lnet/minecraft/client/network/ClientPlayerEntity;isUsingItem()Z"
      ),
      require = 0
   )
   private boolean tickMovementHook(ClientPlayerEntity player) {
      return NoSlow.INSTANCE.noSlow() ? false : player.isUsingItem();
   }

   @Inject(
      at = {@At("HEAD")},
      method = {"tickNausea"},
      cancellable = true
   )
   private void updateNausea(CallbackInfo ci) {
      if (ClientSetting.INSTANCE.portalGui()) {
         ci.cancel();
      }
   }

   @Inject(
      method = {"move"},
      at = {@At(
         value = "INVOKE",
         target = "Lnet/minecraft/client/network/AbstractClientPlayerEntity;move(Lnet/minecraft/entity/MovementType;Lnet/minecraft/util/math/Vec3d;)V"
      )},
      cancellable = true
   )
   public void onMoveHook(MovementType movementType, Vec3d movement, CallbackInfo ci) {
      MoveEvent var4 = MoveEvent.get(movement.x, movement.y, movement.z);
      CrucifixBeta.EVENT_BUS.post(var4);
      if (var4.isCancelled()) {
         ci.cancel();
      } else if (var4.modify) {
         ci.cancel();
         super.move(movementType, new Vec3d(var4.getX(), var4.getY(), var4.getZ()));
         CrucifixBeta.EVENT_BUS.post(MovedEvent.INSTANCE);
      }
   }

   @Inject(
      method = {"move"},
      at = {@At("TAIL")}
   )
   public void onMoveReturnHook(MovementType movementType, Vec3d movement, CallbackInfo ci) {
      CrucifixBeta.EVENT_BUS.post(MovedEvent.INSTANCE);
   }

   @Shadow
   @Override
   public abstract float getPitch(float f);

   @Shadow
   @Override
   public abstract float getYaw(float f);

   @Inject(
      method = {"tick"},
      at = {@At("HEAD")}
   )
   private void tickHead(CallbackInfo ci) {
      try {
         CrucifixBeta.EVENT_BUS.post(TickEvent.get(Event.Stage.Pre));
      } catch (Exception var3) {
         var3.printStackTrace();
         if (ClientSetting.INSTANCE.debug.getValue()) {
            CommandManager.sendMessage("§4An error has occurred (ClientPlayerEntity.tick() [HEAD]) Message: [" + var3.getMessage() + "]");
         }
      }
   }

   @Inject(
      method = {"tick"},
      at = {@At("RETURN")}
   )
   private void tickReturn(CallbackInfo ci) {
      try {
         CrucifixBeta.EVENT_BUS.post(TickEvent.get(Event.Stage.Post));
      } catch (Exception var3) {
         var3.printStackTrace();
         if (ClientSetting.INSTANCE.debug.getValue()) {
            CommandManager.sendMessage("§4An error has occurred (ClientPlayerEntity.tick() [RETURN]) Message: [" + var3.getMessage() + "]");
         }
      }
   }

   @Inject(
      method = {"sendMovementPackets"},
      at = {@At("HEAD")}
   )
   private void onSendMovementPacketsHead(CallbackInfo info) {
      this.rotation();
      if (PacketControl.INSTANCE.isOn()
         && PacketControl.INSTANCE.positionSync.getValue()
         && this.ticksSinceLastPositionPacketSent >= PacketControl.INSTANCE.positionDelay.getValueInt() - 1) {
         ((IClientPlayerEntity)this).setTicksSinceLastPositionPacketSent(50);
      }

      if (RotationManager.snapBack) {
         ((IClientPlayerEntity)this).setTicksSinceLastPositionPacketSent(50);
         ((IClientPlayerEntity)this).setLastYaw(999.0F);
         RotationManager.snapBack = false;
      } else {
         if (AntiCheat.INSTANCE.fullPackets.getValue()) {
            double var2 = this.getX() - this.lastX;
            double var4 = this.getY() - this.lastBaseY;
            double var6 = this.getZ() - this.lastZ;
            double var8 = (double)(this.getYaw() - this.lastYaw);
            double var10 = (double)(this.getPitch() - this.lastPitch);
            boolean var12 = var8 != 0.0 || var10 != 0.0;
            if (AntiCheat.INSTANCE.force.getValue()
               || !(MathHelper.squaredMagnitude(var2, var4, var6) > MathHelper.square(2.0E-4)) && this.ticksSinceLastPositionPacketSent >= 19
               || var12) {
               ((IClientPlayerEntity)this).setTicksSinceLastPositionPacketSent(50);
               ((IClientPlayerEntity)this).setLastYaw(999.0F);
            }
         }
      }
   }

   @Inject(
      method = {"tick"},
      at = {@At(
         value = "INVOKE",
         target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;sendPacket(Lnet/minecraft/network/packet/Packet;)V",
         ordinal = 0
      )}
   )
   private void onTickHasVehicleBeforeSendPackets(CallbackInfo info) {
      this.rotation();
   }

   @Unique
   private void rotation() {
      this.rotation = true;
      this.preYaw = this.getYaw();
      this.prePitch = this.getPitch();
      SendMovementPacketsEvent var1 = SendMovementPacketsEvent.get(this.getYaw(), this.getPitch());
      CrucifixBeta.EVENT_BUS.post(var1);
      CrucifixBeta.ROTATION.rotationYaw = var1.getYaw();
      CrucifixBeta.ROTATION.rotationPitch = var1.getPitch();
      this.setYaw(var1.getYaw());
      this.setPitch(var1.getPitch());
   }

   @Inject(
      method = {"sendMovementPackets"},
      at = {@At("TAIL")}
   )
   private void onSendMovementPacketsTail(CallbackInfo info) {
      if (this.rotation) {
         this.setYaw(this.preYaw);
         this.setPitch(this.prePitch);
         this.rotation = false;
      }
   }

   @Inject(
      method = {"tick"},
      at = {@At(
         value = "INVOKE",
         target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;sendPacket(Lnet/minecraft/network/packet/Packet;)V",
         ordinal = 1,
         shift = Shift.AFTER
      )}
   )
   private void onTickHasVehicleAfterSendPackets(CallbackInfo info) {
      if (this.rotation) {
         this.setYaw(this.preYaw);
         this.setPitch(this.prePitch);
         this.rotation = false;
      }
   }

   @Inject(
      method = {"tickMovement"},
      at = {@At("HEAD")}
   )
   private void tickMovement(CallbackInfo ci) {
      CrucifixBeta.EVENT_BUS.post(TickMovementEvent.INSTANCE);
   }
}
