/*  Toyota ESQ - Tag Cloud Script
 *--------------------------------------------------------------------------*/


var cloudOptions = 
		{
			//URL of XML file containing tags			
			//dataURL: '/esq/ajax/tags4.xml',
			dataURL: '/toyotaSearch/tags?filters=',
			
			//Link template for tags
			// {term} will be replaced with the term
			//tagLink: 'search.html?s={term}',
			tagLink: '/toyotaSearch/esq?tag={term}&filters={filter}',

			//Max number of tags to attempt to fit in the cloud
			//Should be close to what is expected to fit for better performance
			maxTags:200,
			
			//minimum font size for tag
			minFontSize:10,
			
			//maximum font size for tag
			maxFontSize:20,
			
			//minimum max font size
			// to fit all tags, the code will adjust the font size down
			// until it reaches this minimum, then is starts removing tags
			// -the closer this is to maxFontSize, the better the speed performance
			maxFontSizeMinimum:35,
			
			//Color array of tags
			/*
			The tags will fade from one set to the other based on rank
			First tag is considered 100% to min percent
			Second tag is considered from first tag min percent to second tag minpercent
			Can have as many ranges as you want, but the last one should be 00 percent
			The percents should be in order from largest minpercent to smallest
			EXAMPLE:
			colors: [{startHex:0xFFFFFF,endHex:0xEEEEEE,minPercent:.60},
					 {startHex:0xDDDDDD,endHex:0xCCCCCC,minPercent:.30},
				 	 {startHex:0xBBBBBB,endHex:0xAAAAAA,minPercent:.00}],
			
			100.00% startHex -> 0xFFFFFF
						a fade between the two
			60.00%  endHex   -> 0xEEEEEE
			
			59.99%  startHex -> 0xDDDDDD
						a fade between the two
			30.00%  endHex   -> 0xCCCCCC
			
			29.99%  startHex -> 0xBBBBBB
						a fade between the two
			00.00%  endHex   -> 0xAAAAAA
			*/
			colors: [{startHex:0xdee3e4,endHex:0xfdfcfc,minPercent:.60},
					 {startHex:0x949999,endHex:0xa2b9c4,minPercent:.30},
				 	 {startHex:0x407580,endHex:0x286071,minPercent:.00}],

			
			//Preview Cloud Alternate Color
			// The splash page requires the preview cloud to have an alternate color
			previewCloudAltColor: 0x286071,
			
			//Boolean to use the alternate color for the preview clous
			// 	The splash page requires the preview cloud to have an alternate color 
			//	Interior doe snot
			usePreviewCloudAltColor: false,
			
			//minimum vertical spacing between tag rows
			rowSpacing:0,  
			
			//minimum horizontal spacing between tags
			minTagSpacing:2,  
			
			//Maximum rank difference, in % of total rank range, 
			//	allowed between rank sorted tags
			//	-results in a more asthetic tagCloud
			maxRankDifference:.30,
			
			//Reduces the space between lines
			lineHeightMultiplier:.95,
			
			//The maximum percent of empty space on the last line
			// considered acceptable
			// -Prevents the last line from looking barren
			maxEmptyPercent:.40
			
					
		};

//start the process of grabbing the tag data
//and generating the cloud
function initializeTagCloud(filters)
{
	new Ajax.Request(cloudOptions.dataURL + filters,
 	{
    	method:'get',
	    onSuccess: function(result){
			var tagList = parseTagXML(result.responseXML);
			generateTagCloud(tagList);
    	},
	    onFailure: function(){ alert('Something went wrong...') }
  	});
  	
}

//parses the tag xml data into a custom tag array
function parseTagXML(xmlData)
{
	//console.time("parseTagXML");
	//console.info("parseTagXML");

	//get list of tag nodes from XML data
	var tagNodes = xmlData.documentElement.getElementsByTagName("tag");
	
	//create tag array to return
	var tags=new Array(); 
	
	//add tag object to tags array for each tagNode
	for (i=0;i<tagNodes.length;i++)
		tags.push({name:tagNodes[i].childNodes[0].nodeValue, 
				   rank:parseFloat(tagNodes[i].getAttribute("value"))});
	
	//console.timeEnd("parseTagXML");
	return tags;
}

//sort function for the tag array based on rank
var tagSort_rank = function(a,b){			
		return b.rank-a.rank;
};

//sort function for the tag array based on name
var tagSort_name = function(a,b){	
		
	     if (a.name < b.name)
	        return -1;
	     else if (a.name > b.name) 
	        return  1;
	     else
	        return 0;
};

//generates the tag cloud based on the cloudOptions and the tags
function generateTagCloud(tags)
{
	//console.time("sort");
	//console.time("sort");
	//sort tags based on rank
	tags.sort(tagSort_rank);
	
	//console.timeEnd("sort");
	//chop tag list to  size of maxTagCount
	if(tags.length > cloudOptions.maxTags)
		tags.splice(cloudOptions.maxTags, tags.length-cloudOptions.maxTags);
	//console.time("render");
	//console.info("render");
	//determine min,max ranks
	var maxRank = tags[0].rank;
	var minRank = tags[tags.length-1].rank;

	//Adjust rankings so there are no huge jumps
	// huge jumps make for a bad looking result
	for(i=0;i<tags.length;i++)
	{
		if(i == 0)
			continue;
		
		var rankDifference = (tags[i-1].rank-tags[i].rank)/(maxRank-minRank);
		
		if(rankDifference>cloudOptions.maxRankDifference)
		{
			//jump is too large, adjust tag ranks for all tags below i
			var previousRankDiff = tags[i-1].rank-tags[i].rank;
			for(j=i-1;j>=0;j--)
			{
				var currentRankDiff = previousRankDiff;
				previousRankDiff = tags[j].rank-tags[j+1].rank;
				
				tags[j].rank = tags[j+1].rank+currentRankDiff/rankDifference*cloudOptions.maxRankDifference;
			}
		}
	}
	
	//Reset max rank after our rank adjustment
	maxRank = tags[0].rank;

	//sort tags based on name to make an alphabetical tagCloud
	tags.sort(tagSort_name);

	//Create tagCloud container
	// and add it to the document body
	var cloudDivContainer = new Element('div', { 'id':'tagCloud', 
										'style':'width:560px;height:345px;display:block;position:relative;overflow:hidden;'});

	var cloudDiv = new Element('div', {'style':'width:540px;height:325px;display:block;position:relative;overflow:hidden;left:10px;top:10px'});
	
	cloudDivContainer.update(cloudDiv);
	if ($('tagCloud')) {
		$('tagCloud').remove();
	} 
	$(document.body).insert(cloudDivContainer);
	
	//create each tag
	var filters = selectedFilters.join(',');
	for(i=0; i<tags.length; i++)
	{
		//create  the link for the tag
		var link = cloudOptions.tagLink.replace('{term}',encodeURIComponent(tags[i].name.toUpperCase()));
		link = link.replace('{filter}',encodeURIComponent(filters));
		var wordLink = new Element('a', {'style':'width:100%;height:100%;', 'href':link, 'name':tags[i].name.toUpperCase()});
		wordLink.update(String.interpret(tags[i].name.toUpperCase()).escapeHTML());
		Event.observe(wordLink, 'click', function () {
			fireTag(2136.3, {'<cloud_term>':this.name});
		});
		
		//create the div for the tag
		var wordDiv = new Element('div', { 'class':'tagWord', 
									  'style':'color:#888888;position:relative;white-space:nowrap;vertical-align: baseline;float:left;font-weight:bold'} );
		wordDiv.update(wordLink);
		
		cloudDiv.insert(wordDiv); //adding the div to the tagCloud so we can calculate size later
		
		//add the div to the tag element for reference later
		tags[i].element = wordDiv;
		tags[i].aLinkElement = wordLink;
		
	}

	//calculating the width/height we have for the cloud
	var cloudDimensions = Element.getDimensions(cloudDiv);
	var cloudWidth = cloudDimensions.width;
	var cloudHeight = cloudDimensions.height;
		
	//Normalize tag rank
	for(i=0;i<tags.length;i++)
		tags[i].rank = 1-(maxRank-tags[i].rank)/(maxRank-minRank);
	
	var maxFontSize = cloudOptions.maxFontSize;
	var positionInfo = null;
	var maxTagCount = cloudOptions.maxTags;
	
	//Try positioning tags to see if they fit
	//Reduce max font size and tag count until it does
	var fuse = 0;
	while(fuse < 20)
	{	
		fuse++;
		//trim the tag list if it's greater than the maxTagCount
		if(maxTagCount < tags.length)
		{
			//sort based on rank
			tags.sort(tagSort_rank);
			
			//remove lowest ranked tags
			tags.splice(maxTagCount, tags.length-maxTagCount);
			
			//sort based on name
			tags.sort(tagSort_name);
		}
		
		//retain currentPosition info
		var oldPositionInfo = positionInfo;
		
		//create new position info
		var positionInfo = positionTags(tags, cloudDiv, cloudWidth, cloudHeight, cloudOptions.minFontSize, maxFontSize);
		
		//clean up currentPosition info
		// we wait to clean it up be
		if(oldPositionInfo != null)
			cleanPositionInfo(oldPositionInfo);
			
		//determine if we found a good one
		if(positionInfo.rowsPastBottom == 0 && positionInfo.lastLineEmptyPercent <= cloudOptions.maxEmptyPercent)
			break;
		
		//update the maxFont size or the maxTagCount
		//to reduce the size of the tag cloud
		//attempting to make the tag cloud fit 
		if(maxFontSize == cloudOptions.maxFontSizeMinimum || positionInfo.rowsPastBottom == 0)
		{
			//if the font size is at the minimum, 
			// or if all the rows fit, but the last row is too empty
			//we start lowering the tag count
			var tagsToRemove = 0;
			if(positionInfo.rowsPastBottom == 0)
				tagsToRemove = positionInfo.rows[positionInfo.rows.length-1].tags.length; //try remove last row
			else
				//try to remove all tags in rows that are cutoff or not displayed
				for(i=0; i<positionInfo.rowsPastBottom; i++)
					tagsToRemove += positionInfo.rows[positionInfo.rows.length-1-i].tags.length;
					
			maxTagCount = tags.length - tagsToRemove;
			
			if(maxTagCount < 2)
				break;
		}
		else
			maxFontSize--;
	}

	//Justify tags and rows to make them push up against the sides
	//set final styles
	finalizePositions(positionInfo, cloudWidth, cloudHeight);
	
	//Duplicate the cloud into the preview area
	var clonedCloud = cloudDivContainer.cloneNode(true);
	Element.writeAttribute(clonedCloud, 'id', 'tagCloudClone');	
	var cloudContainer = new Element('div', { 'style':'position:absolute;left:-10px;top:0px'} );
	cloudContainer.update(clonedCloud);	
	$('tagCloudPreviewClip').update(cloudContainer);
	
	//Set the tags to the same color if alternate preview color is required
	if(cloudOptions.usePreviewCloudAltColor)
		Element.select(clonedCloud, 'a').each(function(aTag){
			Element.setStyle(aTag, 'color:#'+cloudOptions.previewCloudAltColor.toString(16));
		});

	
	//Hide the cloud
	//Count just set display to none, but it causes issues in IE7/6
	var cloudHideContainer = new Element('div', { 'style':'visibility:hidden;position:absolute;left:0px;top:0px'} );
	cloudHideContainer.update(cloudDivContainer);
	$(document.body).insert(cloudHideContainer);
	//console.timeEnd("render");
}

//Places the tags into rows and positions the rows
function positionTags(tags, cloudDiv, cloudDivWidth, cloudDivHeight, minFontSize, maxFontSize)
{
	//variables for tag/tag row placement
	var xPos = 0; 			//right side of last tag
	var rowIndex = -1;		//current index of the row
	var rows = new Array(); //row objects
	
	for(var i=0; i<tags.length; i++)
	{
	
		var tag = {element:tags[i].element,
					rank:tags[i].rank,
					height:0,
					width:0,
					x:0,
					fontSize:0,
					color:'#FFFFFF',
					aLinkElement: tags[i].aLinkElement
					};
		
		
		//Calculate tag color
		tag.color = calcColor(tag.rank, cloudOptions.colors);
		
		//Calculate font size for the tag		
		tag.fontSize = Math.round(minFontSize + (maxFontSize-minFontSize)*tag.rank);
		
		//Set the font size
		//must be set to determine tag dimensions
		Element.setStyle(tag.element, 'font-size:'+tag.fontSize+'px;line-height:'+cloudOptions.lineHeightMultiplier+'em');
		
		//calculate the tags dimensions
		var tagDimensions = Element.getDimensions(tag.element); 
		tag.width = tagDimensions.width;
		tag.height = tagDimensions.height;
		
		//determine if we need to add a new row
		if(xPos + tag.width > cloudDivWidth || rowIndex == -1)
		{
			//calculate last row bottom, save as new row top
			var rowTop = rowIndex == -1 ? 0 : rows[rowIndex].rowTop + rows[rowIndex].maxTagHeight + cloudOptions.rowSpacing;
			
			//create row div element
			var row = new Element('div', {'style':'position:absolute;top:'+rowTop+'px;left:0px;width:'+cloudDivWidth+'px'});

			//add row to cloudDiv
			cloudDiv.insert(row);
		
			//create a new row meta object
			rows.push({tags:			new Array(),
					   rowTop:			rowTop,
					   maxTagHeight: 	0,
					   maxFontSize:		0,
					   element:			row
					   });
			rowIndex++;
			
			//reset the xPosition
			xPos = 0;	
					
		}	
		
		//add the tag to the row
		rows[rowIndex].tags.push(tag); //to the row meta object
		rows[rowIndex].element.insert(tag.element); //to the row dom element

		//update the tag desired x position
		tag.x = xPos;
		
		//set the tag color
		//Element.setStyle(tag.element, 'color:'+tag.color+''); 
		
		//update xPos
		xPos += tag.width + cloudOptions.minTagSpacing;
		
		//update row meta data with the largest tag height
		if(rows[rowIndex].maxTagHeight < tag.height)
			rows[rowIndex].maxTagHeight = tag.height;
		
		//update row meta data with the largest font-size
		if(rows[rowIndex].maxFontSize < tag.fontSize)
			rows[rowIndex].maxFontSize = tag.fontSize;
	}
		
	//Returns the result of position the tags
	var positionInfo = {
						//row meta data
						rows:			rows,
						
						//how many pixels from the last row bottom to the bottom of the cloud
						distToBottom:  cloudDivHeight - (rows[rowIndex].rowTop + rows[rowIndex].maxTagHeight),
						
						//how many rows are past the bottom
						rowsPastBottom: 0, //set below
						
						//max font size used
						maxFontSize: maxFontSize,
						
						//percent of last line that is empty space
						lastLineEmptyPercent: 0
						};

	//set rows past bottom
	for(var r=rows.length-1;r>=0;r--)
		if(rows[r].rowTop + rows[r].maxTagHeight > cloudDivHeight)
			positionInfo.rowsPastBottom++;
		else
			break;

	//Set lastLineEmptyPercent
	var lastRow = rows[rows.length-1];
	var filledSpace = 0;
	for(var i = 0; i<lastRow.tags.length; i++)
		filledSpace += lastRow.tags[i].width;
	positionInfo.lastLineEmptyPercent = 1-filledSpace/cloudDivWidth;
	
	return positionInfo;
}

//Cleans up elements created by a positionTags 
//call result that is not going to be used
function cleanPositionInfo(positionInfo)
{
	var rows = positionInfo.rows;
	for(r=0;r<rows.length;r++)
		Element.remove(rows[r].element);
}


//Justifies all the tags in rows,
//	justifies rows vertically,
//	and sets the tag and row styles
//	after a positionTags call result
//	is accepted
function finalizePositions(positionInfo, cloudWidth, cloudHeight)
{
	var rows = positionInfo.rows;

	
	//adjust so the tags do not go past the edge
	cloudWidth = cloudWidth-2;
	cloudHeight = cloudHeight-1;
	
	var extraVerticalSpace = cloudHeight;
	
	//store font sizes so we can do sifr replacement
	var fontSizesUsed = new Array();
	
	//justify tags in all rows
	for(r=0;r<rows.length;r++)
	{
		var row = rows[r];

		
		//add space of all rows
		extraVerticalSpace-=row.maxTagHeight;
				
		//calculate the extra space
		var extraSpace = cloudWidth;
		for(i=0;i<=row.tags.length-1;i++)
			extraSpace -= row.tags[i].width;

		//calculate the tag margin
		var tagMargin = extraSpace/ (row.tags.length-1);

		//calculate row baseline
		var rowBaseline = row.maxFontSize*cloudOptions.lineHeightMultiplier;
				
		//keeps track of existing space used by tags for position of later tags
		var tagSpace = 0;
		
		//position the tags
		//set the color
		for(i=0;i<=row.tags.length-1;i++)
		{
			row.tags[i].x = tagSpace + tagMargin*i;
			tagSpace += row.tags[i].width;
			
			//Position each tag's baseline to match the rows baseline			
			var tagBaseline = row.tags[i].fontSize*cloudOptions.lineHeightMultiplier;
		
			var leftOffset = tagMargin*i;
			Element.setStyle(row.tags[i].element, 'width:'+row.tags[i].width+'px;left:'+ leftOffset + 'px;top:'+(rowBaseline-tagBaseline)+'px'); //width is set because sifr will resize the div
			Element.setStyle(row.tags[i].aLinkElement, 'color:'+row.tags[i].color); //setting the color of the link, not on div because of IE7/6

			
		}
		
		//position last tag on the right edge
		if(row.tags.length > 1 && false)
		{
			var rightSide = Element.positionedOffset(row.tags[row.tags.length-1].element)[0] + row.tags[row.tags.length-1].width;
			
			if(rightSide != cloudWidth)
			{
				var offset = parseFloat(Element.getStyle(row.tags[row.tags.length-1].element, "left"));
				offset += cloudWidth - rightSide;
				Element.setStyle(row.tags[i].element, 'left:'+ offset + 'px');
			}
		}		
	}

	//position the rows so they are  spaced out evenly along the entire vertical 	
	var rowMargin = extraVerticalSpace/(rows.length-1);
	var rowSpace = 0;
	for(var r=0;r<=rows.length-1;r++)
	{
		rows[r].rowTop = rowSpace + rowMargin*r;
		rowSpace += rows[r].maxTagHeight;
			
		Element.setStyle(rows[r].element, 'top:'+ rows[r].rowTop + 'px;height:'+rows[r].maxTagHeight+'px;');
		
	}
	
}

//Calculates the color from the range of colors and percent
// -colors are listed in groups with percent ranges
// -picks the range that the percent falls in
// -returns a faded color based on the range colors and percent
function calcColor(percent, colors)
{
	var colorObj = colors[0];
	var high = 1;
	var low = 0;
	for(var i=colors.length-1;i>=0;i--)
	{
		if(colors[i].minPercent <= percent)
		{
			colorObj = colors[i];
			low = colorObj.minPercent;
		}
		else
		{
			high = colors[i].minPercent;
			break;
		}		
	}
	var adjustedPercent = (percent-low)/(high-low);
	return '#'+colorFade(colorObj.endHex, colorObj.startHex, adjustedPercent).toString(16);
}
//returns a color that is p percent between color h1 and color h2
function colorFade(h1, h2, p) { return ((h1>>16)+((h2>>16)-(h1>>16))*p)<<16|(h1>>8&0xFF)+((h2>>8&0xFF)-(h1>>8&0xFF))*p<<8|(h1&0xFF)+((h2&0xFF)-(h1&0xFF))*p; }
