var ComparatorConfiguration = function(config,specGroups){
	this.config = config;
	this.specGroups = specGroups;
	this.dirty = false;
};

//constants
ComparatorConfiguration.SORT_ASCENDING = "asc";
ComparatorConfiguration.SORT_DESCENDING = "dsc";

ComparatorConfiguration.prototype = {

	//data retrieval

	getProducts : function() {
		return this.config.products;
	},

	isProductPinned : function(slug) {
		return this.config.pinnedProducts.indexOf(slug) !== -1;
	},

	getSort : function() {
		return this.config.sort;
	},

	getKeySpecs : function() {
		return this.config.keySpecs;
	},

	getShownSpecGroups : function() {
		return this.config.shownSpecGroups;
	},

	getRefinementQuestions : function() {
		return this.config.refinementQuestions;
	},

	isSpecGroupShown : function(slug) {

		for(i in this.config.shownSpecGroups)
			if(slug === this.config.shownSpecGroups[i])
				return true;
		return false;
	},

	specGroupHasData : function(slug, comparatorProducts) {

		if(!(comparatorProducts instanceof ComparatorProducts)) {
			if(typeof console != 'undefined') console.error('argument not instance of ComparatorProducts');
			return;
		}

		var specs = this.specGroups[slug].defaultOrder;
		for(i in specs) {
			for(j in this.config.products) {
				var product = comparatorProducts.getProduct(this.config.products[j]);
				var spec = product.specs[specs[i]];
				if(product != null && spec != null && typeof spec != 'undefined' && spec.comparableValue != '' && spec.displayValue != '') {
					return true;
				}
			}
		}

		return false;
	},

	//actions

	addKeySpec : function(slug) {
		if(this.config.keySpecs.indexOf(slug) === -1)
			this.config.keySpecs.push(slug);

		this.dirty = true;
	},

	removeKeySpec : function(slug) {
		var i = this.config.keySpecs.indexOf(slug);
		if(i !== -1)
			this.config.keySpecs.splice(i,1);

		this.dirty = true;
	},

	reorderKeySpec : function(slug,pos) { //pos is 1-based

		if(pos > this.config.keySpecs.length) {
			if(typeof console != 'undefined') console.error('key spec position out of bounds');
			return;
		}

		if(pos === 1) {
			this.removeKeySpec(slug);
			this.config.keySpecs.unshift(slug);
		} else if (pos === this.config.keySpecs.length) {
			this.removeKeySpec(slug);
			this.config.keySpecs.push(slug);
		} else {
			var i = this.config.keySpecs.indexOf(slug);
			if(i+1 === pos) { //same position as current, no change
				if(typeof console != 'undefined') console.warn('same position as current');
				return;
			}
			this.config.keySpecs.splice(i,1);
			var left = this.config.keySpecs.slice(0,pos-1);
			var right = this.config.keySpecs.slice(pos-1);
			this.config.keySpecs = Array.concat(left,slug,right);
		}

		this.dirty = true;
	},

	showSpecGroup : function(slug) {
		if(this.config.shownSpecGroups.indexOf(slug) === -1)
			this.config.shownSpecGroups.push(slug);

		this.dirty = true;
	},

	hideSpecGroup : function(slug) {
		var i = this.config.shownSpecGroups.indexOf(slug);
		if(i !== -1)
			this.config.shownSpecGroups.splice(i,1);

		this.dirty = true;
	},

	showAllSpecGroups : function() {

		for(i in this.specGroups) {
			if(this.config.shownSpecGroups.indexOf(this.specGroups[i].slug) === -1)
				this.config.shownSpecGroups.push(this.specGroups[i].slug);
		}

		this.dirty = true;
	},

	// slug - spec slug
	// comparatorProducts - instance of ComparatorProducts
	sortBySpec : function(slug,comparatorProducts,direction) {

		if(!(comparatorProducts instanceof ComparatorProducts)) {
			if(typeof console != 'undefined') console.error('argument not instance of ComparatorProducts');
			return;
		}

		if(this.config.sort && this.config.sort.spec === slug) {
			if(this.config.sort.direction === ComparatorConfiguration.SORT_ASCENDING)
				this.config.sort.direction = ComparatorConfiguration.SORT_DESCENDING;
			else
				this.config.sort.direction = ComparatorConfiguration.SORT_ASCENDING;
		} else {
			this.config.sort = {
				spec : slug,
				direction : ComparatorConfiguration.SORT_ASCENDING
			};
		}

		if(direction != null)
			this.config.sort.direction = direction;

		this.config.products = comparatorProducts.sortProducts(this.config.products,this.config.sort);

		this.dirty = true;
	},

	pinProduct : function(slug) {
		if(this.config.pinnedProducts.indexOf(slug) === -1)
			this.config.pinnedProducts.push(slug);

		this.dirty = true;
	},

	unpinProduct : function(slug) {
		var i = this.config.pinnedProducts.indexOf(slug);
		if(i !== -1)
			this.config.pinnedProducts.splice(i,1);

		this.dirty = true;
	},

	addProduct : function(slug) {
		if(this.config.products.indexOf(slug) === -1)
			this.config.products.push(slug);

		this.dirty = true;
	},

	removeProduct : function(slug) {
		var i = this.config.products.indexOf(slug);
		if(i !== -1)
			this.config.products.splice(i,1);

		this.dirty = true;
	},

	//misc
	getInputsArray : function(input) {	 //	returns an array of inputs (see tag manager)

		if(input === null)
			input = "ComparatorConfig";

		var inputs = {};

		$.each(this.config.products, function(i,o) {
			inputs[input+'[Products]['+i+']'] = o;
		});

		$.each(this.config.pinnedProducts, function(i,o) {
			inputs[input+'[PinnedProducts]['+i+']'] = o;
		});

		if(this.config.sort) {
			inputs[input+'[Sort][Spec]'] = this.config.sort.spec;
			inputs[input+'[Sort][Direction]'] = this.config.sort.direction;
		}

		$.each(this.config.keySpecs, function(i,o) {
			inputs[input+'[KeySpecs]['+i+']'] = o;
		});

		$.each(this.config.shownSpecGroups, function(i,o) {
			inputs[input+'[ShownSpecGroups]['+i+']'] = o;
		});

		$.each(this.config.refinementQuestions, function(i,o) {
			inputs[input+'[RefinementQuestions]['+i+'][Slug]'] = o.slug;
			$.each(o.answers,function(j,a) {
				inputs[input+'[RefinementQuestions]['+i+'][Answers]['+j+']'] = a;
			});
		});

		return inputs;
	},

	isDirty : function() {
		return this.dirty;
	},

	//this should be called when the config is persisted
	resetIsDirty : function() {
		this.dirty = false;
	},

	debug : function() {
		if(typeof console != 'undefined') console.info(this.config);
	}
};

var ComparatorProducts = function(specs,productType) {

	this.productType = productType;
	this.specs = specs;
	var me = this;

/*	//convert list into map
	$.each(specs,function(i,o) {
		me.specs[o.slug] = o;
	});*/
};

ComparatorProducts.prototype = {

	getProduct : function(productSlug) {

		//get from internal array
		var product = this.specs[productSlug];

		if(product != null) return product;

		//not found, make ajax call to get product spec
		if(false) {
			$.ajax({
				url:'/comparator-specs/'+this.productType+'/json/',
				typ:'get',
				async:false,
				data:{products:productSlug},
				dataType:'json',
				success:function(me) {
					return function(data) {
						me.specs[productSlug] = data;
					};
				}(this),
				error:function(xhr) {
					if(typeof console != 'undefined') console.error(xhr);
				},
				complete:function() {
				}
			});

			product = this.specs[productSlug];

			if(product != null) return product;
		}

		if(typeof console != 'undefined') console.warn("Product '"+productSlug+"' not found in productSpecs list. Please re-check comparator configuration and product profiles.");
		$("#please-wait > p").html("The comparator could not be loaded.");
	},

	getSpecName : function(specSlug) {

		for(productSlug in this.specs) {
			var spec = this.getSpec(productSlug,specSlug);
			if(spec !== null && typeof spec !== 'undefined')
				return spec.name;
		}
		return null;
	},

	isSpecSortable : function(specSlug) {
		for(productSlug in this.specs) {
			var spec = this.getSpec(productSlug,specSlug);
			if(spec !== null && typeof spec !== 'undefined')
				return spec.isSortable;
		}
		return false;
	},

	getSpec : function(productSlug,specSlug) {
		var product = this.getProduct(productSlug);
		return product.specs[specSlug];
	},

	// products - list of slugs
	// sort - object from comparator config
	sortProducts : function(products,sort) {

		var me = this;

		return products.sort(function(a,b) {

			var specA = me.getSpec((sort.direction==ComparatorConfiguration.SORT_ASCENDING?a:b),sort.spec);
			var specB = me.getSpec((sort.direction==ComparatorConfiguration.SORT_ASCENDING?b:a),sort.spec);

			if(typeof specA == 'undefined' && typeof specB == 'undefined')
				return 0;
			else if(typeof specA == 'undefined')
				return -1;
			else if(typeof specB == 'undefined')
				return 1;

			if(specA.dataType !== specB.dataType)
				return 0;

			var valA = specA.comparableValue;
			var valB = specB.comparableValue;

			if(specA.dataType === "numeric") {
				valA = parseFloat(specA.comparableValue);
				valB = parseFloat(specB.comparableValue);
			}
			else if(specA.dataType === "boolean") {
				valA = parseInt(specA.comparableValue);
				valB = parseInt(specB.comparableValue);
			}

			if(valA < valB)
				return -1;
			else if(valA > valB)
				return 1;

			return 0;
		});
	}


};

var Comparator = function(comparatorConfig,comparatorProducts,specGroups,maxProducts,maxVisible,useBusyIndicator,navHeight,navBackgroundOffset,floatingHeaderOffset,floatingHeaderOffsetTrigger) {

	this.comparatorConfig = comparatorConfig;
	this.comparatorProducts = comparatorProducts;
	this.specGroups = specGroups;
	this.maxProducts = maxProducts;
	this.maxVisible = maxVisible;
	this.firstVisible = 0;
	this.sortingEnabled = true;
	this.compensationAnchor = null;
	this.compensationAnchorTop = 0;
    this.useBusyIndicator = useBusyIndicator;
    this.navHeight = navHeight;
    this.navBackgroundOffset = navBackgroundOffset;
	this.floatingHeaderOffset = floatingHeaderOffset;
	this.floatingHeaderOffsetTrigger = floatingHeaderOffsetTrigger;

	var me = this;

	//floating header
	this.floatingHeader = $('#floating-header');

	this._buildTable();	//hide products not in the horizontal scroll viewport
	this._initHiddenProducts();
	this._hideBusy(true);

	$(window).load(function() {
		$(window).bind('scroll',function(event){
			me.floatHeader(event);
		});
		me.floatHeader();
	});

	//attach events
	$('a.add-spec-group').bind('click',function(event) {
		event.preventDefault();
		me._showBusy();
		setTimeout(function(){me.addSpecGroup(event);},1);
	});

	$('tr.spec-group > td > a').bind('click',function(event) {
		event.preventDefault();
		me._showBusy();
		setTimeout(function(){me.removeSpecGroup(event);},1);
	});

	$('#scroll-left').add('#scroll-left-float').bind('click',function(event) {
		event.preventDefault();
		me._showBusy();
		setTimeout(function(){me.scrollLeft(event);},1);
	});

	$('#scroll-right').add('#scroll-right-float').bind('click',function(event) {
		event.preventDefault();
		me._showBusy();
		setTimeout(function(){me.scrollRight(event);},1);
	});

	$('th.spec-label > a').bind('click',function(event) {
		event.preventDefault();
		if(!me.sortingEnabled)
			return;
		me._showBusy();
		setTimeout(function(){me.sortBySpec(event);},1);
	});


	this._initialSort();

	//the floating header will appear after scrolling past this object:
	this.cutoffBeginRef = $('tr.spec-row:eq(0)');
	//the floating header will disappear after scrolling past this object:
	this.cutoffEndRef = $('tr.add-spec-group');


	//currently doesn't detect zooming
	this.savedZoomWidth = $('#comparator').outerWidth();
	setTimeout(function(){me._zoomCompensate(me);},1000);


	//remove temporary loading message
	$("#please-wait").remove();
};

Comparator.prototype = {

	_buildTable : function() {
		var start = new Date();

		var comparatorDiv = $('#comparator');
		var table = $("<table>")
			.attr('cellspacing',0)
			.attr('cellpadding',0)
			.attr('id','comparator-table');
		tbody = $("<tbody>");
		table.append(tbody);

		var products = this.comparatorConfig.getProducts();
		var tr;
		var me = this;

		//build navigation row
		tr = $("<tr>")
			.attr('id','comparator-navigation')
			.append(
				$("<th>")
					.addClass('pivot')
					.html("&nbsp;")
			)
			.append(
				$("<td colspan='"+this.maxVisible+"' class='navigation'></td>")
					.append(
						$("<p>").html("Showing 1&ndash;"+this.maxVisible+" of "+products.length)
					)
					.prepend(
						$("<div>")
							.addClass('nav-container')
							.append(
								$("<a class='scroll-link' href='#' id='scroll-left' title='Scroll Left' style='display:none'>Scroll Left</a>")
							)
							.append(
								$("<div>")
									.attr('id','nav-product-icons')
							)
							.append(
								$("<a class='scroll-link' href='#' id='scroll-right' title='Scroll Right'>Scroll Right</a>")
							)
					)
			);
		var productIcons = $('#nav-product-icons',tr);
		for(i in products) {
			var product = this.comparatorProducts.getProduct(products[i]);
			if(product != null) {
				productIcons
					.append(
						$("<a href='#' title='Jump to "+product.name+"' class='product-icon jump-pos-"+i+"'>Jump to "+product.name+"</a>")
							.bind('click',function(event) {
								event.preventDefault();
								me._showBusy();
								setTimeout(function(){me.jumpToProduct(event);},1);
							})
					);
			}
		}
		tbody.append(tr);


		//build floating navgiation bar
		var floatnav = $("<div>").addClass('navigation');
		this.floatingHeader.append(
			$("<div>").addClass('navigation-container').append(floatnav)
		);
		floatnav
			.append(
				$("<p>").html("Showing 1&ndash;"+this.maxVisible+" of "+products.length)
			)
			.prepend(
				$("<div>")
					.addClass('nav-container')
					.append(
						$("<a class='scroll-link' href='#' id='scroll-left-float' title='Scroll Left' style='display:none'>Scroll Left</a>")
					)
					.append(
						$("<div>")
							.attr('id','nav-product-icons-float')
					)
					.append(
						$("<a class='scroll-link' href='#' id='scroll-right-float' title='Scroll Right'>Scroll Right</a>")
					)
			);
		productIcons = $('#nav-product-icons-float',this.floatingHeader);
		for(i in products) {
			var product = this.comparatorProducts.getProduct(products[i]);
			productIcons
				.append(
					$("<a href='#' title='Jump to "+product.name+"' class='product-icon jump-pos-"+i+"'>Jump to "+product.name+"</a>")
						.bind('click',function(event) {
							event.preventDefault();
							me._showBusy();
							setTimeout(function(){me.jumpToProduct(event);},1);
						})
				);
		}



		var ul = $("<ul>");
		this.floatingHeader.append(ul);

		//build image row
		tr = $("<tr>")
			.attr('id','comparator-products')
			.append(
				$("<th class='pivot'><div class='busy'>&nbsp;</div></th>")
			);
		ul.append(
			$("<li>")
				.addClass('pivot')
				.append(
					$("<div class='busy'>&nbsp;</div>")
				)
		);

		columnWidth = ($.browser.mozilla === true && $.browser.version.substr(0,4) === '1.8.' ? "37" : "40");

		for(i in products) {
			var productSlug = products[i];
			var product = this.comparatorProducts.getProduct(productSlug);
			tr.append(
				$("<td valign='top' class='image col_"+i+" prod_"+productSlug+"' "+(products.length==2?"width=\""+columnWidth+"%\"":"")+"></td>")
					.append(
						$("<div>")
							.addClass('product-image')
							.append(
								$("<div>")
									.append(
										(product.image!=""?$("<a href='"+product.url+"' title='"+product.name+"' style='background-image:url("+product.image+")'><img border='0' src='"+product.image+"'/></a>"):null)
									)
							)
					)
					.append(
						$("<p>")
							.append(
								$("<a href='"+product.url+"' title='"+product.name+"'>"+product.name+"</a>")
							)
					)
				);
			ul.append(
				$("<li>")
					.addClass('col_'+i)
					.addClass('prod_'+productSlug)
					.append(
						$("<div>")
							.addClass("center")
							.append(
								$("<div>")
									.append(
										(product.thumbnail!=""?$("<a href='"+product.url+"' title='"+product.name+"' style='background-image:url("+product.image+")'><img border='0' src='"+product.thumbnail+"'/></a>"):null)
									)
							)
					)
					.append(
						$("<p>")
							.append(
								$("<a href='"+product.url+"' title='"+product.name+"'>"+product.name+"</a>")
							)
					)
					.css(
						{display:i<this.maxVisible?'':'none'}
					)
			);
		}
		tbody.append(tr);

		//build key specs section
		var keySpecs = this.comparatorConfig.getKeySpecs();
		for(i in keySpecs) {
			var keySpecSlug = keySpecs[i];
			var specName = this.comparatorProducts.getSpecName(keySpecSlug);
			var isSortable = this.comparatorProducts.isSpecSortable(keySpecSlug);

			if(specName != null) { //if the spec name is null, none of the products have a value for that spec
				var tr = $("<tr>")
					.addClass('spec-row')
					.append(
						$("<th>")
							.addClass('spec-label')
							.addClass('spec_'+keySpecSlug)
							.addClass(isSortable?'not-sorted':null)
							.append(
								isSortable ?
								$("<a href='#' title='Sort by "+specName+"'>"+specName+"</a>")
								:
								$("<span>"+specName+"</span>")
							)
					);
				for(j in products) {
					var productSlug = products[j];
					var product = this.comparatorProducts.getProduct(productSlug);
					var spec = product.specs[keySpecSlug];
					tr
						.append(
							$("<td valign='top' class='spec col_"+j+" prod_"+productSlug+" spec_"+keySpecSlug+"'>"+(spec==null||spec.displayValue==''?'-':spec.displayValue)+"</td>")
						);
				}
				tbody.append(tr);
			}
		}

		//build spec groups
		var specGroups = this.comparatorConfig.getShownSpecGroups();
		for(i in specGroups) {
			var specGroupSlug = specGroups[i];
			var specGroup = this.specGroups[specGroupSlug];
			if(specGroup == null) {
				if(typeof console != 'undefined') console.error("Spec Group '"+specGroupSlug+"' not found! Please remove shown spec group from comparator config.");
				continue;
			}
			var tr = $("<tr>")
				.addClass('spec-group')
				.addClass('spec_group_'+specGroupSlug)
				.append(
					$("<td colspan='"+(this.maxVisible+1)+"'>"+specGroup.name+"</td>")
						.prepend(
							$("<a href='#' title='Collapse this Spec Group'>collapse</a>")
						)
				);
			tbody.append(tr);


			//spec group rows
			for(j in specGroup.defaultOrder) {
				var specSlug = specGroup.defaultOrder[j];
				var specName = this.comparatorProducts.getSpecName(specSlug);
				var isSortable = this.comparatorProducts.isSpecSortable(specSlug);

				if(specName != null) { //if the spec name is null, none of the products have a value for that spec
					var tr = $("<tr>")
						.addClass('spec-row')
						.addClass('spec_group_'+specGroupSlug)
						.append(
							$("<th>")
								.addClass('spec-label')
								.addClass('spec_'+specSlug)
								.addClass(isSortable?'not-sorted':null)
								.append(
									isSortable ?
									$("<a href='#' title='Sort by "+specName+"'>"+specName+"</a>")
									:
									$("<span>"+specName+"</span>")
								)
						);
					for(k in products) {
						var productSlug = products[k];
						var product = this.comparatorProducts.getProduct(productSlug);
						var spec = product.specs[specSlug];
						tr
							.append(
								$("<td valign='top' class='spec col_"+k+" prod_"+productSlug+" spec_"+specSlug+"'>"+(spec==null||spec.displayValue==''?'-':spec.displayValue)+"</td>")
							);
					}
					tbody.append(tr);
				}
			}

			this._hideBusy();
		}

		//build spec group collapse button bar
		var ul  = $("<ul>");

		for(specGroupSlug in this.specGroups) {
			if(!this.comparatorConfig.isSpecGroupShown(specGroupSlug) && this.comparatorConfig.specGroupHasData(specGroupSlug,this.comparatorProducts))
				ul.append(
					$("<li>")
						.append(
							$("<a href='#' class='add-spec-group spec_group_"+specGroupSlug+"'>"+this.specGroups[specGroupSlug].name+"</a>")
						)
				);
		}
		//add li's

        tbody.append(
            $("<tr>")
                .addClass('add-spec-group')
                .append(
                    $("<th>").html(this.specGroups.length>0?"Add a Spec Group":"")
                )
                .append(
                    $("<td colspan='"+(this.maxVisible+1)+"'></td>")
                        .append(ul)
                )
        );

        //do this last to show table at once
		comparatorDiv.append(table);

		this._pruneEmptyRows();
		this._zebraStripeRows();

		//hide navigation if we don't need to scroll
		this.navigation = true;
		if(products.length <= this.maxVisible) {
			$('#comparator-navigation').add('#floating-header > div.navigation-container').hide();
			this.navigation = false;
		}

	},

	_showBusy : function() {
		var div = $('div.busy');
        if(!this.useBusyIndicator) {
            div.css({display:'none'});
            return;
        }
        if(div.css('display') === 'none')
			div.css({display:''});
	},

	_hideBusy : function(immediate) {
		if(immediate == true) {
			$('div.busy').css({display:'none'});
		} else {
			//display busy message for .5sec so it doesn't flash on/off too quickly
			setTimeout(function(){$('div.busy').css({display:'none'});},500);
		}
	},

	_zebraStripeRows : function() {

		$("#comparator table tr.spec-row:nth-child(odd)").addClass("alt");
	},

	_pruneEmptyRows : function() {

		var products = this.comparatorConfig.getProducts();

		//prune empty spec rows
		$('th.spec-label').each(function(){
			var anchorPoint = $(this);
			var spec = anchorPoint.attr('class').match(/spec_([\w|-]*)/);
			if(spec == null) return;
			for(i in products) {
				var td = $('td.col_'+i,anchorPoint.parent());
				var val = td.html();
				if(val.length > 0 && val !== "-") {
					return;
				}
			}
			anchorPoint.parent().remove();
		});

		//prune empty spec groups, resulting from pruning all the rows in a group
		$('tr.spec-group').each(function(){
			var specGroupHeader = $(this);
			var specGroupSlug = specGroupHeader.attr('class').match(/spec_group_([\w|-]*)/)[1];

			var group = $('tr.spec_group_'+specGroupSlug);
			if(group.length == 1)
				group.remove();
		});
	},

	_initHiddenProducts : function() {
		for(var i = 0; i < this.maxProducts; i++) {
			if(i < this.maxVisible)
				$('td.col_'+i).show();
			else
				$('td.col_'+i).hide();
		}
	},

	floatHeader : function(event) {
		var window = $(window);
		var scrollTop = window.scrollTop();
		var scrollLeft = window.scrollLeft();
		var cutoffBegin = this.cutoffBeginRef.position().top;
		var cutoffEnd = this.cutoffEndRef.position().top-this.floatingHeader.height()+1/*border*/;
		var floated = this.floatingHeader.css('display') != 'none';
		var topOffset = $(this.floatingHeaderOffsetTrigger).length == 0 ? 0 : this.floatingHeaderOffset;

		//compensate for horizontal scrolling (when zoomed in)
		this.floatingHeader.css('left',scrollLeft>0 ? $('#comparator').position().left-scrollLeft : '');

		//show the floating header
		if(scrollTop >= cutoffBegin && scrollTop <= cutoffEnd && !floated) {
			if($.browser.msie === true && $.browser.version === '6.0') {
				this.floatingHeader.css('position','absolute');
				this.floatingHeader.css('top',scrollTop+topOffset);
				this._resetFloatingHeader();
				this._adjustFloatingHeader();
				this.floatingHeader.show();
			}
			else {
				this.floatingHeader.css('top',topOffset+'px');
				this._resetFloatingHeader();
				this._adjustFloatingHeader();
				this.floatingHeader.stop().fadeIn(500);
			}
		}
		//adjust the position of the floating header (for IE6 only, others are position=fixed)
		else if($.browser.msie === true && $.browser.version === '6.0' && scrollTop >= cutoffBegin && scrollTop <= cutoffEnd && floated) {
			this.floatingHeader.stop();
			this.floatingHeader
				.animate({
					top: $(window).scrollTop()+topOffset
				},200);
		}
		//hide the floating header
		else if((scrollTop < cutoffBegin || scrollTop > cutoffEnd) && floated) {
			this.floatingHeader.css({display:'none'});
		}
	},

	_resetFloatingHeader : function(event) {
		//reset the heights to auto, so we can set all the li's to the natural height of all of them
		$('li',this.floatingHeader).height('auto');
	},

	_zoomCompensate : function(me) {

		var floated = this.floatingHeader.css('display') != 'none';

		var zoomWidth = $('#comparator').outerWidth();

		if(zoomWidth !== me.savedZoomWidth) {
			me.savedZoomWidth = zoomWidth;
			if(floated) {
				me._resetFloatingHeader();
				me._adjustFloatingHeader();
			}
		}

		setTimeout(function(){me._zoomCompensate(me);},500);
	},

	_adjustFloatingHeader : function(event) {

		var pivotWidth = $('th.pivot:eq(1)').outerWidth();

		$('li.pivot',this.floatingHeader).width(pivotWidth);
		$('div.navigation',this.floatingHeader).css('margin-left',pivotWidth);

		var total = pivotWidth;
		var offset = -1; //accounts for 1px right border
		for(var i = 0; i < this.firstVisible; i++)
			$('li.col_'+i,this.floatingHeader).css({display:'none'});
		for(var i = this.firstVisible; i < this.firstVisible+this.maxVisible; i++) {
			var li = $('li.col_'+i,this.floatingHeader);
			var width = $('td.image.col_'+i).outerWidth()+offset;

			total += (width-offset); //make a width adjustment to the last column just in case it spills past the container's width
            li.width( total>this.savedZoomWidth?width-(total-this.savedZoomWidth):width );

//			li.width(width);
			li.show();
		}

		for(var i = this.firstVisible+this.maxVisible; i < this.maxProducts; i++)
			$('li.col_'+i,this.floatingHeader).css({display:'none'});

		//set all the li's in the floating header to the overall height
        $('li',this.floatingHeader).add('div.busy',this.floatingHeader)
			.height(
    				this.floatingHeader.outerHeight()-2/*bottom border*/-(this.navigation?this.navHeight:0)/*nav height*/
			);
	},

	_initialSort : function() { //this method can only be called when the table is intitially rendered

		var sort = this.comparatorConfig.getSort();

		if(sort.spec === '')
			return;

		var me = this;

		//perform the sort
		this.comparatorConfig.sortBySpec(sort.spec,this.comparatorProducts,sort.direction);

		//get newly sorted products
		var products = this.comparatorConfig.getProducts();

		//set new sort indicator
		$('th.spec_'+sort.spec)
			.removeClass('not-sorted')
			.addClass(sort.direction === ComparatorConfiguration.SORT_ASCENDING?'asc-sorted':'dsc-sorted');

		this._reorderColumns(products);

		this._initHiddenProducts();
		this._resetFloatingHeader();
		this._adjustFloatingHeader();
	},

	_reorderColumns : function(products) {

		var anchorPoint = $('#comparator-products th.pivot');
		var anchorPointHeader = $('#floating-header li.pivot');
		for(i in products) {
			var td = $('td.image.prod_'+products[i],anchorPoint.parent());
			td.insertAfter(anchorPoint);
			anchorPoint = td;
			var col = td.attr('class').match(/(col_[0-9])/)[0];
			if(td.hasClass(col))
				td.removeClass(col);
			td.addClass('col_'+i);

			//header
			var li = $('li.prod_'+products[i],anchorPointHeader.parent());
			li.insertAfter(anchorPointHeader);
			anchorPointHeader = li;
			col = li.attr('class').match(/(col_[0-9])/)[0];
			if(li.hasClass(col))
				li.removeClass(col);
			li.addClass('col_'+i);

			//update nav icon titles and anchor text
			var product = this.comparatorProducts.getProduct(products[i]);
			$('#nav-product-icons a.jump-pos-'+i).add('#nav-product-icons-float a.jump-pos-'+i)
				.attr('title','Jump to '+product.name)
				.html('Jump to '+product.name);
		}

		$('th.spec-label').each(function(){
			var anchorPoint = $(this);
			for(i in products) {
				var td = $('td.prod_'+products[i],anchorPoint.parent());
				td.insertAfter(anchorPoint);
				anchorPoint = td;
				var col = td.attr('class').match(/(col_[0-9])/)[0];
				if(td.hasClass(col))
					td.removeClass(col);
				td.addClass('col_'+i);
			}
		});
	},

	sortBySpec : function(event) {

		var me = this;

		var target = $(event.target);
		var specSlug = target.parent().attr('class').match(/spec_([\w|-]*)/)[1];

		var sort = this.comparatorConfig.getSort();

		//clear previous sort indicator
		if(sort !=null) {
			$('th.spec_'+sort.spec)
				.removeClass('asc-sorted')
				.removeClass('dsc-sorted')
				.addClass('not-sorted');
		}

		//perform the sort
		this.comparatorConfig.sortBySpec(specSlug,this.comparatorProducts);

		//get new sort spec and direction
		sort = this.comparatorConfig.getSort();
		//get newly sorted products
		var products = this.comparatorConfig.getProducts();

		//set new sort indicator
		$('th.spec_'+specSlug)
			.removeClass('not-sorted')
			.addClass(sort.direction === ComparatorConfiguration.SORT_ASCENDING?'asc-sorted':'dsc-sorted');


		//note starting position of sort spec anchor
		if(this.floatingHeader.css('display') != 'none')
			this._scrollDiff(target);

		this._reorderColumns(products);

		//reset hidden columns
		this.firstVisible = 0;
		this._initHiddenProducts();

		//reset navigation controls, sort always resets scrolling to first product in list
		$('td.navigation > p').add('div.navigation > p',this.floatingHeader).html("Showing "+(this.firstVisible+1)+"-"+(this.firstVisible+this.maxVisible)+" of "+products.length);
		var viewport = $('#nav-product-icons').add('#nav-product-icons-float');
		viewport.css('background-position',(this.firstVisible*26)+'px 0px');

		if(this.firstVisible + this.maxVisible < products.length) {
			$('#scroll-right').show();
			$('#scroll-right-float').show();
			viewport.parent().css('padding-right','0');
		}
		$('#scroll-left').hide();
		$('#scroll-left-float').hide();

		this._resetFloatingHeader();
		this._adjustFloatingHeader();

		//have to disable sorting until this animation finishes
		this.sortingEnabled = false;
		$('td.spec_'+specSlug).each(function(i){
			var td = $(this);
			var callback = i==me.maxVisible-1?function(){ me.sortingEnabled = true; }:null;
			if(td.css('display') != 'none') {
				setTimeout(
					function() {
						td.effect("highlight", {}, 500, callback);
					},650+(i*50)
				);
			}
		});

		//automatically scroll the difference caused by the shifting column data
		if(this.floatingHeader.css('display') != 'none')
			this._scrollDiff();

		this._hideBusy();
	},

	_scrollDiff : function(anchor) {

		if(anchor != null) {
			this.compensationAnchor = anchor;
			this.compensationAnchorTop = anchor.position().top;
			return;
		}

		if(this.compensationAnchor == null) {

			//find the topmost visible spec label and use that as an anchor
			var me = this;
			var headerHeight = this.floatingHeader.outerHeight();
			var scrollTop = $(window).scrollTop();
			$('th.spec-label').each(function(){
				$this = $(this);
				var targetTop = $this.position().top;
				var targetHeight = $this.outerHeight();
				if(	me.compensationAnchor == null &&
					targetTop <= scrollTop + headerHeight &&
					targetTop + targetHeight >= scrollTop + headerHeight) {

					me.compensationAnchor = $this;
					/*$this.css('background-color','red');*/
				}
			});

			this.compensationAnchorTop = this.compensationAnchor.position().top;

			return;
		}

		var amount = this.compensationAnchor.position().top - this.compensationAnchorTop;

		$.scrollTo((amount < 0 ? "-=" : "+=")+Math.abs(amount)+"px");

		this.compensationAnchor = null;
		this.compensationAnchorTop = 0;
	},

	jumpToProduct : function(event) {

		var anchor = $(event.target);
		var pos = parseInt(anchor.attr('class').match(/jump-pos-([0-9])/)[1]);
		var products = this.comparatorConfig.getProducts();

		//automatically scroll the difference caused by the shifting column data
		if(this.floatingHeader.css('display') != 'none')
			this._scrollDiff();

		if(pos >= this.firstVisible && pos <= this.firstVisible + this.maxVisible - 1) {
			$('td.col_'+pos).each(function(i){
				var td = $(this);
				setTimeout(function(){td.effect("highlight", {}, 500);},150+(i*50));
			});
			this._hideBusy(true);
			return;
		}
		else {
			if(pos < this.firstVisible)
				this.firstVisible = pos;
			else if(pos > this.firstVisible + this.maxVisible - 1)
				this.firstVisible = pos - this.maxVisible + 1;
		}

		//hide and show td's
		for(var i = 0; i < this.maxProducts; i++) {
			if(i < this.firstVisible || i >= this.firstVisible + this.maxVisible)
				$('td.col_'+i).hide();
			else
				$('td.col_'+i).show();
		}

		//update navigation controls
		$('td.navigation > p').add('div.navigation > p',this.floatingHeader).html("Showing "+(this.firstVisible+1)+"-"+(this.firstVisible+this.maxVisible)+" of "+products.length);

		var viewport = $('#nav-product-icons').add('#nav-product-icons-float');
		viewport.css('background-position',(this.firstVisible*26)+'px 0px');

		if(this.firstVisible + 1 + this.maxVisible > products.length) {
			$('#scroll-right').hide();
			$('#scroll-right-float').hide();
			viewport.parent().css('padding-right',$('#scroll-right').outerWidth());
		}
		else if(this.firstVisible + this.maxVisible < products.length) {
			$('#scroll-right').show();
			$('#scroll-right-float').show();
			viewport.parent().css('padding-right','0');
		}

		if(this.firstVisible > 0) {
			$('#scroll-left').show();
			$('#scroll-left-float').show();
		}
		else if(this.firstVisible == 0) {
			$('#scroll-left').hide();
			$('#scroll-left-float').hide();
		}

		this._resetFloatingHeader();
		this._adjustFloatingHeader();
		if(this.floatingHeader.css('display') != 'none')
			this._scrollDiff();
		this._hideBusy();

/*		//this animation causes problems if two consecutive jumps are performed before the animation finishes, b/c the delayed highlight effects will shown hidden td's
		$('td.col_'+pos).each(function(i){
			var td = $(this);
			setTimeout(function(){td.effect("highlight", {}, 500)},150+(i*50));
		});*/

	},

	scrollRight : function(event) {

		var products = this.comparatorConfig.getProducts();

		//these two conditions should never happen if the scroll links are managed properly
		if(products.length <= this.maxVisible) //no need to scroll
			return;
		if((this.firstVisible + 1 + this.maxVisible) > products.length) //can't scroll anymore
			return;

		//automatically scroll the difference caused by the shifting column data
		if(this.floatingHeader.css('display') != 'none')
			this._scrollDiff();

		$('td.col_'+this.firstVisible).hide();
		this.firstVisible++;
		$('td.col_'+(this.firstVisible+this.maxVisible-1)).show();

		$('td.navigation > p').add('div.navigation > p',this.floatingHeader).html("Showing "+(this.firstVisible+1)+"-"+(this.firstVisible+this.maxVisible)+" of "+products.length);

		var viewport = $('#nav-product-icons').add('#nav-product-icons-float');
		viewport.css('background-position',(this.firstVisible*this.navBackgroundOffset)+'px 0px');

		if(this.firstVisible + 1 + this.maxVisible > products.length) {
			$('#scroll-right').add('#scroll-right-float').hide();
			viewport.parent().css('padding-right',$('#scroll-right').outerWidth());
		}
		if(this.firstVisible > 0) {
			$('#scroll-left').add('#scroll-left-float').show();
		}

		this._resetFloatingHeader();
		this._adjustFloatingHeader();
		if(this.floatingHeader.css('display') != 'none')
			this._scrollDiff();
		this._hideBusy();
	},

	scrollLeft : function(event) {

		var products = this.comparatorConfig.getProducts();

		//these two conditions should never happen if the scroll links are managed properly
		if(products.length <= this.maxVisible) //no need to scroll
			return;
		if(this.firstVisible == 0) //can't scroll anymore
			return;

		//automatically scroll the difference caused by the shifting column data
		if(this.floatingHeader.css('display') != 'none')
			this._scrollDiff();

		$('td.col_'+(this.firstVisible+this.maxVisible-1)).hide();
		this.firstVisible--;
		$('td.col_'+this.firstVisible).show();

		$('td.navigation > p').add('div.navigation > p',this.floatingHeader).html("Showing "+(this.firstVisible+1)+"-"+(this.firstVisible+this.maxVisible)+" of "+products.length);

		var viewport = $('#nav-product-icons').add('#nav-product-icons-float');
		viewport.css('background-position',(this.firstVisible*26)+'px 0px');

		if(this.firstVisible == 0) {
			$('#scroll-left').add('#scroll-left-float').hide();
		}
		if(this.firstVisible + this.maxVisible < products.length) {
			$('#scroll-right').add('#scroll-right-float').show();
			viewport.parent().css('padding-right','0');
		}

		this._resetFloatingHeader();
		this._adjustFloatingHeader();
		if(this.floatingHeader.css('display') != 'none')
			this._scrollDiff();
		this._hideBusy();
	},

	addSpecGroup : function(event) {

		var me = this;
		var specGroupSlug = $(event.target).attr('class').match(/spec_group_([\w|-]*)/)[1];
		var specGroup = this.specGroups[specGroupSlug];
		var footer = $('tr.add-spec-group');
		var products = this.comparatorConfig.getProducts();

		//automatically scroll the difference caused by the shifting column data
		this._scrollDiff(footer);

		//build spec groups
		if(specGroup == null) {
			if(typeof console != 'undefined') console.error("Spec Group "+specGroupSlug+" not found! Add spec group button was created but doesn't exist in known SpecGroup list.");
			return;
		}

		var heading = $("<tr>")
			.addClass('spec-group')
			.addClass('spec_group_'+specGroupSlug)
			.append(
				$("<td colspan='"+(this.maxVisible+1)+"'>"+specGroup.name+"</td>")
					.prepend(
						$("<a href='#' title='Collapse this Spec Group'>collapse</a>")
							.bind('click',function(event){
								event.preventDefault();
								me._showBusy();
								setTimeout(function(){me.removeSpecGroup(event);},1);
							})
					)
			);
		footer.before(heading);

		//spec group rows
		for(j in specGroup.defaultOrder) {
			var specSlug = specGroup.defaultOrder[j];
			var specName = this.comparatorProducts.getSpecName(specSlug);
			var isSortable = this.comparatorProducts.isSpecSortable(specSlug);

			if(specName != null) { //if the spec name is null, none of the products have a value for that spec
				var tr = $("<tr>")
					.addClass('spec-row')
					.addClass('spec_group_'+specGroupSlug)
					.append(
						$("<th>")
							.addClass('spec-label')
							.addClass('spec_'+specSlug)
							.addClass(isSortable?'not-sorted':null)
							.append(
								isSortable ?
								$("<a href='#' title='Sort by "+specName+"'>"+specName+"</a>")
									.bind('click',function(event) {
										event.preventDefault();
										if(!me.sortingEnabled)
											return;
										me._showBusy();
										setTimeout(function(){me.sortBySpec(event);},1);
									})
								:
								$("<span>"+specName+"</span>")
							)
					);
				for(k in products) {
					var productSlug = products[k];
					var product = this.comparatorProducts.getProduct(productSlug);

					var spec = product.specs[specSlug];
					tr
						.append(
							$("<td valign='top' class='spec col_"+(k)+" prod_"+productSlug+" spec_"+specSlug+"'>"+(spec==null||spec.displayValue==''?'-':spec.displayValue)+"</td>")
						);
				}
				footer.before(tr);
			}
		}

		//hide td's that should not be shown
		for(var i = 0; i < this.firstVisible; i++) {
			$('td.col_'+i).hide();
		}
		for(var i = (this.firstVisible+this.maxVisible); i < this.maxProducts; i++) {
			$('td.col_'+i).hide();
		}

		this._pruneEmptyRows();
		this._zebraStripeRows();
		this._resetFloatingHeader();
		this._adjustFloatingHeader();

		//remove the "add spec group" link
		$(event.target).parent().remove();

		this._scrollDiff();

		this._hideBusy();
	},

	removeSpecGroup : function(event) {

		var me = this;

		var slug = $(event.target).parent().parent().attr('class').match(/spec_group_([\w|-]*)/)[1];

		//remove all the rows for this spec group
		$("tr").each(function(){
			var specGroupClass = $(this).attr('class').match(/spec_group_([\w|-]*)/);
			if(specGroupClass !== null && specGroupClass[1] === slug)
				$(this).remove();
		});

		//add the "add spec group" link back to the footer
		var sg = this.specGroups[slug];
		$("tr.add-spec-group ul")
		.append(
			$("<li>")
				.append(
					$("<a href='#' class='add-spec-group spec_group_"+slug+"'>"+sg.name+"</a>")
						.bind('click',function(event) {
							event.preventDefault();
							me._showBusy();
							setTimeout(function(){me.addSpecGroup(event);},1);
						})
				)
		);

		this._pruneEmptyRows();
		this._zebraStripeRows();
		this._resetFloatingHeader();
		this._adjustFloatingHeader();
		this._hideBusy();
	},

	saveConfig : function() {}
};


