package asm.mixins;

import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.events.impl.EntityVelocityUpdateEvent;
import dev.crucifixbeta.api.events.impl.GameLeftEvent;
import dev.crucifixbeta.api.events.impl.InventoryS2CPacketEvent;
import dev.crucifixbeta.api.events.impl.S2CCloseScreenEvent;
import dev.crucifixbeta.api.events.impl.SendMessageEvent;
import dev.crucifixbeta.api.events.impl.ServerChangePositionEvent;
import dev.crucifixbeta.mod.modules.impl.exploit.AntiPacket;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientCommonNetworkHandler;
import net.minecraft.client.network.ClientConnectionState;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.NetworkThreadUtils;
import net.minecraft.network.packet.c2s.play.TeleportConfirmC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.Full;
import net.minecraft.network.packet.s2c.play.CloseScreenS2CPacket;
import net.minecraft.network.packet.s2c.play.EnterReconfigurationS2CPacket;
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
import net.minecraft.network.packet.s2c.play.InventoryS2CPacket;
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
import net.minecraft.network.packet.s2c.play.PositionFlag;
import net.minecraft.util.math.Vec3d;
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({ClientPlayNetworkHandler.class})
public abstract class MixinClientPlayNetworkHandler extends ClientCommonNetworkHandler {
   @Shadow
   private ClientWorld world;
   @Unique
   private boolean frog$worldNotNull;
   @Unique
   private boolean ignore;

   protected MixinClientPlayNetworkHandler(MinecraftClient client, ClientConnection connection, ClientConnectionState connectionState) {
      super(client, connection, connectionState);
   }

   @Inject(
      method = {"onEnterReconfiguration"},
      at = {@At(
         value = "INVOKE",
         target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/util/thread/ThreadExecutor;)V",
         shift = Shift.AFTER
      )}
   )
   private void onEnterReconfiguration(EnterReconfigurationS2CPacket packet, CallbackInfo info) {
      CrucifixBeta.EVENT_BUS.post(GameLeftEvent.INSTANCE);
   }

   @Inject(
      method = {"onGameJoin"},
      at = {@At("HEAD")}
   )
   private void onGameJoinHead(GameJoinS2CPacket packet, CallbackInfo info) {
      this.frog$worldNotNull = this.world != null;
   }

   @Inject(
      method = {"onGameJoin"},
      at = {@At("TAIL")}
   )
   private void onGameJoinTail(GameJoinS2CPacket packet, CallbackInfo info) {
      if (this.frog$worldNotNull) {
         CrucifixBeta.EVENT_BUS.post(GameLeftEvent.INSTANCE);
      }
   }

   @Shadow
   public abstract void sendChatMessage(String string);

   @Inject(
      method = {"onInventory"},
      at = {@At(
         value = "INVOKE",
         target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/util/thread/ThreadExecutor;)V",
         shift = Shift.AFTER
      )},
      cancellable = true
   )
   public void onInventoryS2CPacket(InventoryS2CPacket packet, CallbackInfo ci) {
      InventoryS2CPacketEvent var3 = InventoryS2CPacketEvent.get(packet);
      CrucifixBeta.EVENT_BUS.post(var3);
      if (var3.isCancelled()) {
         ci.cancel();
      }
   }

   @Inject(
      method = {"sendChatMessage"},
      at = {@At("HEAD")},
      cancellable = true
   )
   private void onSendChatMessage(String message, CallbackInfo ci) {
      if (!this.ignore) {
         if (message.startsWith(CrucifixBeta.getPrefix())) {
            CrucifixBeta.COMMAND.command(message.split(" "));
            ci.cancel();
         } else {
            SendMessageEvent var3 = SendMessageEvent.get(message);
            CrucifixBeta.EVENT_BUS.post(var3);
            if (var3.isCancelled()) {
               ci.cancel();
            } else if (!var3.message.equals(var3.defaultMessage)) {
               this.ignore = true;
               this.sendChatMessage(var3.message);
               this.ignore = false;
               ci.cancel();
            }
         }
      }
   }

   @Inject(
      method = {"onCloseScreen"},
      at = {@At(
         value = "INVOKE",
         target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/util/thread/ThreadExecutor;)V",
         shift = Shift.AFTER
      )},
      cancellable = true
   )
   public void onCloseScreen(CloseScreenS2CPacket packet, CallbackInfo ci) {
      S2CCloseScreenEvent var3 = S2CCloseScreenEvent.get();
      CrucifixBeta.EVENT_BUS.post(var3);
      if (var3.isCancelled()) {
         ci.cancel();
      }
   }

   @Redirect(
      method = {"onEntityVelocityUpdate"},
      at = @At(
         value = "INVOKE",
         target = "Lnet/minecraft/entity/Entity;setVelocityClient(DDD)V"
      ),
      require = 0
   )
   private void velocityHook(Entity instance, double x, double y, double z) {
      EntityVelocityUpdateEvent var8 = EntityVelocityUpdateEvent.get(instance, x, y, z, false);
      CrucifixBeta.EVENT_BUS.post(var8);
      if (!var8.isCancelled()) {
         instance.setVelocityClient(var8.getX(), var8.getY(), var8.getZ());
      }
   }

   @Redirect(
      method = {"onExplosion"},
      at = @At(
         value = "INVOKE",
         target = "Lnet/minecraft/util/math/Vec3d;add(DDD)Lnet/minecraft/util/math/Vec3d;"
      ),
      require = 0
   )
   private Vec3d velocityHook2(Vec3d instance, double x, double y, double z) {
      EntityVelocityUpdateEvent var8 = EntityVelocityUpdateEvent.get(this.client.player, x, y, z, true);
      CrucifixBeta.EVENT_BUS.post(var8);
      return !var8.isCancelled() ? instance.add(var8.getX(), var8.getY(), var8.getZ()) : instance;
   }

   @Inject(
      method = {"onPlayerPositionLook"},
      at = {@At(
         value = "INVOKE",
         target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/util/thread/ThreadExecutor;)V",
         shift = Shift.AFTER
      )},
      cancellable = true
   )
   public void onPlayerPositionLook(PlayerPositionLookS2CPacket packet, CallbackInfo ci) {
      boolean var3 = AntiPacket.INSTANCE.isOn() && AntiPacket.INSTANCE.s2CRotate.getValue() && CrucifixBeta.SERVER.playerNull.passedS(0.25);
      if (var3) {
         ci.cancel();
         NetworkThreadUtils.forceMainThread(packet, ClientPlayNetworkHandler.class.cast(this), this.client);
         ClientPlayerEntity var5 = this.client.player;
         Vec3d var6 = var5.getVelocity();
         boolean var7 = packet.getFlags().contains(PositionFlag.X);
         boolean var8 = packet.getFlags().contains(PositionFlag.Y);
         boolean var9 = packet.getFlags().contains(PositionFlag.Z);
         double var10;
         double var12;
         if (var7) {
            var10 = var6.getX();
            var12 = var5.getX() + packet.getX();
            var5.lastRenderX = var5.lastRenderX + packet.getX();
            var5.prevX = var5.prevX + packet.getX();
         } else {
            var10 = 0.0;
            var5.lastRenderX = var12 = packet.getX();
            var5.prevX = var12;
         }

         double var14;
         double var16;
         if (var8) {
            var14 = var6.getY();
            var16 = var5.getY() + packet.getY();
            var5.lastRenderY = var5.lastRenderY + packet.getY();
            var5.prevY = var5.prevY + packet.getY();
         } else {
            var14 = 0.0;
            var5.lastRenderY = var16 = packet.getY();
            var5.prevY = var16;
         }

         double var18;
         double var20;
         if (var9) {
            var18 = var6.getZ();
            var20 = var5.getZ() + packet.getZ();
            var5.lastRenderZ = var5.lastRenderZ + packet.getZ();
            var5.prevZ = var5.prevZ + packet.getZ();
         } else {
            var18 = 0.0;
            var5.lastRenderZ = var20 = packet.getZ();
            var5.prevZ = var20;
         }

         var5.setPosition(var12, var16, var20);
         var5.setVelocity(var10, var14, var18);
         if (AntiPacket.INSTANCE.applyYaw.getValue()) {
            float var22 = packet.getYaw();
            float var23 = packet.getPitch();
            if (packet.getFlags().contains(PositionFlag.X_ROT)) {
               var23 += CrucifixBeta.ROTATION.getLastPitch();
            }

            if (packet.getFlags().contains(PositionFlag.Y_ROT)) {
               var22 += CrucifixBeta.ROTATION.getLastYaw();
            }

            this.connection.send(new TeleportConfirmC2SPacket(packet.getTeleportId()));
            this.connection.send(new Full(var5.getX(), var5.getY(), var5.getZ(), var22, var23, false));
         } else {
            this.connection.send(new TeleportConfirmC2SPacket(packet.getTeleportId()));
            this.connection
               .send(new Full(var5.getX(), var5.getY(), var5.getZ(), CrucifixBeta.ROTATION.getLastYaw(), CrucifixBeta.ROTATION.getLastPitch(), false));
         }

         CrucifixBeta.EVENT_BUS.post(ServerChangePositionEvent.INSTANCE);
      }
   }
}
