ApparelOrderingSystem = new function()
{
	/**************************** Private Properties **************************/
	
	var myRunning = false;
	
	var myProductDataIsLoaded = false;
	
	var myLoadSettingsUrl = 'settings.php';
	
	var mySetup = {};
	var myStrings = {};
	
	var myServiceColors = {};
	
	var myEditingCartItemId = false;
	
	var myECommerceFormId = 'ECommerce_DF_addToCart';
	
	var mySwatchDOMIdPrefix = "Module_ApparelQuote_colorSwatch_";
	
	var myECommerceDF_Form = false;
	var myImageCaptionDiv = false;
	var myECommerceDF_ImageSelector = false;
	var myLoadingDiv = false;
	
	var myContainerDiv = false;
	
	var myStepDivs = false;
	var myStepDivsStatus = false;
	
	var myPriceDiv = false;
	
	var myProductId = false;
	var myProductData = false;
	var myProductImages = {};
	
	var mySelectedColors = $A();
	
	var myQuoteOptions = {
		productId: 0,
		combos: $A(),
		services: $A()
	};
	
	var myAddedCartItem = false;
	
	var myCartItems = false;
	
	var quoteTimer = 5;
	
	/***************************** Private Methods ****************************/
	
	var myReportError = function(e,data)
	{
		var report = {
			data: data,
			running: myRunning,
			setup: mySetup,
			serviceColors: myServiceColors,
			myProductId: myProductId,
			myProductData: myProductData,
			selectedColors: mySelectedColors,
			quoteOptions: myQuoteOptions,
			myAddedCartItem: myAddedCartItem,
			myCartItems: myCartItems
		};
		
		DFTools.reportError(e,'ApparelOrderingSystem',report);
	};
	
	/**
	 * Builds the DIV tags for each of the steps in the ordering process.
	 * 
	 * @return void
	 */
	var myBuildOrderingDivs = function()
	{
		var closeDiv,closeLink;
		
		myContainerDiv = new Element('div',{id:'Module_ApparelQuote_container'});
		
		myStepDivs = $A();
		myStepDivsStatus = $A();
		
		for(var step = 1; step <= 4; step++)
		{
			myStepDivs[step] = new Element('div').addClassName('step'+step);
			myStepDivsStatus[step] = 'h';
			
			myStepDivs[step].appendChild(new Element('h1').update(myStrings['step'+step+'_heading']));
			myStepDivs[step].appendChild(new Element('span').addClassName('notReady').update(myStrings['step'+step+'_notReadyText']));
			myStepDivs[step].appendChild(new Element('div',{'class':'outer'}));
			myContainerDiv.appendChild(myStepDivs[step]);
			
			myStepDivs[step].down('div').appendChild(new Element('div',{'class':'inner'}));
			myStepDivs[step].down('div').hide();
		}
		
		myContainerDiv.hide();
		
		// Add to page:
		myECommerceDF_Form.insert({before:myContainerDiv});
	};
	
	
	/**
	 * Examines the DOM where the ECommerce module has placed images, and populates
	 * our internal array with the information about those images.
	 * 
	 * @return void
	 */
	var myParseImages = function()
	{
		myProductImages = {};
		if(myECommerceDF_ImageSelector)
		{
			myECommerceDF_ImageSelector.childElements().invoke('down').each(function(img)
			{
				myProductImages[img.title.toLowerCase()] = img;
			});
		}
	};
	
	
	/**
	 * Event Handler for when a color swatch is clicked
	 * 
	 * @return void
	 */
	var mySwatchClickHandler = function(e)
	{
		var colorDiv = Event.element(e);
		var colorId = colorDiv.id.substr(mySwatchDOMIdPrefix.length);
		var quoteColorIndex = mySelectedColors.indexOf(colorId)
		
		if(quoteColorIndex == -1)
			this.selectColor(colorId);
		else
			this.deselectColor(colorId);
	}.bindAsEventListener(this);
	
	var myServiceColorClickHandler = function(e)
	{
		var colorDiv = Event.element(e);
		
		if(colorDiv.tagName.toLowerCase() != "div")
		{
			return false;
		}
		
		var nameParts = colorDiv.id.substr(mySwatchDOMIdPrefix.length).split('_');
		
		var areaId = nameParts[1];
		var colorId = nameParts[3];
		
		var quoteColorIndex = myQuoteOptions.services[areaId].colors.indexOf(colorId);
		
		if(quoteColorIndex == -1)
			this.selectServiceColor(areaId,colorId);
		else
			this.deselectServiceColor(areaId,colorId);
		
	}.bindAsEventListener(this);
	
	myForceServiceSelectionLayout = function()
	{
		$$('div.serviceColorSelector').invoke('insert','&nbsp;');
	};
	
	var myBuildServiceColorSelector = function(areaId,kind,container)
	{
		var colorTable = new Element('table').addClassName('colors');
		colorTable.appendChild(new Element('tbody'));
		
		$A(myServiceColors[kind]).eachSlice(mySetup.serviceColorTableColumns,function(colorgroup)
		{
			var tr = new Element('tr');
			
			colorgroup.each(function(color)
			{
				var td = new Element('td');
				var d = new Element('div',{id:mySwatchDOMIdPrefix+'area_'+areaId+'_color_'+color.id});
				try
				{
					d.setStyle({backgroundColor:'#'+color.hex});
				} catch(colorProblem) {}
				d.addClassName('color');
				d.observe('click',myServiceColorClickHandler);
				
				td.appendChild(d);
				
				td.appendChild(new Element('span').update(color.name));
				
				tr.appendChild(td);
				
				// This fixes a firefox issue where the DIVs don't get realigned.
				myForceServiceSelectionLayout.defer();
				
			},this);
			
			colorTable.down().appendChild(tr);
		},this);
		
		container.appendChild(colorTable);
	}.bind(this);
	
	/**
	 * Event handler for when a service is selected from a service menu
	 * 
	 * @return void
	 */
	var myServiceSelectionHandler = function(e)
	{
		var serviceMenu = Event.element(e);
		
		var menuNameParts = serviceMenu.name.split('_');
		
		var area_id = menuNameParts[2];
		
		myServiceForAreaChanged(area_id);
		
	}.bindAsEventListener(this);
	
	var myServiceForAreaChanged = function(area_id)
	{
		var serviceMenu = myStepDivs[3].down('select[name=ApparelOrder_area_'+area_id+'_service]');
		
		var menuNameParts = serviceMenu.name.split('_');
		
		var service = $F(serviceMenu);
		
		if(!service || service == '0')
		{
			service = false;
		}
		
		var optionTD = serviceMenu.up().next();
		
		myQuoteOptions.services[area_id] = {area: area_id, service: service};
			
		
		switch(service)
		{
			case 'Screenprinting':
				optionTD.childElements().invoke('remove');
				if(mySetup.allowUserToSelectScreenprintingColors)
				{
					var inputName = serviceMenu.name+'_colors';
					
					optionTD.appendChild(new Element('span').addClassName('selectColors').update(myStrings.service_screenprinting_selectColorsText));
					//optionTD.appendChild(new Element('input',{name:inputName}).addClassName('screenprinting_colors'));
					
					myQuoteOptions.services[area_id].colors = $A();
					
					var serviceColorDiv = new Element('div').addClassName('serviceColorSelector').hide();
					myBuildServiceColorSelector(area_id,'screenprinting',serviceColorDiv);
					optionTD.appendChild(serviceColorDiv);
					new Effect.Appear(serviceColorDiv);
				}
				else
				{
					var inputName = serviceMenu.name+'_colors';
					
					optionTD.appendChild(new Element('span').update(myStrings.service_screenprinting_optionText));
					optionTD.appendChild(new Element('input',{name:inputName}).addClassName('screenprinting_colors'));
				}
				break;
				
			case 'Embroidery':
				optionTD.childElements().invoke('remove');
				if(mySetup.allowUserToSelectEmbroideryColors)
				{
					optionTD.appendChild(new Element('span').addClassName('selectColors').update(myStrings.service_embroidery_selectColorsText));
					//optionTD.appendChild(new Element('input',{name:inputName}).addClassName('screenprinting_colors'));
					
					myQuoteOptions.services[area_id].colors = $A();
					
					var serviceColorDiv = new Element('div').addClassName('serviceColorSelector').hide();
					myBuildServiceColorSelector(area_id,'embroidery',serviceColorDiv);
					optionTD.appendChild(serviceColorDiv);
					new Effect.Appear(serviceColorDiv);
				}
				
				if(mySetup.allowUserToSelectStichCount)
				{
					var inputName = serviceMenu.name+'_stiches';
					
					optionTD.appendChild(new Element('span').update(myStrings.service_embroidery_optionText));
					optionTD.appendChild(new Element('input',{name:inputName}).addClassName('embroidery_stiches'));
				}
				break;
				
			default:	optionTD.childElements().invoke('remove'); break;
		}
		
		
	}.bind(this);
	
	
	/**
	 * Rebuild the content of myStepDivs based on myProductData
	 * 
	 * @return void
	 */
	var myRefreshOrderingDivs = function()
	{
		// -- Step #1: Select Colors____________________________________________
		var step = 1;
		
		myStepDivs[step].down('div div').childElements().invoke('remove');
		myStepDivs[step].down('div div').appendChild(new Element('p').update(myStrings['step'+step+'_instructions']));
		
		var colorTable = new Element('table').addClassName('colors');
		
		colorTable.appendChild(new Element('tbody'));
		
		myProductData.colors.eachSlice(mySetup.productColorTableColumns,function(colorgroup)
		{
			var tr = new Element('tr');
			
			colorgroup.each(function(color)
			{
				var td = new Element('td');
				var d = new Element('div',{id:mySwatchDOMIdPrefix+color.id});
				try
				{
					d.setStyle({backgroundColor:'#'+color.hex});
				} catch(colorProblem) {}
				d.addClassName('color');
				d.observe('click',mySwatchClickHandler);
				d.observe('mouseover',this.showImage.bind(this,color.name))
				
				td.appendChild(d);
				
				td.appendChild(new Element('span').update(color.name));
				
				tr.appendChild(td);
			},this);
			
			colorTable.down().appendChild(tr);
		},this);
		
		myStepDivs[step].down('div div').appendChild(colorTable);
		
		// -- Step #2: Select Sizes_____________________________________________
		step++;
		
		myStepDivs[step].down('div div').appendChild(new Element('p').update(myStrings['step'+step+'_instructions']));
		
		var sizeTable = new Element('table').addClassName('sizes');
		sizeTable.appendChild(new Element('tbody'));
		
		sizeTable.down().appendChild(new Element('tr'));
		sizeTable.down().down().appendChild(new Element('th'));
		sizeTable.down().down().down().appendChild(new Element('span').update('Size'));
		
		myProductData.sizes.each(function(size)
		{
			tr = new Element('tr');
			
			var td = new Element('td');
			td.appendChild(new Element('span').update(size.name));
			tr.appendChild(td);
			
			sizeTable.down().appendChild(tr);
		},this);
		
		myStepDivs[step].down('div div').appendChild(sizeTable);
		
		
		// -- Step #3: Decoration_______________________________________________
		step++;
		
		myStepDivs[step].down('div div').appendChild(new Element('p').update(myStrings['step'+step+'_instructions']));
		
		var areaTable = new Element('table').addClassName('areas');
		areaTable.appendChild(new Element('tbody'));
		
		areaTable.down().appendChild(new Element('tr'));
		areaTable.down().down().appendChild(new Element('th'));
		areaTable.down().down().down().appendChild(new Element('span').update('Area'));
		areaTable.down().down().appendChild(new Element('th'));
		areaTable.down().down().down().next().appendChild(new Element('span').update('Decoration').addClassName('revealAfterEffects'));
		
		if(myProductData.areas.length)
		{
			myProductData.areas.each(function(area)
			{
				tr = new Element('tr');
				
				var td = new Element('td');
				td.appendChild(new Element('span').addClassName('areaName').update(area.name));
				tr.appendChild(td);
				
				td = new Element('td');
				td.appendChild(new Element('select',{name:'ApparelOrder_area_'+area.id+'_service'}));
				td.down().appendChild(new Element('option',{value:0,selected:true}).update('Select Decoration'));
				$A(area.services).each(function(service)
				{
					this.appendChild(new Element('option',{value:service}).update(service));
				},td.down())
				
				td.down().observe('change',myServiceSelectionHandler);
				
				tr.appendChild(td);
				
				td = new Element('td');	// Additional table data for options
				tr.appendChild(td);
				
				areaTable.down().appendChild(tr);
			},this);
		}
		else
		{
			// No areas -- this product cannot be customized. Hide step 3.
			myStepDivs[step].hide();
		}
		
		myStepDivs[step].down('div div').appendChild(areaTable);
		
		// -- Step #4: Quote____________________________________________________
		step++;
		
		myPriceDiv = new Element('div');
		myPriceDiv.addClassName('price')
		
		myStepDivs[step].down('div div').appendChild(new Element('p').update(myStrings['step'+step+'_instructions']));
		
		myStepDivs[step].down('div div').appendChild(new Element('div').addClassName('details'));
		
		myStepDivs[step].down('div div').appendChild(myPriceDiv);
		
		var addToCartBtnText = myECommerceDF_Form['cartItemId'] ? myStrings.updateInCartBtnText : myStrings.addToCartBtnText;
		
		myStepDivs[step].down('div div').appendChild(new Element('input',{type:'button',value:myStrings.getQuoteBtnText}).addClassName('get').observe('click',this.getQuote.bind(this)));
		myStepDivs[step].down('div div').appendChild(new Element('input',{type:'button',value:addToCartBtnText}).addClassName('add').observe('click',this.addToCart.bind(this)));
		
		
		
		this.showStep(1);
		
		if(BrowserDetect.browser == 'Explorer') {
			window.setTimeout(function(){colorTable.hide();},0);
			window.setTimeout(function(){colorTable.show()},1500);
		}
		
	}.bind(this);
	
	
	var myPopulateOrderingFormWithSelectedCartItemData = function()
	{
		if(myECommerceDF_Form)
		{
			if(!myProductDataIsLoaded)
			{
				DFTools.console.debug("ApparelQuote: myPopulateOrderingFormWithSelectedCartItemData() called but !myProductDataIsLoaded, delaying 0.05 sec");
				myPopulateOrderingFormWithSelectedCartItemData.bind(this).delay(0.05);
			}
			else
			{
				var cartItemId = myECommerceDF_Form['cartItemId'];
				
				if(cartItemId)
				{
					myEditingCartItemId = $F(cartItemId);
					
					var index = myCartItems.pluck('cartItemId').indexOf(myEditingCartItemId);
					
					if(index >= 0)
					{
						var item = myCartItems[index];
						
						item.combos.each(function(combo)
						{
							this.selectColor(combo.color);
							this.setQtyForColorAndSize(combo.color,combo.size,combo.qty);
						},this);
						
						
						item.services.each(function(service)
						{
							this.selectServiceForArea(service.area,service.service);
							if(service.colors)
							{
								service.colors.each(function(c){
									this.selectServiceColor(service.area,c);
								},this);
							}
						},this);
					}
				}
			}
		}
	}.bind(this);
	
	/**
	 * Error handler for AJAX errors while loading product data
	 * 
	 * @return void
	 */
	var myLoadProductDataError = function(t,e)
	{
		if(!DFTools.pageUnload.isHappening())
		{
			myReportError(e,{msg:'Exception thrown in myLoadProductData'});
			DFTools.console.log('ApparelQuote: Exception thrown in myLoadProductData: ',e,t);
			alert(myStrings.loadProductError);
		}
	};
	
	
	/**
	 * Error handler for AJAX errors while loading cart data
	 * 
	 * @return void
	 */
	var myLoadCartError = function(t,e)
	{
		if(!DFTools.pageUnload.isHappening())
		{
			myReportError(e,{msg:'Exception thrown in myLoadCart'});
			DFTools.console.log('ApparelQuote: Exception thrown in myLoadCart: ',e,t);
			alert(myStrings.loadCartError);
		}
	};
	
	/**
	 * Process AJAX for loading cart data
	 * 
	 * @return void
	 */
	var myProcessCartData = function(requester)
	{
		try
		{
			if(!requester.responseJSON)
			{
				throw new Error("Ajax call did not return JSON");
			}
			
			myCartItems = false;
			var newCartItems = $A();
			
			$A(requester.responseJSON.items).each(function(data)
			{
				var cart_item = new ApparelOrderingSystem.CartItem(data);
				
				if(cart_item.ok)
				{
					newCartItems.push(cart_item);
				}
			});
			
			myCartItems = newCartItems;
			
			DFTools.console.debug('ApparelQuote: Loaded Apparel Cart Data.'); 
			
			myPopulateOrderingFormWithSelectedCartItemData();
		}
		catch(e)
		{
			myLoadCartError(requester,e);
		}
	};
	
	/**
	 * (Re)load cart data
	 * 
	 * @return void
	 */
	var myLoadCartData = function(reload)
	{
		if(reload || !myCartItems)
		{
			new Ajax.Request(mySetup.urls.getCart,{
				method:'post',
				parameters: {'t':new Date().getTime()},
				onSuccess: myProcessCartData,
				onException: myLoadCartError,
				onFailure: myLoadCartError
			});
		}
	};
	
	/**
	 * Internal function for fetching product data via AJAX and then processing
	 * the result.
	 * 
	 * @return void
	 */
	var myLoadProductData = function(transport)
	{
		try
		{
			if(typeof(transport) == "undefined")
			{
				// First call, need to fetch the product data.
				
				// Find our product ID:
				
				myProductId = $F(myECommerceDF_Form['pid']);
				
				new Ajax.Request(mySetup.urls.loadProductData,{
					method: 'get',
					parameters: {pid:myProductId},
					onSuccess: myLoadProductData,
					onException: myLoadProductDataError,
					onFailure: myLoadProductDataError
				});
			}
			else
			{
				// Second call, need to process AJAX data retured.
				
				if(!transport.responseJSON)
					throw new Error("Ajax call did not return JSON");
				
				myProductData = transport.responseJSON;
				
				if(typeof(myProductData.colors) != "object")	throw new TypeError("Could not load colors");
				if(typeof(myProductData.sizes) != "object")		throw new TypeError("Could not load sizes");
				if(typeof(myProductData.areas) != "object")		throw new TypeError("Could not load areas");
				
				myProductData.colors = $A(myProductData.colors);
				myProductData.sizes = $A(myProductData.sizes);
				myProductData.areas = $A(myProductData.areas);
				
				myQuoteOptions.productId = myProductId;
				
				// Set the product's color caption
				this.showImage('default');
				
				myRefreshOrderingDivs();
				myContainerDiv.show();
				myLoadingDiv.hide();
				
				myProductDataIsLoaded = true;
			}
		}
		catch (e){ myLoadProductDataError(transport,e); }
	}.bind(this);
	
	
	/**
	 * Stage-2 initialization handler, runs after the AJAX call from the fastInit returns.
	 * This function processes the AJAX data and completes the setup of the ordering system
	 * 
	 * @return void
	 */
	var myInitializeStageTwo = function(requester)
	{
		try
		{
			if(!requester.responseJSON)
				throw new Error("Ajax call did not return JSON");
			
			myStrings = requester.responseJSON.strings;
			
			if(typeof(myStrings) != 'object')
				throw new TypeError("Could not load strings");
				
			mySetup = requester.responseJSON.setup;
			
			if(typeof(mySetup) != 'object')
				throw new TypeError("Could not load module setup");
			
			myServiceColors = requester.responseJSON.serviceColors;
			
			if(typeof(myServiceColors) != 'object')
				throw new TypeError("Could not load service colors. Error was: "+ requester.responseJSON.serviceColorsError);
			
			if(myECommerceDF_Form)
			{
				myBuildOrderingDivs();
			}
			
			myRunning = true;
			
			myLoadCartData();
			
			if(myECommerceDF_Form)
			{
				myLoadProductData();
			}
		}
		catch(e)
		{
			if(!DFTools.pageUnload.isHappening())
			{
				DFTools.console.log(e,"ApparelQuote: Exception in myInitializeStageTwo, Requester object:",requester);
				myReportError(e,{msg:'Stage 2 Init failed and user was notified',ajaxResponseText:requester.responseText});
				alert("Error: The Apparel Order system could not complete Stage 2 Initialization. Please contact us for support");
			}
		}
		
	}.bind(this);
	
	
	/**
	 * Internal function used to prepare myQuoteOptions for sending to server
	 * 
	 * @return void
	 */
	var myRebuildQuoteOptions = function()
	{	
		// Collect all the size and color quantities:
		var sizeTable = myStepDivs[2].down('table.sizes').down();
		
		myQuoteOptions.combos = sizeTable.select('input').collect(function(input)
		{
			var parts = input.name.split('_');
			var qty = parseInt($F(input));
			
			return  qty ? {size:parts[2], color:parts[4], qty: qty} : null;
		}).compact();
		
		DFTools.console.debug('ApparelQuote: starting myRebuildQuoteOptions.services.each with',myQuoteOptions,Object.inspect(myQuoteOptions.services))
		
		myQuoteOptions.services.each(function(currentService,areaId)
		{
			// Skip undefined entries -- the array is sorted by area IDs so there may be many undefined
			// items on IE7.
			if(typeof(currentService) == 'undefined') { return false; }
			
			if(typeof(currentService) != 'object') { throw new TypeError('service for area ID '+areaId+' is not an object in myRebuildQuoteOptions'); }
			
			if(currentService.service)
			{
				if( (currentService.service == 'Screenprinting' && !mySetup.allowUserToSelectScreenprintingColors) )
				{
					var colors = parseInt($F($$('input[name=ApparelOrder_area_'+currentService.area+'_service_colors]')[0]));
					currentService.colors = new Array(colors);

					for(i=0;i<colors;++i)
						currentService.colors[i] = -1;
				}
				else if(!(currentService.colors instanceof Array)  ||  !currentService.colors.length)
				{
					if(currentService.service == 'Screenprinting')
					{

					}
					else if(currentService.service == 'Embroidery')
					{

					}
					else if(currentService.service == 'Digital Art')
					{

					}
				}
				
				if(currentService.service == 'embroidery')
				{
					var stichInput = $(myECommerceDF_Form['ApparelOrder_area_'+areaId+'_service_stiches']);
					
					if(stichInput)
					{
						var stiches = $F(stichInput);
						var area = myProductData.areas[myProductData.areas.pluck('id').indexOf(areaId)];
						
						if(stiches != parseInt(stiches))
						{
							throw {
								showAlert: true,
								error: 'Invalid Number',
								msg: myStrings.invalidUserEmbroideryStiches + '"'+area.name+'"',
								element: stichInput
							};
						}
						currentService.stiches = stiches;
					}
					else
					{
						currentService.stiches = 0;
					}
				}
			}
		},this);
	};
	
	
	
	/**
	 * Error handler for getQuote -- runs when an AJAX error occurs trying to get a quote
	 * 
	 * @return void
	 */
	var myGetQuoteError = function(t,e)
	{
		if(!DFTools.pageUnload.isHappening())
		{
			report = {msg:'Exception thrown in getQuote'};
			if(t)
			{
				if(t.responseJSON)
					report.responseJSON = t.responseJSON;
			}
			
			myReportError(e,report);
			DFTools.console.log('ApparelQuote: Exception thrown in getQuote: ',e,t);
			alert(myStrings.getQuoteAjaxError);
		}
	};
	
	/**
	 * Second stage of getQuote, this function parses the quote data returned by AJAX
	 * 
	 * @return void
	 */
	var myParseQuoteResponse = function(t)
	{
		var result = {};
		
		try
		{
			if(!t.responseJSON)
				throw new Error("Ajax call did not return JSON");
			
			result = t.responseJSON;
			
			if(!result.success)
			{
				var msg = result.message;
				DFTools.console.log("ApparelQuote: Server reported doQuote failed: ",result," transport: ",t);
				alert(msg);
				return;
			}
			
			myPriceDiv.update(result.price_each.numberFormat("$#,###.##") + " each");
			
			var detailsHtml = '<dl>';
			
			detailsHtml += '<dt>Product Price:</dt><dd>'+result.productPrice.numberFormat("$#,###.##")+'</dd>';
			detailsHtml += '<dt>Service Price:</dt><dd>'+result.servicesPrice.numberFormat("$#,###.##")+'</dd>';
			
			var chargeNames = $H({'setup':'Setup Charge:','artwork':'Artwork Charge:','screens':'Screen Charges:','digitizing':'Digitizing Charge:'});
			
			$H(result.otherCharges).each(function(charge)
			{
				if(charge[1])
				{
					var chargeName = chargeNames.get(charge[0]);
					detailsHtml += '<dt>'+chargeName+'</dt><dd>'+charge[1].numberFormat("$#,###.##")+'</dd>';
				}
			});
			
			
			detailsHtml += '<dt class="total">Total:</dt><dd class="total">'+result.price.numberFormat("$#,###.##")+'</dd>';
			
			myStepDivs[4].down('div.details').update(detailsHtml)
		}
		catch(e) { myGetQuoteError(t,e); }
	};
	
	
	
	/**
	 * Error handler for addToCart -- runs when an AJAX error occurs trying to add an item to the cart
	 * 
	 * @return void
	 */
	var myAddToCartError = function(t,e)
	{
		if(!DFTools.pageUnload.isHappening())
		{
			myReportError(e,{msg:'Add To Cart Error'});
			DFTools.console.log('ApparelQuote: Exception thrown in addToCart: ',e,t);
			alert(myStrings.addToCartAjaxError);
		}
	};
	
	/**
	 * Second stage of addToCart, this function parses the quote data returned by AJAX
	 * and then if everything looks good, initiates the ECommerce module add to cart.
	 * 
	 * @return void
	 */
	var myParseAddToCartResponse = function(t)
	{
		var result = {};
		
		myParseQuoteResponse(t);
		
		try
		{
			if(!t.responseJSON)
				throw new Error("Ajax call did not return JSON");
			
			result = t.responseJSON;
			
			if(!result.success)
			{
				var msg = result.message;
				DFTools.console.log("ApparelQuote: Server reported doQuote failed: ",result," transport: ",t);
				alert(msg);
				return;
			}
			
			myAddedCartItem = new ApparelOrderingSystem.CartItem(result.item);
			
			if(!myAddedCartItem.ok)
			{
				throw new Error(cartItem.errState);
			}
			
			myPriceDiv.update(result.price_each.numberFormat("$#,###.##") + " each");
			
			if(myAddedCartItem.cartItemId)
			{
				// we must have been updating a caret item, so remove the old cart item and add the new one.
				
				var index = myCartItems.pluck('cartItemId').indexOf(myAddedCartItem.cartItemId);
				
				myCartItems[index] = myAddedCartItem;
			}
			else
			{
				myCartItems.push(myAddedCartItem);
				
				ECommerce_DF.observe('cartItemAdded',myHandleItemAddedToCart);
			}
			
			DFTools.fireEvent(myECommerceDF_Form,'submit');
		}
		catch(e) { myAddToCartError(t,e); }
	};
	
	/**
	 * Third stage of addToCart, this function is called after the product is added to the
	 * cart by the generic eCommerce system and it will update the ApparelQuote system with
	 * the eCommerce system's cart item ID.
	 * 
	 * @return void
	 */
	var myHandleItemAddedToCart = function(args)
	{
		myAddedCartItem.cartItemId = args.item.cartItemId;
		
		new Ajax.Request(mySetup.urls.updateCartItemId,{
				parameters: {'apcid': myAddedCartItem.apcartItemId, 'ecomid': myAddedCartItem.cartItemId},
				onException: myAddToCartError,
				onFailure: myAddToCartError
			});
		
		ECommerce_DF.stopObserving('cartItemAdded',myHandleItemAddedToCart);
	};
	
	/**
	 * Handler for when the ECommerce shopping cart is refreshed, this function
	 * ensures that all the items in the cart show proper ApparelQuote pricing
	 * 
	 * @return void
	 */
	var myHandleCartRefresh = function(args)
	{
		DFTools.console.debug('ApparelOrderingSystem.myHandleCartRefresh called. Args:',args,'APCart:',myCartItems); 
		
		if(!myCartItems)
		{
			DFTools.console.debug('ApparelOrderingSystem cart data not loaded, delaying 0.1sec');
			
			myHandleCartRefresh.delay(0.1,args);
		}
		else
		{
			var cartItemIds = myCartItems.pluck('cartItemId');
			
			$A(args.items).each(function(item)
			{
				var index = cartItemIds.indexOf(item.cartItemId);
				
				if(index >= 0)
				{
					myCartItems[index].applyTo(item);
				}
			},this);
			
			DFTools.console.debug('ApparelQuote: Updated cart with Apparel Quote Data.');
			
			ECommerce_DF.refreshCartTotal();
		}
	};
	
	var myQtyInputChangeHandler = function(e)
	{
		var field = e.element();
		
		if($F(field) != parseInt($F(field)))
			field.value = '';
		
		if(myStepDivs[2].select('input').pluck('value').any(function(n){ return !n.blank() && n>0; }))
		{
			this.showStep(3);
			this.showStep(4);
		}
		else
		{
			this.hideStep(3);
			this.hideStep(4);
		}
		
	}.bindAsEventListener(this);
	
	var myQtyInputKeypressHandler = function(e)
	{
		var field = e.element();
		
		var c=e.charCode? e.charCode : e.keyCode
		
		switch(c)
		{
			case 9:		// tab
			case 37:	// left arrow
			case 38:	// up arrow
			case 39:	// right arrow
			case 40:	// down arrow
				// Nothing to do, just pass on this event
				break;
				
			case 8:		// backspace
			case 46:	// delete
				// recheck the field.
				myQtyInputChangeHandler.defer(e);
				break;
			
			default:
				DFTools.console.log("ApparelOrderingSystem: Canceled keypress event ",e,",code ",c);
				if(c<48||c>57)
				{
					e.stop();
				}
				else
				{
					// Number pressed.
					myQtyInputChangeHandler.defer(e);
				}
		}
		
	}.bindAsEventListener(this);
	
	/**
	 * Error handler for AJAX errors during initialization
	 * 
	 * @return void
	 */
	var myInitializeError = function(requester,ex)
	{
		if(!DFTools.pageUnload.isHappening())
		{
			DFTools.console.log("ApparelQuote: Ajax request to fetch apparel order config failed. Requester object:",requester);
			if(ex) DFTools.console.log("Exception:",ex);
			alert('The Apparel Ordering system could not be loaded because of an error. Please email us for support.');
			myReportError(ex,{msg:'myInitializeError failed'});
		}
	}.bind(this);
	
	
	/**
	 * Stage 1 (fast) init handler
	 * 
	 * @return void
	 */
	var myFastInitHandler = function()
	{
		// See if we can find an "Add to cart" form. If so, we will capture it and
		// remove it from the page to prevent the user from using it before we're
		// fully loaded.
		
		try
		{
			// Make sure that when the ECommerce system loads the cart data, we can update any cart items:
				
			ECommerce_DF.observe('loaded',function()
			{
				ECommerce_DF.observe('cartRefreshed',myHandleCartRefresh);
			});
			
			myECommerceDF_Form = $(myECommerceFormId);
			
			if(myECommerceDF_Form)
			{
				myLoadingDiv = new Element('div').update('Loading, Please Wait...');
				myECommerceDF_Form.insert({before:myLoadingDiv});
				myECommerceDF_Form.hide();
				
				
				// Find image caption div and image selection div:
				
				if($('main_product_image'))
				{
					myImageCaptionDiv = $('main_product_image').down('.storeImageCaption');
					myECommerceDF_ImageSelector = $('ECommerce_DF_ProductImageSelector');
					
					
					// Hide image selection div, we don't want them to see that:
					
					if(myECommerceDF_ImageSelector) { myECommerceDF_ImageSelector.hide(); }
				}				
				
				
				
				// Now determine which images we have...
				
				myParseImages();
				
				// And replace the image shown with the default, so while our data
				// is being loaded, some random color isn't shown. Note, our
				// showImage() function relies on variables from myStrings so we'll
				// set some empty strings in there for now:
				
				myStrings = {
					colorDefaultCaption : '',
					colorCaptionPrefix : '',
					colorNotAvailableDefaultShown : ''
				};
				
				this.showImage('default');
			}
			else
			{
				myECommerceDF_Form = false;
			}
				
			// Rewrite the load settings URL based on the URL of this static script
			
			$A(document.getElementsByTagName("script")).findAll( function(s) {
				return (s.src && s.src.match(/ApparelOrderingSystem\.js(\?.*)?$/))
			}).each( function(s) {
				var path = s.src.replace(/ApparelOrderingSystem\.js(\?.*)?$/,'');
				myLoadSettingsUrl = path + myLoadSettingsUrl;
			});
			
			// Load our dynamic settings and run second stage initialization:
			
			new Ajax.Request(myLoadSettingsUrl,{
				method: 'get',
				onSuccess: myInitializeStageTwo,
				onException: myInitializeError,
				onFailure: myInitializeError
			});
		}
		catch(e)
		{
			if(!DFTools.pageUnload.isHappening())
			{
				myReportError(e,{msg:'fastInit failed'});
				
				DFTools.console.log("ApparelQuote: Could not load Quote Engine because of an error:",e);
				alert('The Apparel Ordering system could not be loaded because of an error. Please email us for support.');
			}
		}
	}.bind(this);
	
	
	
	/**************************** Public Properties ***************************/
	
	
	
	/*************************** Privileged Methods ***************************/
	
	/**
	 * Is the system up and running?
	 * 
	 * @return bool
	 */
	this.running = function()
	{
		return myRunning;
	};
	
	
	/**
	 * Display an image for a given color name
	 * 
	 * @return void
	 */
	this.showImage = function(colorName)
	{
		var colorIndex = colorName.toLowerCase();
		var img = myProductImages[colorIndex];
		
		var caption = (colorIndex == 'default') ? myStrings.colorDefaultCaption : myStrings.colorCaptionPrefix+colorName;
		
		if(!img)
		{
			colorIndex = 'default';
			img = myProductImages[colorIndex];
			caption = myStrings.colorNotAvailableDefaultShown;
		}
		
		try
		{
			if(img) img.up().onclick();
			if(myImageCaptionDiv) { myImageCaptionDiv.update(caption); }
		} 
		catch (e)
		{
			myReportError(e,{msg:'Exception in showImage'});
			DFTools.console.log("Apparel Ordering System Caught Exception: ",e);
		}
	},
	
	
	this.showStep = function(stepNumber)
	{
		if(typeof(stepNumber) != 'number') { throw new TypeError('stepNumber must be int'); }
		
		if(!myStepDivs[stepNumber]) { throw new RangeError('stepNumber out of range'); }
		
		if(myStepDivsStatus[stepNumber] == 'h')
		{
			var f = false;
			
			myStepDivs[stepNumber].down('span.notReady').hide();
			
			var stepControls = myStepDivs[stepNumber].select('input','select','.revealAfterEffects');
			
			stepControls.invoke('hide');
			
			if(stepNumber == 4 || (BrowserDetect.browser == 'Explorer' && BrowserDetect.version == 6)) {
				new Effect.Appear(myStepDivs[stepNumber].down('div'),{duration: 0.8, queue: 'end'});
			} else {
				new Effect.SlideDown(myStepDivs[stepNumber].down('div'),{duration: 0.8, queue: 'end'});
			}
			
			myStepDivsStatus[stepNumber] = 's';
			
			stepControls.invoke.bind(stepControls,'show').delay(0.85);
		}
		
		//if(stepNumber == 3 && BrowserDetect.browser == 'Explorer')
		//{
		//	window.setTimeout(function(){myStepDivs[stepNumber].down('table').hide();},0);
		//	window.setTimeout(function(){myStepDivs[stepNumber].down('table').show()},100);
		//}
	};
	
	this.hideStep = function(stepNumber)
	{
		if(typeof(stepNumber) != 'number') { throw new TypeError('stepNumber must be int'); }
		
		if(!myStepDivs[stepNumber]) { throw new RangeError('stepNumber out of range'); }
		
		var notReady = false;
		
		for(var s=4; s>=stepNumber; --s)
		{
			if(myStepDivsStatus[s] == 's')
			{
				notReady = myStepDivs[s].down('span.notReady');
				
				new Effect.SlideUp(myStepDivs[s].down('div.outer'),{duration: 0.5, queue: 'end'});
				
				notReady.show.bind(notReady).delay(0.51);
				
				myStepDivsStatus[s] = 'h';
			}
		}
	};
	
	/**
	 * Add a color to the selection of colors to order
	 * 
	 * @return void
	 */
	this.selectColor = function(colorId)
	{
		var colorIndex = myProductData.colors.pluck('id').indexOf(colorId);
		
		if(colorIndex == -1)
			throw new RangeError("No such color ID");
		
		try
		{
			if(mySelectedColors.indexOf(colorId) == -1)
			{
				var check = new Element('img',{src:mySetup.colorCheckmarkImage});
				check.addClassName('transpng');
				check.observe('click',this.deselectColor.bind(this,colorId));
				
				var color = myProductData.colors[colorIndex];
				
				var colorDiv = $(mySwatchDOMIdPrefix+colorId);
				colorDiv.appendChild(check);
				mySelectedColors.push(colorId);
				
				var sizeTable = myStepDivs[2].down('table.sizes').down();
				
				var rows = sizeTable.childElements();
				rows[0].appendChild(new Element('th').addClassName('color_'+color.id).addClassName('revealAfterEffects').update(color.name));
				
				rows[0] = null;
				rows = rows.compact();
				
				var sizeIndex = 0;
				
				rows.each(function(tr)
				{
					var size = myProductData.sizes[sizeIndex];
					
					var inputName = 'ApparelOrder_size_'+size.id+'_color_'+color.id+'_qty';
					
					var input = new Element('input',{name:inputName});
					
					input.observe('change',myQtyInputChangeHandler);
					input.observe('blur',myQtyInputChangeHandler);
					input.observe('keypress',myQtyInputKeypressHandler);
					
					var td = new Element('td').addClassName('color_'+color.id);
					td.appendChild(input);
					
					tr.appendChild(td);
					
					sizeIndex++;
				},this);
				
				this.showStep(2);
				
				//if(BrowserDetect.browser == 'Explorer') {
				//	window.setTimeout(function(){sizeTable.hide();},0);
				//	window.setTimeout(function(){sizeTable.show()},900);
				//}
			}
		}
		catch(e)
		{
			myReportError(e,{msg:'exception in selectColor'});
			
			DFTools.console.log("Apparel ordering System Caught exception: ",e);
			alert(myStrings.selectColorError);
		}
	}
	
	
	/**
	 * Remove a color from the selection of colors on this quote
	 * 
	 * @return void
	 */
	this.deselectColor = function(colorId)
	{
		var colorIndex = myProductData.colors.pluck('id').indexOf(colorId);
		
		if(colorIndex == -1)
			throw new RangeError("No such color ID");
		
		try
		{
			var color = myProductData.colors[colorIndex];
			
			var colorDiv = $(mySwatchDOMIdPrefix+colorId)
			colorDiv.down().remove();
			mySelectedColors = mySelectedColors.without(colorId);
			
			var sizeTable = myStepDivs[2].down('table.sizes').down();
			
			var colorCss = 'color_'+color.id;
			
			sizeTable.select('td.'+colorCss).invoke('remove');
			sizeTable.select('th.'+colorCss).invoke('remove');
			
			if(!mySelectedColors.length) { this.hideStep(2); }
		}
		catch(e)
		{
			myReportError(e,{msg:'exception in deselectColor'});
			
			DFTools.console.log("Apparel Ordering System Caught exception: ",e);
			alert(myStrings.selectColorError);
		}
	}
	
	this.setQtyForColorAndSize = function(color,size,qty)
	{
		myStepDivs[2].down('input[name=ApparelOrder_size_'+size+'_color_'+color+'_qty]').value = qty;
		this.showStep(3);
		this.showStep(4);
	}.bind(this);
	
	this.selectServiceForArea = function(area_id,service)
	{
		var serviceMenu = myStepDivs[3].down('select[name=ApparelOrder_area_'+area_id+'_service]');
		
		if(!serviceMenu) {
			throw new RangeError("No such area ID");
		}
		
		var index = $A(serviceMenu.options).pluck('value').indexOf(service);
		
		if(index <= 0) {
			throw new TypeError("Service not available for area.");
		}
		
		serviceMenu.selectedIndex = index;
		myServiceForAreaChanged(area_id);
	}.bind(this);
	
	/**
	 * Add a color to the selection of service colors to order
	 * 
	 * @return void
	 */
	this.selectServiceColor = function(areaId,colorId)
	{
		var areaIndex = myProductData.areas.pluck('id').indexOf(areaId);
		
		if(areaIndex == -1)
			throw new RangeError("No such area ID");
		
		var service = myQuoteOptions.services[areaId].service;
		
		if(!service)
			throw new TypeError("Area has no service");
		
		service = service.toLowerCase();
		
		if(!myServiceColors[service] || !myServiceColors[service].length)
			throw new Type("Area's service has no colors")
		
		var colorIndex = myServiceColors[service].pluck('id').indexOf(colorId);
			
		if(colorIndex == -1)
			throw new RangeError("No such color ID for that service");
		
		try
		{
			var check = new Element('img',{src:mySetup.colorCheckmarkImage});
			check.addClassName('transpng');
			check.observe('click',this.deselectServiceColor.bind(this,areaId,colorId));
			
			var color = myServiceColors[service][colorIndex];
			
			var colorDiv = $(mySwatchDOMIdPrefix+'area_'+areaId+'_color_'+colorId);
			colorDiv.appendChild(check);
			myQuoteOptions.services[areaId].colors.push(colorId);
		}
		catch(e)
		{
			myReportError(e,{msg:'exception in selectServiceColor'});
		
			DFTools.console.log("Apparel Ordering System Caught exception: "+e);
			alert(myStrings.selectColorError);
		}
	}
	
	
	/**
	 * Remove a color from the selection of colors on this quote
	 * 
	 * @return void
	 */
	this.deselectServiceColor = function(areaId,colorId)
	{
		var areaIndex = myProductData.areas.pluck('id').indexOf(areaId);
		
		if(areaIndex == -1)
			throw new RangeError("No such area ID");
		
		var service = myQuoteOptions.services[areaId].service;
		
		if(!service)
			throw new TypeError("Area has no service");
		
		service = service.toLowerCase();
		
		if(!myServiceColors[service] || !myServiceColors[service].length)
			throw new Type("Area's service has no colors")
		
		var colorIndex = myServiceColors[service].pluck('id').indexOf(colorId);
			
		if(colorIndex == -1)
			throw new RangeError("No such color ID for that service");
		
		try
		{
			var colorDiv = $(mySwatchDOMIdPrefix+'area_'+areaId+'_color_'+colorId);
			colorDiv.down().remove();
			myQuoteOptions.services[areaId].colors = myQuoteOptions.services[areaId].colors.without(colorId);
		}
		catch(e)
		{
			myReportError(e,{msg:'exception in deselectServiceColor'});
			
			DFTools.console.log("Apparel Ordering System Caught exception: "+e);
			alert(myStrings.selectColorError);
		}
	}
	
	
	/**
	 * Get the actual quote for the selected options, this is done asynchronousoly via AJAX
	 * 
	 * @return void
	 */
	this.getQuote = function()
	{
		try
		{
			myPriceDiv.update(myStrings.pleaseWaitFetchingQuote);
			
			myRebuildQuoteOptions();
			
			new Ajax.Request(mySetup.urls.getQuote,{
				parameters: {params: Object.toJSON(myQuoteOptions)},
				onSuccess: myParseQuoteResponse,
				onException: myGetQuoteError,
				onFailure: myGetQuoteError
			});
		}
		catch(e)
		{
			if(e.showAlert && e.msg)
			{
				alert(e.msg);
			}
			else
			{
				myReportError(e,{msg:'exception thrown in getQuote'});
				alert(myStrings.getQuoteError);
			}
			
			myPriceDiv.update('');
			myStepDivs[4].down('div.details').update('');
			
			DFTools.console.log('ApparelQuote: getQuote caught an exception: ',e);
			
			if(e.element)
			{
				e.element.focus();
				new Effect.Highlight(e.element);
			}
		}
	};
	
	
	
	/**
	 * Add this product to the cart. Adding to cart is a multi-stage process initiated
	 * by this function.
	 * 
	 * @return void
	 */
	this.addToCart = function()
	{
		try
		{	
			myRebuildQuoteOptions();
			
			var postData = {params: Object.toJSON(myQuoteOptions)};
			
			if(myEditingCartItemId) {
				postData.updateCartItemId = myEditingCartItemId;
			}
			
			new Ajax.Request(mySetup.urls.addToCart,{
				parameters: postData,				
				onSuccess: myParseAddToCartResponse,
				onException: myAddToCartError,
				onFailure: myAddToCartError
			});
		}
		catch(e)
		{
			if(e.showAlert && e.msg)
			{
				alert(e.msg);
			}
			else
			{
				myReportError(e,{msg:'exception thrown in addToCart'});
				alert(myStrings.addToCartErrorError);
			}
			
			myPriceDiv.update('');
			myStepDivs[4].down('div.details').update('');
			
			DFTools.console.log('ApparelQuote: addToCart caught an exception: ',e);
			
			if(e.element)
			{
				e.element.focus();
				new Effect.Highlight(e.element);
			}
		}
	};
	
	/************************** Initialization Code ***************************/
	
	// Most of our initialization is done after the body has loaded (FastInit)
	
	FastInit.addOnLoad(myFastInitHandler);
};


ApparelOrderingSystem.CartItem = Class.create(
{
	initialize: function(data)
	{
		this._myCartItem = false;
		
		this.loadData(data)
	},
	
	loadData: function(data)
	{
		this.ok = this.errState = false;
		
		try
		{
			this._myRow = false;
			
			if(data.id) this.apcartItemId = data.id; else throw new Error("No ID set");
			if(data.itemId) this.cartItemId = data.itemId;
			
			if(typeof(data.qty) == "number") this.qty = data.qty; else throw new TypeError("Invalid Quantity");
			if(typeof(data.price) == "number") this.price = data.price; else throw new TypeError("Invalid price");
			
			if(data.options instanceof Array) this.options = data.options; else throw new TypeError("Options is not an Array");
			
			if(data.combos instanceof Array) this.combos = data.combos; else throw new TypeError("Combos is not an Array");
			if(data.services instanceof Array) this.services = data.services; else throw new TypeError("Services is not an Array");
			
			if(data.addedAt) this.addedAt = data.addedAt;
			
			this.ok = true;
			
			if(this._myRow)
			{
				this._myRebuildCells();
			}
		}
		catch(e)
		{
			this.errState = e.message;
			this.ok = false;
		}
	},
	
	_myUpdateCartItemRow: function()
	{
		if(this._myCartItem && this._myCartItem._myRow)
		{
			var tr = this._myCartItem._myRow;
			
			tr.down('div.'+ECommerce_DF.cartColumnClasses[1]+' h3').update(this.options.join(', '));
			tr.down('div.'+ECommerce_DF.cartColumnClasses[2]).update(this.qty);
			tr.down('div.'+ECommerce_DF.cartColumnClasses[3]).update(this.price.numberFormat('$#,###.##'));
		}
	},
	
	applyTo: function(cartItem)
	{
		this._myCartItem = cartItem;
		cartItem.price = this.price;
		this._myUpdateCartItemRow();
	}
});