package wayoftime.bloodmagic.anointment;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;

import net.minecraft.client.gui.screen.Screen;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.EquipmentSlotType;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.util.Hand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import wayoftime.bloodmagic.anointment.Anointment.IDamageProvider;
import wayoftime.bloodmagic.core.AnointmentRegistrar;
import wayoftime.bloodmagic.util.Constants;

public class AnointmentHolder
{
	private final Map<Anointment, AnointmentData> anointments;

	public AnointmentHolder(Map<Anointment, AnointmentData> anointments)
	{
		this.anointments = anointments;
	}

	public AnointmentHolder()
	{
		this(Maps.newHashMap());
	}

	// Returns true if the anointment is applied successfully.
	public boolean applyAnointment(ItemStack stack, Anointment anointment, AnointmentData data)
	{
		if (canApplyAnointment(stack, anointment, data))
		{
			anointments.put(anointment, data);
			anointment.applyAnointment(this, stack, data.getLevel());
			return true;
		}

		return false;
	}

	public boolean canApplyAnointment(ItemStack stack, Anointment anointment, AnointmentData data)
	{
		ResourceLocation key = anointment.getKey();
		for (Anointment containedAnoint : anointments.keySet())
		{
			ResourceLocation containedKey = containedAnoint.getKey();
			if (!anointment.isCompatible(containedKey) || !containedAnoint.isCompatible(key))
			{
				return false;
			}
		}

		if (anointments.containsKey(anointment))
		{
			AnointmentData prevData = anointments.get(anointment);
			int level = prevData.getLevel();
			int remainingDur = prevData.getMaxDamage() - prevData.getDamage();
			if (level < data.getLevel() || (level == data.getLevel() && remainingDur < (data.getMaxDamage() - data.getDamage())))
			{
				return true;
			} else
			{
				return false;
			}
		}

		return true;
	}

	public int getAnointmentLevel(Anointment anointment)
	{
		if (anointments.containsKey(anointment))
		{
			return anointments.get(anointment).getLevel();
		}

		return 0;
	}

	public boolean consumeAnointmentDurabilityOnHit(ItemStack weaponStack, EquipmentSlotType type)
	{
//		System.out.println("Attempting consumption");
		boolean didConsume = false;
		List<Anointment> removedAnointments = new ArrayList<Anointment>();
		for (Entry<Anointment, AnointmentData> entry : anointments.entrySet())
		{
			Anointment annointment = entry.getKey();
			if (annointment.consumeOnAttack())
			{
				AnointmentData data = entry.getValue();
				data.damage(1);
				didConsume = true;
				if (data.isMaxDamage())
				{
					removedAnointments.add(annointment);
				}
			}
		}

		for (Anointment anointment : removedAnointments)
		{
			removeAnointment(weaponStack, type, anointment);
		}

		return didConsume;
	}

	public boolean consumeAnointmentDurabilityOnHarvest(ItemStack weaponStack, EquipmentSlotType type)
	{
		boolean didConsume = false;
		List<Anointment> removedAnointments = new ArrayList<Anointment>();
		for (Entry<Anointment, AnointmentData> entry : anointments.entrySet())
		{
			Anointment annointment = entry.getKey();
			if (annointment.consumeOnHarvest())
			{
				AnointmentData data = entry.getValue();
				data.damage(1);
				didConsume = true;
				if (data.isMaxDamage())
				{
					removedAnointments.add(annointment);
				}
			}
		}

		for (Anointment anointment : removedAnointments)
		{
			removeAnointment(weaponStack, type, anointment);
		}

		return didConsume;
	}

	// Called when the specified anointment is to be removed. Occurs if the
	// anointment runs out of uses or if removed via another source.
	public boolean removeAnointment(ItemStack weaponStack, EquipmentSlotType type, Anointment anointment)
	{
		anointments.remove(anointment);
		anointment.removeAnointment(this, weaponStack, type);
		return true;
	}

	public Map<Anointment, AnointmentData> getAnointments()
	{
		return ImmutableMap.copyOf(anointments);
	}

	public double getAdditionalDamage(PlayerEntity player, ItemStack weapon, double damage, LivingEntity attacked)
	{
		double additionalDamage = 0;
		for (Entry<Anointment, AnointmentData> entry : anointments.entrySet())
		{
			IDamageProvider prov = entry.getKey().getDamageProvider();
			if (prov != null)
			{
				additionalDamage += prov.getAdditionalDamage(player, weapon, damage, this, attacked, entry.getKey(), entry.getValue().getLevel());
			}
		}
		return additionalDamage;
	}

	public CompoundNBT serialize()
	{
		CompoundNBT compound = new CompoundNBT();
		ListNBT statList = new ListNBT();
		anointments.forEach((k, v) -> {
			CompoundNBT anoint = new CompoundNBT();
			anoint.putString("key", k.getKey().toString());
			anoint.putInt("level", v.getLevel());
			anoint.putInt("damage", v.getDamage());
			anoint.putInt("max_damage", v.getMaxDamage());
			statList.add(anoint);
		});
		compound.put("anointments", statList);
//
//		compound.putInt("maxPoints", maxPoints);

		return compound;
	}

	public void deserialize(CompoundNBT nbt)
	{
		ListNBT statList = nbt.getList("anointments", 10);
		statList.forEach(tag -> {
			if (!(tag instanceof CompoundNBT))
				return;
			Anointment anoint = AnointmentRegistrar.ANOINTMENT_MAP.getOrDefault(new ResourceLocation(((CompoundNBT) tag).getString("key")), Anointment.DUMMY);
//			LivingUpgrade upgrade = LivingArmorRegistrar.UPGRADE_MAP.getOrDefault(new ResourceLocation(((CompoundNBT) tag).getString("key")), LivingUpgrade.DUMMY);
			if (anoint == Anointment.DUMMY)
				return;
//			double experience = ((CompoundNBT) tag).getDouble("exp");
			AnointmentData data = new AnointmentData(((CompoundNBT) tag).getInt("level"), ((CompoundNBT) tag).getInt("damage"), ((CompoundNBT) tag).getInt("max_damage"));
			anointments.put(anoint, data);
		});
//
//		maxPoints = nbt.getInt("maxPoints");
	}

	public static AnointmentHolder fromNBT(CompoundNBT holderTag)
	{
		AnointmentHolder holder = new AnointmentHolder();
		holder.deserialize(holderTag);
		return holder;
	}

	public static AnointmentHolder fromItemStack(ItemStack stack)
	{
		CompoundNBT nbtTag = stack.getTag();
		if (nbtTag == null)
		{
			return null;
		}

		CompoundNBT holderTag = nbtTag.getCompound(Constants.NBT.ANOINTMENTS);
		if (holderTag != null)
		{
			return fromNBT(holderTag);
		}

		return null;
	}

	public void toItemStack(ItemStack stack)
	{
		CompoundNBT nbtTag = stack.getOrCreateTag();
		CompoundNBT childTag = this.serialize();

		nbtTag.put(Constants.NBT.ANOINTMENTS, childTag);
	}

	public static AnointmentHolder fromPlayer(PlayerEntity player, Hand hand)
	{
		return fromPlayer(player, hand, false);
	}

	public static AnointmentHolder fromPlayer(PlayerEntity player, Hand hand, boolean createNew)
	{
		ItemStack heldItem = player.getHeldItem(hand);

		AnointmentHolder holder = fromItemStack(heldItem);
		return holder == null && createNew ? new AnointmentHolder() : holder;
	}

	public static void toPlayer(PlayerEntity player, Hand hand, AnointmentHolder holder)
	{
		ItemStack heldItem = player.getHeldItem(hand);
		holder.toItemStack(heldItem);
	}

	public static void appendAnointmentTooltip(AnointmentHolder holder, List<ITextComponent> tooltip)
	{
		if (holder != null)
		{
//			System.out.println("Holder is not null. Size: " + holder.getAnointments().size());
//			if (trainable)
//				tooltip.add(new TranslationTextComponent("tooltip.bloodmagic.livingarmour.upgrade.points", stats.getUsedPoints(), stats.getMaxPoints()).mergeStyle(TextFormatting.GOLD));

			holder.getAnointments().forEach((k, v) -> {

//				if (k.getLevel(v.intValue()) <= 0)
//					return;
				boolean sneaking = Screen.hasShiftDown();
//				if (!InputUtil.isKeyPressed(MinecraftClient.getInstance().getWindow().getHandle(), 340) || k.getNextRequirement(v) == 0)
				if (!sneaking)
					tooltip.add(new TranslationTextComponent("%s %s", new TranslationTextComponent(k.getTranslationKey()), new TranslationTextComponent("enchantment.level." + v.getLevel())));
				else
					tooltip.add(new TranslationTextComponent("%s %s", new TranslationTextComponent(k.getTranslationKey()), (": (" + v.getDamageString() + ")")));
			});
		}
	}
}