package com.hero.objects.martialarts;

import java.text.NumberFormat;
import java.util.ArrayList;

import org.jdom.Element;

import com.hero.HeroDesigner;
import com.hero.objects.ElementalControl;
import com.hero.objects.GenericObject;
import com.hero.objects.List;
import com.hero.objects.Multipower;
import com.hero.objects.characteristics.Characteristic;
import com.hero.objects.modifiers.Modifier;
import com.hero.objects.powers.Automaton;
import com.hero.ui.dialog.CustomManeuverDialog;
import com.hero.ui.dialog.GenericDialog;
import com.hero.ui.dialog.ManeuverDialog;
import com.hero.util.Constants;
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 Maneuver extends GenericObject {

	public static final int NONE = 0;
	public static final int STR = 1;
	public static final int NORMAL = 2;
	public static final int KILLING = 3;
	public static final int NND = 4;
	public static final int FLASH = 5;

	private int damageType = 0;

	private boolean custom = false;

	private String OCV;

	private String DCV;

	private String phase;

	private String effect;

	private String weaponEffect;

	private int ranged;

	private String category;

	private boolean useWeapon;

	private int dcs;

	private boolean addSTR;

	private int activeCost;

	private int maxSTR;

	private int STRMultiplier;

	// private static String xmlID = "MANEUVER";

	public int getDamageType() {
		return damageType;
	}

	public void setDamageType(int val) {
		damageType = val;
	}

	public double getActiveCost() {
		return getRealCostPreList();
	}

	private double getActiveCostForEND(GenericObject o) {
		double ret = 0;
		double origBase = getBaseCost();
		baseCost = getEffectiveActiveCost();
		if (baseCost > 0) {
			double total = getTotalCost();
			double advantageTotal = 0d;
			boolean advantagesApplied = false;
			for (Modifier mod : getAssignedModifiers()) {
				if (o != null && mod.getXMLID().equals(o.getXMLID())) {
					continue;
				}
				if (mod.getTotalValue() > 0) {
					advantageTotal += mod.getTotalValue();
					advantagesApplied = true;
				}
			}
			List parent = getParentList();
			if (parent == null && getMainPower() != null) {
				parent = getMainPower().getParentList();
			}
			if (parent != null) {
				LOOP: for (Modifier mod : parent.getAssignedModifiers()) {
					if (o != null && mod.getXMLID().equals(o.getXMLID())) {
						continue;
					}
					if (mod.getTypes().contains("VPP")) {
						continue LOOP;
					}
					if (mod.getXMLID().equals("CHARGES")
							&& parent instanceof Multipower) {
						continue LOOP;
					}
					if (mod.getTotalValue() > 0
							&& (GenericObject.findObjectByID(assignedModifiers,
									mod.getXMLID()) == null
									|| mod.getXMLID().equals("GENERIC_OBJECT")
									|| mod.getXMLID().equals("CUSTOM_MODIFIER") || mod
									.getXMLID().equals("MODIFIER"))) {
						if (parent instanceof Multipower
								|| parent instanceof ElementalControl) {
							continue;
						} else {
							advantageTotal += mod.getTotalValue();
						}
						advantagesApplied = true;
					}
				}
			}
			double val = total * (1 + advantageTotal);
			if (advantagesApplied) {
				val = Rounder.roundHalfDown(val);
			}
			ret += val - baseCost;
		}
		baseCost = origBase;
		return ret;
	}

	public double getRealCostPreList() {
		double ret = getBaseCost();
		double origBase = ret;
		baseCost = getEffectiveActiveCost();
		if (baseCost > 0) {
			ret += super.getActiveCost("REDUCEDEND") - baseCost;
			if (ret < origBase) {
				ret = origBase;
			}
			ArrayList<Modifier> vec = (ArrayList<Modifier>) getAssignedModifiers()
					.clone();
			if (getParentList() != null) {
				vec.addAll(getParentList().getAssignedModifiers());
			}
			if (GenericObject.findObjectByID(vec, "REDUCEDEND") != null) {
				Modifier mod = (Modifier) GenericObject.findObjectByID(vec,
						"REDUCEDEND");
				int additional = (int) Rounder.roundHalfDown((ret - origBase)
						* (1 + mod.getTotalValue()) - (ret - origBase));
				ret += additional;
			}

			boolean limitationsApplied = false;
			double limitationTotal = 0d;
			for (Modifier mod : assignedModifiers) {
				if (mod.getTotalValue() < 0) {
					limitationTotal += mod.getTotalValue();
					limitationsApplied = true;
				}
			}
			List parent = getParentList();
			if (parent == null && getMainPower() != null) {
				parent = getMainPower().getParentList();
			}
			if (parent != null) {
				LOOP: for (Modifier mod : parent.getAssignedModifiers()) {
					if (mod.getTypes().contains("VPP")) {
						continue LOOP;
					}
					if (mod.getXMLID().equals("CHARGES")
							&& getParentList() instanceof Multipower) {
						continue LOOP;
					}
					if (mod.getTotalValue() < 0
							&& (GenericObject.findObjectByID(assignedModifiers,
									mod.getXMLID()) == null
									|| mod.getXMLID().equals("GENERIC_OBJECT")
									|| mod.getXMLID().equals("CUSTOM_MODIFIER") || mod
									.getXMLID().equals("MODIFIER"))) {
						limitationTotal += mod.getTotalValue();
						limitationsApplied = true;
					}
				}
			}
			ret = ret / (1d + Math.abs(limitationTotal));
			if (limitationsApplied) {
				ret = Rounder.roundHalfDown(ret);
			}

			if (ret < 1) {
				ret = 1;
			}

			/*if (ret < origBase) {
				ret = origBase;
			}
			removed this per Steve Long -- phone call on 04/28/2010
			*/
		}
		baseCost = origBase;

		if (HeroDesigner.getActiveHero() != null
				&& HeroDesigner.getActiveHero().getRules().multiplierAllowed()
				&& getMultiplier() != 1) {
			ret = ret * getMultiplier();
			ret = Rounder.roundHalfDown(ret);
		} else if (HeroDesigner.getActiveHero() != null
				&& HeroDesigner.getActiveHero().getRules().multiplierAllowed()
				&& getParentList() != null
				&& getParentList().getMultiplier() != 1) {
			ret = ret * getParentList().getMultiplier();
			ret = Rounder.roundHalfDown(ret);
		}
		if (getQuantity() > 1) {
			double q = (double) getQuantity();
			int doublings = 0;
			while (q > 1d) {
				doublings += 1;
				q = q / 2d;
			}
			ret += doublings * 5;
		}

		return ret;
	}

	public String getXMLID() {
		return "MANEUVER";
	}

	public Maneuver(Element root) {
		super(root);
		target = "DCV";
		doesBODY = true;
		if (effect.indexOf("NNDDC") >= 0) {
			doesBODY = false;
		}
		if (effect.indexOf("FLASHDC") >= 0) {
			doesBODY = false;
		}
		if (effect.indexOf("FLASHDC") >= 0) {
			doesKnockback = false;
		}
		killing = false;
		if (effect.indexOf("KILLINGDC") >= 0) {
			killing = true;
		}
		visible = true;
		usesEND = true;
	}

	/**
	 * Lots of funkiness here, according to the rules from UMA.
	 * 
	 * @return
	 */
	public double getEffectiveActiveCost() {
		double ret = activeCost;
		if (addSTR) {
			// getColumn2Output(); //just to ensure that maxSTR has been set...
			Characteristic STR = HeroDesigner.getInstance().getActiveHero()
					.getCharacteristic(Constants.STR);
			if (STR != null) {
				double lvl = STR.getSecondaryValue();
				if (maxSTR > 0 && lvl > maxSTR) {
					lvl = maxSTR;
				}
				ret += lvl * STR.getLevelCost() / STR.getLevelValue()
						* STRMultiplier;
			}
		}
		if (doesDamage()) {
			boolean hth = getCategory().trim().toUpperCase().startsWith("HAND");
			for (GenericObject go : HeroDesigner.getActiveHero().getManeuvers()) {
				if (hth && go.getXMLID().equals("EXTRADC")) {
					ret += go.getLevels() * 5;
				} else if (!hth && go.getXMLID().equals("RANGEDDC")) {
					ret += go.getLevels() * 5;
				}
			}
		}
		if (ret < 1) {
			ret = 1;
		}
		return ret;
	}

	public void setManeuverActiveCost(int val) {
		activeCost = val;
	}

	public int getManeuverActiveCost() {
		return activeCost;
	}

	/**
	 * Assumes 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
	 */
	public static String getFraction(double val) {
		String ret = "";
		if (val == 0) {
			return "+0";
		}
		if (val < 0) {
			ret += "-";
		} else {
			ret += "+";
		}
		val = Math.abs(val);
		if (val > 1) {
			ret += (int) Rounder.roundDown(val);
			val = val - Rounder.roundDown(val);
		}
		if (val == 0) {
			return ret;
		}
		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 {
				ret = ret + 1;
			}
		}
		if (ret.length() > 1) {
			ret += " ";
		}
		ret += closestMatch;
		ret = ret.trim();
		return ret;
	}

	/**
	 * Whether or not this is a custom Maneuver
	 * 
	 * @param val
	 */
	public void setCustom(boolean val) {
		custom = val;
		minimumCost = 1;
		minSet = true;
		maxSet = true;
		maxCost = 5;
	}

	public int getOCVValue() {
		String ret = OCV.trim();
		if (ret.startsWith("+")) {
			ret = ret.substring(1);
		}
		try {
			return Integer.parseInt(ret);
		} catch (Exception exp) {
			return 0;
		}
	}

	public int getDCVValue() {
		String ret = DCV.trim();
		if (ret.startsWith("+")) {
			ret = ret.substring(1);
		}
		try {
			return Integer.parseInt(ret);
		} catch (Exception exp) {
			return 0;
		}
	}

	protected void init(Element element) {
		display = "???";
		alias = "???";
		baseCost = 3;
		levelCost = 0;
		levelValue = 0;
		minimumCost = 3;
		minimumLevel = 0;
		OCV = "--";
		DCV = "--";
		phase = "1/2";
		effect = "";
		weaponEffect = "";
		ranged = 0;
		category = "Hand To Hand";
		dcs = 0;
		useWeapon = false;
		super.init(element);
		if (types == null) {
			types = new ArrayList<String>();
		}
		types.add("ATTACK");
		String check = XMLUtility.getValue(element, "OCV");
		if (check != null && check.trim().length() > 0) {
			try {
				OCV = check;
				int test = Integer.parseInt(check);
				if (test >= 0) {
					OCV = "+" + OCV;
				}
			} catch (NumberFormatException ex) {
			}
		}
		check = XMLUtility.getValue(element, "CUSTOM");
		if (check != null && check.trim().toUpperCase().startsWith("Y")) {
			custom = true;
		} else {
			custom = false;
		}
		check = XMLUtility.getValue(element, "ALLOWSOTHERADDERS");
		if (check != null && check.trim().toUpperCase().startsWith("Y")) {
			allowsOtherAdders = true;
		} else {
			allowsOtherAdders = false;
		}
		check = XMLUtility.getValue(element, "DCV");
		if (check != null && check.trim().length() > 0) {
			try {
				DCV = check;
				int test = Integer.parseInt(check);
				if (test >= 0) {
					DCV = "+" + DCV;
				}
			} catch (NumberFormatException ex) {
			}
		}
		check = XMLUtility.getValue(element, "RANGE");
		if (check != null && check.trim().length() > 0) {
			try {
				ranged = Integer.parseInt(check);
			} catch (Exception exp) {
			}
		}
		check = XMLUtility.getValue(element, "ACTIVECOST");
		if (check != null && check.trim().length() > 0) {
			try {
				activeCost = Integer.parseInt(check);
			} catch (Exception exp) {
			}
		}
		check = XMLUtility.getValue(element, "MAXSTR");
		if (check != null && check.trim().length() > 0) {
			try {
				maxSTR = Integer.parseInt(check);
			} catch (Exception exp) {
			}
		}
		check = XMLUtility.getValue(element, "STRMULT");
		if (check != null && check.trim().length() > 0) {
			try {
				STRMultiplier = Integer.parseInt(check);
			} catch (Exception exp) {
				STRMultiplier = 1;
			}
		} else {
			STRMultiplier = 1;
		}
		if (STRMultiplier < 1) {
			STRMultiplier = 1;
		}
		check = XMLUtility.getValue(element, "ADDSTR");
		if (check != null && check.trim().toUpperCase().startsWith("Y")) {
			addSTR = true;
		} else {
			addSTR = false;
		}
		check = XMLUtility.getValue(element, "DC");
		if (check != null && check.trim().length() > 0) {
			try {
				dcs = Integer.parseInt(check);
			} catch (Exception exp) {
			}
		}
		check = XMLUtility.getValue(element, "CATEGORY");
		if (check != null && check.trim().length() > 0) {
			category = check;
		}
		check = XMLUtility.getValue(element, "PHASE");
		if (check != null && check.trim().length() > 0) {
			phase = check;
		}
		check = XMLUtility.getValue(element, "EFFECT");
		if (check != null && check.trim().length() > 0) {
			effect = check;
		}
		check = XMLUtility.getValue(element, "WEAPONEFFECT");
		if (check != null && check.trim().length() > 0) {
			weaponEffect = check;
		}
	}

	public int compareTo(Object obj) {
		if (obj instanceof Maneuver) {
			Maneuver manObj = (Maneuver) obj;
			if (getCategory().equals(manObj.getCategory())) {
				return getDisplay().compareTo(manObj.getDisplay());
			} else {
				return getCategory().compareTo(manObj.getCategory());
			}
		} else {
			return toString().compareTo(obj.toString());
		}
	}

	/**
	 * The category of the maneuver. Typically "Hand to Hand" or "Ranged"
	 * 
	 * @return
	 */
	public String getCategory() {
		return category;
	}

	public void setCategory(String category) {
		this.category = category;
	}

	public void setDCV(String DCV) {
		this.DCV = DCV;
	}

	public String getDCV() {
		return DCV;
	}

	String replace(String val, String replace, String replaceWith) {
		while (val.indexOf(replace) >= 0) {
			if (val.indexOf(replace) == 0) {
				val = replaceWith.trim() + " "
						+ val.substring(replace.length(), val.length()).trim();
			} else {
				if (replaceWith.trim().length() == 0) {
					replaceWith = " ";
				}
				String part1 = val.substring(0, val.indexOf(replace));
				String part2 = val.substring(val.indexOf(replace)
						+ replace.length(), val.length());
				val = part1.trim() + " " + replaceWith.trim() + " "
						+ part2.trim();
			}
		}
		return val.trim();
	}

	/**
	 * Adds in Strength and Damage Classes to determine the total damage done by
	 * the maneuver (split value if needed).
	 * 
	 * @param primary
	 * @param maxDouble
	 * @return
	 */
	public double getTotalDC(boolean primary, boolean maxDouble) {
		double ret = getTotalDCNoSTR();
		double holder = ret;
		if (addSTR) {
			Characteristic STR = HeroDesigner.getInstance().getActiveHero()
					.getCharacteristic(Constants.STR);
			if (STR != null) {
				if (primary) {
					if (STR.getPrimaryValue() > 0) {
						ret += STR.getPrimaryValue() / 5;
					}
				} else {
					if (STR.getSecondaryValue() > 0) {
						ret += STR.getSecondaryValue() / 5;
					}
				}
			}
		}
		if (maxDouble && ret > holder * 2) {
			ret = holder * 2;
		}
		if (maxDouble && maxSTR > 0) { // adjust the maxSTR
			// value...
			maxSTR = (int) (ret - holder) * 5;
		}
		return ret;
	}

	/**
	 * Get the total damage of the maneuver without STR.
	 * 
	 * @return
	 */
	public int getTotalDCNoSTR() {
		int ret = dcs;
		boolean handToHand = true;
		if (getCategory().equalsIgnoreCase("RANGED")) {
			handToHand = false;
		}
		if (HeroDesigner.getActiveHero() != null) {
			for (GenericObject obj : HeroDesigner.getActiveHero()
					.getManeuvers()) {
				if (obj instanceof ExtraDamageClasses && handToHand) {
					ret += obj.getLevels();
				} else if (obj instanceof RangedDamageClasses && !handToHand) {
					ret += obj.getLevels();
				}
			}
		}
		return ret;
	}

	/**
	 * Get the Normal damage classes (non-killing) with STR.
	 * 
	 * @return
	 */
	public String getNormalDC() {
		double tot = getTotalDC(true, false);
		double tot2 = getTotalDC(false, false);
		if (tot < .5 && tot2 < .5) {
			return "";
		}
		String ret = " ";
		ret += Rounder.roundDown(tot) + "";
		if (tot - Rounder.roundDown(tot) >= .5) {
			ret += " 1/2";
		}
		ret += "d6";
		if (tot != tot2) {
			ret += " / ";
			ret += Rounder.roundDown(tot2) + "";
			if (tot2 - Rounder.roundDown(tot2) >= .5) {
				ret += " 1/2";
			}
			ret += "d6";
		}
		return ret;
	}

	/**
	 * get the damage classes of the maneuver (for use in weapon-based
	 * maneuvers). STR is not added in.
	 * 
	 * @return
	 */
	public String getWeaponDC() {
		int tot = getTotalDCNoSTR();
		if (tot == 0) {
			return "";
		}
		String ret = " ";
		if (tot > 0) {
			ret += "+";
		}
		ret += tot + " DC ";
		return ret;
	}

	/**
	 * Get the flash damage of the maneuver.
	 * 
	 * @return
	 */
	public String getFlashDC() {
		double tot = getTotalDC(true, false);
		double tot2 = getTotalDC(false, false);
		if (tot < .5 && tot2 < .5) {
			return "";
		}
		String ret = " Flash ";
		ret += Rounder.roundDown(tot) + "";
		if (tot - Rounder.roundDown(tot) >= .5) {
			ret += " 1/2";
		}
		ret += "d6";
		if (tot != tot2) {
			ret += " / ";
			ret += Rounder.roundDown(tot2) + "";
			if (tot2 - Rounder.roundDown(tot2) >= .5) {
				ret += " 1/2";
			}
			ret += "d6";
		}
		return ret;
	}

	/**
	 * get the NND damage of the maneuver.
	 * 
	 * @return
	 */
	public String getNNDDC() {
		double tot = getTotalDC(true, true);
		double tot2 = getTotalDC(false, true);
		if (tot < .5 && tot2 < .5) {
			return "";
		}
		double val = tot / 2d;
		double val2 = tot2 / 2d;
		String ret = " ";
		String dmg = Maneuver.getFraction(val);
		dmg = dmg.substring(1, dmg.length()); // strip the
		// leading "+"...
		ret += dmg + "d6 NND";
		if (val != val2) {
			dmg = Maneuver.getFraction(val);
			dmg = dmg.substring(1, dmg.length()); // strip the
			// leading
			// "+"...
			ret += " / " + dmg + "d6 NND";
		}
		return ret;
	}

	/**
	 * get the STR total for the maneuver.
	 * 
	 * @return
	 */
	public String getSTRDC() {
		double tot = getTotalDC(true, false);
		double tot2 = getTotalDC(false, false);
		if (tot < .5 && tot2 < .5) {
			return "";
		}
		String ret = " ";
		ret += NumberFormat.getIntegerInstance().format(tot * 5) + " STR ";
		if (tot != tot2) {
			ret += " / " + NumberFormat.getIntegerInstance().format(tot2 * 5)
					+ " STR ";
		}
		return ret;
	}

	/**
	 * get the killing damage for the maneuver.
	 * 
	 * @return
	 */
	public String getKillingDC() {
		double preSTR = getTotalDCNoSTR();
		double tot = getTotalDC(true, true);
		double tot2 = getTotalDC(false, true);
		double red = 2d;
		if (HeroDesigner.getActiveTemplate().is6E()) {
			red = 1d;
		}
		if (HeroDesigner.getActiveTemplate().is6E()) {
			tot = getTotalDC(true, false);
			tot2 = getTotalDC(false, false);
		} else {
			tot = preSTR
					/ red
					+ (tot - preSTR >= preSTR / red ? preSTR / red : tot
							- preSTR);
			tot2 = preSTR
					/ red
					+ (tot2 - preSTR >= preSTR / red ? preSTR / red : tot2
							- preSTR);
		}
		if (tot < .5 && tot2 < .5) {
			return "";
		}
		String dmg = "";
		int dice = (int) Rounder.roundDown(tot / 3);
		int rem = (int) (tot % 3);
		dmg = "" + dice;
		if (rem == 1) {
			dmg = dmg + "d6 +1";
		} else if (rem == 2) {
			dmg = dmg + " 1/2d6";
		} else {
			dmg = dmg + "d6";
		}
		String ret = " HKA " + dmg;
		if (tot != tot2) {
			dice = (int) Rounder.roundDown(tot2 / 3);
			rem = (int) (tot2 % 3);
			dmg = "" + dice;
			if (rem == 1) {
				dmg = dmg + "d6 +1";
			} else if (rem == 2) {
				dmg = dmg + " 1/2d6";
			} else {
				dmg = dmg + "d6";
			}
			ret += " / HKA " + dmg;
		}
		return ret;
	}

	/**
	 * get the killing damage for a weapon-based maneuver.
	 * 
	 * @return
	 */
	public String getWeaponKillingDC() {
		int tot = getTotalDCNoSTR();
		double red = 2d;
		if (HeroDesigner.getActiveTemplate().is6E()) {
			red = 1d;
		}
		tot = (int) Rounder.roundDown(tot / red);
		if (tot == 0) {
			return "";
		}
		String ret = " HKA ";
		ret += tot + " DC ";
		return ret;
	}

	/**
	 * Get the effect display for the maneuver.
	 * 
	 * @return
	 */
	public String getEffect() {
		String ret = effect;
		ret = replace(ret, "[NORMALDC]", getNormalDC());
		ret = replace(ret, "[WEAPONDC]", getWeaponDC());
		ret = replace(ret, "[FLASHDC]", getFlashDC());
		ret = replace(ret, "[NNDDC]", getNNDDC());
		ret = replace(ret, "[STRDC]", getSTRDC());
		ret = replace(ret, "[KILLINGDC]", getKillingDC());
		ret = replace(ret, "[WEAPONKILLINGDC]", getWeaponKillingDC());
		ret += getModifierString();
		return ret;
	}

	public String getDamageString() {
		String ret = "";
		if (useWeapon()) {
			return ("Weapon " + getWeaponDC()).trim();
		} else {
			switch (getDamageType()) {
			case NONE:
				return "";
			case STR:
				return getSTRDC();
			case NORMAL:
				return getNormalDC();
			case KILLING:
				return getKillingDC();
			case NND:
				return getNNDDC();
			case FLASH:
				return getFlashDC();
			}

		}

		return ret;
	}

	public void setOCV(String OCV) {
		this.OCV = OCV;
	}

	public String getOCV() {
		return OCV;
	}

	public String getPhase() {
		return phase;
	}

	public void setPhase(String phase) {
		this.phase = phase;
	}

	public String getWeaponEffect() {
		String ret = weaponEffect;
		ret = replace(ret, "[NORMALDC]", getNormalDC());
		ret = replace(ret, "[WEAPONDC]", getWeaponDC());
		ret = replace(ret, "[FLASHDC]", getFlashDC());
		ret = replace(ret, "[NNDDC]", getNNDDC());
		ret = replace(ret, "[STRDC]", getSTRDC());
		ret = replace(ret, "[KILLINGDC]", getKillingDC());
		ret = replace(ret, "[WEAPONKILLINGDC]", getWeaponKillingDC());
		ret += getModifierString();
		return ret;
	}

	public int getRanged() {
		return ranged;
	}

	public void setRanged(int ranged) {
		this.ranged = ranged;
		if (ranged >= 0) {
			category = "Ranged";
		} else {
			category = "Hand to Hand";
		}
	}

	public void setUseWeapon(boolean val) {
		useWeapon = val;
	}

	public boolean useWeapon() {
		return useWeapon;
	}

	public void setEffect(String val) {
		effect = val;
		weaponEffect = val;
	}

	public String getColumn3Output() {
		if (getEndUsage() == 0) {
			return null;
		} else {
			return "" + getEndUsage();
		}
	}

	public boolean isCustom() {
		return custom;
	}

	public String getManeuverEffect() {
		String ret = "";
		String eff = "";
		if (useWeapon()) {
			eff += getWeaponEffect();
		} else {
			eff += getEffect();
		}
		if (eff.indexOf("[DAMAGE]") >= 0) {
			eff = eff.replaceAll("\\[DAMAGE\\]", getDamageString());
			ret += eff;
		} else {
			String dmg = getDamageString();
			if (dmg != null && dmg.trim().length() > 0) {
				ret += dmg + ", ";
			}
			ret += eff;
		}
		return ret;
	}

	public String getColumn2Output() {
		String ret = getAlias();
		ret += ":  " + getPhase() + " Phase, " + getOCV() + " OCV, " + getDCV()
				+ " DCV, ";
		if (getCategory().equalsIgnoreCase("Ranged")) {
			ret += "Range " + (getRanged() >= 0 ? "+" : "-") + getRanged()
					+ ", ";
		}
		if (custom) {
			ret += getManeuverEffect();
		} else {
			if (useWeapon()) {
				ret += getWeaponEffect();
			} else {
				ret += getEffect();
			}
		}

		if (getEndUsage() > 0
				&& GenericObject.findObjectByID(HeroDesigner.getActiveHero()
						.getPowers(), "ENDURANCERESERVE") != null
				&& GenericObject.findObjectByID(getAssignedModifiers(),
						"ENDRESERVEOREND") == null
				&& !HeroDesigner.getInstance().getPrefs().useWG()) {
			if (getUseENDReserve()) {
				ret += " (uses END Reserve)";
			} else {
				ret += " (uses Personal END)";
			}
		}

		return ret;
	}

	public int getDC() {
		return dcs;
	}

	public void setDC(int val) {
		dcs = val;
	}

	public Element getSaveXML() {
		Element root = getGeneralSaveXML();
		root.setName("MANEUVER");
		if (custom) {
			root.setAttribute("CUSTOM", "Yes");
		}
		root.setAttribute("CATEGORY", getCategory());
		root.setAttribute("DISPLAY", "" + display);
		root.setAttribute("OCV", "" + getOCV());
		root.setAttribute("DCV", "" + getDCV());
		root.setAttribute("DC", "" + getDC());
		root.setAttribute("PHASE", getPhase());
		root.setAttribute("EFFECT", effect);
		root.setAttribute("ADDSTR", addSTR ? "Yes" : "No");
		root.setAttribute("ACTIVECOST", "" + activeCost);
		root.setAttribute("DAMAGETYPE", "" + damageType);
		root.setAttribute("MAXSTR", "" + maxSTR);
		root.setAttribute("STRMULT", "" + STRMultiplier);
		root.setAttribute("USEWEAPON", useWeapon ? "Yes" : "No");
		if (getWeaponEffect() != null && getWeaponEffect().trim().length() > 0) {
			root.setAttribute("WEAPONEFFECT", weaponEffect);
		}
		if (getCategory() != null && getCategory().equalsIgnoreCase("Ranged")) {
			root.setAttribute("RANGE", "" + getRanged());
		}
		return root;
	}

	public void restoreFromSave(Element root) {
		super.restoreFromSave(root);
		String check = XMLUtility.getValue(root, "CATEGORY");
		if (check != null && check.trim().length() > 0) {
			category = check;
		}
		check = XMLUtility.getValue(root, "OCV");
		if (check != null && check.trim().length() > 0) {
			OCV = check;
		}
		check = XMLUtility.getValue(root, "DCV");
		if (check != null && check.trim().length() > 0) {
			DCV = check;
		}
		check = XMLUtility.getValue(root, "DAMAGETYPE");
		if (check != null && check.trim().length() > 0) {
			try {
				damageType = Integer.parseInt(check);
			} catch (NumberFormatException ex) {
			}
		}
		check = XMLUtility.getValue(root, "RANGE");
		if (check != null && check.trim().length() > 0) {
			try {
				ranged = Integer.parseInt(check);
			} catch (NumberFormatException ex) {
			}
		}
		check = XMLUtility.getValue(root, "DC");
		if (check != null && check.trim().length() > 0) {
			try {
				dcs = Integer.parseInt(check);
			} catch (NumberFormatException ex) {
			}
		}
		check = XMLUtility.getValue(root, "PHASE");
		if (check != null && check.trim().length() > 0) {
			phase = check;
		}
		check = XMLUtility.getValue(root, "EFFECT");
		if (check != null && check.trim().length() > 0) {
			effect = check;
		}

		if (effect.indexOf("STR [NORMALDC]") >= 0) {
			effect = effect.substring(0, effect.indexOf("STR [NORMALDC]"))
					+ effect.substring(effect.indexOf("STR [NORMALDC]") + 4,
							effect.length());
		}
		check = XMLUtility.getValue(root, "WEAPONEFFECT");
		if (check != null && check.trim().length() > 0) {
			weaponEffect = check;
		}
		check = XMLUtility.getValue(root, "USEWEAPON");
		if (check != null && check.trim().toUpperCase().startsWith("Y")) {
			useWeapon = true;
		} else {
			useWeapon = false;
		}
		check = XMLUtility.getValue(root, "ACTIVECOST");
		try {
			activeCost = Integer.parseInt(check);
		} catch (Exception exp) {
		}
		check = XMLUtility.getValue(root, "CUSTOM");
		if (check != null) {
			custom = check.startsWith("Y");
		}
	}

	public GenericDialog getDialog(boolean isNew, boolean isPower) {
		if (custom) {
			return new CustomManeuverDialog(this, isNew, isPower);
		} else {
			return new ManeuverDialog(this, isNew, isPower);
		}
	}

	public int getEndUsage() {
		int denom = HeroDesigner.getActiveHero().getRules().getAPPerEND();
		double ret = 0;
		double active = getActiveCostForEND(null);
		double mult = 1;
		ArrayList<Modifier> vec = (ArrayList<Modifier>) getAssignedModifiers()
				.clone();
		List parent = getParentList();
		if (parent == null && getMainPower() != null) {
			parent = getMainPower().getParentList();
		}
		if (parent != null) {
			ArrayList<Modifier> par = parent.getAssignedModifiers();
			for (Modifier mod : par) {
				if (getParentList() instanceof ElementalControl) {
					vec.add(mod);
				} else {
					vec.add(mod);
				}
			}
		}
		/*
		 * if (GenericObject.findObjectByID(vec, "UOO") != null) { denom =
		 * HeroDesigner.getActiveHero().getRules().getAPPerEND(); }
		 */
		if (GenericObject.findObjectByID(vec, "CHARGES") != null) {
			denom = 0;
		}
		if (GenericObject.findObjectByID(vec, "COSTSEND") != null) {
			denom = HeroDesigner.getActiveHero().getRules().getAPPerEND();
		}
		if (GenericObject.findObjectByID(vec, "REDUCEDEND") != null) {
			GenericObject mod = GenericObject.findObjectByID(vec, "REDUCEDEND");
			if (mod.getSelectedOption().getXMLID().equals("HALFEND")) {
				mult = .5;
			} else {
				denom = 0;
			}
			active = getActiveCostForEND(mod);
		}
		if (GenericObject.findObjectByID(vec, "COSTSENDONLYTOACTIVATE") != null) {
			GenericObject mod = GenericObject.findObjectByID(vec,
					"COSTSENDONLYTOACTIVATE");
			active = getActiveCostForEND(mod);
		}
		if (GenericObject.findObjectByID(vec, "INCREASEDEND") != null) {
			GenericObject mod = GenericObject.findObjectByID(vec,
					"INCREASEDEND");
			if (GenericObject.findObjectByID(mod.getAssignedAdders(),
					"CIRCUMSTANCE") == null) {
				mult = (int) Rounder.roundHalfUp(mod.getSelectedOption()
						.getLevelValue());
			}
		}
		if (getTypes().contains("DEFENSE")
				&& HeroDesigner.getActiveHero() != null) {
			if (GenericObject.findObjectByID(HeroDesigner.getActiveHero()
					.getPowers(), "AUTOMATON") != null) {
				Automaton auto = (Automaton) GenericObject.findObjectByID(
						HeroDesigner.getActiveHero().getPowers(), "AUTOMATON");
				if (auto.getSelectedOption().getXMLID().toUpperCase()
						.startsWith("NOSTUN")) {
					active = active / auto.getDefenseCostMultiplier();
				}
			}
		}

		if (denom != 0) {
			ret = active / denom;
		}
		if (Rounder.roundHalfDown(ret) == 0 && active > 0 && denom != 0) {
			ret = 1;
		}
		ret = Rounder.roundHalfDown(ret);
		ret = ret * mult;
		if (Rounder.roundHalfDown(ret) == 0 && active > 0 && denom != 0) {
			ret = 1;
		}
		if (ret < 0) {
			ret = 0;
		}
		return (int) Rounder.roundHalfDown(ret);
	}

	public boolean usesEND() {
		boolean ret = usesEND;
		if (apperend != -1) {
			usesEND = apperend > 0;
		}
		ArrayList<Modifier> vec = (ArrayList<Modifier>) getAssignedModifiers()
				.clone();
		List parent = getParentList();
		if (parent == null && getMainPower() != null) {
			parent = getMainPower().getParentList();
		}
		if (parent != null) {
			ArrayList<Modifier> par = parent.getAssignedModifiers();
			for (Modifier mod : par) {
				if (getParentList() instanceof ElementalControl) {
					if (mod.getTotalValue() < 0) {
						vec.add(mod);
					}
				} else {
					vec.add(mod);
				}
			}
		}

		boolean hasAdvantages = false;
		for (Modifier mod : vec) {
			if (mod.getTotalValue() > 0) {
				hasAdvantages = true;
			}
		}
		if (!hasAdvantages) {
			return false;
		}

		/*
		 * if (GenericObject.findObjectByID(vec, "UOO") != null) { ret = true; }
		 */
		if (GenericObject.findObjectByID(vec, "CHARGES") != null) {
			ret = false;
		}
		if (GenericObject.findObjectByID(vec, "COSTSEND") != null) {
			ret = true;
		}
		if (GenericObject.findObjectByID(vec, "REDUCEDEND") != null) {
			GenericObject mod = GenericObject.findObjectByID(vec, "REDUCEDEND");
			if (mod.getSelectedOption().getXMLID().equals("HALFEND")) {
				ret = true;
			} else {
				ret = false;
			}
		}
		return ret;
	}

	@Override
	public boolean allowsOtherModifiers() {
		return (getManeuverActiveCost() > 0);
	}

}