package tc.oc.pgm.itemmeta;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.annotation.Nullable;

import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.jdom2.Document;
import org.jdom2.Element;
import tc.oc.commons.bukkit.item.BooleanItemTag;
import tc.oc.pgm.kits.ItemParser;
import tc.oc.pgm.map.MapModule;
import tc.oc.pgm.map.MapModuleContext;
import tc.oc.pgm.map.MapModuleFactory;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchModuleFactory;
import tc.oc.pgm.module.ModuleDescription;
import tc.oc.pgm.utils.MaterialMatcher;
import tc.oc.pgm.utils.XMLUtils;
import tc.oc.pgm.xml.InvalidXMLException;

@ModuleDescription(name = "Item Modify")
public class ItemModifyModule implements MapModule, MatchModuleFactory<ItemModifyMatchModule> {
    private static final BooleanItemTag APPLIED = new BooleanItemTag("custom-meta-applied", false);

    private final List<ItemRule> rules;

    public ItemModifyModule(List<ItemRule> rules) {
        this.rules = rules;
    }

    public boolean shouldApply(ItemStack stack) {
        return stack != null &&
               stack.getType() != Material.AIR &&
               !APPLIED.get(stack);
    }

    public ItemStack applyToCopy(ItemStack stack) {
        if(shouldApply(stack)) {
            final boolean immutable = stack.isImmutable();
            stack = stack.clone();
            applyRules(stack);
            if(immutable) {
                stack = stack.immutableCopy();
            }
        }
        return stack;
    }

    public boolean applyRules(ItemStack stack) {
        if(!shouldApply(stack)) {
            return false;
        } else {
            boolean defaultAttributes = false;
            boolean attributesModified = false;

            for(ItemRule rule : rules) {
                if(rule.matches(stack)) {
                    rule.apply(stack);
                    APPLIED.set(stack, true);
                    attributesModified |= rule.meta.hasAttributeModifiers();
                    defaultAttributes |= rule.defaultAttributes;
                }
            }

            // If any rule had the defaultAttributes flag, and any custom attributes were added,
            // add the default attributes now. We do this here so it only happens once.
            if(defaultAttributes && attributesModified) {
                final ItemMeta meta = stack.getItemMeta();
                for(Map.Entry<String, List<ItemAttributeModifier>> entry : Bukkit.getItemFactory().getAttributeModifiers(stack.getData()).entrySet()) {
                    for(ItemAttributeModifier mod : entry.getValue()) {
                        meta.addAttributeModifier(entry.getKey(), mod);
                    }
                }
                stack.setItemMeta(meta);
            }

            return true;
        }
    }

    @Override
    public ItemModifyMatchModule createMatchModule(Match match) {
        return new ItemModifyMatchModule(match);
    }

    public static class Factory extends MapModuleFactory<ItemModifyModule> {
        @Override
        public @Nullable ItemModifyModule parse(MapModuleContext context, Logger logger, Document doc) throws InvalidXMLException {
            List<ItemRule> rules = new ArrayList<>();
            for(Element elRule : XMLUtils.flattenElements(doc.getRootElement(), "item-mods", "rule")) {
                MaterialMatcher items = XMLUtils.parseMaterialMatcher(XMLUtils.getRequiredUniqueChild(elRule, "match"));

                // Always use a PotionMeta so the rule can have potion effects, though it will only apply those to potion items
                final Element elModify = XMLUtils.getRequiredUniqueChild(elRule, "modify");
                final PotionMeta meta = (PotionMeta) Bukkit.getItemFactory().getItemMeta(Material.POTION);
                context.needModule(ItemParser.class).parseItemMeta(elModify, meta);
                final boolean defaultAttributes = XMLUtils.parseBoolean(elModify.getAttribute("default-attributes"), false);

                ItemRule rule = new ItemRule(items, meta, defaultAttributes);
                rules.add(rule);
            }

            return rules.isEmpty() ? null : new ItemModifyModule(rules);
        }
    }
}
