/* Build and Price - Options, Packages and Accessories Configurator Module */
/* Object represents available configurations for a particular trim/grade
	and exposes methods to create an interactive DHTML configurator application,
	store and transfer configurations, and to communicate selected configurations
	to back-end processes in compressed and detailed formats */

/* Object represents configurator */
function bapOPA (name, id, mapid, msrp) {
	document.OPA = this;
	
	// Object array contains an ordered list of OPA items
	this.name = name;
	this.id = id;
	this.Summary = false;
	this.List = new Array();	
	// An object to which OPA items are attached as members
	this.Items = new Object();
	// Stores user selections in the order they were made
	this.Selections = new Array();
	this.RequiredSelections = new Array();
	this.Inclusions = new Array();

	// Property contains the current mapping's ID (paragraph number)
	this.Map = mapid;
	// Property contains the current total MSRP
	this.baseMSRP = msrp;
	this.MSRP = new Object();
	this.MSRP.value = msrp;
	this.MSRP.destination = "0";
	this.MSRP.htmlElement = null;

	this.Currency = new Object();
	this.Currency.Symbol = "$";
	this.Currency.Decimal = ".";
	this.Currency.Separator = ",";
	this.Currency.UseDecimal = true;

	// String ID of the element which stores the selector string to be sent to the next page of the tool
	this.Selector = "";
	
	// Object array contains an ordered array of previous configurations
	this.History = new Array();

	this.LoadSelections = function(selections,requiredselections) {
		for (var i = 0; i < requiredselections.length; i+=2) {
			this.RequiredSelections.push(requiredselections.substr(i,2));
		}
		for (var i = 0; i < selections.length; i+=2) {
			if (this.RequiredSelections.join(",").indexOf(selections.substr(i,2)) == -1) {
				this.Modify(selections.charAt(i),selections.charAt(i+1));				
			}
		}
	};

	this.FormatNumeric = function(curVal) {
		var re = new RegExp("[^\d" + this.Currency.Decimal + "]", "ig");
		return eval("+(curVal.toString().replace(/[^\\d\\" + this.Currency.Decimal + "]/ig,''))");
	};

	this.FormatCurrency = function(curVal) {
		var curString = "";
		if (typeof(curVal) != 'string') curVal = ""+curVal;
		if (this.Currency.UseDecimal || curVal.indexOf(this.Currency.Decimal) != -1) {
			if (curVal.indexOf(this.Currency.Decimal) != -1) {
				curString = curVal.substr(curVal.indexOf(this.Currency.Decimal)+1,2);
				while (curString.length < 2) {
					curString += "0";
				}
				curString = this.Currency.Decimal + curString;
				curVal = curVal.substr(0,curVal.indexOf(this.Currency.Decimal));
			} else {
				curString = this.Currency.Decimal + "00";
			}
			if (curVal.length == 0) {
				curString = "0" + curString;
			}
		}

		for (var i = curVal.length; curVal.length > 0; i - 3) {
			curString = curVal.substr((curVal.length - 3 > 0 ? curVal.length - 3 : 0)) + curString;
			if (curVal.length > 3) {
				curVal = curVal.substr(0,curVal.length-3);
				curString = this.Currency.Separator + curString;
			} else {
				curVal = "";
			}
		}

		return this.Currency.Symbol + curString;
	};
	
	// Method adds a configurable item to the List array and Items object
	this.AddItem = function (type,value,desc,details,msrp,code,productcode,include,exclude,require) {
		this.List.push(new bapItem(type,value,desc,details,msrp,code,productcode,include,exclude,require));
		if (value == 1) {
			this.Inclusions.push(type.toString()+code);
		}
		if (typeof(this.Items[type]) == 'undefined')
			this.Items[type] = new Object();
		// Non-Linear Access
		this.Items[type][code] = this.List[this.List.length - 1];
		this.Items[type][code].parent = this;
	};
	
	// Method returns a serialized version of the current configuration
	this.Serialize = function () {
		return this.Map + "-" + this.Selections.join("") + (this.RequiredSelections.length > 0 ? "+" + this.RequiredSelections.join("") : "");
	};

	this.AppSubmitValue = function () {
		var appval = new Array();
		for (var i = 0; i < this.compact.Select.length; i++) {
			var ois = this.compact.Select[i];
			var ot = this.Items[ois.charAt(0)][ois.charAt(1)];
			var otappval = "name=" + ot.Desc + "|price="+ot.MSRP+"|type="+ot.Type+"|sku="+ot.ProductCode;
			appval.push(otappval);
		}
		return appval.join("?");
	};
		
	// Method returns a URL which can be used to return to the configurator at a later date
	this.URL = function() {};
	
	// Method draws DHTML interface
	this.Draw = function () {
		var container = document.getElementById(this.name + "ListContainer");
		var oiulopt = document.createElement("ul");
		oiulopt.className = "bapList";
		oiulopt.id = "bapOptions";
		var optCount = 0;
		var oiulpkg = document.createElement("ul");
		oiulpkg.className = "bapList";
		oiulpkg.id = "bapPackages";
		var pkgCount = 0;
		var oiulacc = document.createElement("ul");
		oiulacc.className = "bapList";
		oiulacc.id = "bapAccessories";
		var accCount = 0;

		// oiul.className = "bapList";
		for (var i = 0; i < this.List.length; i++) {
			var oi = this.List[i];
			var oili = document.createElement("li");
			oili.id = "bapDiv" + oi.toString();
			
			var oicb = document.createElement("input");
			oicb.type = "hidden";
			oicb.id = "bapCheckBox" + oi.toString();
			oicb.value = oi.State;
			// oicb.onchange = new Function(this.id+".Modify('" + oi.Type + "','" + oi.Code + "')");
			
			// var oil = document.createElement("label");
			// oil.htmlFor = "bapCheckBox" + oi.toString();
			
			var oil = document.createElement("a");
			oil.id = "bapLink" + oi.toString();
			oil.className = "bapOPALink bapOPAState" + (""+oi.State).replace("-","_");
			oil.href = "javascript:" + this.id+".Modify('" + oi.Type + "','" + oi.Code + "')";
			oil.innerHTML = oi.Desc;

			var oilab = document.createElement("dt");
			oilab.appendChild(oil);

			// image += "<img border=\"0\" alt=\"[?]\" name=\"img_details" + refID + "\" src=\"" + context + "/img/global/gl_icon_question.gif\"
			// class=\"tInfoImg\" style=\"display: inline !important\"/>";
			var oidet = document.createElement("img");
			oidet.border = "0";
			oidet.alt = "[?]";
			oidet.name = "img_details" + oi.toString();
			oidet.src = (document.location.href.indexOf("/author/") != -1 ? "/author" : "") + "/img/global/gl_icon_question.gif";
			oidet.className = "tInfoImg";
			// oidet.style["display"] = "inline !important";
			oidet.style.display = "inline";

			// <a style=\"display: inline\" class=\"tIndOptSup\" onclick=\"showDhtmlPop(\'details" + refID + "\',null,this,\'side\');";
			// image += "return false;\" href=\"#\">";

			var oidetlink = document.createElement("a");
			oidetlink.style["display"] = "inline";
			oidetlink.style["paddingLeft"] = "5px";
			oidetlink.className = "tIndOptSup";
			oidetlink.href = "#";
			oidetlink.setAttribute("detail","details" + oi.toString());
			oidetlink.setAttribute("detailpos","right");
			oidetlink.onclick = function (event) { showDhtmlPop(this.getAttribute("detail"),event,this,this.getAttribute("detailpos")); return false; };

			oidetlink.appendChild(oidet);
			oilab.appendChild(oidetlink);

			var oimsrp = document.createElement("dd");
			oimsrp.className = "bapOPAMsrp bapOPAState" + (""+oi.State).replace("-","_");
			oimsrp.innerHTML = this.FormatCurrency(oi.MSRP);

			var oidl = document.createElement("dl");
			oidl.id = "bapItem" + oi.toString();

			oidl.appendChild(oilab);
			oidl.appendChild(oimsrp);
			
			oili.appendChild(oicb);
			oili.appendChild(oidl);
			switch (oi.Type) {
				case 1:
					oiulopt.appendChild(oili);
					optCount++;
					break;
				case 2:
					oiulpkg.appendChild(oili);
					pkgCount++;
					break;
				case 3:
					oiulacc.appendChild(oili);
					accCount++;
					break;
			}
			// oiul.appendChild(oili);
			oi.htmlElement = "bapDiv" + oi.toString();
		}

		if (pkgCount > 0 || optCount > 0) {
			var pkgHeader = document.createElement("h4");
			pkgHeader.innerHTML = this.PackagesHeader ? this.PackagesHeader : "Packages";
			var pkgMSRP = document.createElement("h5");
			pkgMSRP.innerHTML = this.ListMSRPLabel ? this.ListMSRPLabel : "MSRP";
			var optHeader;
			var optMSRP;
			if (typeof(this.OptionsHeader) != 'undefined' && this.OptionsHeader.length > 0) {
				optHeader = document.createElement("h4");
				optHeader.innerHTML = this.OptionsHeader ? this.OptionsHeader : "Options";
				optMSRP = document.createElement("h5");
				optMSRP.innerHTML = this.ListMSRPLabel ? this.ListMSRPLabel : "MSRP";
			}	

			if (pkgCount > 0 || (typeof(optHeader) == 'undefined' && optCount > 0)) {
				container.appendChild(pkgHeader);
				container.appendChild(pkgMSRP);
				container.appendChild(oiulpkg);
			}
		
			if (optCount > 0) {
				if (typeof(optHeader) != 'undefined') {
					container.appendChild(optHeader);
					container.appendChild(optMSRP);
				}
				container.appendChild(oiulopt);
			}
		}

		if (accCount > 0) {
			var accHeader = document.createElement("h4");
			accHeader.innerHTML = this.AccessoriesHeader ? this.AccessoriesHeader : "Accessories";
			var accMSRP = document.createElement("h5");
			accMSRP.innerHTML = this.ListMSRPLabel ? this.ListMSRPLabel : "MSRP";

			container.appendChild(accHeader);
			container.appendChild(accMSRP);
			container.appendChild(oiulacc);
		}
		
		var msrp = document.createElement("input");
		msrp.id = this.name + "MSRP";
		msrp.type = "hidden";
		msrp.value = this.MSRP.value;
		msrp.destination = this.MSRP.destination;
		msrp.parent = this;

		msrp.onchange = function () {
			var base = document.getElementById(this.id + "Base");
			var configured = document.getElementById(this.id + "Configured");
			var total = document.getElementById(this.id + "Total");
			// var destination = document.getElementById(this.id + "Destination");

			base.innerHTML = this.parent.FormatCurrency(base.getAttribute("value"));
			configured.innerHTML = this.parent.FormatCurrency(this.value);
			total.innerHTML = this.parent.FormatCurrency(this.parent.FormatNumeric(this.value) + this.parent.FormatNumeric(this.destination));
		};
		
		container.appendChild(document.createElement("p").appendChild(msrp));

		var selector = document.createElement("input");
		selector.type = "hidden";
		selector.id = "bapSelector";
		container.appendChild(selector);
		
		this.Selector = "bapSelector";
		this.MSRP.htmlElement = "bapMSRP";
	};

	this.UpdateMSRP = function () {
		id = this.name + "MSRP";
		value = this.MSRP.value;
		destination = this.MSRP.destination;
		if (typeof(destination) == 'undefined') destination = "0";
		var base = document.getElementById(id + "Base");
		var configured = document.getElementById(id + "Configured");
		var total = document.getElementById(id + "Total");

		base.innerHTML = this.FormatCurrency(base.getAttribute("value"));
		configured.innerHTML = this.FormatCurrency(value);
		total.innerHTML = this.FormatCurrency(this.FormatNumeric(value) + this.FormatNumeric(destination));
	};
	
	// Method to run after all items have been added, in which each items' include/exclude signature is created
	this.Finalize = function () {
		for (var i = 0; i < this.List.length; i++) {
			var oi = this.List[i];
			
			// object holds compact signature of all included items
			oi.compact = new this.CompactItem();
			oi.compact.Select.push(oi.Type.toString()+oi.Code);
			
			// Combine the configuration signature of all included items			
			this.Compact(oi,oi.compact);
		}
	};

	this.CompactItem = function () {
		this.Select = new Array();
		this.Include = new Array();
		this.Exclude = new Array();
		this.Require = new Array();
		// Parallel array to Require, holds the item which depends on a requirement
		this.Depends = new Array();
		return this;
	};
	
	// Draw Summary area which lists included options, their prices and the subtotal, total after destination & handling
	this.DrawSummary = function () {
		var summary;
		var summaryItems;
		var summaryTable;

		if (document.getElementById(this.name + "SummaryTable")) {
			summaryTable = document.getElementById(this.name + "SummaryTable");
		}

		if (!document.getElementById(this.name + "Summary")) {
			summary = document.createElement("div");
			summary.id = this.name + "Summary";
			document.body.appendChild(summary);
		} else {
			summary = document.getElementById(this.name + "Summary");
			if (document.getElementById(this.name + "SummaryItems")) {
				summaryItems = document.getElementById(this.name + "SummaryItems");
			} else {
				summaryItems = document.createElement("div");
				summaryItems.id = this.name + "SummaryItems";
				summary.appendChild(summaryItems);				
			}
		}
		
		var dlp = document.createElement("dl");		
		var dla = document.createElement("dl");

		if (document.getElementById(this.name + "PkgSummary")) {
			summaryItems.replaceChild(dlp,document.getElementById(this.name + "PkgSummary"));
		} else {
			dlp = summaryItems.appendChild(dlp);
		}
		dlp.id = this.name + "PkgSummary";

		if (document.getElementById(this.name + "AccSummary")) {
			summaryItems.replaceChild(dla,document.getElementById(this.name + "AccSummary"));
		} else {
			summaryItems.appendChild(dla);
		}
		dla.id = this.name + "AccSummary";

		// use compact selection to add to summary
		if (typeof(this.compact) != 'null' && !(this.compact == null)) {
			if (this.compact.Select) {
				for (var i = 0; i < this.compact.Select.length; i++) {
					var ois = this.compact.Select[i].toString();
					var oi = this.Items[ois.charAt(0)][ois.charAt(1)];

					// create DT & DD elements for item
					var is = document.createElement("dt");
					is.innerHTML = oi.Desc;
					var ip = document.createElement("dd");
					if (oi.State >= 2) ip.innerHTML = this.FormatCurrency(oi.MSRP);
					
					// create TD elements for item if table
					var tr;
					var tdd;
					var tdp;

					if (summaryTable) {
						tr = document.createElement("tr");
						tdd = document.createElement("th");
						tdp = document.createElement("td");			
						tdd.innerHTML = oi.Desc;
						if (oi.State >= 2) tdp.innerHTML = this.FormatCurrency(oi.MSRP);
						tr.appendChild(tdd);
						tr.appendChild(tdp);
						summaryTable.appendChild(tr);
						tdd.className="BAPCell";
						tdp.className="BAPCell";
					if (i%2) {tr.className="altBAPRow";}
					}

					if (oi.compact.Include.length > 0) {
						var iul = document.createElement("ul");
						for (var li = 0; li < oi.compact.Include.length; li++) {
							var ili = document.createElement("li");
							var ilis = oi.compact.Include[li].toString();
							var ilit = this.Items[ilis.charAt(0)][ilis.charAt(1)];
							ili.innerHTML = ilit.Desc;
							iul.appendChild(ili);
						}
						is.appendChild(iul);
						if (summaryTable) {
							tdd.appendChild(iul.cloneNode(true));
						}
					}
					switch (ois.charAt(0)) {
						case "1":
						case "2":
							dlp.appendChild(ip);
							dlp.appendChild(is);
							break;
						case "3":
							dla.appendChild(ip);
							dla.appendChild(is);
							break;
					}
				}
			}
		}
		dlp.style.display = (dlp.hasChildNodes() ? "block" : "none");
		dla.style.display = (dla.hasChildNodes() ? "block" : "none");
		dla.className = "bapSummaryList";
		dlp.className = "bapSummaryList";

		// resize frame if needed
		if (summary.parentNode.clientHeight < summary.clientHeight) {
			summary.parentNode.style.height = (summary.clientHeight + 50) + "px";
		}

		// Update selector value
		if (document.getElementById(this.Selector)) document.getElementById(this.Selector).value = this.Serialize();
	};
	
	// Method called to toggle the selection state of an item
	this.Modify = function (type,code) {
		var ot = this.Items[type.toString()][code];
		if (typeof(ot) != 'undefined') {
			var remove = new Array();
			switch (ot.State) {
				case -1:
					remove = this.ExclusionCheck(type+code);
					break;
				case 3:
					remove = this.DependentCheck(type+code);
					break;			
			}
			switch (ot.State) {
				case -1:
				case 3:
				case 2:
				case 0:
					ot = this.ChangeItemState(ot);
					this.Finalize();
					this.Combine();

					var consent;
		
					if (this.compact != null) {
						var required = this.RequirementCheck(this.compact,new Array(type+code));
						// var remove = this.DependentCheck(this.compact,required.concat(new Array(type+code)));
						if (required.length > 0 || remove.length > 0) {
							consent = this.Notable(type+code,required,remove);
						}
					}
		
					this.Finalize();
					this.Combine();
					break;
			}
		}
	};
	
	this.ChangeItemState = function (ot,ns) {
		var type = ot.Type;
		var code = ot.Code;
		ot.PreviousState = ot.State;
		switch (ot.State) {
			case 3:
			case 2:
				switch (ot.State) {
					case 3:
						if (typeof(ns) == 'undefined') ns = 0;
						ot.ChangeState(ns);
						this.RequiredSelections = this.RemoveSelection(this.RequiredSelections,type.toString()+code);
						this.RequiredSelections = this.CompactRequired();
						break;			
					case 2:
						if (typeof(ns) == 'undefined') ns = 0;
						ot.ChangeState(ns);
						this.Selections = this.RemoveSelection(this.Selections,type.toString()+code);
						this.RequiredSelections = this.CompactRequired();
						break;
				}

				break;
			case 0:
			case -1:
				if (typeof(ns) == 'undefined') ns = 2;
				ot.ChangeState(ns);
				switch (ns) {
					case 2:
						// Reorder selections if necessary
						if(this.Selections.join(",").indexOf(type+code) != -1)
							this.Selections = this.RemoveSelection(this.Selections,type.toString()+code);
						this.Selections.push(type.toString() + code);

						break;
					case 3:
						if(this.RequiredSelections.join(",").indexOf(type+code) == -1)
							this.RequiredSelections.push(type.toString() + code);
						break;
				}
				break;
			default:
				break;
		}
		return ot;
	};
	
	this.RemoveSelection = function (sela,code) {
		sela = sela.join(",").replace(code,"").replace(",,",",").replace(/,$/i,"").replace(/^,/i,"").split(",");
		if (sela.length == 1 && sela[0] == "") {
			sela.shift();
		}
		return sela;
	};

	this.CompactRequired = function () {
		var req	= new Array();
		for (var i = 0; i < this.Selections.length; i++) {
			var ois = this.Selections[i];
			if (this.compact.Exclude.join(",").indexOf(ois) == -1) {
				var oisreq = this.Items[ois.charAt(0)][ois.charAt(1)].compact.Require.join("");
				for (var j = 0; j < oisreq.length; j+=2) {
					if (req.join(",").indexOf(oisreq.substr(j,2)) == -1 && this.Selections.join(",").indexOf(oisreq.substr(j,2)) == -1) {
						var ois2 = oisreq.substr(j,2);
						if (typeof(this.Items[ois2.charAt(0)][ois2.charAt(1)]) != 'undefined') {
							req.push(ois2);
						}
					}
				}
			}
		}	

		return req;
	};

	this.Notable = function (code,required,excluded) {
		var requiredTitles = new Array();
		var excludedTitles = new Array();
		var newMSRP = this.MSRP.value + this.FormatNumeric(this.MSRP.destination);
		var curMSRP = newMSRP;

		var ot = this.Items[code.charAt(0)][code.charAt(1)];
		addition = ot.PreviousState < 2 ? true : false;
		title = ot.Desc;

		for (var i = 0; i < required.length; i++) {
			var ois = required[i];
			var oit = this.Items[ois.charAt(0)][ois.charAt(1)];
			this.ChangeItemState(oit,3);
			requiredTitles.push(oit.Desc);
			newMSRP += oit.MSRP;
		}
		for (var i = 0; i < excluded.length; i++) {
			var ois = excluded[i];
			var oit = this.Items[ois.charAt(0)][ois.charAt(1)];
			this.ChangeItemState(oit,0);
			excludedTitles.push(oit.Desc);
			curMSRP += oit.MSRP;
		}

		var itemList = new Array();
		itemList.push(code);
		itemList = itemList.concat(required).concat(excluded);

		if (typeof(document.Notable) == 'undefined') {
			// if (this.Summary) {
				consent = true;
			/* } else {
				var consent = window.confirm("The following items are required and will be added to your purchase: \n\n" + requiredTitles.join("\n") + "\n\n"
					+ excludedTitles.join("\n"));
			} */
		} else {
			document.Notable.showNotable(addition,title,code,requiredTitles,excludedTitles,itemList,this.FormatCurrency(curMSRP),this.FormatCurrency(newMSRP));
			consent = true;
		}

		if (!consent) {
			this.Revert(itemList.join(","));
		}
		return consent;
	};
	
	this.Revert = function (itemListString) {
		var itemList = itemListString.split(",");
		for (var i = 0; i < itemList.length; i++) {
			var oit = this.Items[itemList[i].charAt(0)][itemList[i].charAt(1)];
			this.ChangeItemState(oit,oit.PreviousState);
		}
		this.RequiredSelections = this.CompactRequired();
		this.Finalize();
		this.Combine();
	};
	
	// Combine each selected item into the complete current compact configuration
	// Then update list states
	this.Combine = function () { 
		var ois = "";
		var op = this.CompactItem();
		
		if (this.Selections.length > 0) {
			ois = this.Selections[this.Selections.length - 1];
			op1 = this.Items[ois.charAt(0)][ois.charAt(1)].compact;
			op = this.Add(op,op1);
		}
		
		if (this.Selections.length > 1) {
			for (var os = this.Selections.length - 2; os >= 0; os--) {
				var ois1 = this.Selections[os];
				var op2 = this.Items[ois1.charAt(0)][ois1.charAt(1)].compact;		
				op = this.Add(op,op2);
			}
		}

		if (this.RequiredSelections.length > 0) {
			for (var os = this.RequiredSelections.length - 1; os >= 0; os--) {
				var ois = this.RequiredSelections[os];
				var op3 = this.Items[ois.charAt(0)][ois.charAt(1)].compact;
				op = this.Add(op,op3);
			}
		}

		if (this.Inclusions.length > 0) {
			for (var os = this.Inclusions.length - 1; os >= 0; os--) {
				var ois = this.Inclusions[os];
				var op4 = this.Items[ois.charAt(0)][ois.charAt(1)].compact;
				op = this.Add(op,op4);
			}
		}

		this.compact = op;
		
		this.MSRP.value = this.baseMSRP;
		
		// Update the state of all items in list
		this.UpdateState(this.List,0);
		
		if (this.compact != null) {
			// we add back in those selections which survive the compacting process
			this.UpdateState(this.Select,2);
			this.UpdateState(this.RequiredSelections,3);
			this.UpdateState(this.Inclusions,1);
			this.UpdateState(op.Include,1);
			this.UpdateState(op.Exclude,-1);
		}
		
		if (typeof(this.MSRP.htmlElement) == 'string') {
			msrp = document.getElementById(this.MSRP.htmlElement);
			msrp.value = this.MSRP.value;
			msrp.onchange();
			
			this.DrawSummary();
		}
	};
	
	// Update the state of items from array
	this.UpdateState = function (opas,state) {
		for (var i = 0; i < opas.length; i++) {
			var ois = opas[i].toString();
			var oi = this.Items[ois.charAt(0)][ois.charAt(1)];
			if (oi != null) {
				oi.ChangeState(state);
				if (state >= 2) {
					this.MSRP.value += oi.MSRP;
				}
			}
		}
	};
	
	// Take an object which represents the compacted representation of each selection
	// And return an object which represents the addition of another compact selection
	this.Add = function (op1,op2) {
		// If op1 excludes op2 items, return primary operand
		var excluded = this.AddExclusionCheck(op1.Exclude,op2.Include.concat(op2.Select).concat(op2.Require));
		if (excluded) return op1;
		
		// If op2 excludes op1 items, return primary operand
		var excluded = this.AddExclusionCheck(op2.Exclude,op1.Include.concat(op1.Select).concat(op1.Require));
		if (excluded) return op1;
		
		// If op2 is already included, return primary operand (previous inclusion trumps selection)
		var included = this.AddExclusionCheck(op1.Include,op2.Select);
		if (included) return op1;
		
		// If the operands aren't mutually exclusive, combine them
		op1.Include = this.CompactArrayAdd(op1.Include,op2.Include);
		op1.Exclude = this.CompactArrayAdd(op1.Exclude,op2.Exclude);
		op1.Select  = this.CompactArrayAdd(op1.Select, op2.Select);
		
		// Requirements and Dependents are not compacted.  Each requirement is separately managed, because of dependents
		op1.Require = op1.Require.concat(op2.Require); // this.CompactArrayAdd(op1.Require,op2.Require);
		op1.Depends = op1.Depends.concat(op2.Depends);

		return op1;
	};
	
	// Combine two compacted array representations
	this.CompactArrayAdd = function (opa1,opa2) {
		var opa1string = opa1.join(",");
		for (var i = 0; i < opa2.length; i++) {
			if (opa1string.indexOf(opa2[i]) == -1) {
				opa1.push(opa2[i]);
			}
		}
		return opa1;
	};

	// Check whether two items are mutually exclusive
	this.AddExclusionCheck = function (op1,op2) {
		var op2include = op2.join(",");	
		var excluded = false;
		
		// check whether op2 or op2 included items are excluded by op1
		for (var ei = 0; ei < op1.length; ei++) {
			if (op2include.indexOf(op1[ei]) != -1) {
				excluded = true;
				break;
			}
		}
		return excluded;
	};

	// Check whether exclusive items are selected
	this.ExclusionCheck = function (code) {
		var exclude = new Array();
		for (var i = 0; i < this.Selections.length; i++) {
			var ois = this.Selections[i];
			var oit = this.Items[ois.charAt(0)][ois.charAt(1)];
			if (oit.compact.Exclude.join(",").indexOf(code) != -1) {
				if (exclude.join(",").indexOf(ois) == -1) exclude.push(ois);
			}
		}
		return exclude;
	};
	
	// Check whether required items are no longer selected
	this.DependentCheck = function (code) {
		var remove = new Array();
		for (var i = 0; i < this.Selections.length; i++) {
			var ois = this.Selections[i];
			var oit = this.Items[ois.charAt(0)][ois.charAt(1)];
			if (oit.compact.Require.join(",").indexOf(code) != -1) {
				if (remove.join(",").indexOf(ois) == -1) remove.push(ois);
			}
		}
		return remove;
	};

	// Check whether all required items are selected or included
	// Returns an array of required items which must be added
	this.RequirementCheck = function (compact,notrequired) {
		var require = compact.Require;
		var incsel = compact.Include.concat(compact.Select).concat(notrequired).join(",");
		var addreq = new Array();
		
		for (var i = require.length - 1; i >= 0; i--) {
			var requires = require[i];
			if (requires.charAt(1) == '-') requires = requires.charAt(0);
			if (incsel.indexOf(requires) == -1) {
				try {
					var ois = requires;
					if (ois.length == 1) {
						for (var i in this.Items[ois.charAt(0)]) {
							ois = ois + this.Items[ois.charAt(0)][i].Code;
							break;
						}
					}
					var oit = this.Items[ois.charAt(0)][ois.charAt(1)].compact;
					incsel = incsel.split(",").concat(oit.Include).concat(oit.Select).join(",");
					addreq.push(ois);
				} catch (e) {}
			}
		}

		var reqsel = this.RequiredSelections;
		var required = compact.Require.join(",");
		
		for (var i = 0; i < reqsel.length; i++) {
			if (required.indexOf(reqsel[i]) == -1) {
				var ois = reqsel[i];
				var ot = this.Items[ois.charAt(0)][ois.charAt(1)];
				this.ChangeItemState(ot,0);
			}
		}
		
		return addreq;
	};
	
	// Combines/compacts the included/excluded/required children of the item
	this.Compact = function (item, compact,ireType) {
		if (typeof(item) != 'undefined') {
			if (ireType != 'Require') {		
				for (var i = 0; i < item.Include.length; i+=2) {
					var ois = item.Include.substr(i,2);
					var oit = this.Items[ois.charAt(0)][ois.charAt(1)];
					if (typeof(oit) != 'undefined') {
						compact.Include.push(ois);
						this.Compact(oit,compact,ireType);
					}
				}
			}
		
			for (var r = 0; r < item.Require.length; r+=2) {
				var ois = item.Require.substr(r,2);
				var oit;
				if (typeof(this.Items[ois.charAt(0)])!= "undefined") {
					oit = this.Items[ois.charAt(0)][ois.charAt(1)];
				}
				var ois2 = item.Type.toString() + item.Code;			

				if (typeof(oit) != 'undefined') {
					if (compact.Require.join("|").indexOf(ois) == -1) {
						compact.Require.push(ois);
						compact.Depends.push(ois2);
						this.Compact(oit,compact,'Require');
					}	
				}
			}
		
			for (var e = 0; e < item.Exclude.length; e+=2) {
				var oes = item.Exclude.substr(e,2);
				compact.Exclude.push(oes);
			}
		}
	};	
}

// Object represents a single OPA item
// Can contain other packages, options, accessories
// Can exclude other packages, options, accessories
function bapItem (type,value,desc,details,msrp,code,productcode,include,exclude,require) {
	// Type: bap type; 1 = option, 2 = package, 3 = accessory, 4 = ?? (extensible)
	this.Type = type;
	// Description: Short description
	this.Desc = desc;
	// Details: Long "disclaimer" type text
	this.Details = details;
	// MSRP: Suggested Price
	this.MSRP = msrp;
	// Code: Single-character reference for this OPA item
	this.Code = code;
	// Product Code: SKU
	this.ProductCode = productcode;
	// State: Inclusion state; -1 = excluded, 0 = not referenced, 1 = included, 2 = selected
	this.State = (typeof(value) != 'undefined') ? value : 0;
	this.PreviousState = this.State;
	// Include: Reference String representing OPA sub-items
	this.Include = include;
	// Exclude: Reference String representing OPA sub-items which are not allowed
	this.Exclude = exclude;
	// Require: Required items (charged for)
	this.Require = require;
	// Holds reference to parent bapOPA object
	this.parent = null;
	// Holds reference to HTML element
	this.htmlElement = null;
	// Method to update HTML element when this element's state changes
	this.ChangeState = function (state) {
		this.State = state;
		if (typeof(this.htmlElement) == 'string') {
			var he = document.getElementById(this.htmlElement);
			he.getElementsByTagName("input")[0].value = state;
			he.getElementsByTagName("a")[0].className = "bapOPALink bapOPAState" + (""+state).replace("-","_");
			he.getElementsByTagName("dd")[0].className = "bapOPAState" + (""+state).replace("-","_");
			switch (this.State) {
				case -1:
					break;
				case 0:
					break;
				case 1:
					break;
				case 2:
					break;
				case 3:
					break;
				default:
					break;
			}
		}	
	};
	this.toString = function () {
		return this.Type + this.Code;
	};
}

function bapNotable (name, notableElement) {
	document.Notable = this;

	this.Name = name;
	this.HeaderText = "Selection Requires Changes";
	this.SelectionText = "You have selected ";
	this.SelectionRemovalText = "You have unselected ";
	this.ChangeRequiredText = "This will require the following changes:";
	this.AdditionText = "will be added";
	this.RemovalText = "will be removed";
	this.ExistingPriceText = "This will change the existing price of";
	this.AdjustedPriceText = "The adjusted price will be";
	this.ConfirmQuestionText = "Would you like to make this change?";
	this.ConfirmYesText = "Yes";
	this.ConfirmNoText = "No";
	this.NotableElement = notableElement;

	this.showNotable = function (addition,selection,selectionCode,additions,removals,itemList,curPrice,newPrice) {
		var notableElement = document.getElementById(this.NotableElement);
		var header = notableElement.getElementsByTagName("h1")[0];
		var pars = notableElement.getElementsByTagName("p");
		var selected = pars[0];
		var change = pars[1];
		var existPrice = pars[2];
		var adjustPrice = pars[3];
		var confirm = pars[4];
		var changeList = notableElement.getElementsByTagName("ul")[0];
		var confirmYesLink = notableElement.getElementsByTagName("ol")[0].getElementsByTagName("li")[0].firstChild;
		var confirmNoLink = notableElement.getElementsByTagName("ol")[0].getElementsByTagName("li")[1].firstChild;

		header.innerHTML = this.HeaderText;
		selected.innerHTML = (addition ? this.SelectionText : this.SelectionRemovalText) + " <strong>" + selection + "</strong>";
		change.innerHTML = this.ChangeRequiredText;
		
		while (changeList.hasChildNodes()) {
			changeList.removeChild(changeList.firstChild);
		}

		for (var i = 0; i < additions.length; i++) {
			var addli = document.createElement("li");
			addli.innerHTML = "<strong>" + additions[i] + "</strong> " + this.AdditionText;
			changeList.appendChild(addli);
		}

		for (var i = 0; i < removals.length; i++) {
			var rem = document.createElement("li");
			rem.innerHTML = "<strong>" + removals[i] + "</strong> " + this.RemovalText;
			changeList.appendChild(rem);
		}

		existPrice.innerHTML = this.ExistingPriceText + " " + curPrice;
		adjustPrice.innerHTML = this.AdjustedPriceText + " " + newPrice;

		confirmNoLink.href = "javascript:void(new function() { document.OPA.Revert('" + itemList.join(",") + "'); " + this.Name + ".hideNotable()});";
		confirmNoLink.innerHTML = this.ConfirmNoText;
		confirmYesLink.innerHTML = this.ConfirmYesText;
		confirm.innerHTML = this.ConfirmQuestionText;
		notableElement.style["display"] = "block";
		document.getElementById(this.Name + "Shader").style["display"] = "block";
		document.getElementById(this.Name + "Shader").style["height"] = document.getElementById(this.Name + "Shader").parentNode.clientHeight + "px";
	};

	this.hideNotable = function () {
		document.getElementById(this.Name).style["display"] = "none";
		document.getElementById(this.Name + "Shader").style["display"] = "none";
	};
}
