package com.hero.objects.powers;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;

import org.jdom.Element;

import com.hero.HeroDesigner;
import com.hero.objects.Adder;
import com.hero.objects.GenericObject;
import com.hero.ui.dialog.GenericDialog;
import com.hero.ui.dialog.SenseDialog;
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 Sense extends Power {
	private static ArrayList<Sense> allSenses = new ArrayList<Sense>();

	private static ArrayList<Sense> builtInSenses = new ArrayList<Sense>();

	private static long lastCalculated;

	private static ArrayList<Sense> timeSaver;

	/**
	 * Called to clear all available Senses (including built-in Senses). This is
	 * an extremely dangerous method to call and should only be called when
	 * loading in a new template.
	 */
	public static void clear() {
		Sense.allSenses.clear();
		Sense.builtInSenses.clear();
	}

	/**
	 * Returns a Vector of Sense objects containing all Senses defined in the
	 * active template.
	 * 
	 * @return
	 */
	public static ArrayList<Sense> getAllSenses() {
		return Sense.allSenses;
	}

	/**
	 * Returns a Vector of Sense objects containing all Senses that are
	 * automatically possessed by a character. These values are defined in the
	 * template.
	 * 
	 * @return
	 */
	public static ArrayList<Sense> getBuiltInSenses() {
		return Sense.builtInSenses;
	}

	/**
	 * Returns a Vector of Sense objects that defines all Senses that the
	 * character possesses (either built-in or purchased).
	 * 
	 * @return
	 */
	public static ArrayList<Sense> getOwnedSenses() {
		long current = System.currentTimeMillis();
		if ((Sense.lastCalculated > 0)
				&& (Sense.lastCalculated >= GenericObject.lastEdit)
				&& (HeroDesigner.lastEdit > 0)
				&& (Sense.lastCalculated > HeroDesigner.lastEdit)
				&& (Sense.timeSaver != null)) {
			return Sense.timeSaver;
		}
		Sense.lastCalculated = current;
		ArrayList<Sense> ret = new ArrayList<Sense>();
		ret.addAll(Sense.builtInSenses);
		if (HeroDesigner.getActiveHero() != null) {
			for (GenericObject o : HeroDesigner.getActiveHero().getTalents()) {
				Sense sense = (Sense) GenericObject.findObjectByID(
						Sense.allSenses, o.getXMLID());
				if ((sense != null) && sense.allowSenseModifiers()) {
					if (o instanceof Sense) {
						ret.add((Sense) o);
					} else {
						ret.add(sense);
					}
				} else if (o instanceof CompoundPower) {
					CompoundPower cp = (CompoundPower) o;
					for (GenericObject o2 : cp.getPowers()) {
						Sense sense2 = (Sense) GenericObject.findObjectByID(
								Sense.allSenses, o2.getXMLID());
						if ((sense2 != null) && sense2.allowSenseModifiers()) {
							if (o2 instanceof Sense) {
								ret.add((Sense) o2);
							} else {
								ret.add(sense2);
							}
						}
					}
				}
			}
			for (GenericObject o : HeroDesigner.getActiveHero().getPowers()) {
				Sense sense = (Sense) GenericObject.findObjectByID(
						Sense.allSenses, o.getXMLID());
				if ((sense != null) && sense.allowSenseModifiers()) {
					if (o instanceof Sense) {
						ret.add((Sense) o);
					} else {
						ret.add(sense);
					}
				} else if (o instanceof CompoundPower) {
					CompoundPower cp = (CompoundPower) o;
					for (GenericObject o2 : cp.getPowers()) {
						Sense sense2 = (Sense) GenericObject.findObjectByID(
								Sense.allSenses, o2.getXMLID());
						if ((sense2 != null) && sense2.allowSenseModifiers()) {
							if (o2 instanceof Sense) {
								ret.add((Sense) o2);
							} else {
								ret.add(sense2);
							}
						}
					}
				}
			}
			for (GenericObject o : HeroDesigner.getActiveHero().getEquipment()) {
				Sense sense = (Sense) GenericObject.findObjectByID(
						Sense.allSenses, o.getXMLID());
				if ((sense != null) && sense.allowSenseModifiers()) {
					if (o instanceof Sense) {
						ret.add((Sense) o);
					} else {
						ret.add(sense);
					}
				} else if (o instanceof CompoundPower) {
					CompoundPower cp = (CompoundPower) o;
					for (GenericObject o2 : cp.getPowers()) {
						Sense sense2 = (Sense) GenericObject.findObjectByID(
								Sense.allSenses, o2.getXMLID());
						if ((sense2 != null) && sense2.allowSenseModifiers()) {
							if (o2 instanceof Sense) {
								ret.add((Sense) o2);
							} else {
								ret.add(sense2);
							}
						}
					}
				}
			}
		}
		Sense.timeSaver = ret;
		return ret;
	}

	/**
	 * Find a Sense by XMLID.
	 * 
	 * @param id
	 * @return
	 */
	public static Sense getSenseByID(String id) {
		ArrayList<Sense> all = Sense.getAllSenses();
		for (Sense s : all) {
			if (s.getXMLID().equals(id)) {
				return s;
			}
		}
		return null;
	}

	protected boolean active;

	private boolean activeSelectable;

	private boolean allowAnyGroup;

	private boolean allowSenseModifiers;

	private long assignedAddersLastCall;

	private ArrayList<Adder> assignedAddersSaver;

	private long availableAddersLastCall;

	private ArrayList<Adder> availableAddersSaver;

	private long builtInSenseAddersLastCall;

	private long builtInSenseAddersLastID;

	private ArrayList<String> builtInSenseAddersSaver;

	private SenseGroup group;

	private String groupID;

	private ArrayList<String> senseAdders;

	private long senseModifiersLastCall;

	private ArrayList<String> senseModifiersSaver;

	public Sense(Element root) {
		super(root);
		if ((xmlID == null) || (xmlID.trim().length() == 0)) {
			xmlID = XMLUtility.getValue(root, "XMLID");
		}
		if ((xmlID != null) && (xmlID.trim().length() > 0) && (display != null)
				&& (display.trim().length() > 0) && includedInTemplate()) {
			while (GenericObject.findObjectByID(Sense.allSenses, xmlID) != null) {
				Sense.allSenses.remove(GenericObject.findObjectByID(
						Sense.allSenses, xmlID));
			}
			Sense.allSenses.add(this);
		}
	}

	public Sense(Element root, String id) {
		super(root, id);
		if ((xmlID == null) || (xmlID.trim().length() == 0)) {
			xmlID = id;
		}
		if ((xmlID != null) && (xmlID.trim().length() > 0) && (display != null)
				&& (display.trim().length() > 0) && includedInTemplate()) {
			while (GenericObject.findObjectByID(Sense.allSenses, xmlID) != null) {
				Sense.allSenses.remove(GenericObject.findObjectByID(
						Sense.allSenses, xmlID));
			}
			Sense.allSenses.add(this);
		}
	}

	public boolean allowSenseModifiers() {
		return allowSenseModifiers;
	}

	public String getAdderString() {
		ArrayList<String> vec = new ArrayList<String>();
		ArrayList<Adder> assigned = getAssignedAdders();
		Collections.sort(assigned, new Comparator() {
			public int compare(Object o1, Object o2) {
				GenericObject g1 = (GenericObject) o1;
				GenericObject g2 = (GenericObject) o2;
				String s1 = g1.getSortingValue().toUpperCase();
				String s2 = g2.getSortingValue().toUpperCase();
				if (s1.equals("ANALYZE")) {
					s1 = "DISCRIMINATORYANALYZE";
				} else if (s2.equals("ANALYZE")) {
					s2 = "DISCRIMINATORYANALYZE";
				}
				return s1.compareTo(s2);
			}

			public boolean equals(Object o) {
				return false;
			}
		});
		for (Adder adder : assigned) {
			adder.addAliasToVector(vec);
		}
		StringBuffer ret = new StringBuffer();
		for (String s : vec) {
			if (s.trim().length() > 0) {
				if (ret.length() > 0) {
					ret.append(", ");
				}
				ret.append(s.trim());
			}
		}
		return ret.toString();
	}

	public ArrayList<Adder> getAssignedAdders() {
		if ((assignedAddersLastCall > 0)
				&& (assignedAddersLastCall >= GenericObject.lastEdit)
				&& (HeroDesigner.lastEdit > 0)
				&& (assignedAddersLastCall > HeroDesigner.lastEdit)
				&& (assignedAddersSaver != null)) {
			return assignedAdders;
		}
		ArrayList<Adder> ret = super.getAssignedAdders();
		ArrayList<String> groupAdders = new ArrayList<String>();
		if (getGroup() != null) {
			groupAdders = getGroup().getSenseAdders();
		}
		if (senseAdders == null) {
			senseAdders = new ArrayList<String>();
		}
		ArrayList<String> sAdders = (ArrayList<String>) senseAdders.clone();
		sAdders.addAll(getSenseModifiers());
		for (int i = ret.size() - 1; i >= 0; i--) {
			Adder ad = ret.get(i);
			if (groupAdders.contains(ad.getXMLID())
					|| sAdders.contains(ad.getXMLID())) {
				ret.remove(i);
				continue;
			}
			if (ad.getXMLID().equals("INCREASEDARC240")) {
				if (groupAdders.contains("INCREASEDARC360")
						|| sAdders.contains("INCREASEDARC360")
						|| (GenericObject
								.findObjectByID(ret, "INCREASEDARC360") != null)) {
					ret.remove(i);
					continue;
				}
			} else if (ad.getXMLID().equals("DIMENSIONALSINGLE")) {
				if (groupAdders.contains("DIMENSIONALALL")
						|| sAdders.contains("DIMENSIONALALL")
						|| (GenericObject.findObjectByID(ret, "DIMENSIONALALL") != null)) {
					ret.remove(i);
					continue;
				} else if (groupAdders.contains("DIMENSIONALGROUP")
						|| sAdders.contains("DIMENSIONALGROUP")
						|| (GenericObject.findObjectByID(ret,
								"DIMENSIONALGROUP") != null)) {
					ret.remove(i);
					continue;
				}
			} else if (ad.getXMLID().equals("DIMENSIONALGROUP")) {
				if (groupAdders.contains("DIMENSIONALALL")
						|| sAdders.contains("DIMENSIONALALL")
						|| (GenericObject.findObjectByID(ret, "DIMENSIONALALL") != null)) {
					ret.remove(i);
					continue;
				} else if (groupAdders.contains("DIMENSIONALSINGLE")
						|| sAdders.contains("DIMENSIONALSINGLE")
						|| (GenericObject.findObjectByID(ret,
								"DIMENSIONALSINGLE") != null)) {
					ret.remove(i);
					continue;
				}
			} else if (ad.getXMLID().equals("DIMENSIONALALL")) {
				if (groupAdders.contains("DIMENSIONALGROUP")
						|| sAdders.contains("DIMENSIONALGROUP")
						|| (GenericObject.findObjectByID(ret,
								"DIMENSIONALGROUP") != null)) {
					ret.remove(i);
					continue;
				} else if (groupAdders.contains("DIMENSIONALSINGLE")
						|| sAdders.contains("DIMENSIONALSINGLE")
						|| (GenericObject.findObjectByID(ret,
								"DIMENSIONALSINGLE") != null)) {
					ret.remove(i);
					continue;
				}
			} else if (ad.getXMLID().equals("CONCEALED")) {
				if (active
						|| groupAdders.contains("TRANSMIT")
						|| sAdders.contains("TRANSMIT")
						|| (GenericObject.findObjectByID(ret, "TRANSMIT") != null)) {
					ad.setDisplay("Concealed (-[LVL] with " + getAlias()
							+ " PER Rolls)");
					ad.setAlias(ad.getDisplay());
				} else {
					ret.remove(i);
					continue;
				}
			} else if (ad.getXMLID().equals("ANALYZESENSE")) {
				if (!sAdders.contains("DISCRIMINATORY")
						&& !groupAdders.contains("DISCRIMINATORY")
						&& (GenericObject.findObjectByID(ret, "DISCRIMINATORY") == null)) {
					ret.remove(i);
				}
			}
		}
		assignedAddersSaver = ret;
		assignedAddersLastCall = System.currentTimeMillis();
		return ret;
	}

	public ArrayList<Adder> getAvailableAdders() {
		if ((availableAddersLastCall > 0)
				&& (availableAddersLastCall > GenericObject.lastEdit)
				&& (HeroDesigner.lastEdit > 0)
				&& (availableAddersLastCall > HeroDesigner.lastEdit)
				&& (availableAddersSaver != null)) {
			return availableAddersSaver;
		}
		ArrayList<Adder> ret = super.getAvailableAdders();
		ArrayList<String> added = new ArrayList<String>();
		ArrayList<Adder> automatic = new ArrayList<Adder>();
		ArrayList<SenseAdder> all = SenseAdder.getAllSenseAdders();
		ArrayList<String> free = getBuiltInSenseAdders();
		ArrayList<Adder> assigned = getAssignedAdders();
		ArrayList<Adder> avail = new ArrayList<Adder>();
		for (Adder ad : assigned) {
			if (ad.getXMLID().equals("GENERIC_OBJECT")) {
				continue;
			}
			if (GenericObject.findObjectByID(ret, ad.getXMLID()) != null) {
				continue;
			}
			avail.add(ad);
			added.add(ad.getXMLID());
		}
		for (GenericObject o : all) {
			if (!free.contains(o.getXMLID()) && !added.contains(o.getXMLID())) {
				if (o.getXMLID().equals("INCREASEDARC240")
						&& free.contains("INCREASEDARC360")) {
					continue;
				} else if (o.getXMLID().equals("DIMENSIONALSINGLE")
						&& (free.contains("DIMENSIONALGROUP") || assigned
								.contains("DIMENSIONALGROUP"))) {
					continue;
				} else if (o.getXMLID().equals("DIMENSIONALSINGLE")
						&& (free.contains("DIMENSIONALALL") || assigned
								.contains("DIMENSIONALALL"))) {
					continue;
				} else if (o.getXMLID().equals("DIMENSIONALGROUP")
						&& (free.contains("DIMENSIONALSINGLE") || assigned
								.contains("DIMENSIONALSINGLE"))) {
					continue;
				} else if (o.getXMLID().equals("DIMENSIONALGROUP")
						&& (free.contains("DIMENSIONALALL") || assigned
								.contains("DIMENSIONALALL"))) {
					continue;
				} else if (o.getXMLID().equals("DIMENSIONALALL")
						&& (free.contains("DIMENSIONALGROUP") || assigned
								.contains("DIMENSIONALGROUP"))) {
					continue;
				} else if (o.getXMLID().equals("DIMENSIONALALL")
						&& (free.contains("DIMENSIONALSINGLE") || assigned
								.contains("DIMENSIONALSINGLE"))) {
					continue;
				}

				Adder ad = new Adder(o);
				if (o instanceof SenseAdder) {
					ad.setExclusive(true);
					if (o.getXMLID().equals("ANALYZESENSE")) {
						if (!free.contains("DISCRIMINATORY")) {
							ad.getRequires().add("DISCRIMINATORY");
						} else {
							ad.getRequires().clear();
						}
					} else if (ad.getXMLID().equals("CONCEALED")) {
						if (active
								|| free.contains("TRANSMIT")
								|| (GenericObject.findObjectByID(assigned,
										"TRANSMIT") != null)) {
							ad.setDisplay("Concealed (-[LVL] with "
									+ getAlias() + " PER Rolls)");
							ad.setAlias(ad.getDisplay());
						} else {
							continue;
						}
					} else if (ad.getXMLID().equals("ENHANCEDPERCEPTION")) {
						ad.setDisplay("+[LVL] to PER Roll");
						ad.setAlias(ad.getDisplay());
					}
					SenseAdder add = (SenseAdder) o;
					if (add.getLevelValue() > 0) {
						ad.setLevelCost(add.senseCost);
					} else {
						ad.setBaseCost(add.senseCost);
					}
				}
				ret.add(ad);
				added.add(ad.getXMLID());
			} else if (!added.contains(o.getXMLID())) {
				Adder ad = new Adder(o);
				ad.setAlias(ad.getAlias() + " (built in)");
				ad.setBaseCost(0);
				ad.setLevelCost(0);
				ad.setSelected(false);
				ad.setSelectable(false);
				automatic.add(ad);
			}
		}
		automatic.addAll(avail);

		Collections.sort(ret);
		automatic.addAll(ret);
		availableAddersLastCall = System.currentTimeMillis();
		availableAddersSaver = automatic;
		return automatic;
	}

	/**
	 * Returns a Vector of SenseGroup objects defining the Sense Groups that are
	 * available for this Sense to be assigned to.
	 * 
	 * @return
	 * @see com.hero.objects.powers.SenseGroup
	 */
	public ArrayList<SenseGroup> getAvailableGroups() {
		ArrayList<SenseGroup> ret = new ArrayList<SenseGroup>();
		ret.add(getGroup());
		if (allowAnyGroup) {
			ArrayList<SenseGroup> all = SenseGroup.getAllGroups();
			for (SenseGroup g : all) {
				if (!ret.contains(g)) {
					ret.add(g);
				}
			}
		}
		return ret;
	}

	/**
	 * Returns a Vector of Strings, each String being the XMLID of a Sense
	 * Modifier which is a built-in part of this Sense's definition.
	 * 
	 * @see com.hero.objects.powers.SenseAdder
	 * @return
	 */
	public ArrayList<String> getBuiltInSenseAdders() {
		ArrayList<String> ret = getSenseAdders();
		if (getGroup() != null) {
			ret.addAll(getGroup().getSenseAdders());
		}
		if (HeroDesigner.getActiveHero() == null) {
			return ret;
		}
		ret.addAll(getSenseModifiers());
		return ret;
	}

	/**
	 * Returns a Vector of Strings, each String being the XMLID of a Sense
	 * Modifier which is a built-in part of this Sense's definition.
	 * 
	 * @see com.hero.objects.powers.SenseAdder
	 * @param excludeID
	 *            Any Sense Modifiers gained by an ability with the specified ID
	 *            are skipped.
	 * @return
	 */
	public ArrayList<String> getBuiltInSenseAdders(long excludeID) {
		if ((builtInSenseAddersLastCall > 0)
				&& (builtInSenseAddersLastCall > Power.lastSenseEdit)
				&& (HeroDesigner.lastEdit > 0)
				&& (builtInSenseAddersLastCall > HeroDesigner.lastEdit)
				&& (builtInSenseAddersSaver != null)
				&& (builtInSenseAddersLastID == excludeID)) {
			return builtInSenseAddersSaver;
		}

		ArrayList<String> ret = getSenseAdders();
		if (getGroup() != null) {
			ret.addAll(getGroup().getSenseAdders(excludeID));
		}
		if (HeroDesigner.getActiveHero() == null) {
			return ret;
		}
		OUTER: for (GenericObject o : HeroDesigner.getActiveHero().getPowers()) {
			if (o instanceof SenseAdder) {
				SenseAdder ad = (SenseAdder) o;
				if (ad.getID() == excludeID) {
					continue;
				}
				if (ad.getSenses().contains(getXMLID())
						&& !ret.contains(ad.getXMLID())) {
					if (getXMLID().equals("DETECT")) {
						boolean skip = true;
						if (ad.getSelectedOption().getDisplay().equals(
								SenseAdder.getDetectDisplay((Detect) this))) {
							skip = false;
						}
						ArrayList<Adder> adders = ad.getAssignedAdders();
						INNER: for (Adder adder : adders) {
							if (adder.getDisplay().equals(
									SenseAdder.getDetectDisplay((Detect) this))) {
								skip = false;
								break INNER;
							}
						}
						if (skip) {
							continue OUTER;
						}
					}
					ret.add(ad.getXMLID());
				}
			} else if (o instanceof CompoundPower) {
				CompoundPower cp = (CompoundPower) o;
				CP: for (GenericObject o2 : cp.getPowers()) {
					if (o2 instanceof SenseAdder) {
						SenseAdder ad = (SenseAdder) o2;
						if (ad.getID() == excludeID) {
							continue;
						}
						if (ad.getSenses().contains(getXMLID())
								&& !ret.contains(ad.getXMLID())) {
							if (getXMLID().equals("DETECT")) {
								boolean skip = true;
								if (ad
										.getSelectedOption()
										.getDisplay()
										.equals(
												SenseAdder
														.getDetectDisplay((Detect) this))) {
									skip = false;
								}
								ArrayList<Adder> adders = ad
										.getAssignedAdders();
								INNER: for (Adder adder : adders) {
									if (adder
											.getDisplay()
											.equals(
													SenseAdder
															.getDetectDisplay((Detect) this))) {
										skip = false;
										break INNER;
									}
								}
								if (skip) {
									continue CP;
								}
							}
							ret.add(ad.getXMLID());
						}
					}
				}
			}
		}
		EQUIPMENT: for (GenericObject o : HeroDesigner.getActiveHero()
				.getEquipment()) {
			if (o instanceof SenseAdder) {
				SenseAdder ad = (SenseAdder) o;
				if (ad.getID() == excludeID) {
					continue;
				}
				if (ad.getSenses().contains(getXMLID())
						&& !ret.contains(ad.getXMLID())) {
					if (getXMLID().equals("DETECT")) {
						boolean skip = true;
						if (ad.getSelectedOption().getDisplay().equals(
								SenseAdder.getDetectDisplay((Detect) this))) {
							skip = false;
						}
						ArrayList<Adder> adders = ad.getAssignedAdders();
						INNER: for (Adder adder : adders) {
							if (adder.getDisplay().equals(
									SenseAdder.getDetectDisplay((Detect) this))) {
								skip = false;
								break INNER;
							}
						}
						if (skip) {
							continue EQUIPMENT;
						}
					}
					ret.add(ad.getXMLID());
				}
			} else if (o instanceof CompoundPower) {
				CompoundPower cp = (CompoundPower) o;
				CP: for (GenericObject o2 : cp.getPowers()) {
					if (o2 instanceof SenseAdder) {
						SenseAdder ad = (SenseAdder) o2;
						if (ad.getID() == excludeID) {
							continue;
						}
						if (ad.getSenses().contains(getXMLID())
								&& !ret.contains(ad.getXMLID())) {
							if (getXMLID().equals("DETECT")) {
								boolean skip = true;
								if (ad
										.getSelectedOption()
										.getDisplay()
										.equals(
												SenseAdder
														.getDetectDisplay((Detect) this))) {
									skip = false;
								}
								ArrayList<Adder> adders = ad
										.getAssignedAdders();
								INNER: for (Adder adder : adders) {
									if (adder
											.getDisplay()
											.equals(
													SenseAdder
															.getDetectDisplay((Detect) this))) {
										skip = false;
										break INNER;
									}
								}
								if (skip) {
									continue CP;
								}
							}
							ret.add(ad.getXMLID());
						}
					}
				}
			}
		}
		builtInSenseAddersSaver = ret;
		builtInSenseAddersLastCall = System.currentTimeMillis();
		builtInSenseAddersLastID = excludeID;
		return ret;
	}

	public String getColumn2Output() {
		String ret = getAlias();
		if (getName().trim().length() > 0) {
			ret = "<i>" + getName() + ":</i>  " + ret;
		}
		if ((getGroup() != null) && (getAvailableGroups().size() > 1)) {
			ret += " (" + getGroup().getAlias() + ")";
		} else if ((getGroup() == null) && (getAvailableGroups().size() > 1)) {
			ret += " (Unusual Group)";
		}
		if ((getInput() != null) && (getInput().trim().length() > 0)) {
			ret += ":  " + getInput();
		}
		if (getSelectedOption() != null) {
			ret += ", ";
			ret += getSelectedOption().getAlias();

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

		return ret;
	}

	public GenericDialog getDialog(boolean isNew, boolean isPower) {
		Power.lastSenseEdit = System.currentTimeMillis();
		return new SenseDialog(this, isNew, isPower);
	}

	/**
	 * Returns the SenseGroup that this Sense is assigned to.
	 * 
	 * @see com.hero.objects.powers.SenseGroup
	 * @return
	 */
	public SenseGroup getGroup() {
		if (group == null) {
			ArrayList<SenseGroup> all = SenseGroup.getAllGroups();
			for (SenseGroup g : all) {
				if (g.getXMLID().equals(groupID)) {
					group = g;
					return group;
				}
			}
		}
		if (group == null) {
			SenseGroup nogroup = new SenseGroup(new Element("SENSEGROUP"),
					false);
			nogroup.setDisplay("no Sense Group");
			nogroup.setAlias("no Sense Group");
			nogroup.setXMLID("NOGROUP");
			return nogroup;
		}
		return group;
	}

	public Element getSaveXML() {
		Element ret = super.getSaveXML();
		ret.setAttribute("GROUP", getGroup().getXMLID());
		return ret;
	}

	public Adder getSelectedOption() {
		Adder ret = super.getSelectedOption();
		if (ret == null) {
			ArrayList<Adder> avail = getOptions();
			if (avail.size() > 0) {
				ret = avail.get(0);
			}
		}
		return ret;
	}

	@Override
	public double getTotalCost() {
		double ret = super.getTotalCost();
		if (HeroDesigner.getInstance().getActiveTemplate().is6E()) {
			// reduce cost for any built-in sense adders that are provided by
			// assigned sense group
			if (getGroup() != null && getGroup().getSenseAdders() != null
					&& getGroup().getSenseAdders().size() > 0) {
				if (senseAdders == null) {
					senseAdders = new ArrayList<String>();
				}
				for (String id:senseAdders) {
					if (getGroup().getSenseAdders().contains(id)) {
						//assigned sense group provides the sense adder...reduce the cost appropriately
						for (SenseAdder ad:SenseAdder.getAllSenseAdders()) {
							if (ad.getXMLID().equalsIgnoreCase(id) && ad.senseCost >= 0) {
								ret -= ad.senseCost;
								break;
							}
						}
					}
				}
			}
		}
		return ret;
	}

	/**
	 * Returns a Vector of Strings which define the XMLIDs of any Sense
	 * Modifiers assigned to this Sense.
	 * 
	 * @return
	 */
	protected ArrayList<String> getSenseAdders() {
		if (senseAdders == null) {
			senseAdders = new ArrayList<String>();
		}
		ArrayList<String> ret = (ArrayList<String>) senseAdders.clone();
		for (Adder ad : assignedAdders) {
			if (!ret.contains(ad.getXMLID())) {
				ret.add(ad.getXMLID());
			}
		}
		return ret;
	}

	private ArrayList<String> getSenseModifiers() {
		if ((senseModifiersLastCall > 0)
				&& (senseModifiersLastCall > GenericObject.lastEdit)
				&& (HeroDesigner.lastEdit > 0)
				&& (senseModifiersLastCall > HeroDesigner.lastEdit)
				&& (senseModifiersSaver != null)) {
			return senseModifiersSaver;
		}
		ArrayList<String> ret = new ArrayList<String>();
		if (HeroDesigner.getActiveHero() != null) {
			OUTER: for (GenericObject o : HeroDesigner.getActiveHero()
					.getPowers()) {
				if (o instanceof SenseAdder) {
					SenseAdder ad = (SenseAdder) o;
					if (ad.getSenses().contains(getXMLID())
							&& !ret.contains(ad.getXMLID())) {
						if (getXMLID().equals("DETECT")) {
							boolean skip = true;
							if (ad.getSelectedOption().getDisplay().equals(
									SenseAdder.getDetectDisplay((Detect) this))) {
								skip = false;
							}
							ArrayList<Adder> adders = ad.getAssignedAdders();
							INNER: for (Adder adder : adders) {
								if (adder
										.getDisplay()
										.equals(
												SenseAdder
														.getDetectDisplay((Detect) this))) {
									skip = false;
									break INNER;
								}
							}
							if (skip) {
								continue OUTER;
							}
						}
						ret.add(ad.getXMLID());
					}
				} else if (o instanceof CompoundPower) {
					CompoundPower cp = (CompoundPower) o;
					CP: for (GenericObject o2 : cp.getPowers()) {
						if (o2 instanceof SenseAdder) {
							SenseAdder ad = (SenseAdder) o2;
							if (ad.getSenses().contains(getXMLID())
									&& !ret.contains(ad.getXMLID())) {
								if (getXMLID().equals("DETECT")) {
									boolean skip = true;
									if (ad
											.getSelectedOption()
											.getDisplay()
											.equals(
													SenseAdder
															.getDetectDisplay((Detect) this))) {
										skip = false;
									}
									ArrayList<Adder> adders = ad
											.getAssignedAdders();
									INNER: for (Adder adder : adders) {
										if (adder
												.getDisplay()
												.equals(
														SenseAdder
																.getDetectDisplay((Detect) this))) {
											skip = false;
											break INNER;
										}
									}
									if (skip) {
										continue CP;
									}
								}
								ret.add(ad.getXMLID());
							}
						}
					}
				}
			}
			EQUIPMENT: for (GenericObject o : HeroDesigner.getActiveHero()
					.getEquipment()) {
				if (o instanceof SenseAdder) {
					SenseAdder ad = (SenseAdder) o;
					if (ad.getSenses().contains(getXMLID())
							&& !ret.contains(ad.getXMLID())) {
						if (getXMLID().equals("DETECT")) {
							boolean skip = true;
							if (ad.getSelectedOption().getDisplay().equals(
									SenseAdder.getDetectDisplay((Detect) this))) {
								skip = false;
							}
							ArrayList<Adder> adders = ad.getAssignedAdders();
							INNER: for (Adder adder : adders) {
								if (adder
										.getDisplay()
										.equals(
												SenseAdder
														.getDetectDisplay((Detect) this))) {
									skip = false;
									break INNER;
								}
							}
							if (skip) {
								continue EQUIPMENT;
							}
						}
						ret.add(ad.getXMLID());
					}
				} else if (o instanceof CompoundPower) {
					CompoundPower cp = (CompoundPower) o;
					CP: for (GenericObject o2 : cp.getPowers()) {
						if (o2 instanceof SenseAdder) {
							SenseAdder ad = (SenseAdder) o2;
							if (ad.getSenses().contains(getXMLID())
									&& !ret.contains(ad.getXMLID())) {
								if (getXMLID().equals("DETECT")) {
									boolean skip = true;
									if (ad
											.getSelectedOption()
											.getDisplay()
											.equals(
													SenseAdder
															.getDetectDisplay((Detect) this))) {
										skip = false;
									}
									ArrayList<Adder> adders = ad
											.getAssignedAdders();
									INNER: for (Adder adder : adders) {
										if (adder
												.getDisplay()
												.equals(
														SenseAdder
																.getDetectDisplay((Detect) this))) {
											skip = false;
											break INNER;
										}
									}
									if (skip) {
										continue CP;
									}
								}
								ret.add(ad.getXMLID());
							}
						}
					}
				}
			}
		}
		senseModifiersSaver = ret;
		senseModifiersLastCall = System.currentTimeMillis();
		return ret;
	}

	protected void init(Element element) {
		super.init(element);
		allowAnyGroup = true;
		active = false;
		activeSelectable = false;
		allowSenseModifiers = true;
		String id = XMLUtility.getValue(element, "XMLID");
		if (id != null) {
			xmlID = id;
		}
		Iterator iter = element.getChildren("PROVIDES").iterator();
		while (iter.hasNext()) {
			Element next = (Element) iter.next();
			String val = next.getTextTrim().toUpperCase();
			if (senseAdders == null) {
				senseAdders = new ArrayList<String>();
			}
			if (!senseAdders.contains(val)) {
				senseAdders.add(val);
			}
		}
		String check = XMLUtility.getValue(element, "INCLUDED");
		if ((check != null) && check.trim().toUpperCase().startsWith("Y")) {
			if ((xmlID != null) && (xmlID.trim().length() > 0)) {
				Sense.builtInSenses.add(this);
			}
		}
		check = XMLUtility.getValue(element, "ACTIVE");
		if ((check != null) && check.trim().toUpperCase().startsWith("Y")) {
			active = true;
		}
		check = XMLUtility.getValue(element, "ALLOWSENSEMODIFIERS");
		if ((check != null) && check.trim().toUpperCase().startsWith("N")) {
			allowSenseModifiers = false;
		}
		check = XMLUtility.getValue(element, "ACTIVESELECT");
		if ((check != null) && check.trim().toUpperCase().startsWith("Y")) {
			activeSelectable = true;
		}
		check = XMLUtility.getValue(element, "ALLOWANYGROUP");
		if ((check != null) && check.trim().toUpperCase().startsWith("N")) {
			allowAnyGroup = false;
		}
		check = XMLUtility.getValue(element, "SENSEGROUP");
		if ((check != null) && (check.trim().length() > 0)) {
			groupID = check;
		} else {
			groupID = "UNUSUALGROUP";
		}
	}

	/**
	 * Whether the Sense is Active or Passive.
	 * 
	 * @return
	 */
	public boolean isActive() {
		return active;
	}

	/**
	 * Whether the user can change the Active/Passive setting.
	 * 
	 * @return
	 */
	public boolean isActiveSelectable() {
		return activeSelectable;
	}

	public void restoreFromSave(Element root) {
		String g = XMLUtility.getValue(root, "GROUP");
		if ((g != null) && (g.trim().length() > 0)) {
			group = null;
			groupID = g;
		}

		super.restoreFromSave(root);
		ArrayList<Adder> holder = availableAdders;
		ArrayList<Adder> current = assignedAdders;
		assignedAdders = new ArrayList<Adder>();
		availableAdders = new ArrayList<Adder>();
		ArrayList<Adder> avail = getAvailableAdders();
		availableAdders = holder;
		ArrayList<Adder> assigned = new ArrayList<Adder>();
		for (Adder ad : current) {
			INNER: for (Adder check : avail) {
				if ((ad.getXMLID().startsWith(check.getXMLID()) || check
						.getXMLID().startsWith(ad.getXMLID()))
						&& (ad.getBaseCost() == check.getBaseCost())) {
					try {
						Adder orig = ad;
						ad = (Adder) check.clone();
						ad.setSelected(true);
						ad.setAlias(orig.getAlias());
						ad.setLevels(orig.getLevels());
						ad.setSelectedOption(orig.getSelectedOption());
					} catch (Exception exp) {

					}
					break INNER;
				}
			}
			assigned.add(ad);
		}
		assignedAdders = assigned;

		Power.lastSenseEdit = System.currentTimeMillis();
	}

	/**
	 * Whether the Sense is Active or Passive.
	 * 
	 * @param val
	 */
	public void setActive(boolean val) {
		if (activeSelectable) {
			active = val;
		}
	}

	/**
	 * Sets what Sense Group the Sense belongs to.
	 * 
	 * @see com.hero.objects.powers.SenseGroup
	 * @param gr
	 */
	public void setGroup(SenseGroup gr) {
		group = gr;
		Power.lastSenseEdit = System.currentTimeMillis();
	}

	public void setSelectedOption(Adder o) {
		GenericObject.lastEdit = System.currentTimeMillis();
		super.setSelectedOption(o);
		Power.lastSenseEdit = System.currentTimeMillis();
	}

}