package tc.oc.pgm.kits;

import com.google.common.base.Splitter;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Range;
import com.google.common.collect.SetMultimap;
import java.time.Duration;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.bukkit.Color;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.potion.PotionEffect;
import org.jdom2.Element;
import tc.oc.pgm.api.filter.Filter;
import tc.oc.pgm.api.map.factory.MapFactory;
import tc.oc.pgm.doublejump.DoubleJumpKit;
import tc.oc.pgm.filters.StaticFilter;
import tc.oc.pgm.kits.tag.Grenade;
import tc.oc.pgm.kits.tag.ItemTags;
import tc.oc.pgm.projectile.ProjectileDefinition;
import tc.oc.pgm.shield.ShieldKit;
import tc.oc.pgm.shield.ShieldParameters;
import tc.oc.pgm.util.bukkit.BukkitUtils;
import tc.oc.pgm.util.material.Materials;
import tc.oc.pgm.util.xml.InvalidXMLException;
import tc.oc.pgm.util.xml.Node;
import tc.oc.pgm.util.xml.XMLUtils;

public abstract class KitParser {
  protected final MapFactory factory;
  protected final Set<AttributeModifier> attributeModifiers = new HashSet<>();
  protected final Set<Kit> kits = new HashSet<>();

  public KitParser(MapFactory factory) {
    this.factory = factory;
  }

  /**
   * Return all {@link AttributeModifier}s used by parsed {@link AttributeKit}s. We need to keep
   * track of these so we can remove them from players.
   */
  public Set<AttributeModifier> getAttributeModifiers() {
    return attributeModifiers;
  }

  public Set<Kit> getKits() {
    return kits;
  }

  public abstract Kit parse(Element el) throws InvalidXMLException;

  public abstract Kit parseReference(Node node, String name) throws InvalidXMLException;

  protected boolean maybeReference(Element el) {
    return "kit".equals(el.getName())
        && el.getAttribute("parent") == null
        && el.getAttribute("parents") == null
        && el.getChildren().isEmpty();
  }

  public @Nullable Kit parseKitProperty(Element el, String name) throws InvalidXMLException {
    return parseKitProperty(el, name, null);
  }

  public Kit parseKitProperty(Element el, String name, @Nullable Kit def)
      throws InvalidXMLException {
    org.jdom2.Attribute attr = el.getAttribute(name);
    Element child = XMLUtils.getUniqueChild(el, name);
    if (attr != null) {
      if (child != null) {
        throw new InvalidXMLException("Kit reference conflicts with inline kit '" + name + "'", el);
      }
      return this.parseReference(new Node(attr), attr.getValue());
    } else if (child != null) {
      return this.parse(child);
    }
    return def;
  }

  protected KitDefinition parseDefinition(Element el) throws InvalidXMLException {
    List<Kit> kits = Lists.newArrayList();

    Node attrParents = Node.fromAttr(el, "parent", "parents");
    if (attrParents != null) {
      Iterable<String> parentNames = Splitter.on(',').split(attrParents.getValue());
      for (String parentName : parentNames) {
        kits.add(parseReference(attrParents, parentName.trim()));
      }
    }

    Boolean force = XMLUtils.parseBoolean(Node.fromAttr(el, "force"));
    Boolean potionParticles = XMLUtils.parseBoolean(Node.fromAttr(el, "potion-particles"));
    Filter filter = factory.getFilters().parseFilterProperty(el, "filter", StaticFilter.ALLOW);

    kits.add(this.parseClearItemsKit(el)); // must be added before anything else

    for (Element child : el.getChildren("kit")) {
      kits.add(this.parse(child));
    }

    kits.add(this.parseArmorKit(el));
    kits.add(this.parseItemKit(el));
    kits.add(this.parsePotionKit(el));
    kits.add(this.parseAttributeKit(el));
    kits.add(this.parseHealthKit(el));
    kits.add(this.parseHungerKit(el));
    kits.add(this.parseKnockbackReductionKit(el));
    kits.add(this.parseWalkSpeedKit(el));
    kits.add(this.parseDoubleJumpKit(el));
    kits.add(this.parseEnderPearlKit(el));
    kits.add(this.parseFlyKit(el));
    kits.add(this.parseGameModeKit(el));
    kits.add(this.parseShieldKit(el));
    kits.addAll(this.parseRemoveKits(el));

    kits.removeAll(Collections.singleton((Kit) null)); // Remove any nulls returned above
    this.kits.addAll(kits);

    return new KitNode(kits, filter, force, potionParticles);
  }

  public KnockbackReductionKit parseKnockbackReductionKit(Element el) throws InvalidXMLException {
    Element child = el.getChild("knockback-reduction");
    if (child == null) {
      return null;
    }
    return new KnockbackReductionKit(XMLUtils.parseNumber(child, Float.class));
  }

  public WalkSpeedKit parseWalkSpeedKit(Element el) throws InvalidXMLException {
    Element child = el.getChild("walk-speed");
    if (child == null) {
      return null;
    }
    return new WalkSpeedKit(
        XMLUtils.parseNumber(child, Float.class, Range.closed(WalkSpeedKit.MIN, WalkSpeedKit.MAX)));
  }

  public ClearItemsKit parseClearItemsKit(Element el) throws InvalidXMLException {
    if ("".equals(el.getChildText("clear"))) return new ClearItemsKit(true);
    if ("".equals(el.getChildText("clear-items"))) return new ClearItemsKit(false);
    return null;
  }

  /*
   ~ <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  }
  */
  public FlyKit parseFlyKit(Element el) throws InvalidXMLException {
    Element child = el.getChild("fly");
    if (child == null) {
      return null;
    }

    boolean canFly = XMLUtils.parseBoolean(el.getAttribute("can-fly"), true);
    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);
  }

  private ArmorKit.ArmorItem parseArmorItem(Element el) throws InvalidXMLException {
    if (el == null) {
      return null;
    }
    ItemStack stack = parseItem(el, true);
    boolean locked = XMLUtils.parseBoolean(el.getAttribute("locked"), false);
    return new ArmorKit.ArmorItem(stack, locked);
  }

  public ArmorKit parseArmorKit(Element el) throws InvalidXMLException {
    Map<ArmorType, ArmorKit.ArmorItem> armor = new HashMap<>();

    for (ArmorType armorType : ArmorType.values()) {
      ArmorKit.ArmorItem armorItem =
          this.parseArmorItem(el.getChild(armorType.name().toLowerCase()));
      if (armorItem != null) {
        armor.put(armorType, armorItem);
      }
    }

    if (!armor.isEmpty()) {
      return new ArmorKit(armor);
    } else {
      return null;
    }
  }

  public ItemKit parseItemKit(Element el) throws InvalidXMLException {
    Map<Slot, ItemStack> slotItems = Maps.newHashMap();
    List<ItemStack> freeItems = new ArrayList<>();

    for (Element itemEl : el.getChildren()) {
      ItemStack item = null;
      switch (itemEl.getName()) {
        case "item":
          item = parseItem(itemEl, true);
          break;

        case "book":
          item = parseBook(itemEl);
          break;

        case "head":
          item = parseHead(itemEl);
          break;
      }

      if (item != null) {
        Node nodeSlot = Node.fromAttr(itemEl, "slot");
        if (nodeSlot == null) {
          freeItems.add(item);
        } else {
          Slot slot = parseInventorySlot(nodeSlot);
          if (null != slotItems.put(slot, item)) {
            throw new InvalidXMLException("Kit already has an item in " + slot.getKey(), nodeSlot);
          }
        }
      }
    }

    return slotItems.isEmpty() ? null : new ItemKit(slotItems, freeItems);
  }

  public Slot parseInventorySlot(Node node) throws InvalidXMLException {
    String value = node.getValue();
    Slot slot;
    try {
      slot = Slot.Player.forIndex(Integer.parseInt(value));
      if (slot == null) {
        throw new InvalidXMLException(
            "Invalid inventory slot index (must be between 0 and 39)", node);
      }
    } catch (NumberFormatException e) {
      slot = Slot.forKey(value);
      if (slot == null) {
        throw new InvalidXMLException("Invalid inventory slot name", node);
      }
    }

    if (slot instanceof Slot.EnderChest) {
      throw new InvalidXMLException("Ender chest kits are not yet supported", node);
    }

    return slot;
  }

  public PotionKit parsePotionKit(Element el) throws InvalidXMLException {
    List<PotionEffect> potions = parsePotions(el);
    return potions.isEmpty() ? null : new PotionKit(ImmutableSet.copyOf(potions));
  }

  public List<PotionEffect> parsePotions(Element el) throws InvalidXMLException {
    List<PotionEffect> effects = new ArrayList<>();

    Node attr = Node.fromAttr(el, "potion", "potions", "effect", "effects");
    if (attr != null) {
      for (String piece : attr.getValue().split(";")) {
        effects.add(XMLUtils.parseCompactPotionEffect(attr, piece));
      }
    }

    for (Node elPotion : Node.fromChildren(el, "potion", "effect")) {
      effects.add(XMLUtils.parsePotionEffect(elPotion.getElement()));
    }

    return effects;
  }

  public AttributeKit parseAttributeKit(Element el) throws InvalidXMLException {
    SetMultimap<String, AttributeModifier> modifiers = parseAttributeModifiers(el);
    attributeModifiers.addAll(modifiers.values());
    return modifiers.isEmpty() ? null : new AttributeKit(modifiers);
  }

  public SetMultimap<String, AttributeModifier> parseAttributeModifiers(Element el)
      throws InvalidXMLException {
    SetMultimap<String, AttributeModifier> modifiers = HashMultimap.create();

    Node attr = Node.fromAttr(el, "attribute", "attributes");
    if (attr != null) {
      for (String modifierText : Splitter.on(";").split(attr.getValue())) {
        Map.Entry<String, AttributeModifier> mod =
            XMLUtils.parseCompactAttributeModifier(attr, modifierText);
        modifiers.put(mod.getKey(), mod.getValue());
      }
    }

    for (Element elAttribute : el.getChildren("attribute")) {
      Map.Entry<String, AttributeModifier> mod = XMLUtils.parseAttributeModifier(elAttribute);
      modifiers.put(mod.getKey(), mod.getValue());
    }

    return modifiers;
  }

  public ItemStack parseBook(Element el) throws InvalidXMLException {
    ItemStack itemStack = parseItem(el, Material.WRITTEN_BOOK);
    BookMeta meta = (BookMeta) itemStack.getItemMeta();
    meta.setTitle(BukkitUtils.colorize(XMLUtils.getRequiredUniqueChild(el, "title").getText()));
    meta.setAuthor(BukkitUtils.colorize(XMLUtils.getRequiredUniqueChild(el, "author").getText()));

    Element elPages = el.getChild("pages");
    if (elPages != null) {
      for (Element elPage : elPages.getChildren("page")) {
        String text = elPage.getText();
        text = text.trim(); // Remove leading and trailing whitespace
        text =
            Pattern.compile("^[ \\t]+", Pattern.MULTILINE)
                .matcher(text)
                .replaceAll(""); // Remove indentation on each line
        text =
            Pattern.compile("^\\n", Pattern.MULTILINE)
                .matcher(text)
                .replaceAll(
                    " \n"); // Add a space to blank lines, otherwise they vanish for unknown reasons
        text = BukkitUtils.colorize(text); // Color codes
        meta.addPage(text);
      }
    }

    itemStack.setItemMeta(meta);
    return itemStack;
  }

  public ItemStack parseHead(Element el) throws InvalidXMLException {
    ItemStack itemStack = parseItem(el, Material.SKULL_ITEM, (short) 3);
    SkullMeta meta = (SkullMeta) itemStack.getItemMeta();
    meta.setOwner(
        XMLUtils.parseUsername(Node.fromChildOrAttr(el, "name")),
        XMLUtils.parseUuid(Node.fromRequiredChildOrAttr(el, "uuid")),
        XMLUtils.parseUnsignedSkin(Node.fromRequiredChildOrAttr(el, "skin")));
    itemStack.setItemMeta(meta);
    return itemStack;
  }

  public ItemStack parseRequiredItem(Element parent) throws InvalidXMLException {
    ItemStack stack = parseItem(parent.getChild("item"), false);
    if (stack == null) {
      throw new InvalidXMLException("Item expected", parent);
    }
    return stack;
  }

  public ItemStack parseItem(Element el, boolean allowAir) throws InvalidXMLException {
    if (el == null) return null;

    org.jdom2.Attribute attrMaterial = el.getAttribute("material");
    String name = attrMaterial != null ? attrMaterial.getValue() : el.getValue();
    Material type = Materials.parseMaterial(name);
    if (type == null || (type == Material.AIR && !allowAir)) {
      throw new InvalidXMLException("Invalid material type '" + name + "'", el);
    }

    return parseItem(el, type);
  }

  public ItemStack parseItem(Element el, Material type) throws InvalidXMLException {
    return parseItem(
        el, type, XMLUtils.parseNumber(el.getAttribute("damage"), Short.class, (short) 0));
  }

  public ItemStack parseItem(Element el, Material type, short damage) throws InvalidXMLException {
    int amount = XMLUtils.parseNumber(el.getAttribute("amount"), Integer.class, 1);

    // must be CraftItemStack to keep track of NBT data
    ItemStack itemStack = CraftItemStack.asCraftCopy(new ItemStack(type, amount, damage));

    if (itemStack.getType() != type) {
      throw new InvalidXMLException("Invalid item/block", el);
    }

    ItemMeta meta = itemStack.getItemMeta();
    if (meta != null) { // This happens if the item is "air"
      parseItemMeta(el, meta);
      itemStack.setItemMeta(meta);
    }

    parseCustomNBT(el, itemStack);

    return itemStack;
  }

  public void parseItemMeta(Element el, ItemMeta meta) throws InvalidXMLException {
    for (Map.Entry<Enchantment, Integer> enchant : parseEnchantments(el).entrySet()) {
      meta.addEnchant(enchant.getKey(), enchant.getValue(), true);
    }

    List<PotionEffect> potions = parsePotions(el);
    if (!potions.isEmpty() && meta instanceof PotionMeta) {
      PotionMeta potionMeta = (PotionMeta) meta;

      for (PotionEffect effect : potionMeta.getCustomEffects()) {
        potionMeta.removeCustomEffect(effect.getType());
      }

      for (PotionEffect effect : potions) {
        potionMeta.addCustomEffect(effect, false);
      }
    }

    for (Map.Entry<String, AttributeModifier> entry : parseAttributeModifiers(el).entries()) {
      meta.addAttributeModifier(entry.getKey(), entry.getValue());
    }

    String customName = el.getAttributeValue("name");
    if (customName != null) {
      meta.setDisplayName(BukkitUtils.colorize(customName));
    } else if (XMLUtils.parseBoolean(el.getAttribute("grenade"), false)) {
      meta.setDisplayName("Grenade");
    }

    if (meta instanceof LeatherArmorMeta) {
      LeatherArmorMeta armorMeta = (LeatherArmorMeta) meta;
      org.jdom2.Attribute attrColor = el.getAttribute("color");
      if (attrColor != null) {
        String raw = attrColor.getValue();
        if (!raw.matches("[a-fA-F0-9]{6}")) {
          throw new InvalidXMLException("Invalid color format", attrColor);
        }
        armorMeta.setColor(Color.fromRGB(Integer.parseInt(attrColor.getValue(), 16)));
      }
    }

    String loreText = el.getAttributeValue("lore");
    if (loreText != null) {
      List<String> lore =
          ImmutableList.copyOf(Splitter.on('|').split(BukkitUtils.colorize(loreText)));
      meta.setLore(lore);
    }

    for (ItemFlag flag : ItemFlag.values()) {
      if (!XMLUtils.parseBoolean(Node.fromAttr(el, "show-" + itemFlagName(flag)), true)) {
        meta.addItemFlags(flag);
      }
    }

    if (XMLUtils.parseBoolean(el.getAttribute("unbreakable"), false)) {
      meta.spigot().setUnbreakable(true);
    }

    Element elCanDestroy = el.getChild("can-destroy");
    if (elCanDestroy != null) {
      meta.setCanDestroy(XMLUtils.parseMaterialMatcher(elCanDestroy).getMaterials());
    }

    Element elCanPlaceOn = el.getChild("can-place-on");
    if (elCanPlaceOn != null) {
      meta.setCanPlaceOn(XMLUtils.parseMaterialMatcher(elCanPlaceOn).getMaterials());
    }
  }

  String itemFlagName(ItemFlag flag) {
    switch (flag) {
      case HIDE_ATTRIBUTES:
        return "attributes";
      case HIDE_ENCHANTS:
        return "enchantments";
      case HIDE_UNBREAKABLE:
        return "unbreakable";
      case HIDE_DESTROYS:
        return "can-destroy";
      case HIDE_PLACED_ON:
        return "can-place-on";
      case HIDE_POTION_EFFECTS:
        return "other";
    }
    throw new IllegalStateException("Unknown item flag " + flag);
  }

  public void parseCustomNBT(Element el, ItemStack itemStack) throws InvalidXMLException {
    if (XMLUtils.parseBoolean(el.getAttribute("grenade"), false)) {
      Grenade.ITEM_TAG.set(
          itemStack,
          new Grenade(
              XMLUtils.parseNumber(el.getAttribute("grenade-power"), Float.class, 1f),
              XMLUtils.parseBoolean(el.getAttribute("grenade-fire"), false),
              XMLUtils.parseBoolean(el.getAttribute("grenade-destroy"), true)));
    }

    if (XMLUtils.parseBoolean(el.getAttribute("prevent-sharing"), false)) {
      ItemTags.PREVENT_SHARING.set(itemStack, true);
    }

    Node projectileNode = Node.fromAttr(el, "projectile");
    if (projectileNode != null) {
      ItemTags.PROJECTILE.set(
          itemStack,
          factory
              .getFeatures()
              .createReference(projectileNode, ProjectileDefinition.class)
              .getId());
      String name = itemStack.getItemMeta().getDisplayName();
      ItemTags.ORIGINAL_NAME.set(itemStack, name != null ? name : "");
    }
  }

  public Map.Entry<Enchantment, Integer> parseEnchantment(Element el) throws InvalidXMLException {
    return new AbstractMap.SimpleImmutableEntry<>(
        XMLUtils.parseEnchantment(new Node(el)),
        XMLUtils.parseNumber(Node.fromAttr(el, "level"), Integer.class, 1));
  }

  public Map<Enchantment, Integer> parseEnchantments(Element el) throws InvalidXMLException {
    Map<Enchantment, Integer> enchantments = Maps.newHashMap();

    Node attr = Node.fromAttr(el, "enchantment", "enchantments");
    if (attr != null) {
      Iterable<String> enchantmentTexts = Splitter.on(";").split(attr.getValue());
      for (String enchantmentText : enchantmentTexts) {
        int level = 1;
        List<String> parts = Lists.newArrayList(Splitter.on(":").limit(2).split(enchantmentText));
        Enchantment enchant = XMLUtils.parseEnchantment(attr, parts.get(0));
        if (parts.size() > 1) {
          level = XMLUtils.parseNumber(attr, parts.get(1), Integer.class);
        }
        enchantments.put(enchant, level);
      }
    }

    for (Element elEnchantment : el.getChildren("enchantment")) {
      Map.Entry<Enchantment, Integer> entry = parseEnchantment(elEnchantment);
      enchantments.put(entry.getKey(), entry.getValue());
    }

    return enchantments;
  }

  public HealthKit parseHealthKit(Element parent) throws InvalidXMLException {
    Element el = XMLUtils.getUniqueChild(parent, "health");
    if (el == null) {
      return null;
    }

    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);
  }

  public HungerKit parseHungerKit(Element parent) throws InvalidXMLException {
    Float saturation = null;
    Element el = XMLUtils.getUniqueChild(parent, "saturation");
    if (el != null) {
      saturation = XMLUtils.parseNumber(el, Float.class);
    }

    Integer foodLevel = null;
    el = XMLUtils.getUniqueChild(parent, "foodlevel");
    if (el != null) {
      foodLevel = XMLUtils.parseNumber(el, Integer.class);
    }

    if (saturation != null || foodLevel != null) {
      return new HungerKit(saturation, foodLevel);
    } else {
      return null;
    }
  }

  public DoubleJumpKit parseDoubleJumpKit(Element parent) throws InvalidXMLException {
    Element child = XMLUtils.getUniqueChild(parent, "double-jump");

    if (child != null) {
      boolean enabled = XMLUtils.parseBoolean(child.getAttribute("enabled"), true);
      float power =
          XMLUtils.parseNumber(
              child.getAttribute("power"), Float.class, DoubleJumpKit.DEFAULT_POWER);
      Duration rechargeTime =
          XMLUtils.parseDuration(
              child.getAttribute("recharge-time"), DoubleJumpKit.DEFAULT_RECHARGE);
      boolean rechargeInAir =
          XMLUtils.parseBoolean(child.getAttribute("recharge-before-landing"), false);

      return new DoubleJumpKit(enabled, power, rechargeTime, rechargeInAir);
    } else {
      return null;
    }
  }

  public ResetEnderPearlsKit parseEnderPearlKit(Element parent) throws InvalidXMLException {
    return XMLUtils.parseBoolean(parent.getAttribute("reset-ender-pearls"), false)
        ? new ResetEnderPearlsKit()
        : null;
  }

  public Collection<RemoveKit> parseRemoveKits(Element parent) throws InvalidXMLException {
    Set<RemoveKit> kits = Collections.emptySet();
    for (Element el : parent.getChildren("remove")) {
      if (kits.isEmpty()) kits = new HashSet<>();

      Node idAttr = Node.fromAttr(el, "id");
      RemoveKit kit;
      if (idAttr != null) {
        kit = new RemoveKit(parseReference(idAttr, idAttr.getValue()));
      } else {
        kit = new RemoveKit(parse(el));
      }
      kits.add(kit);
      factory
          .getFeatures()
          .addFeature(el, kit); // So we can retrieve the node from KitModule#postParse
    }
    return kits;
  }

  public GameModeKit parseGameModeKit(Element parent) throws InvalidXMLException {
    GameMode gameMode =
        XMLUtils.parseGameMode(Node.fromNullable(parent.getChild("game-mode")), (GameMode) null);
    return gameMode == null ? null : new GameModeKit(gameMode);
  }

  public ShieldKit parseShieldKit(Element parent) throws InvalidXMLException {
    Element el = XMLUtils.getUniqueChild(parent, "shield");
    if (el == null) return null;

    double health =
        XMLUtils.parseNumber(
            el.getAttribute("health"), Double.class, ShieldParameters.DEFAULT_HEALTH);
    Duration rechargeDelay =
        XMLUtils.parseDuration(el.getAttribute("delay"), ShieldParameters.DEFAULT_DELAY);
    return new ShieldKit(new ShieldParameters(health, rechargeDelay));
  }
}