package com.r3944realms.leashedplayer.mixin.client;

import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.datafixers.util.Either;
import com.r3944realms.leashedplayer.LeashedPlayer;
import com.r3944realms.leashedplayer.content.entities.LeashRopeArrow;
import com.r3944realms.leashedplayer.modInterface.ILivingEntityExtension;
import com.r3944realms.leashedplayer.modInterface.IPlayerRendererExtension;
import com.r3944realms.leashedplayer.modInterface.PlayerLeashable;
import net.minecraft.client.Camera;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Leashable;
import net.minecraft.world.entity.decoration.LeashFenceKnotEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
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.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import javax.annotation.Nullable;
import java.util.List;
import java.util.UUID;

@Mixin(LevelRenderer.class)
public abstract class MixinLevelRenderer {
    @Shadow
    @Nullable
    private ClientLevel level;

    @Shadow protected abstract void renderEntity(Entity pEntity, double pCamX, double pCamY, double pCamZ, float pPartialTick, PoseStack pPoseStack, MultiBufferSource pBufferSource);

    @Shadow @Final
    private Minecraft minecraft;

    @Shadow @Final private RenderBuffers renderBuffers;

    @Inject(
            method = {"renderLevel"},
            at = @At(
                    value = "INVOKE",
                    target = "Lnet/minecraft/client/renderer/RenderBuffers;bufferSource()Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;",
                    shift = At.Shift.AFTER
            )
    )
    private void renderLevel(DeltaTracker pDeltaTracker, boolean pRenderBlockOutline, Camera pCamera, GameRenderer pGameRenderer, LightTexture pLightTexture, Matrix4f pFrustumMatrix, Matrix4f pProjectionMatrix, CallbackInfo ci) {
        assert this.level != null;
        PoseStack poseStack = new PoseStack();
        MultiBufferSource.BufferSource multibuffersource$buffersource = this.renderBuffers.bufferSource();
        for(Entity entity : this.level.entitiesForRendering()) {
            //对于玩家实体拴绳渲染（从第一人称视角）
            if (entity instanceof AbstractClientPlayer abstractClientPlayer) {
                if(!(pCamera.getEntity() instanceof AbstractClientPlayer)) continue;
                Minecraft mc = Minecraft.getInstance();
                PlayerRenderer playerRenderer = (PlayerRenderer) mc.getEntityRenderDispatcher().getRenderer(abstractClientPlayer);
                IPlayerRendererExtension playerRendererExtension = (IPlayerRendererExtension) playerRenderer;
                if (mc.options.getCameraType().isFirstPerson()) {
                    Leashable.LeashData leashDataFromEntityData = ((PlayerLeashable) abstractClientPlayer).getLeashDataFromEntityData();
                    if(leashDataFromEntityData == null) continue;
                    Either<UUID, BlockPos> delayedLeashInfo = leashDataFromEntityData.delayedLeashInfo;
                    if(delayedLeashInfo != null) {
                        float partialTickTime = pCamera.getPartialTickTime();
                        Vec3 position = pCamera.getPosition();
                        double dX = Mth.lerp(partialTickTime, abstractClientPlayer.xOld, abstractClientPlayer.getX()) - position.x;
                        double dY = Mth.lerp(partialTickTime, abstractClientPlayer.yOld, abstractClientPlayer.getY()) - position.y;
                        double dZ = Mth.lerp(partialTickTime, abstractClientPlayer.zOld, abstractClientPlayer.getZ()) - position.z;
                        Vec3 vec3 = playerRenderer.getRenderOffset(abstractClientPlayer, partialTickTime);
                        double dX_ = dX + vec3.x();
                        double dY_ = dY + vec3.y();
                        double dZ_ = dZ + vec3.z();
                        poseStack.pushPose();
                        poseStack.translate(dX_, dY_, dZ_);
                        ClientLevel level = mc.level;
                        if (delayedLeashInfo.right().isPresent() && delayedLeashInfo.left().isEmpty()) {
                            assert level != null;
                            playerRendererExtension.renderLeashForCamera(pCamera, partialTickTime, poseStack, multibuffersource$buffersource, LeashFenceKnotEntity.getOrCreateKnot(level, delayedLeashInfo.right().get()));
                        } else if (delayedLeashInfo.right().isEmpty() && delayedLeashInfo.left().isPresent()) {
                            assert level != null;

                            Player playerByUUID = level.getPlayerByUUID(delayedLeashInfo.left().get());
                            if (playerByUUID != null) {
                                playerRendererExtension.renderLeashForCamera(pCamera, partialTickTime, poseStack, multibuffersource$buffersource, playerByUUID);
                            } else {
                                double MaxLeashLength = ((ILivingEntityExtension) abstractClientPlayer).getLeashLength() * LeashedPlayer.M1() * LeashedPlayer.M2();
                                List<Entity> entities = level.getEntities(
                                        null,
                                        new AABB(
                                                abstractClientPlayer.getX() - MaxLeashLength,
                                                abstractClientPlayer.getY() - MaxLeashLength,
                                                abstractClientPlayer.getZ() - MaxLeashLength,
                                                abstractClientPlayer.getX() + MaxLeashLength,
                                                abstractClientPlayer.getY() + MaxLeashLength,
                                                abstractClientPlayer.getZ() + MaxLeashLength
                                        )
                                );
                                Entity holder = null;
                                for (Entity entity_ : entities) {
                                    if(entity_.getUUID().equals(delayedLeashInfo.left().get())) {
                                        holder = entity_;
                                        break;
                                    }
                                }
                                if (holder != null) {
                                    if(holder instanceof LeashRopeArrow) {
                                        playerRendererExtension.renderLeashForCamera(pCamera, partialTickTime, poseStack, multibuffersource$buffersource, holder, new Vec3(0.,-0.09, 0));//TODO: 待擴展Vec3吗？
                                    }
                                     else playerRendererExtension.renderLeashForCamera(pCamera, partialTickTime, poseStack, multibuffersource$buffersource, holder);
                                }
                            }
                            break;
                        }
                    }
                }
            }
        }
    }
}

