package me.loganb1max.minionsplus.model;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import me.loganb1max.minionsplus.MinionsPlus;
import me.loganb1max.minionsplus.model.minions.*;
import me.loganb1max.minionsplus.util.*;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.text.DecimalFormat;
import java.util.*;

@Getter
public abstract class Minion {

	private final ConfigurationSection section;
	private final UUID owner;
	private final String ownerName;
	@Setter(AccessLevel.PROTECTED) private Location location;
	@Setter(AccessLevel.PROTECTED) private double energy = 0.0;
	@Setter(AccessLevel.PROTECTED) private int level = 1;
	@Setter(AccessLevel.PROTECTED)private Set<Block> linkedBlocks = new HashSet<>();
	private ArmorStand stand;

	@Getter(AccessLevel.NONE)
	private double offset = 0.1;

	public Minion(final ConfigurationSection section, final UUID owner, final String ownerName) {
		this.section = section;
		this.owner = owner;
		this.ownerName = ownerName;
	}

	protected void findStand() {
		if (this.stand != null) return;
		this.location.getChunk().load();
		this.location.getWorld().getNearbyEntities(this.location, 0.5, 0.5, 0.5).stream().filter(entity -> entity instanceof ArmorStand && !entity.getCustomName().isEmpty()).findFirst().ifPresent(entity -> {
			this.stand = (ArmorStand) entity;
		});
	}

	public void spawn(final Location location) {
		this.location = location;
		if (this.stand == null) {
			this.stand = (ArmorStand) location.getWorld().spawnEntity(location, EntityType.ARMOR_STAND);
			this.stand.setSmall(true);
			this.stand.setBasePlate(false);
			this.stand.setGravity(false);
			this.stand.setCanPickupItems(false);
			this.stand.setRemoveWhenFarAway(false);
			this.stand.setMarker(false);
			this.stand.setCustomName(Text.color(getName()));
			this.stand.setCustomNameVisible(true);
			this.stand.setRightArmPose(this.stand.getRightLegPose().setX(6));
			this.equipEquipment(this.stand);
			MinionsPlus.getInstance().getMinionManager().getMinions().add(this);
		} else {
			this.stand.teleport(location);
		}
	}

	public void despawn(final Player player) {
		if (this.stand == null) return;
		this.location = null;
		this.stand.remove();
		this.stand = null;
		this.linkedBlocks.clear();
		player.getInventory().addItem(this.toItemStack()).forEach((integer, itemStack) -> player.getWorld().dropItemNaturally(player.getLocation(), itemStack));
		MinionsPlus.getInstance().getMinionManager().getMinions().remove(this);
	}

	public void animationTick() {
		if (this.stand == null) return;
		if (this.stand.getRightArmPose().getX() < 5) offset = 0.1;
		if (this.stand.getRightArmPose().getX() > 6) offset = -0.1;
		if (this.energy > 0) this.stand.setRightArmPose(this.stand.getRightArmPose().add(offset, 0, 0));
		final DecimalFormat format = new DecimalFormat("#.#");
		this.stand.setCustomName(Text.color(getName() + " &8[&f" + format.format(getEnergy()) + "&8]"));
		this.stand.setFireTicks(0);
	}

	public double gainEnergy(final double amount) {
		return this.energy += amount;
	}

	public double consumeEnergy(final double amount) {
		return this.energy -= amount;
	}

	public void equipEquipment(final ArmorStand stand) {
		final String skull = this.section.getString("Equipment.Skull", "");
		ItemStack skullItem = SkullCreator.itemFromBase64(skull);
		stand.setHelmet(skullItem);
		final ItemStack chestplate = ItemUtil.fromConfigSection(this.section.getConfigurationSection("Equipment.Chestplate"));
		stand.setChestplate(chestplate);
		final ItemStack leggings = ItemUtil.fromConfigSection(this.section.getConfigurationSection("Equipment.Leggings"));
		stand.setLeggings(leggings);
		final ItemStack boots = ItemUtil.fromConfigSection(this.section.getConfigurationSection("Equipment.Boots"));
		stand.setBoots(boots);
		final ItemStack rightHand = ItemUtil.fromConfigSection(this.section.getConfigurationSection("Equipment.RightHand"));
		stand.setItemInHand(rightHand);
	}

	public ItemStack toItemStack() {
		ItemStack item = getIcon();
		item = ItemUtil.setKeyString(item, "minion-type", getType());
		item = ItemUtil.setKeyString(item, "minion-owner", this.owner.toString());
		item = ItemUtil.setKeyString(item, "minion-owner-name", this.ownerName);
		item = ItemUtil.setKeyInt(item, "minion-level", this.level);
		item = ItemUtil.setKeyDouble(item, "minion-energy", this.energy);
		return item;
	}

	public ItemStack getIcon() {
		final Material material = Material.getMaterial(this.section.getString("Item.Material"));
		final int durability = this.section.getInt("Item.Durability");
		final String skull = this.section.getString("Item.Skull", null);
		final String displayName = this.section.getString("Item.DisplayName");
		final List<String> lore = this.section.getStringList("Item.Lore");

		ItemStack item = null;

		if (material == Material.SKULL_ITEM) {
			item = SkullCreator.itemFromBase64(skull);
		} else {
			item = new ItemStack(material);
		}

		item = ItemBuilder.of(item)
				.durability((short) durability)
				.name(displayName)
				.lore(getReplacer().replace(lore))
				.build();

		return item;
	}

	public UUID getId() {
		if (this.stand == null) return null;
		return this.stand.getUniqueId();
	}

	public String getType() {
		return this.section.getName();
	}

	public String getName() {
		return this.section.getString("Name");
	}

	public double getEnergyPerUse(final int level) {
		return this.section.getDouble("Levels." + level + ".EnergyPerAction", 0.0);
	}

	public double getPrice(final int level) {
		return this.section.getDouble("Levels." + level + ".Price", 0.0);
	}

	public int getMaxLevel() {
		return this.section.getConfigurationSection("Levels").getKeys(false).size();
	}

	public boolean isMaxLevel() {
		return getLevel() == getMaxLevel();
	}

	public void levelUp() {
		this.level++;
	}

	public JsonElement serialize() {
		final Gson gson = new Gson();
		final JsonObject object = new JsonObject();
		object.addProperty("type", getType());
		object.addProperty("owner", this.owner.toString());
		object.addProperty("ownerName", this.ownerName);
		final JsonObject loc = new JsonObject();
		loc.addProperty("world", this.location.getWorld().getName());
		loc.addProperty("x", this.location.getX());
		loc.addProperty("y", this.location.getY());
		loc.addProperty("z", this.location.getZ());
		object.add("location", loc);
		object.addProperty("energy", this.energy);
		object.addProperty("level", this.level);
		final List<JsonElement> linked = new ArrayList<>();
		this.linkedBlocks.forEach(block -> {
			JsonObject b = new JsonObject();
			b.addProperty("world", block.getWorld().getName());
			b.addProperty("x", block.getX());
			b.addProperty("y", block.getY());
			b.addProperty("z", block.getZ());
			linked.add(b);
		});
		object.add("linkedBlocks", gson.toJsonTree(linked));
		return object;
	}

	public static Minion deserialize(final MinionsPlus plugin, final JsonElement element) {
		final JsonObject data = element.getAsJsonObject();
		final String type = data.get("type").getAsString();
		final UUID owner = UUID.fromString(data.get("owner").getAsString());
		final String ownerName = data.get("ownerName").getAsString();
		final JsonObject locData = data.get("location").getAsJsonObject();
		final Location location = new Location(Bukkit.getWorld(locData.get("world").getAsString()), locData.get("x").getAsDouble(), locData.get("y").getAsDouble(), locData.get("z").getAsDouble());
		final double energy = data.get("energy").getAsDouble();
		final int level = data.get("level").getAsInt();
		final Set<Block> linkedBlocks = new HashSet<>();
		for (JsonElement el : data.get("linkedBlocks").getAsJsonArray()) {
			JsonObject o = el.getAsJsonObject();
			final Block b = Bukkit.getWorld(o.get("world").getAsString()).getBlockAt(
					o.get("x").getAsInt(),
					o.get("y").getAsInt(),
					o.get("z").getAsInt()
			);
			linkedBlocks.add(b);
		}
		Minion toReturn = null;
		switch (type) {
			case "Miner":
				toReturn = new MinerMinion(owner, ownerName, location, energy, level, linkedBlocks);
				break;
			case "Farmer":
				toReturn = new FarmerMinion(owner, ownerName, location, energy, level, linkedBlocks);
				break;
			case "Fisher":
				toReturn = new FisherMinion(owner, ownerName, location, energy, level, linkedBlocks);
				break;
			case "Seller":
				toReturn = new SellerMinion(owner, ownerName, location, energy, level, linkedBlocks);
				break;
			case "Butcher":
				toReturn = new ButcherMinion(owner, ownerName, location, energy, level, linkedBlocks);
				break;
			case "Collector":
				toReturn = new CollectorMinion(owner, ownerName, location, energy, level, linkedBlocks);
				break;
			case "Breeder":
				toReturn = new BreederMinion(owner, ownerName, location, energy, level, linkedBlocks);
				break;
		}
		return toReturn;
	}

	public static boolean isMinion(final ItemStack itemStack) {
		if (itemStack == null || itemStack.getType() == Material.AIR) return false;
		return ItemUtil.hasKey(itemStack, "minion-type");
	}

	public static Minion of(final ItemStack itemStack, final Location location) {
		if (!isMinion(itemStack)) return null;
		final String type = ItemUtil.getKeyString(itemStack, "minion-type");
		final UUID owner = UUID.fromString(ItemUtil.getKeyString(itemStack, "minion-owner"));
		final String ownerName = ItemUtil.getKeyString(itemStack, "minion-owner-name");
		final int level = ItemUtil.getKeyInt(itemStack, "minion-level");
		final double energy = ItemUtil.getKeyDouble(itemStack, "minion-energy");
		Minion toReturn = null;
		switch (type) {
			case "Miner":
				toReturn = new MinerMinion(owner, ownerName, location, energy, level, new HashSet<>());
				break;
			case "Farmer":
				toReturn = new FarmerMinion(owner, ownerName, location, energy, level, new HashSet<>());
				break;
			case "Fisher":
				toReturn = new FisherMinion(owner, ownerName, location, energy, level, new HashSet<>());
				break;
			case "Seller":
				toReturn = new SellerMinion(owner, ownerName, location, energy, level, new HashSet<>());
				break;
			case "Butcher":
				toReturn = new ButcherMinion(owner, ownerName, location, energy, level, new HashSet<>());
				break;
			case "Collector":
				toReturn = new CollectorMinion(owner, ownerName, location, energy, level, new HashSet<>());
				break;
			case "Breeder":
				toReturn = new BreederMinion(owner, ownerName, location, energy, level, new HashSet<>());
				break;
		}
		return toReturn;
	}

	public abstract void tick();

	public abstract Replacer getReplacer();

}
