package com.thebrokenrail.gestus.entity;

import com.thebrokenrail.gestus.Gestus;
import com.thebrokenrail.gestus.emote.EmoteLayer;
import com.thebrokenrail.gestus.emote.EmotePart;
import com.thebrokenrail.gestus.mixin.ArmorStandEntityAccessor;
import com.thebrokenrail.gestus.skin.SkinColor;
import com.thebrokenrail.gestus.util.ServerPlayerEntityExtension;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.decoration.ArmorStandEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.CrossbowItem;
import net.minecraft.item.DyeableItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Arm;
import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
import net.minecraft.util.UseAction;
import net.minecraft.util.math.EulerAngle;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;

import java.util.Map;

@SuppressWarnings("EntityConstructor")
public class FakePlayerEntity extends ArmorStandEntity {
    private static final int STILL_LAYER = 0;
    private static final int WALK_LAYER = 1;
    private static final int HIT_LAYER = 2;
    private static final int CUSTOM_LAYER = 3;
    private static final int AIM_LAYER = 4;

    private static final Identifier STILL_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/still"); // Still Layer

    private static final Identifier WALK_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/walk"); // Walk Layer
    private static final Identifier SPRINT_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/sprint"); // Walk Layer
    private static final Identifier RIDE_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/ride"); // Walk Layer

    private static final Identifier HIT_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/hit"); // Hit Layer

    private static final Identifier AIM_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/aim"); // Aim Layer
    private static final Identifier SHIELD_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/shield"); // Aim Layer
    private static final Identifier TRIDENT_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/trident"); // Aim Layer
    private static final Identifier EAT_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/eat"); // Aim Layer

    public final ServerPlayerEntity player;

    private final EmoteLayer[] layers;

    public FakePlayerEntity(EntityType<FakePlayerEntity> entityType, World world, Text customName, ServerPlayerEntity player) {
        super(entityType, world);
        setSilent(true);
        ((ArmorStandEntityAccessor) this).callSetShowArms(true);
        ((ArmorStandEntityAccessor) this).callSetHideBasePlate(true);
        setInvulnerable(true);
        setNoGravity(true);
        setCustomName(customName);
        setCustomNameVisible(true);
        noClip = true;
        this.player = player;

        layers = new EmoteLayer[5];
        layers[STILL_LAYER] = new EmoteLayer(STILL_EMOTE, true); // Still Layer
        layers[WALK_LAYER] = new EmoteLayer(null, false); // Walk Layer
        layers[HIT_LAYER] = new EmoteLayer(null, false); // Hit Layer
        layers[CUSTOM_LAYER] = new EmoteLayer(null, false); // Custom Layer
        layers[AIM_LAYER] = new EmoteLayer(null, false); // Aim Layer
    }

    public FakePlayerEntity(EntityType<FakePlayerEntity> entityType, World world) {
        this(entityType, world, new LiteralText("Invalid"), null);
        if (world.isClient()) {
            throw new UnsupportedOperationException();
        }
    }

    @Override
    public void tick() {
        super.tick();
        if (getEntityWorld().isClient()) {
            throw new UnsupportedOperationException();
        } else if (player == null || player.removed || ((ServerPlayerEntityExtension) player).getShadow() != this) {
            remove();
        }
    }

    private void applyPose() {
        for (EmoteLayer layer : layers) {
            applyPose(layer);
        }
    }

    private void applyPose(EmoteLayer layer) {
        Map<EmotePart, EulerAngle> data = layer.next();
        for (Map.Entry<EmotePart, EulerAngle> entry : data.entrySet()) {
            TrackedData<EulerAngle> tracker;
            switch (entry.getKey()) {
                case RIGHT_ARM: {
                    tracker = TRACKER_RIGHT_ARM_ROTATION;
                    break;
                }
                case LEFT_ARM: {
                    tracker = TRACKER_LEFT_ARM_ROTATION;
                    break;
                }
                case RIGHT_LEG: {
                    tracker = TRACKER_RIGHT_LEG_ROTATION;
                    break;
                }
                case LEFT_LEG: {
                    tracker = TRACKER_LEFT_LEG_ROTATION;
                    break;
                }
                case BODY: {
                    tracker = TRACKER_BODY_ROTATION;
                    break;
                }
                default: {
                    throw new UnsupportedOperationException();
                }
            }
            getDataTracker().set(tracker, entry.getValue());
        }
    }

    private static final double MINIMUM_MOVEMENT_FOR_ANIMATION = 0.02d;

    private void updateWalk() {
        if (player.hasVehicle()) {
            layers[WALK_LAYER].play(RIDE_EMOTE, false);
        } else if (!layers[WALK_LAYER].isPlaying()) {
            Vec3d lastPos = ((ServerPlayerEntityExtension) player).getLastPos();
            Vec3d pos = player.getPos();
            if (lastPos != null && pos != null && (Math.abs(pos.getX() - lastPos.getX()) >= MINIMUM_MOVEMENT_FOR_ANIMATION || Math.abs(pos.getZ() - lastPos.getZ()) >= MINIMUM_MOVEMENT_FOR_ANIMATION)) {
                if (player.isSprinting()) {
                    layers[WALK_LAYER].play(SPRINT_EMOTE, false);
                } else {
                    layers[WALK_LAYER].play(WALK_EMOTE, false);
                }
            }
        }
    }

    private boolean updateAim(Hand hand, boolean active) {
        ItemStack stack = player.getStackInHand(hand);

        Identifier emote = null;

        UseAction action = null;
        if (player.getItemUseTimeLeft() > 0 && active) {
            action = stack.getUseAction();
        } else if (!player.handSwinging && stack.getItem() == Items.CROSSBOW && CrossbowItem.isCharged(stack)) {
            action = UseAction.CROSSBOW;
        }

        if (action != null) {
            switch (action) {
                case BLOCK: {
                    emote = SHIELD_EMOTE;
                    break;
                }
                case CROSSBOW:
                case BOW: {
                    emote = AIM_EMOTE;
                    break;
                }
                case SPEAR: {
                    emote = TRIDENT_EMOTE;
                    break;
                }
                case EAT:
                case DRINK: {
                    emote = EAT_EMOTE;
                    break;
                }
            }
        }

        if (emote != null) {
            layers[AIM_LAYER].play(emote, (hand == Hand.OFF_HAND) != isLeftHanded());
            return false;
        } else {
            return true;
        }
    }

    private void updateAim() {
        Hand hand = player.getActiveHand();
        if (updateAim(hand, true) && updateAim(hand == Hand.MAIN_HAND ? Hand.OFF_HAND : Hand.MAIN_HAND, false)) {
            layers[AIM_LAYER].play(null, false);
        }
    }

    private ItemStack head = null;

    private void updateHead() {
        if (head == null) {
            head = new ItemStack(Items.PLAYER_HEAD);
            CompoundTag tag = new CompoundTag();
            tag.putString("SkullOwner", player.getGameProfile().getName());
            Items.PLAYER_HEAD.postProcessTag(tag);
            head.setTag(tag);
        }
    }

    private SkinColor skinColor = null;

    private void updateSkinColor() {
        if (skinColor == null) {
            skinColor = SkinColor.get(player.getUuid());
            if (skinColor == null) {
                skinColor = SkinColor.blank();
            }
        }
    }

    private ItemStack colorItem(Item item, int color) {
        ItemStack stack = new ItemStack(item);
        if (color != -1) {
            ((DyeableItem) item).setColor(stack, color);
        }
        return stack;
    }

    private ItemStack getFallbackItem(EquipmentSlot slot, ItemStack stack, boolean invisible) {
        if (stack.isEmpty() && !invisible) {
            switch (slot) {
                case HEAD: {
                    updateHead();
                    return head;
                }
                case CHEST: {
                    updateSkinColor();
                    return colorItem(Items.LEATHER_CHESTPLATE, skinColor.chest);
                }
                case LEGS: {
                    updateSkinColor();
                    return colorItem(Items.LEATHER_LEGGINGS, skinColor.legs);
                }
                case FEET: {
                    updateSkinColor();
                    return colorItem(Items.LEATHER_BOOTS, skinColor.boots);
                }
                default: {
                    return ItemStack.EMPTY;
                }
            }
        } else {
            return stack;
        }
    }

    private boolean isLeftHanded() {
        return player.getMainArm() == Arm.LEFT;
    }

    private void updateEquipment() {
        for (EquipmentSlot slot : EquipmentSlot.values()) {
            ItemStack target = getFallbackItem(slot, player.getEquippedStack(slot).copy(), isInvisible()).copy();
            if (!getEquippedStack(slot).isItemEqual(target)) {
                equipStack(slot, target);
            }
        }
        if (isLeftHanded()) {
            ItemStack main = getEquippedStack(EquipmentSlot.MAINHAND);
            ItemStack off = getEquippedStack(EquipmentSlot.OFFHAND);
            equipStack(EquipmentSlot.OFFHAND, main);
            equipStack(EquipmentSlot.MAINHAND, off);
        }
    }

    public void update() {
        updateEquipment();

        updateWalk();

        updateAim();

        applyPose();
    }

    public void playCustomEmote(Identifier id) {
        layers[CUSTOM_LAYER].play(id, isLeftHanded());
    }

    public void hit(Hand hand) {
        layers[HIT_LAYER].play(HIT_EMOTE, (hand == Hand.OFF_HAND) != isLeftHanded());
    }

    @Override
    public boolean damage(DamageSource source, float amount) {
        return player.damage(source, amount);
    }

    @Override
    public ActionResult interactAt(PlayerEntity player, Vec3d hitPos, Hand hand) {
        return ActionResult.PASS;
    }

    @Override
    public ActionResult interact(PlayerEntity player, Hand hand) {
        return ActionResult.PASS;
    }
}
