package com.hero.objects.skills;

import java.util.ArrayList;
import java.util.Collections;
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.objects.List;
import com.hero.objects.characteristics.Characteristic;
import com.hero.ui.dialog.GenericDialog;
import com.hero.ui.dialog.LanguageDialog;
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 Language extends Skill {

	protected class ChartEntry {
		private String display;

		private String family;

		private boolean included;

		private ArrayList<String> level1;

		private ArrayList<String> level2;

		private ArrayList<String> level3;

		private ArrayList<String> level4;

		public ChartEntry(Element template) {
			init(template);
		}

		@Override
		public boolean equals(Object o) {
			if (o instanceof Language.ChartEntry) {
				ChartEntry comp = (ChartEntry) o;
				return getDisplay().equals(comp.getDisplay());
			} else {
				return false;
			}
		}

		protected String getDiscountedBy() {
			if (discountedBy == null) {
				return "";
			} else {
				return discountedBy;
			}
		}

		public String getDisplay() {
			return display;
		}

		public String getFamily() {
			if (family == null) {
				family = "";
			}
			return family;
		}

		public ArrayList<String> getLevel1() {
			return level1;
		}

		public ArrayList<String> getLevel2() {
			return level2;
		}

		public ArrayList<String> getLevel3() {
			return level3;
		}

		public ArrayList<String> getLevel4() {
			return level4;
		}

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

		public boolean included() {
			return included;
		}

		private void init(Element root) {
			display = "";
			level1 = new ArrayList<String>();
			level2 = new ArrayList<String>();
			level3 = new ArrayList<String>();
			level4 = new ArrayList<String>();
			family = "";
			included = true;
			nativeTongue = false;
			display = XMLUtility.getValue(root, "DISPLAY");
			family = XMLUtility.getValue(root, "TYPE");
			if ((display == null) || (display.trim().length() == 0)) {
				included = false;
				return;
			}

			java.util.List list = root.getChildren("ONEPOINTSIMILARITY");
			Iterator iterator = list.iterator();
			while (iterator.hasNext()) {
				Element sim = (Element) iterator.next();
				if ((sim.getTextTrim() != null)
						&& (sim.getTextTrim().length() > 0)) {
					level1.add(sim.getTextTrim().toUpperCase());
				}
			}
			list = root.getChildren("TWOPOINTSIMILARITY");
			iterator = list.iterator();
			while (iterator.hasNext()) {
				Element sim = (Element) iterator.next();
				if ((sim.getTextTrim() != null)
						&& (sim.getTextTrim().length() > 0)) {
					level2.add(sim.getTextTrim().toUpperCase());
				}
			}
			list = root.getChildren("THREEPOINTSIMILARITY");
			iterator = list.iterator();
			while (iterator.hasNext()) {
				Element sim = (Element) iterator.next();
				if ((sim.getTextTrim() != null)
						&& (sim.getTextTrim().length() > 0)) {
					level3.add(sim.getTextTrim().toUpperCase());
				}
			}
			list = root.getChildren("FOURPOINTSIMILARITY");
			iterator = list.iterator();
			while (iterator.hasNext()) {
				Element sim = (Element) iterator.next();
				if ((sim.getTextTrim() != null)
						&& (sim.getTextTrim().length() > 0)) {
					level4.add(sim.getTextTrim().toUpperCase());
				}
			}

		}

		public void setFamily(String val) {
			family = val;
		}
	}

	private static ArrayList<ChartEntry> chart;

	private static String globalExclude;

	private static int numberLanguagesPurchased;

	private static String xmlID = "LANGUAGES";

	private String discountedBy;

	private boolean nativeTongue;

	public Language(Element template) {
		super(template, Language.xmlID);
	}

	@Override
	public String getColumn2Output() {
		String ret = getAlias();
		if ((getName() != null) && (getName().trim().length() > 0)) {
			ret = "<i>" + getName() + ":</i>  " + ret;
		}
		if ((getInput() != null) && (getInput().trim().length() > 0)) {
			ret += ":  " + getInput();
		}
		if (getSelectedOption() != null) {
			ret += " (" + getSelectedOption().getAlias();
			if (HeroDesigner.getActiveHero().getRules().useLanguagesAsINTSkill()) {
				Characteristic intelligence = HeroDesigner.getActiveHero().getCharacteristic(Constants.INT);
				if (intelligence != null) {
					ret += "; "+intelligence.getRoll();
				}
			}
			String adderString = getAdderString();
			if (adderString.trim().length() > 0) {
				ret += "; ";
				ret += adderString;
			}
			ret += ")";
		} else {
			String adderString = getAdderString();
			if (adderString.trim().length() > 0) {
				ret += " (";
				ret += adderString;
				ret += ")";
			}
		}
		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.trim();
	}

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

	private String getDiscountedBy() {
		if (discountedBy == null) {
			return "";
		}
		return discountedBy;
	}

	/**
	 * 
	 * Change made on 6/7/2016 -- only "upstream" Languages can be used for discounting language. 
	 * Done to adhere to rule that only Languages which are already known can be used to discount a particular language.
	 * 
	 * Ordering of Languages will change when Linguist is purchased (alphabetical), but it is hoped that this will 
	 * only have a VERY rare impact on points and is an acceptable compromise.
	 */
	private Language getDiscountingLanguage(ArrayList<String> ignore,
			ChartEntry entry) {
		Language level1 = null;
		Language level2 = null;
		Language level3 = null;
		Language level4 = null;
		Language.numberLanguagesPurchased = 0;
		if ((getInput() == null) || (getInput().trim().length() == 0)) {
			return null;
		} // no language specified
		for (int i = 0; i<HeroDesigner.getActiveHero().getSkills().size(); i++) {
			GenericObject o = HeroDesigner.getActiveHero().getSkills().get(i);
			
			if (o instanceof Language) {
				Language.numberLanguagesPurchased++;
				if (o.equals(this)) {
					break; //only languages above the current count.
				}
				Language lang = (Language) o;
				if (ignore.contains(lang.getInput().trim().toUpperCase())) {
					continue;
				}
				if (lang.getBaseCost() < getBaseCost()) {
					//continue; not sure why this was here...not a rule to my knowledge...
				}
				if (lang.getInput().equals(getInput())) {
					break; //only languages above the current count.
				}
				if (entry.getLevel4().contains(
						lang.getInput().toUpperCase().trim())) {
					if (level4 != null) {
						if (lang.getBaseCost() > level4.getBaseCost()) {
							level4 = lang;
						}
					} else {
						level4 = lang;
					}
				} else if (entry.getLevel3().contains(
						lang.getInput().toUpperCase().trim())) {
					if (level3 != null) {
						if (lang.getBaseCost() > level3.getBaseCost()) {
							level3 = lang;
						}
					} else {
						level3 = lang;
					}
				} else if (entry.getLevel2().contains(
						lang.getInput().toUpperCase().trim())) {
					if (level2 != null) {
						if (lang.getBaseCost() > level2.getBaseCost()) {
							level2 = lang;
						}
					} else {
						level2 = lang;
					}
				} else if (entry.getLevel1().contains(
						lang.getInput().toUpperCase().trim())) {
					if (level1 != null) {
						if (lang.getBaseCost() > level1.getBaseCost()) {
							level1 = lang;
						}
					} else {
						level1 = lang;
					}
				} else {
					String family = getFamily(lang);
					if (entry.getFamily().contains(family)) {

						if (level1 != null) {
							if (lang.getBaseCost() > level1.getBaseCost()) {
								level1 = lang;
							}
						} else {
							level1 = lang;
						}
					}
				}
			}
			else if (o instanceof List) {
				List l = (List)o;
				for (int j=0; j<l.getObjects().size(); j++) {
					GenericObject o2 = l.getObjects().get(j);
					
					if (o2 instanceof Language) {
						Language.numberLanguagesPurchased++;
						if (o2.equals(this)) {
							break; //only languages above the current count.
						}
						Language lang = (Language) o2;
						if (ignore.contains(lang.getInput().trim().toUpperCase())) {
							continue;
						}
						if (lang.getBaseCost() < getBaseCost()) {
							//continue; not sure why this was here...shouldn't be part of the equation...
						}
						if (entry.getLevel4().contains(
								lang.getInput().toUpperCase().trim())) {
							if (level4 != null) {
								if (lang.getBaseCost() > level4.getBaseCost()) {
									level4 = lang;
								}
							} else {
								level4 = lang;
							}
						} else if (entry.getLevel3().contains(
								lang.getInput().toUpperCase().trim())) {
							if (level3 != null) {
								if (lang.getBaseCost() > level3.getBaseCost()) {
									level3 = lang;
								}
							} else {
								level3 = lang;
							}
						} else if (entry.getLevel2().contains(
								lang.getInput().toUpperCase().trim())) {
							if (level2 != null) {
								if (lang.getBaseCost() > level2.getBaseCost()) {
									level2 = lang;
								}
							} else {
								level2 = lang;
							}
						} else if (entry.getLevel1().contains(
								lang.getInput().toUpperCase().trim())) {
							if (level1 != null) {
								if (lang.getBaseCost() > level1.getBaseCost()) {
									level1 = lang;
								}
							} else {
								level1 = lang;
							}
						} else {
							String family = getFamily(lang);
							if (entry.getFamily().contains(family)) {

								if (level1 != null) {
									if (lang.getBaseCost() > level1.getBaseCost()) {
										level1 = lang;
									}
								} else {
									level1 = lang;
								}
							}
						}
					}
				}
			}
		}
		if (level4 != null) {
			return level4;
		} else if (level3 != null) {
			return level3;
		} else if (level2 != null) {
			return level2;
		} else if (level1 != null) {
			return level1;
		} else {
			return null;
		}
	}

	private String getFamily(Language lang) {
		String family = "";
		for (ChartEntry c : Language.chart) {
			if (c.getDisplay().toLowerCase().contains(
					lang.getInput().toLowerCase().trim())) {
				family = c.getFamily();
			}
		}
		if ((family == null) || (family.trim().length() == 0)) {
			family = lang.getInput();
		}
		return family;
	}

	@Override
	public double getRealCostPreList() {
		discountedBy = "";
		int nativeDiscount = 0;
		if (nativeTongue) {
			double max = 4;
			for (Adder ad : getOptions()) {
				if (ad.getXMLID().equals("IDIOMATIC")) {
					max = (int) ad.getBaseCost();
				}
			}
			if ((getSelectedOption() != null)
					&& (getSelectedOption().getBaseCost() < max)) {
				nativeDiscount += getSelectedOption().getBaseCost();
			} else {
				nativeDiscount += max;
			}
			if (GenericObject.findObjectByID(getAssignedAdders(), "LITERACY") != null) {
				Adder ad = (Adder) GenericObject.findObjectByID(
						getAssignedAdders(), "LITERACY");
				if ((ad.isSelected() && HeroDesigner.getActiveHero().getRules()
						.isNativeLiteracyFree())
						|| HeroDesigner.getActiveHero().getRules()
								.isLiteracyFree()) {
					nativeDiscount += ad.getTotalCost();
				}
			}
		}
		double ret = super.getRealCostPreList();
		if (nativeTongue) {
			ret -= nativeDiscount;
			if (ret < 0) {
				ret = 0;
			}
		} else if (HeroDesigner.getActiveHero().getRules().isLiteracyFree()) {
			if (GenericObject.findObjectByID(getAssignedAdders(), "LITERACY") != null) {
				Adder ad = (Adder) GenericObject.findObjectByID(
						getAssignedAdders(), "LITERACY");
				if ((ad.isSelected() && HeroDesigner.getActiveHero().getRules()
						.isNativeLiteracyFree())
						|| HeroDesigner.getActiveHero().getRules()
								.isLiteracyFree()) {
					ret -= ad.getTotalCost();
				}
				if (ret < 0) {
					ret = 0;
				}
			}
		}
		if ((ret <= 0)
				|| !HeroDesigner.getActiveHero().getRules()
						.isLanguageSimilaritiesUsed() || nativeTongue) {
			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);
			}
			return ret;
		}

		// figure in similarity chart discounts or penalties...
		boolean level4 = false;
		ArrayList<String> exclude = new ArrayList<String>();
		if ((Language.globalExclude != null)
				&& (Language.globalExclude.trim().length() > 0)) {
			exclude.add(Language.globalExclude);
		}
		ChartEntry entry = null;
		for (ChartEntry ce : Language.chart) {
			if (ce.getDisplay().equalsIgnoreCase(getInput().trim())) {
				entry = ce;
				break;
			}
		}
		if (entry == null) { // no matching chart entry...
			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);
			}
			return ret;
		}
		Language lang = getDiscountingLanguage(exclude, entry);
		/* shouldn't be needed anymore...now that we're only looking above in the list
		while ((lang != null) && lang.getDiscountedBy().equals(getInput())) {
		 
			exclude.add(lang.getInput().trim().toUpperCase());
			lang = getDiscountingLanguage(exclude, entry);
		}
		*/
		if ((lang != null)
				&& (lang.getBaseCost() == getBaseCost())
				&& ((Language.globalExclude == null) || (Language.globalExclude
						.trim().length() == 0))) {
			discountedBy = lang.getInput();
			/*
			Language.globalExclude = getInput();
			lang.getRealCostPreList();
			if ((lang.getDiscountedBy() == null)
					|| (lang.getDiscountedBy().trim().length() == 0)) {
				// this is the only language that the similar language can
				// match with....try to yield to it...
				Language holder = lang;
				discountedBy = "";
				Language.globalExclude = null;
				exclude.add(lang.getInput().trim().toUpperCase());
				lang = getDiscountingLanguage(exclude, entry);
				while ((lang != null)
						&& lang.getDiscountedBy().equals(getInput())) {
					exclude.add(lang.getInput().trim().toUpperCase());
					lang = getDiscountingLanguage(exclude, entry);
				}
				if (lang == null) {
					// we tried...and failed
					lang = holder;
				}
			}
			*/
			Language.globalExclude = "";
		}
		if (Language.numberLanguagesPurchased <= 1) {
			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);
			}
			return ret;
		}
		if (lang != null) {
			discountedBy = lang.getInput();
			String family = getFamily(lang);
			String thisFamily = getFamily(this);
			if (entry.getLevel4()
					.contains(lang.getInput().toUpperCase().trim())) {
				level4 = true;
				if (lang.getBaseCost() / 2 > 1) {
					ret -= Rounder.roundHalfDown(lang.getBaseCost() / 2);
				} else {
					ret -= 0;
				}
			} else if (entry.getLevel3().contains(
					lang.getInput().toUpperCase().trim())) {
				ret -= 1;
			} else if (entry.getLevel2().contains(
					lang.getInput().toUpperCase().trim())) {
				ret -= 1;
			} else if (entry.getLevel1().contains(
					lang.getInput().toUpperCase().trim())) {
				ret += 0;
			} else if (family.equalsIgnoreCase(thisFamily)) {
				ret += 0;
			}
		} else { // no matching entry on the familiarity chart at 1-point
			// level
			// or higher.
			if ((ret >= 1)
					&& (entry != null)
					&& HeroDesigner.getActiveHero().getRules()
							.penalizeNoLevel1()
					&& !nativeTongueExistsInGroup(entry)) {
				ret += 1;
			}
		}
		int min = 1;
		if (level4) {
			min = 0;
		}
		if (ret < min) {
			ret = min;
		}

		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);
		}

		return ret;
	}

	@Override
	public String getRoll() {
		return "";
	}

	@Override
	public Element getSaveXML() {
		Element root = super.getSaveXML();
		root.removeAttribute("CHARACTERISTIC");
		root.setAttribute("NATIVE_TONGUE", nativeTongue ? "Yes" : "No");
		return root;
	}

	@Override
	public double getTotalCost() {
		rollBased = false;
		return super.getTotalCost();
	}

	@Override
	protected void init(Element element) {
		display = "Language";
		baseCost = 0;
		levelCost = 0;
		levelValue = 0;
		minimumCost = 0;
		minimumLevel = 0;
		rollBased = false;
		Language.chart = new ArrayList<ChartEntry>();
		super.init(element);
		alias = "Language";
		java.util.List list = element.getChildren("LANGUAGE");
		Iterator iterator = list.iterator();
		while (iterator.hasNext()) {
			Element lang = (Element) iterator.next();
			ChartEntry entry = new ChartEntry(lang);
			if (entry.included()) {
				if (!Language.chart.contains(entry)) {
					Language.chart.add(entry);
					if (!examples.contains(entry.getDisplay())) {
						examples.add(entry.getDisplay());
					}
				}
			}
		}
		Collections.sort(examples);
	}

	/**
	 * Whether this Language is a Native Tongue or not.
	 * 
	 * @return
	 */
	public boolean isNativeTongue() {
		return nativeTongue;
	}

	private boolean nativeTongueExistsInGroup(ChartEntry entry) {
		Language.numberLanguagesPurchased = 0;
		if ((getInput() == null) || (getInput().trim().length() == 0)) {
			return false;
		} // no language specified
		for (GenericObject o : HeroDesigner.getActiveHero().getSkills()) {
			if (o instanceof Language) {
				Language.numberLanguagesPurchased++;
				Language lang = (Language) o;
				if (lang.getInput().equals(getInput())) {
					continue;
				}
				if (!lang.isNativeTongue()) {
					continue;
				}
				if (entry.getLevel4().contains(
						lang.getInput().toUpperCase().trim())) {
					return true;
				} else if (entry.getLevel3().contains(
						lang.getInput().toUpperCase().trim())) {
					return true;
				} else if (entry.getLevel2().contains(
						lang.getInput().toUpperCase().trim())) {
					return true;
				} else if (entry.getLevel1().contains(
						lang.getInput().toUpperCase().trim())) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Whether the Language is a Native Tongue.
	 * 
	 * @return
	 */
	public boolean nativeTongueSelected() {
		for (GenericObject o : HeroDesigner.getActiveHero().getSkills()) {
			if (o instanceof Language) {
				Language.numberLanguagesPurchased++;
				Language lang = (Language) o;
				if (lang.isNativeTongue()) {
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public void restoreFromSave(Element root) {
		super.restoreFromSave(root);
		String check = XMLUtility.getValue(root, "NATIVE_TONGUE");
		if ((check != null) && check.trim().toUpperCase().startsWith("Y")) {
			nativeTongue = true;
		} else {
			nativeTongue = false;
		}
	}

	/**
	 * Sets whether or not this Language is a Native Tongue.
	 * 
	 * @param val
	 */
	public void setNativeTongue(boolean val) {
		nativeTongue = val;
	}

}