jQuery.extend({
    keys: function(obj){
        var a = [];
        jQuery.each(obj, function(k){ a.push(k) });
        return a;
    },
    copy_keys: function(obj){
        var a = {};
        jQuery.each(obj, function(k){ a[k]=null });
        return a;
    },
    exists: function(obj){
		var length;
		if ((typeof obj==='object')&&(obj instanceof jQuery)) {
			length = obj.length;
		}
		else {
			length = jQuery(obj).length;
		}
        return (length>0);
    },
	empty: function(mixed_var){
		// http://phpjs.org/functions/empty
		var key;
		if (mixed_var === "" ||
			mixed_var === 0 ||
			mixed_var === "0" ||
			mixed_var === null ||
			mixed_var === false ||
			typeof mixed_var === 'undefined'
		){
			return true;
		}
		if (typeof mixed_var == 'object') {
			for (key in mixed_var) {
				return false;
			}
			return true;
		}
		return false;
	},
	count_object_elements: function(obj, exclude_empty_values){
		exclude_empty_values = (typeof exclude_empty_values !== 'undefined')? exclude_empty_values : false;
		if ((typeof Object.prototype['__count__'] === 'number') && !exclude_empty_values) {
			return obj['__count__'];
		}
		else {
			var count = 0;
			for (var k in obj) {
				if (obj.hasOwnProperty(k)) {
					if (!exclude_empty_values || !jQuery.empty(obj[k])) {
						++count;
					}
				}
			}
			return count;
		}
	}
});

var common_json_files					= {
											"all":					['__fabrics','leg_finish','nailhead_finish','leg_caster_finish'],
											"pending_requests":		{},
											"data":					{}
};

function custom_upholstery_tool(sku, form_id, options) {
	var $								= jQuery;
	var item							= this;

	this.sku							= sku;
	this.form_id						= form_id;
	this.options						= options;

	this.gsMainSKU						= null;
	this.giMainRes						= null;
	this.gsPipingSKU					= null;
	this.giPipingRes					= null;

	this.gsMainTitle					= null;
	this.gsMainTitleBuy					= null;
	this.gsPipingTitle					= null;
	this.gsPipingTitleBuy				= null;

	this.has_swatches					= false;
	this.has_piping						= false;
	this.can_show_popups				= (typeof $.fn.colorbox === 'function')? true : false;
	this.form_fields					= {};
	this.default_values					= {
											"main_fabric":				"G101"				// fabric sku
										,	"piping_fabric":			"G101"				// fabric sku
										,	"leg_finish":				"natural"			// descriptive key into json data (or null)
										,	"nailhead_finish":			"nickel"			// descriptive key into json data (or null)
										,	"leg_caster_finish":		"natural/nickel"	// descriptive key into json data (or null)
	};
	this.qs_parameters					= {
											"main_fabric":				"color"				// fabric sku
										,	"piping_fabric":			"piping"			// fabric sku
										,	"leg_finish":				"leg"				// descriptive key into json data
										,	"nailhead_finish":			"nailhead"			// descriptive key into json data
										,	"leg_caster_finish":		"leg_caster"		// descriptive key into json data
	};
	// dependency: jquery.parsequery.js
	this.qs_data						= {
											"global":					$.parseQuery()
										,	"filtered":					undefined
	};
	this.form_field_aliases				= {
											"main_fabric":				"base_fabric"
										,	"piping_fabric":			"piping"
	};
	this.form_field_value_filters;		// default value: undefined. structure: {"main_fabric":{"whitelist":[sku1,sku2,sku3], "blacklist":[]}, "piping_fabric":{"whitelist":[], "blacklist":[sku4,sku5]}}
	this.hidden_form_fields				= [];												// label_txt
	this.json_file_urls					= {};
	this.required_json_files			= ['__fabrics','__grade_fabric_mapping'];
	this.json_files						= {};
	this.additional_json_files			= {};
	this.additional_customized_fields;

	// note: important that key for json data matches key for form field that requires it..
	this.json_data						= {};
	this.json_file_count				= 0;
	this.json_reply_count				= 0;


	// -------------------
	// data initialization
	// --------------------------------------------------------------------------------------------
	// obtain form/field element IDs from inspection of the DOM, fill data structures based on what is found.

	this.inspect_DOM = function() {
	  $('#' + item.sku).trigger('pre__inspect_DOM', [item]);

	  item.has_swatches = (
		(typeof item.options.callback_function.get_swatch_container === 'function') &&
		( item.options.callback_function.get_swatch_container(item).find('.fabric_main_swatches').length === 1 )
	  );
	  if (item.has_swatches) {
		$.each(item.form_field_aliases, function(alias, label_txt){
			if ($.inArray(label_txt, item.hidden_form_fields) === -1) {
				item.hidden_form_fields.push(label_txt);
			}
		});
	  }

	  // delay declaration to allow modification of option values by hook functions
	  item.json_file_urls = {
				"__fabrics":				item.options.baseurl_json + 'fabrics.json'
			,	"__grade_fabric_mapping":	item.options.baseurl_json + item.sku + '-VIG/grade_fabrics_mapping.json'
			,	"leg_finish":				item.options.baseurl_json + 'leg_colors.json'
			,	"nailhead_finish":			item.options.baseurl_json + 'nailhead_colors.json'
			,	"leg_caster_finish":		item.options.baseurl_json + 'leg_caster_color_combinations.json'
	  };

	  $.each(item.required_json_files, function(i, key){
		var file_url  = item.json_file_urls[key];
	    var clean_key = key.replace(/^[_]{2}?(.*)$/, '$1');
		item.json_files[clean_key] = file_url;
	  });

	  var txt_labels = [];
	  $.each(item.json_file_urls, function(label_txt){
		txt_labels[txt_labels.length] = label_txt;
	  });
	  $.each(item.form_field_aliases, function(alias, label_txt){
		txt_labels[txt_labels.length] = label_txt;
	  });
	  $.each(item.options.additional_formlabel_images, function(i, label_txt){
		txt_labels[txt_labels.length] = label_txt;
	  });

	  $('#' + item.form_id + ' select').each(function(){
		var select_id		= $(this).attr('name');
		var label_id		= select_id + '_fs_lbl';
		var label_element	= $('#' + item.form_id).find('#' + label_id);
		var label_txt		= $.trim( label_element.find('> a:first').text().toLowerCase() ).replace(/[^a-zA-Z0-9]/g, '_');

		if ($.inArray(label_txt, txt_labels) != -1) {
			item.form_fields[label_txt] = select_id;

			if ($.inArray(label_txt, item.hidden_form_fields) != -1) {
				// hide form field elements that the upholstery customization tool will manage via a special GUI
				label_element.closest('tr').hide();
			}
			else {
				// swap form field text labels for images
				if (item.options.show_image_labels) {
					label_element.html('<img src="' + item.options.baseurl_formlabel_images + 'form_field_labels/' + label_txt + '.gif" />');
				}
			}

			// true even when field is hidden
			if (label_txt == item.form_field_aliases.piping_fabric) {
				item.has_piping = true;
			}

			if (!$.empty(item.json_file_urls[label_txt])) {
				// changes to the form field will update the main product image.
				// add a hook into the customization tool to enable this behavior.
				item.additional_json_files[label_txt] = item.json_file_urls[label_txt];
			}
		}
	  });

	  if (item.has_swatches && !item.has_piping) {
		var swatch_container = item.options.callback_function.get_swatch_container(item);
		$('.piping', swatch_container).remove();
	  }

	  $('#' + item.sku).trigger('post__inspect_DOM', [item]);
	};

	// -------------------
	// ready function
	// --------------------------------------------------------------------------------------------
	// pre-condition: called after the DOM is ready

	this.ready = function(){
		if (!$.empty(item.form_fields)) {
			$('#' + item.sku).trigger('pre__json_requests', [item]);

			item.merge_additional_fields();
			item.configure_initial_upholstery_options();

			item.json_file_count = $.keys(item.json_files).length;

			$.each(item.json_files, function(key){
				item.load_json(key);
			});
		}
	};

	// --------------------------------------------------------------------------------------------
	// this is a little legacy, based on the assumption that the data structures would be statically defined as generated by the server response.
	// since the data is scraped on the client side, the strategy used may seem unnecessary.
	// but it works well.. and may be useful down the road when we gain more control over our data.

	this.merge_additional_fields = function() {
	  if (!$.empty(item.additional_json_files)) {
		$.extend(item.json_files, item.additional_json_files);
		item.additional_customized_fields = $.copy_keys(item.additional_json_files);
	  }
	};

	// --------------------------------------------------------------------------------------------

	this.load_json = function(key) {
		if (typeof common_json_files.data[key]						!= 'undefined') {
			item.load_json_callback(key, common_json_files.data[key]);
		}
		else if (typeof common_json_files.pending_requests[key]		!= 'undefined') {
			common_json_files.pending_requests[key].push(item);
		}
		else {
			if ($.inArray(key, common_json_files.all) >= 0) {
				common_json_files.pending_requests[key] = [];
			}
			var url = item.json_files[key];
			$.getJSON(url, function(data) {
				if (typeof common_json_files.pending_requests[key]	!= 'undefined') {
					common_json_files.data[key] = data;
					$.each(common_json_files.pending_requests[key], function(i, callback_obj){
						callback_obj.load_json(key);
					});
					delete common_json_files.pending_requests[key];
					item.load_json(key);
				}
				else {
					item.load_json_callback(key, data);
				}
			});
		}
	};

	// --------------------------------------------------------------------------------------------

	this.load_json_callback = function(key, data) {
		item.json_data[key] = data;
		item.json_reply_count++;
		if (item.json_reply_count == item.json_file_count) {
			$('#' + item.sku).trigger('post__json_requests', [item]);
			item.bootstrap_upholstery_tool();
		}
	};

	// --------------------------------------------------------------------------------------------
	// dependency: jquery.parsequery.js

	this.passes_querystring_filter = function() {
		if (typeof item.qs_data.filtered === 'undefined') {
			if (item.options.apply_querystring_filter == false) {
				item.qs_data.filtered = item.qs_data.global;
				return true;
			}
			else {
				item.qs_data.filtered = {};
			}
		}
		else {
			return true;
		}

		var skus;
		switch (typeof item.qs_data.global.sku){
			case 'string':
				skus = [item.qs_data.global.sku];
				break;
			case 'object':
				skus = item.qs_data.global.sku;
				break;
			case 'undefined':
			default:
				skus = [];
				break;
		}
		var sku_index			= $.inArray(item.sku, skus);
		var passes_filter		= (sku_index !== -1);
		var intended_sku_regexp	= /^([^&]+)(&|$)/;
		var chunks, intended_sku;

		if (passes_filter){
			chunks			= top.location.search.split(/(?:^|[?&])sku=/);
			$.each(chunks, function(i, chunk){
				intended_sku = intended_sku_regexp.exec(chunk);
				if (intended_sku !== null){
					intended_sku = intended_sku[1];
					if (intended_sku === item.sku){
						item.qs_data.filtered = $.parseQuery('sku=' + chunk);
						return false;
					}
				}
			});
		}

		return passes_filter;
	};

	// --------------------------------------------------------------------------------------------

	this.determine_initial_upholstery_fabrics = function() {
		var qs_value_main_fabric;
		var qs_value_piping_fabric;
		var passes_filter = item.passes_querystring_filter();
		if (passes_filter) {
			qs_value_main_fabric	= item.qs_data.filtered[ item.qs_parameters.main_fabric ];
			qs_value_piping_fabric	= item.qs_data.filtered[ item.qs_parameters.piping_fabric ];
		}
		var trigger = item.trigger_event_handler_for_querystring_field_values();
		var fabrics = {
			"main_fabric":		{
				"sku":				(!$.empty(qs_value_main_fabric))?   qs_value_main_fabric   : item.default_values.main_fabric
			,	"trigger_event":	(!$.empty(qs_value_main_fabric)   && trigger)
			}
		,	"piping_fabric":	{
				"sku":				(!$.empty(qs_value_piping_fabric))? qs_value_piping_fabric : item.default_values.piping_fabric
			,	"trigger_event":	(!$.empty(qs_value_piping_fabric) && trigger)
			}
		};
		return fabrics;
	};

	// --------------------------------------------------------------------------------------------

	this.configure_initial_upholstery_options = function() {
		// ========================================================================================
		// optional dropdown select fields aren't given a default value.
		// when no value (null) is known for such a field, then no query string parameter is included in the url when the main image is updated.
		// when this is an undesirable behavior, a logical default values can be set.. which is only used in the query string until the user selects a valid value to over-ride the default.
		var keys = ['leg_finish','nailhead_finish','leg_caster_finish'];

		// ========================================================================================
		// first scan the URL of the current page to see if it contains a query string intended to set default value(s):
		var passes_filter = item.passes_querystring_filter();
		if (passes_filter) {
			if (typeof item.additional_customized_fields != 'undefined') {
				var trigger = item.trigger_event_handler_for_querystring_field_values();
				$.each(keys, function(i, key){
					var qs_value = item.qs_data.filtered[ item.qs_parameters[key] ];
					if ( (typeof item.additional_customized_fields[key] != 'undefined') && (item.additional_customized_fields[key] === null) && (!$.empty(qs_value)) ) {
						qs_value = qs_value.replace('+',' ');
						item.additional_customized_fields[key] = qs_value;

						// only in this case do we want to pre-populate the dropdown select field
						var form_field_id = item.form_fields[key];
						var form_field = $('#' + item.form_id).find('#' + form_field_id);
						form_field.find('option').each(function(){
							var description = $(this).text().toLowerCase();
							if (description === qs_value) {
								form_field.find("option:selected").removeAttr('selected');
								$(this).attr('selected', 'selected');
								if (trigger) {
									// note: bootstrap adds an additional "change" event handler to these form fields, but that occurs later.. after all the ajax files have been downloaded.
									//       so there's no harm in triggering the stock NetSuite event handler, which recalculates the product's price based on a "matrix".
									form_field.change();
								}
								return false;
							}
						});
					}
				});
			}
		}

		// ========================================================================================
		// next, over ride any undefined optional dropdown select field values with its default
		if (typeof item.additional_customized_fields != 'undefined') {
			$.each(keys, function(i, key){
				if ( (typeof item.additional_customized_fields[key] != 'undefined') && (item.additional_customized_fields[key] === null) && (typeof item.default_values[key] != 'undefined') ) { item.additional_customized_fields[key] = item.default_values[key]; }
			});
		}
	};

	// --------------------------------------------------------------------------------------------

	this.bootstrap_upholstery_tool = function() {
		$('#' + item.sku).trigger('pre__bootstrap', [item]);

		var fabrics			= item.json_data.fabrics;
		var fabric_colors	= item.determine_initial_upholstery_fabrics();
		var fabric_sku		= fabric_colors.main_fabric.sku;
		var piping_sku		= fabric_colors.piping_fabric.sku;

		var select_tag, selected_main_fabric_option, selected_piping_fabric_option, selected_main_fabric_grade_category;

		item.initialize_fabrics();

		item.gsMainSKU = fabric_sku;
		item.giMainRes = fabrics[fabric_sku].resolution;

		select_tag = $('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.main_fabric] + '"]');
		selected_main_fabric_option = select_tag.find('option[sku="'  + item.gsMainSKU   + '"]');
		selected_main_fabric_option.attr('selected', 'selected');
		if (fabric_colors.main_fabric.trigger_event) {
			select_tag.change();
		}

		if (item.has_piping) {
			selected_main_fabric_grade_category	= selected_main_fabric_option.attr('grade_category');
			if (
				(selected_main_fabric_grade_category == 'piping_1-to-1') ||
				(
					(selected_main_fabric_grade_category == 'piping_1-to-many') &&
					($.empty(piping_sku))
				)
			) {
				piping_sku = fabric_sku;
			}
			item.gsPipingSKU = piping_sku;
			item.giPipingRes = fabrics[piping_sku].resolution;

			select_tag = $('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.piping_fabric] + '"]');
			if (
				(selected_main_fabric_grade_category == 'piping_1-to-1') ||
				(
					(selected_main_fabric_grade_category == 'piping_1-to-many') &&
					(piping_sku == fabric_sku)
				)
			) {
				var fabric_grades = item.json_data.grade_fabric_mapping;
				var magic_bullet  = fabric_grades.options['piping_1-to-1_option_value'];
				selected_piping_fabric_option = select_tag.find('option[value="' + magic_bullet + '"]');
			}
			else { // (selected_main_fabric_grade_category == 'piping_1-to-many') && (piping_sku != fabric_sku)
				selected_piping_fabric_option = select_tag.find('option[sku="' + piping_sku + '"]');
			}
			selected_piping_fabric_option.attr('selected', 'selected');
			if (fabric_colors.piping_fabric.trigger_event) {
				select_tag.change();
			}
		}

		if (item.has_swatches) {
			var swatch_container = item.options.callback_function.get_swatch_container(item);
			$('.fabric_grade_selector a', swatch_container).each(function(){
				$(this).click(function(event){
					event.preventDefault();
					item.change_cover_fabric_grade($(this).attr('value'));
				});
			});

			item.gsMainTitle	= '<span class="fabric_selected_title">' + fabrics[fabric_sku].name + '</span>';
			$('.main .title .fabric_name', swatch_container).html(item.gsMainTitle);

			if (item.can_show_popups){
				item.gsMainTitleBuy	= $('<img src="' + item.options.baseurl_formlabel_images + 'tool_labels/buy_swatch.gif" />');
				item.gsMainTitleBuy
					.css('cursor','pointer')
					.click(function(){
						$.colorbox({
							"inline":		true,
							"href":			'#' + item.sku + ' .fabric_swatches_shopping_popup'
						});
					});
			}
			else {
				item.gsMainTitleBuy	= '<a href="' + fabrics[fabric_sku].link + '"><img src="' + item.options.baseurl_formlabel_images + 'tool_labels/buy_swatch.gif" /></a>';
			}
			$('.buy .main', swatch_container).empty().append(item.gsMainTitleBuy);

			$('.fabric_main_swatches .swatch[sku="'   + item.gsMainSKU   + '"]', swatch_container).addClass('fabric_selected');

			if (item.has_piping) {
				item.gsPipingTitle		= '<span class="fabric_selected_title">' + fabrics[piping_sku].name + '</span>';
				$('.piping .title .fabric_name', swatch_container).html(item.gsPipingTitle);

			//	item.gsPipingTitleBuy	= '<a href="' + fabrics[piping_sku].link + '"><img src="' + item.options.baseurl_formlabel_images + 'tool_labels/buy_swatch.gif" /></a>';
			//	$('.buy .piping', swatch_container).html(item.gsPipingTitleBuy);
			}
		}

		if (!$.empty(item.additional_customized_fields)) {
			$.each(item.additional_customized_fields, function(key, default_description){
				var form_field_id = item.form_fields[key];
				var form_field = $('#' + item.form_id).find('#' + form_field_id);
				var form_field_description = form_field.find("option:selected").text();

				item.additional_customized_fields[key] = $.empty(form_field_description)? default_description : form_field_description.toLowerCase();

				form_field.change(item.trigger_additional_customized_field_update);
			});
		}

		$('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.main_fabric] + '"]').change(function(){ // main fabric
			var selected_main_fabric_option	= $(this).find("option:selected");
			var sku							= selected_main_fabric_option.attr('sku');

			if (
				(!$.empty(sku)) &&
				(sku != item.gsMainSKU)
			) {
				var fabric					= fabrics[sku];

				if (item.has_piping) {
					var selected_main_fabric_grade_category	= selected_main_fabric_option.attr('grade_category');
					if (selected_main_fabric_grade_category == 'piping_1-to-1') {
						var fabric_grades = item.json_data.grade_fabric_mapping;
						var magic_bullet  = fabric_grades.options['piping_1-to-1_option_value'];
						item.gsPipingSKU = fabric.sku;
						item.giPipingRes = fabric.resolution;
						$('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.piping_fabric] + '"]' + ' option[value="' + magic_bullet + '"]').attr('selected', 'selected');
					}
					else if ( ($.inArray(item.form_field_aliases.piping_fabric, item.hidden_form_fields) >= 0) && !item.has_swatches ) {	// grade category of main_fabric is "piping_1-to-many", piping_fabric dropdown is hidden, no swatches available
						item.gsPipingSKU = fabric.sku;
						item.giPipingRes = fabric.resolution;
						$('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.piping_fabric] + '"]' + ' option[sku="' + item.gsPipingSKU + '"]').attr('selected', 'selected');
					}
				}

				if (item.has_swatches) {
					var swatch_container = item.options.callback_function.get_swatch_container(item);

					item.gsMainTitle = fabric.name;
					var lsMainTitle = '<span class="fabric_selected_title">' + item.gsMainTitle + '</span>';
					$('.main .title .fabric_name', swatch_container).html(lsMainTitle);

					if (! item.can_show_popups){
						var lsBuySwatch = fabric.link;
						item.gsMainTitleBuy = '<a href="' + lsBuySwatch + '"><img src="' + item.options.baseurl_formlabel_images + 'tool_labels/buy_swatch.gif" /></a>';
						$('.buy .main', swatch_container).html(item.gsMainTitleBuy);
					}

					$('.fabric_main_swatches .fabric_selected', swatch_container).removeClass('fabric_selected');
					$('.fabric_main_swatches .swatch[sku="' + fabric.sku + '"]', swatch_container).addClass('fabric_selected');
				}

				item.fnChangeImage(fabric.sku, fabric.resolution, '', '');
			}
			else {
				item.update_redirect_field();
			}
		});

		if (item.has_piping) {
			$('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.piping_fabric] + '"]').change(function(){ // piping fabric
				var sku = $(this).find("option:selected").attr('sku');

				if (
					(!$.empty(sku)) &&
					(sku != item.gsPipingSKU)
				) {
					var fabric = fabrics[sku];

					if (item.has_swatches) {
						var swatch_container = item.options.callback_function.get_swatch_container(item);

						item.gsPipingTitle = fabric.name;
						var lsPipingTitle = '<span class="fabric_selected_title">' + item.gsPipingTitle + '</span>';
						$('.piping .title .fabric_name', swatch_container).html(lsPipingTitle);

					//	var lsBuySwatch = fabric.link;
					//	item.gsPipingTitleBuy = '<a href="' + lsBuySwatch + '"><img src="' + item.options.baseurl_formlabel_images + 'tool_labels/buy_swatch.gif" /></a>';
					//	$('.buy .piping', swatch_container).html(item.gsPipingTitleBuy);
					}

					item.fnChangeImage('', '', fabric.sku, fabric.resolution);
				}
				else {
					item.update_redirect_field();
				}
			});
		}

		item.add_redirect_field_to_DOM();

		if (typeof item.options.event_handler.redraw === 'function') {
			$('#' + item.sku).bind('redraw', item.options.event_handler.redraw);
		}
		item.fnChangeImage('','','','');

		$('#' + item.sku).trigger('post__bootstrap', [item]);
		$.event.trigger('custom_upholstery_bootstrap_complete', [item]);
	};

	// --------------------------------------------------------------------------------------------

	this.trigger_additional_customized_field_update = function(event) {
		var key;
		var value = $(this).attr('id');
		$.each(item.form_fields, function(k,v){
			if (v == value) {
				key = k;
				return false;
			}
		});
		if (!$.empty(key)) {
			var form_field_description = $(this).find("option:selected").text();
			var default_value = (typeof item.default_values[key] === 'undefined')? null : item.default_values[key];
			item.additional_customized_fields[key] = $.empty(form_field_description)? default_value : form_field_description.toLowerCase();
			item.fnChangeImage('','','','');
		}
	};

	// --------------------------------------------------------------------------------------------
	// dependency: jquery.printf.js

	this.fnChangeImage = function(psMainSKU,piMainRes,psPipingSKU,piPipingRes) {
		!psMainSKU ? psMainSKU = item.gsMainSKU : item.gsMainSKU = psMainSKU;
		!piMainRes ? piMainRes = item.giMainRes : item.giMainRes = piMainRes;
		!psPipingSKU ? psPipingSKU = item.gsPipingSKU : item.gsPipingSKU = psPipingSKU;
		!piPipingRes ? piPipingRes = item.giPipingRes : item.giPipingRes = piPipingRes;

		var querystring = [];
		querystring.push('obj=main');
		querystring.push('src=' + psMainSKU);
		querystring.push('res=' + piMainRes);
		if (item.has_piping) {
			querystring.push('obj=piping');
			querystring.push('src=' + psPipingSKU);
			querystring.push('res=' + piPipingRes);
		}
		querystring = querystring.join('&');

		var additional_querystring = [];
		if (!$.empty(item.additional_customized_fields)) {
			$.each(item.additional_customized_fields, function(key,description){
				if ( (!$.empty(description)) && (!$.empty(item.json_data[key])) ) {
					var additional_json_data = item.json_data[key];
					var qs_format		= additional_json_data.querystring_parameter;
					var qs_values		= additional_json_data.description_to_querystring_value_mapping[description];

					if ($.isArray(qs_values) && qs_values.length === 1) {
						qs_values		= qs_values[0];
					}
					else if ($.isArray(qs_values) && qs_values.length === 2) {
						qs_format		= qs_values[1];			// allows a particular option to over-ride the querystring format. (Not used often.)
						qs_values		= qs_values[0];
					}
					else {
						return true;							// continue next iteration of loop
					}

					var vsprintf_args	= $.merge([qs_format], qs_values);
					additional_querystring.push($.vsprintf(vsprintf_args));
				}
			});
		}
		additional_querystring = ($.empty(additional_querystring))? '' : '&' + additional_querystring.join('&');

		$('#' + item.sku).trigger('redraw', [item, querystring + additional_querystring]);

		item.update_redirect_field();
	};

	// --------------------------------------------------------------------------------------------

	this.initialize_fabrics = function() {
	    var fabrics				= item.json_data.fabrics;
	    var fabric_grades		= item.json_data.grade_fabric_mapping;
		var fabric_grades_count	= $.count_object_elements(fabric_grades.grades, true);

		$('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.main_fabric] + '"]').empty();
		$('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.main_fabric] + '"]').append('<option value="" selected="selected"></option>');

		if (item.has_piping) {
			$('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.piping_fabric] + '"]').empty();
			$('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.piping_fabric] + '"]').append('<option value="" selected="selected"></option>');
			$('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.piping_fabric] + '"]').append('<option value="' + fabric_grades.options['piping_1-to-1_option_value'] + '">Self Piping</option>');
		}

		if (item.has_swatches) {
			var swatch_container = item.options.callback_function.get_swatch_container(item);
			var grade_count = 0;
			var swatch_count;
			var swatch_count_in_group = 0;
			// width of row containing swatches  = (item.options.swatch_css.width)(x) + (item.options.swatch_css.padding)(x) + (item.options.swatch_css.spacing)(x-1) = wX + pX + sX -s = (w+p+s)X - s
			// width of row containing swatches <= width of div.fabric_main_swatches
			// (w+p+s)X - s <= d
			// X <= (d+s)/(w+p+s)
			// X = floor( (d+s)/(w+p+s) )
			var d = $('.fabric_main_swatches', swatch_container).width();
			var s = item.options.swatch_css.spacing;
			var w = item.options.swatch_css.width;
			var p = item.options.swatch_css.padding;
			var swatch_nth_child_class = Math.floor( (d+s)/(w+p+s) );	// X swatches per row. Remove left margin from every Xn+1 swatch.

			$('.fabric_grade_selector', swatch_container).append('<a href="#" value="">all</a>');
			var fabric_grade_spacer = '<span class="pipe_spacer">|</span>';

			$('.fabric_main_swatches', swatch_container).after(
				'<div style="display:none;"><div class="fabric_swatches_shopping_popup"></div></div>'
			);
		}

		$.each(fabric_grades.grades, function(grade_key, categorized_fabrics_array){
			if (item.has_swatches) {
				swatch_count = 0;
			}
			$.each(categorized_fabrics_array, function(i, categorized_fabrics){
				var grade_category	= categorized_fabrics.category;
				var grade_fabrics	= categorized_fabrics.fabric;

				if (grade_category == 'piping_1-to-many' && item.has_piping) {
					var fabric_count = $.keys(grade_fabrics).length;
					var html_snippet;

					if (fabric_count > 0) {
						$.each(grade_fabrics, function(main_sku, piping_skus){
							var fabric;
							var select_element;

							// apply filters (main_fabric):
							var passes_value_filters = item.passes_form_field_value_filters('main_fabric', main_sku);
							if (!passes_value_filters) {return true;}

							fabric = fabrics[main_sku];
							select_element = $('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.main_fabric] + '"]');
							if (!$.exists( select_element.find('option[sku="' + fabric.sku + '"]') )) {
								html_snippet = '<option grade_category="' + grade_category + '" sku="' + fabric.sku + '" value="' + fabric.fabric_option_value + '">' + fabric.name + '</option>';
								select_element.append(html_snippet);
							}

							if (item.has_piping) {
								$.each(piping_skus, function(i, piping_sku){
									// apply filters (piping_fabric):
									var passes_value_filters = item.passes_form_field_value_filters('piping_fabric', piping_sku);
									if (!passes_value_filters) {return true;}

									fabric = fabrics[piping_sku];
									select_element = $('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.piping_fabric] + '"]');
									if (!$.exists( select_element.find('option[sku="' + fabric.sku + '"]') )) {
										html_snippet = '<option sku="' + fabric.sku + '" value="' + fabric.piping_option_value + '">' + fabric.name + '</option>';
										select_element.append(html_snippet);
									}

									if (item.has_swatches) {
										var swatch_css_class = '';
										swatch_count_in_group++;
										if (swatch_count_in_group == 1) {
											swatch_css_class = ' nth_child';
										}
										if (swatch_count_in_group == swatch_nth_child_class) {
											swatch_count_in_group = 0;
										}

										var swatch_img_url = item.options.callback_function.get_url_swatch_image(item, main_sku, piping_sku);
										var swatch_size = item.options.swatch_css.width;
										html_snippet = '<a href="#" main_sku="' + main_sku + '" piping_sku="' + piping_sku + '" grade_category="' + grade_category + '" grade="' + grade_key + '" class="swatch' + swatch_css_class + '"><img src="' + swatch_img_url + '" style="width:' + swatch_size + 'px; height:' + swatch_size + 'px;" /></a>';
										$('.fabric_main_swatches', swatch_container).append(html_snippet);
										swatch_count++;
									}
								});
							}
						});
					}
				}

				if (grade_category == 'piping_1-to-1') {
					var fabric_count = grade_fabrics.length;
					var html_snippet;

					if (fabric_count > 0) {
						$.each(grade_fabrics, function(i, main_sku){
							var fabric = fabrics[main_sku];

							// apply filters (main_fabric only):
							var passes_value_filters = item.passes_form_field_value_filters('main_fabric', main_sku);
							if (!passes_value_filters) {return true;}

							html_snippet = '<option grade_category="' + grade_category + '" sku="' + fabric.sku + '" value="' + fabric.fabric_option_value + '">' + fabric.name + '</option>';
							$('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.main_fabric] + '"]').append(html_snippet);

							if (item.has_piping) {
								html_snippet = '<option sku="' + fabric.sku + '" value="' + fabric.piping_option_value + '">' + fabric.name + '</option>';
								$('#' + item.form_id + ' [name="' + item.form_fields[item.form_field_aliases.piping_fabric] + '"]').append(html_snippet);
							}

							if (item.has_swatches) {
								var swatch_css_class = '';
								swatch_count_in_group++;
								if (swatch_count_in_group == 1) {
									swatch_css_class = ' nth_child';
								}
								if (swatch_count_in_group == swatch_nth_child_class) {
									swatch_count_in_group = 0;
								}

								var swatch_img_url = item.options.callback_function.get_url_swatch_image(item, main_sku);
								var swatch_size = item.options.swatch_css.width;
								html_snippet = '<a href="#" main_sku="' + fabric.sku + (item.has_piping? ('" piping_sku="' + fabric.sku) : '') + '" grade_category="' + grade_category + '" grade="' + grade_key + '" class="swatch' + swatch_css_class + '"><img src="' + swatch_img_url + '"  alt="' + fabric.name + '" style="width:' + swatch_size + 'px; height:' + swatch_size + 'px;" /></a>';
								$('.fabric_main_swatches', swatch_container).append(html_snippet);
								swatch_count++;
							}
						});
					}
				}

			});
			if (item.has_swatches && (swatch_count > 0)) {
				html_snippet = fabric_grade_spacer + '<a href="" value="' + grade_key + '">' + grade_key + '</a>';
				$('.fabric_grade_selector', swatch_container).append(html_snippet);
				grade_count++;

				if (swatch_count >= item.options.swatches_required_for_grade_separation ) {
					if (grade_count < fabric_grades_count) {
						html_snippet = '<div class="swatch_spacer">&nbsp;</div>';
						$('.fabric_main_swatches', swatch_container).append(html_snippet);
					}
					swatch_count_in_group = 0;
				}
			}
		});
		if (item.has_swatches) {
			if (grade_count <= 1) {
				$('.fabric_grade_container', swatch_container).addClass('hidden');
			}
			$('.fabric_main_swatches .swatch', swatch_container).mouseover(item.main_swatch_mouseover);
			$('.fabric_main_swatches .swatch', swatch_container).mouseout(item.main_swatch_mouseout);
			$('.fabric_main_swatches .swatch', swatch_container).click(item.main_swatch_click);

			$('.fabric_swatches_shopping_popup', swatch_container)
				.append(
					'<div class="fabric_swatches_shopping_popup_heading"></div>' +
					'<div class="fabric_swatches_shopping_popup_header">' +
					'	<div class="fabric_swatches_shopping_popup_header_copy">' +
					'		<img src="/site/custom_upholstery_tool/images/buy_swatches_popup/copy.gif" />' +
					'	</div>' +
					'	<div class="fabric_swatches_shopping_popup_titlebar"></div>' +
					'</div>' +
					'<div class="fabric_swatches_shopping_popup_element_container"></div>' +
					'<div style="clear: both;"></div>' +
					'<div class="fabric_swatches_shopping_popup_footer">' +
					'	<div class="fabric_swatches_shopping_popup_footer_price"></div>' +
					'	<div class="fabric_swatches_shopping_popup_footer_submit"></div>' +
					'</div>'
				);
			var unique_fabric_skus		= {};
			$('.fabric_main_swatches .swatch', swatch_container).each(function(){
				var main_sku					= $(this).attr('main_sku');
				unique_fabric_skus[main_sku]	= true;
			});
			$.each(unique_fabric_skus, function(main_sku){
				var html_element	= $('<div></div>').addClass('fabric_swatches_shopping_popup_element');

			//	var swatch_img_url	= item.options.callback_function.get_url_swatch_image(item, main_sku);
			//	var swatch_size		= item.options.swatch_css.width;
				var swatch_img_url	= item.options.callback_function.get_url_swatch_image(item, main_sku).replace(/\$tileSwatch50\$/, 'wid=75&hei=75&fmt=jpeg&qlt=100&resMode=sharp2&op_usm=1.0,1.0,0,0');
				var swatch_size		= '75px';
				var img_element		= '<img src="' + swatch_img_url + '"  alt="' + fabrics[main_sku].name + '" style="width:' + swatch_size + 'px; height:' + swatch_size + 'px;" />';

				html_element
					.append(
						img_element
					)
					.append(
						$('<input type="checkbox"></input>').attr('sku', main_sku)
					);
				$('.fabric_swatches_shopping_popup_element_container', swatch_container).append(html_element);
			});
			// adjust css width to compensate for vertical scrollbar
			var calculate_scrollbar_width = function(element_to_adjust){
				var el				= $('<div></div>').css('cssText','position:absolute;left:-9999px;top:0px;width:100px;height:100px;overflow:hidden;').text('hello world').appendTo($('body'));
				var w;
				w					= el.width();
				el.css('overflow','scroll');
				w					-= el.width();
				if (!w) w			= el.width() - el[0].clientWidth;	// IE in standards mode
				el.detach().remove();

				if (
					(typeof element_to_adjust === 'object') &&
					(element_to_adjust instanceof jQuery) &&
					(w >= 0)
				){
					var css_width	= parseInt( element_to_adjust.css('width') );
					if (! isNaN(css_width)){
						css_width += w;									// assumes the css definition uses pixel units!
						element_to_adjust.css('width', css_width + 'px');
					}
				}
				return w;
			};
			calculate_scrollbar_width( $('.fabric_swatches_shopping_popup', swatch_container) );
			$('.fabric_swatches_shopping_popup_element', swatch_container).hover(
				function(){
					var titlebar	= $(this).closest('.fabric_swatches_shopping_popup').find('.fabric_swatches_shopping_popup_titlebar');	// #colorbox .fabric_swatches_shopping_popup_titlebar
					var fabric_sku	= $(this).find('input').attr('sku');
					titlebar.text( fabrics[fabric_sku].name );
				},
				function(){
					var titlebar	= $(this).closest('.fabric_swatches_shopping_popup').find('.fabric_swatches_shopping_popup_titlebar');	// #colorbox .fabric_swatches_shopping_popup_titlebar
					titlebar.text('');
				}
			);
			$('.fabric_swatches_shopping_popup_footer_submit', swatch_container).click(function(){
				var checked_swatches = $(this).closest('.fabric_swatches_shopping_popup').find('.fabric_swatches_shopping_popup_element input:checked');
				if (checked_swatches.length > 0){
					var swatch_ids = [];
					$.each(checked_swatches, function(){
						var sku		= $(this).attr('sku');
						var id		= fabrics[sku].fabric_swatch_internal_item_id;
						swatch_ids.push( id + ',1' );
					});
					var form = $('<form method="post" action="/app/site/backend/additemtocart.nl?c=668979&n=1"></form>');
					form
						.append( $('<input></input>').attr('type','hidden').attr('name','c').attr('value','668979') )
						.append( $('<input></input>').attr('type','hidden').attr('name','qtyadd').attr('value','1') )
						.append( $('<input></input>').attr('type','hidden').attr('name','buyid').attr('value','multi') )
						.append( $('<input></input>').attr('type','hidden').attr('name','multi').attr('value', swatch_ids.join(';')) )
						.append( $('<input></input>').attr('type','hidden').attr('name','redirect').attr('value', item.get_redirect_URL()) )
						.hide()
						.appendTo( $('body') )
						.submit();
				}
			});
		}
	};

	// --------------------------------------------------------------------------------------------

	this.change_cover_fabric_grade = function(grade_key) {
		if (item.has_swatches) {
			var swatch_container = item.options.callback_function.get_swatch_container(item);
			if (!$.empty(grade_key)){
				$('.fabric_main_swatches .swatch', swatch_container).hide();
				$('.fabric_main_swatches .swatch_spacer', swatch_container).hide();
				$('.fabric_main_swatches .swatch[grade="' + grade_key + '"]', swatch_container).show();
			}
			else {
				$('.fabric_main_swatches .swatch', swatch_container).show();
				$('.fabric_main_swatches .swatch_spacer', swatch_container).show();
			}
		}
	};

	// --------------------------------------------------------------------------------------------

	this.main_swatch_mouseover = function(){
		if (item.has_swatches) {
			var swatch_container = item.options.callback_function.get_swatch_container(item);
			var fabrics = item.json_data.fabrics;

			$(this).addClass('fabric_hover');

			var main_sku  = $(this).attr('main_sku');
			var main_desc = fabrics[main_sku]['name'];
			$('.main .title .fabric_name', swatch_container).html(main_desc);

			if (item.has_piping) {
				var piping_sku  = $(this).attr('piping_sku');
				var piping_desc = fabrics[piping_sku]['name'];
				$('.piping .title .fabric_name', swatch_container).html(piping_desc);
			}
		}
	};

	// --------------------------------------------------------------------------------------------

	this.main_swatch_mouseout = function(){
		if (item.has_swatches) {
			var swatch_container = item.options.callback_function.get_swatch_container(item);
			var main_desc = '<span class="fabric_selected_title">' + item.gsMainTitle + '</span>';
			$('.main .title .fabric_name', swatch_container).html(main_desc);

			if (item.has_piping) {
				var piping_desc = '<span class="fabric_selected_title">' + item.gsPipingTitle + '</span>';
				$('.piping .title .fabric_name', swatch_container).html(piping_desc);
			}

			$(this).removeClass('fabric_hover');
		}
	};

	// --------------------------------------------------------------------------------------------

	this.main_swatch_click = function(event){
		if (item.has_swatches) {
			var swatch_container = item.options.callback_function.get_swatch_container(item);
			var fabrics = item.json_data.fabrics;

			event.preventDefault();
			var main_sku		= $(this).attr('main_sku');
			var main_fabric		= fabrics[main_sku];

			if (item.has_piping) {
				var piping_sku		= $(this).attr('piping_sku');
				var piping_fabric	= fabrics[piping_sku];
			}
			else {
				var piping_fabric	= {"sku":"", "resolution":""};
			}

			item.gsMainTitle = main_fabric.name;
			var lsSwatchTitle = '<span class="fabric_selected_title">' + item.gsMainTitle + '</span>';
			$('.main .title .fabric_name', swatch_container).html(lsSwatchTitle);

			if (! item.can_show_popups){
				var lsBuySwatch = main_fabric.link;
				item.gsMainTitleBuy = ' <a href="' + lsBuySwatch + '"><img src="' + item.options.baseurl_formlabel_images + 'tool_labels/buy_swatch.gif" /></a>';
				$('.buy .main', swatch_container).html(item.gsMainTitleBuy);
			}

			if (item.has_piping) {
				item.gsPipingTitle = piping_fabric.name;
				var lsSwatchTitle = '<span class="fabric_selected_title">' + item.gsPipingTitle + '</span>';
				$('.piping .title .fabric_name', swatch_container).html(lsSwatchTitle);

			//	var lsBuySwatch = piping_fabric.link;
			//	item.gsPipingTitleBuy = '<a href="' + lsBuySwatch + '"><img src="' + item.options.baseurl_formlabel_images + 'tool_labels/buy_swatch.gif" /></a>';
			//	$('.buy .piping', swatch_container).html(item.gsPipingTitleBuy);
			}

			item.fnChangeImage(main_fabric.sku, main_fabric.resolution, piping_fabric.sku, piping_fabric.resolution);

			$('.fabric_main_swatches .fabric_selected', swatch_container).removeClass('fabric_selected');
			$(this).addClass('fabric_selected');

			$('#' + item.form_id).find('#' + item.form_fields[item.form_field_aliases.main_fabric] + ' option[selected]').removeAttr('selected');
			$('#' + item.form_id).find('#' + item.form_fields[item.form_field_aliases.main_fabric] + ' option[sku="' + main_sku + '"]').attr('selected', 'selected');

			if (item.has_piping) {
				$('#' + item.form_id).find('#' + item.form_fields[item.form_field_aliases.piping_fabric] + ' option[selected]').removeAttr('selected');

				var grade_category = $(this).attr('grade_category');
				if (
					(grade_category == 'piping_1-to-1') ||
					(
						(grade_category == 'piping_1-to-many') &&
						(piping_sku == main_sku)
					)
				) {
					var fabric_grades = item.json_data.grade_fabric_mapping;
					var magic_bullet  = fabric_grades.options['piping_1-to-1_option_value'];
					$('#' + item.form_id).find('#' + item.form_fields[item.form_field_aliases.piping_fabric] + ' option[value="' + magic_bullet + '"]').attr('selected', 'selected');
				}
				else {  // (grade_category == 'piping_1-to-many') && (piping_sku != main_sku)
					$('#' + item.form_id).find('#' + item.form_fields[item.form_field_aliases.piping_fabric] + ' option[sku="' + piping_sku + '"]').attr('selected', 'selected');
				}
			}

			//	-----------------------------------------------
			//	this seems redundant:
			//		as the onchange event handler for the main fabric form field will:
			//			1. update the image (which just occurred)
			//			2. recalculate the product's price (which may vary based upon the selected fabrics)
			//	-----------------------------------------------
			$('#' + item.form_id).find('#' + item.form_fields[item.form_field_aliases.main_fabric]).change();
		}
	};

	// --------------------------------------------------------------------------------------------
	// apply filters (main_fabric):
	//		no filters || ( (no blacklist || not in blacklist) && (no whitelist || in whitelist) )
	this.passes_form_field_value_filters = function(filters_key, test_value) {
		var passes_value_filters = (
			(
				(typeof item.form_field_value_filters === 'undefined') ||
				(typeof item.form_field_value_filters[filters_key] === 'undefined')
			) ||
			(
				(
					(typeof item.form_field_value_filters[filters_key].blacklist === 'undefined') ||
					($.inArray(test_value, item.form_field_value_filters[filters_key].blacklist) === -1)
				) &&
				(
					(typeof item.form_field_value_filters[filters_key].whitelist === 'undefined') ||
					($.inArray(test_value, item.form_field_value_filters[filters_key].whitelist) >= 0)
				)
			)
		);
		return passes_value_filters;
	};

	// --------------------------------------------------------------------------------------------
	// the "add to cart" form (identified by variable: form_id) accepts an optional parameter: redirect
	// which behaves as expected..
	// what we want is to set this value to point back to this page and include a query string that customizes the default value for fabric, piping, and optional fields.
	// it would also be good to add an additional parameter to look for when a page loads, alerting the script to the fact that the user just added an item to their cart.
	// this would allow the script to add a notification into the DOM to acknowledge to the user that the request succeeded.

	this.get_redirect_URL = function() {
		var url = document.URL;
		var qsIndex = url.indexOf('?');
		if (qsIndex >= 0) {
			url = url.substring(0, qsIndex);
		}

		var additional_querystring = [];
		var qs_category_id = item.qs_data.global[ 'category' ];
		if (!$.empty(qs_category_id)) {
			additional_querystring.push('category=' + qs_category_id);
		}
		additional_querystring.push('is_redirect=1');
		additional_querystring.push('sku=' + item.sku);
		$.each(item.form_field_aliases, function(key, label_txt){
			if ( (key === 'piping_fabric') && (!item.has_piping) ) {return true;}

			var field_id	= item.form_fields[label_txt];
			var field_value	= $('#' + item.form_id).find('#' + field_id + ' :selected').attr('sku');
			if ( (typeof field_value === 'string') && (!$.empty(field_value)) ) {
				additional_querystring.push(item.qs_parameters[key] + '=' + field_value);
			}
			else if (key === 'piping_fabric') {
				var fabric_grades = item.json_data.grade_fabric_mapping;
				var magic_bullet  = fabric_grades.options['piping_1-to-1_option_value'];
				field_value	= $('#' + item.form_id).find('#' + field_id + ' :selected').attr('value');
				if (field_value == magic_bullet) {
					// self-piping main fabric
					additional_querystring.push(item.qs_parameters[key] + '=' + item.gsPipingSKU);
				}
			}
		});
		if (!$.empty(item.additional_customized_fields)) {
			$.each(item.additional_customized_fields, function(key,description){
				if (!$.empty(description)) {
					additional_querystring.push(item.qs_parameters[key] + '=' + description);
				}
			});
		}
		additional_querystring = ($.empty(additional_querystring))? '' : '?' + additional_querystring.join('&');

		url += additional_querystring;
		return url;
	};

	this.add_redirect_field_to_DOM = function() {
		$('#' + item.form_id).prepend('<input type="hidden" name="redirect" value="" />');
	};

	this.update_redirect_field = function() {
		$('#' + item.form_id + ' [name="redirect"]').attr('value', item.get_redirect_URL());
	};

	// --------------------------------------------------------------------------------------------

	this.trigger_event_handler_for_querystring_field_values = function() {
		if (typeof item.options.trigger_event_handler_for_querystring_field_values === 'undefined')	{ return false;											}
		if (item.options.trigger_event_handler_for_querystring_field_values === 'never')			{ return false;											}
		if (item.options.trigger_event_handler_for_querystring_field_values === 'always')			{ return true;											}
		if (item.options.trigger_event_handler_for_querystring_field_values === 'redirect_only')	{ return (item.qs_data.global[ 'is_redirect' ] === '1');}
		return false;
	};

	// --------------------------------------------------------------------------------------------
}

// ================================================================================================
// called from a jQuery(document).ready() function:

function bootstrap_custom_upholstery_tool(skus, img_width, popup_width, configs) {
	var $						= jQuery;
	var debug					= (window.location.protocol.toLowerCase() === 'file:');
	var custom_upholstery_tools	= [];
	var default_options = {
		"baseurl_json":					(debug? '' : '/') + "site/custom_upholstery_tool/json/"
	,	"baseurl_formlabel_images":		(debug? '' : '/') + "site/custom_upholstery_tool/images/"
	,	"additional_formlabel_images":	[]
	,	"show_image_labels":			true
	,	"apply_querystring_filter":		(skus.length>1)
	,	"show_flash":					false
	,	"anchor_css_class":				{
			"flash":					"scene7Popup"
		,	"image":					"scene_7_popup_cust"
		}
	,	"callback_function":			{
			"rewrite_url":				function(url){
				return url;
			}
		,	"get_url_image":			function(caller, querystring, no_cache){
				var url;
				url = 'http://s7d4.scene7.com/ir/render/SerenaandLilyRender/' + caller.sku + '-VIG?wid=' + img_width + '&' + querystring + '&resmode=sharp&qlt=95';
				if (no_cache){
					url += '&cache=off';
				}
				url = caller.options.callback_function.rewrite_url(url);
				return url;
			}
		,	"get_url_popup":			function(caller, querystring, no_cache){
				var url;
				if ( (typeof caller.options.show_flash !== 'undefined') && (caller.options.show_flash) ) {
					url = 'http://s7d5.scene7.com/s7/zoom/flasht_zoom.jsp?company=SerenaandLily&sku=' + caller.sku + '&config=SerenaandLily/popupMultipleViews&locale=en&' + querystring;
				}
				else {
					url = 'http://s7d4.scene7.com/ir/render/SerenaandLilyRender/' + caller.sku + '-VIG?wid=' + popup_width + '&' + querystring + '&resmode=sharp&qlt=95';
					if (no_cache){
						url += '&cache=off';
					}
					url = caller.options.callback_function.rewrite_url(url);
				}
				return url;
			}
		,	"get_url_swatch_image":		function(caller, fabric_sku, piping_sku){
				var swatch_size_class = '$tileSwatch50$';
				if (
					(typeof piping_sku === 'undefined') ||
					(
						(piping_sku === fabric_sku) && (!caller.options.display_self_piping_swatches)
					)
				) {
					return 'http://s7d5.scene7.com/is/image/SerenaandLily/' + fabric_sku + '?' + swatch_size_class;
				}
				else {
					return 'http://s7d5.scene7.com/is/image/SerenaandLily/fabric_piping_swatch?' + swatch_size_class + '&$base_fabric=SerenaandLily/' + fabric_sku + '&$piping_fabric=SerenaandLily/' + piping_sku + '&$shadow_opacity=40';
				}
			}
		,	"get_swatch_container":		function(caller){
				var swatch_container_selector = '#' + caller.sku;
				return $(swatch_container_selector);
			}
		}
	,	"event_handler":				{
			"redraw":					function(event, caller, querystring){
				event.stopPropagation();

				var url_image, url_popup, css_class;
				url_image = caller.options.callback_function.get_url_image(caller, querystring);

				if (
					(window.is_widescreen	=== false)							&&
					(window.is_itemlist		=== false)							&&
					(typeof window.flyout_image_viewer__object === 'object')	&&
					(window.flyout_image_viewer__object.initialization_complete)
				){
					window.flyout_image_viewer__object.update_vignette_URL(url_image, true);
				}
				else {
					url_popup = caller.options.callback_function.get_url_popup(caller, querystring);
					css_class = (caller.options.show_flash)? caller.options.anchor_css_class.flash : caller.options.anchor_css_class.image;

					$('#' + caller.sku + ' a.' + css_class + ':first img').each(	function(){  $(this).attr('src',  url_image);  });
					$('#' + caller.sku +  ' .' + css_class).each(					function(){  $(this).attr('href', url_popup);  });
				}
			}
		}
	,	"swatch_css":					{
			"width":					50
		,	"padding":					4
		,	"spacing":					9
		}
	,	"display_self_piping_swatches":							false
	,	"swatches_required_for_grade_separation":				2
	,	"trigger_event_handler_for_querystring_field_values":	"redirect_only"				// accepted values: "always", "never", "redirect_only"
	};
	var options = $.extend(true, {}, default_options, configs);

	// opportunity to modify options globally
	if (typeof window['upholstery_customizer_hook___pre_object_instantiation__global'] == 'function')	{ window['upholstery_customizer_hook___pre_object_instantiation__global'](options); }
	$.each(skus, function(i, sku){
		// after the default hook function "upholstery_customizer_hook___pre_DOM_inspection__item" inspects the DOM,
		// in IE:		the <form> will be found at: $('#' + sku).closest('form')
		// in non-IE:	the <form> will be found at: $('#' + sku).find('form:first')
		var form_id = $('#' + sku).closest('tr').find('form:first').attr('id');
		// each instance gets a copy of options, which allows non-global hook functions to modify each individually.
		custom_upholstery_tools[custom_upholstery_tools.length] = new custom_upholstery_tool(sku, form_id, $.extend(true, {}, options));
	});

	// attach global event handler:
	var bootstraps_remaining = custom_upholstery_tools.length;
	$(document).bind('custom_upholstery_bootstrap_complete', function(event, tool_instance){
		bootstraps_remaining--;
		if (
			(bootstraps_remaining === 0) &&
			(typeof window['upholstery_customizer_hook___post_bootstrap_loader__global'] == 'function')
		)																								{ window['upholstery_customizer_hook___post_bootstrap_loader__global'](); }
	});

	// attach per-instance event handlers:
	$.each(custom_upholstery_tools, function(i){
		var sku = custom_upholstery_tools[i].sku;

		if (typeof window['upholstery_customizer_hook___pre_DOM_inspection__item'] == 'function'){
			$('#' + sku).bind('pre__inspect_DOM', function(event, caller){
				event.stopPropagation();
				window['upholstery_customizer_hook___pre_DOM_inspection__item'](caller);
			});
		}
		if (typeof window['upholstery_customizer_hook___post_DOM_inspection__item'] == 'function'){
			$('#' + sku).bind('post__inspect_DOM', function(event, caller){
				event.stopPropagation();
				window['upholstery_customizer_hook___post_DOM_inspection__item'](caller);
			});
		}
		if (typeof window['upholstery_customizer_hook___pre_JSON_downloads__item'] == 'function'){
			$('#' + sku).bind('pre__json_requests', function(event, caller){
				event.stopPropagation();
				window['upholstery_customizer_hook___pre_JSON_downloads__item'](caller);
			});
		}
		if (typeof window['upholstery_customizer_hook___post_JSON_downloads__item'] == 'function'){
			$('#' + sku).bind('post__json_requests', function(event, caller){
				event.stopPropagation();
				window['upholstery_customizer_hook___post_JSON_downloads__item'](caller);
			});
		}
		if (typeof window['upholstery_customizer_hook___pre_bootstrap_loader__item'] == 'function'){
			$('#' + sku).bind('pre__bootstrap', function(event, caller){
				event.stopPropagation();
				window['upholstery_customizer_hook___pre_bootstrap_loader__item'](caller);
			});
		}
		if (typeof window['upholstery_customizer_hook___post_bootstrap_loader__item'] == 'function'){
			$('#' + sku).bind('post__bootstrap', function(event, caller){
				event.stopPropagation();
				window['upholstery_customizer_hook___post_bootstrap_loader__item'](caller);
			});
		}
	});

	// step 1: DOM inspection
	if (typeof window['upholstery_customizer_hook___pre_DOM_inspection__global'] == 'function')			{ window['upholstery_customizer_hook___pre_DOM_inspection__global'](); }
	$.each(custom_upholstery_tools, function(i){
		custom_upholstery_tools[i].inspect_DOM();
	});

	// step 2: bootstrapping => json downloads, swatch creation, event wiring
	if (typeof window['upholstery_customizer_hook___pre_bootstrap_loader__global'] == 'function')		{ window['upholstery_customizer_hook___pre_bootstrap_loader__global'](); }
	$.each(custom_upholstery_tools, function(i){
		custom_upholstery_tools[i].ready();
	});
}

// ================================================================================================

