package mage;

import mage.abilities.condition.Condition;
import mage.constants.ColoredManaSymbol;
import mage.constants.ManaType;
import mage.filter.FilterMana;
import mage.util.CardUtil;
import mage.util.Copyable;
import org.apache.log4j.Logger;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;

/**
 * WARNING, all mana operations must use overflow check, see usage of CardUtil.addWithOverflowCheck and same methods
 *
 * @author BetaSteward_at_googlemail.com, JayDi85
 */
public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {

    private static final Logger logger = Logger.getLogger(Mana.class);

    protected int white;
    protected int blue;
    protected int black;
    protected int red;
    protected int green;
    protected int generic;
    protected int colorless;
    protected int any;
    protected boolean flag;

    /**
     * Default constructor. Creates a {@link Mana} object with 0 values.
     */
    public Mana() {
        white = 0;
        blue = 0;
        black = 0;
        red = 0;
        green = 0;
        generic = 0;
        colorless = 0;
        any = 0;
        flag = false;
    }

    /**
     * Creates a {@link Mana} object with the passed in values. Values can not
     * be less than 0. Any values less than 0 will be logged and set to 0.
     *
     * @param white     total White mana to have.
     * @param blue      total Blue mana to have.
     * @param black     total Black mana to have.
     * @param red       total Red mana to have.
     * @param green     total Green mana to have.
     * @param generic   total Generic mana to have.
     * @param any       total Any mana to have.
     * @param colorless total Colorless mana to have.
     */
    public Mana(final int white, final int blue, final int black, final int red, final int green, final int generic, final int any, final int colorless) {
        this.white = notNegative(white, "White");
        this.blue = notNegative(blue, "Blue");
        this.black = notNegative(black, "Black");
        this.red = notNegative(red, "Red");
        this.green = notNegative(green, "Green");
        this.generic = notNegative(generic, "Generic");
        this.colorless = notNegative(colorless, "Colorless");
        this.any = notNegative(any, "Any");
        this.flag = false;
    }

    /**
     * Copy constructor. Creates a {@link Mana} object from existing
     * {@link Mana}
     *
     * @param mana object to create copy from.
     */
    protected Mana(final Mana mana) {
        Objects.requireNonNull(mana, "The passed in mana can not be null");
        this.white = mana.white;
        this.blue = mana.blue;
        this.black = mana.black;
        this.red = mana.red;
        this.green = mana.green;
        this.generic = mana.generic;
        this.colorless = mana.colorless;
        this.any = mana.any;
        this.flag = mana.flag;
    }


    public Mana(final ColoredManaSymbol color) {
        this(color, 1);
    }

    /**
     * Creates {@link Mana} object from {@link ColoredManaSymbol}. Created
     * {@link Mana} will have a single mana of the passed in
     * {@link ColoredManaSymbol} color.
     *
     * @param color  the color to create the {@link Mana} object with.
     * @param amount the number of mana to add of the given color
     */

    public Mana(final ColoredManaSymbol color, int amount) {
        this();
        Objects.requireNonNull(color, "The passed in ColoredManaSymbol can not be null");
        switch (color) {
            case W:
                white = CardUtil.overflowInc(white, amount);
                break;
            case U:
                blue = CardUtil.overflowInc(blue, amount);
                break;
            case B:
                black = CardUtil.overflowInc(black, amount);
                break;
            case R:
                red = CardUtil.overflowInc(red, amount);
                break;
            case G:
                green = CardUtil.overflowInc(green, amount);
                break;
            default:
                throw new IllegalArgumentException("Unknown mana color: " + color);
        }
    }

    /**
     * Creates a {@link Mana} object of one mana of the passed {@link ManaType}.
     *
     * @param manaType The type of mana to set to one.
     */
    public Mana(final ManaType manaType) {
        this();
        Objects.requireNonNull(manaType, "The passed in ManaType can not be null");
        switch (manaType) {
            case WHITE:
                white = CardUtil.overflowInc(white, 1);
                break;
            case BLUE:
                blue = CardUtil.overflowInc(blue, 1);
                break;
            case BLACK:
                black = CardUtil.overflowInc(black, 1);
                break;
            case RED:
                red = CardUtil.overflowInc(red, 1);
                break;
            case GREEN:
                green = CardUtil.overflowInc(green, 1);
                break;
            case COLORLESS:
                colorless = CardUtil.overflowInc(colorless, 1);
                break;
            case GENERIC:
                generic = CardUtil.overflowInc(generic, 1);
                break;
            default:
                throw new IllegalArgumentException("Unknown manaType: " + manaType);
        }
    }

    /**
     * Creates a {@link Mana} object of #num mana of the passed {@link ManaType}.
     *
     * @param manaType The type of mana to set.
     * @param num      The number of mana available of the passed ManaType.
     **/
    public Mana(final ManaType manaType, int num) {
        this();
        Objects.requireNonNull(manaType, "The passed in ManaType can not be null");
        switch (manaType) {
            case WHITE:
                white = CardUtil.overflowInc(white, num);
                break;
            case BLUE:
                blue = CardUtil.overflowInc(blue, num);
                break;
            case BLACK:
                black = CardUtil.overflowInc(black, num);
                break;
            case RED:
                red = CardUtil.overflowInc(red, num);
                break;
            case GREEN:
                green = CardUtil.overflowInc(green, num);
                break;
            case COLORLESS:
                colorless = CardUtil.overflowInc(colorless, num);
                break;
            case GENERIC:
                generic = CardUtil.overflowInc(generic, num);
                break;
            default:
                throw new IllegalArgumentException("Unknown manaType: " + manaType);
        }
    }

    /**
     * Creates a {@link Mana} object with the passed in {@code num} of White
     * mana. {@code num} can not be a negative value. Negative values will be
     * logged and set to 0.
     *
     * @param num value of White mana to create.
     * @return a {@link Mana} object with the passed in {@code num} of White
     * mana.
     */
    public static Mana WhiteMana(int num) {
        return new Mana(notNegative(num, "White"), 0, 0, 0, 0, 0, 0, 0);
    }

    /**
     * Creates a {@link Mana} object with the passed in {@code num} of Blue
     * mana. {@code num} can not be a negative value. Negative values will be
     * logged and set to 0.
     *
     * @param num value of Blue mana to create.
     * @return a {@link Mana} object with the passed in {@code num} of Blue
     * mana.
     */
    public static Mana BlueMana(int num) {
        return new Mana(0, notNegative(num, "Blue"), 0, 0, 0, 0, 0, 0);
    }

    /**
     * Creates a {@link Mana} object with the passed in {@code num} of Black
     * mana. {@code num} can not be a negative value. Negative values will be
     * logged and set to 0.
     *
     * @param num value of Black mana to create.
     * @return a {@link Mana} object with the passed in {@code num} of Black
     * mana.
     */
    public static Mana BlackMana(int num) {
        return new Mana(0, 0, notNegative(num, "Black"), 0, 0, 0, 0, 0);
    }

    /**
     * Creates a {@link Mana} object with the passed in {@code num} of Red mana.
     * {@code num} can not be a negative value. Negative values will be logged
     * and set to 0.
     *
     * @param num value of Red mana to create.
     * @return a {@link Mana} object with the passed in {@code num} of Red mana.
     */
    public static Mana RedMana(int num) {
        return new Mana(0, 0, 0, notNegative(num, "Red"), 0, 0, 0, 0);
    }

    /**
     * Creates a {@link Mana} object with the passed in {@code num} of Green
     * mana. {@code num} can not be a negative value. Negative values will be
     * logged and set to 0.
     *
     * @param num value of Green mana to create.
     * @return a {@link Mana} object with the passed in {@code num} of Green
     * mana.
     */
    public static Mana GreenMana(int num) {
        return new Mana(0, 0, 0, 0, notNegative(num, "Green"), 0, 0, 0);
    }

    /**
     * Creates a {@link Mana} object with the passed in {@code num} of Generic
     * mana. {@code num} can not be a negative value. Negative values will be
     * logged and set to 0.
     *
     * @param num value of Generic mana to create.
     * @return a {@link Mana} object with the passed in {@code num} of Generic
     * mana.
     */
    public static Mana GenericMana(int num) {
        return new Mana(0, 0, 0, 0, 0, notNegative(num, "Generic"), 0, 0);
    }

    /**
     * Creates a {@link Mana} object with the passed in {@code num} of Colorless
     * mana. {@code num} can not be a negative value. Negative values will be
     * logged and set to 0.
     *
     * @param num value of Colorless mana to create.
     * @return a {@link Mana} object with the passed in {@code num} of Colorless
     * mana.
     */
    public static Mana ColorlessMana(int num) {
        return new Mana(0, 0, 0, 0, 0, 0, 0, notNegative(num, "Colorless"));
    }

    /**
     * Creates a {@link Mana} object with the passed in {@code num} of Any mana.
     * {@code num} can not be a negative value. Negative values will be logged
     * and set to 0.
     *
     * @param num value of Any mana to create.
     * @return a {@link Mana} object with the passed in {@code num} of Any mana.
     */
    public static Mana AnyMana(int num) {
        return new Mana(0, 0, 0, 0, 0, 0, notNegative(num, "Any"), 0);
    }

    /**
     * Adds mana from the passed in {@link Mana} object to this object.
     * Ignores conditions from conditional mana
     *
     * @param mana mana to add to this object.
     */
    public void add(final Mana mana) {
        white = CardUtil.overflowInc(white, mana.white);
        blue = CardUtil.overflowInc(blue, mana.blue);
        black = CardUtil.overflowInc(black, mana.black);
        red = CardUtil.overflowInc(red, mana.red);
        green = CardUtil.overflowInc(green, mana.green);
        generic = CardUtil.overflowInc(generic, mana.generic);
        colorless = CardUtil.overflowInc(colorless, mana.colorless);
        any = CardUtil.overflowInc(any, mana.any);
    }

    /**
     * Increases this mana by one of the passed in ManaType.
     *
     * @param manaType the type of mana to increase by one.
     */
    public void increase(ManaType manaType) {
        increaseOrDecrease(manaType, true);
    }

    /**
     * Decreases this mana by onw of the passed in ManaType.
     *
     * @param manaType the type of mana to increase by one.
     */
    public void decrease(ManaType manaType) {
        increaseOrDecrease(manaType, false);
    }

    /**
     * Helper function for increase and decrease to not have the code duplicated.
     *
     * @param manaType
     * @param increase
     */
    private void increaseOrDecrease(ManaType manaType, boolean increase) {
        BiFunction<Integer, Integer, Integer> overflowIncOrDec = increase ? CardUtil::overflowInc : CardUtil::overflowDec;
        switch (manaType) {
            case WHITE:
                white = overflowIncOrDec.apply(white, 1);
                break;
            case BLUE:
                blue = overflowIncOrDec.apply(blue, 1);
                break;
            case BLACK:
                black = overflowIncOrDec.apply(black, 1);
                break;
            case RED:
                red = overflowIncOrDec.apply(red, 1);
                break;
            case GREEN:
                green = overflowIncOrDec.apply(green, 1);
                break;
            case COLORLESS:
                colorless = overflowIncOrDec.apply(colorless, 1);
                break;
            case GENERIC:
                generic = overflowIncOrDec.apply(generic, 1);
                break;
        }
    }

    /**
     * Increases the White mana by one.
     */
    public void increaseWhite() {
        white = CardUtil.overflowInc(white, 1);
    }

    /**
     * Increases the Blue mana by one.
     */
    public void increaseBlue() {
        blue = CardUtil.overflowInc(blue, 1);
    }

    /**
     * Increases the Black mana by one.
     */
    public void increaseBlack() {
        black = CardUtil.overflowInc(black, 1);
    }

    /**
     * Increases the Red mana by one.
     */
    public void increaseRed() {
        red = CardUtil.overflowInc(red, 1);
    }

    /**
     * Increases the Green mana by one.
     */
    public void increaseGreen() {
        green = CardUtil.overflowInc(green, 1);
    }

    /**
     * Increases the Generic mana by one.
     */
    public void increaseGeneric() {
        generic = CardUtil.overflowInc(generic, 1);
    }

    /**
     * Increases the Colorless mana by one.
     */
    public void increaseColorless() {
        colorless = CardUtil.overflowInc(colorless, 1);
    }

    public void increaseAny() {
        any = CardUtil.overflowInc(any, 1);
    }

    public void decreaseAny() {
        any = CardUtil.overflowDec(any, 1);
    }

    /**
     * Subtracts the passed in mana values from this instance.
     *
     * @param mana mana values to subtract
     */
    public void subtract(final Mana mana) {
        white = CardUtil.overflowDec(white, mana.white);
        blue = CardUtil.overflowDec(blue, mana.blue);
        black = CardUtil.overflowDec(black, mana.black);
        red = CardUtil.overflowDec(red, mana.red);
        green = CardUtil.overflowDec(green, mana.green);
        generic = CardUtil.overflowDec(generic, mana.generic);
        colorless = CardUtil.overflowDec(colorless, mana.colorless);
        any = CardUtil.overflowDec(any, mana.any);
    }

    /**
     * Mana must contains only positive values
     */
    public boolean isValid() {
        return white >= 0
                && blue >= 0
                && black >= 0
                && red >= 0
                && green >= 0
                && generic >= 0
                && colorless >= 0
                && any >= 0;
    }

    /**
     * Subtracts the passed in mana values from this instance. The difference
     * between this and {@code subtract()} is that if we do not have the
     * available generic mana to pay, we take mana from our colored mana pools.
     *
     * @param mana mana values to subtract
     * @throws ArithmeticException thrown if there is not enough available
     *                             mana to pay the generic cost
     */
    public void subtractCost(final Mana mana) throws ArithmeticException {
        this.subtract(mana);

        // Handle any unpaid generic mana costs
        while (generic < 0) {
            int oldColorless = generic;
            if (white > 0) {
                white = CardUtil.overflowDec(white, 1);
                generic = CardUtil.overflowInc(generic, 1);
                continue;
            }
            if (blue > 0) {
                blue = CardUtil.overflowDec(blue, 1);
                generic = CardUtil.overflowInc(generic, 1);
                continue;
            }
            if (black > 0) {
                black = CardUtil.overflowDec(black, 1);
                generic = CardUtil.overflowInc(generic, 1);
                continue;
            }
            if (red > 0) {
                red = CardUtil.overflowDec(red, 1);
                generic = CardUtil.overflowInc(generic, 1);
                continue;
            }
            if (green > 0) {
                green = CardUtil.overflowDec(green, 1);
                generic = CardUtil.overflowInc(generic, 1);
                continue;
            }
            if (colorless > 0) {
                colorless = CardUtil.overflowDec(colorless, 1);
                generic = CardUtil.overflowInc(generic, 1);
                continue;
            }
            if (any > 0) {
                any = CardUtil.overflowDec(any, 1);
                generic = CardUtil.overflowInc(generic, 1);
                continue;
            }
            if (oldColorless == generic) {
                throw new ArithmeticException("Not enough mana to pay colorless");
            }
        }
    }

    /**
     * Returns the total count of all combined mana.
     *
     * @return the total count of all combined mana.
     */
    public int count() {
        int sum = countColored();
        sum = CardUtil.overflowInc(sum, generic);
        sum = CardUtil.overflowInc(sum, colorless);

        return sum;
    }

    /**
     * Returns the total count of all colored mana.
     *
     * @return the total count of all colored mana.
     */
    public int countColored() {
        int sum = CardUtil.overflowInc(white, blue);
        sum = CardUtil.overflowInc(sum, black);
        sum = CardUtil.overflowInc(sum, red);
        sum = CardUtil.overflowInc(sum, green);
        sum = CardUtil.overflowInc(sum, any);

        return sum;
    }

    /**
     * Returns the count of filtered mana provided by the passed in
     * {@link FilterMana}. If {@link FilterMana} is null, the total mana count
     * is returned via {@link #count() count}.
     *
     * @param filter the colors of mana to return the count for.
     * @return the count of filtered mana provided by the passed in
     * {@link FilterMana}.
     */
    public int count(final FilterMana filter) {
        if (filter == null) {
            return count();
        }
        int count = 0;
        if (filter.isWhite()) {
            count = CardUtil.overflowInc(count, white);
        }
        if (filter.isBlue()) {
            count = CardUtil.overflowInc(count, blue);
        }
        if (filter.isBlack()) {
            count = CardUtil.overflowInc(count, black);
        }
        if (filter.isRed()) {
            count = CardUtil.overflowInc(count, red);
        }
        if (filter.isGreen()) {
            count = CardUtil.overflowInc(count, green);
        }
        if (filter.isGeneric()) {
            count = CardUtil.overflowInc(count, generic);
        }
        if (filter.isColorless()) {
            count = CardUtil.overflowInc(count, colorless);
        }
        return count;
    }

    /**
     * Sets all mana to 0.
     */
    public void clear() {
        white = 0;
        blue = 0;
        black = 0;
        red = 0;
        green = 0;
        generic = 0;
        colorless = 0;
        any = 0;
    }

    /**
     * Used in order to reorder mana combinations so they're returned in the order found on cards.
     */
    private static final Map<String, String> colorLetterMap = new HashMap<>();

    static {
        colorLetterMap.put("WR", "RW");
        colorLetterMap.put("WG", "GW");
        colorLetterMap.put("UG", "GU");
        colorLetterMap.put("WRG", "RGW");
        colorLetterMap.put("WUG", "GWU");
        colorLetterMap.put("WUR", "URW");
        colorLetterMap.put("URG", "GUR");
        colorLetterMap.put("UBG", "BGU");
        colorLetterMap.put("WBG", "RWB");
        colorLetterMap.put("WBRG", "BRGW");
        colorLetterMap.put("WURG", "RGWU");
        colorLetterMap.put("WUBG", "GWUB");
    }

    /**
     * The use of a StringBuilder was
     *
     * @return
     */
    private String getColorsInOrder() {
        StringBuilder sb = new StringBuilder();
        if (white > 0) {
            sb.append("W");
        }
        if (blue > 0) {
            sb.append("U");
        }
        if (black > 0) {
            sb.append("B");
        }
        if (red > 0) {
            sb.append("R");
        }
        if (green > 0) {
            sb.append("G");
        }
        String manaString = sb.toString();
        return colorLetterMap.getOrDefault(manaString, manaString);
    }

    private int colorCharToAmount(char color) {
        switch (color) {
            case 'W':
                return white;
            case 'U':
                return blue;
            case 'B':
                return black;
            case 'R':
                return red;
            case 'G':
                return green;
        }
        return 0;
    }

    /**
     * Returns this objects values as a {@link String}.
     *
     * @return this objects values as a {@link String}.
     */
    @Override
    public String toString() {
        StringBuilder sbMana = new StringBuilder();

        if (generic > 0) {
            sbMana.append('{').append(generic).append('}');
        }

        // normal mana
        if (colorless < 20) {
            for (int i = 0; i < colorless; i++) {
                sbMana.append("{C}");
            }
        } else {
            sbMana.append(colorless).append("{C}");
        }

        String colorsInOrder = getColorsInOrder();
        for (char c : colorsInOrder.toCharArray()) {
            int amount = colorCharToAmount(c);
            if (amount < 20) {
                for (int i = 0; i < amount; i++) {
                    sbMana.append('{').append(c).append('}');
                }
            } else {
                sbMana.append(amount).append('{').append(c).append('}');
            }
        }

        if (any < 20) {
            for (int i = 0; i < any; i++) {
                sbMana.append("{Any}");
            }
        } else {
            sbMana.append(any).append("{Any}");
        }

        return sbMana.toString();
    }

    /**
     * Returns a deep copy of this object.
     *
     * @return a deep copy of this object.
     */
    @Override
    public Mana copy() {
        return new Mana(this);
    }

    /**
     * Returns if the cost (this) can be paid by the mana provided by the passed in {@link Mana} object.
     *
     * @param avail The mana to compare too.
     * @return boolean indicating if there is enough available mana to pay.
     */
    public boolean enough(final Mana avail) {
        Mana compare = avail.copy();

        // Subtract the mana cost (this) from the mana available (compare).
        // This will only subtract like mana types from one another (e.g. green from green, coloreless from colorless).
        compare.subtract(this);

        // A negative value for compare.X means that mana of type X from the cost could not be paid by mana
        // of the same kind from the available mana.
        // Check each of the types, and see if there is enough mana of any color left to pay for the colors.

        if (compare.colorless < 0) { // Put first to shortcut the calculations
            // Colorless mana can only be paid by colorless mana.
            // If there's a negative value, then there's nothing else that can be used to pay for it.
            return false;
        }
        if (compare.white < 0) {
            compare.any = CardUtil.overflowInc(compare.any, compare.white);
            // A negatice value means that there was more mana of the given type required than there was mana of any
            // color to pay for it.
            // So, there is not enough mana to pay the avail.
            if (compare.any < 0) {
                return false;
            }
            compare.white = 0;
        }
        if (compare.blue < 0) {
            compare.any = CardUtil.overflowInc(compare.any, compare.blue);
            if (compare.any < 0) {
                return false;
            }
            compare.blue = 0;
        }
        if (compare.black < 0) {
            compare.any = CardUtil.overflowInc(compare.any, compare.black);
            if (compare.any < 0) {
                return false;
            }
            compare.black = 0;
        }
        if (compare.red < 0) {
            compare.any = CardUtil.overflowInc(compare.any, compare.red);
            if (compare.any < 0) {
                return false;
            }
            compare.red = 0;
        }
        if (compare.green < 0) {
            compare.any = CardUtil.overflowInc(compare.any, compare.green);
            if (compare.any < 0) {
                return false;
            }
            compare.green = 0;
        }
        if (compare.generic < 0) {
            compare.generic = CardUtil.overflowInc(compare.generic, compare.white);
            compare.generic = CardUtil.overflowInc(compare.generic, compare.blue);
            compare.generic = CardUtil.overflowInc(compare.generic, compare.black);
            compare.generic = CardUtil.overflowInc(compare.generic, compare.red);
            compare.generic = CardUtil.overflowInc(compare.generic, compare.green);
            compare.generic = CardUtil.overflowInc(compare.generic, compare.colorless);
            compare.generic = CardUtil.overflowInc(compare.generic, compare.any);
            return compare.generic >= 0;
        }
        return true;
    }

    /**
     * Returns the total mana needed to meet the cost of this given the available mana passed in
     * as a {@link Mana} object.
     * <p>
     * Used by the AI to calculate what mana it needs to obtain for a spell to become playable.
     *
     * @param avail the mana available to pay the cost
     * @return the total mana needed to pay this given the available mana passed in as a {@link Mana} object.
     */
    public Mana needed(final Mana avail) {
        Mana compare = avail.copy();

        // Subtract the mana cost (this) from the mana available (compare).
        // This will only subtract like mana types from one another (e.g. green from green, coloreless from colorless).
        compare.subtract(this);

        // A negative value for compare.X means that mana of type X from the cost could not be paid by mana
        // of the same kind from the available mana (this).
        if (compare.white < 0 && compare.any > 0) {
            // Calculate how much of the unpaid colored mana can be covered by mana of any color
            int diff = Math.min(compare.any, Math.abs(compare.white));
            // Make the payment
            compare.any = CardUtil.overflowDec(compare.any, diff);
            compare.white = CardUtil.overflowInc(compare.white, diff);
        }
        if (compare.blue < 0 && compare.any > 0) {
            int diff = Math.min(compare.any, Math.abs(compare.blue));
            compare.any = CardUtil.overflowDec(compare.any, diff);
            compare.blue = CardUtil.overflowInc(compare.blue, diff);
        }
        if (compare.black < 0 && compare.any > 0) {
            int diff = Math.min(compare.any, Math.abs(compare.black));
            compare.any = CardUtil.overflowDec(compare.any, diff);
            compare.black = CardUtil.overflowInc(compare.black, diff);
        }
        if (compare.red < 0 && compare.any > 0) {
            int diff = Math.min(compare.any, Math.abs(compare.red));
            compare.any = CardUtil.overflowDec(compare.any, diff);
            compare.red = CardUtil.overflowInc(compare.red, diff);
        }
        if (compare.green < 0 && compare.any > 0) {
            int diff = Math.min(compare.any, Math.abs(compare.green));
            compare.any = CardUtil.overflowDec(compare.any, diff);
            compare.green = CardUtil.overflowInc(compare.green, diff);
        }

        // Colorless mana can only be paid by colorless sources, so a check for it is not performed.

        if (compare.generic < 0) {
            // Calculate total leftover mana available to pay for generic costs
            int remaining = 0;
            remaining = CardUtil.overflowInc(remaining, Math.max(0, compare.white));
            remaining = CardUtil.overflowInc(remaining, Math.max(0, compare.blue));
            remaining = CardUtil.overflowInc(remaining, Math.max(0, compare.black));
            remaining = CardUtil.overflowInc(remaining, Math.max(0, compare.red));
            remaining = CardUtil.overflowInc(remaining, Math.max(0, compare.green));
            remaining = CardUtil.overflowInc(remaining, Math.max(0, compare.colorless));
            remaining = CardUtil.overflowInc(remaining, Math.max(0, compare.any));

            if (remaining > 0) {
                // Calculate how much of the unpaid generic cost can be paid by the leftover mana
                int diff = Math.min(remaining, Math.abs(compare.generic));
                // Make the payment
                compare.generic = CardUtil.overflowInc(compare.generic, diff);
            }
        }

        // Create the mana object holding the mana needed to pay the cost
        // If the value in compare is positive it means that there's excess mana of that type, and no more is needed.
        Mana needed = new Mana();
        if (compare.white < 0) {
            needed.white = CardUtil.overflowDec(needed.white, compare.white);
        }
        if (compare.blue < 0) {
            needed.blue = CardUtil.overflowDec(needed.blue, compare.blue);
        }
        if (compare.black < 0) {
            needed.black = CardUtil.overflowDec(needed.black, compare.black);
        }
        if (compare.red < 0) {
            needed.red = CardUtil.overflowDec(needed.red, compare.red);
        }
        if (compare.green < 0) {
            needed.green = CardUtil.overflowDec(needed.green, compare.green);
        }
        if (compare.colorless < 0) {
            needed.colorless = CardUtil.overflowDec(needed.colorless, compare.colorless);
        }
        if (compare.generic < 0) {
            needed.generic = CardUtil.overflowDec(needed.generic, compare.generic);
        }
        return needed;
    }

    /**
     * Returns total White mana.
     *
     * @return total White mana.
     */
    public int getWhite() {
        return white;
    }

    /**
     * Sets the total White mana. Can not be negative. Negative values will be
     * logged and set to 0.
     *
     * @param white total White mana.
     */
    public void setWhite(int white) {
        this.white = notNegative(white, "White");
    }

    /**
     * Returns total Blue mana.
     *
     * @return total Blue mana.
     */
    public int getBlue() {
        return blue;
    }

    /**
     * Sets the total Blue mana. Can not be negative. Negative values will be
     * logged and set to 0.
     *
     * @param blue total Blue mana.
     */
    public void setBlue(int blue) {
        this.blue = notNegative(blue, "Blue");
    }

    /**
     * Returns total Black mana.
     *
     * @return total Black mana.
     */
    public int getBlack() {
        return black;
    }

    /**
     * Sets the total Black mana. Can not be negative. Negative values will be
     * logged and set to 0.
     *
     * @param black total Black mana.
     */
    public void setBlack(int black) {
        this.black = notNegative(black, "Black");
    }

    /**
     * Returns total Red mana.
     *
     * @return total Red mana.
     */
    public int getRed() {
        return red;
    }

    /**
     * Sets the total Red mana. Can not be negative. Negative values will be
     * logged and set to 0.
     *
     * @param red total Red mana.
     */
    public void setRed(int red) {
        this.red = notNegative(red, "Red");
    }

    /**
     * Returns total Green mana.
     *
     * @return total Green mana.
     */
    public int getGreen() {
        return green;
    }

    /**
     * Sets the total Green mana. Can not be negative. Negative values will be
     * logged and set to 0.
     *
     * @param green total Green mana.
     */
    public void setGreen(int green) {
        this.green = notNegative(green, "Green");
    }

    /**
     * Returns total Generic mana.
     *
     * @return total Generic mana.
     */
    public int getGeneric() {
        return generic;
    }

    /**
     * Sets the total Generic mana. Can not be negative. Negative values will be
     * logged and set to 0.
     *
     * @param generic total Generic mana.
     */
    public void setGeneric(int generic) {
        this.generic = notNegative(generic, "Generic");
    }

    /**
     * Returns total Colorless mana.
     *
     * @return total Colorless mana.
     */
    public int getColorless() {
        return colorless;
    }

    /**
     * Sets the total Colorless mana. Can not be negative. Negative values will
     * be logged and set to 0.
     *
     * @param colorless total Colorless mana.
     */
    public void setColorless(int colorless) {
        this.colorless = notNegative(colorless, "Colorless");
    }

    /**
     * Returns total Any mana.
     *
     * @return total Any mana.
     */
    public int getAny() {
        return any;
    }

    /**
     * Sets the total Any mana. Can not be negative. Negative values will be
     * logged and set to 0.
     *
     * @param any total Any mana.
     */
    public void setAny(int any) {
        this.any = notNegative(any, "Any");
    }

    /**
     * Returns this objects total mana minus the passed in {@link Mana}'s mana.
     *
     * @param o the object to compare to.
     * @return this objects total mana minus the passed in {@link Mana}'s mana.
     */
    @Override
    public int compareTo(final Mana o) {
        return Integer.compare(this.count(), o.count());
    }

    /**
     * Returns if this objects mana contains any number of the passed in
     * {@link Mana}'s mana.
     *
     * @param mana the mana to check for
     * @return true if this contains any values that mana has
     */
    public boolean contains(final Mana mana) {
        if (mana.white > 0 && this.white > 0) {
            return true;
        }
        if (mana.blue > 0 && this.blue > 0) {
            return true;
        }
        if (mana.black > 0 && this.black > 0) {
            return true;
        }
        if (mana.red > 0 && this.red > 0) {
            return true;
        }
        if (mana.green > 0 && this.green > 0) {
            return true;
        }
        if (mana.colorless > 0 && this.colorless > 0) {
            return true;
        }
        return mana.generic > 0 && this.count() > 0;
    }

    public boolean containsAny(final Mana mana) {
        return containsAny(mana, false);
    }

    /**
     * Returns if this objects mana contains any coloured mana the same as the
     * passed in {@link Mana}'s mana.
     *
     * @param mana             the mana to check for
     * @param includeColorless also check for colorless
     * @return true if this contains any of the same type of coloured mana that
     * this has
     */
    public boolean containsAny(final Mana mana, boolean includeColorless) {
        if (mana.white > 0 && this.white > 0) {
            return true;
        } else if (mana.blue > 0 && this.blue > 0) {
            return true;
        } else if (mana.black > 0 && this.black > 0) {
            return true;
        } else if (mana.red > 0 && this.red > 0) {
            return true;
        } else if (mana.green > 0 && this.green > 0) {
            return true;
        } else if (mana.colorless > 0 && this.colorless > 0 && includeColorless) {
            return true;
        } else return mana.any > 0 && this.count() > 0;
    }

    /**
     * Returns the total count of mana in this object as specified by the passed
     * in {@link ColoredManaSymbol}.
     *
     * @param color the color to return the count for.
     * @return the total count of mana in this object as specified by the passed
     * in {@link ColoredManaSymbol}.
     */
    public int getColor(final ColoredManaSymbol color) {
        switch (color) {
            case W:
                return white;
            case U:
                return blue;
            case B:
                return black;
            case R:
                return red;
            case G:
                return green;
        }
        return 0;
    }

    /**
     * Returns the total count of mana in this object as specified by the passed
     * in {@link ManaType}.
     *
     * @param manaType the type to return the count for.
     * @return the total count of mana in this object as specified by the passed
     * in {@link ManaType}.
     */
    public int get(final ManaType manaType) {
        switch (manaType) {
            case WHITE:
                return white;
            case BLUE:
                return blue;
            case BLACK:
                return black;
            case RED:
                return red;
            case GREEN:
                return green;
            case COLORLESS:
                return CardUtil.overflowInc(generic, colorless); // TODO: This seems like a mistake
        }
        return 0;
    }

    /**
     * Set the color of mana specified by the passed in {@link ManaType} to
     * {@code amount} .
     * <p>
     * WARNING, you must check amount for overflow values, see CardUtil.multiplyWithOverflowCheck
     * and other CardUtil.xxxWithOverflowCheck math methods
     *
     * @param manaType the color of the mana to set
     * @param amount   the value to set the mana too
     */
    public void set(final ManaType manaType, final int amount) {
        switch (manaType) {
            case WHITE:
                setWhite(notNegative(amount, "white"));
                break;
            case BLUE:
                setBlue(notNegative(amount, "blue"));
                break;
            case BLACK:
                setBlack(notNegative(amount, "black"));
                break;
            case RED:
                setRed(notNegative(amount, "red"));
                break;
            case GREEN:
                setGreen(notNegative(amount, "green"));
                break;
            case COLORLESS:
                setColorless(notNegative(amount, "colorless"));
                break;
            default:
                throw new IllegalArgumentException("Unknown color: " + manaType);
        }
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public boolean getFlag() {
        return flag;
    }

    /**
     * Sets this objects mana to that of the passed in {@link Mana}
     *
     * @param mana the mana to set this object to.
     */
    public void setToMana(final Mana mana) {
        this.any = mana.any;
        this.white = mana.white;
        this.blue = mana.blue;
        this.black = mana.black;
        this.red = mana.red;
        this.green = mana.green;
        this.colorless = mana.colorless;
        this.generic = mana.generic;
        //this.flag = mana.flag;
    }

    /**
     * Returns if the passed in {@link Mana} values are equal to this objects.
     *
     * @param mana the {@link Mana} to compare to.
     * @return if the passed in {@link Mana} values are equal to this object.
     */
    public boolean equalManaValue(final Mana mana) {
        return this.any == mana.any
                && this.white == mana.white
                && this.blue == mana.blue
                && this.black == mana.black
                && this.red == mana.red
                && this.green == mana.green
                && this.colorless == mana.colorless
                && this.generic == mana.generic;
    }

    /**
     * Compare two mana - is one part includes into another part. Support any mana types and uses a payment logic.
     * <p>
     * Used for AI and mana optimizations to remove duplicated mana options.
     */
    public boolean includesMana(Mana manaPart) {
        if (!this.isValid() || !manaPart.isValid()) {
            // how-to fix: make sure mana calculations do not add or subtract values without result checks or isValid call
            throw new IllegalArgumentException("Wrong code usage: found negative values in mana calculations: main " + this + ", part " + manaPart);
        }

        if (this.count() < manaPart.count()) {
            return false;
        }

        if (manaPart.count() == 0) {
            return true;
        }

        // it's uses pay logic with additional {any} mana support:
        // - {any} in cost - can be paid by {any} mana only
        // - {any} in pay - can be used to pay {any}, {1} and colored mana (but not {C})
        Mana pool = this.copy();
        Mana cost = manaPart.copy();

        // first pay type by type (it's important to pay {any} first)
        // 10 - 3 = 7 in pool, 0 in cost
        // 5 - 7 = 0 in pool, 2 in cost
        pool.subtract(cost);
        cost.white = Math.max(0, -1 * pool.white);
        pool.white = Math.max(0, pool.white);
        cost.blue = Math.max(0, -1 * pool.blue);
        pool.blue = Math.max(0, pool.blue);
        cost.black = Math.max(0, -1 * pool.black);
        pool.black = Math.max(0, pool.black);
        cost.red = Math.max(0, -1 * pool.red);
        pool.red = Math.max(0, pool.red);
        cost.green = Math.max(0, -1 * pool.green);
        pool.green = Math.max(0, pool.green);
        cost.generic = Math.max(0, -1 * pool.generic);
        pool.generic = Math.max(0, pool.generic);
        cost.colorless = Math.max(0, -1 * pool.colorless);
        pool.colorless = Math.max(0, pool.colorless);
        cost.any = Math.max(0, -1 * pool.any);
        pool.any = Math.max(0, pool.any);
        if (cost.count() > pool.count()) {
            throw new IllegalArgumentException("Wrong mana calculation: " + cost + " - " + pool);
        }

        // can't pay {any} or {C}
        if (cost.any > 0 || cost.colorless > 0) {
            return false;
        }

        // then pay colored by {any}
        if (pool.any > 0 && cost.white > 0) {
            int diff = Math.min(pool.any, cost.white);
            pool.any -= diff;
            cost.white -= diff;
        }
        if (pool.any > 0 && cost.blue > 0) {
            int diff = Math.min(pool.any, cost.blue);
            pool.any -= diff;
            cost.blue -= diff;
        }
        if (pool.any > 0 && cost.black > 0) {
            int diff = Math.min(pool.any, cost.black);
            pool.any -= diff;
            cost.black -= diff;
        }
        if (pool.any > 0 && cost.red > 0) {
            int diff = Math.min(pool.any, cost.red);
            pool.any -= diff;
            cost.red -= diff;
        }
        if (pool.any > 0 && cost.green > 0) {
            int diff = Math.min(pool.any, cost.green);
            pool.any -= diff;
            cost.green -= diff;
        }

        // can't pay colored
        if (cost.countColored() > 0) {
            return false;
        }

        // then pay generic by {any}, colored or {C}
        int leftPool = pool.count();
        if (leftPool > 0 && cost.generic > 0) {
            int diff = Math.min(leftPool, cost.generic);
            cost.generic -= diff;
        }

        return cost.count() == 0;
    }

    public boolean isMoreValuableThan(Mana that) {
        // Use of == is intentional since getMoreValuableMana returns one of its inputs.
        return this == Mana.getMoreValuableMana(this, that);
    }

    /**
     * Returns the mana that is more colored or has a greater amount but does
     * not contain one less mana in any type but generic.
     * <p>
     * See tests ManaTest.moreValuableManaTest for several examples
     * <p>
     * Examples:
     * {1}       and {R}       -> {R}
     * {2}       and {1}{W}    -> {1}{W}
     * {3}       and {1}{W}    -> {1}{W}
     * {1}{W}{R} and {G}{W}{R} -> {G}{W}{R}
     * {G}{W}{R} and {G}{W}{R} -> null
     * {G}{W}{B} and {G}{W}{R} -> null
     * {C}       and {ANY}     -> null
     *
     * @param mana1 The 1st mana to compare.
     * @param mana2 The 2nd mana to compare.
     * @return The greater of the two manas, or null if they're the same OR they cannot be compared
     */
    public static Mana getMoreValuableMana(final Mana mana1, final Mana mana2) {
        if (mana1.equals(mana2)) {
            return null;
        }

        boolean mana1IsConditional = mana1 instanceof ConditionalMana;
        boolean mana2IsConditional = mana2 instanceof ConditionalMana;
        if (mana1IsConditional != mana2IsConditional) {
            return null;
        }

        if (mana1IsConditional) {
            List<Condition> conditions1 = ((ConditionalMana) mana1).getConditions();
            List<Condition> conditions2 = ((ConditionalMana) mana2).getConditions();
            if (!Objects.equals(conditions1, conditions2)) {
                return null;
            }
        }

        // Set one mana as moreMana and one as lessMana.
        Mana moreMana;
        Mana lessMana;
        if (mana2.any > mana1.any
                || mana2.colorless > mana1.colorless
                || mana2.countColored() > mana1.countColored()
                || (mana2.countColored() == mana1.countColored()
                && mana2.colorless == mana1.colorless
                && mana2.count() > mana1.count())) {
            moreMana = mana2;
            lessMana = mana1;
        } else {
            moreMana = mana1;
            lessMana = mana2;
        }

        if (lessMana.any > moreMana.any) {
            return null;
        }
        if (lessMana.colorless > moreMana.colorless) {
            return null; // Any (color) can't produce colorless mana
        }

        int anyDiff = CardUtil.overflowDec(moreMana.any, lessMana.any);

        int whiteDiff = CardUtil.overflowDec(lessMana.white, moreMana.white);
        if (whiteDiff > 0) {
            anyDiff = CardUtil.overflowDec(anyDiff, whiteDiff);
            if (anyDiff < 0) {
                return null;
            }
        }

        int redDiff = CardUtil.overflowDec(lessMana.red, moreMana.red);
        if (redDiff > 0) {
            anyDiff = CardUtil.overflowDec(anyDiff, redDiff);
            if (anyDiff < 0) {
                return null;
            }
        }

        int greenDiff = CardUtil.overflowDec(lessMana.green, moreMana.green);
        if (greenDiff > 0) {
            anyDiff = CardUtil.overflowDec(anyDiff, greenDiff);
            if (anyDiff < 0) {
                return null;
            }
        }

        int blueDiff = CardUtil.overflowDec(lessMana.blue, moreMana.blue);
        if (blueDiff > 0) {
            anyDiff = CardUtil.overflowDec(anyDiff, blueDiff);
            if (anyDiff < 0) {
                return null;
            }
        }

        int blackDiff = CardUtil.overflowDec(lessMana.black, moreMana.black);
        if (blackDiff > 0) {
            anyDiff = CardUtil.overflowDec(anyDiff, blackDiff);
            if (anyDiff < 0) {
                return null;
            }
        }

        return moreMana;
    }

    /**
     * Returns the total count of mana colors that have at least one.
     *
     * @return the total count of mana colors that have at least one.
     */
    public int getDifferentColors() {
        int count = 0;
        if (white > 0) {
            count++;
        }
        if (blue > 0) {
            count++;
        }
        if (black > 0) {
            count++;
        }
        if (red > 0) {
            count++;
        }
        if (green > 0) {
            count++;
        }
        return count;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Mana mana = (Mana) o;
        return flag == mana.flag
                && white == mana.white
                && blue == mana.blue
                && black == mana.black
                && red == mana.red
                && green == mana.green
                && generic == mana.generic
                && colorless == mana.colorless
                && any == mana.any;
    }

    /**
     * Hardcoding here versus using Objects.hash in order to increase performance since this is
     * called thousands of times by {@link mage.abilities.mana.ManaOptions#addManaWithCost(List, Game)}
     *
     * @return
     */
    @Override
    public int hashCode() {
        long result = 1;

        result = 31 * result + white;
        result = 31 * result + blue;
        result = 31 * result + black;
        result = 31 * result + red;
        result = 31 * result + green;
        result = 31 * result + generic;
        result = 31 * result + colorless;
        result = 31 * result + any;
        result = 31 * result + (flag ? 1 : 0);

        return Long.hashCode(result);
    }

    /**
     * Checks that the {@code value} passed in is not less than 0. If the value
     * is negative, it is logged and 0 is returned.
     *
     * @param value the value to check.
     * @param name  the name of the value to check. Used to make logging of the
     *              {@code value} easier
     * @return the {@code value} passed in, unless it is minus, in which case 0
     * is returned.
     */
    private static int notNegative(int value, final String name) {
        if (value < 0) {
            logger.info(name + " can not be less than 0. Passed in: " + value + " Defaulting to 0.");
            value = 0;
        }
        return value;
    }
}
