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.CharAffectingObject;
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.Charges;
import com.hero.objects.modifiers.Modifier;
import com.hero.ui.dialog.CompoundPowerDialog;
import com.hero.ui.dialog.GenericDialog;
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 CompoundPower extends Power {

	private static String xmlID = "COMPOUNDPOWER";

	private String listSeparator;

	private ArrayList<GenericObject> powers;

	public CompoundPower(Element root) {
		super(root, CompoundPower.xmlID);
		powers = new ArrayList<GenericObject>();
		listSeparator = " <b>plus</b> ";
	}

	/**
	 * Returns true if any of the component Powers affect Characteristics.
	 */
	@Override
	public boolean affectsCharacteristics() {
		for (GenericObject o : powers) {
			if (!(o instanceof CharAffectingObject)) {
				continue;
			}
			CharAffectingObject co = (CharAffectingObject) o;
			if (co.getStrIncrease() != 0) {
				return true;
			}
			if (co.getDexIncrease() != 0) {
				return true;
			}
			if (co.getConIncrease() != 0) {
				return true;
			}
			if (co.getBodyIncrease() != 0) {
				return true;
			}
			if (co.getIntIncrease() != 0) {
				return true;
			}
			if (co.getEgoIncrease() != 0) {
				return true;
			}
			if (co.getPreIncrease() != 0) {
				return true;
			}
			if (co.getComIncrease() != 0) {
				return true;
			}
			if (co.getPdIncrease() != 0) {
				return true;
			}
			if (co.getEdIncrease() != 0) {
				return true;
			}
			if (co.getSpdIncrease() != 0) {
				return true;
			}
			if (co.getRecIncrease() != 0) {
				return true;
			}
			if (co.getEndIncrease() != 0) {
				return true;
			}
			if (co.getStunIncrease() != 0) {
				return true;
			}
			if (co.getDefIncrease() != 0) {
				return true;
			}
			if (co.getSizeIncrease() != 0) {
				return true;
			}
			if (co.getRunningIncrease() != 0) {
				return true;
			}
			if (co.getSwimmingIncrease() != 0) {
				return true;
			}
			if (co.getLeapingIncrease() != 0) {
				return true;
			}
		}
		return false;
	}

	@Override
	public CompoundPower clone() {
		CompoundPower cp;
		cp = (CompoundPower) super.clone();
		cp.setPowers((ArrayList<GenericObject>) getPowers().clone());
		return cp;
	}

	@Override
	public double getActiveCost() {
		double ret = 0;
		for (GenericObject o : powers) {
			o.setParent(getParentList());
			ret += o.getActiveCost();
			o.setParent(null);
		}
		return ret;
	}

	@Override
	public double getActiveCost(String excludedID) {
		double ret = 0;
		for (GenericObject o : powers) {
			o.setParent(getParentList());
			ret += o.getActiveCost(excludedID);
			o.setParent(null);
		}
		return ret;
	}

	/**
	 * Returns true if any of the component Powers affect the primary total.
	 */
	@Override
	public boolean getAffectPrimary() {
		for (GenericObject o : powers) {
			if (o instanceof CharAffectingObject) {
				CharAffectingObject o2 = (CharAffectingObject) o;
				if (o2.getAffectPrimary()) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Returns true if any of the component Powers affect the total.
	 */
	@Override
	public boolean getAffectTotal() {
		for (GenericObject pow : powers) {
			if (pow instanceof CharAffectingObject) {
				CharAffectingObject o = (CharAffectingObject) pow;
				if (o.getAffectTotal()) {
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public String getColumn2Output() {
		return getColumn2Output(true);
	}

	public String getColumn2Output(boolean includeNames) {
		String ret = "";
		ArrayList<Modifier> parentMods = new ArrayList<Modifier>();
		if (getParentList() != null) {
			parentMods = getParentList().getAssignedModifiers();
		}
		for (GenericObject o : powers) {
			ArrayList<Modifier> origMods = o.getAssignedModifiers();
			ArrayList<Modifier> newMods = (ArrayList<Modifier>) o
					.getAssignedModifiers().clone();
			for (int j = 0; (j < parentMods.size())
					&& HeroDesigner.getInstance().getPrefs()
							.showCommonLimitations(); j++) {
				Modifier mod = parentMods.get(j);
				if ((mod instanceof Charges)
						&& (getParentList() instanceof Multipower)) {
					continue;
				}
				if ((mod.getTotalValue() < 0)
						&& ((GenericObject.findObjectByID(origMods, mod
								.getXMLID()) == null)
								|| mod.getXMLID().equals("MODIFIER")
								|| mod.getXMLID().equals("CUSTOM_MODIFIER") || mod
								.getXMLID().equals("GENERIC_OBJECT"))) {
					newMods.add(mod);
				}
			}
			o.setAssignedModifiers(newMods);
			if (ret.trim().length() > 0) {
				ret += listSeparator;
			}
			if (includeNames) {
				ret += o.getTextOutput();
			} else {
				ret += o.getNamelessColumn2Output();
			}
			if (getDisplayActiveCost()) {
				ret += " (Real Cost: "
						+ Rounder.roundHalfDown(o.getRealCostPreList()) + ")";
			}
			o.setAssignedModifiers(origMods);
		}
		if (getDisplayActiveCost()) {
			ret = "(Total: " + Rounder.roundHalfUp(getActiveCost())
					+ " Active Cost, "
					+ Rounder.roundHalfUp(getRealCostPreList())
					+ " Real Cost) " + ret;
		}
		if ((getName() != null) && (getName().trim().length() > 0)) {
			ret = "<i>" + getName() + ":</i>  " + ret;
		}
		return ret;
	}

	@Override
	public String getColumn3Output() {
		if (getEndUsage() > 0) {
			return "" + getEndUsage();
		} else {
			Charges charges = null;
			for (GenericObject o : getPowers()) {
				if (GenericObject.findObjectByID(o.getAssignedModifiers(),
						"CHARGES") != null) {
					charges = (Charges) GenericObject.findObjectByID(o
							.getAssignedModifiers(), "CHARGES");
					break;
				}
			}
			if (charges == null) {
				return "0";
			} else {
				String ret = charges.getSelectedOption().getAlias();
				if (GenericObject.findObjectByID(charges.getAssignedAdders(),
						"BOOSTABLE") != null) {
					return "[" + ret + " bc]";
				} else if (GenericObject.findObjectByID(charges
						.getAssignedAdders(), "RECOVERABLE") != null) {
					return "[" + ret + " rc]";
				} else if (GenericObject.findObjectByID(charges
						.getAssignedAdders(), "CONTINUING") != null) {
					return "[" + ret + " cc]";
				} else if (GenericObject.findObjectByID(charges
						.getAssignedAdders(), "NEVERRECOVER") != null) {
					return "[" + ret + " nr]";
				} else {
					return "[" + ret + "]";
				}
			}
		}
	}

	@Override
	public GenericDialog getDialog(boolean isNew, boolean isPower) {
		return new CompoundPowerDialog(this, isNew);
	}

	@Override
	public int getEndUsage() {
		int ret = 0;
		for (GenericObject o : powers) {
			o.setParent(getParentList());
			ret += o.getEndUsage();
			o.setParent(null);
		}
		return ret;
	}

	@Override
	public double getIncrease(int type) {
		double ret = 0;
		for (GenericObject o : powers) {
			if ((o instanceof Characteristic)
					&& (((Characteristic) o).getType() == type)) {
				Characteristic ch = (Characteristic) o;
				ret += ch.getLevels();
			} else if (o instanceof CharAffectingObject) {
				CharAffectingObject o2 = (CharAffectingObject) o;
				ret += o2.getIncrease(type);
			}
		}
		return ret;
	}

	@Override
	public int getIncreaseLevels(int type) {
		int ret = 0;
		for (int i = 0; i < powers.size(); i++) {
			if ((powers.get(i) instanceof Characteristic)
					&& (((Characteristic) powers.get(i)).getType() == type)) {
				ret = 1;
			} else if (powers.get(i) instanceof CharAffectingObject) {
				CharAffectingObject o = (CharAffectingObject) powers.get(i);
				ret = Math.max(ret, o.getIncreaseLevels(type));
			}
		}
		return ret;
	}

	@Override
	public double getIncreaseValue(int type, boolean primary) {
		double ret = 0;
		for (int i = 0; i < powers.size(); i++) {
			if (powers.get(i) instanceof Characteristic) {
				if (((Characteristic) powers.get(i)).getType() == type) {
					Characteristic ch = (Characteristic) powers.get(i);
					if (primary && ch.getAffectPrimary() && ch.getAffectTotal()) {
						ret += ch.getLevels();
					} else if (!primary && !ch.getAffectPrimary()
							&& ch.getAffectTotal()) {
						ret += ch.getLevels();
					}
				} else if (!primary) {
					CharAffectingObject o = (CharAffectingObject) powers.get(i);
					if (o.getIncreaseLevels(type) != 0) {
						ret += o.getIncreaseValue(type, primary);
					}
				}
			} else if (powers.get(i) instanceof CharAffectingObject) {
				CharAffectingObject o = (CharAffectingObject) powers.get(i);
				if (o.getIncreaseLevels(type) != 0) {
					ret += o.getIncreaseValue(type, primary);
				}
			}
		}
		return ret;
	}

	/**
	 * The String to use to separate component Powers. "&lt;b&gt;plus&lt;/b&gt;"
	 * by default.
	 * 
	 * @return
	 */
	public String getListSeparator() {
		return listSeparator;
	}

	@Override
	public int getMdLevels() {
		int ret = 0;
		for (int i = 0; i < powers.size(); i++) {
			if (powers.get(i) instanceof Power) {
				Power pow = (Power) powers.get(i);
				if (!(pow instanceof DamageResistance)) {
					ret += pow.getMdLevels();
				}
			}
		}
		return ret;
	}

	@Override
	public String getNamelessColumn2Output() {
		String holder = getName();
		setName("");
		String ret = getColumn2Output(false);
		setName(holder);
		return ret;
	}

	/**
	 * Returns a Vector of Generic Objects that are contained by this Compound
	 * Power.
	 * 
	 * @return
	 */
	public ArrayList<GenericObject> getPowers() {
		return powers;
	}

	@Override
	public double getRealCost() {
		if (parent != null) {
			if (parent instanceof ElementalControl) {
				double real = 0;
				double total = getActiveCost();
				double ecBonus = parent.getActiveCost();
				for (int j = 0; j < powers.size(); j++) {
					GenericObject child = powers.get(j);
					ArrayList<Adder> realAdders = child.getAssignedAdders();
					ArrayList<Modifier> realModifiers = child
							.getAssignedModifiers();
					ArrayList<Adder> calcAdders = (ArrayList<Adder>) realAdders
							.clone();
					calcAdders.addAll(parent.getAssignedAdders());
					ArrayList<Modifier> calcModifiers = (ArrayList<Modifier>) realModifiers
							.clone();
					ArrayList<Modifier> ecMods = new ArrayList<Modifier>();
					for (Modifier mod : parent.getAssignedModifiers()) {
						if (mod.getTotalValue() < 0) {
							ecMods.add(mod);
						}
					}
					calcModifiers.addAll(ecMods);
					double active = child.getActiveCost();
					double discount = active / total * ecBonus;
					if (active >= discount * 2) {
						active = active - discount;
					} else {
						active = discount;
					}
					double limitationTotal = 0d;
					for (Modifier mod : calcModifiers) {
						if (mod.getTotalValue() < 0) {
							limitationTotal += mod.getTotalValue();
						}
					}
					double ret = active / (1d + Math.abs(limitationTotal));
					if (limitationTotal != 0) {
						ret = Rounder.roundHalfDown(ret);
					}
					if (ret < 1) {
						ret = 1;
					}
					real += ret;
				}
				return real;
			} else {
				return parent.getRealCostForChild(this);
			}
		} else {
			return getRealCostPreList();
		}
	}

	@Override
	public double getRealCostPreList() {
		double ret = 0;
		if (getQuantity() > 1) {
			double q = getQuantity();
			int doublings = 0;
			while (q > 1d) {
				doublings += 1;
				q = q / 2d;
			}
			ret += doublings * 5;
		}
		for (GenericObject o : powers) {
			List p = getParentList();
			o.setParent(p);
			ret += o.getRealCostPreList();
			o.setParent(null);
		}
		return ret;
	}

	@Override
	public Element getSaveXML() {
		Element root = super.getSaveXML();
		for (GenericObject o : powers) {
			Element sub = o.getSaveXML();
			root.addContent(sub);
		}
		return root;
	}

	@Override
	public double getTotalCost() {
		double ret = 0;
		for (GenericObject o : powers) {
			o.setParent(getParentList());
			ret += o.getTotalCost();
			o.setParent(null);
		}
		return ret;
	}

	@Override
	public ArrayList<String> getTypes() {
		ArrayList<String> ret = new ArrayList<String>();
		for (GenericObject o : powers) {
			ret.addAll(o.getTypes());
		}
		return ret;
	}

	/**
	 * If any of the component Powers provide resistant defenses, returns true.
	 */
	@Override
	public boolean resistantDefenses() {
		for (GenericObject o : powers) {
			if (o instanceof Power) {
				Power co = (Power) o;
				if (co.resistantDefenses()) {
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public void restoreFromSave(Element root) {
		super.restoreFromSave(root);
		java.util.List powerList = root.getChildren("POWER");
		Iterator iterator = powerList.iterator();
		while (iterator.hasNext()) {
			Element sk = (Element) iterator.next();
			if (sk != null) {
				String xmlID = XMLUtility.getValue(sk, "XMLID");
				LOOP: for (int j = 0; j < HeroDesigner.getActiveTemplate()
						.getPowers().size(); j++) {
					if (HeroDesigner.getActiveTemplate().getPowers().get(j) instanceof List) {
						List li = (List) HeroDesigner.getActiveTemplate()
								.getPowers().get(j);
						ArrayList<GenericObject> ps = li.getObjects();
						for (GenericObject power : ps) {
							if (power instanceof List) {
								// second loop necessary because of
								// Enhanced Senses (list in a list)
								List l2 = (List) power;
								for (GenericObject p : l2.getObjects()) {
									if (p.getXMLID().equals(xmlID)) {
										p = p.clone();
										p.restoreFromSave(sk);
										powers.add(p);
										break LOOP;
									}
								}
							} else if (power.getXMLID().equals(xmlID)) {
								power = power.clone();
								power.restoreFromSave(sk);
								powers.add(power);
								break LOOP;
							}
						}
					} else {
						GenericObject power = HeroDesigner.getActiveTemplate()
								.getPowers().get(j);
						if (power.getXMLID().equals(xmlID)) {
							power = power.clone();
							power.restoreFromSave(sk);
							powers.add(power);
							break LOOP;
						}
					}
				}
			}
		}
		// now restore the Power Skills
		powerList = root.getChildren("SKILL");
		iterator = powerList.iterator();
		while (iterator.hasNext()) {
			Element sk = (Element) iterator.next();
			if (sk != null) {
				String xmlID = XMLUtility.getValue(sk, "XMLID");
				LOOP: for (int j = 0; j < HeroDesigner.getActiveTemplate()
						.getPowers().size(); j++) {
					if (HeroDesigner.getActiveTemplate().getPowers().get(j) instanceof com.hero.objects.List) {
						com.hero.objects.List li = (com.hero.objects.List) HeroDesigner
								.getActiveTemplate().getPowers().get(j);
						ArrayList<GenericObject> ps = li.getObjects();
						for (GenericObject power : ps) {
							if (power.getXMLID().equals(xmlID)) {
								power = power.clone();
								power.restoreFromSave(sk);
								powers.add(power);
								break LOOP;
							}
						}
					} else {
						GenericObject power = HeroDesigner.getActiveTemplate()
								.getPowers().get(j);
						if (power.getXMLID().equals(xmlID)) {
							power = power.clone();
							power.restoreFromSave(sk);
							powers.add(power);
							break LOOP;
						}
					}
				}
			}
		}
		// now restore the Power Perks
		powerList = root.getChildren("PERK");
		iterator = powerList.iterator();
		while (iterator.hasNext()) {
			Element sk = (Element) iterator.next();
			if (sk != null) {
				String xmlID = XMLUtility.getValue(sk, "XMLID");
				LOOP: for (int j = 0; j < HeroDesigner.getActiveTemplate()
						.getPowers().size(); j++) {
					if (HeroDesigner.getActiveTemplate().getPowers().get(j) instanceof com.hero.objects.List) {
						com.hero.objects.List li = (com.hero.objects.List) HeroDesigner
								.getActiveTemplate().getPowers().get(j);
						ArrayList<GenericObject> ps = li.getObjects();
						for (GenericObject power : ps) {
							if (power.getXMLID().equals(xmlID)) {
								power = power.clone();
								power.restoreFromSave(sk);
								powers.add(power);
								break LOOP;
							}
						}
					} else {
						GenericObject power = HeroDesigner.getActiveTemplate()
								.getPowers().get(j);
						if (power.getXMLID().equals(xmlID)) {
							power = power.clone();
							power.restoreFromSave(sk);
							powers.add(power);
							break LOOP;
						}
					}
				}
			}
		}
		// now restore the Power Talents
		powerList = root.getChildren("TALENT");
		iterator = powerList.iterator();
		while (iterator.hasNext()) {
			Element sk = (Element) iterator.next();
			if (sk != null) {
				String xmlID = XMLUtility.getValue(sk, "XMLID");
				LOOP: for (int j = 0; j < HeroDesigner.getActiveTemplate()
						.getPowers().size(); j++) {
					if (HeroDesigner.getActiveTemplate().getPowers().get(j) instanceof com.hero.objects.List) {
						com.hero.objects.List li = (com.hero.objects.List) HeroDesigner
								.getActiveTemplate().getPowers().get(j);
						ArrayList<GenericObject> ps = li.getObjects();
						for (GenericObject power : ps) {
							if (power.getXMLID().equals(xmlID)) {
								power = power.clone();
								power.restoreFromSave(sk);
								powers.add(power);
								break LOOP;
							}
						}
					} else {
						GenericObject power = HeroDesigner.getActiveTemplate()
								.getPowers().get(j);
						if (power.getXMLID().equals(xmlID)) {
							power = power.clone();
							power.restoreFromSave(sk);
							powers.add(power);
							break LOOP;
						}
					}
				}
			}
		}
		// now restore the Power Chars
		for (int b = 0; b < HeroDesigner.getActiveTemplate()
				.getCharacteristics().size(); b++) {
			GenericObject ch = HeroDesigner.getActiveTemplate()
					.getCharacteristics().get(b);
			powerList = root.getChildren(ch.getXMLID());
			iterator = powerList.iterator();
			while (iterator.hasNext()) {
				Element sk = (Element) iterator.next();
				if ((sk != null) && (HeroDesigner.getActiveHero() != null)) {
					String xmlID = ch.getXMLID();
					GenericObject power = ch.clone();
					power.restoreFromSave(sk);
					power.setPower(true);
					powers.add(power);
				}
			}
		}
		Collections.sort(powers, new Comparator() {
			public int compare(Object o1, Object o2) {
				if ((o1 instanceof GenericObject)
						&& (o2 instanceof GenericObject)) {
					GenericObject go1 = (GenericObject) o1;
					GenericObject go2 = (GenericObject) o2;
					return go1.getPosition() - go2.getPosition();
				} else {
					return o1.toString().compareTo(o2.toString());
				}
			}

			@Override
			public boolean equals(Object o) {
				return false;
			}
		});
		for (GenericObject o : powers) {
			o.setMainPower(this);
		}
	}

	/**
	 * Sets the String to use to separate component Powers in the output.
	 * 
	 * @param val
	 */
	public void setListSeparator(String val) {
		listSeparator = val;
	}

	@Override
	public void setParent(List parent) {
		super.setParent(parent);
		for (int i = 0; i < powers.size(); i++) {
			GenericObject o = powers.get(i);
			o.setParent(parent);
		}
	}

	/**
	 * Sets the list of component Powers contained by this Compound Power.
	 * Vector should contain Generic Objects.
	 * 
	 * @param val
	 */
	public void setPowers(ArrayList<GenericObject> val) {
		powers = val;
		for (GenericObject o : powers) {
			o.setMainPower(this);
		}
	}

	@Override
	public boolean usesEND() {
		for (GenericObject o : powers) {
			if (o.usesEND()) {
				return true;
			}
		}
		return false;
	}
}