package tc.oc.commons.bukkit.item;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;
import javax.annotation.Nullable;

import org.bukkit.Material;
import org.bukkit.SkullType;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import tc.oc.commons.bukkit.configuration.ConfigUtils;
import tc.oc.commons.bukkit.inventory.Slot;
import tc.oc.commons.bukkit.util.NMSHacks;
import tc.oc.commons.core.util.MapUtils;
import tc.oc.minecraft.api.configuration.InvalidConfigurationException;

import static tc.oc.commons.core.exception.LambdaExceptionUtils.rethrowSupplier;

/**
 * Parses {@link ItemStack}s and {@link Skin}s from {@link ConfigurationSection}s
 */
public class ItemConfigurationParser {

    private final ConfigurationSection root;
    private final Map<String, Skin> skins = new HashMap<>();

    public ItemConfigurationParser(ConfigurationSection root) {
        this.root = root;
    }

    public Skin needSkin(String text) {
        return MapUtils.computeIfAbsent(skins, text, t -> {
            final ConfigurationSection skinSection = root.getConfigurationSection("skins");
            if(skinSection != null) {
                final String referenced = skinSection.getString(text);
                if(referenced != null && !referenced.equals(text)) {
                    return needSkin(referenced);
                }
            }
            return new Skin(text, null);
        });
    }

    public <T extends Slot> T needSlotByPosition(ConfigurationSection section, @Nullable String key, @Nullable String value, Class<T> type) throws InvalidConfigurationException {
        final String[] coords = ConfigUtils.needStringOrSectionName(section, key, value).split("\\s*,\\s*");
        if(coords.length != 2) {
            throw new InvalidConfigurationException(section, key, "Expected slot in \"column, row\" format");
        }

        final int column, row;
        final T slot;
        try {
            slot = Slot.atPosition(type, column = Integer.parseInt(coords[0]), row = Integer.parseInt(coords[1]));
        } catch(NumberFormatException e) {
            throw new InvalidConfigurationException(section, key, e.getMessage());
        }
        if(slot == null) {
            throw new InvalidConfigurationException(section, key, "No slot at column " + column + ", row " + row);
        }

        return slot;
    }

    public Material needItemType(ConfigurationSection section, String key) throws InvalidConfigurationException {
        final Material item = NMSHacks.materialByKey(section.needString(key));
        if(item == null) {
            throw new InvalidConfigurationException(section, key, "Unknown item type '" + key + "'");
        }
        return item;
    }

    public void needSkull(ItemStack stack, ConfigurationSection section, String key) throws InvalidConfigurationException {
        final ItemMeta meta = stack.getItemMeta();
        if(!(meta instanceof SkullMeta)) {
            throw new InvalidConfigurationException(section, key, "Item type " + NMSHacks.getKey(stack.getType()) + " cannot be skinned");
        }
        ((SkullMeta) meta).setOwner("SkullOwner", UUID.randomUUID(), needSkin(section.needString(key)));
        stack.setItemMeta(meta);
    }

    public ItemStack needSkull(ConfigurationSection section, String key) throws InvalidConfigurationException {
        final ItemStack stack = new ItemStack(Material.SKULL_ITEM);
        stack.setDurability((short) SkullType.PLAYER.ordinal());
        needSkull(stack, section, key);
        return stack;
    }

    public ItemStack getItem(ConfigurationSection section, String key, Supplier<ItemStack> def) throws InvalidConfigurationException {
        if(section.isString(key)) {
            return new ItemStack(needItemType(section, key));
        }

        if(!section.isConfigurationSection(key)) {
            return def.get();
        }

        final ConfigurationSection itemSection = section.needSection(key);

        if(itemSection.isString("skull")) {
            return needSkull(itemSection, "skull");
        }

        final Material material = needItemType(itemSection, "id");

        final int damage = itemSection.getInt("damage", 0);
        if(damage < Short.MIN_VALUE || damage > Short.MAX_VALUE) {
            throw new InvalidConfigurationException(itemSection, "damage", "Item damage out of range");
        }

        final ItemStack stack = new ItemStack(material, 1, (short) damage);

        if(itemSection.isString("skin")) {
            needSkull(stack, itemSection, "skin");
        }

        return stack;
    }

    public ItemStack needItem(ConfigurationSection section, String key) throws InvalidConfigurationException {
        return getItem(section, key, rethrowSupplier(() -> {
            throw new InvalidConfigurationException("Missing required item " + key);
        }));
    }
}
