package tc.oc.pgm.kits;

import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.inject.Inject;

import com.google.common.collect.Range;
import org.bukkit.inventory.ItemStack;
import org.jdom2.Element;
import tc.oc.commons.bukkit.inventory.ArmorType;
import tc.oc.commons.bukkit.inventory.Slot;
import tc.oc.pgm.blitz.LivesKit;
import tc.oc.pgm.compose.CompositionParser;
import tc.oc.pgm.doublejump.DoubleJumpKit;
import tc.oc.pgm.features.FeatureDefinitionContext;
import tc.oc.pgm.features.FeatureDefinitionParser;
import tc.oc.pgm.features.FeatureParser;
import tc.oc.pgm.features.MagicMethodFeatureParser;
import tc.oc.pgm.filters.matcher.StaticFilter;
import tc.oc.pgm.filters.parser.FilterParser;
import tc.oc.pgm.itemmeta.ItemModifier;
import tc.oc.pgm.map.MapLogger;
import tc.oc.pgm.physics.RelativeFlags;
import tc.oc.pgm.shield.ShieldKit;
import tc.oc.pgm.shield.ShieldParameters;
import tc.oc.pgm.teams.TeamFactory;
import tc.oc.pgm.utils.AllMaterialMatcher;
import tc.oc.pgm.utils.MethodParser;
import tc.oc.pgm.utils.XMLUtils;
import tc.oc.pgm.xml.InvalidXMLException;
import tc.oc.pgm.xml.Node;
import tc.oc.pgm.xml.NodeSplitter;
import tc.oc.pgm.xml.property.PropertyBuilderFactory;

import static tc.oc.commons.core.exception.LambdaExceptionUtils.rethrowFunction;
import static tc.oc.commons.core.stream.Collectors.toImmutableSet;

public class KitDefinitionParser extends MagicMethodFeatureParser<Kit> implements FeatureDefinitionParser<Kit> {

    @Inject protected FeatureDefinitionContext features;
    @Inject protected ItemParser itemParser;
    @Inject protected FilterParser filterParser;
    @Inject protected FeatureParser<TeamFactory> teamParser;
    @Inject protected CompositionParser<Kit> composer;
    @Inject protected KitParser kitParser;
    @Inject protected PropertyBuilderFactory<Boolean, ?> booleanParser;
    @Inject protected MapLogger mapLogger;
    @Inject protected ItemModifier itemModifier;

    private Stream<Kit> parseParentKits(Element el) throws InvalidXMLException {
        return Node.attributes(el, "parent", "parents")
                   .flatMap(attr -> NodeSplitter.LIST.split(attr)
                                                     .map(rethrowFunction(id -> kitParser.parseReference(attr, id.trim()))));
    }

    // TODO: this check can be removed after a little while
    private static final Set<String> LEGACY_ACTION_ATTRS = Stream.of(KitRule.Action.values())
                                                                 .map(action -> action.name().toLowerCase())
                                                                 .collect(toImmutableSet());
    @MethodParser
    Kit kit(Element el) throws InvalidXMLException {
        Node.nodes(el, LEGACY_ACTION_ATTRS).forEach(attr ->
            mapLogger.warning(attr, "Kit give/take/lend attributes are no longer supported")
        );

        return new KitNodeImpl(
            parseParentKits(el),
            composer.parseElement(el),
            filterParser.property(el, "filter").optional(StaticFilter.ALLOW),
            booleanParser.property(el, "force").elements(false).optional(), // attrs only, since <force> is a type of kit
            booleanParser.property(el, "potion-particles").optional()
        );
    }

    protected Kit makeItemKit(Optional<Slot.Player> slot, ItemStack item) throws InvalidXMLException {
        final ItemStack modded = itemModifier.modify(item).immutableCopy();
        return slot.map(s -> (Kit) new SlotItemKit(modded, s))
                   .orElseGet(() -> new FreeItemKit(modded));
    }

    public Kit parseArmorKit(Element el, ArmorType type) throws InvalidXMLException {
        return makeItemKit(Optional.of(Slot.Armor.forType(type)), itemParser.parseItem(el, true));
    }

    public Optional<Slot.Player> parseSlot(Element el) throws InvalidXMLException {
        return Optional.ofNullable(Node.fromAttr(el, "slot"))
                       .map(rethrowFunction(XMLUtils::parsePlayerSlot));
    }

    @MethodParser
    public Kit item(Element el) throws InvalidXMLException {
        return makeItemKit(parseSlot(el), itemParser.parseItem(el, true));
    }

    @MethodParser
    public Kit book(Element el) throws InvalidXMLException {
        return makeItemKit(parseSlot(el), itemParser.parseBook(el));
    }

    @MethodParser
    public Kit head(Element el) throws InvalidXMLException {
        return makeItemKit(parseSlot(el), itemParser.parseHead(el));
    }

    @MethodParser public Kit boots(Element el) throws InvalidXMLException { return parseArmorKit(el, ArmorType.BOOTS); }
    @MethodParser public Kit leggings(Element el) throws InvalidXMLException { return parseArmorKit(el, ArmorType.LEGGINGS); }
    @MethodParser public Kit chestplate(Element el) throws InvalidXMLException { return parseArmorKit(el, ArmorType.CHESTPLATE); }
    @MethodParser public Kit helmet(Element el) throws InvalidXMLException { return parseArmorKit(el, ArmorType.HELMET); }

    @MethodParser
    public Kit clear(Element el) throws InvalidXMLException {
        return new ClearKit(
            parseSlot(el),
            XMLUtils.parseMaterialMatcher(el, AllMaterialMatcher.INSTANCE)
        );
    }

    @MethodParser
    public Kit clear_items(Element el) throws InvalidXMLException {
        return new ClearItemsKit();
    }

    @MethodParser
    public Kit knockback_reduction(Element el) throws InvalidXMLException {
        return new KnockbackReductionKit(XMLUtils.parseNumber(el, Float.class));
    }

    @MethodParser
    public Kit walk_speed(Element el) throws InvalidXMLException {
        return new WalkSpeedKit(XMLUtils.parseNumber(el, Float.class, Range.closed(WalkSpeedKit.MIN, WalkSpeedKit.MAX)));
    }

    /*
      ~ <fly/>                      {FlyKit: allowFlight = true,  flying = null  }
      ~ <fly flying="false"/>       {FlyKit: allowFlight = true,  flying = false }
      ~ <fly allowFlight="false"/>  {FlyKit: allowFlight = false, flying = null  }
      ~ <fly flying="true"/>        {FlyKit: allowFlight = true,  flying = true  }
     */
    @MethodParser
    public Kit fly(Element el) throws InvalidXMLException {
        final boolean canFly = XMLUtils.parseBoolean(el.getAttribute("can-fly"), true);
        final Boolean flying = XMLUtils.parseBoolean(el.getAttribute("flying"), null);
        org.jdom2.Attribute flySpeedAtt = el.getAttribute("fly-speed");
        float flySpeedMultiplier = 1;
        if(flySpeedAtt != null) {
            flySpeedMultiplier = XMLUtils.parseNumber(el.getAttribute("fly-speed"), Float.class, Range.closed(FlyKit.MIN, FlyKit.MAX));
        }

        return new FlyKit(canFly, flying, flySpeedMultiplier);
    }

    @MethodParser
    public Kit gravity(Element el) throws InvalidXMLException {
        return new GravityKit(XMLUtils.parseBoolean(el, true));
    }

    @MethodParser({"effect", "potion"})
    public Kit effect(Element el) throws InvalidXMLException {
        return new PotionKit(itemParser.parsePotionEffect(el));
    }


    @MethodParser
    public Kit attribute(Element el) throws InvalidXMLException {
        return new AttributeKit(XMLUtils.parseAttributeModifier(el));
    }

    @MethodParser
    public Kit health(Element el) throws InvalidXMLException {
        int health = XMLUtils.parseNumber(el, Integer.class);
        if(health < 1 || health > 20) {
            throw new InvalidXMLException(health + " is not a valid health value, must be between 1 and 20", el);
        }
        return new HealthKit(health);
    }

    @MethodParser
    public Kit max_health(Element el) throws InvalidXMLException {
        return new MaxHealthKit(XMLUtils.parseNumber(el, Double.class, Range.atLeast(1d)));
    }

    @MethodParser
    public Kit saturation(Element el) throws InvalidXMLException {
        return new HungerKit(XMLUtils.parseNumber(el, Float.class, Range.atLeast(0f)), null);
    }

    @MethodParser
    public Kit foodlevel(Element el) throws InvalidXMLException {
        return new HungerKit(null, XMLUtils.parseNumber(el, Integer.class, Range.atLeast(0)));
    }

    @MethodParser
    public Kit double_jump(Element el) throws InvalidXMLException {
        return new DoubleJumpKit(XMLUtils.parseBoolean(el.getAttribute("enabled"), true),
                                 XMLUtils.parseNumber(el.getAttribute("power"), Float.class, DoubleJumpKit.DEFAULT_POWER),
                                 XMLUtils.parseDuration(el.getAttribute("recharge-time"), DoubleJumpKit.DEFAULT_RECHARGE),
                                 XMLUtils.parseBoolean(el.getAttribute("recharge-before-landing"), false));
    }

    @MethodParser
    public Kit reser_ender_pearls(Element el) throws InvalidXMLException {
        return new ResetEnderPearlsKit();
    }

    @MethodParser
    public Kit game_mode(Element el) throws InvalidXMLException {
        return new GameModeKit(XMLUtils.parseGameMode(new Node(el)));
    }

    @MethodParser
    public Kit shield(Element el) throws InvalidXMLException {
        return new ShieldKit(new ShieldParameters(XMLUtils.parseNumber(el.getAttribute("health"), Double.class, ShieldParameters.DEFAULT_HEALTH),
                                                  XMLUtils.parseDuration(el.getAttribute("delay"), ShieldParameters.DEFAULT_DELAY)));
    }

    @MethodParser
    public Kit fast_regeneration(Element el) throws InvalidXMLException {
        return new NaturalRegenerationKit(true, XMLUtils.parseBoolean(new Node(el)));
    }

    @MethodParser
    public Kit slow_regeneration(Element el) throws InvalidXMLException {
        return new NaturalRegenerationKit(false, XMLUtils.parseBoolean(new Node(el)));
    }

    @MethodParser
    public Kit hitbox(Element el) throws InvalidXMLException {
        return new HitboxKit(XMLUtils.parseNumber(Node.fromRequiredAttr(el, "width"), Double.class));
    }

    @MethodParser
    private Kit remove(Element el) throws InvalidXMLException {
        return features.validate(new RemoveKit(kitParser.parseReferenceElement(el)), new Node(el), RemovableValidation.get());
    }

    @MethodParser
    private Kit team_switch(Element el) throws InvalidXMLException {
        return new TeamSwitchKit(teamParser.property(el, "team").required());
    }

    @MethodParser
    private Kit eliminate(Element el) throws InvalidXMLException {
        return new EliminateKit();
    }

    private RelativeFlags parseRelativeFlags(Element el) throws InvalidXMLException {
        return RelativeFlags.of(XMLUtils.parseBoolean(el.getAttribute("yaw"), false),
                                XMLUtils.parseBoolean(el.getAttribute("pitch"), false));
    }

    @MethodParser
    private Kit impulse(Element el) throws InvalidXMLException {
        return new ImpulseKit(XMLUtils.parseVector(new Node(el)),
                              parseRelativeFlags(el));
    }

    @MethodParser
    private Kit force(Element el) throws InvalidXMLException {
        return new ForceKit(XMLUtils.parseVector(new Node(el)),
                            parseRelativeFlags(el));
    }

    @MethodParser
    private Kit lives(Element el) throws InvalidXMLException {
        return new LivesKit(XMLUtils.parseNumber(new Node(el), Integer.class, false, +1));
    }
}
