package com.hero.objects.modifiers;

import java.util.ArrayList;
import java.util.Iterator;

import org.jdom.Element;

import com.hero.HeroDesigner;
import com.hero.objects.Adder;
import com.hero.objects.ElementalControl;
import com.hero.objects.GenericObject;
import com.hero.objects.List;
import com.hero.objects.Multipower;
import com.hero.objects.VariablePowerPool;
import com.hero.objects.disads.Disadvantage;
import com.hero.objects.martialarts.Maneuver;
import com.hero.objects.powers.NakedModifier;
import com.hero.ui.dialog.ModifierDialog;
import com.hero.util.Rounder;
import com.hero.util.XMLUtility;

/**
 * Copyright (c) 2000 - 2005, CompNet Design, Inc. All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, is prohibited unless the following conditions are met: 1.
 * Express written consent of CompNet Design, Inc. is obtained by the developer.
 * 2. Redistributions must retain this copyright notice. THIS SOFTWARE IS
 * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * @author CompNet Design, Inc.
 * @version $Revision$
 */

public class Modifier extends GenericObject {
	/**
	 * Gets a specific subclass of Modifier, if available, based on the XMLID
	 * attribute of the passed in Element.
	 * 
	 * @param root
	 * @return
	 */
	public static Modifier getInstance(Element root) {
		String XMLID = XMLUtility.getValue(root, "XMLID");
		if ((XMLID == null) || (XMLID.trim().length() == 0)) {
			XMLID = "GENERIC_OBJECT";
		}
		XMLID = XMLID.trim().toUpperCase();
		
		if (XMLID.equals("ONLYTOACTIVATE")) {
			return new OnlyToActivate(root);
		} else if (XMLID.equals("TIMELIMIT")) {   
			return new TimeLimit(root);
		} else if (XMLID.equals("HALFRANGEMODIFIER")) {   
			return new HalfRangeModifier(root);
		} else if (XMLID.equals("DAMAGEOVERTIME")) {
			return new DamageOverTime(root);
		} else if (XMLID.equals("AVAD")) {
			return new AVAD(root);
		}  else if (XMLID.equals("DOUBLEENDCOST")) {
			return new DoubleEnduranceCost(root);
		} else if (XMLID.equals("PHYSICALMANIFESTATION")) {
			return new PhysicalManifestation(root);
		} else if (XMLID.equals("TRANSPARENT")) {
			return new Transparent(root);
		} else if (XMLID.equals("SEMIARMORPIERCING")) {
			return new SemiArmorPiercing(root);
		} else if (XMLID.equals("PARTIALCOVERAGE")) {
			return new PartialCoverage(root);
		} else if (XMLID.equals("NOTELEPORT")) {
			return new CannotEscapeWithTeleport(root);
		} else if (XMLID.equals("LIMITEDARCOFFIRE")) {
			return new LimitedArcOfFire(root);
		} else if (XMLID.equals("REQUIREDHANDS")) {
			return new RequiredHands(root);
		} else if (XMLID.equals("REALWEAPON")) {
			return new RealWeapon(root);
		} else if (XMLID.equals("MOBILE")) {
			return new Mobile(root);
		} else if (XMLID.equals("LINGERING")) {
			return new Lingering(root);
		} else if (XMLID.equals("VARIABLETARGETS")) {
			return new VariableTarget(root);
		} else if (XMLID.equals("FEEDBACK")) {
			return new Feedback(root);
		} else if (XMLID.equals("ONLYONAPPROPRIATETERRAIN")) {
			return new OnlyOnAppropriateTerrain(root);
		} else if (XMLID.equals("DROPPED")) {
			return new Dropped(root);
		} else if (XMLID.equals("DELAYEDRETURNRATE")) {
			return new DelayedReturnRate(root);
		} else if (XMLID.equals("VARIABLEEFFECT")) {
			return new VariableEffect(root);
		} else if (XMLID.equals("ONLYTOSTARTING")) {
			return new OnlyToStarting(root);
		} else if (XMLID.equals("SELFONLY")) {
			return new SelfOnly(root);
		} else if (XMLID.equals("OTHERSONLY")) {
			return new OthersOnly(root);
		} else if (XMLID.equals("BEAM")) {
			return new Beam(root);
		} else if (XMLID.equals("CANBEMISSILEDEFLECTED")) {
			return new CanBeMissileDeflected(root);
		} else if (XMLID.equals("NOKB")) {
			return new NoKB(root);
		} else if (XMLID.equals("COSTSENDONLYTOACTIVATE")) {
			return new CostsENDOnlyToActivate(root);
		} else if (XMLID.equals("HARDENED")) {
			return new Hardened(root);
		} else if (XMLID.equals("DOESNOTPROVIDEMENTALAWARENESS")) {
			return new DoesNotProvideMentalAwareness(root);
		} else if (XMLID.equals("NOTTHROUGHMINDLINK")) {
			return new NotThroughMindLink(root);
		} else if (XMLID.equals("COSTSENDTOMAINTAIN")) {
			return new CostsENDToMaintain(root);
		} else if (XMLID.equals("NORMALRANGE")) {
			return new NormalRange(root);
		} else if (XMLID.equals("TURNMODE")) {
			return new TurnMode(root);
		} else if (XMLID.equals("AFFECTSDESOLID")) {
			return new AffectsDesolid(root);
		} else if (XMLID.equals("AOE")) {
			return new AreaEffect(root);
		} else if (XMLID.equals("ARMORPIERCING")) {
			return new ArmorPiercing(root);
		} else if (XMLID.equals("AVLD")) {
			return new AVLD(root);
		} else if (XMLID.equals("AUTOFIRE")) {
			return new Autofire(root);
		} else if (XMLID.equals("BOECV")) {
			return new BasedOnECV(root);
		} else if (XMLID.equals("CUMULATIVE")) {
			return new Cumulative(root);
		} else if (XMLID.equals("DAMAGESHIELD")) {
			return new DamageShield(root);
		} else if (XMLID.equals("DELAYEDEFFECT")) {
			return new DelayedEffect(root);
		} else if (XMLID.equals("DIFFICULTTODISPEL")) {
			return new DifficultToDispel(root);
		} else if (XMLID.equals("DOESBODY")) {
			return new DoesBODY(root);
		} else if (XMLID.equals("DOESKB")) {
			return new DoesKB(root);
		} else if (XMLID.equals("DOUBLEKB")) {
			return new DoubleKB(root);
		} else if (XMLID.equals("CONTINUOUS")) {
			return new Continuous(root);
		} else if (XMLID.equals("PERSISTENT")) {
			return new Persistent(root);
		} else if (XMLID.equals("INHERENT")) {
			return new Inherent(root);
		} else if (XMLID.equals("EXPLOSION")) {
			return new Explosion(root);
		} else if (XMLID.equals("HOLEINTHEMIDDLE")) {
			return new HoleInTheMiddle(root);
		} else if (XMLID.equals("INDIRECT")) {
			return new Indirect(root);
		} else if (XMLID.equals("INVISIBLE")) {
			return new Invisible(root);
		} else if (XMLID.equals("MEGASCALE")) {
			return new Megascale(root);
		} else if (XMLID.equals("NND")) {
			return new NND(root);
		} else if (XMLID.equals("PENETRATING")) {
			return new Penetrating(root);
		} else if (XMLID.equals("PERSONALIMMUNITY")) {
			return new PersonalImmunity(root);
		} else if (XMLID.equals("INCREASEDMAXRANGE")) {
			return new IncreasedMaxRange(root);
		} else if (XMLID.equals("LOS")) {
			return new LineOfSight(root);
		} else if (XMLID.equals("NORANGEMODIFIER")) {
			return new NoRangeModifier(root);
		} else if (XMLID.equals("RANGED")) {
			return new Ranged(root);
		} else if (XMLID.equals("REDUCEDEND")) {
			return new ReducedEND(root);
		} else if (XMLID.equals("DELAYEDEND")) {
			return new DelayedEND(root);
		} else if (XMLID.equals("STICKY")) {
			return new Sticky(root);
		} else if (XMLID.equals("TRANSDIMENSIONAL")) {
			return new Transdimensional(root);
		} else if (XMLID.equals("TRIGGER")) {
			return new Trigger(root);
		} else if (XMLID.equals("UNCONTROLLED")) {
			return new Uncontrolled(root);
		} else if (XMLID.equals("UOO")) {
			return new UsableOnOthers(root);
		} else if (XMLID.equals("VARIABLEADVANTAGE")) {
			return new VariableAdvantage(root);
		} else if (XMLID.equals("ACTIVATIONROLL")) {
			return new ActivationRoll(root);
		} else if (XMLID.equals("ALWAYSON")) {
			return new AlwaysOn(root);
		} else if (XMLID.equals("CHARGES")) {
			return new Charges(root);
		} else if (XMLID.equals("CONCENTRATION")) {
			return new Concentration(root);
		} else if (XMLID.equals("INSTANT")) {
			return new Instant(root);
		} else if (XMLID.equals("NONPERSISTENT")) {
			return new Nonpersistent(root);
		} else if (XMLID.equals("COSTSEND")) {
			return new CostsEND(root);
		} else if (XMLID.equals("INCREASEDEND")) {
			return new IncreasedEND(root);
		} else if (XMLID.equals("EXTRATIME")) {
			return new ExtraTime(root);
		} else if (XMLID.equals("FOCUS")) {
			return new Focus(root);
		} else if (XMLID.equals("GESTURES")) {
			return new Gestures(root);
		} else if (XMLID.equals("INCANTATIONS")) {
			return new Incantations(root);
		} else if (XMLID.equals("LINKED")) {
			return new Linked(root);
		} else if (XMLID.equals("NORANGE")) {
			return new NoRange(root);
		} else if (XMLID.equals("LIMITEDRANGE")) {
			return new LimitedRange(root);
		} else if (XMLID.equals("RANGEBASEDONSTR")) {
			return new RangeBasedOnSTR(root);
		} else if (XMLID.equals("REDUCEDBYRANGE")) {
			return new ReducedByRange(root);
		} else if (XMLID.equals("SUBJECTTORANGEMODIFIER")) {
			return new SubjectToRangeModifier(root);
		} else if (XMLID.equals("REQUIRESASKILLROLL")) {
			return new RequiresSkillRoll(root);
		} else if (XMLID.equals("RESTRAINABLE")) {
			return new Restrainable(root);
		} else if (XMLID.equals("SIDEEFFECTS")) {
			return new SideEffects(root);
		} else if (XMLID.equals("VARIABLELIMITATIONS")) {
			return new VariableLimitations(root);
		} else if (XMLID.equals("VISIBLE")) {
			return new Visible(root);
		} else if (XMLID.equals("AFFECTSPHYSICALWORLD")) {
			return new AffectsPhysicalWorld(root);
		} else if (XMLID.equals("ENDRESERVEOREND")) {
			return new ENDReserveOrEND(root);
		} else if (XMLID.equals("ACV")) {
			return new AlternateCombatValue(root);
		} else {
			return new Modifier(root);
		}
	}

	protected boolean isLimitation;

	protected boolean isLimitationSet;

	protected boolean availableCheck;

	protected boolean isMultiplier;

	protected ArrayList<String> excludes;

	protected GenericObject parent;

	protected ArrayList<String> requires;

	protected String duration;

	protected boolean requiresAll;

	protected boolean fullDisplay;

	protected boolean showOptionOnly;

	protected boolean showOptionInParens;
	
	protected boolean showInputInParens;

	protected boolean privateMod;

	protected boolean displayInString = true;

	protected String comments;
	
	protected boolean forceAllow = false;

	protected Modifier(Element root) {
		super(root);
		privateMod = getTypes().contains("VPP") || getTypes().contains("MP")
				|| getTypes().contains("EC") || getTypes().contains("LIST");
		xmlID = "MODIFIER";
		String check = XMLUtility.getValue(root, "XMLID");
		
		if ((check != null) && (check.trim().length() > 0)) {
			xmlID = check;
		}
		
		check = XMLUtility.getValue(root, "ISLIMITATION");
		if ((check != null) && (check.trim().length() > 0)) {
			isLimitationSet = true;
			isLimitation = check.trim().toUpperCase().startsWith("Y");
		}
		check = XMLUtility.getValue(root, "MINCOST");
		if ((check == null) || (check.trim().length() == 0)) {
			minimumCost = -10;
		}
		check = XMLUtility.getValue(root, "MAXCOST");
		if ((check == null) || (check.trim().length() == 0)) {
			maxCost = 10;
		}
		minSet = true;
		maxSet = true;
		check = XMLUtility.getValue(root, "FIXEDVALUE");
		if ((check == null) || (check.trim().length() == 0)) {
			fixedValue = true;
		}
		resetID();
	}

	protected Modifier(Element root, String xmlID) {
		super(root);
		String check = XMLUtility.getValue(root, "XMLID");
		if (!check.equals(xmlID)) {
			System.out.println("Error initing " + xmlID + ", check = " + check);
		}
	}

	public Modifier clone() {
		Modifier c;
		c = (Modifier) super.clone();
		c.setParent(parent);
		c.setSelectedOption(getSelectedOption());
		c.setForceAllow(forceAllow);
		return c;
	}

	public int compareTo(Object o) {
		if (o instanceof Modifier) {
			return toString().compareTo(o.toString());
		} else {
			return toString().compareTo(o.toString());
		}
	}

	/**
	 * This is used in Modifier Intelligence. If the Modifier has any types
	 * defined, then one of those types must match one of the types on the
	 * ability it is being applied to.
	 */
	public boolean containsType(String type) {
		if (types == null) {
			types = new ArrayList<String>();
		}
		if (types.size() == 0) {
			return true;
		} else {
			return types.contains(type);
		}
	}

	public boolean displayInString() {
		return displayInString;
	}

	/**
	 * Runs a deep comparison to determine equality, even in the event of user
	 * edits to the ability.
	 */
	public boolean equals(Object o) {
		if (o instanceof Modifier) {
			Modifier comp = (Modifier) o;

			if (comp.getXMLID().equals(getXMLID())) {
				if (!getXMLID().equals("CUSTOM_MODIFIER")
						&& !getXMLID().equals("GENERIC_OBJECT")
						&& !getXMLID().equals("MODIFIER")) {
					if (isAvailableCheck() || comp.isAvailableCheck()) {
						return true;
					}
					if (!isAvailableCheck() && !comp.isAvailableCheck()) {
						return getID() == comp.getID();
					}
				} else {
					return comp.getID() == getID();
				}
			} else {
				if (!getXMLID().equals("CUSTOM_MODIFIER")
						&& !getXMLID().equals("GENERIC_OBJECT")
						&& !comp.getXMLID().equals("GENERIC_OBJECT")
						&& !comp.getXMLID().equals("CUSTOM_MODIFIER")) {
					return false;
				} else if (!getXMLID().equals(comp.getXMLID())) {
					return false;
				}
			}
			if (isAvailableCheck() || comp.isAvailableCheck()) {
				return getDisplay().equals(comp.getDisplay());
			} else {
				if (getAlias().equals(comp.getAlias())) { // checks
					// alias
					return getBaseCost() == comp.getBaseCost();
				} else {
					return false;
				}
			}
		} else {
			return false;
		}
	}

	public String getColumn2Output() {
		String adderString = "";
		String ret = "";
		if (!showOptionOnly) {
			ret += getAlias();
		}
		double val = getTotalValue();
		if (!showOptionInParens && (getSelectedOption() != null)
				&& getSelectedOption().displayInString()
				&& (getSelectedOption().getAlias().trim().length() > 0)) {
			ret += " " + getSelectedOption().getAlias();
			ret = ret.trim();
		}
		if (!showInputInParens && (getInput() != null) && (getInput().trim().length() > 0)) {
			if (ret.trim().length() > 0) {
				ret += " ";
			}
			ret += getInput();
		}
		ret = ret.trim();
		for (Modifier mod : getAssignedModifiers()) {
			ret += ", " + mod.getAlias();
		}
		int index = 0;
		int parenCount = 0;
		while (ret.indexOf("(", index) >= 0) {
			parenCount++;
			index = ret.indexOf("(", index) + 1;
		}
		index = 0;
		while (ret.indexOf(")", index) >= 0) {
			parenCount--;
			index = ret.indexOf(")", index) + 1;
		}
		if (parenCount <= 0) {
			ret += " (";
		} else {
			ret += "; ";
		}
		if (showOptionInParens && (getSelectedOption() != null)
				&& getSelectedOption().displayInString()
				&& (getSelectedOption().getAlias().trim().length() > 0)) {
			ret += getSelectedOption().getAlias().trim() + "; ";
		}
		if (showInputInParens && (getInput() != null) && (getInput().trim().length() > 0)) {
			ret += getInput()+"; ";
		}
		for (Adder ad : getAssignedAdders()) {
			if (!ad.isSelected()) {
				continue;
			}
			if (ad.getColumn2Output().trim().length() > 0) {
				ret += ad.getColumn2Output().trim() + "; ";
			}
		}
		if (getComments().trim().length() > 0) {
			ret += getComments() + "; ";
		}
		if ((val > maxCost) && maxSet) {
			val = maxCost;
		}
		if ((val < minimumCost) && minSet) {
			val = minimumCost;
		}
		ret += getFraction(val) + ")";
		parenCount--;
		while (parenCount > 0) {
			ret += ")";
			parenCount--;
		}
		if (adderString.trim().length() > 0) {
			if (ret.trim().length() > 0) {
				ret += ", ";
			}
			ret += adderString;
		}
		return ret;
	}

	/**
	 * Comments are placed inside the parentheses, with the value.
	 * 
	 * @param val
	 */
	public String getComments() {
		if (comments == null) {
			comments = "";
		}
		return comments;
	}

	public ModifierDialog getDialog(GenericObject parent, boolean isNew) {
		return new ModifierDialog(this, isNew, parent);
	}

	/**
	 * Returns a Vector of Strings. Each String is an XMLID of a Modifier that
	 * is not allowed to be used in conjunction with this one.
	 * 
	 * @return
	 */
	public ArrayList<String> getExcludes() {
		return excludes;
	}

	/**
	 * Assumes that the modifier has a value somewhere between -25 and 25.
	 * Granularity is 1/4.
	 * 
	 * @param val
	 *            the double for which you want a fraction.
	 * @return The String representing the fractional value of the modifier
	 */
	public String getFraction(double val) {
		return getFraction(val, useMultiplier());
	}

	private String getFraction(double val, boolean useMultiplier) {
		String ret = "";
		String flag = "";
		if (HeroDesigner.getInstance().getPrefs().flagForcedModifiers() && forceAllow()) {
			flag = "*";
		}
		if (val == 0) {
			if (useMultiplier) {
				return "x1"+flag;
			} else if (isLimitation()) {
				return "-0"+flag;
			} else {
				return "+0"+flag;
			}
		}
		if (useMultiplier) {
			ret = "x";
			boolean isNeg = false;
			if (val < 0) {
				isNeg = true;
				val = Math.abs(val);
			}
			String frac = "";
			long check = Rounder.roundDown(val + .0000001);
			if (check < val) {
				GenericObject holder = parent;
				parent = null;
				frac = getFraction(val - check, false);
				parent = holder;
				if (frac.startsWith("+")) {
					frac = frac.substring(1, frac.length());
				}
			}
			if (frac.trim().length() > 0) {
				frac = " " + frac;
			}
			check++;
			if (isNeg) {
				ret += "1/" + check + frac;
			} else {
				ret += check + frac;
			}
			return ret+flag;
		}

		if (val < 0) {
			ret += "-";
		} else {
			ret += "+";
		}
		val = Math.abs(val);
		if (val > 1) {
			if (useMultiplier()) {
				ret += (int) Rounder.roundDown(val + 1);
			} else {
				ret += (int) Rounder.roundDown(val);
			}
			val = val - Rounder.roundDown(val);
		}
		if (val == 0) {
			return ret+flag;
		}
		String closestMatch = "";
		double closest = 1d;
		if (Math.abs((double) 1 / (double) 4 - val) < closest) {
			closest = Math.abs((double) 1 / (double) 4 - val);
			closestMatch = "1/4";
		}
		if (Math.abs((double) 1 / (double) 2 - val) < closest) {
			closest = Math.abs((double) 1 / (double) 2 - val);
			closestMatch = "1/2";
		}
		if (Math.abs((double) 3 / (double) 4 - val) < closest) {
			closest = Math.abs((double) 3 / (double) 4 - val);
			closestMatch = "3/4";
		}
		if (Math.abs(1 - val) < closest) {
			closestMatch = "";
			if (ret.length() > 1) {
				int check = Integer.parseInt(ret.substring(1, ret.length()));
				ret = ret.substring(0, 1) + (check + 1);
			} else if ((ret.length() == 1) && !ret.equals("+")
					&& !ret.equals("-") && !ret.equals("x")) {
				ret = "" + (Integer.parseInt(ret) + 1);
			} else if ((ret.trim().length() == 0) || ret.trim().equals("+")) {
				ret = "+1";
			} else {
				ret = "-1";
			}
		}
		if (ret.length() > 1) {
			ret += " ";
		}
		ret += closestMatch;
		ret = ret.trim();
		return ret+flag;
	}

	/**
	 * Some Modifiers provide additional (text) information next to the levels
	 * field during edit.
	 * 
	 * @return
	 */
	public String getLevelInfo() {
		return "";
	}

	public double getMaxCost() {
		if (getXMLID().equals("GENERIC_OBJECT")
				|| getXMLID().equals("CUSTOM_MODIFIER")) {
			return 10;
		} else {
			return super.getMaxCost();
		}
	}

	public double getMinimumCost() {
		if (getXMLID().equals("GENERIC_OBJECT")
				|| getXMLID().equals("CUSTOM_MODIFIER")) {
			return -10;
		} else {
			return super.getMinimumCost();
		}
	}

	/**
	 * Gets the ability that this Modifier has been assigned to. Will drill up
	 * through multiple levels of Modifiers, if necessary.
	 * 
	 * @return
	 */
	public GenericObject getProgenitor() {
		if (parent == null) {
			return null;
		}
		if (parent instanceof Modifier) {
			return ((Modifier) parent).getProgenitor();
		}
		return parent;
	}

	/**
	 * Returns a Vector of Strings. Each String is an XMLID of a Modifier that
	 * is required in order for this one to be applied.
	 * 
	 * @return
	 */
	public ArrayList<String> getRequires() {
		return requires;
	}
	
	public void setForceAllow(boolean val) {
		forceAllow = val;
	}
	
	public boolean forceAllow() {
		return forceAllow;
	}

	public Element getSaveXML() {
		Element root = getGeneralSaveXML();
		root.setName("MODIFIER");
		root.setAttribute("COMMENTS", getComments());
		root.setAttribute("PRIVATE", isPrivate() ? "Yes" : "No");
		root.setAttribute("FORCEALLOW", forceAllow()? "Yes":"No");
		return root;
	}

	/**
	 * Returns the overall value of the Modifier, taking all Adders and
	 * "sub-Modifiers" into account. Result is rounded to nearest 1/4.
	 * 
	 * @return
	 */
	public double getTotalValue() {
		double val = getBaseCost();
		for (Adder ad : getAssignedAdders()) {
			val += ad.getDoubleTotal();
		}
		if (getLevelValue() > 0) {
			val += getLevels() / getLevelValue() * getLevelCost();
		}
		double advantageTotal = 0d;
		for (Modifier mod : getAssignedModifiers()) {
			if (mod.getTotalValue() > 0) {
				advantageTotal += mod.getTotalValue();
			}
		}
		double active = val * (1 + advantageTotal);
		double limitationTotal = 0d;
		for (Modifier mod : getAssignedModifiers()) {
			if (mod.getTotalValue() < 0) {
				limitationTotal += Math.abs(mod.getTotalValue());
			}
		}
		val = active / (1 + limitationTotal);

		// the following effectively "rounds" the value to the closest 1/4
		val = val * 4d;
		int mult = 1;
		if (val < 0) {
			mult = -1;
		}
		val = val * mult;
		val = Rounder.roundHalfUp(val);
		val = val * mult;
		val = val / 4d;

		if ((val < getMinimumCost()) && isMinSet()) {
			return getMinimumCost();
		} else if ((val > getMaxCost()) && isMaxSet()) {
			return getMaxCost();
		} else {
			return val;
		}
	}

	public int hashCode() {
		return getXMLID().hashCode();
	}

	/**
	 * This is the main method for checking Modifier Intelligence. If Modifier
	 * is valid, returned String will be empty. If not, then returned String
	 * will contain a description of the first violation of Modifier
	 * Intelligence that was encountered.
	 * 
	 * @param power
	 * @return
	 */
	public String included(GenericObject power) {
		String ret = "";
		
		boolean modifierParent = false;
		if (power == null) {
			return "";
		}
		if (power instanceof Modifier) {
			modifierParent = true;
			Modifier m = (Modifier) power;
			power = m.getProgenitor();
			if (power == null) {
				return "";
			}
		}
		if (forceAllow()) {
			return ret;
		}
		if ((duration != null) && (duration.trim().length() > 0)) {
			if (duration.equalsIgnoreCase("INSTANT")) {
				if (!power.getDuration().equals("INSTANT")) {
					return getDisplay()
							+ " can only be applied to Instant Powers.";
				}
			} else if (duration.equalsIgnoreCase("CONSTANT")) {
				if (power.getDuration().equals("INSTANT")) {
					return getDisplay()
							+ " can only be applied to Constant Powers.";
				}
			} else if (duration.equalsIgnoreCase("PERSISTENT")) {
				if (power.getDuration().equals("INSTANT")
						|| power.getDuration().equals("CONSTANT")) {
					return getDisplay()
							+ " can only be applied to Persistent Powers.";
				}
			} else if (duration.equalsIgnoreCase("INHERENT")) {
				if (power.getDuration().equals("INSTANT")
						|| power.getDuration().equals("CONSTANT")
						|| power.getDuration().equals("PERSISTENT")) {
					return getDisplay()
							+ " can only be applied to Inherent Powers.";
				}
			}
		}
		/*if ((power instanceof Maneuver) && isLimitation()
				&& !power.isListModCheck()) {
			return "Only Advantages can be applied to individual Maneuvers.";
		}
		removed per phone conversation with Steve Long -- 04/28/2010
		

		if (power instanceof List) {
			List l = (List) power;
			boolean hasManeuvers = false;
			double total = 0d;
			for (GenericObject o : l.getObjects()) {
				total += o.getTotalCost();
				if (o instanceof Maneuver) {
					hasManeuvers = true;
				}
			}
			if (hasManeuvers && (total < 10) && isLimitation()) {
				return "Only a full Martial Arts Style (10 points or more) may have Limitations applied to it.";
			}
		}
		*/
		if ((power instanceof ElementalControl) && !isLimitation()
				&& !modifierParent) {
			return getDisplay()
					+ " cannot be applied to an Elemental Control.  Advantages should be applied to each slot individually.";
		}
		if ((types == null) || (types.size() == 0)
				|| (power instanceof com.hero.objects.List)) {
			if ((types == null) || (types.size() == 0)) {
				ret = "";
			} else if (!(power instanceof VariablePowerPool)
					&& types.contains("VPP")) {
				ret = getDisplay()
						+ " can only be applied to a Variable Power Pool.";
			} else if (!(power instanceof Multipower) && types.contains("MP")) {
				ret = getDisplay() + " can only be applied to a Multipower.";
			} else if (!(power instanceof ElementalControl)
					&& types.contains("EC")) {
				ret = getDisplay()
						+ " can only be applied to an Elemental Control.";
			} else if (!(power instanceof List) && types.contains("LIST")) {
				ret = getDisplay() + " can only be applied to a List.";
			} else {
				ret = "";
			}
		} else {
			ret = getDisplay() + " can only be applied to abilities of type ";
			for (int i = 0; i < types.size(); i++) {
				if (i > 0) {
					ret += ", ";
				}
				if ((i == types.size() - 1) && (i > 0)) {
					ret += "or ";
				}
				ret += types.get(i).toString().toLowerCase();
			}
			ArrayList<String> pTypes = power.getTypes();
			if (pTypes == null) {
				pTypes = new ArrayList<String>();
			}
			for (String s : pTypes) {
				if (containsType(s)) {
					ret = "";
				}
			}
		}
		if (ret.trim().length() == 0) { // valid for power...now check
			// excludes/requires...
			if (excludes == null) {
				excludes = new ArrayList<String>();
			}
			for (String id : excludes) {
				id = id.toUpperCase().trim();
				ArrayList<Modifier> mods = power.getAssignedModifiers();
				for (Modifier mod : mods) {
					if (mod.getXMLID().toUpperCase().trim().equals(id)) {
						return getDisplay()
								+ " cannot be applied to abilties which have "
								+ mod.getDisplay();
					}
				}
				if (power.getXMLID().toUpperCase().trim().equals(id)) {
					return getDisplay() + " cannot be applied to "
							+ power.getDisplay();
				}
				ArrayList<Adder> ads = power.getAssignedAdders();
				for (Adder add : ads) {
					if (add.getXMLID().toUpperCase().trim().equals(id)
							&& add.isSelected()) {
						return getDisplay()
								+ " cannot be applied to abilties which have "
								+ add.getDisplay();
					}
				}
			}
			if (requires == null) {
				requires = new ArrayList<String>();
			}
			if (requires.size() > 0) {
				if (requires.size() > 1) {
					if (requiresAll) {
						ret = getDisplay()
								+ " requires the following modifiers:  ";
					} else {
						ret = getDisplay()
								+ " requires at least one of the following: ";
					}
				} else {
					ret = getDisplay() + " requires " + requires.get(0);
				}
				for (int i = 0; i < requires.size(); i++) {
					if (i > 0) {
						ret += ", ";
					}
					if (i == requires.size() - 1) {
						ret += requiresAll ? "and " : "or ";
					}
					ret += requires.get(i);
				}
			}
			boolean missedOne = false;
			REQUIRES: for (String id : requires) {
				id = id.toUpperCase().trim();
				String optionId = null;
				if (id.indexOf(".") > 0 && id.indexOf(".") < id.length() - 1) {
					optionId = id.substring(id.indexOf(".") + 1, id.length());
					if (optionId.trim().length() == 0)
						optionId = null;
					id = id.substring(0, id.indexOf("."));
				}
				if (power.getXMLID().toUpperCase().trim().equals(id)) {
					if (optionId == null
							|| (power.getSelectedOption() != null && power
									.getSelectedOption().getXMLID()
									.equalsIgnoreCase(optionId))) {
						if (requiresAll) {
							continue REQUIRES;
						}
						ret = "";
						break REQUIRES;
					}
				}
				ArrayList<Modifier> mods = power.getAssignedModifiers();
				for (Modifier mod : mods) {
					if (mod.getXMLID().toUpperCase().trim().equals(id)) {
						if (optionId == null
								|| (mod.getSelectedOption() != null && mod
										.getSelectedOption().getXMLID()
										.equalsIgnoreCase(optionId))) {
							if (requiresAll) {
								continue REQUIRES;
							}
							ret = "";
							break REQUIRES;
						}
					}
				}
				ArrayList<Adder> ads = power.getAssignedAdders();
				for (Adder add : ads) {
					if (add.getXMLID().toUpperCase().trim().equals(id)) {
						if (optionId == null
								|| (add.getSelectedOption() != null && add
										.getSelectedOption().getXMLID()
										.equalsIgnoreCase(optionId))) {
							if (requiresAll) {
								continue REQUIRES;
							}
							ret = "";
							break REQUIRES;
						}
					}
				}
				missedOne = true;
				if (requiresAll) {
					break REQUIRES;
				}
			}
			if (!missedOne) {
				ret = "";
			}
		}
		return ret;
	}

	protected void init(Element element) {
		requires = new ArrayList<String>();
		excludes = new ArrayList<String>();
		fullDisplay = false;
		showOptionOnly = false;
		super.init(element);
		availableCheck = false;
		String check = XMLUtility.getValue(element, "XMLID");
		if ((check != null) && (check.trim().length() > 0)) {
			setXMLID(check);
		}
		check = XMLUtility.getValue(element, "SHOWOPTIONONLY");
		if ((check != null) && check.trim().toUpperCase().startsWith("Y")) {
			showOptionOnly = true;
		}
		check = XMLUtility.getValue(element, "DURATION");
		if ((check != null) && (check.trim().length() > 0)) {
			duration = check;
		}
		check = XMLUtility.getValue(element, "SHOWOPTIONINPARENS");
		if ((check != null) && check.trim().toUpperCase().startsWith("Y")) {
			showOptionInParens = true;
		} else {
			showOptionInParens = false;
		}
		check = XMLUtility.getValue(element, "SHOWINPUTINPARENS");
		if ((check != null) && check.trim().toUpperCase().startsWith("Y")) {
			showInputInParens = true;
		} else {
			showInputInParens = false;
		}
		check = XMLUtility.getValue(element, "MULTIPLIER");
		if ((check != null) && check.toUpperCase().startsWith("Y")) {
			isMultiplier = true;
		}
		check = XMLUtility.getValue(element, "REQUIRESALL");
		requiresAll = (check != null)
				&& check.trim().toUpperCase().startsWith("Y");
		java.util.List list = element.getChildren("EXCLUDES");
		Iterator iterator = list.iterator();
		while (iterator.hasNext()) {
			Element child = (Element) iterator.next();
			if ((child.getText() != null)
					&& (child.getText().trim().length() > 0)) {
				excludes.add(child.getText());
			}
		}
		list = element.getChildren("REQUIRES");
		iterator = list.iterator();
		while (iterator.hasNext()) {
			Element child = (Element) iterator.next();
			if ((child.getText() != null)
					&& (child.getText().trim().length() > 0)) {
				requires.add(child.getText());
			}
		}
	}

	/**
	 * Used in the equals method to determine the depth of the comparison.
	 * 
	 * @return
	 */
	public boolean isAvailableCheck() {
		return availableCheck;
	}

	/**
	 * Utility method to determine if Modifier is Limitation or Advantage. May
	 * be overridden by subclasses to provide specific values.
	 * 
	 * @return
	 */
	public boolean isLimitation() {
		if (isLimitationSet) {
			return isLimitation;
		}
	
		else if (getOptions() != null && getOptions().size()>0) {
			boolean hasPositive = false;
			boolean hasNegative = false;
			for (Adder a:getOptions()) {
				if (a.getBaseCost()<0) {
					hasNegative = true;
				}
				if (a.getBaseCost()>0) {
					hasPositive = true;
				}
				if (a.getLevelCost()>0) {
					hasPositive = true;
				}
				if (a.getLevelCost()<0) {
					hasNegative = true;
				}
			}
			if (hasPositive && hasNegative) {
				return getTotalValue() < 0;
			} else if (hasPositive) {
				return false;
			} else if (hasNegative) {
				return true;
			} else {
				return true;
			}
		}
		return getTotalValue() < 0;
	}

	/**
	 * Whether the Modifier is part of the list of private mods or is a common
	 * Modifier. Only valid when applied to a List.
	 * 
	 * @see com.hero.objects.List
	 * @return
	 */
	public boolean isPrivate() {
		if ((getProgenitor() != null) && !(getProgenitor() instanceof List)
				&& !(getProgenitor() instanceof NakedModifier)) {
			return false;
		}
		return privateMod;
	}

	/**
	 * Whether to display an edit dialog or not.
	 * 
	 * @return
	 */
	public boolean noDisplayDialog() {
		return false;
	}

	/**
	 * Whether to refresh available Adders in the edit dialog whenever the
	 * Modifier is altered/updated.
	 * 
	 * @return
	 */
	public boolean refreshAddersOnUpdate() {
		return false;
	}

	public void restoreFromSave(Element root) {
		super.restoreFromSave(root);
		String check = XMLUtility.getValue(root, "PRIVATE");
		if ((check != null) && check.trim().toUpperCase().startsWith("Y")) {
			privateMod = true;
		} else if ((check != null)
				&& check.trim().toUpperCase().startsWith("N")) {
			privateMod = false;
		} else {
			privateMod = getTypes().contains("VPP")
					|| getTypes().contains("MP") || getTypes().contains("EC")
					|| getTypes().contains("LIST");
		}
		check = XMLUtility.getValue(root, "FORCEALLOW");
		if ((check != null) && check.trim().toUpperCase().startsWith("Y")) {
			forceAllow = true;
		} else  {
			forceAllow = false;
		}
		comments = XMLUtility.getValue(root, "COMMENTS");
	}

	/**
	 * Determines the depth of the comparison that is performed during the
	 * equals check.
	 * 
	 * @param val
	 */
	public void setAvailableCheck(boolean val) {
		availableCheck = val;
	}

	/**
	 * Comments are placed inside the parentheses, with the value.
	 * 
	 * @param val
	 */
	public void setComments(String val) {
		comments = val;
	}

	public void setDisplayInString(boolean val) {
		displayInString = val;
	}

	/**
	 * Determines whether to display the full text for this Modifier in the
	 * toString() method or just the display name.
	 * 
	 * @param val
	 */
	public void setFullDisplay(boolean val) {
		fullDisplay = val;
	}

	/**
	 * Sets the parent for this Modifier -- what it is being applied to.
	 * 
	 * @param val
	 */
	public void setParent(GenericObject val) {
		parent = val;
	}

	/**
	 * Whether the Modifier is part of the list of private mods or is a common
	 * Modifier. Only valid when applied to a List.
	 * 
	 * @see com.hero.objects.List
	 * @return
	 */
	public void setPrivate(boolean val) {
		privateMod = val;
	}

	/**
	 * Whether to refresh available Adders in the edit dialog whenever the
	 * Modifier is altered/updated.
	 * 
	 * @return
	 */
	public void setRefreshAddersOnUpdate(boolean val) {
	}

	public String toString() {
		if (fullDisplay) {
			return "<html>" + getColumn2Output() + "</html>";
		} else {
			return getDisplay();
		}
	}

	/**
	 * Whether to display as a Multiplier (e.g. "x3") or as a Modifier (e.g.
	 * "+2")
	 * 
	 * @return
	 */
	public boolean useMultiplier() {
		if (isMultiplier) {
			return true;
		}
		if (parent == null) {
			return false;
		}
		if (parent instanceof Modifier) {
			return true;
		}
		if (parent instanceof Disadvantage) {
			return true;
		} else {
			return false;
		}
	}

}