/**
 * @class HD.TagCloudWidget
 * @description The purpose of TagCloudWidget is to provide a mechanism for users to choose
 * 				a tag and see content tagged with that string
 * 				Registered as hd_tag_cloud_widget.
 * @constructor
 * @extends HD.Widget
 * @see HD.TagCloudWidget.hooks
 * @see HD.TagCloudWidget.templates
 * @property {object} 	config
 *                    	The configuration for the widget.
 * @property {object} 	[config.hooks=HD.TagCloudWidget.hooks]
 *                 		Customized subset of hooks (merged with the defaults).
 * @property {boolean} 	config.multipleSelect
 *                    	Allows application of multiple tags.
 *                    	Default is false.
 * @property {number}	config.pageSize
 * 						Number of tags to be shown
 * 						Default is 0
 * @property {string} 	config.pageType
 *                 		Shows either hard limit of tags or tries to approximate
 * @property {number} 	config.minFontSize
 *                 		Percentage font size for smallest tag
 *                 		Default is 80
 * @property {number}	config.maxFontSize
 * 						Percentage font size for largest tag
 * 						Default is 150	
 * @property {object} 	[config.templates=HD.TagCloudWidget.templates]
 *                 		Customized subset of templates (merged with the defaults).			         
 */
HD.TagCloudWidget = function(config) {
    this.config = config || {};
	this.config.multipleSelect = this.config.multipleSelect || false;
	this.config.pageSize = this.config.pageSize || 0;
	this.config.pageType = this.config.pageType || 'limit' || 'hint'; 
	this.config.minFontSize = Math.max(50, this.config.minFontSize || 80);
	this.config.maxFontSize = Math.min(300, this.config.maxFontSize || 150);
    
	/** Observer collection */
    this.observers = [];
	this.loadTemplates(arguments.callee);
};

/**
 * The collection of default templates.
 * @fieldOf HD.TagCloudWidget
 */
HD.TagCloudWidget.templates = {
		
	/**
	 * More tags label for the widget
	 * @memberOf HD.TagCloudWidget
	 */
	moreTagsLabel : 'More Tags',
	
	/**
	 * Less tags label for the widget
	 * @memberOf HD.TagCloudWidget
	 */
	lessTagsLabel : 'Less Tags',
	
	/**
	 * @constant
	 * @return {string} More tags label for the widget: moreTagsLabel
	 * @memberOf HD.TagCloudWidget
	 */
	getMoreTagsLabel : function() {
		return this.moreTagsLabel;
	},
	
	/**
	 * @constant
	 * @return {string} Less tags label for the widget: lessTagsLabel
	 * @memberOf HD.TagCloudWidget
	 */
	getLessTagsLabel : function() {
		return this.lessTagsLabel;
	},
	
	/**
	 * @constant
	 * @return {string} Main template for the widget
	 * @memberOf HD.TagCloudWidget
	 */
	getHtml : function() {
		return this.html;
	},
	
	/** 
	 * Main template for the widget
	 * @memberOf HD.TagCloudWidget
	 * @type jst_template
	 */	
	html :  '\
		<div class="${classes.TAG_CLOUD} ${classes.WIDGET} ${thresholdClasses} ${hooks.CLOUD}">\
		{for tag in tags}\
			{if tag_index} {/if}\
			<a href="javascript:void(0)" class="${classes.TAG} ${activeTags|isActive} {if isHint}scale-${tag.fontSize|parse} {elseif isLimit(tag)}scale-limit {/if} ${hooks.TAG}" style="font-size: ${tag.fontSize}">${tag.tagName}</a>\
		{/for}\
		{if hasThreshold}\
			<div>\
				<a href="javascript:void(0)" class="${classes.MORE} ${hooks.MORE}">${templates.getMoreTagsLabel()}</a>\
				<a href="javascript:void(0)" class="${classes.LESS} ${hooks.LESS}">${templates.getLessTagsLabel()}</a>\
			</div>\
		{/if}\
		</div>'
};

(function() {
	var classes = HD.CSS_CLASSES;
	
	/**
	 * The collection of hooks
	 */
	HD.TagCloudWidget.hooks = {
		
		/**
		 * Standalone: 0..n<br />
		 * Use: tag link<br />
		 * @memberOf HD.TagCloudWidget
		 */
		TAG : classes.TAG + '-tag',
		
		/**
		 * Dependencies: 1: CLOUD <br/>
		 * Use: More link for more tags<br />
		 * @memberOf HD.TagCloudWidget
		 */
		MORE : classes.TAG + '-more',
		
		/**
		 * Dependencies: 1: CLOUD <br/>
		 * Use: Less link for fewer tags<br />
		 * @memberOf HD.TagCloudWidget
		 */
		LESS : classes.TAG + '-less',
		
		/**
		 * Standalone: (1)<br />
		 * Use: Tag cloud container<br />
		 * @memberOf HD.TagCloudWidget
		 */
		CLOUD : classes.TAG + '-cloud'
		
	};
})();

HD.TagCloudWidget.prototype = {    

	/**
	 * Sets event listeners for the widget.
	 * @param {object} [data]
	 *                 Event data from the render phase
	 */
    setListeners : function(data) {
    	var tags = data.tags;
		var classes = HD.CSS_CLASSES;
		var self = this;
		var parentEl = this.getParent();
		
		// Attach to tags
    	var tagEls = HD.getByClass(this.config.hooks.TAG, '*', parentEl);
    	for(var i = 0, len = tagEls.length; i < len; i++) {
    		this.assignListener(tagEls[i], tags[i]);
    	}

		// Attach to more/less toggle
		var containerEl = HD.getByClass(this.config.hooks.CLOUD, '*', parentEl)[0];
    	var moreEl = HD.getByClass(this.config.hooks.MORE, '*', parentEl)[0];
    	var lessEl = HD.getByClass(this.config.hooks.LESS, '*', parentEl)[0];
		if (moreEl && lessEl && containerEl) {
			moreEl.onclick = function() {
				HD.replaceClass(containerEl, classes.TAG_CLOUD+'-'+classes.LESS, classes.TAG_CLOUD+'-'+classes.MORE);
			};
			lessEl.onclick = function() {
				HD.replaceClass(containerEl, classes.TAG_CLOUD+'-'+classes.MORE, classes.TAG_CLOUD+'-'+classes.LESS);
			};
		}
    },
    
    /**
	 * Adds listeners to a tag link
	 * @param {HTMLElement} tagEl
	 *                 		The link element
	 * @param {object} 		tag
	 *                 		Tag for the link
	 */
    assignListener : function(tagEl, tag) {
    	var modelCb = this.model;
		var self = this;
    	var t = tag;
   
		var classes = HD.CSS_CLASSES;
    	tagEl.onclick = function() {
			var mSelect = self.config.multipleSelect;
			var tags = modelCb.filters.tags || [];
			if (HD.hasClass(this, classes.ACTIVE)) {
				HD.removeClass(this, classes.ACTIVE);
				if (!mSelect) {
					modelCb.clearTags();
				}
				else {
					for (var i = 0; i < tags.length; i++) {
						if (tags[i] == t.tagName) {
							tags.splice(i, 1);
							break;
						}
					}
					if (tags.length > 0) {
						modelCb.setTags(tags);
					}
					else {
						modelCb.clearTags();
					}
				}
			}
			else {
				if (!mSelect) {
			    	var tagEls = HD.getByClass(self.config.hooks.TAG, '*', self.getParent());
			    	for(var i = 0, len = tagEls.length; i < len; i++) {
			    		HD.removeClass(tagEls[i], classes.ACTIVE);
			    	}
				}
				HD.addClass(this, classes.ACTIVE);
				modelCb.setTags(mSelect ? tags.concat([t.tagName]) : [t.tagName]);
			}
    	};
    },
    
    /**
	 * Monitors events by HD.TagCloudWidget.
	 * @param {string} eventName
	 *                 The name of the event
	 * @param {object} [eventData]
	 *                 Data for the event
	 */
    update : function(eventName, eventData) {
    	if (eventName == "getTags_Start") {
    		this.loading(true);
    	}
		else if (eventName == "getTags_Finish") {
    		this.loading(false);
    		this.render(eventData);
    	}
		else if (eventName == "tags_Set" || eventName == "tags_Clear") {
	    	var classes = HD.CSS_CLASSES;
	    	var tagEls = HD.getByClass(this.config.hooks.TAG, '*', this.getParent());
	    	
	    	// Update tag state
	    	for (var i = 0, len = tagEls.length; i < len; i++) {
				HD.removeClass(tagEls[i], classes.ACTIVE);
				for (var j = 0; j < eventData.length; j++) {
					if (tagEls[i].innerHTML == eventData[j]) {
						HD.addClass(tagEls[i], classes.ACTIVE);
						break;
					}
				}
	    	}
		}
    },
    
    /**
	 * Renders the HTML for the widget based on dynamic data.
	 * @param {object} [data]
	 *                 Event data from the render phase
	 * @returns {string} HTML for the widget
	 */
	getHtml : function(data) {
		var classes = HD.CSS_CLASSES;
		var tags = data.tags;
		var threshold = this.setFontSizes(data.tags);
		var thresholdClasses = [];
		var limit = this.config.pageSize;
		var activeTags = this.model.filters && this.model.filters.tags ? this.model.filters.tags.slice(0) : [];
		
		if (threshold > 0) {
			thresholdClasses.push(classes.TAG_CLOUD, '-', classes.LESS);
			if (this.config.pageType == 'hint') {
				for (var i = threshold; i <= this.config.maxFontSize; i += 10) {
					thresholdClasses.push(' ', classes.TAG_CLOUD, '-scale-', i);
				}
			}
		}
		
		var self = this;
		return this.processTemplate(this.config.templates.getHtml(), {
			activeTags       : activeTags,
			data             : data,
			tags             : data.tags,
			thresholdClasses : thresholdClasses.join(''),
			threshold        : threshold,
			hasThreshold     : threshold > 0,
			limit            : limit,
			isHint           : threshold > 0 && this.config.pageType == "hint",
			isLimit          : function(tag) {
				if (threshold > 0 && self.config.pageType == "limit" && (tag.count < threshold || limit == 0)) {
					return true;
				}
				else {
					limit--;
					return false;
				}
			},
			_MODIFIERS       : {
				parse        : function(number) { return parseInt(number, 10); },
				isActive     : function(activeTags) {
					if (activeTags.length > 0) {
						for (var j = activeTags.length - 1; j >= 0; j--) {
							if (activeTags[j] == tag.tagName) {
								activeTags.splice(j,1);
								return HD.CSS_CLASSES.ACTIVE;
								break;
							}
						}
					}
					return '';
				}
			}
		});
	},
	
	 /**
	 * Sets the tag link font sizes.
	 * @param {object} [data]
	 *                 Event data from the render phase
	 * @returns {number} Threshold
	 */
	setFontSizes : function(tags) {
		var max = 0, sizes = [], counts = [];
		var numSort = function(a,b) { return a > b ? 1 : (a < b ? -1 : 0); };
		
		for(var i=0, len = tags.length; i < len; i++) {
			if(tags[i].count > max) {
				max = tags[i].count;
			}
			counts.push(tags[i].count);
		}
		
		// Set the font sizes.
		for(var i=0, len = tags.length; i < len; i++) {
			// Minimum size of 50%, must be an integer in multiples of 10.
			sizes.push(Math.max(this.config.minFontSize, Math.floor((this.config.maxFontSize / 20) * (1.0 + (1.5*tags[i].count-max/2) / max)) * 10));
			tags[i].fontSize = sizes[sizes.length - 1] + "%";
		}
		
		// Only return a non-zero threshold if tags will be hidden.
		if (this.config.pageType == 'hint') {
			sizes = sizes.sort(numSort);
			return sizes.length && this.config.pageSize && sizes.length > this.config.pageSize && sizes[sizes.length - this.config.pageSize] > sizes[0] ? sizes[sizes.length - this.config.pageSize] : 0;
		}
		else if (this.config.pageType == 'limit') {
			counts = counts.sort(numSort);
			return counts.length && this.config.pageSize && counts.length > this.config.pageSize /*&& counts[counts.length - this.config.pageSize] > counts[0]*/ ? counts[counts.length - this.config.pageSize] : 0;
		}
		else {
			return 0;
		}
	}
};

HD.extend(HD.TagCloudWidget, [HD.Widget]);

HD.register('hd_tag_cloud_widget', 'HD.TagCloudWidget', {version: "1.0", build: "1"});
