package com.thebrokenrail.twine.component;

import com.thebrokenrail.twine.Twine;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.PersistentState;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class StageDataComponent extends PersistentState {
    private static final int CHUNK_RADIUS = 16 * 16;
    private static final String STAGE_DATA_ID = "twine_stage_data";

    public StageDataComponent(String key) {
        super(key);
    }

    private final Map<UUID, StageData[]> data = new HashMap<>();

    public StageData[] getData(UUID uuid) {
        return data.computeIfAbsent(uuid, k -> new StageData[Twine.STAGE_COUNT]);
    }

    public static int findPersonalStageOfChunk(BlockPos pos, StageDataComponent.StageData[] data) {
        for (int i = Twine.STAGE_COUNT - 1; i >= 0; i--) {
            StageDataComponent.StageData stage = data[i];
            if (stage != null && Math.abs(stage.x - pos.getX()) <= CHUNK_RADIUS && Math.abs(stage.z - pos.getZ()) <= CHUNK_RADIUS) {
                return i;
            }
        }
        data[0] = new StageDataComponent.StageData();
        data[0].x = pos.getX();
        data[0].z = pos.getZ();
        return 0;
    }

    public int findEffectiveStageOfChunk(ServerWorld world, BlockPos pos) {
        int stage = 0;
        for (Map.Entry<UUID, StageData[]> entry : data.entrySet()) {
            if (world.getPlayerByUuid(entry.getKey()) != null) {
                stage = Math.max(stage, findPersonalStageOfChunk(pos, entry.getValue()));
            }
        }
        return stage;
    }

    public long findEffectiveTimeOfChunk(ServerWorld world, BlockPos pos) {
        long time = 0;
        int stage = findEffectiveStageOfChunk(world, pos);
        for (Map.Entry<UUID, StageData[]> entry : data.entrySet()) {
            if (world.getPlayerByUuid(entry.getKey()) != null) {
                int personalStage = findPersonalStageOfChunk(pos, entry.getValue());
                if (personalStage == stage) {
                    time = Math.max(time, entry.getValue()[stage].time);
                }
            }
        }
        return time;
    }

    public Text findEffectiveCauseOfChunk(ServerWorld world, BlockPos pos) {
        int stage = findEffectiveStageOfChunk(world, pos);
        for (Map.Entry<UUID, StageData[]> entry : data.entrySet()) {
            PlayerEntity player = world.getPlayerByUuid(entry.getKey());
            if (player != null) {
                int personalStage = findPersonalStageOfChunk(pos, entry.getValue());
                if (personalStage == stage) {
                    return player.getDisplayName();
                }
            }
        }
        return new LiteralText("");
    }

    public static StageDataComponent getFromWorld(ServerWorld world) {
        return world.getPersistentStateManager().getOrCreate(() -> new StageDataComponent(STAGE_DATA_ID), STAGE_DATA_ID);
    }

    @Override
    public void fromTag(CompoundTag compoundTag) {
        data.clear();
        Tag uuidTag = compoundTag.get("Data");
        if (uuidTag instanceof ListTag) {
            for (int i = 0; i < ((ListTag) uuidTag).size(); i++) {
                CompoundTag obj = ((ListTag) uuidTag).getCompound(i);
                UUID uuid = obj.getUuid("UUID");

                Tag tag = obj.get("Stages");
                StageData[] newData = new StageData[Twine.STAGE_COUNT];
                if (tag instanceof ListTag) {
                    for (int k = 0; k < ((ListTag) tag).size(); k++) {
                        Tag newTag = ((ListTag) tag).get(k);
                        if (newTag instanceof CompoundTag) {
                            StageData stage = new StageData();
                            stage.x = ((CompoundTag) newTag).getInt("X");
                            stage.z = ((CompoundTag) newTag).getInt("Z");
                            stage.time = ((CompoundTag) newTag).getLong("Time");
                            newData[((CompoundTag) newTag).getInt("Index")] = stage;
                        }
                    }
                    data.put(uuid, newData);
                }
            }
        }
    }

    @Override
    public CompoundTag toTag(CompoundTag compoundTag) {
        ListTag list = new ListTag();
        for (Map.Entry<UUID, StageData[]> entry : data.entrySet()) {
            CompoundTag obj = new CompoundTag();
            obj.putUuid("UUID", entry.getKey());
            ListTag newList = new ListTag();
            for (int i = 0; i < Twine.STAGE_COUNT; i++) {
                if (entry.getValue()[i] != null) {
                    CompoundTag tag = new CompoundTag();
                    tag.putInt("X", entry.getValue()[i].x);
                    tag.putInt("Z", entry.getValue()[i].z);
                    tag.putLong("Time", entry.getValue()[i].time);
                    tag.putInt("Index", i);
                    newList.add(tag);
                }
            }
            obj.put("Stages", newList);
            list.add(obj);
        }
        compoundTag.put("Data", list);
        return compoundTag;
    }

    public static class StageData {
        public int x;
        public int z;
        public long time = 0;
    }
}
