forge.game.card.CardFactoryUtil.java Source code

Java tutorial

Introduction

Here is the source code for forge.game.card.CardFactoryUtil.java

Source

/*
 * Forge: Play Magic: the Gathering.
 * Copyright (C) 2011  Forge Team
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package forge.game.card;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import org.apache.commons.lang3.StringUtils;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;

import forge.GameCommand;
import forge.card.CardStateName;
import forge.card.CardType;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
import forge.card.mana.ManaCostShard;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.Cost;
import forge.game.cost.CostPayment;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.phase.PhaseHandler;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.Ability;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.OptionalCost;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityRestriction;
import forge.game.spellability.SpellPermanent;
import forge.game.spellability.TargetRestrictions;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.util.Aggregates;
import forge.util.collect.FCollectionView;
import forge.util.Lang;

/**
 * <p>
 * CardFactoryUtil class.
 * </p>
 * 
 * @author Forge
 * @version $Id: CardFactoryUtil.java 30235 2015-10-06 04:53:14Z Agetian $
 */
public class CardFactoryUtil {

    /**
     * <p>
     * abilityMorphDown.
     * </p>
     * 
     * @param sourceCard
     *            a {@link forge.game.card.Card} object.
     * @return a {@link forge.game.spellability.SpellAbility} object.
     */
    public static SpellAbility abilityMorphDown(final Card sourceCard) {
        final Spell morphDown = new Spell(sourceCard, new Cost(ManaCost.THREE, false)) {
            private static final long serialVersionUID = -1438810964807867610L;

            @Override
            public void resolve() {
                Card c = sourceCard.getGame().getAction().moveToPlay(sourceCard);
                c.setPreFaceDownState(CardStateName.Original);
            }

            @Override
            public boolean canPlay() {
                CardStateName stateBackup = sourceCard.getCurrentStateName();
                sourceCard.setState(CardStateName.FaceDown, false);
                boolean success = super.canPlay();
                sourceCard.setState(stateBackup, false);
                return success;
            }
        };

        morphDown.setDescription("(You may cast this card face down as a 2/2 creature for {3}.)");
        morphDown.setStackDescription("Morph - Creature 2/2");
        morphDown.setCastFaceDown(true);

        return morphDown;
    }

    /**
     * <p>
     * abilityMorphUp.
     * </p>
     * 
     * @param sourceCard
     *            a {@link forge.game.card.Card} object.
     * @param cost
     *            a {@link forge.game.cost.Cost} object.
     * @return a {@link forge.game.spellability.AbilityActivated} object.
     */
    public static AbilityStatic abilityMorphUp(final Card sourceCard, final Cost cost, final boolean mega) {
        final AbilityStatic morphUp = new AbilityStatic(sourceCard, cost, null) {

            @Override
            public void resolve() {
                if (sourceCard.turnFaceUp()) {
                    String sb = this.getActivatingPlayer() + " has unmorphed " + sourceCard.getName();
                    sourceCard.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
                    sourceCard.getGame().fireEvent(new GameEventCardStatsChanged(sourceCard));
                }
                if (mega) {
                    sourceCard.addCounter(CounterType.P1P1, 1, true);
                }
            }

            @Override
            public boolean canPlay() {
                return sourceCard.getController().equals(this.getActivatingPlayer()) && sourceCard.isFaceDown()
                        && sourceCard.isInPlay() && CostPayment.canPayAdditionalCosts(cost, this);
            }

        }; // morph_up

        String costDesc = cost.toString();
        // get rid of the ": " at the end
        costDesc = costDesc.substring(0, costDesc.length() - 2);
        final StringBuilder sb = new StringBuilder();
        sb.append("Morph");
        if (!cost.isOnlyManaCost()) {
            sb.append(" -");
        }
        sb.append(" ").append(costDesc).append(" (Turn this face up any time for its morph cost.)");
        morphUp.setDescription(sb.toString());

        final StringBuilder sbStack = new StringBuilder();
        sbStack.append(sourceCard.getName()).append(" - turn this card face up.");
        morphUp.setStackDescription(sbStack.toString());
        morphUp.setIsMorphUp(true);

        return morphUp;
    }

    public static AbilityStatic abilityManifestFaceUp(final Card sourceCard, final ManaCost manaCost) {
        final Cost cost = new Cost(manaCost, false);

        final AbilityStatic manifestUp = new AbilityStatic(sourceCard, cost, null) {

            @Override
            public void resolve() {
                if (sourceCard.turnFaceUp(true)) {
                    String sb = this.getActivatingPlayer() + " has unmanifested " + sourceCard.getName();
                    sourceCard.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
                    sourceCard.getGame().fireEvent(new GameEventCardStatsChanged(sourceCard));
                }
            }

            @Override
            public boolean canPlay() {
                return sourceCard.getController().equals(this.getActivatingPlayer()) && sourceCard.isFaceDown()
                        && sourceCard.isInPlay() && sourceCard.isManifested();
            }

        }; // manifest_up

        String costDesc = cost.toString();
        // get rid of the ": " at the end
        costDesc = costDesc.substring(0, costDesc.length() - 2);
        final StringBuilder sb = new StringBuilder();
        sb.append("Unmanifest");
        if (!cost.isOnlyManaCost()) {
            sb.append(" -");
        }
        sb.append(" ").append(costDesc).append(" (Turn this face up any time for its mana cost.)");
        manifestUp.setDescription(sb.toString());

        final StringBuilder sbStack = new StringBuilder();
        sbStack.append(sourceCard.getName()).append(" - turn this card face up.");
        manifestUp.setStackDescription(sbStack.toString());
        manifestUp.setIsManifestUp(true);

        return manifestUp;
    }

    public static boolean handleHiddenAgenda(Player player, Card card) {
        SpellAbility sa = new SpellAbility.EmptySa(card);
        sa.getMapParams().put("AILogic", card.getSVar("AgendaLogic"));
        Predicate<PaperCard> cpp = Predicates.alwaysTrue();
        //Predicate<Card> pc = Predicates.in(player.getAllCards());
        // TODO This would be better to send in the player's deck, not all cards
        String name = player.getController().chooseCardName(sa, cpp, "Card", "Name a card for " + card.getName());
        if (name == null || name.isEmpty()) {
            return false;
        }

        card.setNamedCard(name);
        card.turnFaceDown();
        // Hopefully face down also hides the named card?
        card.addSpellAbility(abilityRevealHiddenAgenda(card));
        return true;
    }

    public static AbilityStatic abilityRevealHiddenAgenda(final Card sourceCard) {
        final AbilityStatic revealAgenda = new AbilityStatic(sourceCard, Cost.Zero, null) {

            @Override
            public void resolve() {
                if (sourceCard.turnFaceUp()) {
                    String sb = this.getActivatingPlayer() + " has revealed " + sourceCard.getName()
                            + " with the chosen name " + sourceCard.getNamedCard();
                    sourceCard.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
                    sourceCard.getGame().fireEvent(new GameEventCardStatsChanged(sourceCard));
                }
            }

            @Override
            public boolean canPlay() {
                return sourceCard.getController().equals(this.getActivatingPlayer()) && sourceCard.isFaceDown()
                        && sourceCard.isInZone(ZoneType.Command);
            }

            // TODO When should the AI activate this?

        }; // reveal hidden agenda

        revealAgenda.setDescription("Reveal this Hidden Agenda at any time. ");
        return revealAgenda;
    }

    /**
     * <p>
     * abilityCycle.
     * </p>
     * 
     * @param sourceCard
     *            a {@link forge.game.card.Card} object.
     * @param cycleCost
     *            a {@link java.lang.String} object.
     * @return a {@link forge.game.spellability.SpellAbility} object.
     */
    public static SpellAbility abilityCycle(final Card sourceCard, String cycleCost) {
        StringBuilder sb = new StringBuilder();
        sb.append("AB$ Draw | Cost$ ");
        sb.append(cycleCost);
        sb.append(" Discard<1/CARDNAME> | ActivationZone$ Hand | PrecostDesc$ Cycling ");
        sb.append("| SpellDescription$ Draw a card.");

        SpellAbility cycle = AbilityFactory.getAbility(sb.toString(), sourceCard);
        cycle.setIsCycling(true);

        return cycle;
    } // abilityCycle()

    /**
     * <p>
     * abilityTypecycle.
     * </p>
     * 
     * @param sourceCard
     *            a {@link forge.game.card.Card} object.
     * @param cycleCost
     *            a {@link java.lang.String} object.
     * @param type
     *            a {@link java.lang.String} object.
     * @return a {@link forge.game.spellability.SpellAbility} object.
     */
    public static SpellAbility abilityTypecycle(final Card sourceCard, String cycleCost, final String type) {
        StringBuilder sb = new StringBuilder();
        sb.append("AB$ ChangeZone | Cost$ ").append(cycleCost);

        String desc = type;
        if (type.equals("Basic")) {
            desc = "Basic land";
        }

        sb.append(" Discard<1/CARDNAME> | ActivationZone$ Hand | PrecostDesc$ ").append(desc).append("cycling ");
        sb.append("| Origin$ Library | Destination$ Hand |");
        sb.append("ChangeType$ ").append(type);
        sb.append(" | SpellDescription$ Search your library for a ").append(desc).append(" card, reveal it,");
        sb.append(" and put it into your hand. Then shuffle your library.");

        SpellAbility cycle = AbilityFactory.getAbility(sb.toString(), sourceCard);
        cycle.setIsCycling(true);

        return cycle;
    } // abilityTypecycle()

    /**
     * <p>
     * abilitySuspendStatic.
     * </p>
     * 
     * @param sourceCard
     *            a {@link forge.game.card.Card} object.
     * @param suspendCost
     *            a {@link java.lang.String} object.
     * @param timeCounters
     *            a int.
     * @return a {@link forge.game.spellability.SpellAbility} object.
     */
    public static SpellAbility abilitySuspendStatic(final Card sourceCard, final String suspendCost,
            final String timeCounters) {
        // be careful with Suspend ability, it will not hit the stack
        Cost cost = new Cost(suspendCost, true);
        final SpellAbility suspend = new AbilityStatic(sourceCard, cost, null) {
            @Override
            public boolean canPlay() {
                if (!(this.getRestrictions().canPlay(sourceCard, this))) {
                    return false;
                }

                if (sourceCard.isInstant() || sourceCard.hasKeyword("Flash")) {
                    return true;
                }

                return sourceCard.getOwner().canCastSorcery();
            }

            @Override
            public void resolve() {
                final Game game = sourceCard.getGame();
                final Card c = game.getAction().exile(sourceCard);

                int counters = AbilityUtils.calculateAmount(c, timeCounters, this);
                c.addCounter(CounterType.TIME, counters, true);

                String sb = String.format("%s has suspended %s with %d time counters on it.",
                        this.getActivatingPlayer(), c.getName(), counters);
                game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
            }
        };
        final StringBuilder sbDesc = new StringBuilder();
        sbDesc.append("Suspend ").append(timeCounters).append(" - ").append(cost.toSimpleString());
        sbDesc.append(
                " (Rather than cast CARDNAME from your hand, you may pay cost and exile it with three time counters on it. At the beginning of your upkeep, remove a time counter. When the last is removed, cast it without paying its mana cost.)");
        suspend.setDescription(sbDesc.toString());

        String svar = "X"; // emulate "References X" here
        suspend.setSVar(svar, sourceCard.getSVar(svar));

        final StringBuilder sbStack = new StringBuilder();
        sbStack.append(sourceCard.getName()).append(" suspending for ").append(timeCounters).append(" turns.)");
        suspend.setStackDescription(sbStack.toString());

        suspend.getRestrictions().setZone(ZoneType.Hand);
        return suspend;
    } // abilitySuspendStatic()

    public static void addSuspendUpkeepTrigger(Card card) {
        //upkeep trigger
        StringBuilder upkeepTrig = new StringBuilder();
        String triggerSvar = "SuspendTrigSV"; // FIXME: the SVars were previously random UUIDs (which caused the keyword not to work). 
        String removeCounterSvar = "SuspendRemoveCtrSV";

        upkeepTrig.append("Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Exile | CheckSVar$ ");
        upkeepTrig.append(triggerSvar);
        upkeepTrig.append(" | SVarCompare$ GE1 | References$ ");
        upkeepTrig.append(triggerSvar);
        upkeepTrig.append(" | Execute$ ");
        upkeepTrig.append(removeCounterSvar);
        // Mark this trigger as Secondary, so it's not displayed twice
        upkeepTrig.append(
                " | Secondary$ True | TriggerDescription$ At the beginning of your upkeep, if this card is suspended, remove a time counter from it");

        card.setSVar(removeCounterSvar, "DB$ RemoveCounter | Defined$ Self | CounterType$ TIME | CounterNum$ 1");
        card.setSVar(triggerSvar, "Count$ValidExile Card.Self+suspended");

        final Trigger parsedUpkeepTrig = TriggerHandler.parseTrigger(upkeepTrig.toString(), card, true);
        card.addTrigger(parsedUpkeepTrig);
    }

    public static void addSuspendPlayTrigger(Card card) {
        //play trigger
        StringBuilder playTrig = new StringBuilder();
        String playSvar = "SuspendPlaySV"; // FIXME: the SVars were previously random UUIDs (which caused the keyword not to work).

        playTrig.append(
                "Mode$ CounterRemoved | TriggerZones$ Exile | ValidCard$ Card.Self | CounterType$ TIME | NewCounterAmount$ 0 | Secondary$ True | Execute$ ");
        playTrig.append(playSvar);
        playTrig.append(
                " | TriggerDescription$ When the last time counter is removed from this card, if it's exiled, play it without paying its mana cost if able.  ");
        playTrig.append(
                "If you can't, it remains exiled. If you cast a creature spell this way, it gains haste until you lose control of the spell or the permanent it becomes.");

        StringBuilder playWithoutCost = new StringBuilder();
        playWithoutCost.append("DB$ Play | Defined$ Self | WithoutManaCost$ True | SuspendCast$ True");

        final Trigger parsedPlayTrigger = TriggerHandler.parseTrigger(playTrig.toString(), card, true);
        card.addTrigger(parsedPlayTrigger);

        card.setSVar(playSvar, playWithoutCost.toString());
    }

    /**
     * <p>
     * multiplyCost.
     * </p>
     * 
     * @param manacost
     *            a {@link java.lang.String} object.
     * @param multiplier
     *            a int.
     * @return a {@link java.lang.String} object.
     */
    public static String multiplyCost(final String manacost, final int multiplier) {
        if (multiplier == 0) {
            return "";
        }
        if (multiplier == 1) {
            return manacost;
        }

        final String[] tokenized = manacost.split("\\s");
        final StringBuilder sb = new StringBuilder();

        if (Character.isDigit(tokenized[0].charAt(0))) {
            // cost starts with "colorless" number cost
            int cost = Integer.parseInt(tokenized[0]);
            cost = multiplier * cost;
            tokenized[0] = "" + cost;
            sb.append(tokenized[0]);
        } else {
            if (tokenized[0].contains("<")) {
                final String[] advCostPart = tokenized[0].split("<");
                final String costVariable = advCostPart[1].split(">")[0];
                final String[] advCostPartValid = costVariable.split("\\/", 2);
                // multiply the number part of the cost object
                int num = Integer.parseInt(advCostPartValid[0]);
                num = multiplier * num;
                tokenized[0] = advCostPart[0] + "<" + num;
                if (advCostPartValid.length > 1) {
                    tokenized[0] = tokenized[0] + "/" + advCostPartValid[1];
                }
                tokenized[0] = tokenized[0] + ">";
                sb.append(tokenized[0]);
            } else {
                for (int i = 0; i < multiplier; i++) {
                    // tokenized[0] = tokenized[0] + " " + tokenized[0];
                    sb.append((" "));
                    sb.append(tokenized[0]);
                }
            }
        }

        for (int i = 1; i < tokenized.length; i++) {
            if (tokenized[i].contains("<")) {
                final String[] advCostParts = tokenized[i].split("<");
                final String costVariables = advCostParts[1].split(">")[0];
                final String[] advCostPartsValid = costVariables.split("\\/", 2);
                // multiply the number part of the cost object
                int num = Integer.parseInt(advCostPartsValid[0]);
                num = multiplier * num;
                tokenized[i] = advCostParts[0] + "<" + num;
                if (advCostPartsValid.length > 1) {
                    tokenized[i] = tokenized[i] + "/" + advCostPartsValid[1];
                }
                tokenized[i] = tokenized[i] + ">";
                sb.append((" "));
                sb.append(tokenized[i]);
            } else {
                for (int j = 0; j < multiplier; j++) {
                    // tokenized[i] = tokenized[i] + " " + tokenized[i];
                    sb.append((" "));
                    sb.append(tokenized[i]);
                }
            }
        }

        String result = sb.toString();
        System.out.println("result: " + result);
        result = result.trim();
        return result;
    }

    /**
     * <p>
     * isTargetStillValid.
     * </p>
     * 
     * @param ability
     *            a {@link forge.game.spellability.SpellAbility} object.
     * @param target
     *            a {@link forge.game.card.Card} object.
     * @return a boolean.
     */
    public static boolean isTargetStillValid(final SpellAbility ability, final Card target) {
        Zone zone = target.getGame().getZoneOf(target);
        if (zone == null) {
            return false; // for tokens that disappeared
        }

        final Card source = ability.getHostCard();
        final TargetRestrictions tgt = ability.getTargetRestrictions();
        if (tgt != null) {
            // Reconfirm the Validity of a TgtValid, or if the Creature is still
            // a Creature
            if (tgt.doesTarget()
                    && !target.isValid(tgt.getValidTgts(), ability.getActivatingPlayer(), ability.getHostCard())) {
                return false;
            }

            // Check if the target is in the zone it needs to be in to be targeted
            if (!tgt.getZone().contains(zone.getZoneType())) {
                return false;
            }
        } else {
            // If an Aura's target is removed before it resolves, the Aura
            // fizzles
            if (source.isAura() && !target.isInZone(ZoneType.Battlefield)) {
                return false;
            }
        }

        // Make sure it's still targetable as well
        return ability.canTarget(target);
    }

    // does "target" have protection from "card"?
    /**
     * <p>
     * hasProtectionFrom.
     * </p>
     * 
     * @param card
     *            a {@link forge.game.card.Card} object.
     * @param target
     *            a {@link forge.game.card.Card} object.
     * @return a boolean.
     */
    public static boolean hasProtectionFrom(final Card card, final Card target) {
        if (target == null) {
            return false;
        }

        return target.hasProtectionFrom(card);
    }

    /**
     * <p>
     * isCounterable.
     * </p>
     * 
     * @param c
     *            a {@link forge.game.card.Card} object.
     * @return a boolean.
     */
    public static boolean isCounterable(final Card c) {
        if (c.hasKeyword("CARDNAME can't be countered.") || !c.getCanCounter()) {
            return false;
        }

        return true;
    }

    /**
     * <p>
     * isCounterableBy.
     * </p>
     * 
     * @param c
     *            a {@link forge.game.card.Card} object.
     * @param sa
     *            the sa
     * @return a boolean.
     */
    public static boolean isCounterableBy(final Card c, final SpellAbility sa) {
        if (!isCounterable(c)) {
            return false;
        }
        // Autumn's Veil
        if (c.hasKeyword("CARDNAME can't be countered by blue or black spells.") && sa.isSpell()
                && (sa.getHostCard().isBlack() || sa.getHostCard().isBlue())) {
            return false;
        }
        return true;
    }

    /**
     * <p>
     * countOccurrences.
     * </p>
     * 
     * @param arg1
     *            a {@link java.lang.String} object.
     * @param arg2
     *            a {@link java.lang.String} object.
     * @return a int.
     */
    public static int countOccurrences(final String arg1, final String arg2) {
        int count = 0;
        int index = 0;
        while ((index = arg1.indexOf(arg2, index)) != -1) {
            ++index;
            ++count;
        }
        return count;
    }

    /**
     * <p>
     * parseMath.
     * </p>
     * 
     * @param l
     *            an array of {@link java.lang.String} objects.
     * @return an array of {@link java.lang.String} objects.
     */
    public static String extractOperators(final String expression) {
        String[] l = expression.split("/");
        return l.length > 1 ? l[1] : null;
    }

    /**
     * <p>
     * Parse player targeted X variables.
     * </p>
     * 
     * @param players
     *            a {@link java.util.ArrayList} object.
     * @param s
     *            a {@link java.lang.String} object.
     * @param source
     *            a {@link forge.game.card.Card} object.
     * @return a int.
     */
    public static int objectXCount(final List<?> objects, final String s, final Card source) {
        if (objects.isEmpty()) {
            return 0;
        }

        if (s.startsWith("Valid")) {
            final CardCollection cards = new CardCollection();
            for (Object o : objects) {
                if (o instanceof Card) {
                    cards.add((Card) o);
                }
            }
            return CardFactoryUtil.handlePaid(cards, s, source);
        }

        int n = s.startsWith("Amount") ? objects.size() : 0;
        return doXMath(n, extractOperators(s), source);
    }

    /**
     * <p>
     * Parse player targeted X variables.
     * </p>
     * 
     * @param players
     *            a {@link java.util.ArrayList} object.
     * @param s
     *            a {@link java.lang.String} object.
     * @param source
     *            a {@link forge.game.card.Card} object.
     * @return a int.
     */
    public static int playerXCount(final List<Player> players, final String s, final Card source) {
        if (players.size() == 0) {
            return 0;
        }

        final String[] l = s.split("/");
        final String m = extractOperators(s);

        int n = 0;

        // methods for getting the highest/lowest playerXCount from a range of players
        if (l[0].startsWith("Highest")) {
            for (final Player player : players) {
                final int current = playerXProperty(player, s.replace("Highest", ""), source);
                if (current > n) {
                    n = current;
                }
            }

            return doXMath(n, m, source);
        }

        if (l[0].startsWith("Lowest")) {
            n = 99999; // if no players have fewer than 99999 valids, the game is frozen anyway
            for (final Player player : players) {
                final int current = playerXProperty(player, s.replace("Lowest", ""), source);
                if (current < n) {
                    n = current;
                }
            }
            return doXMath(n, m, source);
        }

        final String[] sq;
        sq = l[0].split("\\.");

        // the number of players passed in
        if (sq[0].equals("Amount")) {
            return doXMath(players.size(), m, source);
        }

        if (sq[0].startsWith("HasProperty")) {
            int totPlayer = 0;
            String property = sq[0].substring(11);
            for (Player p : players) {
                if (p.hasProperty(property, source.getController(), source)) {
                    totPlayer++;
                }
            }
            return doXMath(totPlayer, m, source);
        }

        if (sq[0].contains("DamageThisTurn")) {
            int totDmg = 0;
            for (Player p : players) {
                totDmg += p.getAssignedDamage();
            }
            return doXMath(totDmg, m, source);
        }

        if (players.size() > 0) {
            return playerXProperty(players.get(0), s, source);
        }

        return doXMath(n, m, source);
    }

    public static int playerXProperty(final Player player, final String s, final Card source) {
        final String[] l = s.split("/");
        final String m = extractOperators(s);

        final Game game = player.getGame();

        // count valid cards in any specified zone/s
        if (l[0].startsWith("Valid") && !l[0].contains("Valid ")) {
            String[] lparts = l[0].split(" ", 2);
            final List<ZoneType> vZone = ZoneType.listValueOf(lparts[0].split("Valid")[1]);
            String restrictions = l[0].replace(lparts[0] + " ", "");
            final String[] rest = restrictions.split(",");
            CardCollection cards = CardLists.getValidCards(game.getCardsIn(vZone), rest, player, source);
            return doXMath(cards.size(), m, source);
        }

        // count valid cards on the battlefield
        if (l[0].startsWith("Valid ")) {
            final String restrictions = l[0].substring(6);
            final String[] rest = restrictions.split(",");
            CardCollection cardsonbattlefield = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest,
                    player, source);
            return doXMath(cardsonbattlefield.size(), m, source);
        }

        final String[] sq = l[0].split("\\.");
        final String value = sq[0];

        if (value.contains("CardsInHand")) {
            return doXMath(player.getCardsIn(ZoneType.Hand).size(), m, source);
        }

        if (value.contains("NumPowerSurgeLands")) {
            return doXMath(player.getNumPowerSurgeLands(), m, source);
        }

        if (value.contains("DomainPlayer")) {
            int n = 0;
            final CardCollectionView someCards = player.getCardsIn(ZoneType.Battlefield);
            final List<String> basic = MagicColor.Constant.BASIC_LANDS;

            for (int i = 0; i < basic.size(); i++) {
                if (!CardLists.getType(someCards, basic.get(i)).isEmpty()) {
                    n++;
                }
            }
            return doXMath(n, m, source);
        }

        if (value.contains("CardsInLibrary")) {
            return doXMath(player.getCardsIn(ZoneType.Library).size(), m, source);
        }

        if (value.contains("CardsInGraveyard")) {
            return doXMath(player.getCardsIn(ZoneType.Graveyard).size(), m, source);
        }
        if (value.contains("LandsInGraveyard")) {
            return doXMath(CardLists.getType(player.getCardsIn(ZoneType.Graveyard), "Land").size(), m, source);
        }

        if (value.contains("CreaturesInPlay")) {
            return doXMath(player.getCreaturesInPlay().size(), m, source);
        }

        if (value.contains("CardsInPlay")) {
            return doXMath(player.getCardsIn(ZoneType.Battlefield).size(), m, source);
        }

        if (value.contains("LifeTotal")) {
            return doXMath(player.getLife(), m, source);
        }

        if (value.contains("LifeLostThisTurn")) {
            return doXMath(player.getLifeLostThisTurn(), m, source);
        }

        if (value.contains("LifeLostLastTurn")) {
            return doXMath(player.getLifeLostLastTurn(), m, source);
        }

        if (value.contains("LifeGainedThisTurn")) {
            return doXMath(player.getLifeGainedThisTurn(), m, source);
        }

        if (value.contains("PoisonCounters")) {
            return doXMath(player.getPoisonCounters(), m, source);
        }

        if (value.contains("TopOfLibraryCMC")) {
            return doXMath(
                    Aggregates.sum(player.getCardsIn(ZoneType.Library, 1), CardPredicates.Accessors.fnGetCmc), m,
                    source);
        }

        if (value.contains("LandsPlayed")) {
            return doXMath(player.getLandsPlayedThisTurn(), m, source);
        }

        if (value.contains("CardsDrawn")) {
            return doXMath(player.getNumDrawnThisTurn(), m, source);
        }

        if (value.contains("CardsDiscardedThisTurn")) {
            return doXMath(player.getNumDiscardedThisTurn(), m, source);
        }

        if (value.contains("AttackersDeclared")) {
            return doXMath(player.getAttackersDeclaredThisTurn(), m, source);
        }

        if (value.equals("DamageDoneToPlayerBy")) {
            return doXMath(source.getDamageDoneToPlayerBy(player.getName()), m, source);
        }

        if (value.contains("DamageToOppsThisTurn")) {
            int oppDmg = 0;
            for (Player opp : player.getOpponents()) {
                oppDmg += opp.getAssignedDamage();
            }
            return doXMath(oppDmg, m, source);
        }

        return doXMath(0, m, source);
    }

    /**
     * <p>
     * Parse non-mana X variables.
     * </p>
     * 
     * @param c
     *            a {@link forge.game.card.Card} object.
     * @param expression
     *            a {@link java.lang.String} object.
     * @return a int.
     */
    public static int xCount(final Card c, final String expression) {
        if (StringUtils.isBlank(expression)) {
            return 0;
        }
        if (StringUtils.isNumeric(expression)) {
            return Integer.parseInt(expression);
        }

        final Player cc = c.getController();
        final Game game = c.getGame();
        final Player activePlayer = game.getPhaseHandler().getPlayerTurn();

        final String[] l = expression.split("/");
        final String m = extractOperators(expression);

        // accept straight numbers
        if (l[0].startsWith("Number$")) {
            final String number = l[0].substring(7);
            if (number.equals("ChosenNumber")) {
                return doXMath(c.getChosenNumber(), m, c);
            }
            return doXMath(Integer.parseInt(number), m, c);
        }

        if (l[0].startsWith("Count$")) {
            l[0] = l[0].substring(6);
        }

        if (l[0].startsWith("SVar$")) {
            return doXMath(xCount(c, c.getSVar(l[0].substring(5))), m, c);
        }

        if (l[0].startsWith("Controller$")) {
            return playerXProperty(cc, l[0].substring(11), c);
        }

        // Manapool
        if (l[0].startsWith("ManaPool")) {
            final String color = l[0].split(":")[1];
            if (color.equals("All")) {
                return cc.getManaPool().totalMana();
            }
            return cc.getManaPool().getAmountOfColor(MagicColor.fromName(color));
        }

        // count valid cards in any specified zone/s
        if (l[0].startsWith("Valid")) {
            String[] lparts = l[0].split(" ", 2);
            final String[] rest = lparts[1].split(",");

            final CardCollectionView cardsInZones = lparts[0].length() > 5
                    ? game.getCardsIn(ZoneType.listValueOf(lparts[0].substring(5)))
                    : game.getCardsIn(ZoneType.Battlefield);

            CardCollection cards = CardLists.getValidCards(cardsInZones, rest, cc, c);
            return doXMath(cards.size(), m, c);
        }

        if (l[0].startsWith("ImprintedCardManaCost") && !c.getImprintedCards().isEmpty()) {
            return c.getImprintedCards().get(0).getCMC();
        }

        if (l[0].startsWith("GreatestPower_")) {
            final String restriction = l[0].substring(14);
            final String[] rest = restriction.split(",");
            CardCollection list = CardLists.getValidCards(cc.getGame().getCardsIn(ZoneType.Battlefield), rest, cc,
                    c);
            int highest = 0;
            for (final Card crd : list) {
                if (crd.getNetPower() > highest) {
                    highest = crd.getNetPower();
                }
            }
            return highest;
        }

        if (l[0].startsWith("GreatestToughness_")) {
            final String restriction = l[0].substring(18);
            final String[] rest = restriction.split(",");
            CardCollection list = CardLists.getValidCards(cc.getGame().getCardsIn(ZoneType.Battlefield), rest, cc,
                    c);
            int highest = 0;
            for (final Card crd : list) {
                if (crd.getNetToughness() > highest) {
                    highest = crd.getNetToughness();
                }
            }
            return highest;
        }

        if (l[0].startsWith("HighestCMC_")) {
            final String restriction = l[0].substring(11);
            final String[] rest = restriction.split(",");
            CardCollection list = CardLists.getValidCards(cc.getGame().getCardsInGame(), rest, cc, c);
            int highest = 0;
            for (final Card crd : list) {
                if (crd.isSplitCard()) {
                    if (crd.getCMC(Card.SplitCMCMode.LeftSplitCMC) > highest) {
                        highest = crd.getCMC(Card.SplitCMCMode.LeftSplitCMC);
                    }
                    if (crd.getCMC(Card.SplitCMCMode.RightSplitCMC) > highest) {
                        highest = crd.getCMC(Card.SplitCMCMode.RightSplitCMC);
                    }
                } else {
                    if (crd.getCMC() > highest) {
                        highest = crd.getCMC();
                    }
                }
            }
            return highest;
        }

        if (l[0].startsWith("DifferentCardNames_")) {
            final List<String> crdname = new ArrayList<String>();
            final String restriction = l[0].substring(19);
            final String[] rest = restriction.split(",");
            CardCollection list = CardLists.getValidCards(cc.getGame().getCardsInGame(), rest, cc, c);
            for (final Card card : list) {
                if (!crdname.contains(card.getName())) {
                    crdname.add(card.getName());
                }
            }
            return doXMath(crdname.size(), m, c);
        }

        if (l[0].startsWith("RememberedSize")) {
            return doXMath(c.getRememberedCount(), m, c);
        }

        if (l[0].startsWith("RememberedNumber")) {
            int num = 0;
            for (final Object o : c.getRemembered()) {
                if (o instanceof Integer) {
                    num += (Integer) o;
                }
            }
            return doXMath(num, m, c);
        }

        // Count$CountersAdded <CounterType> <ValidSource>
        if (l[0].startsWith("CountersAdded")) {
            final String[] components = l[0].split(" ", 3);
            final CounterType counterType = CounterType.valueOf(components[1]);
            String restrictions = components[2];
            final String[] rest = restrictions.split(",");
            CardCollection candidates = CardLists.getValidCards(game.getCardsInGame(), rest, cc, c);

            int added = 0;
            for (final Card counterSource : candidates) {
                added += c.getCountersAddedBy(counterSource, counterType);
            }
            return doXMath(added, m, c);
        }

        if (l[0].startsWith("CommanderCastFromCommandZone")) {
            // Read SVar CommanderCostRaise from Commander Effect
            Card commeff = CardLists
                    .filter(cc.getCardsIn(ZoneType.Command), CardPredicates.nameEquals("Commander Effect")).get(0);
            return doXMath(xCount(commeff, commeff.getSVar("CommanderCostRaise")), "DivideEvenlyDown.2", c);
        }

        if (l[0].startsWith("MostProminentCreatureType")) {
            String restriction = l[0].split(" ")[1];
            CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, cc,
                    c);
            return doXMath(getMostProminentCreatureTypeSize(list), m, c);
        }

        if (l[0].startsWith("SecondMostProminentColor")) {
            String restriction = l[0].split(" ")[1];
            CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, cc,
                    c);
            int[] colorSize = SortColorsFromList(list);
            return doXMath(colorSize[colorSize.length - 2], m, c);
        }

        if (l[0].startsWith("RolledThisTurn")) {
            return game.getPhaseHandler().getPlanarDiceRolledthisTurn();
        }

        final String[] sq;
        sq = l[0].split("\\.");

        if (sq[0].contains("xPaid")) {
            return doXMath(c.getXManaCostPaid(), m, c);
        }

        if (sq[0].contains("xColorPaid")) {
            String[] attrs = sq[0].split(" ");
            String colors = "";
            for (int i = 1; i < attrs.length; i++) {
                colors += attrs[i];
            }
            return doXMath(c.getXManaCostPaidCount(colors), m, c);
        }

        if (sq[0].equals("YouDrewThisTurn")) {
            return doXMath(c.getController().getNumDrawnThisTurn(), m, c);
        }

        if (sq[0].equals("FirstSpellTotalManaSpent")) {
            return doXMath(c.getFirstSpellAbility().getTotalManaSpent(), m, c);
        }
        if (sq[0].equals("StormCount")) {
            return doXMath(game.getStack().getSpellsCastThisTurn().size() - 1, m, c);
        }
        if (sq[0].equals("DamageDoneThisTurn")) {
            return doXMath(c.getDamageDoneThisTurn(), m, c);
        }
        if (sq[0].equals("BloodthirstAmount")) {
            return doXMath(c.getController().getBloodthirstAmount(), m, c);
        }
        if (sq[0].equals("RegeneratedThisTurn")) {
            return doXMath(c.getRegeneratedThisTurn(), m, c);
        }

        // TriggeringObjects
        if (sq[0].startsWith("Triggered")) {
            return doXMath(xCount((Card) c.getTriggeringObject("Card"), sq[0].substring(9)), m, c);
        }

        if (sq[0].contains("YourStartingLife")) {
            return doXMath(cc.getStartingLife(), m, c);
        }

        if (sq[0].contains("YourLifeTotal")) {
            return doXMath(cc.getLife(), m, c);
        }
        if (sq[0].contains("OppGreatestLifeTotal")) {
            return doXMath(cc.getOpponentsGreatestLifeTotal(), m, c);
        }
        if (sq[0].contains("OppsAtLifeTotal")) {
            final int lifeTotal = xCount(c, sq[1]);
            int number = 0;
            for (final Player opp : cc.getOpponents()) {
                if (opp.getLife() == lifeTotal) {
                    number++;
                }
            }
            return doXMath(number, m, c);
        }

        //  Count$TargetedLifeTotal (targeted player's life total)
        if (sq[0].contains("TargetedLifeTotal")) {
            // This doesn't work in some circumstances, since the active SA isn't passed through
            for (final SpellAbility sa : c.getCurrentState().getNonManaAbilities()) {
                final SpellAbility saTargeting = sa.getSATargetingPlayer();
                if (saTargeting != null) {
                    for (final Player tgtP : saTargeting.getTargets().getTargetPlayers()) {
                        return doXMath(tgtP.getLife(), m, c);
                    }
                }
            }
        }

        if (sq[0].contains("LifeYouLostThisTurn")) {
            return doXMath(cc.getLifeLostThisTurn(), m, c);
        }
        if (sq[0].contains("LifeYouGainedThisTurn")) {
            return doXMath(cc.getLifeGainedThisTurn(), m, c);
        }
        if (sq[0].contains("LifeOppsLostThisTurn")) {
            int lost = 0;
            for (Player opp : cc.getOpponents()) {
                lost += opp.getLifeLostThisTurn();
            }
            return doXMath(lost, m, c);
        }

        if (sq[0].equals("TotalDamageDoneByThisTurn")) {
            return doXMath(c.getTotalDamageDoneBy(), m, c);
        }
        if (sq[0].equals("TotalDamageReceivedThisTurn")) {
            return doXMath(c.getTotalDamageRecievedThisTurn(), m, c);
        }

        if (sq[0].contains("YourPoisonCounters")) {
            return doXMath(cc.getPoisonCounters(), m, c);
        }
        if (sq[0].contains("TotalOppPoisonCounters")) {
            return doXMath(cc.getOpponentsTotalPoisonCounters(), m, c);
        }

        if (sq[0].contains("YourDamageThisTurn")) {
            return doXMath(cc.getAssignedDamage(), m, c);
        }
        if (sq[0].contains("TotalOppDamageThisTurn")) {
            return doXMath(cc.getOpponentsAssignedDamage(), m, c);
        }
        if (sq[0].contains("MaxOppDamageThisTurn")) {
            return doXMath(cc.getMaxOpponentAssignedDamage(), m, c);
        }

        // Count$YourTypeDamageThisTurn Type
        if (sq[0].contains("YourTypeDamageThisTurn")) {
            return doXMath(cc.getAssignedDamage(sq[0].split(" ")[1]), m, c);
        }
        if (sq[0].contains("YourDamageSourcesThisTurn")) {
            Iterable<Card> allSrc = cc.getAssignedDamageSources();
            String restriction = sq[0].split(" ")[1];
            CardCollection filtered = CardLists.getValidCards(allSrc, restriction, cc, c);
            return doXMath(filtered.size(), m, c);
        }

        if (sq[0].contains("YourLandsPlayed")) {
            return doXMath(cc.getLandsPlayedThisTurn(), m, c);
        }

        // Count$TopOfLibraryCMC
        if (sq[0].contains("TopOfLibraryCMC")) {
            final Card topCard = cc.getCardsIn(ZoneType.Library).getFirst();
            return doXMath(topCard == null ? 0 : topCard.getCMC(), m, c);
        }

        // Count$EnchantedControllerCreatures
        if (sq[0].contains("EnchantedControllerCreatures")) {
            if (c.getEnchantingCard() != null) {
                return CardLists.count(c.getEnchantingCard().getController().getCardsIn(ZoneType.Battlefield),
                        CardPredicates.Presets.CREATURES);
            }
            return 0;
        }

        // Count$MonstrosityMagnitude
        if (sq[0].contains("MonstrosityMagnitude")) {
            return doXMath(c.getMonstrosityNum(), m, c);
        }

        // Count$Chroma.<color name>
        // Count$Devotion.<color name>
        if (sq[0].contains("Chroma") || sq[0].equals("Devotion")) {
            ZoneType sourceZone = sq[0].contains("ChromaInGrave") ? ZoneType.Graveyard : ZoneType.Battlefield;
            String colorName = sq[1];
            if (colorName.contains("Chosen")) {
                colorName = MagicColor.toShortString(c.getChosenColor());
            }
            final CardCollectionView cards;
            if (sq[0].contains("ChromaSource")) { // Runs Chroma for passed in Source card
                cards = new CardCollection(c);
            } else {
                cards = cc.getCardsIn(sourceZone);
            }

            int colorOcurrencices = 0;
            byte colorCode = MagicColor.fromName(colorName);
            for (Card c0 : cards) {
                for (ManaCostShard sh : c0.getManaCost()) {
                    if ((sh.getColorMask() & colorCode) != 0)
                        colorOcurrencices++;
                }
            }
            return doXMath(colorOcurrencices, m, c);
        }
        // Count$DevotionDual.<color name>.<color name>
        if (sq[0].contains("DevotionDual")) {
            int colorOcurrencices = 0;
            byte color1 = MagicColor.fromName(sq[1]);
            byte color2 = MagicColor.fromName(sq[2]);
            for (Card c0 : cc.getCardsIn(ZoneType.Battlefield)) {
                for (ManaCostShard sh : c0.getManaCost()) {
                    if ((sh.getColorMask() & (color1 | color2)) != 0) {
                        colorOcurrencices++;
                    }
                }
            }
            return doXMath(colorOcurrencices, m, c);
        }

        if (sq[0].contains("Hellbent")) {
            return doXMath(Integer.parseInt(sq[cc.hasHellbent() ? 1 : 2]), m, c);
        }
        if (sq[0].contains("Metalcraft")) {
            return doXMath(Integer.parseInt(sq[cc.hasMetalcraft() ? 1 : 2]), m, c);
        }
        if (sq[0].contains("FatefulHour")) {
            return doXMath(Integer.parseInt(sq[cc.getLife() <= 5 ? 1 : 2]), m, c);
        }

        if (sq[0].contains("Landfall")) {
            return doXMath(Integer.parseInt(sq[cc.hasLandfall() ? 1 : 2]), m, c);
        }
        if (sq[0].contains("Threshold")) {
            return doXMath(Integer.parseInt(sq[cc.hasThreshold() ? 1 : 2]), m, c);
        }
        if (sq[0].startsWith("Kicked")) {
            return doXMath(Integer.parseInt(sq[c.getKickerMagnitude() > 0 ? 1 : 2]), m, c);
        }
        if (sq[0].startsWith("AltCost")) {
            return doXMath(Integer.parseInt(sq[c.isOptionalCostPaid(OptionalCost.AltCost) ? 1 : 2]), m, c);
        }

        // Count$wasCastFrom<Zone>.<true>.<false>
        if (sq[0].startsWith("wasCastFrom")) {
            boolean zonesMatch = c.getCastFrom() == ZoneType.smartValueOf(sq[0].substring(11));
            return doXMath(Integer.parseInt(sq[zonesMatch ? 1 : 2]), m, c);
        }

        if (sq[0].startsWith("Devoured")) {
            final String validDevoured = l[0].split(" ")[1];
            CardCollection cl = CardLists.getValidCards(c.getDevoured(), validDevoured.split(","), cc, c);
            return doXMath(cl.size(), m, c);
        }

        if (sq[0].contains("CardPower")) {
            return doXMath(c.getNetPower(), m, c);
        }
        if (sq[0].contains("CardToughness")) {
            return doXMath(c.getNetToughness(), m, c);
        }
        if (sq[0].contains("CardSumPT")) {
            return doXMath((c.getNetPower() + c.getNetToughness()), m, c);
        }

        // Count$SumPower_valid
        if (sq[0].contains("SumPower")) {
            final String[] restrictions = l[0].split("_");
            final String[] rest = restrictions[1].split(",");
            CardCollection filteredCards = CardLists.getValidCards(cc.getGame().getCardsIn(ZoneType.Battlefield),
                    rest, cc, c);
            return doXMath(Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetNetPower), m, c);
        }
        // Count$CardManaCost
        if (sq[0].contains("CardManaCost")) {
            Card ce;
            if (sq[0].contains("Equipped") && c.isEquipping()) {
                ce = c.getEquipping();
            } else if (sq[0].contains("Remembered")) {
                ce = (Card) c.getFirstRemembered();
            } else {
                ce = c;
            }

            return doXMath(ce == null ? 0 : ce.getCMC(), m, c);
        }
        // Count$SumCMC_valid
        if (sq[0].contains("SumCMC")) {
            final String[] restrictions = l[0].split("_");
            final String[] rest = restrictions[1].split(",");
            CardCollectionView cardsonbattlefield = game.getCardsIn(ZoneType.Battlefield);
            CardCollection filteredCards = CardLists.getValidCards(cardsonbattlefield, rest, cc, c);
            return Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetCmc);
        }

        if (sq[0].contains("CardNumColors")) {
            return doXMath(CardUtil.getColors(c).countColors(), m, c);
        }
        if (sq[0].contains("ChosenNumber")) {
            return doXMath(c.getChosenNumber(), m, c);
        }
        if (sq[0].contains("CardCounters")) {
            // CardCounters.ALL to be used for Kinsbaile Borderguard and anything that cares about all counters
            int count = 0;
            if (sq[1].equals("ALL")) {
                for (Integer i : c.getCounters().values()) {
                    if (i != null && i > 0) {
                        count += i;
                    }
                }
            } else {
                count = c.getCounters(CounterType.getType(sq[1]));
            }
            return doXMath(count, m, c);
        }

        // Count$TotalCounters.<counterType>_<valid>
        if (sq[0].contains("TotalCounters")) {
            final String[] restrictions = l[0].split("_");
            final CounterType cType = CounterType.getType(restrictions[1]);
            final String[] validFilter = restrictions[2].split(",");
            CardCollectionView validCards = game.getCardsIn(ZoneType.Battlefield);
            validCards = CardLists.getValidCards(validCards, validFilter, cc, c);
            int cCount = 0;
            for (final Card card : validCards) {
                cCount += card.getCounters(cType);
            }
            return doXMath(cCount, m, c);
        }

        if (sq[0].contains("CardTypes")) {
            return doXMath(getCardTypesFromList(game.getCardsIn(ZoneType.smartValueOf(sq[1]))), m, c);
        }

        if (sq[0].contains("BushidoPoint")) {
            return doXMath(c.getKeywordMagnitude("Bushido"), m, c);
        }
        if (sq[0].contains("TimesKicked")) {
            return doXMath(c.getKickerMagnitude(), m, c);
        }
        if (sq[0].contains("TimesPseudokicked")) {
            return doXMath(c.getPseudoKickerMagnitude(), m, c);
        }

        // Count$IfMainPhase.<numMain>.<numNotMain> // 7/10
        if (sq[0].contains("IfMainPhase")) {
            final PhaseHandler cPhase = cc.getGame().getPhaseHandler();
            final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.getPlayerTurn().equals(cc);
            return doXMath(Integer.parseInt(sq[isMyMain ? 1 : 2]), m, c);
        }

        // Count$ThisTurnEntered <ZoneDestination> [from <ZoneOrigin>] <Valid>
        if (sq[0].contains("ThisTurnEntered")) {
            final String[] workingCopy = l[0].split("_");

            ZoneType destination = ZoneType.smartValueOf(workingCopy[1]);
            final boolean hasFrom = workingCopy[2].equals("from");
            ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null;
            String validFilter = workingCopy[hasFrom ? 4 : 2];

            final CardCollection res = CardUtil.getThisTurnEntered(destination, origin, validFilter, c);
            if (origin == null) { // Remove cards on the battlefield that changed controller
                CardCollectionView sameDest = CardUtil.getThisTurnEntered(destination, destination, validFilter, c);
                res.removeAll(sameDest);
            }
            return doXMath(res.size(), m, c);
        }

        // Count$LastTurnEntered <ZoneDestination> [from <ZoneOrigin>] <Valid>
        if (sq[0].contains("LastTurnEntered")) {
            final String[] workingCopy = l[0].split("_");

            ZoneType destination = ZoneType.smartValueOf(workingCopy[1]);
            final boolean hasFrom = workingCopy[2].equals("from");
            ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null;
            String validFilter = workingCopy[hasFrom ? 4 : 2];

            final CardCollection res = CardUtil.getLastTurnEntered(destination, origin, validFilter, c);
            if (origin == null) { // Remove cards on the battlefield that changed controller
                CardCollectionView sameDest = CardUtil.getLastTurnEntered(destination, destination, validFilter, c);
                res.removeAll(sameDest);
            }
            return doXMath(res.size(), m, c);
        }

        // Count$AttackersDeclared
        if (sq[0].contains("AttackersDeclared")) {
            return doXMath(cc.getAttackersDeclaredThisTurn(), m, c);
        }

        // Count$ThisTurnCast <Valid>
        // Count$LastTurnCast <Valid>
        if (sq[0].contains("ThisTurnCast") || sq[0].contains("LastTurnCast")) {

            final String[] workingCopy = l[0].split("_");
            final String validFilter = workingCopy[1];

            CardCollection res;
            if (workingCopy[0].contains("This")) {
                res = CardUtil.getThisTurnCast(validFilter, c);
            } else {
                res = CardUtil.getLastTurnCast(validFilter, c);
            }

            final int ret = doXMath(res.size(), m, c);
            return ret;
        }

        // Count$Morbid.<True>.<False>
        if (sq[0].startsWith("Morbid")) {
            final CardCollection res = CardUtil.getThisTurnEntered(ZoneType.Graveyard, ZoneType.Battlefield,
                    "Creature", c, true);
            if (res.size() > 0) {
                return doXMath(Integer.parseInt(sq[1]), m, c);
            }
            return doXMath(Integer.parseInt(sq[2]), m, c);
        }

        if (sq[0].equals("YourTurns")) {
            return doXMath(cc.getTurn(), m, c);
        }

        if (sq[0].equals("TotalTurns")) {
            // Sorry for the Singleton use, replace this once this function has game passed into it
            return doXMath(game.getPhaseHandler().getTurn(), m, c);
        }

        //Count$Random.<Min>.<Max>
        if (sq[0].equals("Random")) {
            int min = StringUtils.isNumeric(sq[1]) ? Integer.parseInt(sq[1]) : xCount(c, c.getSVar(sq[1]));
            int max = StringUtils.isNumeric(sq[2]) ? Integer.parseInt(sq[2]) : xCount(c, c.getSVar(sq[2]));

            return forge.util.MyRandom.getRandom().nextInt(1 + max - min) + min;
        }

        // Count$Domain
        if (sq[0].startsWith("Domain")) {
            int n = 0;
            Player neededPlayer = sq[0].equals("DomainActivePlayer") ? activePlayer : cc;
            CardCollection someCards = CardLists.filter(neededPlayer.getCardsIn(ZoneType.Battlefield),
                    Presets.LANDS);
            for (String basic : MagicColor.Constant.BASIC_LANDS) {
                if (!CardLists.getType(someCards, basic).isEmpty()) {
                    n++;
                }
            }
            return doXMath(n, m, c);
        }
        // Count$Converge
        if (sq[0].contains("Converge")) {
            return doXMath(c.getSunburstValue(), m, c);
        }
        // Count$ColoredCreatures *a DOMAIN for creatures*
        if (sq[0].contains("ColoredCreatures")) {
            int mask = 0;
            CardCollection someCards = CardLists.filter(cc.getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
            for (final Card crd : someCards) {
                mask |= CardUtil.getColors(crd).getColor();
            }
            return doXMath(ColorSet.fromMask(mask).countColors(), m, c);
        }

        // Count$CardMulticolor.<numMC>.<numNotMC>
        if (sq[0].contains("CardMulticolor")) {
            final boolean isMulti = CardUtil.getColors(c).isMulticolor();
            return doXMath(Integer.parseInt(sq[isMulti ? 1 : 2]), m, c);
        }

        // Complex counting methods
        CardCollectionView someCards = getCardListForXCount(c, cc, sq);

        // 1/10 - Count$MaxCMCYouCtrl
        if (sq[0].contains("MaxCMC")) {
            int mmc = Aggregates.max(someCards, CardPredicates.Accessors.fnGetCmc);
            return doXMath(mmc, m, c);
        }

        return doXMath(someCards.size(), m, c);
    }

    private static CardCollectionView getCardListForXCount(final Card c, final Player cc, final String[] sq) {
        final List<Player> opps = cc.getOpponents();
        CardCollection someCards = new CardCollection();
        final Game game = c.getGame();

        // Generic Zone-based counting
        // Count$QualityAndZones.Subquality

        // build a list of cards in each possible specified zone

        if (sq[0].contains("YouCtrl")) {
            someCards.addAll(cc.getCardsIn(ZoneType.Battlefield));
        }

        if (sq[0].contains("InYourYard")) {
            someCards.addAll(cc.getCardsIn(ZoneType.Graveyard));
        }

        if (sq[0].contains("InYourLibrary")) {
            someCards.addAll(cc.getCardsIn(ZoneType.Library));
        }

        if (sq[0].contains("InYourHand")) {
            someCards.addAll(cc.getCardsIn(ZoneType.Hand));
        }

        if (sq[0].contains("InYourSideboard")) {
            someCards.addAll(cc.getCardsIn(ZoneType.Sideboard));
        }

        if (sq[0].contains("OppCtrl")) {
            for (final Player p : opps) {
                someCards.addAll(p.getZone(ZoneType.Battlefield).getCards());
            }
        }

        if (sq[0].contains("InOppYard")) {
            for (final Player p : opps) {
                someCards.addAll(p.getCardsIn(ZoneType.Graveyard));
            }
        }

        if (sq[0].contains("InOppHand")) {
            for (final Player p : opps) {
                someCards.addAll(p.getCardsIn(ZoneType.Hand));
            }
        }

        if (sq[0].contains("InChosenHand")) {
            if (c.getChosenPlayer() != null) {
                someCards.addAll(c.getChosenPlayer().getCardsIn(ZoneType.Hand));
            }
        }

        if (sq[0].contains("InChosenYard")) {
            if (c.getChosenPlayer() != null) {
                someCards.addAll(c.getChosenPlayer().getCardsIn(ZoneType.Graveyard));
            }
        }

        if (sq[0].contains("OnBattlefield")) {
            someCards.addAll(game.getCardsIn(ZoneType.Battlefield));
        }

        if (sq[0].contains("InAllYards")) {
            someCards.addAll(game.getCardsIn(ZoneType.Graveyard));
        }

        if (sq[0].contains("SpellsOnStack")) {
            someCards.addAll(game.getCardsIn(ZoneType.Stack));
        }

        if (sq[0].contains("InAllHands")) {
            someCards.addAll(game.getCardsIn(ZoneType.Hand));
        }

        //  Count$InTargetedHand (targeted player's cards in hand)
        if (sq[0].contains("InTargetedHand")) {
            for (final SpellAbility sa : c.getCurrentState().getNonManaAbilities()) {
                final SpellAbility saTargeting = sa.getSATargetingPlayer();
                if (saTargeting != null) {
                    for (final Player tgtP : saTargeting.getTargets().getTargetPlayers()) {
                        someCards.addAll(tgtP.getCardsIn(ZoneType.Hand));
                    }
                }
            }
        }

        //  Count$InTargetedHand (targeted player's cards in hand)
        if (sq[0].contains("InEnchantedHand")) {
            GameEntity o = c.getEnchanting();
            Player controller = null;
            if (o instanceof Card) {
                controller = ((Card) o).getController();
            } else {
                controller = (Player) o;
            }
            if (controller != null) {
                someCards.addAll(controller.getCardsIn(ZoneType.Hand));
            }
        }
        if (sq[0].contains("InEnchantedYard")) {
            GameEntity o = c.getEnchanting();
            Player controller = null;
            if (o instanceof Card) {
                controller = ((Card) o).getController();
            } else {
                controller = (Player) o;
            }
            if (controller != null) {
                someCards.addAll(controller.getCardsIn(ZoneType.Graveyard));
            }
        }

        // filter lists based on the specified quality

        // "Clerics you control" - Count$TypeYouCtrl.Cleric
        if (sq[0].contains("Type")) {
            someCards = CardLists.filter(someCards, CardPredicates.isType(sq[1]));
        }

        // "Named <CARDNAME> in all graveyards" - Count$NamedAllYards.<CARDNAME>

        if (sq[0].contains("Named")) {
            if (sq[1].equals("CARDNAME")) {
                sq[1] = c.getName();
            }
            someCards = CardLists.filter(someCards, CardPredicates.nameEquals(sq[1]));
        }

        // Refined qualities

        // "Untapped Lands" - Count$UntappedTypeYouCtrl.Land
        // if (sq[0].contains("Untapped")) { someCards = CardLists.filter(someCards, Presets.UNTAPPED); }

        // if (sq[0].contains("Tapped")) { someCards = CardLists.filter(someCards, Presets.TAPPED); }

        //        String sq0 = sq[0].toLowerCase();
        //        for (String color : MagicColor.Constant.ONLY_COLORS) {
        //            if (sq0.contains(color))
        //                someCards = someCards.filter(CardListFilter.WHITE);
        //        }
        // "White Creatures" - Count$WhiteTypeYouCtrl.Creature
        // if (sq[0].contains("White")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.WHITE));
        // if (sq[0].contains("Blue"))  someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.BLUE));
        // if (sq[0].contains("Black")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.BLACK));
        // if (sq[0].contains("Red"))   someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.RED));
        // if (sq[0].contains("Green")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.GREEN));

        if (sq[0].contains("Multicolor")) {
            someCards = CardLists.filter(someCards, new Predicate<Card>() {
                @Override
                public boolean apply(final Card c) {
                    return CardUtil.getColors(c).isMulticolor();
                }
            });
        }

        if (sq[0].contains("Monocolor")) {
            someCards = CardLists.filter(someCards, new Predicate<Card>() {
                @Override
                public boolean apply(final Card c) {
                    return CardUtil.getColors(c).isMonoColor();
                }
            });
        }
        return someCards;
    }

    public static int doXMath(final int num, final String operators, final Card c) {
        if (operators == null || operators.equals("none")) {
            return num;
        }

        final String[] s = operators.split("\\.");
        int secondaryNum = 0;

        try {
            if (s.length == 2) {
                secondaryNum = Integer.parseInt(s[1]);
            }
        } catch (final Exception e) {
            secondaryNum = xCount(c, c.getSVar(s[1]));
        }

        if (s[0].contains("Plus")) {
            return num + secondaryNum;
        } else if (s[0].contains("NMinus")) {
            return secondaryNum - num;
        } else if (s[0].contains("Minus")) {
            return num - secondaryNum;
        } else if (s[0].contains("Twice")) {
            return num * 2;
        } else if (s[0].contains("Thrice")) {
            return num * 3;
        } else if (s[0].contains("HalfUp")) {
            return (int) (Math.ceil(num / 2.0));
        } else if (s[0].contains("HalfDown")) {
            return (int) (Math.floor(num / 2.0));
        } else if (s[0].contains("ThirdUp")) {
            return (int) (Math.ceil(num / 3.0));
        } else if (s[0].contains("ThirdDown")) {
            return (int) (Math.floor(num / 3.0));
        } else if (s[0].contains("Negative")) {
            return num * -1;
        } else if (s[0].contains("Times")) {
            return num * secondaryNum;
        } else if (s[0].contains("DivideEvenlyDown")) {
            if (secondaryNum == 0) {
                return 0;
            } else {
                return num / secondaryNum;
            }
        } else if (s[0].contains("Mod")) {
            return num % secondaryNum;
        } else if (s[0].contains("Abs")) {
            return Math.abs(num);
        } else if (s[0].contains("LimitMax")) {
            if (num < secondaryNum) {
                return num;
            } else {
                return secondaryNum;
            }
        } else if (s[0].contains("LimitMin")) {
            if (num > secondaryNum) {
                return num;
            } else {
                return secondaryNum;
            }

        } else {
            return num;
        }
    }

    /**
     * <p>
     * handlePaid.
     * </p>
     * 
     * @param paidList
     *            a {@link forge.CardList} object.
     * @param string
     *            a {@link java.lang.String} object.
     * @param source
     *            a {@link forge.game.card.Card} object.
     * @return a int.
     */
    public static int handlePaid(final CardCollectionView paidList, final String string, final Card source) {
        if (paidList == null) {
            if (string.contains(".")) {
                final String[] splitString = string.split("\\.", 2);
                return doXMath(0, splitString[1], source);
            } else {
                return 0;
            }
        }
        if (string.startsWith("Amount")) {
            if (string.contains(".")) {
                final String[] splitString = string.split("\\.", 2);
                return doXMath(paidList.size(), splitString[1], source);
            } else {
                return paidList.size();
            }

        }
        if (string.startsWith("Valid")) {

            final String[] splitString = string.split("/", 2);
            String valid = splitString[0].substring(6);
            final CardCollection list = CardLists.getValidCards(paidList, valid, source.getController(), source);
            return doXMath(list.size(), splitString.length > 1 ? splitString[1] : null, source);
        }

        String filteredString = string;
        CardCollection filteredList = new CardCollection(paidList);
        final String[] filter = filteredString.split("_");

        if (string.startsWith("FilterControlledBy")) {
            final String pString = filter[0].substring(18);
            FCollectionView<Player> controllers = AbilityUtils.getDefinedPlayers(source, pString, null);
            filteredList = CardLists.filterControlledBy(filteredList, controllers);
            filteredString = filteredString.replace(pString, "");
            filteredString = filteredString.replace("FilterControlledBy_", "");
        }

        int tot = 0;
        for (final Card c : filteredList) {
            tot += xCount(c, filteredString);
        }

        return tot;
    }

    /**
     * <p>
     * isMostProminentColor.
     * </p>
     * 
     * @param list
     *            a {@link forge.CardList} object.
     * @return a boolean.
     */
    public static byte getMostProminentColors(final Iterable<Card> list) {
        int cntColors = MagicColor.WUBRG.length;
        final Integer[] map = new Integer[cntColors];
        for (int i = 0; i < cntColors; i++) {
            map[i] = 0;
        }

        for (final Card crd : list) {
            ColorSet color = CardUtil.getColors(crd);
            for (int i = 0; i < cntColors; i++) {
                if (color.hasAnyColor(MagicColor.WUBRG[i]))
                    map[i]++;
            }
        } // for

        byte mask = 0;
        int nMax = -1;
        for (int i = 0; i < cntColors; i++) {
            if (map[i] > nMax)
                mask = MagicColor.WUBRG[i];
            else if (map[i] == nMax)
                mask |= MagicColor.WUBRG[i];
            else
                continue;
            nMax = map[i];
        }
        return mask;
    }

    /**
     * <p>
     * SortColorsFromList.
     * </p>
     * 
     * @param list
     *            a {@link forge.CardList} object.
     * @return a List.
     */
    public static int[] SortColorsFromList(final CardCollection list) {
        int cntColors = MagicColor.WUBRG.length;
        final int[] map = new int[cntColors];
        for (int i = 0; i < cntColors; i++) {
            map[i] = 0;
        }

        for (final Card crd : list) {
            ColorSet color = CardUtil.getColors(crd);
            for (int i = 0; i < cntColors; i++) {
                if (color.hasAnyColor(MagicColor.WUBRG[i]))
                    map[i]++;
            }
        } // for
        Arrays.sort(map);
        return map;
    }

    /**
     * <p>
     * getMostProminentColorsFromList.
     * </p>
     * 
     * @param list
     *            a {@link forge.CardList} object.
     * @return a boolean.
     */
    public static byte getMostProminentColorsFromList(final CardCollectionView list,
            final List<String> restrictedToColors) {
        List<Byte> colorRestrictions = new ArrayList<Byte>();
        for (final String col : restrictedToColors) {
            colorRestrictions.add(MagicColor.fromName(col));
        }
        int cntColors = colorRestrictions.size();
        final Integer[] map = new Integer[cntColors];
        for (int i = 0; i < cntColors; i++) {
            map[i] = 0;
        }

        for (final Card crd : list) {
            ColorSet color = CardUtil.getColors(crd);
            for (int i = 0; i < cntColors; i++) {
                if (color.hasAnyColor(colorRestrictions.get(i))) {
                    map[i]++;
                }
            }
        }

        byte mask = 0;
        int nMax = -1;
        for (int i = 0; i < cntColors; i++) {
            if (map[i] > nMax)
                mask = colorRestrictions.get(i);
            else if (map[i] == nMax)
                mask |= colorRestrictions.get(i);
            else
                continue;
            nMax = map[i];
        }
        return mask;
    }

    /**
     * <p>
     * getMostProminentCreatureType.
     * </p>
     * 
     * @param list
     *            a {@link forge.CardList} object.
     * @return an int.
     */
    public static int getMostProminentCreatureTypeSize(final CardCollection list) {
        if (list.isEmpty()) {
            return 0;
        }
        int allCreatureType = 0;

        final Map<String, Integer> map = new HashMap<String, Integer>();
        for (final Card c : list) {
            // Remove Duplicated types
            final Set<String> creatureTypes = c.getType().getCreatureTypes();
            for (String creatureType : creatureTypes) {
                if (creatureType.equals("AllCreatureTypes")) {
                    allCreatureType++;
                } else {
                    Integer count = map.get(creatureType);
                    map.put(creatureType, count == null ? 1 : count + 1);
                }
            }
        }

        int max = 0;
        for (final Entry<String, Integer> entry : map.entrySet()) {
            if (max < entry.getValue()) {
                max = entry.getValue();
            }
        }
        return max + allCreatureType;
    }

    /**
     * <p>
     * sharedKeywords.
     * </p>
     * 
     * @param kw
     *            a {@link forge.CardList} object.
     * @return a List<String>.
     */
    public static List<String> sharedKeywords(final String[] kw, final String[] restrictions,
            final List<ZoneType> zones, final Card host) {
        final List<String> filteredkw = new ArrayList<String>();
        final Player p = host.getController();
        CardCollection cardlist = new CardCollection(p.getGame().getCardsIn(zones));
        final List<String> landkw = new ArrayList<String>();
        final List<String> protectionkw = new ArrayList<String>();
        final List<String> allkw = new ArrayList<String>();

        cardlist = CardLists.getValidCards(cardlist, restrictions, p, host);
        for (Card c : cardlist) {
            for (String k : c.getKeywords()) {
                if (k.endsWith("walk")) {
                    if (!landkw.contains(k)) {
                        landkw.add(k);
                    }
                } else if (k.startsWith("Protection")) {
                    if (!protectionkw.contains(k)) {
                        protectionkw.add(k);
                    }
                }
                if (!allkw.contains(k)) {
                    allkw.add(k);
                }
            }
        }
        for (String keyword : kw) {
            if (keyword.equals("Protection")) {
                filteredkw.addAll(protectionkw);
            } else if (keyword.equals("Landwalk")) {
                filteredkw.addAll(landkw);
            } else if (allkw.contains(keyword)) {
                filteredkw.add(keyword);
            }
        }
        return filteredkw;
    }

    public static int getCardTypesFromList(final CardCollectionView list) {
        EnumSet<CardType.CoreType> types = EnumSet.noneOf(CardType.CoreType.class);
        for (Card c1 : list) {
            Iterables.addAll(types, c1.getType().getCoreTypes());
        }
        return types.size();
    }

    public static List<SpellAbility> getBushidoEffects(final Card c) {
        final List<SpellAbility> list = new ArrayList<SpellAbility>();
        for (final String kw : c.getKeywords()) {
            if (kw.contains("Bushido")) {
                final String[] parse = kw.split(" ");
                final String s = parse[1];
                final int magnitude = Integer.parseInt(s);

                String description = String.format(
                        "Bushido %d (When this blocks or becomes blocked, it gets +%d/+%d until end of turn).",
                        magnitude, magnitude, magnitude);
                String regularPart = String.format(
                        "AB$ Pump | Cost$ 0 | Defined$ CardUID_%d | NumAtt$ +%d | NumDef$ +%d | StackDescription$ %s",
                        c.getId(), magnitude, magnitude, description);

                SpellAbility ability = AbilityFactory.getAbility(regularPart, c);
                ability.setDescription(ability.getStackDescription());
                ability.setTrigger(true); // can be copied by Strionic Resonator
                list.add(ability);
            }
        }
        return list;
    }

    /**
     * <p>
     * getNeededXDamage.
     * </p>
     * 
     * @param ability
     *            a {@link forge.game.spellability.SpellAbility} object.
     * @return a int.
     */
    public static int getNeededXDamage(final SpellAbility ability) {
        // when targeting a creature, make sure the AI won't overkill on X
        // damage
        final Card target = ability.getTargetCard();
        int neededDamage = -1;

        if ((target != null)) {
            neededDamage = target.getNetToughness() - target.getDamage();
        }

        return neededDamage;
    }

    public static void correctAbilityChainSourceCard(final SpellAbility sa, final Card card) {
        sa.setHostCard(card);

        if (sa.getSubAbility() != null) {
            correctAbilityChainSourceCard(sa.getSubAbility(), card);
        }
    }

    /**
     * Adds the ability factory abilities.
     * 
     * @param card
     *            the card
     */
    public static final void addAbilityFactoryAbilities(final Card card) {
        // **************************************************
        // AbilityFactory cards
        for (String rawAbility : card.getUnparsedAbilities()) {
            final SpellAbility intrinsicAbility = AbilityFactory.getAbility(rawAbility, card);
            card.addSpellAbility(intrinsicAbility);
            intrinsicAbility.setIntrinsic(true);
        }
    }
    /*
    public static final void addCommanderAbilities(final Card cmd) {
    ReplacementEffect re = ReplacementHandler.parseReplacement(
            "Event$ Moved | Destination$ Graveyard,Exile | ValidCard$ Card.Self | Secondary$ True | Optional$ True | OptionalDecider$ You | ReplaceWith$ CommanderMoveReplacement | " +
            "Description$ If a commander would be put into its owner's graveyard or exile from anywhere, that player may put it into the command zone instead.",
            cmd, true);
    cmd.addReplacementEffect(re);
    if (StringUtils.isBlank(cmd.getSVar("CommanderCostRaise"))) // why condition check is needed?
        cmd.setSVar("CommanderCostRaise", "Number$0");
        
    String cmdManaCost = cmd.getManaCost().toString();
    cmd.setSVar("CommanderMoveReplacement", "DB$ ChangeZone | Origin$ Battlefield,Graveyard,Exile,Library | Destination$ Command | Defined$ ReplacedCard");
    cmd.setSVar("DBCommanderIncCast", "DB$ StoreSVar | SVar$ CommanderCostRaise | Type$ CountSVar | Expression$ CommanderCostRaise/Plus.2");
    SpellAbility sa = AbilityFactory.getAbility("SP$ PermanentCreature | SorcerySpeed$ True | ActivationZone$ Command | SubAbility$ DBCommanderIncCast | Cost$ " + cmdManaCost, cmd);
    cmd.addSpellAbility(sa);
        
    cmd.addIntrinsicAbility("SP$ PermanentCreature | SorcerySpeed$ True | ActivationZone$ Command | SubAbility$ DBCommanderIncCast | Cost$ " + cmdManaCost);
    cmd.addStaticAbility("Mode$ RaiseCost | Amount$ CommanderCostRaise | Type$ Spell | ValidCard$ Card.Self+wasCastFromCommand | EffectZone$ All | AffectedZone$ Stack");
    }
    */

    /**
     * <p>
     * postFactoryKeywords.
     * </p>
     * 
     * @param card
     *            a {@link forge.game.card.Card} object.
     */
    public static void setupKeywordedAbilities(final Card card) {
        // this function should handle any keywords that need to be added after
        // a spell goes through the factory
        // Cards with Cycling abilities
        // -1 means keyword "Cycling" not found

        for (String keyword : card.getKeywords()) {
            if (keyword.startsWith("Multikicker")) {
                final String[] k = keyword.split("kicker ");
                String mkCost = k[1].split(":")[0];
                final SpellAbility sa = card.getFirstSpellAbility();
                sa.setMultiKickerManaCost(new ManaCost(new ManaCostParser(mkCost)));
                if (k[1].endsWith("Generic")) {
                    sa.addAnnounceVar("Pseudo-multikicker");
                } else {
                    sa.addAnnounceVar("Multikicker");
                }
            } else if (keyword.startsWith("Replicate")) {
                card.getFirstSpellAbility().addAnnounceVar("Replicate");
            } else if (keyword.startsWith("Fuse")) {
                card.getState(CardStateName.Original).addNonManaAbility(AbilityFactory.buildFusedAbility(card));
            } else if (keyword.startsWith("Evoke")) {
                card.addSpellAbility(makeEvokeSpell(card, keyword));
            } else if (keyword.startsWith("Dash")) {
                card.addSpellAbility(makeDashSpell(card, keyword));
            } else if (keyword.startsWith("Awaken")) {
                card.addSpellAbility(makeAwakenSpell(card, keyword));
            } else if (keyword.startsWith("Monstrosity")) {
                final String[] k = keyword.split(":");
                final String magnitude = k[0].substring(12);
                final String manacost = k[1];
                card.removeIntrinsicKeyword(keyword);

                String ref = "X".equals(magnitude) ? " | References$ X" : "";
                String counters = StringUtils.isNumeric(magnitude)
                        ? Lang.nounWithNumeral(Integer.parseInt(magnitude), "+1/+1 counter")
                        : "X +1/+1 counters";
                String effect = "AB$ PutCounter | Cost$ " + manacost + " | ConditionPresent$ "
                        + "Card.Self+IsNotMonstrous | Monstrosity$ True | CounterNum$ " + magnitude
                        + " | CounterType$ P1P1 | SpellDescription$ Monstrosity " + magnitude
                        + " (If this creature isn't monstrous, put " + counters
                        + " on it and it becomes monstrous.) | StackDescription$ SpellDescription" + ref;

                card.addSpellAbility(AbilityFactory.getAbility(effect, card));
                // add ability to instrinic strings so copies/clones create the ability also
                card.getCurrentState().addUnparsedAbility(effect);
            } else if (keyword.startsWith("Unearth")) {
                card.removeIntrinsicKeyword(keyword);

                final String[] k = keyword.split(":");
                final String manacost = k[1];

                String effect = "AB$ ChangeZone | Cost$ " + manacost + " | Defined$ Self"
                        + " | Origin$ Graveyard | Destination$ Battlefield | SorcerySpeed$"
                        + " True | ActivationZone$ Graveyard | Unearth$ True | SubAbility$"
                        + " UnearthPumpSVar | PrecostDesc$ Unearth | StackDescription$ "
                        + "Unearth: Return CARDNAME to the battlefield. | SpellDescription$" + " ("
                        + ManaCostParser.parse(manacost) + ": Return CARDNAME to the "
                        + "battlefield. It gains haste. Exile it at the beginning of the next"
                        + " end step or if it would leave the battlefield. Unearth only as a sorcery.)";
                String dbpump = "DB$ Pump | Defined$ Self | KW$ Haste & HIDDEN If CARDNAME"
                        + " would leave the battlefield, exile it instead of putting it "
                        + "anywhere else. | Permanent$ True | SubAbility$ UnearthDelayTrigger";
                String delTrig = "DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn"
                        + " | Execute$ UnearthTrueDeath | TriggerDescription$ Exile "
                        + "CARDNAME at the beginning of the next end step.";
                String truedeath = "DB$ ChangeZone | Defined$ Self | Origin$ Battlefield" + " | Destination$ Exile";
                card.setSVar("UnearthPumpSVar", dbpump);
                card.setSVar("UnearthDelayTrigger", delTrig);
                card.setSVar("UnearthTrueDeath", truedeath);
                card.addSpellAbility(AbilityFactory.getAbility(effect, card));
                // add ability to instrinic strings so copies/clones create the ability also
                card.getCurrentState().addUnparsedAbility(effect);
            } else if (keyword.startsWith("Level up")) {
                final String strMaxLevel = card.getSVar("maxLevel");
                card.removeIntrinsicKeyword(keyword);

                final String[] k = keyword.split(":");
                final String manacost = k[1];

                String effect = "AB$ PutCounter | Cost$ " + manacost + " | "
                        + "SorcerySpeed$ True | LevelUp$ True | CounterNum$ 1"
                        + " | CounterType$ LEVEL | PrecostDesc$ Level Up | MaxLevel$ " + strMaxLevel
                        + " | SpellDescription$ (Put a level counter on"
                        + " this permanent. Activate this ability only any time you" + " could cast a sorcery.)";

                card.addSpellAbility(AbilityFactory.getAbility(effect, card));
                // add ability to instrinic strings so copies/clones create the ability also
                card.getCurrentState().addUnparsedAbility(effect);
            } else if (keyword.startsWith("Cycling")) {
                card.removeIntrinsicKeyword(keyword);

                final String[] k = keyword.split(":");
                final String manacost = k[1];

                card.addSpellAbility(abilityCycle(card, manacost));
            } else if (keyword.startsWith("TypeCycling")) {
                card.removeIntrinsicKeyword(keyword);

                final String[] k = keyword.split(":");
                final String type = k[1];
                final String manacost = k[2];

                card.addSpellAbility(abilityTypecycle(card, manacost, type));
            } else if (keyword.startsWith("Transmute")) {
                card.removeIntrinsicKeyword(keyword);
                final String manacost = keyword.split(":")[1];
                final String sbTransmute = "AB$ ChangeZone | Cost$ " + manacost + " Discard<1/CARDNAME>"
                        + " | CostDesc$ Transmute " + ManaCostParser.parse(manacost) + " | ActivationZone$ Hand"
                        + " | Origin$ Library | Destination$ Hand | ChangeType$ Card.cmcEQ"
                        + card.getManaCost().getCMC()
                        + " | ChangeNum$ 1 | SorcerySpeed$ True | References$ TransmuteX | SpellDescription$ ("
                        + ManaCostParser.parse(manacost) + ", Discard this card: Search your library for a card "
                        + "with the same converted mana cost as the discarded card, reveal that card, "
                        + "and put it into your hand. Then shuffle your library. Activate this ability "
                        + "only any time you could cast a sorcery.)";
                final SpellAbility abTransmute = AbilityFactory.getAbility(sbTransmute, card);
                card.addSpellAbility(abTransmute);
                card.getCurrentState().addUnparsedAbility(sbTransmute);
            } else if (keyword.startsWith("Soulshift")) {
                final String[] k = keyword.split(" ");
                final int manacost = Integer.parseInt(k[1]);

                final String actualTrigger = "Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard"
                        + "| OptionalDecider$ You | ValidCard$ Card.Self | Execute$ SoulshiftAbility"
                        + "| TriggerController$ TriggeredCardController | TriggerDescription$ " + keyword
                        + " (When this creature dies, you may return target Spirit card with converted mana cost "
                        + manacost + " or less from your graveyard to your hand.)";
                final String abString = "DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand"
                        + "| ValidTgts$ Spirit.YouOwn+cmcLE" + manacost;
                final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, true);
                card.addTrigger(parsedTrigger);
                card.setSVar("SoulshiftAbility", abString);
            } else if (keyword.startsWith("Champion")) {
                card.removeIntrinsicKeyword(keyword);

                final String[] k = keyword.split(":");
                final String[] valid = k[1].split(",");
                String desc = k.length > 2 ? k[2] : k[1];
                String article = Lang.startsWithVowel(desc) ? "an" : "a";
                if (desc.equals("Creature")) {
                    desc = "creature"; //use lowercase for "Champion a creature"
                }

                StringBuilder changeType = new StringBuilder();
                for (String v : valid) {
                    if (changeType.length() != 0) {
                        changeType.append(",");
                    }
                    changeType.append(v).append(".YouCtrl+Other");
                }

                StringBuilder trig = new StringBuilder();
                trig.append("Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | ");
                trig.append("Execute$ ChampionAbility | TriggerDescription$ Champion ").append(article + " ");
                trig.append(desc)
                        .append(" (When this enters the battlefield, sacrifice it unless you exile another ");
                trig.append(desc).append(
                        " you control. When this leaves the battlefield, that card returns to the battlefield.)");

                StringBuilder trigReturn = new StringBuilder();
                trigReturn.append(
                        "Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | ");
                trigReturn.append(
                        "Execute$ ChampionReturn | Secondary$ True | TriggerDescription$ When this leaves the battlefield, that card returns to the battlefield.");

                StringBuilder ab = new StringBuilder();
                ab.append(
                        "DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | Champion$ True | ");
                ab.append("Hidden$ True | Optional$ True | SubAbility$ ChampionSacrifice | ChangeType$ ")
                        .append(changeType);

                StringBuilder subAb = new StringBuilder();
                subAb.append(
                        "DB$ Sacrifice | Defined$ Card.Self | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0");

                String returnChampion = "DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield";
                final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig.toString(), card, true);
                final Trigger parsedTrigReturn = TriggerHandler.parseTrigger(trigReturn.toString(), card, true);
                card.addTrigger(parsedTrigger);
                card.addTrigger(parsedTrigReturn);
                card.setSVar("ChampionAbility", ab.toString());
                card.setSVar("ChampionReturn", returnChampion);
                card.setSVar("ChampionSacrifice", subAb.toString());
            } else if (keyword.startsWith("If CARDNAME would be put into a graveyard "
                    + "from anywhere, reveal CARDNAME and shuffle it into its owner's library instead.")) {
                String replacement = "Event$ Moved | Destination$ Graveyard | ValidCard$ Card.Self | ReplaceWith$ GraveyardToLibrary";
                String ab = "DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Library | Defined$ ReplacedCard | Reveal$ True | Shuffle$ True";

                card.addReplacementEffect(ReplacementHandler.parseReplacement(replacement, card, true));
                card.setSVar("GraveyardToLibrary", ab);
            } else if (keyword.startsWith("Echo")) {
                final String[] k = keyword.split(":");
                final String cost = k[1];

                String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield "
                        + " | Execute$ TrigUpkeepEcho | IsPresent$ Card.Self+cameUnderControlSinceLastUpkeep | Secondary$ True | "
                        + "TriggerDescription$ Echo: At the beginning of your upkeep, if CARDNAME came under your control since the "
                        + "beginning of your last upkeep, sacrifice it unless you pay the Echo cost";
                String ref = "X".equals(cost) ? " | References$ X" : "";
                card.setSVar("TrigUpkeepEcho",
                        "AB$ Sacrifice | Cost$ 0 | SacValid$ Self | " + "Echo$ " + cost + ref);

                final Trigger parsedUpkeepTrig = TriggerHandler.parseTrigger(upkeepTrig, card, true);
                card.addTrigger(parsedUpkeepTrig);
            } else if (keyword.startsWith("Suspend")) {
                card.removeIntrinsicKeyword(keyword);
                card.setSuspend(true);
                final String[] k = keyword.split(":");

                final String timeCounters = k[1];
                final String cost = k[2];
                card.addSpellAbility(abilitySuspendStatic(card, cost, timeCounters));
                addSuspendUpkeepTrigger(card);
                addSuspendPlayTrigger(card);
            } else if (keyword.startsWith("Fading")) {
                final String[] k = keyword.split(":");

                card.addIntrinsicKeyword("etbCounter:FADE:" + k[1] + ":no Condition:no desc");

                String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield "
                        + " | Execute$ TrigUpkeepFading | Secondary$ True | TriggerDescription$ At the beginning of "
                        + "your upkeep, remove a fade counter from CARDNAME. If you can't, sacrifice CARDNAME.";

                card.setSVar("TrigUpkeepFading", "AB$ RemoveCounter | Cost$ 0 | Defined$ Self | CounterType$ FADE"
                        + " | CounterNum$ 1 | RememberRemoved$ True | SubAbility$ DBUpkeepFadingSac");
                card.setSVar("DBUpkeepFadingSac",
                        "DB$ Sacrifice | SacValid$ Self | ConditionCheckSVar$ FadingCheckSVar"
                                + " | ConditionSVarCompare$ EQ0 | References$ FadingCheckSVar | SubAbility$ FadingCleanup");
                card.setSVar("FadingCleanup", "DB$ Cleanup | ClearRemembered$ True");
                card.setSVar("FadingCheckSVar", "Count$RememberedSize");
                final Trigger parsedUpkeepTrig = TriggerHandler.parseTrigger(upkeepTrig, card, true);
                card.addTrigger(parsedUpkeepTrig);
            } else if (keyword.startsWith("Renown")) {
                final String[] k = keyword.split(" ");
                final String suffix = !k[1].equals("1") ? "s" : "";
                card.removeIntrinsicKeyword(keyword);
                String renownTrig = "Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player"
                        + " | IsPresent$ Card.Self+IsNotRenowned | CombatDamage$ True | Execute$"
                        + " TrigBecomeRenown | TriggerDescription$ Renown " + k[1] + " (When this "
                        + "creature deals combat damage to a player, if it isn't renowned, put " + k[1]
                        + " +1/+1 counter" + suffix + " on it and it becomes renowned.) ";
                card.setSVar("TrigBecomeRenown", "AB$ PutCounter | Cost$ 0 | Defined$ Self | "
                        + "CounterType$ P1P1 | CounterNum$ " + k[1] + " | Renown$ True");
                final Trigger parseRenownTrig = TriggerHandler.parseTrigger(renownTrig, card, true);
                card.addTrigger(parseRenownTrig);
            } else if (keyword.startsWith("Vanishing")) {
                final String[] k = keyword.split(":");
                // etbcounter
                card.addIntrinsicKeyword("etbCounter:TIME:" + k[1] + ":no Condition:no desc");
                // Remove Time counter trigger
                String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | "
                        + "TriggerZones$ Battlefield | IsPresent$ Card.Self+counters_GE1_TIME"
                        + " | Execute$ TrigUpkeepVanishing | TriggerDescription$ At the "
                        + "beginning of your upkeep, if CARDNAME has a time counter on it, "
                        + "remove a time counter from it. | Secondary$ True";
                card.setSVar("TrigUpkeepVanishing",
                        "AB$ RemoveCounter | Cost$ 0 | Defined$ Self" + " | CounterType$ TIME | CounterNum$ 1");
                final Trigger parsedUpkeepTrig = TriggerHandler.parseTrigger(upkeepTrig, card, true);
                card.addTrigger(parsedUpkeepTrig);
                // sacrifice trigger
                String sacTrig = "Mode$ CounterRemoved | TriggerZones$ Battlefield | ValidCard$"
                        + " Card.Self | NewCounterAmount$ 0 | Secondary$ True | CounterType$ TIME |"
                        + " Execute$ TrigVanishingSac | TriggerDescription$ When the last time "
                        + "counter is removed from CARDNAME, sacrifice it.";
                card.setSVar("TrigVanishingSac", "AB$ Sacrifice | Cost$ 0 | SacValid$ Self");

                final Trigger parsedSacTrigger = TriggerHandler.parseTrigger(sacTrig, card, true);
                card.addTrigger(parsedSacTrigger);
            } else if (keyword.equals("Delve")) {
                card.getSpellAbilities().getFirst().setDelve(true);
            } else if (keyword.startsWith("Haunt")) {
                setupHauntSpell(card);
            } else if (keyword.equals("Provoke")) {
                final String actualTrigger = "Mode$ Attacks | ValidCard$ Card.Self | "
                        + "OptionalDecider$ You | Execute$ ProvokeAbility | Secondary$ True | TriggerDescription$ "
                        + "When this attacks, you may have target creature defending player "
                        + "controls untap and block it if able.";
                final String abString = "DB$ MustBlock | ValidTgts$ Creature.DefenderCtrl | "
                        + "TgtPrompt$ Select target creature defending player controls | SubAbility$ ProvokeUntap";
                final String dbString = "DB$ Untap | Defined$ Targeted";
                final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, true);
                card.addTrigger(parsedTrigger);
                card.setSVar("ProvokeAbility", abString);
                card.setSVar("ProvokeUntap", dbString);
            } else if (keyword.equals("Living Weapon")) {
                card.removeIntrinsicKeyword(keyword);

                final StringBuilder sbTrig = new StringBuilder();
                sbTrig.append("Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ");
                sbTrig.append("ValidCard$ Card.Self | Execute$ TrigGerm | TriggerDescription$ ");
                sbTrig.append("Living Weapon (When this Equipment enters the battlefield, ");
                sbTrig.append("put a 0/0 black Germ creature token onto the battlefield, then attach this to it.)");

                final StringBuilder sbGerm = new StringBuilder();
                sbGerm.append(
                        "DB$ Token | TokenAmount$ 1 | TokenName$ Germ | TokenTypes$ Creature,Germ | RememberTokens$ True | ");
                sbGerm.append(
                        "TokenOwner$ You | TokenColors$ Black | TokenPower$ 0 | TokenToughness$ 0 | TokenImage$ B 0 0 Germ | SubAbility$ DBGermAttach");

                final StringBuilder sbAttach = new StringBuilder();
                sbAttach.append("DB$ Attach | Defined$ Remembered | SubAbility$ DBGermClear");

                final StringBuilder sbClear = new StringBuilder();
                sbClear.append("DB$ Cleanup | ClearRemembered$ True");

                card.setSVar("TrigGerm", sbGerm.toString());
                card.setSVar("DBGermAttach", sbAttach.toString());
                card.setSVar("DBGermClear", sbClear.toString());

                final Trigger etbTrigger = TriggerHandler.parseTrigger(sbTrig.toString(), card, true);
                card.addTrigger(etbTrigger);
            } else if (keyword.equals("Epic")) {
                makeEpic(card);
            } else if (keyword.equals("Soulbond")) {
                // Setup ETB trigger for card with Soulbond keyword
                final String actualTriggerSelf = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | "
                        + "ValidCard$ Card.Self | Execute$ TrigBondOther | OptionalDecider$ You | "
                        + "IsPresent$ Creature.Other+YouCtrl+NotPaired | Secondary$ True | "
                        + "TriggerDescription$ When CARDNAME enters the battlefield, "
                        + "you may pair CARDNAME with another unpaired creature you control";
                final String abStringSelf = "AB$ Bond | Cost$ 0 | Defined$ Self | ValidCards$ Creature.Other+YouCtrl+NotPaired";
                final Trigger parsedTriggerSelf = TriggerHandler.parseTrigger(actualTriggerSelf, card, true);
                card.addTrigger(parsedTriggerSelf);
                card.setSVar("TrigBondOther", abStringSelf);
                // Setup ETB trigger for other creatures you control
                final String actualTriggerOther = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | "
                        + "ValidCard$ Creature.Other+YouCtrl | TriggerZones$ Battlefield | OptionalDecider$ You | "
                        + "Execute$ TrigBondSelf | IsPresent$ Creature.Self+NotPaired | Secondary$ True | "
                        + " TriggerDescription$ When another unpaired creature you control enters the battlefield, "
                        + "you may pair it with CARDNAME";
                final String abStringOther = "AB$ Bond | Cost$ 0 | Defined$ TriggeredCard | ValidCards$ Creature.Self+NotPaired";
                final Trigger parsedTriggerOther = TriggerHandler.parseTrigger(actualTriggerOther, card, true);
                card.addTrigger(parsedTriggerOther);
                card.setSVar("TrigBondSelf", abStringOther);
            } else if (keyword.equals("Extort")) {
                final String extortTrigger = "Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | "
                        + "TriggerZones$ Battlefield | Execute$ ExtortOpps | Secondary$ True"
                        + " | TriggerDescription$ Extort (Whenever you cast a spell, you may pay W/B. If you do, "
                        + "each opponent loses 1 life and you gain that much life.)";
                final String abString = "AB$ LoseLife | Cost$ WB | Defined$ Player.Opponent | "
                        + "LifeAmount$ 1 | SubAbility$ ExtortGainLife";
                final String dbString = "DB$ GainLife | Defined$ You | LifeAmount$ AFLifeLost | References$ AFLifeLost";
                final Trigger parsedTrigger = TriggerHandler.parseTrigger(extortTrigger, card, true);
                card.addTrigger(parsedTrigger);
                card.setSVar("ExtortOpps", abString);
                card.setSVar("ExtortGainLife", dbString);
                card.setSVar("AFLifeLost", "Number$0");
            } else if (keyword.equals("Evolve")) {
                final String evolveTrigger = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | "
                        + " ValidCard$ Creature.YouCtrl+Other | EvolveCondition$ True | "
                        + "TriggerZones$ Battlefield | Execute$ EvolveAddCounter | Secondary$ True | "
                        + "TriggerDescription$ Evolve (Whenever a creature enters the battlefield under your "
                        + "control, if that creature has greater power or toughness than this creature, put a "
                        + "+1/+1 counter on this creature.)";
                final String abString = "AB$ PutCounter | Cost$ 0 | Defined$ Self | CounterType$ P1P1 | "
                        + "CounterNum$ 1 | Evolve$ True";
                final Trigger parsedTrigger = TriggerHandler.parseTrigger(evolveTrigger, card, true);
                card.addTrigger(parsedTrigger);
                card.setSVar("EvolveAddCounter", abString);
            } else if (keyword.startsWith("Dredge")) {
                final int dredgeAmount = card.getKeywordMagnitude("Dredge");

                final String actualRep = "Event$ Draw | ActiveZones$ Graveyard | ValidPlayer$ You | "
                        + "ReplaceWith$ DredgeCards | Secondary$ True | Optional$ True | CheckSVar$ "
                        + "DredgeCheckLib | SVarCompare$ GE" + dredgeAmount + " | References$ "
                        + "DredgeCheckLib | AICheckDredge$ True | Description$ " + card.getName() + " - Dredge "
                        + dredgeAmount;
                final String abString = "DB$ Mill | Defined$ You | NumCards$ " + dredgeAmount + " | "
                        + "SubAbility$ DredgeMoveToPlay";
                final String moveToPlay = "DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | "
                        + "Defined$ Self";
                final String checkSVar = "Count$ValidLibrary Card.YouOwn";
                card.setSVar("DredgeCards", abString);
                card.setSVar("DredgeMoveToPlay", moveToPlay);
                card.setSVar("DredgeCheckLib", checkSVar);
                card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true));
            } else if (keyword.startsWith("Tribute")) {
                final int tributeAmount = card.getKeywordMagnitude("Tribute");

                final String actualRep = "Event$ Moved | Destination$ Battlefield | ValidCard$ Card.Self |"
                        + " ReplaceWith$ TributeAddCounter | Secondary$ True | Description$ Tribute "
                        + tributeAmount + " (As this creature enters the battlefield, an opponent of your"
                        + " choice may place " + tributeAmount + " +1/+1 counter on it.)";
                final String abString = "DB$ PutCounter | Defined$ ReplacedCard | Tribute$ True | "
                        + "CounterType$ P1P1 | CounterNum$ " + tributeAmount
                        + " | ETB$ True | SubAbility$ TributeMoveToPlay";
                final String moveToPlay = "DB$ ChangeZone | Origin$ All | Destination$ Battlefield | "
                        + "Defined$ ReplacedCard | Hidden$ True";
                card.setSVar("TributeAddCounter", abString);
                card.setSVar("TributeMoveToPlay", moveToPlay);
                card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true));
            } else if (keyword.startsWith("Amplify")) {
                // find position of Amplify keyword
                final int ampPos = card.getKeywordPosition("Amplify");
                final String[] ampString = card.getKeywords().get(ampPos).split(":");
                final String amplifyMagnitude = ampString[1];
                final String suffix = !amplifyMagnitude.equals("1") ? "s" : "";
                final String ampTypes = ampString[2];
                String[] refinedTypes = ampTypes.split(",");
                final StringBuilder types = new StringBuilder();
                for (int i = 0; i < refinedTypes.length; i++) {
                    types.append("Card.").append(refinedTypes[i]).append("+YouCtrl");
                    if (i + 1 != refinedTypes.length) {
                        types.append(",");
                    }
                }
                // Setup ETB replacement effects
                final String actualRep = "Event$ Moved | Destination$ Battlefield | ValidCard$ Card.Self |"
                        + " ReplaceWith$ AmplifyReveal | Secondary$ True | Description$ As this creature "
                        + "enters the battlefield, put " + amplifyMagnitude + " +1/+1 counter" + suffix
                        + " on it for each " + ampTypes.replace(",", " and/or ")
                        + " card you reveal in your hand.)";
                final String abString = "DB$ Reveal | AnyNumber$ True | RevealValid$ " + types.toString()
                        + " | RememberRevealed$ True | SubAbility$ Amplify";
                final String dbString = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ P1P1 | "
                        + "CounterNum$ AmpMagnitude | References$ Revealed,AmpMagnitude | SubAbility$"
                        + " AmplifyMoveToPlay | ETB$ True";
                final String moveToPlay = "DB$ ChangeZone | Origin$ All | Destination$ Battlefield | "
                        + "Defined$ ReplacedCard | Hidden$ True | SubAbility$ DBCleanup";
                card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true));
                card.setSVar("AmplifyReveal", abString);
                card.setSVar("AmplifyMoveToPlay", moveToPlay);
                card.setSVar("Amplify", dbString);
                card.setSVar("DBCleanup", "DB$ Cleanup | ClearRemembered$ True");
                card.setSVar("AmpMagnitude", "SVar$Revealed/Times." + amplifyMagnitude);
                card.setSVar("Revealed", "Remembered$Amount");
            } else if (keyword.startsWith("Equip")) {
                // Check for additional params such as preferred AI targets
                final String equipString = keyword.substring(5);
                final String[] equipExtras = equipString.contains("|") ? equipString.split("\\|", 2) : null;
                // Get cost string
                String equipCost = "";
                if (equipExtras != null) {
                    equipCost = equipExtras[0].trim();
                } else {
                    equipCost = equipString.trim();
                }
                // Create attach ability string
                final StringBuilder abilityStr = new StringBuilder();
                abilityStr.append("AB$ Attach | Cost$ ");
                abilityStr.append(equipCost);
                abilityStr
                        .append(" | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control ");
                abilityStr.append(
                        "| SorcerySpeed$ True | Equip$ True | AILogic$ Pump | IsPresent$ Card.Self+nonCreature ");
                if (equipExtras != null) {
                    abilityStr.append("| ").append(equipExtras[1]).append(" ");
                }
                abilityStr.append("| PrecostDesc$ Equip ");
                Cost cost = new Cost(equipCost, true);
                if (!cost.isOnlyManaCost()) { //Something other than a mana cost
                    abilityStr.append("- ");
                }
                abilityStr.append("| CostDesc$ " + cost.toSimpleString() + " ");
                abilityStr.append("| SpellDescription$ (" + cost.toSimpleString()
                        + ": Attach to target creature you control. Equip only as a sorcery.)");
                // instantiate attach ability
                final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card);
                card.addSpellAbility(sa);
                // add ability to instrinic strings so copies/clones create the ability also
                card.getCurrentState().addUnparsedAbility(abilityStr.toString());
            } else if (keyword.startsWith("Outlast")) {
                final String outlastString = keyword.substring(7);
                final String[] outlastExtras = outlastString.contains("|") ? outlastString.split("\\|", 2) : null;
                // Get cost string
                String outlastCost = "";
                if (outlastExtras != null) {
                    outlastCost = outlastExtras[0].trim();
                } else {
                    outlastCost = outlastString.trim();
                }
                // Create outlast ability string
                final StringBuilder abilityStr = new StringBuilder();
                abilityStr.append("AB$ PutCounter | Cost$ ");
                abilityStr.append(outlastCost);
                abilityStr.append(" T | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 ");
                abilityStr.append("| SorcerySpeed$ True | Outlast$ True ");
                if (outlastExtras != null) {
                    abilityStr.append("| ").append(outlastExtras[1]).append(" ");
                }
                abilityStr.append("| PrecostDesc$ Outlast ");
                Cost cost = new Cost(outlastCost, true);
                if (!cost.isOnlyManaCost()) { //Something other than a mana cost
                    abilityStr.append("- ");
                }
                abilityStr.append("| CostDesc$ " + cost.toSimpleString() + " ");
                abilityStr.append("| SpellDescription$ (" + cost.toSimpleString()
                        + ", {T}: Put a +1/+1 counter on this creature. Outlast only as a sorcery.)");
                final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card);
                card.addSpellAbility(sa);
                // add ability to instrinic strings so copies/clones create the ability also
                card.getCurrentState().addUnparsedAbility(abilityStr.toString());
            } else if (keyword.startsWith("Fortify")) {
                final String equipString = keyword.substring(7);
                final String[] equipExtras = equipString.contains("|") ? equipString.split("\\|", 2) : null;
                // Get cost string
                String equipCost = "";
                if (equipExtras != null) {
                    equipCost = equipExtras[0].trim();
                } else {
                    equipCost = equipString.trim();
                }
                // Create attach ability string
                final StringBuilder abilityStr = new StringBuilder();
                abilityStr.append("AB$ Attach | Cost$ ");
                abilityStr.append(equipCost);
                abilityStr.append(" | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select target land you control ");
                abilityStr.append("| SorcerySpeed$ True | AILogic$ Pump | IsPresent$ Card.Self+nonCreature ");
                if (equipExtras != null) {
                    abilityStr.append("| ").append(equipExtras[1]).append(" ");
                }
                abilityStr.append("| PrecostDesc$ Fortify ");
                Cost cost = new Cost(equipCost, true);
                if (!cost.isOnlyManaCost()) { //Something other than a mana cost
                    abilityStr.append("- ");
                }
                abilityStr.append("| CostDesc$ " + cost.toSimpleString() + " ");
                abilityStr.append("| SpellDescription$ (" + cost.toSimpleString()
                        + ": Attach to target land you control. Fortify only as a sorcery.)");

                // instantiate attach ability
                final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card);
                card.addSpellAbility(sa);
                // add ability to intrinsic strings so copies/clones create the ability also
                card.getCurrentState().addUnparsedAbility(abilityStr.toString());
            } else if (keyword.startsWith("Bestow")) {
                final String[] params = keyword.split(":");
                final String cost = params[1];
                card.removeIntrinsicKeyword(keyword);

                final StringBuilder sbAttach = new StringBuilder();
                sbAttach.append("SP$ Attach | Cost$ ");
                sbAttach.append(cost);
                sbAttach.append(" | AILogic$ ").append(params.length > 2 ? params[2] : "Pump");
                sbAttach.append(" | Bestow$ True | ValidTgts$ Creature");
                final SpellAbility bestow = AbilityFactory.getAbility(sbAttach.toString(), card);

                bestow.setDescription("Bestow " + ManaCostParser.parse(cost) + " (If you cast this"
                        + " card for its bestow cost, it's an Aura spell with enchant creature. It"
                        + " becomes a creature again if it's not attached to a creature.)");
                bestow.setStackDescription("Bestow - " + card.getName());
                bestow.setBasicSpell(false);
                card.addSpellAbility(bestow);
                card.getCurrentState().addUnparsedAbility(sbAttach.toString());
            } else if (keyword.equals("Hideaway")) {
                card.getCurrentState().addIntrinsicKeyword("CARDNAME enters the battlefield tapped.");

                final Trigger hideawayTrigger = TriggerHandler.parseTrigger(
                        "Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigHideawayDig | TriggerDescription$ When CARDNAME enters the battlefield, look at the top four cards of your library, exile one face down, then put the rest on the bottom of your library.",
                        card, true);
                card.addTrigger(hideawayTrigger);
                card.setSVar("TrigHideawayDig",
                        "DB$ Dig | Defined$ You | DigNum$ 4 | DestinationZone$ Exile | ExileFaceDown$ True | RememberChanged$ True | SubAbility$ DBHideawayEffect");
                final Trigger gainControlTrigger = TriggerHandler.parseTrigger(
                        "Mode$ ChangesController | ValidCard$ Card.Self | Execute$ DBHideawayEffect | Static$ True",
                        card, true);
                card.addTrigger(gainControlTrigger);
                card.setSVar("DBHideawayEffect",
                        "DB$ Effect | StaticAbilities$ STHideawayEffectLookAtCard | Triggers$ THideawayEffectCleanup | SVars$ DBHideawayEffectExileSelf | ImprintOnHost$ True | Duration$ Permanent | SubAbility$ DBHideawayRemember");
                card.setSVar("STHideawayEffectLookAtCard",
                        "Mode$ Continuous | Affected$ Card.IsRemembered | MayLookAt$ True | EffectZone$ Command | AffectedZone$ Exile | Description$ You may look at the exiled card.");
                card.setSVar("THideawayEffectCleanup",
                        "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZone$ Command | Execute$ DBHideawayEffectExileSelf | Static$ True");
                card.setSVar("DBHideawayEffectExileSelf",
                        "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile");
                final Trigger changeZoneTrigger = TriggerHandler.parseTrigger(
                        "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZone$ Command | Execute$ DBHideawayCleanup | Static$ True",
                        card, true);
                card.addTrigger(changeZoneTrigger);
                card.setSVar("DBHideawayRemember",
                        "DB$ Animate | Defined$ Imprinted | RememberObjects$ Remembered | Permanent$ True");
                card.setSVar("DBHideawayCleanup", "DB$ Cleanup | ClearRemembered$ True");
            }
        }

        // AddCost
        if (card.hasSVar("FullCost")) {
            final SpellAbility sa1 = card.getFirstSpellAbility();
            if (sa1 != null && sa1.isSpell()) {
                sa1.setPayCosts(new Cost(card.getSVar("FullCost"), sa1.isAbility()));
            }
        }

        // AltCost
        String altCost = card.getSVar("AltCost");
        if (StringUtils.isNotBlank(altCost)) {
            final SpellAbility sa1 = card.getFirstSpellAbility();
            if (sa1 != null && sa1.isSpell()) {
                card.addSpellAbility(makeAltCostAbility(card, altCost, sa1));
            }
        }

        setupEtbKeywords(card);
    }

    /**
     * TODO: Write javadoc for this method.
     * @param card
     */
    private static void setupEtbKeywords(final Card card) {
        for (String kw : card.getKeywords()) {

            if (kw.startsWith("ETBReplacement")) {
                String[] splitkw = kw.split(":");
                ReplacementLayer layer = ReplacementLayer.smartValueOf(splitkw[1]);
                SpellAbility repAb = AbilityFactory.getAbility(card.getSVar(splitkw[2]), card);
                String desc = repAb.getDescription();
                setupETBReplacementAbility(repAb);

                final String valid = splitkw.length >= 6 ? splitkw[5] : "Card.Self";

                StringBuilder repEffsb = new StringBuilder();
                repEffsb.append("Event$ Moved | ValidCard$ ").append(valid);
                repEffsb.append(" | Destination$ Battlefield | Description$ ").append(desc);
                if (splitkw.length >= 4) {
                    if (splitkw[3].contains("Optional")) {
                        repEffsb.append(" | Optional$ True");
                    }
                }
                if (splitkw.length >= 5) {
                    if (!splitkw[4].isEmpty()) {
                        repEffsb.append(" | ActiveZones$ " + splitkw[4]);
                    }
                }

                ReplacementEffect re = ReplacementHandler.parseReplacement(repEffsb.toString(), card, true);
                re.setLayer(layer);
                re.setOverridingAbility(repAb);

                card.addReplacementEffect(re);
            } else if (kw.startsWith("etbCounter")) {
                String parse = kw;
                card.removeIntrinsicKeyword(parse);

                String[] splitkw = parse.split(":");

                String desc = "CARDNAME enters the battlefield with " + splitkw[2] + " "
                        + CounterType.valueOf(splitkw[1]).getName() + " counters on it.";
                String extraparams = "";
                String amount = splitkw[2];
                if (splitkw.length > 3) {
                    if (!splitkw[3].equals("no Condition")) {
                        extraparams = splitkw[3];
                    }
                }
                if (splitkw.length > 4) {
                    desc = !splitkw[4].equals("no desc") ? splitkw[4] : "";
                }
                String abStr = "DB$ PutCounter | Defined$ Self | CounterType$ " + splitkw[1]
                        + " | ETB$ True | CounterNum$ " + amount + " | SubAbility$ ETBCounterDBSVar";
                String dbStr = "DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Battlefield"
                        + "| Defined$ ReplacedCard";
                try {
                    Integer.parseInt(amount);
                } catch (NumberFormatException ignored) {
                    abStr += " | References$ " + amount;
                }
                card.setSVar("ETBCounterSVar", abStr);
                card.setSVar("ETBCounterDBSVar", dbStr);

                String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield "
                        + "| ReplaceWith$ ETBCounterSVar | Description$ " + desc
                        + (!extraparams.equals("") ? " | " + extraparams : "");

                ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true);
                re.setLayer(ReplacementLayer.Other);

                card.addReplacementEffect(re);
            } else if (kw.equals("CARDNAME enters the battlefield tapped.")) {
                String parse = kw;
                card.removeIntrinsicKeyword(parse);

                String abStr = "AB$ Tap | Cost$ 0 | Defined$ Self | ETB$ True | SubAbility$ MoveETB";
                String dbStr = "DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Battlefield"
                        + "| Defined$ ReplacedCard";

                card.setSVar("ETBTappedSVar", abStr);
                card.setSVar("MoveETB", dbStr);

                String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield "
                        + "| ReplaceWith$ ETBTappedSVar | Description$ CARDNAME enters the battlefield tapped.";

                ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true);
                re.setLayer(ReplacementLayer.Other);

                card.addReplacementEffect(re);
            }
        }
    }

    /**
     * TODO: Write javadoc for this method.
     * @param card
     * @return
     */
    private static void makeEpic(final Card card) {

        // Add the Epic effect as a subAbility
        String dbStr = "DB$ Effect | Triggers$ EpicTrigger | SVars$ EpicCopy | StaticAbilities$ EpicCantBeCast | Duration$ Permanent | Unique$ True";

        final AbilitySub newSA = (AbilitySub) AbilityFactory.getAbility(dbStr.toString(), card);

        card.setSVar("EpicCantBeCast",
                "Mode$ CantBeCast | ValidCard$ Card | Caster$ You | EffectZone$ Command | Description$ For the rest of the game, you can't cast spells.");
        card.setSVar("EpicTrigger",
                "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ EpicCopy | TriggerDescription$ "
                        + "At the beginning of each of your upkeeps, copy " + card.toString()
                        + " except for its epic ability.");
        card.setSVar("EpicCopy", "DB$ CopySpellAbility | Defined$ EffectSource");

        final SpellAbility origSA = card.getSpellAbilities().getFirst();

        SpellAbility child = origSA;
        while (child.getSubAbility() != null) {
            child = child.getSubAbility();
        }
        child.setSubAbility(newSA);
        newSA.setParent(child);
    }

    /**
     * TODO: Write javadoc for this method.
     * @param card
     */
    private static void setupHauntSpell(final Card card) {
        final int hauntPos = card.getKeywordPosition("Haunt");
        final String[] splitKeyword = card.getKeywords().get(hauntPos).split(":");
        final String hauntSVarName = splitKeyword[1];
        final String abilityDescription = splitKeyword[2];
        final String hauntAbilityDescription = abilityDescription.substring(0, 1).toLowerCase()
                + abilityDescription.substring(1);
        String hauntDescription;
        if (card.isCreature()) {
            final StringBuilder sb = new StringBuilder();
            sb.append("When ").append(card.getName());
            sb.append(" enters the battlefield or the creature it haunts dies, ");
            sb.append(hauntAbilityDescription);
            hauntDescription = sb.toString();
        } else {
            final StringBuilder sb = new StringBuilder();
            sb.append("When the creature ").append(card.getName());
            sb.append(" haunts dies, ").append(hauntAbilityDescription);
            hauntDescription = sb.toString();
        }

        card.getKeywords().remove(hauntPos);

        // First, create trigger that runs when the haunter goes to the
        // graveyard
        final StringBuilder sbHaunter = new StringBuilder();
        sbHaunter.append("Mode$ ChangesZone | Origin$ Battlefield | ");
        sbHaunter.append("Destination$ Graveyard | ValidCard$ Card.Self | ");
        sbHaunter.append("Static$ True | Secondary$ True | TriggerDescription$ Blank");

        final Trigger haunterDies = TriggerHandler.parseTrigger(sbHaunter.toString(), card, true);

        final Ability haunterDiesWork = new Ability(card, ManaCost.ZERO) {
            @Override
            public void resolve() {
                this.getTargets().getFirstTargetedCard().addHauntedBy(card);
                card.getGame().getAction().exile(card);
            }
        };
        haunterDiesWork.setDescription(hauntDescription);
        haunterDiesWork.setTargetRestrictions(new TargetRestrictions(null, new String[] { "Creature" }, "1", "1")); // not null to make stack preserve targets set

        final Ability haunterDiesSetup = new Ability(card, ManaCost.ZERO) {
            @Override
            public void resolve() {
                final Game game = card.getGame();
                this.setActivatingPlayer(card.getController());
                haunterDiesWork.setActivatingPlayer(card.getController());
                CardCollection allCreatures = CardLists.filter(game.getCardsIn(ZoneType.Battlefield),
                        Presets.CREATURES);
                final CardCollection creats = CardLists.getTargetableCards(allCreatures, haunterDiesWork);
                if (creats.isEmpty()) {
                    return;
                }

                final Card toHaunt = card.getController().getController().chooseSingleEntityForEffect(creats,
                        new SpellAbility.EmptySa(ApiType.InternalHaunt, card), "Choose target creature to haunt.");
                haunterDiesWork.setTargetCard(toHaunt);
                haunterDiesWork.setActivatingPlayer(card.getController());
                game.getStack().add(haunterDiesWork);
            }
        };

        haunterDies.setOverridingAbility(haunterDiesSetup);

        // Second, create the trigger that runs when the haunted creature dies
        final StringBuilder sbDies = new StringBuilder();
        sbDies.append("Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ");
        sbDies.append("ValidCard$ Creature.HauntedBy | Execute$ ").append(hauntSVarName);
        sbDies.append(" | TriggerDescription$ ").append(hauntDescription);

        final Trigger hauntedDies = forge.game.trigger.TriggerHandler.parseTrigger(sbDies.toString(), card, true);

        // Third, create the trigger that runs when the haunting creature
        // enters the battlefield
        final StringBuilder sbETB = new StringBuilder();
        sbETB.append("Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ ");
        sbETB.append(hauntSVarName).append(" | Secondary$ True | TriggerDescription$ ");
        sbETB.append(hauntDescription);

        final Trigger haunterETB = forge.game.trigger.TriggerHandler.parseTrigger(sbETB.toString(), card, true);

        // Fourth, create a trigger that removes the haunting status if the
        // haunter leaves the exile
        final StringBuilder sbUnExiled = new StringBuilder();
        sbUnExiled.append("Mode$ ChangesZone | Origin$ Exile | Destination$ Any | ");
        sbUnExiled.append("ValidCard$ Card.Self | Static$ True | Secondary$ True | ");
        sbUnExiled.append("TriggerDescription$ Blank");

        final Trigger haunterUnExiled = forge.game.trigger.TriggerHandler.parseTrigger(sbUnExiled.toString(), card,
                true);

        final Ability haunterUnExiledWork = new Ability(card, ManaCost.ZERO) {
            @Override
            public void resolve() {
                if (card.getHaunting() != null) {
                    card.getHaunting().removeHauntedBy(card);
                    card.setHaunting(null);
                }
            }
        };

        haunterUnExiled.setOverridingAbility(haunterUnExiledWork);

        // Fifth, add all triggers and abilities to the card.
        if (card.isCreature()) {
            card.addTrigger(haunterETB);
            card.addTrigger(haunterDies);
        } else {
            final String abString = card.getSVar(hauntSVarName).replace("AB$", "SP$") + " | SpellDescription$ "
                    + abilityDescription;

            final SpellAbility sa = AbilityFactory.getAbility(abString, card);
            sa.setPayCosts(new Cost(card.getManaCost(), false));
            card.addSpellAbility(sa);
        }

        card.addTrigger(hauntedDies);
        card.addTrigger(haunterUnExiled);
    }

    /**
     * TODO: Write javadoc for this method.
     * @param card
     * @param abilities
     * @return 
     */
    private static SpellAbility makeAltCostAbility(final Card card, final String altCost, final SpellAbility sa) {
        final Map<String, String> params = AbilityFactory.getMapParams(altCost);

        final SpellAbility altCostSA = sa.copy();
        final Cost abCost = new Cost(params.get("Cost"), altCostSA.isAbility());
        altCostSA.setPayCosts(abCost);
        altCostSA.setBasicSpell(false);
        altCostSA.addOptionalCost(OptionalCost.AltCost);

        final SpellAbilityRestriction restriction = new SpellAbilityRestriction();
        restriction.setRestrictions(params);
        if (!params.containsKey("ActivationZone")) {
            restriction.setZone(ZoneType.Hand);
        }
        altCostSA.setRestrictions(restriction);

        final String costDescription = params.containsKey("Description") ? params.get("Description")
                : String.format("You may %s rather than pay %s's mana cost.", abCost.toStringAlt(), card.getName());

        altCostSA.setDescription(costDescription);
        if (params.containsKey("References")) {
            for (String svar : params.get("References").split(",")) {
                altCostSA.setSVar(svar, card.getSVar(svar));
            }
        }
        return altCostSA;
    }

    /**
     * TODO: Write javadoc for this method.
     * @param card
     * @param evokeKeyword
     * @return
     */
    private static SpellAbility makeEvokeSpell(final Card card, final String evokeKeyword) {
        final String[] k = evokeKeyword.split(":");
        final Cost evokedCost = new Cost(k[1], false);

        final SpellAbility evokedSpell = new SpellPermanent(card) {
            private static final long serialVersionUID = -1598664196463358630L;

            @Override
            public void resolve() {
                final Game game = card.getGame();
                card.setEvoked(true);
                game.getAction().moveToPlay(card);
            }
        };
        card.removeIntrinsicKeyword(evokeKeyword);
        final StringBuilder desc = new StringBuilder();
        desc.append("Evoke ").append(evokedCost.toSimpleString());
        desc.append(" (You may cast this spell for its evoke cost. ");
        desc.append("If you do, when it enters the battlefield, sacrifice it.)");

        evokedSpell.setDescription(desc.toString());

        final StringBuilder sb = new StringBuilder();
        sb.append(card.getName()).append(" (Evoked)");
        evokedSpell.setStackDescription(sb.toString());
        evokedSpell.setBasicSpell(false);
        evokedSpell.setPayCosts(evokedCost);
        return evokedSpell;
    }

    private static final Map<String, String> emptyMap = new TreeMap<String, String>();

    public static void setupETBReplacementAbility(SpellAbility sa) {
        sa.appendSubAbility(new AbilitySub(ApiType.InternalEtbReplacement, sa.getHostCard(), null, emptyMap));
        // ETBReplacementMove(sa.getHostCard(), null));
    }

    /**
     * make Dash keyword
     * @param card
     * @param dashKeyword
     * @return
     */
    private static SpellAbility makeDashSpell(final Card card, final String dashKeyword) {
        final String[] k = dashKeyword.split(":");
        final Cost dashCost = new Cost(k[1], false);
        card.removeIntrinsicKeyword(dashKeyword);
        final String dashString = "SP$ PermanentCreature | Cost$ " + k[1] + " | SubAbility$" + " DashPump";
        final String dbHaste = "DB$ Pump | Defined$ Self | KW$ Haste | Permanent$ True"
                + " | SubAbility$ DashDelayedTrigger";
        final String dbDelayTrigger = "DB$ DelayedTrigger | Mode$ Phase | Phase$"
                + " End of Turn | Execute$ DashReturnSelf | RememberObjects$ Self"
                + " | TriggerDescription$ Return CARDNAME from the battlefield to" + " its owner's hand.";
        final String dbReturn = "DB$ ChangeZone | Origin$ Battlefield | Destination$ Hand"
                + " | Defined$ DelayTriggerRemembered";
        card.setSVar("DashPump", dbHaste);
        card.setSVar("DashDelayedTrigger", dbDelayTrigger);
        card.setSVar("DashReturnSelf", dbReturn);

        final SpellAbility dashSpell = AbilityFactory.getAbility(dashString, card);
        String desc = "Dash " + dashCost.toSimpleString() + " (You may cast this "
                + "spell for its dash cost. If you do, it gains haste, and it's "
                + "returned from the battlefield to its owner's hand at the beginning" + " of the next end step.)";
        dashSpell.setStackDescription(card.getName() + " (Dash)");
        dashSpell.setDescription(desc);
        dashSpell.setBasicSpell(false);
        dashSpell.setPayCosts(dashCost);
        dashSpell.setDash(true);
        return dashSpell;
    }

    /**
     * make Awaken keyword
     * @param card
     * @param awakenKeyword
     * @return
     */
    private static SpellAbility makeAwakenSpell(final Card card, final String awakenKeyword) {
        final String[] k = awakenKeyword.split(":");
        final String counters = k[1];
        final String suffix = !counters.equals("1") ? "s" : "";
        final Cost awakenCost = new Cost(k[2], false);
        // Leave intrinsic Keyword for retrieval by Halimar Tidecaller
        //card.removeIntrinsicKeyword(awakenKeyword);

        final SpellAbility awakenSpell = card.getFirstSpellAbility().copy();

        // get the last subability of the spell to attach awaken in the end
        SpellAbility lastSub = awakenSpell;
        while (lastSub.getSubAbility() != null) {
            final AbilitySub copySubSA = ((AbilitySub) lastSub.getSubAbility()).getCopy();
            lastSub.setSubAbility(copySubSA);
            lastSub = copySubSA;
        }

        final String awaken = "DB$ PutCounter | CounterType$ P1P1 | CounterNum$ " + counters + " | "
                + "ValidTgts$ Land.YouCtrl | TgtPrompt$ Select target land you control | SubAbility$"
                + " AwakenAnimate";
        final String dbAnimate = "DB$ Animate | Defined$ Targeted | Power$ 0 | Toughness$ 0 | Types$"
                + " Creature,Elemental | Permanent$ True | Keywords$ Haste";
        card.setSVar("AwakenAnimate", dbAnimate);
        final AbilitySub awakenSub = (AbilitySub) AbilityFactory.getAbility(awaken, card);
        lastSub.setSubAbility(awakenSub);
        String desc = "Awaken " + counters + " - " + awakenCost.toSimpleString() + " (If you cast "
                + "this spell for " + awakenCost.toSimpleString() + ", also put " + counters + " +1/+1 counter"
                + suffix + " on target land you control and it becomes a 0/0 "
                + "Elemental creature with haste. It's still a land.)";
        awakenSpell.setDescription(desc);
        awakenSpell.setBasicSpell(false);
        awakenSpell.setPayCosts(awakenCost);
        return awakenSpell;
    }

    /**
     * <p>
     * hasKeyword.
     * </p>
     * 
     * @param c
     *            a {@link forge.game.card.Card} object.
     * @param k
     *            a {@link java.lang.String} object.
     * @return a int.
     */
    public static final int hasKeyword(final Card c, final String k) {
        return hasKeyword(c, k, 0);
    }

    /**
     * <p>
     * hasKeyword.
     * </p>
     * 
     * @param c
     *            a {@link forge.game.card.Card} object.
     * @param k
     *            a {@link java.lang.String} object.
     * @param startPos
     *            a int.
     * @return a int.
     */
    private static final int hasKeyword(final Card c, final String k, final int startPos) {
        final List<String> a = c.getKeywords();
        for (int i = startPos; i < a.size(); i++) {
            if (a.get(i).startsWith(k)) {
                return i;
            }
        }

        return -1;
    }

    /**
     * <p>
     * parseKeywords.
     * </p>
     * Pulling out the parsing of keywords so it can be used by the token
     * generator
     * 
     * @param card
     *            a {@link forge.game.card.Card} object.
     * @param cardName
     *            a {@link java.lang.String} object.
     * 
     */
    public static final void parseKeywords(final Card card, final String cardName) {
        if (hasKeyword(card, "Sunburst") != -1) {
            final GameCommand sunburstCIP = new GameCommand() {
                private static final long serialVersionUID = 1489845860231758299L;

                @Override
                public void run() {
                    if (card.isCreature()) {
                        card.addCounter(CounterType.P1P1, card.getSunburstValue(), true);
                    } else {
                        card.addCounter(CounterType.CHARGE, card.getSunburstValue(), true);
                    }

                }
            };

            final GameCommand sunburstLP = new GameCommand() {
                private static final long serialVersionUID = -7564420917490677427L;

                @Override
                public void run() {
                    card.setSunburstValue(0);
                }
            };

            card.addComesIntoPlayCommand(sunburstCIP);
            card.addLeavesPlayCommand(sunburstLP);
        }

        if (hasKeyword(card, "Devoid") != -1) {
            card.setColor("1");
        }

        if (hasKeyword(card, "Morph") != -1) {
            final int n = hasKeyword(card, "Morph");
            if (n != -1) {

                final String parse = card.getKeywords().get(n).toString();
                Map<String, String> sVars = card.getSVars();

                final String[] k = parse.split(":");
                final Cost cost = new Cost(k[1], true);

                card.addSpellAbility(abilityMorphDown(card));

                card.setState(CardStateName.FaceDown, false);

                card.addSpellAbility(abilityMorphUp(card, cost, false));
                card.setSVars(sVars); // for Warbreak Trumpeter.

                card.setState(CardStateName.Original, false);
            }

        } // Morph
        if (hasKeyword(card, "Megamorph") != -1) {
            final int n = hasKeyword(card, "Megamorph");
            if (n != -1) {

                final String parse = card.getKeywords().get(n).toString();
                Map<String, String> sVars = card.getSVars();

                final String[] k = parse.split(":");
                final Cost cost = new Cost(k[1], true);

                card.addSpellAbility(abilityMorphDown(card));

                card.setState(CardStateName.FaceDown, false);

                card.addSpellAbility(abilityMorphUp(card, cost, true));
                card.setSVars(sVars);
                card.setState(CardStateName.Original, false);
            }
        } // Megamorph

        if (hasKeyword(card, "Madness") != -1) {
            final int n = hasKeyword(card, "Madness");
            if (n != -1) {
                // Set Madness Replacement effects
                String repeffstr = "Event$ Discard | ActiveZones$ Hand | ValidCard$ Card.Self | "
                        + "ReplaceWith$ DiscardMadness | Secondary$ True | Description$ If you would"
                        + " discard this card, you discard it, but may exile it instead of putting it"
                        + " into your graveyard";
                ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true);
                card.addReplacementEffect(re);
                String sVarMadness = "DB$ Discard | Defined$ ReplacedPlayer"
                        + " | Mode$ Defined | DefinedCards$ ReplacedCard | Madness$ True";
                card.setSVar("DiscardMadness", sVarMadness);

                // Set Madness Triggers
                final String parse = card.getKeywords().get(n).toString();
                // card.removeIntrinsicKeyword(parse);
                final String[] k = parse.split(":");
                String trigStr = "Mode$ Discarded | ValidCard$ Card.Self | IsMadness$ True | "
                        + "Execute$ TrigPlayMadness | Secondary$ True | TriggerDescription$ " + "Play Madness - "
                        + card.getName();
                final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, true);
                card.addTrigger(myTrigger);
                String playMadness = "AB$ Play | Cost$ 0 | Defined$ Self | PlayMadness$ " + k[1]
                        + " | Optional$ True | SubAbility$ DBWasNotPlayMadness | RememberPlayed$ True";
                String moveToYard = "DB$ ChangeZone | Defined$ Self | Origin$ Exile | "
                        + "Destination$ Graveyard | ConditionDefined$ Remembered | ConditionPresent$"
                        + " Card | ConditionCompare$ EQ0 | SubAbility$ DBMadnessCleanup";
                String cleanUp = "DB$ Cleanup | ClearRemembered$ True";
                card.setSVar("TrigPlayMadness", playMadness);
                card.setSVar("DBWasNotPlayMadness", moveToYard);
                card.setSVar("DBMadnessCleanup", cleanUp);
            }
        } // madness

        if (hasKeyword(card, "Miracle") != -1) {
            final int n = hasKeyword(card, "Miracle");
            if (n != -1) {
                final String parse = card.getKeywords().get(n).toString();
                // card.removeIntrinsicKeyword(parse);

                final String[] k = parse.split(":");
                card.setMiracleCost(new Cost(k[1], false));
            }
        } // miracle

        if (hasKeyword(card, "Devour") != -1) {
            final int n = hasKeyword(card, "Devour");
            if (n != -1) {

                final String parse = card.getKeywords().get(n).toString();
                // card.removeIntrinsicKeyword(parse);

                final String[] k = parse.split(":");
                final String magnitude = k[1];

                String abStr = "DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Battlefield"
                        + " | Defined$ ReplacedCard | SubAbility$ DevourCleanup";
                String dbStr = "DB$ Sacrifice | Defined$ You | Amount$ DevourSacX | "
                        + "References$ DevourSacX | SacValid$ Creature.Other | SacMessage$ creature | "
                        + "RememberSacrificed$ True | Optional$ True | "
                        + "Devour$ True | SubAbility$ DevourCounters";
                String counterStr = "DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ DevourX"
                        + " | ETB$ True | References$ DevourX,DevourSize | SubAbility$ DevourETB";

                card.setSVar("DevourETB", abStr);
                card.setSVar("DevourSac", dbStr);
                card.setSVar("DevourSacX", "Count$Valid Creature.YouCtrl+Other");
                card.setSVar("DevourCounters", counterStr);
                card.setSVar("DevourX", "SVar$DevourSize/Times." + magnitude);
                card.setSVar("DevourSize", "Count$RememberedSize");
                card.setSVar("DevourCleanup", "DB$ Cleanup | ClearRemembered$ True");

                String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$ DevourSac";

                ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, true);
                re.setLayer(ReplacementLayer.Other);
                card.addReplacementEffect(re);
            }
        } // Devour

        if (hasKeyword(card, "Modular") != -1) {
            final int n = hasKeyword(card, "Modular");
            if (n != -1) {
                final String parse = card.getKeywords().get(n).toString();
                card.getKeywords().remove(parse);

                final int m = Integer.parseInt(parse.substring(8));

                card.addIntrinsicKeyword("etbCounter:P1P1:" + m + ":no Condition: " + "Modular " + m
                        + " (This enters the battlefield with " + m
                        + " +1/+1 counters on it. When it's put into a graveyard, "
                        + "you may put its +1/+1 counters on target artifact creature.)");

                final String abStr = "AB$ PutCounter | Cost$ 0 | References$ ModularX | ValidTgts$ Artifact.Creature | "
                        + "TgtPrompt$ Select target artifact creature | CounterType$ P1P1 | CounterNum$ ModularX";
                card.setSVar("ModularTrig", abStr);
                card.setSVar("ModularX", "TriggeredCard$CardCounters.P1P1");

                String trigStr = "Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Graveyard"
                        + " | OptionalDecider$ TriggeredCardController | TriggerController$ TriggeredCardController | Execute$ ModularTrig | "
                        + "Secondary$ True | TriggerDescription$ When CARDNAME is put into a graveyard from the battlefield, "
                        + "you may put a +1/+1 counter on target artifact creature for each +1/+1 counter on CARDNAME";
                final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, true);
                card.addTrigger(myTrigger);
            }
        } // Modular

        /*
         * WARNING: must keep this keyword processing before etbCounter keyword
         * processing.
         */
        final int graft = hasKeyword(card, "Graft");
        if (graft != -1) {
            final String parse = card.getKeywords().get(graft).toString();

            final int m = Integer.parseInt(parse.substring(6));
            final String abStr = "AB$ MoveCounter | Cost$ 0 | Source$ Self | "
                    + "Defined$ TriggeredCardLKICopy | CounterType$ P1P1 | CounterNum$ 1";
            card.setSVar("GraftTrig", abStr);

            String trigStr = "Mode$ ChangesZone | ValidCard$ Creature.Other | "
                    + "Origin$ Any | Destination$ Battlefield"
                    + " | TriggerZones$ Battlefield | OptionalDecider$ You | "
                    + "IsPresent$ Card.Self+counters_GE1_P1P1 | " + "Execute$ GraftTrig | TriggerDescription$ "
                    + "Whenever another creature enters the battlefield, you "
                    + "may move a +1/+1 counter from this creature onto it.";
            final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, true);
            card.addTrigger(myTrigger);

            card.addIntrinsicKeyword("etbCounter:P1P1:" + m);
        }

        final int bloodthirst = hasKeyword(card, "Bloodthirst");
        if (bloodthirst != -1) {
            final String numCounters = card.getKeywords().get(bloodthirst).split(" ")[1];
            String desc = "Bloodthirst " + numCounters
                    + " (If an opponent was dealt damage this turn, this creature enters the battlefield with "
                    + numCounters + " +1/+1 counters on it.)";
            if (numCounters.equals("X")) {
                desc = "Bloodthirst X (This creature enters the battlefield with X +1/+1 counters on it, "
                        + "where X is the damage dealt to your opponents this turn.)";
                card.setSVar("X", "Count$BloodthirstAmount");
            }

            card.addIntrinsicKeyword("etbCounter:P1P1:" + numCounters + ":Bloodthirst$ True:" + desc);
        } // bloodthirst

        final int storm = card.getAmountOfKeyword("Storm");
        for (int i = 0; i < storm; i++) {
            final StringBuilder trigScript = new StringBuilder(
                    "Mode$ SpellCast | ValidCard$ Card.Self | Execute$ Storm "
                            + "| TriggerDescription$ Storm (When you cast this spell, "
                            + "copy it for each spell cast before it this turn.)");

            card.setSVar("Storm",
                    "AB$ CopySpellAbility | Cost$ 0 | Defined$ TriggeredSpellAbility | Amount$ StormCount | References$ StormCount");
            card.setSVar("StormCount", "TriggerCount$CurrentStormCount/Minus.1");
            final Trigger stormTrigger = TriggerHandler.parseTrigger(trigScript.toString(), card, true);

            card.addTrigger(stormTrigger);
        } // Storm
        final int cascade = card.getAmountOfKeyword("Cascade");
        for (int i = 0; i < cascade; i++) {
            final StringBuilder trigScript = new StringBuilder(
                    "Mode$ SpellCast | ValidCard$ Card.Self | Execute$ TrigCascade | Secondary$ "
                            + "True | TriggerDescription$ Cascade - CARDNAME");

            final String abString = "AB$ DigUntil | Cost$ 0 | Defined$ You | Amount$ 1 | Valid$ "
                    + "Card.nonLand+cmcLTCascadeX | FoundDestination$ Exile | RevealedDestination$"
                    + " Exile | References$ CascadeX | ImprintRevealed$ True | RememberFound$ True"
                    + " | SubAbility$ CascadeCast";
            final String dbCascadeCast = "DB$ Play | Defined$ Remembered | WithoutManaCost$ True | "
                    + "Optional$ True | SubAbility$ CascadeMoveToLib";
            final String dbMoveToLib = "DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered,Card.IsImprinted"
                    + " | Origin$ Exile | Destination$ Library | RandomOrder$ True | LibraryPosition$ -1"
                    + " | SubAbility$ CascadeCleanup";
            card.setSVar("TrigCascade", abString);
            card.setSVar("CascadeCast", dbCascadeCast);
            card.setSVar("CascadeMoveToLib", dbMoveToLib);
            card.setSVar("CascadeX", "Count$CardManaCost");
            card.setSVar("CascadeCleanup", "DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True");
            final Trigger cascadeTrigger = TriggerHandler.parseTrigger(trigScript.toString(), card, true);

            card.addTrigger(cascadeTrigger);
        } // Cascade

        if (hasKeyword(card, "Recover") != -1) {
            final String recoverCost = card.getKeywords().get(card.getKeywordPosition("Recover")).split(":")[1];
            final String abStr = "AB$ ChangeZone | Cost$ 0 | Defined$ Self"
                    + " | Origin$ Graveyard | Destination$ Hand | UnlessCost$ " + recoverCost
                    + " | UnlessPayer$ You | UnlessSwitched$ True"
                    + " | UnlessResolveSubs$ WhenNotPaid | SubAbility$ RecoverExile";
            card.setSVar("RecoverTrig", abStr);
            card.setSVar("RecoverExile",
                    "DB$ ChangeZone | Defined$ Self" + " | Origin$ Graveyard | Destination$ Exile");
            String trigObject = card.isCreature() ? "Creature.Other+YouOwn" : "Creature.YouOwn";
            String trigArticle = card.isCreature() ? "another" : "a";
            String trigStr = "Mode$ ChangesZone | ValidCard$ " + trigObject
                    + " | Origin$ Battlefield | Destination$ Graveyard | "
                    + "TriggerZones$ Graveyard | Execute$ RecoverTrig | " + "TriggerDescription$ When "
                    + trigArticle + " creature is " + "put into your graveyard from the battlefield, you "
                    + "may pay " + recoverCost + ". If you do, return "
                    + "CARDNAME from your graveyard to your hand. Otherwise,"
                    + " exile CARDNAME. | Secondary$ True";
            final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, true);
            card.addTrigger(myTrigger);
        } // Recover

        int ripplePos = hasKeyword(card, "Ripple");
        while (ripplePos != -1) {
            final int n = ripplePos;
            final String parse = card.getKeywords().get(n);
            final String[] k = parse.split(":");
            final int num = Integer.parseInt(k[1]);
            final String triggerSvar = "DBRipple";

            final String actualTrigger = "Mode$ SpellCast | ValidCard$ Card.Self | " + "Execute$ " + triggerSvar
                    + " | Secondary$ True | TriggerDescription$" + " Ripple " + num
                    + " - CARDNAME | OptionalDecider$ You";
            final String abString = "AB$ Dig | Cost$ 0 | NoMove$ True | DigNum$ " + num
                    + " | Reveal$ True | RememberRevealed$ True | SubAbility$ DBCastRipple";
            final String dbCast = "DB$ Play | Valid$ Card.IsRemembered+sameName | "
                    + "ValidZone$ Library | WithoutManaCost$ True | Optional$ True | "
                    + "Amount$ All | SubAbility$ RippleMoveToBottom";

            card.setSVar(triggerSvar.toString(), abString);
            card.setSVar("DBCastRipple", dbCast);
            card.setSVar("RippleMoveToBottom",
                    "DB$ ChangeZoneAll | ChangeType$ "
                            + "Card.IsRemembered | Origin$ Library | Destination$ Library | "
                            + "LibraryPosition$ -1 | SubAbility$ RippleCleanup");
            card.setSVar("RippleCleanup", "DB$ Cleanup | ClearRemembered$ True");

            final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, true);
            card.addTrigger(parsedTrigger);

            ripplePos = hasKeyword(card, "Ripple", n + 1);
        } // Ripple

        final int dethrone = card.getAmountOfKeyword("Dethrone");
        card.removeIntrinsicKeyword("Dethrone");
        for (int i = 0; i < dethrone; i++) {
            final StringBuilder trigScript = new StringBuilder(
                    "Mode$ Attacks | ValidCard$ Card.Self | Attacked$ Player.withMostLife | "
                            + "TriggerZones$ Battlefield | Execute$ DethroneCounters | TriggerDescription$"
                            + " Dethrone (Whenever this creature attacks the player with the most life or "
                            + "tied for the most life, put a +1/+1 counter on it.)");

            final String abString = "DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | " + "CounterNum$ 1";
            card.setSVar("DethroneCounters", abString);
            final Trigger cascadeTrigger = TriggerHandler.parseTrigger(trigScript.toString(), card, true);
            card.addTrigger(cascadeTrigger);
        } // Dethrone
        final int prowess = card.getAmountOfKeyword("Prowess");
        card.removeIntrinsicKeyword("Prowess");
        final StringBuilder trigProwess = new StringBuilder(
                "Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ You | "
                        + "Execute$ ProwessPump | TriggerZones$ Battlefield | TriggerDescription$ "
                        + "Prowess (Whenever you cast a noncreature spell, this creature gets +1/+1 "
                        + "until end of turn.)");

        final String abStringProwess = "DB$ Pump | Defined$ Self | NumAtt$ +1 | NumDef$ +1";
        card.setSVar("ProwessPump", abStringProwess);
        final Trigger prowessTrigger = TriggerHandler.parseTrigger(trigProwess.toString(), card, true);
        for (int i = 0; i < prowess; i++) {
            card.addTrigger(prowessTrigger);
            card.setSVar("BuffedBy", "Card.nonCreature+nonLand"); // for the AI
        } // Prowess
        final int exploit = card.getAmountOfKeyword("Exploit");
        card.removeIntrinsicKeyword("Exploit");
        final StringBuilder trigExploit = new StringBuilder(
                "Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield"
                        + " | Execute$ ExploitSac | TriggerDescription$ Exploit (When this creature enters"
                        + " the battlefield, you may sacrifice a creature.)");
        final String abStringExploit = "DB$ Sacrifice | SacValid$ Creature | Exploit$ True | Optional$ True";
        card.setSVar("ExploitSac", abStringExploit);
        final Trigger exploitTrigger = TriggerHandler.parseTrigger(trigExploit.toString(), card, true);
        for (int i = 0; i < exploit; i++) {
            card.addTrigger(exploitTrigger);
        } // Exploit
        final int ingest = card.getAmountOfKeyword("Ingest");
        card.removeIntrinsicKeyword("Ingest");
        final StringBuilder trigIngest = new StringBuilder(
                "Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True"
                        + " | Execute$ IngestExile | TriggerZones$ Battlefield | TriggerDescription$ Ingest "
                        + "(Whenever this creature deals combat damage to a player, that player exiles the "
                        + "top card of his or her library.)");
        final String abStringIngest = "DB$ Mill | NumCards$ 1 | Destination$ Exile | Defined$ TriggeredTarget";
        card.setSVar("IngestExile", abStringIngest);
        final Trigger ingestTrigger = TriggerHandler.parseTrigger(trigIngest.toString(), card, true);
        for (int i = 0; i < ingest; i++) {
            card.addTrigger(ingestTrigger);
        } // Ingest
    }

    public final static void refreshTotemArmor(Card c) {
        boolean hasKw = c.hasKeyword("Totem armor");

        CardState state = c.getCurrentState();
        FCollectionView<ReplacementEffect> res = state.getReplacementEffects();
        for (int ix = 0; ix < res.size(); ix++) {
            ReplacementEffect re = res.get(ix);
            if (re.getMapParams().containsKey("TotemArmor")) {
                if (hasKw) {
                    return;
                } // has re and kw - nothing to do here
                state.removeReplacementEffect(re);
                ix--;
            }
        }

        if (hasKw) {
            ReplacementEffect re = ReplacementHandler.parseReplacement(
                    "Event$ Destroy | ActiveZones$ Battlefield | ValidCard$ Card.EnchantedBy | ReplaceWith$ RegenTA | Secondary$ True | TotemArmor$ True | Description$ Totem armor - "
                            + c,
                    c, true);
            c.getSVars().put("RegenTA",
                    "AB$ DealDamage | Cost$ 0 | Defined$ ReplacedCard | Remove$ All | SubAbility$ DestroyMe");
            c.getSVars().put("DestroyMe", "DB$ Destroy | Defined$ Self");
            state.addReplacementEffect(re);
        }
    }
}