package com.hero.objects;

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

import org.jdom.Element;

import com.hero.HeroDesigner;
import com.hero.objects.modifiers.Modifier;
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 Adder extends GenericObject {

    private boolean selectable;

    private boolean selected;

    private boolean availableCheck;

    private boolean isGroup = false;

    private boolean isPrivate = false;

    private ArrayList<String> excludes;

    private ArrayList<String> requires;

    private boolean displayInString;

    private boolean showAlias;

    private boolean required;

    private boolean includeInBase;
    
    public Adder(Element root) {
        super(root);
    }

    public Adder() {
        super();
        excludes = new ArrayList<String>();
        requires = new ArrayList<String>();
        assignedModifiers = new ArrayList<Modifier>();
        assignedAdders = new ArrayList<Adder>();
        multiplier = 1;
        selectable = true;
        selected = false;
        availableCheck = false;
        displayInString = true;
        required = false;
        showAlias = true;
        isPrivate = false;
    }
    
    public Adder clone() {
    	Adder ad = (Adder) super.clone();
    	ad.setRequired(isRequired());
    	ad.setIncludeInBase(includeInBase);
    	return ad;
    }

    public void setRequired(boolean val) {
    	required = val;
    }
    public boolean isPrivate() {
        return isPrivate;
    }

    public void setPrivate(boolean val) {
        isPrivate = val;
    }
    
    public boolean includeInBase() {
    	return includeInBase || isRequired();
    }
    
    public void setIncludeInBase(boolean val) {
    	includeInBase = val;
    }
    
    public boolean isCustom() {
    	return getXMLID().equals("GENERIC_OBJECT") || getXMLID().equals("ADDER");
    }

    @Override
    public boolean includedInTemplate() {
        if (HeroDesigner.getInstance().getPrefs().getSources().size() > 0) {
            for (String s : HeroDesigner.getInstance().getPrefs().getSources()) {
                if (sources.contains(s)) {
                    return true;
                }
            }
            return false;
        } else {
            return true;
        }
    }

    /**
     * Creates an Adder representation of the specified GenericObject. Good way
     * to take an existing ability and turn it into an Adder.
     * 
     * @param clone
     */
    public Adder(GenericObject clone) {
        super();
        init(new Element("ADDER"));
        alias = clone.alias;
        availableAdders = clone.availableAdders;
        availableModifiers = clone.availableModifiers;
        baseCost = clone.baseCost;
        definition = clone.definition;
        display = clone.display;
        dynamicDisplay = clone.dynamicDisplay;
        examples = clone.examples;
        exclusive = clone.exclusive;
        fixedValue = clone.fixedValue;
        input = clone.input;
        inputLabel = clone.inputLabel;
        levelCost = clone.levelCost;
        levelMultiplier = clone.levelMultiplier;
        levelPower = clone.levelPower;
        levels = clone.levels;
        levelValue = clone.levelValue;
        maxCost = clone.maxCost;
        maxLevel = clone.maxLevel;
        maxSet = clone.maxSet;
        minimumCost = clone.minimumCost;
        minimumLevel = clone.minimumLevel;
        minSet = clone.minSet;
        name = clone.name;
        notes = clone.notes;
        options = clone.options;
        otherInputAllowed = clone.otherInputAllowed;
        selectedOption = clone.selectedOption;
        types = clone.types;
        userInput = clone.userInput;
        xmlID = clone.xmlID;
        selectable = true;
        showAlias = true;
        if (selectedOption != null) {
            setSelectedOption(selectedOption);
        }
    }

    @Override
    public void setXMLID(String val) {
        xmlID = val;
    }

    @Override
    protected void init(Element element) {
        super.init(element);
        excludes = new ArrayList<String>();
        requires = new ArrayList<String>();
        selectable = true;
        selected = false;
        availableCheck = false;
        displayInString = true;
        isGroup = false;
        showAlias = true;
        isPrivate = false;
        includeInBase = false;
        String check = XMLUtility.getValue(element, "BASECOST");
        if (check != null && check.trim().length() > 0) {
            try {
                baseCost = Double.parseDouble(check);
                selectable = true;
            } catch (Exception exp) {
                selectable = false;
            }
        } else {
            selectable = false;
        }
        check = XMLUtility.getValue(element, "XMLID");
        if (check != null && check.trim().length() > 0) {
            setXMLID(check.trim());
        }
        check = XMLUtility.getValue(element, "REQUIRED");
        if (check != null && check.trim().length() > 0) {
            required = check.trim().toUpperCase().startsWith("Y");
        }
        check = XMLUtility.getValue(element, "INCLUDEINBASE");
        
        if (check != null && check.trim().length() > 0) {
            includeInBase = check.trim().toUpperCase().startsWith("Y");
        }
        
        check = XMLUtility.getValue(element, "SHOWALIAS");
        if (check != null && check.trim().length() > 0) {
            showAlias = check.trim().toUpperCase().startsWith("Y");
        }
        check = XMLUtility.getValue(element, "DISPLAYINSTRING");
        if (check != null && check.trim().toUpperCase().startsWith("N")) {
            displayInString = false;
        }

        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());
            }
        }
        list = element.getChildren("OPTION");
        iterator = list.iterator();
        if (iterator.hasNext()) {
            selectable = true;
        }
        if (getOptions().size() > 0) {
            setSelectedOption(getOptions().get(0));
        }
        if (getAvailableAdders().size() > 0) {
            isGroup = true;
        }
    }

    public boolean isRequired() {
        if ((getOptions() == null || getOptions().size() == 0) && (getLevelCost()==0)) {
            return false;
        }
        return required;
    }

    @Override
    public Element getSaveXML() {
        Element ret = getGeneralSaveXML();
        ret.setName("ADDER");
        ret.setAttribute("SHOWALIAS", showAlias ? "Yes" : "No");
        ret.setAttribute("PRIVATE", isPrivate ? "Yes" : "No");
        ret.setAttribute("REQUIRED", isRequired() ? "Yes" : "No");
        ret.setAttribute("INCLUDEINBASE", includeInBase() ? "Yes" : "No");
        ret.setAttribute("DISPLAYINSTRING", displayInString() ? "Yes" : "No");
        ret.setAttribute("GROUP", isGroup ? "Yes" : "No");
        if (getLevelCost() != 0) {
            ret.setAttribute("LVLCOST", "" + getLevelCost());
            ret.setAttribute("LVLVAL", "" + getLevelValue());
        }
        ret.setAttribute("SELECTED", (selected ? "YES" : "NO"));
        return ret;
    }

    @Override
    public void restoreFromSave(Element root) {
        super.restoreFromSave(root);
        String check = XMLUtility.getValue(root, "SELECTED");
        if (check != null && check.trim().toUpperCase().startsWith("Y")) {
            setSelected(true);
        } else {
            setSelected(false);
        }
        setXMLID(XMLUtility.getValue(root, "XMLID"));
        check = XMLUtility.getValue(root, "DISPLAYINSTRING");
        if (check != null && check.trim().length() > 0) {
            displayInString = check.trim().toUpperCase().startsWith("Y");
        }
        check = XMLUtility.getValue(root, "REQUIRED");
        if (check != null && check.trim().length() > 0) {
            required = check.trim().toUpperCase().startsWith("Y");
        }
        check = XMLUtility.getValue(root, "INCLUDEINBASE");
        if (check != null && check.trim().length() > 0) {
            includeInBase = check.trim().toUpperCase().startsWith("Y");
        }
        check = XMLUtility.getValue(root, "PRIVATE");
        isPrivate = check != null && check.trim().toUpperCase().startsWith("Y");
        check = XMLUtility.getValue(root, "GROUP");
        if (check != null && check.trim().length() > 0) {
            isGroup = check.trim().toUpperCase().startsWith("Y");
        }
        check = XMLUtility.getValue(root, "SHOWALIAS");
        if (check != null && check.trim().length() > 0) {
            showAlias = check.trim().toUpperCase().startsWith("Y");
        }
        check = XMLUtility.getValue(root, "LVLCOST");
		if (check != null) {
			try {
				levelCost = Double.parseDouble(check);
			} catch (Exception exp) {
			}
		}
		check = XMLUtility.getValue(root, "LVLVAL");
		if (check != null) {
			try {
				levelValue = Double.parseDouble(check);
			} catch (Exception exp) {
			}
		}
    }

    public boolean isSelectable() {
        return selectable;
    }

    public boolean isSelected() {
        return selected;
    }

    public void setSelected(boolean val) {
        selected = val;
    }

    /**
     * Comparing two Adders is extremely difficult due to the malleable nature
     * of the object. ID alone doesn't do it in some cases, as we are concerned
     * with the display, but comparing just the display is not what we want in
     * other situations This method behaves differently depending on the state
     * of the "available check" variable.
     * 
     * @see setAvailableCheck(boolean val)
     */
    @Override
    public boolean equals(Object o) {
        if (o instanceof Adder) {
            Adder comp = (Adder) o;
            if (comp.isExclusive() && comp.getXMLID().equals(getXMLID())) {
                if (!getXMLID().equals("ADDER")
                        && !getXMLID().equals("GENERIC_OBJECT")) {
                    if (getXMLID().equals("DETECT")) {
                        return getDisplay().equals(comp.getDisplay());
                    } else {
                        return true;
                    }
                }
            } else if (comp.isExclusive()) {
                if (!getXMLID().equals("ADDER")
                        && !getXMLID().equals("GENERIC_OBJECT")) {
                    return false;
                }
            }
            if (isAvailableCheck() || comp.isAvailableCheck()) {
                if (getXMLID().equals("ADDER")
                        || getXMLID().equals("GENERIC_OBJECT")) {
                    return getDisplay().equals(comp.getDisplay());
                } else {
                    return getXMLID().equals(comp.getXMLID());
                }
            } else {
                if (getID() == comp.getID()) {
                    if (getColumn2Output().equals(comp.getColumn2Output())) { // checks
                        // alias
                        // and
                        // input
                        if (getSelectedOption() != null
                                && comp.getSelectedOption() != null) {
                            if (getSelectedOption().equals(
                                    comp.getSelectedOption())) {
                                return getLevels() == comp.getLevels();
                            } else {
                                return false;
                            }
                        }
                        return getLevels() == comp.getLevels();
                    } else {
                        return false;
                    }
                } else {
                    return false;
                }
            }
        } else {
            return false;
        }
    }

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

    /**
     * Returns a Vector of Strings. Each String represents an XMLID which is not
     * allowed in conjunction with this Adder.
     * 
     * @return
     */
    public ArrayList<String> getExcludes() {
        if (excludes == null) {
            excludes = new ArrayList<String>();
        }
        return excludes;
    }

    /**
     * Returns a Vector of Strings. Each String represents an XMLID which is
     * required in order for this Adder to be taken.
     * 
     * @return
     */
    public ArrayList<String> getRequires() {
        return requires;
    }

    @Override
    public String getColumn2Output() {
        String adderString = getAdderString();
        String ret = "";
        if (isSelected()) {
            if (showAlias) {
                ret += getAlias();
            }
            ret = ret.trim();
            if (getSelectedOption() != null) {
                if (ret.trim().length() > 0) {
                    ret += " ";
                }
                ret += getSelectedOption().getAlias();
            }
            if (getInput() != null && getInput().trim().length() > 0) {
                if (ret.trim().length() > 0) {
                    ret += " ";
                }
                ret += getInput();
            }
        }
        if (adderString.trim().length() > 0) {
            if (ret.trim().length() > 0) {
                ret += ", ";
            }
            ret += adderString;
        }
        if (getLevels() > 0 && display.indexOf("[LVL]") < 0) {
            ret += ":  +" + getLevels();
        }
        return ret;
    }

    @Override
    public double getTotalCost() {
        double total = 0;
        if (isSelected()) { // use this adder's cost
            total += getBaseCost();
            if (levelValue != 0) {
                total += getLevels() / levelValue * levelCost;
            }
        } else { // sum the cost of any children
            for (Adder ad : getAssignedAdders()) {
                total += ad.getRealCost();
            }
        }
        if (total < minimumCost && total < 0 && minSet) {
            total = getMinimumCost();
        } else if (total > maxCost && total > 0 && maxSet) {
            total = getMaxCost();
        }
        return total;
    }

    public double getTotalCost(boolean ignoreSelection) {
        double total = 0;
        if (isSelected() || ignoreSelection) { // use this adder's
            // cost
            total += getBaseCost();
            if (levelValue != 0) {
                total += getLevels() / levelValue * levelCost;
            }
        } else { // sum the cost of any children
            for (Adder ad : getAssignedAdders()) {
                total += ad.getRealCost();
            }
        }
        if (total < minimumCost && total < 0 && minSet) {
            total = getMinimumCost();
        } else if (total > maxCost && total > 0 && maxSet) {
            total = getMaxCost();
        }
        return total;
    }

    public double getDoubleTotal(boolean selectedMatters) {
        double total = 0;
        if (isSelected() || !selectedMatters) { // use this adder's
            // cost
            total += getBaseCost();
            if (levelValue != 0) {
                total += getLevels() / levelValue * levelCost;
            }
        } else { // sum the cost of any children
            for (Adder ad : getAssignedAdders()) {
                total += ad.getRealCost();
            }
        }
        if (total < minimumCost && total < 0 && minSet) {
            total = getMinimumCost();
        } else if (total > maxCost && total > 0 && maxSet) {
            total = getMaxCost();
        }
        return total;
    }

    public double getDoubleTotal() {
        return getDoubleTotal(false);
    }

    public boolean isGroup() {
        return isGroup;
    }

    @Override
    public int compareTo(Object o) {
        if (o instanceof Adder) {
            Adder comp = (Adder) o;
            String s1 = getAlias();
            String s2 = comp.getAlias();
            if (isGroup() != comp.isGroup()) {
                if (isGroup()) {
                    return -1;
                } else {
                    return 1;
                }
            } else {
                return s1.compareTo(s2);
            }
        } else if (o instanceof GenericObject) {
            GenericObject go = (GenericObject) o;
            return getAdderString().compareTo(go.getAdderString());
        } else {
            return toString().compareTo(o.toString());
        }
    }

    /**
     * This method is used in the app to construct the Adder string. Special
     * sorting is performed on the resulting Vector in accordance with the
     * Writers' Guidelines.
     * 
     * @param vec
     *            The Vector to add the Adder's display values to.
     */
    public void addAliasToVector(ArrayList<String> vec) {
        if (isSelected() && displayInString) {
            String alias = "";
            if (showAlias) {
                alias = getAlias();
            }
            alias = alias.trim();
            if (getSelectedOption() != null) {
                if (alias.trim().length() > 0) {
                    alias += " ";
                }
                alias += getSelectedOption().getAlias();
            }
            if (getInput() != null && getInput().trim().length() > 0) {
                if (alias.trim().length() > 0) {
                    alias += " ";
                }
                alias += getInput();
            }
            if (getLevels() > 0 && display.indexOf("[LVL]") < 0) {
                String lvlString = "+" + getLevels() * getLevelMultiplier();
                if (levelPower != 1) {
                    lvlString = "x"
                            + NumberFormat.getIntegerInstance().format(
                                    getLevelMultiplier() * Math.pow(getLevelPower(), getLevels()));
                }
                alias += ":  " + lvlString;
            }
            vec.add(alias);
        }
        for (Adder ad : getAssignedAdders()) {
            ad.addAliasToVector(vec);
        }
    }

    private boolean isAvailableCheck() {
        return availableCheck;
    }

    /**
     * Sets the state of the "available check". When true, only a cursory
     * comparison is done to determine if two Adders are the same. When false, a
     * more thorough comparison is performed.
     * 
     * @param availableCheck
     */
    public void setAvailableCheck(boolean availableCheck) {
        this.availableCheck = availableCheck;
    }

    /**
     * Returns true if this Adder (or any of its selected children, if a group)
     * contains the specified type. This is primarily used for abilities such as
     * Combat Piloting and Riding, where Adders of a particular type may be
     * discounted.
     */
    @Override
    public boolean containsType(String type) {
        if (isSelected()) {
            return types.contains(type);
        } else {
            for (Adder ad : assignedAdders) {
                if (ad.containsType(type)) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * Whether the Adder can be selected or not.
     * 
     * @param val
     */
    public void setSelectable(boolean val) {
        selectable = val;
    }

    @Override
    public String toString() {
        if (isSelected()) {
            return getAlias();
        } else {
            return super.toString();
        }
    }

    /**
     * If false, the Adder is not inserted into the display String for the
     * object it is assigned to.
     * 
     * @return
     */
    public boolean displayInString() {
        if (alias.trim().length() == 0) {
            return false;
        }
        return displayInString;
    }

    /**
     * If false, the Adder is not inserted into the display String for the
     * object it is assigned to.
     */
    public void setDisplayInString(boolean val) {
        displayInString = val;
    }

}