package com.thebrokenrail.herobrine.entity;

import com.thebrokenrail.herobrine.HerobrineRewoven;
import com.thebrokenrail.herobrine.config.HardcodedConfig;
import com.thebrokenrail.herobrine.data.HerobrineData;
import com.thebrokenrail.herobrine.entity.ai.HerobrineAI;
import com.thebrokenrail.herobrine.entity.ai.stage.TeleportStage;
import net.minecraft.block.BlockState;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.attribute.DefaultAttributeContainer;
import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.entity.boss.BossBar;
import net.minecraft.entity.boss.ServerBossBar;
import net.minecraft.entity.boss.WitherEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.data.DataTracker;
import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.data.TrackedDataHandler;
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.MessageType;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.s2c.play.GameMessageS2CPacket;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvent;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.Arm;
import net.minecraft.util.Formatting;
import net.minecraft.util.Util;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.Difficulty;
import net.minecraft.world.GameRules;
import net.minecraft.world.World;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

@SuppressWarnings("EntityConstructor")
public class HerobrineEntity extends LivingEntity {
    private static final TrackedDataHandler<Pose> POSE_HANDLER = new TrackedDataHandler<Pose>() {
        @Override
        public void write(PacketByteBuf data, Pose object) {
            data.writeEnumConstant(object);
        }

        @Override
        public Pose read(PacketByteBuf packetByteBuf) {
            return packetByteBuf.readEnumConstant(Pose.class);
        }

        @Override
        public Pose copy(Pose object) {
            return object;
        }
    };
    static {
        TrackedDataHandlerRegistry.register(POSE_HANDLER);
    }
    public static final TrackedData<Pose> POSE = DataTracker.registerData(HerobrineEntity.class, POSE_HANDLER);

    private final ServerBossBar bossBar;

    private final List<ServerPlayerEntity> tracking = new ArrayList<>();
    private int noPlayerTeleportCountdown = HardcodedConfig.HEROBRINE_NO_PLAYER_TELEPORT_COUNTDOWN;

    private final HerobrineAI ai = new HerobrineAI(this);

    public HerobrineEntity(EntityType<? extends LivingEntity> entityType, World world) {
        super(entityType, world);
        bossBar = new ServerBossBar(getDisplayName(), BossBar.Color.RED, BossBar.Style.PROGRESS);
        setHealth(getMaxHealth());
        if (world.random.nextDouble() <= 0.001) {
            setCustomName(new TranslatableText("entity." + HerobrineRewoven.NAMESPACE + ".herobrine/easter_egg"));
        }
        noClip = true;
    }

    @Override
    protected void initDataTracker() {
        super.initDataTracker();
        getDataTracker().startTracking(POSE, Pose.NORMAL);
    }

    @Override
    public void setCustomName(Text name) {
        super.setCustomName(name);
        bossBar.setName(name);
    }

    @Override
    public Iterable<ItemStack> getArmorItems() {
        return Collections.emptyList();
    }

    @Override
    public ItemStack getEquippedStack(EquipmentSlot slot) {
        if (slot == EquipmentSlot.MAINHAND) {
            return new ItemStack(Items.DIAMOND_SWORD);
        } else {
            return ItemStack.EMPTY;
        }
    }

    @Override
    public void equipStack(EquipmentSlot slot, ItemStack stack) {
    }

    @Override
    public Arm getMainArm() {
        return Arm.RIGHT;
    }

    @Override
    public boolean isInvulnerableTo(DamageSource damageSource) {
        return damageSource == DamageSource.IN_WALL || super.isInvulnerableTo(damageSource);
    }

    @Override
    public boolean hasNoGravity() {
        return true;
    }

    public static DefaultAttributeContainer.Builder createMobAttributes() {
        return LivingEntity.createLivingAttributes().add(EntityAttributes.GENERIC_ATTACK_DAMAGE, 1.0D).add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.10000000149011612D).add(EntityAttributes.GENERIC_ATTACK_SPEED).add(EntityAttributes.GENERIC_LUCK).add(EntityAttributes.GENERIC_MAX_HEALTH, HardcodedConfig.HEROBRINE_HEALTH);
    }

    private int destroyTime = 0;

    @Override
    public void tick() {
        super.tick();
        if (!getEntityWorld().isClient()) {
            if (getEntityWorld().getDifficulty() == Difficulty.PEACEFUL || HerobrineData.get((ServerWorld) getEntityWorld()).set(getUuid())) {
                // Herobrine Already Exists Or Is Peaceful
                remove();
            } else {
                ai.tick();

                if (destroyTime > 0) {
                    destroyTime--;
                    if (destroyTime == 0 && getEntityWorld().getGameRules().getBoolean(GameRules.DO_MOB_GRIEFING)) {
                        int j = MathHelper.floor(this.getY());
                        int n = MathHelper.floor(this.getX());
                        int o = MathHelper.floor(this.getZ());
                        boolean sendEvent = false;

                        for (int p = -1; p <= 1; p++) {
                            for (int q = -1; q <= 1; q++) {
                                for (int r = 0; r <= 3; r++) {
                                    int s = n + p;
                                    int t = j + r;
                                    int u = o + q;
                                    BlockPos blockPos = new BlockPos(s, t, u);
                                    BlockState blockState = getEntityWorld().getBlockState(blockPos);
                                    if (WitherEntity.canDestroy(blockState)) {
                                        sendEvent = getEntityWorld().breakBlock(blockPos, true, this) || sendEvent;
                                    }
                                }
                            }
                        }

                        if (sendEvent) {
                            getEntityWorld().syncWorldEvent(null, 1022, this.getBlockPos(), 0);
                        }
                    }
                } else {
                    destroyTime = 20;
                }
            }
        }
        bossBar.setPercent(getHealth() / getMaxHealth());
    }

    public String getMessageKey(String id) {
        return "entity." + HerobrineRewoven.NAMESPACE + ".herobrine.msg." + id;
    }

    public void dataTick() {
        boolean reset = false;
        if (getAI().getStage() instanceof TeleportStage) {
            reset = true;
        } else {
            if (tracking.size() > 0) {
                reset = true;
            } else if (noPlayerTeleportCountdown == 0) {
                reset = true;
                List<? extends PlayerEntity> players = getEntityWorld().getPlayers();
                if (players.size() > 0 || getNearestPlayer().distanceTo(this) >= HardcodedConfig.HEROBRINE_MAX_DISTANCE) {
                    while (true) {
                        PlayerEntity player = players.get(getEntityWorld().random.nextInt(players.size()));
                        if (player.isAlive()) {
                            getAI().setStage(new TeleportStage(this, player.getUuid()));
                            break;
                        }
                    }
                }
            } else {
                noPlayerTeleportCountdown--;
            }
        }
        if (reset) {
            noPlayerTeleportCountdown = HardcodedConfig.HEROBRINE_NO_PLAYER_TELEPORT_COUNTDOWN;
        }
    }

    @Override
    public void onStartedTrackingBy(ServerPlayerEntity player) {
        super.onStartedTrackingBy(player);
        bossBar.addPlayer(player);
        tracking.add(player);
    }

    @Override
    public void onStoppedTrackingBy(ServerPlayerEntity player) {
        super.onStoppedTrackingBy(player);
        bossBar.removePlayer(player);
        tracking.remove(player);
    }

    public ServerPlayerEntity getNearestPlayer() {
        tracking.sort((player1, player2) -> Float.compare(distanceTo(player1), distanceTo(player2)));
        if (tracking.size() > 0) {
            int i = 0;
            while (true) {
                if (tracking.get(i).isAlive()) {
                    return tracking.get(i);
                } else {
                    i++;
                }
            }
        } else {
            return null;
        }
    }

    public static void summon(PlayerEntity player) {
        HerobrineData data = HerobrineData.get((ServerWorld) player.getEntityWorld());
        HerobrineEntity entity = data.get(player.getEntityWorld());
        if (entity == null) {
            ServerWorld world = Objects.requireNonNull(player.getEntityWorld().getServer()).getWorld(World.OVERWORLD);
            entity = new HerobrineEntity(HerobrineRewoven.HEROBRINE_ENTITY_TYPE, world);
            entity.updatePosition(0d, 51200d, 0d);
            assert world != null;
            Objects.requireNonNull(world.getServer().getWorld(World.OVERWORLD)).spawnEntity(entity);
            data.set(entity.getUuid());
            world.getServer().getPlayerManager().sendToAll(new GameMessageS2CPacket(new TranslatableText("multiplayer.player.joined", entity.getDisplayName()).formatted(Formatting.YELLOW), MessageType.CHAT, Util.NIL_UUID));
            entity.getAI().setStage(new TeleportStage(entity, world.getSpawnPos()));
            world.getServer().getPlayerManager().sendToAll(new GameMessageS2CPacket(new TranslatableText(entity.getMessageKey("spawn"), entity.getDisplayName()), MessageType.CHAT, Util.NIL_UUID));
        }
    }

    public ServerWorld getServerWorld() {
        return (ServerWorld) getEntityWorld();
    }

    public HerobrineAI getAI() {
        return ai;
    }

    public void handleStageChange() {
        getDataTracker().set(POSE, Pose.NORMAL);
    }

    @Override
    public void writeCustomDataToTag(CompoundTag tag) {
        super.writeCustomDataToTag(tag);
        tag.put("AI", ai.toTag());
        tag.putInt("DestroyTime", destroyTime);
    }

    @Override
    public void readCustomDataFromTag(CompoundTag tag) {
        super.readCustomDataFromTag(tag);
        ai.fromTag(tag.getCompound("AI"));
        destroyTime = tag.getInt("DestroyTime");
    }

    @Override
    public void setRotation(float yaw, float pitch) {
        super.setRotation(yaw, pitch);
    }

    public void playSound(SoundEvent sound) {
        getEntityWorld().playSound(null, getX(), getY(), getZ(), sound, SoundCategory.HOSTILE, 1.0f, 1.0f);
    }

    public enum Pose {
        NORMAL,
        FIREBALL,
        LIGHTNING
    }
}
