// Functions to handle the dynaminc modifcation of attribute drop downs, images
// and prices

// Global store for all the products and attribute lists
var attributeStore = new Object;
attributeStore.savedDropdowns = new Object;

// Create and/or retrieve a product instance identified by uuid
function getProduct ( uuid ) {

	var product = attributeStore[uuid] || new Product(uuid);
	attributeStore[uuid] = product;
	return product;
};

// Create a new product object given the details we have
function Product ( uuid ) {

	this.uuid = uuid;
	this.attributes = new Array( 'att1', 'att2', 'att3', 'att4' );
	this.prices = new Array( 'atrmsrp', 'atrsell', 'atrwas' );
	this.attributeValues = new Object;

	// Store the attribute names (e.g. att1 => 'Colour', att2 => 'Size')
	this.setAttributeNames = function ( attributeNames ) {
		this.attributeNames = attributeNames;
	};

	// Text strings for currency symbols, default sale text, sale text, etc
	this.setLabels = function ( strings ) {
		this.labels = strings;
	};

	// Store attributes for screening the drop downs
	this.setAttributeData = function ( attributeValues, data ) {
		var attributeString = this.attributeString(attributeValues);
		data['attributeString'] = attributeString;
		this.attributeValues[attributeString] = { values: attributeValues, data: data };

		// Track the minimum prices for defaulting
		for ( var i in this.prices ) {
			if ( data[ this.prices[i] ] ) {
				this.storeMinimum( this.prices[i], data[ this.prices[i] ] );
			}
		}
	};

	// Store a record of the minimum value of a named attribute
	this.storeMinimum = function ( name, value ) {
		value = parseFloat(value);  // extract a number
		this.minimum = this.minimum || new Object;
		if ( ( !isNaN(value) && value < this.minimum[name] )
		  || !this.minimum[name] ) {
			this.minimum[name] = value;
		}
	};

	// Join the attribute values together to give us a unique string by which to
	// look up prices/images, etc when the user chooses their attributes.
	this.attributeString = function ( attributeValues ) {
		var attributes = this.attributes; // att1, att2, etc
		var attributeString = '';
		for ( var i = 0 ; i < attributes.length; i++ ) {
			if ( attributeValues[attributes[i]] != undefined ) {
				attributeString = attributeString + attributeValues[attributes[i]];
			}
		}
		return attributeString;
	};

	// OnChange of an attribute drop down, restrict the availability of the
	// remaining dropdowns and update any page elements necessary
	this.changeAttributes = function ( select ) {

		// Get a list of what has been selected and a copy of the full (ordered)
		// list of each attribute
		var selected = new Object;
		for ( var attribute in this.attributeNames ) {

			var selectId = this.getElementId(attribute);

			// If the element exists...
			var selectElement;
			if ( selectElement = document.getElementById( selectId ) ) {

				// Note down what is currently selected
				selected[attribute] = selectElement[selectElement.selectedIndex].value;

				// Take a copy the first time to avoid copying filtered lists.
				if ( attributeStore.savedDropdowns[selectId] == undefined ) {

					attributeStore.savedDropdowns[selectId] = new Array;
					for ( var i = 0; i < selectElement.options.length; i++ ) {
						attributeStore.savedDropdowns[selectId][i] = selectElement.options[i];
					}
				}

				// And reset the lists to their original state
				for ( var i = 0; i < attributeStore.savedDropdowns[selectId].length; i++ ) {
					selectElement.options[i] = attributeStore.savedDropdowns[selectId][i];
				}
			}
		};

		// Create a little closure by which to filter the available attributes
		var attributeNames = this.attributeNames; // Copied here for the filter closure
		var filterAttributesFunction = function ( element, index, array ) {
			// We must match each element which has been selected.
			for ( var attribute in attributeNames ) {

				// And even if we do, it must match your selection
				if ( selected[attribute] && attribute <= select.name ) {
					// Must match your selection
					if ( element['values'][attribute] != selected[attribute] ) {
						return false; 
					}
				}
			}
			return true;
		};

		// Work out the acceptable values using our closure
		var allowed = new Object;

		// Match every attribute against the selected options
		var toFilter = new Array;
		for ( var atrsku in this.attributeValues ) {
			toFilter.push( this.attributeValues[atrsku] );
		}

		var filteredAttributes = toFilter.filter( filterAttributesFunction );
		for ( var filtered in filteredAttributes ) {
			for ( var att in filteredAttributes[filtered]['values'] ) {
				allowed[att] = allowed[att] || new Object;
				allowed[att][ filteredAttributes[filtered]['values'][att] ] = true;
			}
		}

		// Foreach attribute, 
		for ( var att in allowed ) {

			// Only act on subsequent attributes
			if ( att <= select.name ) {
				continue;
			}
			// check each select
			var selectId = this.getElementId(att);

			var selectElement;
			if ( selectElement = document.getElementById( selectId ) ) {

				// Delete any elements which are not in the newly filtered list
				// Going backwards so we don't mess up the index as we delete things
				// out of it
				for ( var i = selectElement.options.length - 1; i > 0; i-- ) {
					if ( !( selectElement.options[i].value in allowed[att] ) ) {
						selectElement.options[i] = null;
					}
				}
			}
		}

		// Change anything that's asked us to change it
		this.updateElements( select );
	};

	// Change anything that's asked us to change it
	this.updateElements = function ( select ) {
		// Make any changes to the page based on this selection
		for ( var elementType in this.elements ) {

			var elementId = this.elements[elementType];
			var element = document.getElementById( elementId );

			if ( !element ) {
				alert( "Unable to find element '" + elementId + "' for " + elementType );
				continue;
			}

			// Bit of a hack for 'media items'
			if ( element.nodeName == 'IMG' ) {
				var chosenAttribute = select[select.selectedIndex].value;
				if ( this.type[elementType][chosenAttribute] != undefined ) {
					element.src = this.type[elementType][chosenAttribute];
				}
			}
			else if ( element.nodeName == 'SPAN' || element.nodeName == 'DIV' ) {
				
				var value; // value contains the price string (was, sell, msrp, saving)
				var label; // label contains the text of the price label 
				
				var attributeString = this.chosenAttributeString();	
				if ( this.validCombination(attributeString) ) {
					var attributeSku = this.attributeValues[attributeString];

					if ( elementType in attributeSku['data'] ) {
							value = attributeSku['data'][elementType] || '';
							label = this.labels[elementType] || '';
					}
				}
				else {
					value = this.minimum[elementType] || '';
					label = this.labels[ 'default_' + elementType ] || '';					
				}
				
				// Look for a label, too
				var labelElement = document.getElementById( elementId + 'Label' );
				if ( value == '' ) {
					// Hide the elements if we don't have a value for
					// them.
					element.style.display = 'none';
					labelElement.style.display = 'none';
					element.innerHTML = '';
					labelElement.innerHTML = '';
					if ( !labelElement ) {
						labelElement.style.display = 'none';
					}
				}
				else {
					if (value.toFixed) {
						value = value.toFixed(2);//if browser supports toFixed() method, fix to 2 dp
					}
					if ( this.labels['currsym'] != '' ) {
						value = this.labels['currsym'] + value;
					}

					//element.innerHTML = value;
					while (element.hasChildNodes()) {
						element.removeChild(element.lastChild);
					}
					element.appendChild(document.createTextNode(value));
					element.style.display = 'inline';
					if ( labelElement ) {
						//labelElement.innerHTML = label;
						while (labelElement.hasChildNodes()) {
							labelElement.removeChild(labelElement.lastChild);
						}
						labelElement.appendChild(document.createTextNode(label));
						labelElement.style.display = 'inline';
					}
				}
			}
		}
	};

	// Build an attribute String based on which of this product's attributes are
	// currently selected
	this.chosenAttributeString = function () {
		var attributeValues = new Object;
		for ( var attribute in this.attributeNames ) {
			if ( attribute in this.elements ) {
				var element = document.getElementById(this.elements[attribute]);
				if ( element ) {
					attributeValues[attribute] = element[element.selectedIndex].value
				}
				else {
					alert( "Can't find element: '" + this.elements[attribute] + "'" );
				}
			}
		}
		return this.attributeString(attributeValues);
	};

	// Initial hacky way of updating images (they only work on att1 and are
	// generated separately)
	this.setChanges = function ( args ) {
		this.type = this.type || new Object;
		this.type[ args['type'] ] = args['map'];
	};

	// Register an element as belonging to this product instance.
	this.registerElement = function () {
		this.elements = this.elements || new Object;
		for ( var i = 0; i < arguments.length; i++ ) {
			this.elements[ arguments[i]['type'] ] = arguments[i]['id'];
		}
	};

	// Fetch an element back
	this.getElementId = function ( element ) {
		return this.elements[element];
	};

	// Work out if this is a valid thing to add to the basket.
	// target and next come from the previous CheckAtt() - I'm pretty sure they
	// could be avoided by better use of an input type="image" and getting the
	// JS out if the way in an onClick
	this.checkAttributes = function ( form, target, next ) {

		// Set the form target 
		if ( target != "" )  {
			form.target = target;
		}
		// Set the hidden field in the form
		if ( next != "" )  {
			form.next.value = next;
		}
		
		// Make sure the combination exists
		// Make sure there are enough of them
		var attributeString = this.chosenAttributeString();
		var quantity = form.qty.value || 1; // sensible default?
		if ( this.validCombination(attributeString) ) {
			return true;
		}
		alert("Sorry, that combination is not available to purchase. Please try another.");
		return false;
	};

	// Is this a valid attribute combo?
	this.validCombination = function ( attributeString ) {
		if ( attributeString in this.attributeValues )
		{
			return true;
		}
		return false;
	};

};

// Return the subset of a list for which the given expression is true.
// Synonymous to perl's grep.
// http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:filter
if (!Array.prototype.filter) {
	Array.prototype.filter = function ( fun ) {
		var len = this.length;
		if (typeof fun != 'function')
		  throw new TypeError();

		var res = new Array();
		var thisp = arguments[1];

		for ( var i = 0; i < len; i++ ) {
		  if (i in this) {
			var val = this[i]; // in case fun mutates this
			if (fun.call(thisp, val, i, this))
			  res.push(val);
		  }
		}

		return res;
	  };
};

