function HeadupAnnotation() {
	_headup = this;		// set alias name
    this.widgetMode = 'tabs';
    this.Terms;
    this.toStop = false;
	
	this.allowedConfigs = [];

    this.HeadupWidth = 407;
    this.HeadupHeight = 414;
    this.OpenTimeStatistics = 3 * 1000;

    this.openedTerm = null;

    /***************************
    <script type='text/javascript'>
    var hc_Customer = 'jpost';
    var hc_MaxTotalAnnotations = 20;
    var hc_MaxDuplicateAnnotations = 2;
    var hc_HoverToOpenTime = 1500;
    var hc_MarkLinks = false;
	var hc_Annotation = true;
    </script>
    ****************************/
	
    this.customer = this.isDefined('hc_Customer') ? hc_Customer : this.getCustomerId() || 'genericwidget';
    this.totalAnnotationsLeft = 100;
    this.maxDuplicateAnnotations = 2;
    this.markLinks = false;
	this.markColor;

    this.widgetTimerID;

	this.annotateResult = null;
	this.gloablFuncCounter = 0;
    this.numberOfAnnotations = 0;

	this.triggeredId = "headup_triggered";
    this.hoveredElement = null;
	this.dragged = false;
	this.closeOnMouseOut = true;
	this.hoverToOpenTime = 500;
	
	this.popupTimeout = 0;
    this.x = this.y = 0;
	
    this.preloaded = false;
	this.testGroup = this.getTestGroup();
	//this.initTestGroup();
    this.HeadupInCenter = false;
    
	this.HeadupOpenFlag = false;
	this.HeadupCloseFlag = false;
	
	this.sessionId;
	this.widgetId;
	this.elementsAnnotated = [];

/* Default Tags Blacklist 
--------------------------------*/
	this.TagsBlackList = ['option','button','script','noscript','headupmark','textarea','style','input','label','object','iframe','param','embed','img','video','link'];
	this.classIdBlackList = [];

	this.clientHost = 'http://mint1.headup.com';
    this.cdnHost    = 'http://static1.headup.com/';
}

HeadupAnnotation.prototype.generateGuid = function(){
	try{
		function S4() {
		   return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
		}
		
		return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
	}
	catch (err){
        this.ShowLog('generateGuid', err);
    }
}

HeadupAnnotation.prototype.userGuid = function() {
	try{
		var guid = this.readCookie('headup-user-guid');
		if (guid){
			return guid;
		}
		else {
			guid = _headup.generateGuid();
			this.createCookie("headup-user-guid", guid, 365);
			return guid;
		}
	}
	catch (err){
		return 0;
        this.ShowLog('userGuid', err);
    }
}

HeadupAnnotation.prototype.initTestGroup = function() {
	
}

HeadupAnnotation.prototype.getTestGroup = function() {
	try{
		var testGroupSubjects = ['ruhanirabin.com','heyuguys.co.uk','jewlicious.com'], testGroup = 'b';
		var d = document.domain.replace('www.','');
		for( var i=0; i < testGroupSubjects.length; i++ ){
			if( testGroupSubjects[i] == d ){
				testGroup = _headup.readCookie('testGroup');
				if (testGroup)
					return testGroup;

				testGroup = (Math.random() > 0.9) ? "a" : "b";
				_headup.createCookie("testGroup", testGroup, 365);
				return testGroup;
			}
		}
		return testGroup;
	}
	catch (err){
        this.ShowLog('getTestGroup', err);
    }
}

// createCookie
HeadupAnnotation.prototype.createCookie = function(name,value,days) {
	var expires;
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		expires = "; expires="+date.toGMTString();
	}
	else 
		expires = "";
	document.cookie = name+"="+ value + expires +"; path=/";
}

// readCookie
HeadupAnnotation.prototype.readCookie = function(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	}
	return null;
}

// eraseCookie
HeadupAnnotation.prototype.eraseCookie = function(name) {
	this.createCookie(name,"",-1);
}

HeadupAnnotation.prototype.OnPageLoad = function() {
    try {
        this.sessionId = _headup.generateGuid();
		/*
        window.onbeforeunload = function() {
            _headup.writeStatistic({ event: 'onbeforeunload' });
        }
		*/
        if (!document.body || !this.isPermittedBrowser())
            return;
        // removed by talm until further testing	
        //_headup.initUserSelection();

        this.loadSettings();
		if (!this.isDefined('hc_Annotation')) {
			var url = this.annotationUrl + escape(window.location.href);
			this.loadJSON(url, '_headup.annotationDone');
		}
    }
    catch (err) {
        this.ShowLog('OnPageLoad', err);
    }
}

HeadupAnnotation.prototype.initUserSelection = function() {
	try{
		var selection, selectionText, selectionButton, newRange;
		var speed = j$.browser.msie ? 0 : 80;
		
		j$('span.headup-bubble').live('click',function(e){
			e.preventDefault();
			_headup.mouseX = e.pageX;
			_headup.mouseY = e.pageY;
			_headup.OpenOnText(selectionText, true);
			j$(selectionButton).css('visibility','hidden');
			_headup.writeStatistic({ event:'manualAnnotaion', term:selectionText });
		});
		
		j$(document).bind("mouseup", function(e){
			if (e.target == selectionButton)
				return false;
				
			if (selectionButton)
				cleanUp();
			selection = getSelectedText();
			selectionText = selection && selection.toString();
			
			var wc = wordCount(selectionText);
			
			if (selectionText && wc < 6){
				insertButton();
			}
		}).bind("mousedown", function(e){
			if (selectionButton && e.target != selectionButton)
				cleanUp(); 
		});
		
		function cleanUp() {
			selection = null;
			newRange && newRange.pasteHTML && newRange.pasteHTML('');
			newRange = null;
			j$(selectionButton).fadeOut(speed, function(){ j$(this).remove() });
			selectionButton = null;
			selectionText = '';
		}
		
		function insertButton(){
			try{
				selectionButton = document.createElement("span");
				selectionButton.title = 'Learn More';
				selectionButton.className = 'headup-bubble';
				//j$(selectionButton).css("opacity",0.8);
				
				if (document.createRange) {
					var range = selection.getRangeAt(0);
					newRange = document.createRange();
					newRange.setStart(selection.focusNode, range.endOffset);
					newRange.insertNode(selectionButton);
				}
				else{
					newRange = selection.duplicate();
					newRange.setEndPoint( "StartToEnd", selection);
					newRange.pasteHTML( selectionButton.outerHTML );
					selectionButton = j$("span.headup-bubble"); // rebind it
				}
			}
			catch (err){ }
		}
		
		function getSelectedText() {
			var txt = '';
			if( window.getSelection ){
				return window.getSelection();
			}
			else if( document.getSelection ){
				return document.getSelection();
			}
			else if( document.selection && document.selection.createRange() ){
				var selection = document.selection && document.selection.createRange();
				selection.toString = function() { return this.text };
				return selection;
			}
			else return false; 
		}
		
		function wordCount(inStr) {
			var wc;
			wc = inStr && inStr.replace(/[^\s\w]+/g, ""); // get rid of punctuation
			wc = wc && wc.replace(/^\s*/, "").replace(/\s*$/, ""); // trim
			wc = wc && wc.length && wc.split(/\s+/).length; // split & count
			return Number(wc);
		}
	}
	catch (err) {
        this.ShowLog('initUserSelection', err);
    }
}

HeadupAnnotation.prototype.initTemplate = function() {
    // Load external Headup CSS file
	var cssfile = this.widgetMode == 'snippet' ? '/css/CharmSnippetUI.css' : '/css/CharmUI.css';
	var myStylesLocation = this.serverWorkplace + cssfile;
	j$('<link rel="stylesheet" type="text/css" href="' + myStylesLocation + '" >').appendTo("head");
    this.templateUrl = this.templateUrl + '&v=1.1';
    this.loadJSON(this.templateUrl, '_headup.TemplateRetrieved');
}

HeadupAnnotation.prototype.TemplateRetrieved = function(data) {
    try {
        // Get the desired widget type from the template response, accorings to 'this.widgetMode'
        var response = j$('<div/>').html(data);
        var index = this.widgetMode == 'snippet' ? 1 : 0;
        var headup_widget = response.children('div')[index];

        j$("body").append(headup_widget);
        _headup.headupFrame = j$(headup_widget);
		_headup.widgetTitle = _headup.headupFrame.find("div.headup-header");
		
		var shadowBG = j$('<div class="shadowBG"></div>');
		
		if( document.compatMode == 'BackCompat' && j$.browser.msie )
			shadowBG.width(368).height(262);
	
		this.headupFrame.append(shadowBG);
		
		// sets widget width & height
		if( this.widgetMode == 'snippet' ){
			this.HeadupWidth = 360;
			this.HeadupHeight = 254;
		} else{
			this.HeadupWidth = 407;
			this.HeadupHeight = 414;
		}
		// firefox and webkit uses box-shadow CSS instead of a shadow image that is been streched
		if( j$.browser.msie )
			this.headupFrame.append('<img src="'+this.serverWorkplace +'/images/shadowBG.png" alt="" id="shadowBG" />');

		_headup.UserConfig();
		
		// bind the satisfy (yes/no) buttons 
		_headup.satisfyCode = j$("#headup-satisfy").html();
		_headup.satisfy();
		
		// Close the popup when pressing [ESC] key
		j$(document).keyup(function(e){
			if( j$("#headup").is(":visible") && e.keyCode == 27){
				_headup.CloseHeadup();
			}
		});
		
		j$("#headup button.headup-close-btn").click(function(){
			_headup.CloseHeadup();
		});
		
		j$('#headup #headup-stop-mark').click(function(){
			_headup.dialogBox.show("Headup content will be turned off<br /><small>(Clear cookies to restore)</small>");
		});
		
		// bind events to the frame, only for annotated text term
		if( _headup.widgetMode == 'snippet' && _headup.closeOnMouseOut ){
			_headup.headupFrame.mouseenter(function(e){
				clearTimeout(_headup.popupTimeout);
			}).mouseleave(function(e){
				if( e.relatedTarget == _headup.hoveredElement || _headup.dragged || _headup.HeadupCloseFlag ){
					clearTimeout(_headup.popupTimeout);
					return false;
				}
				clearTimeout(_headup.popupTimeout);
				_headup.popupTimeout = setTimeout(function(){_headup.CloseHeadup()},1000);
			});
		}

        //this.headupFrame.css({ zIndex: 100, position: 'absolute', top: '50%', left: '50%', width: this.HeadupWidth, height: this.HeadupHeight, marginTop: (-this.HeadupHeight/2), marginLeft: (-this.HeadupWidth/2) });
        //this.headupFrame.css({ marginTop: (-this.HeadupHeight/2), marginLeft: (-this.HeadupWidth/2) });
        var handle = 'div.headup-header';

		//var cursor = j$.browser.mozilla ? '-moz-grabbing' : 'move';
		
		if( j$().draggable )
			this.headupFrame.draggable({
				iframeFix: true,
				cancel: 'h1',
				handle: handle, 
				start: function(event, ui){
					_headup.headupFrame.addClass('unselectable').find("b.pointer").hide();
					_headup.dragged = true;
					clearTimeout(_headup.popupTimeout);
				},
				stop: function(event, ui){ 
					_headup.headupFrame.removeClass('unselectable') 
				},
				//containment: [ -20, -20, document.width - _headup.HeadupWidth - 30, window.innerHeight - _headup.HeadupHeight/2 ],
				drag: function(event, ui){
					_headup.adjustShadow( 
						j$(window).width(), 
						j$(window).height(), 
						Math.floor(_headup.headupFrame.offset().left + _headup.HeadupWidth/2 + 25), 
						Math.floor(_headup.headupFrame.offset().top + _headup.HeadupHeight/2 + 25)-j$(window).scrollTop() 
					);
				}
			});
	}
    catch (err) {
        this.ShowLog('TemplateRetrieved', err);
    }
}

HeadupAnnotation.prototype.adjustShadow = function(w,h,sw,sh) {
	//sw = w/sw > 2 ? sw - this.HeadupWidth/2 - 25 : sw + this.HeadupWidth/2 - 25;
	//sh = h/sh > 2 ? sh - this.HeadupHeight/2 - 25 : sh + this.HeadupHeight/2 - 25;
	var x = (24/w)*sw - 12,
		y = (24/h)*sh - 12;
	
	var tiltShadowLeft = -x +'px', 
		tiltShadowTop = -y +'px';
	_headup.headupFrame.find("div.shadowBG")
		.css('-mozBoxShadow',tiltShadowLeft+' '+ tiltShadowTop +' 15px rgba(0,0,0,0.4)')
		.css('-webkitBoxShadow',tiltShadowLeft+' '+ tiltShadowTop +' 15px rgba(0,0,0,0.4)');
}

HeadupAnnotation.prototype.isPermittedBrowser = function() {
    if (this.isChrome())
        return true;
    else if (j$.browser.msie && j$.browser.version == 6.0)
        return false;
    else
        return true;
}

HeadupAnnotation.prototype.isChrome = function(){
    return navigator.userAgent.toLowerCase().indexOf('chrome') != -1;
}

// Get the customerId from the annotate.js?customerid=something
HeadupAnnotation.prototype.getCustomerId = function(){
	var scripts = document.getElementsByTagName('script');

	var this_script_tag;
	for(var i=0; i < scripts.length; i++){
		if( scripts[i].src && scripts[i].src.indexOf('annotate.js') != -1 )
			this_script_tag = scripts[i];
	}

	//var s = (script.getAttribute.length !== undefined) ? script.getAttribute('src') : script.getAttribute('src', 2);
	/*
	var this_script_tag = scripts[scripts.length - 1]; //script tag of this file
	var this_script_tag = scripts[0]; //script tag of this file, which is currently append as the first script tag
	*/

	if( typeof this_script_tag != undefined && this_script_tag.src )
		var s = this_script_tag.src;
	else{
		return this.customer;
	}
	return _headup.gup('customerid',s) || _headup.customer;
};


/*  loadSettings is only called with the extra argument 'cutomer' when the
**  headup.customer returned no configuration data from the server, then
**  loadSettings is called with the default customerId = 'genericWidget'
*/ 
HeadupAnnotation.prototype.loadSettings = function(customer) {
	try{
		this.customer = customer ? customer : this.customer;

		if( this.isDefined('hc_MaxTotalAnnotations') )
			this.totalAnnotationsLeft = hc_MaxTotalAnnotations;
			
		//if user choose not to annotate any words
		if( this.isDefined('hc_Annotation') )
			this.totalAnnotationsLeft = 0;

		if( this.isDefined('hc_MaxDuplicateAnnotations') )
			this.maxDuplicateAnnotations = hc_MaxDuplicateAnnotations;

		if( this.isDefined('hc_HoverToOpenTime') )
			this.hoverToOpenTime = hc_HoverToOpenTime;

		if( this.isDefined('hc_MarkLinks') )
			this.markLinks = hc_MarkLinks;
			
		if( this.isDefined('headupDirectLink') )
			this.widgetMode = 'link';
			
		this.oldSettings();

		//this.backOfficeUrl = this.clientHost + '/Services/Mint/ClientScripts/' + 'backoffice-' + this.customer +'.js';

		this.annotationUrl 		= this.replaceText( this.def_annotationUrl );
		this.annotationTextUrl 	= this.replaceText( this.def_annotationTextUrl );
		this.annotationUserText = this.replaceText( this.def_annotationUserText );
		this.annotationBlockUrl = this.replaceText( this.def_annotationBlockUrl );
		this.serverConfigUrl 	= this.replaceText( this.def_serverConfigUrl );
		this.templateUrl 		= this.replaceText( this.def_templateUrl );
		//this.widgetPage		= this.replaceText( this.def_widgetPage ); // MOVED TO loadServerSettings()
		//this.backOfficeUrl 	= this.replaceText(this.backOfficeUrl);

		// only after the JSON is retrieved from server, continue to initTemplate()
		
		this.loadJSON(this.serverConfigUrl, '_headup.loadServerSettings');
	}
    catch( err ){
		this.ShowLog('loadSettings', err);
    }
}

HeadupAnnotation.prototype.replaceText = function(str) {
	if(str) return str.replace( '[customer]', this.customer ).replace( '[AB]', this.testGroup );
}

HeadupAnnotation.prototype.oldSettings = function() {
	var mintObj = j$('#semantinet-mint');
	if( mintObj.length ){
		if( mintObj.attr('customerid') )
			this.customer = mintObj.attr('customerid');

		if( mintObj.attr('maxTotalAnnotations') )
			this.totalAnnotationsLeft = mintObj.attr('maxTotalAnnotations');

		if( mintObj.attr('maxDuplicateAnnotations') )
			this.maxDuplicateAnnotations = mintObj.attr('maxDuplicateAnnotations');
	}
}

HeadupAnnotation.prototype.loadServerSettings = function(data) {
    try{
		_headup.userConfigJSON = data;
		//_headup.UserConfig();  // should be called when '_headup.userConfigJSON' is ready & template is loaded in page
        if( data ){
            this.totalAnnotationsLeft = data.MaxAnnotations;
            this.maxDuplicateAnnotations = data.MaxDuplicateAnnotations;
			
			// Tags blacklist
			if( data.TagsBlackArray )
				this.TagsBlackList = this.TagsBlackList.concat(data.TagsBlackArray).unique();
			
			// Classes & Ids blacklist
			if( data.ClassesBlackArray )
				this.classIdBlackList = this.classIdBlackList.concat(data.ClassesBlackArray).unique();
				
			if( data.MarkColor )
				_headup.markColor = data.MarkColor;
			
			if( data.pathBlackList )
				_headup.pathBlackList = data.pathBlackList;
			
            //j$.getScript(_headup.backOfficeUrl);
			
			if( data.WidgetMode )
				this.widgetMode = this.testGroup == 'a' ? 'snippet' : data.WidgetMode.toLowerCase() || this.widgetMode;  // if null use default, if A/B testing, use option 'a'
			
			if( this.widgetMode == 'snippet' )
				this.def_widgetPage = this.serverWorkplace + '/SnippetPage.htm?v=2#&uri=[uri]&configid=[customer]&abtest=[AB]';
			
			// re-creat the widgetPage link, so it will reffer to the right location, in association to the widgetMode.
			this.widgetPage	= this.replaceText( this.def_widgetPage );
			
			this.writeStatistic({ event: 'PageView' });  // only after we have all the user settings
			
			if( _headup.widgetMode == 'link' ){
				this.TagsBlackList.push('a');
				j$('a.headupLink').live('click',function(){
					_headup.writeStatistic({ event:'tpClick' });
				});
			}
			// if not 'link' mode, then init the template
			else{
				this.markLinks = data.MarkLinks;
				if( !data.MarkLinks )
					this.TagsBlackList.push('a');

				this.initTemplate();
			}
        }
		else{
			this.writeStatistic({ exception:'error', name:'loadServerSettings', error:'Configuration data for: "' + _headup.customer + '" is null or empty' });
			// if user configuration returned empty, try to load the settings again with the default one.
			if( _headup.customer != 'genericwidget' )
				_headup.loadSettings('genericwidget');
			return false;
		}
		
		_headup.annotationDone();
    }
    catch( err ){
		this.ShowLog('loadServerSettings', err);
    }
}

/* set extra publisher configuration */
HeadupAnnotation.prototype.UserConfig = function() {
	try {
		this.pathBlackList = [];
		// UserConfig is called twice, so need to check both tests are 'true' before continue
		if( j$("#headup").length && _headup.userConfigJSON ){
			if( _headup.userConfigJSON.CustomLogo ){
				var customImg = new Image();
				customImg.onload = function(){
					this.onload = null;
					
					customImg.removeAttribute ("width");
					customImg.removeAttribute ("height");

					// position the logo in the middle
					var tempHeightTest = _headup.widgetMode == 'tabs' ? 27 : 22;
					
					if( customImg.height < tempHeightTest ){
						customImg.style.top = '50%';
						customImg.style.marginTop = -customImg.height/2 + 'px';
					}
					else{
						//customImg.height = tempHeightTest;
					}
					
					// if image has a url, wrap it around it
					if( _headup.userConfigJSON.CustomLogoUrl )
						customImg = j$("<a class='customLogo' href='"+ _headup.userConfigJSON.CustomLogoUrl +"' style='float:left; margin-right:10px; height:100%;'></a>").append(customImg);
					
					_headup.headupFrame.find('div.headup-footer').prepend(customImg);
				}
				
				customImg.src 		= _headup.userConfigJSON.CustomLogo;
				customImg.alt 		= '';
				customImg.className = 'customLogo';
			}
		}
		
	}
    catch (err) {
        this.ShowLog('UserConfig', err);
    }
}

HeadupAnnotation.prototype.loadJSON = function(url, callback) {
    if(callback != null)
        url = url.replace('callback=\?', 'callback=' + callback);
		
    j$.getScript(url);
}

/*	Handle Get and Set CSS Rules
---------------------------------------*/
	HeadupAnnotation.prototype.setStyleRule = function(selector, rule){
		// TODO: make sure that aditional selectors are added to the same <style> and not create others each time this function is invoked
		/*
		var stylesheet;
        for( var i in document.styleSheets ){
			if( !document.styleSheets[i].href ){
				stylesheet = document.styleSheets[i];
				
				if( stylesheet.addRule ){
					stylesheet.addRule(selector, rule);
				} 
				else if( stylesheet.insertRule ){
					stylesheet.insertRule(selector + ' { ' + rule + ' }', stylesheet.cssRules.length);
				}

				return;
			}
        }
		*/
		
		// added 'body' to strenghten the css specificity (IE thing)
		j$("head").append('<style type="text/css">body ' + selector + '{' + rule + '}' + '</style>');
		
		//stylesheet = document.createElement('style');
		//document.documentElement.firstChild.appendChild(stylesheet);
		//stylesheet = document.head.styleSheets[document.head.styleSheets.length];  // should be the one we just inserted
	
	}
	
	/* getStyle -> grabs a certain style from a CSS selector */
	HeadupAnnotation.prototype.getStyle = function(selector, property) {
		el = j$(selector)[0];
		if( window.getComputedStyle ){
			return document.defaultView.getComputedStyle(el, null).getPropertyValue(property);  
		}
		else{
			return el.currentStyle.name;
		}
	}

// annotationDone: a callback function after the page has been analyzied 
// by the server and returned the terms that should be annotated.
HeadupAnnotation.prototype.annotationDone = function(data) {
	try{
		this.gloablFuncCounter++;
		
		this.annotateResult = data || this.annotateResult;
		if( this.gloablFuncCounter < 2 )
			return false;
		
		// starts scanning the DOM only when it's ready
		j$(document).ready(function(){
			data = _headup.annotateResult;
			
			var termsToAnnotateArr=[0], stat = {event:'annotated'};
			
			// extract 'termsToAnnotateArr' for satistics
			if(data && typeof data == 'object' && data.hasOwnProperty('Terms') ){
				for(var i in data.Terms){
					if( data.Terms.hasOwnProperty(i) )
						termsToAnnotateArr[i] = data.Terms[i].Term;
				}
				
				stat.terms_from_server = termsToAnnotateArr.join('$$');
				
				if( _headup.markColor )
					_headup.setStyleRule( '.headupTerm','border-color:' + _headup.markColor );
				
				//else{
				//	var siteLinksColor = _headup.getStyle('a', 'color');
				//	_headup.setStyleRule( '.headupTerm','border-color:' + siteLinksColor );
				//}
				
				if( _headup.widgetMode != 'link' )
					_headup.bindAnnotations();

				_headup.Terms = data.Terms;
				// hc_Path -> defines a spesific path to start scanning DOM from
				var element = _headup.isDefined('hc_Path') ? j$(hc_Path)[0] : document.body;
				var elementsFound = 0, elementsToExplore = ['body div.post','body div.entry, body div.entry_body_text'];
				
				for( var i=0; i < elementsToExplore.length; i++ ){
					var jPath = elementsToExplore[i];
					j$(jPath).each(function(){
						elementsFound++;
						// re-defined the max number of annotations per post, before every AnnotateRecursive call on each post
						_headup.postAnnotationsLeft = _headup.totalAnnotationsLeft;
						_headup.AnnotateRecursive( this, {} );
					});
				}
				// if no elements found, start scanning from the BODY element
				if( elementsFound == 0 ){
					_headup.postAnnotationsLeft = _headup.totalAnnotationsLeft;
					_headup.AnnotateRecursive( element, {} );
				}

			} else{
				stat.terms = 0;
				stat.terms_from_server = 0;
			}
			
			if( !data || typeof data != 'object' ){
				stat.error = 'bad response: '+ data;
			}
			
			_headup.elementsAnnotatedReport( stat );
			
		});
	}
	catch(err) {
		this.ShowLog('annotationDone', err);
	}
}

HeadupAnnotation.prototype.elementsAnnotatedReport = function(stat) {
	var sortedArr = _headup.elementsAnnotated.sort();
	sortedArr = this.handleDuplicates(sortedArr);
	
	stat.terms = sortedArr.length ? sortedArr.join('$$') : 0;
			
	// split the annotated terms statistics if GET querystring length is too long
	if( stat.terms && stat.terms.length > 1500 ){
		var i = 0, chunk = 15;  // chunk : how much to send for each statistic HTTP request
		stat.batchSize = Math.ceil( sortedArr.length / chunk ) + 1; // batchSize : total number of requests that will be sent
		_headup.writeStatistic({ event:stat.event, terms_from_server:stat.terms_from_server, batchSize:stat.batchSize });

		(function cut(){
			i += chunk;
			var b = sortedArr.slice(i-chunk,i);
			stat.terms = b.join('$$');
			_headup.writeStatistic({ event:stat.event, terms:stat.terms, batchSize:stat.batchSize });
			if( b.length == chunk && i < sortedArr.length )
				cut();
		})();
	}
	else{
		stat.batchSize = 1;
		this.writeStatistic(stat);
	}
}

HeadupAnnotation.prototype.handleDuplicates = function(arr) {
	var i, len=arr.length, out=[], obj={};

	for (i=0; i<len; i++) {
		var item = arr[i];									// for arr=['x','y','y'] get-> obj['x'], obj['y']
		obj[item] = obj[item] >= 1 ? obj[item] + 1 : 1;     // obj['x'] = 1, obj['y'] = 1, obj['y'] = 2
	}
	for (var i in obj) {
		out.push([i+'$$'+obj[i]]);
	}
	return out;
}

HeadupAnnotation.prototype.bindAnnotations = function() {
	try {
		j$(".headupTerm").live("mouseover",function(e){
			j$(this).addClass("hover");
			if( this != _headup.hoveredElement ){
				clearTimeout(_headup.popupTimeout);
				_headup.popupTimeout = setTimeout(function(){
					// only 1 unique headup link has this ID. if mouse is
					// over this element, then do not close the popup
					_headup.CloseHeadup(true);
					//j$("#headupTrigger").removeAttr("id"); // already inside _headup.CloseHeadup();
					_headup.hoveredElement = this
					//_headup.hoveredElement.attr('id',_headup.triggeredId);
					_headup.OnMouseOverHandler(e);
				},_headup.hoverToOpenTime);
			}else
				clearTimeout(_headup.popupTimeout);
		}).live("mouseout",function(e){
			if( !_headup.dragged && _headup.widgetMode == 'snippet' && _headup.closeOnMouseOut ){
				clearTimeout(_headup.popupTimeout);
				_headup.popupTimeout = setTimeout(function(){
					_headup.CloseHeadup();
				},1000);
			} else
				clearTimeout(_headup.popupTimeout);
			j$(this).removeClass("hover");
		});
		
		/*  assign events to the term that triggered the popup
		and the popup itself, and start a timeout.
		if mouse leaves the term, or the popup, then close it
		-------------------------------------------------------------
		if( this.widgetMode == 'snippet' ){
			j$(_headup.hoveredElement).mouseleave(function(){
				if( !_headup.dragged ){
					clearTimeout(_headup.popupTimeout);
					_headup.popupTimeout = setTimeout(function(){_headup.CloseHeadup(true)},500);
				}
			}).mouseenter(function(){
				clearTimeout(_headup.popupTimeout);
			});
		}
		*/
	
	}
    catch (err) {
		this.ShowLog('bindAnnotations', err);
    }
}

HeadupAnnotation.prototype.OnMouseOverHandler = function(event) {
    var target = event.srcElement;
    if (target == null)
        target = event.target;
    this.hoveredElement = target;
	
	this.linkTop = j$(target).offset().top;
    this.linkLeft = j$(target).offset().left;
	this.linkWidth = target.offsetWidth;
	this.linkHeight = j$(target).height();
	
    this.x = event.pageX;
    this.y = event.pageY;

    if (this.x < 0)
        this.x = 0;
    if (this.y < 0)
        this.y = 0;
		
    var item = _headup.hoveredElement['headup'];
	this.dragged = false; // need to reset this for every link, but only for annotated text
    _headup.OpenHeadup( item.Senses[0], this.x, this.y, true );
    // init entity type & combobox
    _headup.initSuggestMenu(item.Senses);
}

HeadupAnnotation.prototype.AnnotateRecursive = function(element, alreadyAnnotated){
    try{
		if( _headup.postAnnotationsLeft == 0 )
			return;

		if( _headup.toStop ) 
			return;

		if( element.nodeType === 1 && !_headup.ValidElementToAnnotate(element) )
			return;

		// if its a text node, and its not empty or has whitespaces & is more than 2 chars
		if (element.nodeType == 3 && j$.trim(element.nodeValue).length > 2 ){
			var nodeValue = element.nodeValue.toLowerCase();
			var index = 0;
			for (var i = 0; i < _headup.Terms.length; i++){
				if( _headup.widgetMode == 'snippet' && !_headup.Terms[i].Senses[0].Snippet )
					continue;
	
				// check how many annotations remains. if true, keep annotating
				if (_headup.postAnnotationsLeft > 0) {
					var loweredTerm = _headup.Terms[i].Term.toLowerCase(),
						timesAnnotatedSoFar = alreadyAnnotated[loweredTerm] || 0;

					if (timesAnnotatedSoFar <= _headup.maxDuplicateAnnotations){
						// always continute searching from the last place found for each text node. if the tests didn't pass, 
						// then try again searching for the same term (i--), but start from the last place that was found+1, so it won't fail again on the same place.
						index = nodeValue.indexOf(loweredTerm, index);
						if( index != -1 ){
							// check if the term found in the textNode should be annotated (by a set of rules)
							var invalidNodeValue = _headup.invalidNodeValue(element.nodeValue,index,loweredTerm);
							
							if( invalidNodeValue ){
								index++;
								i--; continue;
							}

							var beforeText = element.nodeValue.substr(0, index);
							var term = document.createTextNode( element.nodeValue.substr(index, _headup.Terms[i].Term.length) );
							var afterText = element.nodeValue.substr(index + term.length);
							
							var beforeElement = document.createTextNode(beforeText);
							
							var headupElement = _headup.widgetMode == 'link' ? 'a' : 'headupmark';
							var termElement = document.createElement(headupElement);
								
							var uri = _headup.Terms[i].Senses[0].URI.replace('http://schemas.semantinet.com/','');
							
							if( _headup.widgetMode == 'link' ){
								var confId = _headup.customer != 'genericwidget' ? '&configid=' + _headup.customer : '';

								termElement.href 	  = _headup.clientHost + '/Services/FrontService/Horizon/default.htm?uri=http://schemas.semantinet.com/' + encodeURI(uri) + '&name=' + _headup.Terms[i].Senses[0].Term + confId;
								termElement.target 	  = '_blank';
								termElement.className = 'headupLink';
								termElement.title 	  = 'Click to find out more about this topic';
							}
							else
								termElement.className = 'headupTerm';
							
							termElement.appendChild(term);
							
							termElement['headup'] = _headup.Terms[i];
							
							_headup.elementsAnnotated.push(uri);
							_headup.numberOfAnnotations++;
							
							var afterElement = document.createTextNode(afterText);
							
							var parent = element.parentNode;
							//var newDoc = document.createDocumentFragment();

							parent.insertBefore(beforeElement, element);
							parent.insertBefore(termElement, element);
							parent.insertBefore(afterElement, element);

							//newDoc.appendChild(beforeElement);
							//newDoc.appendChild(termElement);
							//newDoc.appendChild(afterElement);

							parent.removeChild(element);
							//parent.appendChild(newDoc);

							timesAnnotatedSoFar = alreadyAnnotated[loweredTerm] ? alreadyAnnotated[loweredTerm]+1 : 1;
							alreadyAnnotated[loweredTerm] = timesAnnotatedSoFar;
							_headup.postAnnotationsLeft--;
							_headup.AnnotateRecursive(parent, alreadyAnnotated);
							return;
						}
					}
				}
				index = 0;
			}
		}
		else{
			for (var i = 0; i < element.childNodes.length; i++){
				_headup.AnnotateRecursive(element.childNodes[i], alreadyAnnotated );
			}
		}
    }
    catch(err){
		_headup.elementsAnnotated.push("-1");
        _headup.toStop = true;
        _headup.ShowLog('Annotate', err);
    }
}


// invalidNodeValue should return true if one of the tests faild 
HeadupAnnotation.prototype.invalidNodeValue = function(text, index, term){
	// var delimiters = " ’.:;!,?-()[]\n\r\"'"; not in use
	var fc = text.charAt(index),					// first char
		alc = text.charAt(index+term.length),		// after last char
		bfc = text.charAt(index-1); 				// before first char
	if (_headup.customer == "ivc")					// ivc is demo hebrew configuration
		return false;
	else
		return	fc == fc.toLowerCase() || 					// check if first char is lowercase
				bfc.toLowerCase() != bfc.toUpperCase() || 	// check if its a letter ( 'a' != 'A' means its a letter )
				(bfc >= '0' && bfc <='9') ||
				alc.toLowerCase() != alc.toUpperCase() ||
				(alc >= '0' && alc <='9') ||
				bfc == '.' && alc == '.';
}

HeadupAnnotation.prototype.ValidElementToAnnotate = function(element) {
	try{
		var tag = element.tagName.toLowerCase(),
		isValid = true;
		
		if( j$.inArray(tag, this.TagsBlackList) != -1 ){
			return false;
		}
		
		var isLink = element.nodeType === 1 && element.tagName === "A";
		var sameDomain = element.host == window.location.host;

		if( isLink && sameDomain )
			return false;
		
		if( isLink && element.href ){
			var isActionLink = j$(element).attr("href").toLowerCase().indexOf('javascript:') == 0;
			if( isActionLink )
				return false;
		}
		
		if (element.className.toLowerCase() == 'no_headup' || element.id == 'headup' || j$.inArray(element.id, this.classIdBlackList) != -1 )
			return false;
		
		// go through all the classes this element has and check if one blacklisted
		if( element.className ){
			var elClassArr = element.className.toLowerCase().split(' ');
			
			for(var className=0; className < elClassArr; className++){
				if( j$.inArray(elClassArr[className], this.classIdBlackList) != -1 )
					return false;
			}
		}
		
		// example :
		// _headup.pathBlackList = ['body > p:last > span:first'];
		
		var pbl = this.pathBlackList;
		if( pbl && pbl.length && pbl.length > 0 ){
			for(var i=0; i < pbl; i++){
				var jPath = pbl[i];
				// might find more than one element in the document
				j$(jPath).each(function(){
					// if the element we're validating is in the pathBlackList, return false
					if(this === element){
						//console.log(this +" : "+ element);
						isValid = false;
					}
				});
			}
		}
		
		return isValid;
	}
	catch(err){ 
		this.ShowLog('ValidElementToAnnotate', err); 
	}
}

HeadupAnnotation.prototype.alert = function(text){
	try{
		var obj = j$("<div/>");
		obj.text( text );
		obj[0].className = 'headup-alert';
		//obj.css({ display:'none', position:'absolute', padding:'10px', textAlign:'center', maxWidth:'320px', backgroundColor:'#fff', border:'5px solid #CCC', fontSize:'1.4em', color:'#666', MozBorderRadius:'6px',MozBoxShaddow:'0 0 10px rgba(0,0,0,0.8)', WebkitBorderRadius:'6px', marginTop:'10px' });
		obj.appendTo("body");
		
		if( !this.mouseX && !this.mouseY )
			obj.center();
		
		else{
			var posX = (_headup.mouseX + (obj.width()/2) > j$(window).width()) ? 'right' : Math.max(_headup.mouseX - (obj.width()/2),0);
			var posY = _headup.mouseY - 50;
			
			if( posX == 'right' )
				obj.css({ right:0, top:posY });
			else
				obj.css({ left:posX, top:posY });
		}
		
		obj.animate({ marginTop:0, opacity:'toggle' }, 200);
		setTimeout( function(){ 
			obj.animate({ marginTop:10, opacity:'toggle' }, 200, function(){ 
				obj.remove();
			});
		}, 1500);
		
		this.mouseX = this.mouseY = null;
	}
	catch(err){
        this.ShowLog('alert', err);
    }
}

HeadupAnnotation.prototype.OpenOnText = function(text, userSelected) {
	this.dragged = true; // trick so when mouseOut from the popup, it will not close
	
    var url =  userSelected ? this.annotationUserText + text : this.annotationTextUrl + text;
	this.loadJSON(url, '_headup.OpenOnTextData');
}

HeadupAnnotation.prototype.OpenOnTextData = function(data) {
	if( data.Log ){
		_headup.alert(data.Log);
		//return false;
		if( !data.Terms[0] )
			return false;
	}
	
	_headup.OpenHeadup( data.Terms[0].Senses[0] );
	_headup.initSuggestMenu( data.Terms[0].Senses );
}

//HeadupAnnotation.prototype.headupFrame = null;

HeadupAnnotation.prototype.CloseHeadup = function(quickClose){
	try{
		j$("#"+this.triggeredId).removeAttr("id");
		// if closeing has began (=true), do not continute
		if( this.HeadupCloseFlag || this.headupFrame.is(':hidden') )
			return false;
			
		this.hoveredElement = null;
		this.HeadupCloseFlag = true;
		this.HeadupOpenFlag = false;
		
		//j$("object, embed").css("visibility","visible");
		
		if(this.openedTerm)
			_headup.writeStatistic({ event:'widgetClosed' });
		this.widgetId = null; // reset the widget ID

		this.openedTerm = null;
		
		this.headupFrame.find("b.pointer").remove();
		j$('#headupWidget').remove(); 		
		
		if( j$.browser.msie )
			this.headupFrame.find("#shadowBG").hide();
			
		if(quickClose){
			this.headupFrame.hide();
			this.HeadupCloseFlag = false;
		}
		else{
			this.headupFrame.animate({ top:"+=30px", opacity:"toggle" }, 180, function(){  
				_headup.HeadupCloseFlag = false;
			})
		}
		
		_headup.dialogBox.close();
	}
	catch(err){
        this.ShowLog('CloseHeadup', err);
    }
}

HeadupAnnotation.prototype.writeStatistic = function(statsJSON) {
	var url = ["?"];

/*
	for (var key in statsJSON) {
		// make sure that the key is an actual property of an object, and doesn't come from the prototype
		if (statsJSON.hasOwnProperty(key)) {
			//alert(key + " -> " + statsJSON[key]);
			var sign = (!url[0]) ? '?' : '&';
			url.push('&');
			url.push(key + '=');
			url.push(encodeURI(statsJSON[key]));
		}
	}
*/
	// Serializes an object
	url.push( j$.param(statsJSON) );
	url.push('&customerId=' + this.customer);
	url.push('&widgetMode=' + this.widgetMode);
	url.push('&testGroup=' + this.testGroup);
	url.push('&userId=' + _headup.userGuid());
	url.push('&sessionId=');
	url.push(_headup.sessionId || 'null');
	url.push('&pageUrl=' + escape(document.location.href));
	url.push('&timeStamp=');
	url.push(new Date().getTime());

	if (this.openedTerm) {
		url.push('&widgetId=' + _headup.widgetId);
		var uri = this.openedTerm['URI'];
		uri = uri.replace('http://schemas.semantinet.com/', '');
		url.push('&URI=' + escape(uri));
	}

	var stat = new Image();
	stat.src = this.clientHost + '/Services/ClientScripts/' + 'headupStats.gif' + url.join("");
}

HeadupAnnotation.prototype.dump = function(){
	try{
		var str = '', terms = [];

		for (var i = 0; i < _headup.Terms.length; i++) {
			terms.push( _headup.Terms[i].Term );
		}

		str += '\nCustomer Id  :  ' + this.customer;
		str += '\nNumber Of Annotations  :  ' + this.numberOfAnnotations;
		str += '\nMax Annotations (per post/page)  :  ' + this.totalAnnotationsLeft;
		str += '\nMax Duplicates  :  ' + this.maxDuplicateAnnotations;
		str += '\nPosts in Page  :  ' + (j$('.post').length + j$('.entry').length);
		str += '\nMark Links  :  ' + this.markLinks;
		str += '\ntestGroup  :  ' + this.testGroup;
		
		/*
		str += '\nannotationUrl : ' + _headup.annotationUrl;
		str += '\nannotationTextUrl : ' + _headup.annotationTextUrl;
		str += '\nannotationBlockUrl : ' + _headup.annotationBlockUrl;

		str += '\nserverConfigUrl : ' + _headup.serverConfigUrl;
		str += '\ntemplateUrl : ' + _headup.templateUrl;
		str += '\nwidgetPage : ' + _headup.widgetPage;
		str += '\nbackOfficeUrl : ' + _headup.backOfficeUrl;
		*/
		str += '\n\n'; 
		
		str += _headup.Terms.length + ' terms returned from server :';
		str += '\n\n'; 
		str += terms.join(', ');

		//str += '\nclientHost : ' + this.clientHost;
		//str += '\nsettingsUrl : ' + this.settingsUrl;
		//str += '\nbackOfficeUrl : ' + this.backOfficeUrl;

		str += '\n';
		
		alert(str);
	}
	catch (err){
        this.ShowLog('dump', err);
    }
}

var lastSelected = 0;

HeadupAnnotation.prototype.getViewport = function() {
	try{
		var viewPortWidth, viewPortHeight;

		// the more standards compliant browsers (mozilla/netscape/opera/IE7) use window.innerWidth and window.innerHeight
		if (typeof window.innerWidth != 'undefined') {
			viewPortWidth = window.innerWidth,
			viewPortHeight = window.innerHeight
		}

		// IE6 in standards compliant mode (i.e. with a valid doctype as the first line in the document)
		else if( typeof document.documentElement != 'undefined' && typeof document.documentElement.clientWidth != 'undefined' && document.documentElement.clientWidth != 0 ){
			viewPortWidth = document.documentElement.clientWidth,
			viewPortHeight = document.documentElement.clientHeight
		}

		// older versions of IE
		else {
			viewPortWidth = document.getElementsByTagName('body')[0].clientWidth,
			viewPortHeight = document.getElementsByTagName('body')[0].clientHeight
		}
		return [viewPortWidth, viewPortHeight];
	}
	catch (err){
        this.ShowLog('getViewport', err);
    }
}

HeadupAnnotation.prototype.OpenHeadup = function(sense, mouseX, mouseY, openOnHover) {
	try {
		//if opening of modal has began (=true), do not continute
		if( this.HeadupOpenFlag )
			return false;
		
		this.openedTerm = sense;
		this.HeadupOpenFlag	= true;
		this.widgetId = _headup.generateGuid();
		
		_headup.writeStatistic({ event:'widgetOpened' });
		//j$("object, embed").css("visibility","hidden");
		
		//this.headupFrame.attr('style', 'display:none');

		// this.CloseHeadup(false);
		_headup.initIframe(sense);

		// shows back the 'satisfaction' question
		clearTimeout( _headup.satisfyTimer );
		j$("#headup-satisfy").stop().html(_headup.satisfyCode).show();

		// Sets the widget title
		if( sense.FullName.length > 35 ){
			var widgetTitle = sense.FullName.substring(0, 36) + "&hellip;";
			j$('#headup-suggest-text').parent().hide();
		}
		else{
			var widgetTitle = sense.FullName;
			j$('#headup-suggest-text').parent().show();
		}

		var widgetFullTitle = sense.DisplayName ? sense.FullName +', ' + sense.DisplayName : sense.FullName;
		_headup.widgetTitle.find("h1").html( widgetTitle ).attr('title',widgetFullTitle)
		_headup.headupFrame.find("button")[0].blur();  // don't select the Close button
		
		var headupWrap = _headup.headupFrame.find("div.headup-wrap"); // main content that holds the iframe
		headupWrap.hide(); // show it later, after animation
		
		var windowHeight = headup.getViewport()[1] //j$(window).height(),
			windowWidth  = headup.getViewport()[0] //j$(window).width();

		function openInCenter(){
			_headup.headupFrame.css({ width:0, height:0, margin:0, top:j$(document).scrollTop() + windowHeight/2 });
			_headup.headupFrame.animate({ width:_headup.HeadupWidth, height:_headup.HeadupHeight, marginTop:-_headup.HeadupHeight/2, marginLeft:-_headup.HeadupWidth/2 }, 200, "swing",function(){
				if( j$.browser.msie )
					_headup.headupFrame.find("#shadowBG").show();
					
				_headup.headupFrame.find("div.shadowBG").removeAttr('style');
				headupWrap.show();
				_headup.HeadupOpenFlag = false;
			});
		}
		
		if( openOnHover ){
			_headup.mouseX = mouseX;
			_headup.mouseY = mouseY;
			var sTop = j$(document).scrollTop(),
				baseline = sTop + windowHeight,
				offsetTop = 0,
				offsetLeft = 10,
				posY = this.linkTop,
				direction,
				tipY = 0, 
				tipX = 0,
				shadow = 25,
				hw = _headup.HeadupWidth + 2*shadow,
				hh = _headup.HeadupHeight + 2*shadow;
			
				var pointer = j$("<b class='pointer'></b>");
				this.headupFrame.append(pointer);

			// clac Horizontal position
			//if( this.linkLeft + this.linkWidth + hw > windowWidth )
			//	var posX = this.linkLeft - hw;
			//else
			
			var posX = (mouseX + (hw/2) > windowWidth) ? windowWidth - hw : Math.max(mouseX - (hw/2),0);
			//calc vertical position
			if( (posX < mouseX && Math.max(posX, 0) + hw > mouseX) ) {
				if( /*this.linkTop + hh + offsetTop < baseline && */mouseY - sTop > hh + offsetTop){
					posY = this.linkTop - hh - offsetTop;
					direction = "top";
				} 
				else if( this.linkTop + hh + offsetTop < baseline && mouseY - sTop < hh + offsetTop ){
					posY = this.linkTop + this.linkHeight + offsetTop;
					direction = "bottom";
				}
				else{ // If no space for the popup to be opened on top or bottom of the link, place it by its side
					if( this.linkWidth > this.linkLeft && this.linkLeft > hw || this.linkLeft + this.linkWidth + hw > windowWidth ){
						// left
						var posX = this.linkLeft - hw;
					} else{
						// right
						var posX = this.linkWidth + this.linkLeft;
					}
					
					// if distance of modal popup from mouseX > 250 or popup position might be minus
					if( mouseX - posX - hw - offsetLeft > 250 || posX < 0 || posX - mouseX - offsetLeft > 250 ){
						if( mouseX + hw > windowWidth )
							posX = mouseX - hw - offsetLeft;
						else
							posX = mouseX + offsetLeft;
					}
					var vMiddle = this.linkTop - hh/2;

					// if popup height is bigger than the viewable window height OR if check if popup has enough space to show in vertical middle, stick it to the top
					if( hh >= windowHeight || vMiddle < sTop ){
						posY = sTop;
					}
					// if when positioned at vertical middle, the popup is lower than bottom of the screen
					else if( baseline < this.linkTop + hh/2 ){
						posY = baseline - hh - offsetTop;
					}
					else{
						posY = vMiddle;
					}
					
				}
			}
			else if( this.linkTop + hh + offsetTop > baseline ){  // This part is not really needed
				posY = (hh >= windowHeight) ? sTop : baseline - hh - offsetTop;
			}
			
			if(!direction)
				direction = (Math.ceil(posX) < mouseX) ? 'left' : 'right';
			
			pointer.addClass(direction).hide();
			
			if( /(left|right)/.test(direction) && posX >= 0 ){
				tipY = mouseY - posY  - shadow;
				tipY = tipY < 10 ? 10 : tipY;
				if( tipY > (_headup.HeadupHeight - 33) ){
					tipY =  _headup.HeadupHeight - 33;
					pointer.addClass('mirror');
				}
				pointer.css({ top:tipY });
			}
			// if popup height fits into document height
			else if( baseline > posY + hh - shadow ){
				tipX = mouseX - posX - shadow;
				pointer.css({ left:tipX });
			}
			
			/*-- Claculate Shadow --*/
			_headup.adjustShadow( 
				windowWidth, 
				windowHeight, 
				Math.floor(posX + _headup.HeadupWidth/2 + shadow), 
				Math.floor(posY + _headup.HeadupHeight/2 + shadow)-j$(window).scrollTop() 
			);
			
			// If the window Height & Width can't fit for the popup, then center it
			animationSpeed = _headup.widgetMode == 'snippet' ? 250 : 290;

			if( posX >= 0 && baseline > (posY + hh/2) ){
				_headup.headupFrame.stop()
				.attr('style','')
				.css({ left:mouseX - shadow, top:mouseY - shadow, width:0, height:0 }).hide()
				.animate({ left:posX, top:posY, width:_headup.HeadupWidth, height:_headup.HeadupHeight, opacity:'toggle' }, animationSpeed, "easeOutBack",function(){
					if( j$.browser.msie ){
						_headup.headupFrame.find("#shadowBG").show();
						pointer.show();
					}
					else pointer.fadeIn('fast');
					headupWrap.fadeIn(200);
				});
			} else{
				openInCenter();
			}
		
		}else{ // open popup on center of the screen
			openInCenter();
		}
	}
	catch (err) {
		_headup.ShowLog('OpenHeadup', err);
	}
}

HeadupAnnotation.prototype.initIframe = function(sense){
	try{
		var src = _headup.widgetPage.replace('[uri]', sense.URI);
		
		// Term name is needed to generate the link inside the snippet for the topic page
		if( this.widgetMode == 'snippet' ){
			src = src + '&snippet=' + sense.Snippet + '&termName=' + sense.FullName; 
		}

		// userId, sessionId, widgetId, testGroup, widgetMode
		var statsJSON = { 
			'userId' : _headup.userGuid(),
			'sessionId' : _headup.sessionId,
			'widgetId' : _headup.widgetId,
			'testGroup' : _headup.testGroup,
			'widgetMode' : _headup.widgetMode,
			'domain' : document.domain // _headup.userConfigJSON.Domain
		}
		
		src += '&' + j$.param(statsJSON);
		
		j$('#headupWidget').remove();
		var iframeObj = document.createElement("iframe");
		iframeObj.id = 'headupWidget';
		iframeObj.scrolling = 'no';
		iframeObj.frameBorder = 0;
		iframeObj.allowTransparency = 'true';
		
		// fix chrome bug, to set src, iframe must be appended first : http://code.google.com/p/chromium/issues/detail?id=36779
		j$("#headup div.headup-iframe-wrap").html(iframeObj);
		iframeObj.src = src;
	}
	catch (err) {
		_headup.ShowLog('initIframe', err);
	}
}

HeadupAnnotation.prototype.ShowLog = function(name, err) {
    if (this.debug) {
        if (name == null)
            name == 'unknown';

        if ( err.message && err.lineNumber )
			alert(name + ': ' + err.message + '\n\Line Number: ' + err.lineNumber);
		else if (err)
			alert(name + ': ' + err.message);
        else
			alert(name + ': some error');
    }
	this.writeStatistic({ exception:'error', name:name, error:err.message, line:err.lineNumber });
}

HeadupAnnotation.prototype.loadJQueryPlugins = function() {
	j$.fn.center = function () {
		this.css("position","absolute")
			.css("top", ( j$(window).height() - this.height() ) / 2 + j$(window).scrollTop() + "px")
			.css("left", ( j$(window).width() - this.width() ) / 2 + j$(window).scrollLeft() + "px");
		return this;
	}
	
	j$.easing['jswing'] = j$.easing['swing'];

	j$.extend( j$.easing,{
		def: 'easeOutQuad',
		swing: function (x, t, b, c, d) {
			//alert(j$.easing.default);
			return j$.easing[j$.easing.def](x, t, b, c, d);
		},
		easeInQuad: function (x, t, b, c, d) {
			return c*(t/=d)*t + b;
		},
		easeOutQuad: function (x, t, b, c, d) {
			return -c *(t/=d)*(t-2) + b;
		},
		easeInBack: function (x, t, b, c, d, s) {
			if (s == undefined) s = 1.70158;
			return c*(t/=d)*t*((s+1)*t - s) + b;
		},
		easeOutBack: function (x, t, b, c, d, s) {
			if (s == undefined) s = 1.70158;
			return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
		}
	});
	
	/*
	** 	Taken from : http://debuggable.com/posts/run-intense-js-without-freezing-the-browser:480f4dd6-f864-4f72-ae16-41cccbdd56cb
	*/
	_headup.queue = {
		_timer: null,
		_queue: [],
		add: function(fn, context, time) {
			var setTimer = function(time) {
				_headup.queue._timer = setTimeout(function() {
					time = _headup.queue.add();
					if (_headup.queue._queue.length) {
						setTimer(time);
					}
				}, time || 2);
			}

			if (fn) {
				_headup.queue._queue.push([fn, context, time]);
				if (_headup.queue._queue.length == 1) {
					setTimer(time);
				}
				return;
			}

			var next = _headup.queue._queue.shift();
			if (!next) {
				return 0;
			}
			next[0].call(next[1] || window);
			return next[2];
		},
		clear: function() {
			clearTimeout(_headup.queue._timer);
			_headup.queue._queue = [];
		}
	};
}

HeadupAnnotation.prototype.onJQueryReady = function(){
	j$ = _headup.jquery_hack ? hjquery.noConflict() : jQuery.noConflict();
	if( this.old$ )
		$ = this.old$;
	
	this.loadJQueryPlugins();
	this.settings();
	
	var scriptToLoad = [];

	j$.ajaxSetup({ cache: true });
	//var jqueryUi_Url = _headup.cdnHost + 'jquery-ui-1.7.1.custom.min.js';
	var jqueryUi_Url = 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.1/jquery-ui.min.js';
	if( _headup.jquery_hack ){
		// jquery-ui is included in the hjqeury file anyway
		_headup.OnPageLoad();
	}
	else{
		j$.getScript(jqueryUi_Url, function() {
			var scriptCounter = scriptToLoad.length;
			if( scriptCounter > 0 ){
				for (var i = 0; i < scriptToLoad.length; i++)
					j$.getScript(scriptToLoad[i], scriptLoaded);
			} else
				_headup.OnPageLoad();
				
			var scriptLoaded = function() {
				if (--scriptCounter < 0)
					setTimeout(function() { _headup.OnPageLoad(); }, 0);
			}
		});
	}
}

HeadupAnnotation.prototype.preLoad = function(){
	// Allow only these customer id's
	/*
	var allowedFlag = false, allowedLength = _headup.allowedConfigs.length;
	while( allowedLength-- ){
		if( this.customer.toLowerCase() == _headup.allowedConfigs[allowedLength] ){
			allowedFlag = true;
			break;
		}
	}
	if( !allowedFlag && typeof hc_allow == 'undefined' )
		return false;
	*/
	if (this.preloaded) 
		return;
	
	this.old$ = typeof $ != 'undefined' ? $ : "";
	
	this.jquery_hack = false;	// if true, means useing hjquery because there is another jquery already on the page (older version), and we don't want to mess with that
	this.preloaded = true;
	// get jQuery version and parse it to a number 1.4.2 -> 142
	var jQVersion = typeof jQuery == 'undefined' ? 0 : Number( jQuery().jquery.replace( /\./g, '' ) );
	// check if the jQuery on the page version is what Headup needs, is less, than load hJquery (play nice with onpage script)
	var jqueryUrl_local = this.cdnHost + 'hjquery-1.4.2.min.js?v1';
	var jqueryUrl = 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js';

	if( !jQVersion )
		this.loadScript(jqueryUrl, function(){ _headup.onJQueryReady() });
	else if( jQVersion < 130 ){
		_headup.jquery_hack = true;
		this.loadScript(jqueryUrl_local, function(){ _headup.onJQueryReady() });
	}
	else
		this.onJQueryReady();
}

HeadupAnnotation.prototype.loadScript = function(url, callback){
	var head = document.getElementsByTagName("head")[0];
	var script = document.createElement("script");
	script.type = 'text/javascript';
	script.src = url;
	script.async = true;

	// Attach handlers for all browsers
	var done = false;
	script.onload = script.onreadystatechange = function(){
		if( !done && ( !this.readyState || this.readyState == "loaded" || this.readyState == "complete") ){
				done = true;

				// Continue your code
				if(callback)
					callback();

				// Handle memory leak in IE
				script.onload = script.onreadystatechange = null;
				head.removeChild( script );
		}
	};
	head.appendChild(script);
}

HeadupAnnotation.prototype.settings = function() {
    var workplace 				= 'http://mint1.headup.com/Services/FrontService'; // RemoveThisLineOnSetup1

    this.serverWorkplace 		= workplace;
    this.def_serverConfigUrl 	= workplace + '/GetConfig.ashx?v=1.1&callback=?&configId=[customer]&abtest=[AB]';
    this.def_annotationUrl 		= workplace + '/AnnotateURL.ashx?v=1.1&callback=?&configId=[customer]&abtest=[AB]&url='; // RemoveThisLineOnSetup1
    this.def_annotationTextUrl 	= workplace + '/AnnotateMintLive.ashx?v=1.1&callback=?&configId=[customer]&abtest=[AB]&text=';
    this.def_annotationUserText = workplace + '/AnnotateText.ashx?v=1.1&callback=?&configId=[customer]&abtest=[AB]&text=';
    this.def_annotationBlockUrl = workplace + '/AddToAnnotationBlackList.ashx?v=1.1&callback=?&configId=[customer]&abtest=[AB]&uri=[uri]&term=[term]';
    this.def_templateUrl 		= workplace + '/GetTemplate.ashx?v=2&callback=?&configid=[customer]&abtest=[AB]';
    this.def_widgetPage 		= workplace + '/WidgetPage.htm?v=2#&uri=[uri]&configid=[customer]&abtest=[AB]';
}

HeadupAnnotation.prototype.DoNotAnnotateClicked = function() {
    var url = this.annotationBlockUrl.replace('[uri]', this.openedTerm['uri']).replace('[term]', this.openedTerm['headup']);

    j$.getJSON(url, function(data) {
        alert(data);
    });
}

HeadupAnnotation.prototype.isDefined = function(variable) {
    return (typeof (window[variable]) == "undefined") ? false : true;
}

HeadupAnnotation.prototype.gup = function(name, query) {
    name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
    var regexS = "[\\?&]" + name + "=([^&#]*)";
    var regex = new RegExp(regexS);
    var results = regex.exec(query);
    if (results == null)
        return "";
    else
        return results[1];
}

HeadupAnnotation.prototype.getQueryStrings = (function(){
    var argList = {};

    if(window.location != null && window.location.search.length > 1) {
        var urlParms = window.location.search.substring(1);
        var argPairs = urlParms.split('&');

        for(var i = 0; i < argPairs.length; i++) {
            var pos = argPairs[i].indexOf('=');

            if(pos == -1)
                continue;
            else {
                var argName = argPairs[i].substring(0, pos);
                var argVal = argPairs[i].substring(pos + 1);

                if(argVal.indexOf('+') != -1)
                    argVal = argVal.replace(/\+/g, ' ');

                argList[argName] = unescape(argVal);
            }
        }
    }

    return argList;
})();

HeadupAnnotation.prototype.initSuggestMenu = function(items){
	try{
		_headup.widgetTitle.children("span").empty();
		var fn = items[0].FullName, dn = items[0].DisplayName;
		
		if( this.widgetMode == 'snippet' ){
			if( fn.length > 25 && dn.length > 12 || fn.length + dn.length > 32 )
				return false;
			
			var type = dn ? ', ' + dn : '';
			_headup.widgetTitle.children("span").html( type );
			return false;
		}
		
		if( fn.length > 25 && dn.length > 12 || fn.length + dn.length > 45 )
			return false;

		var menuObj = _headup.widgetTitle.find("div.headup-suggest-menu");
		menuObj.html("");
		menuObj.append("<span id='headup-suggest-text'>" + items[0].DisplayName + "</span>");
		
		if( items.length > 1 ){
			var suggestionsList = j$("<ul/>");
			
			menuObj.addClass("headup-has-items");
			j$(items).each(function(index, item) {
				var itemLink = j$("<a/>").append(item.DisplayName);
				itemLink.data("uri", item);

				suggestionsList.append(j$("<li/>").append(itemLink));

				if (index == items.length - 1)
					suggestionsList.appendTo(menuObj);
			});
		
			// now bind the appropriate events to the menu
			
				// handle click outside the Suggest Sense box
				j$("#headup").click(function(e) {
					if ( !j$(e.target).is("#headup-suggest-text") )
						j$("#headup-suggest-text + ul").slideUp("fast");
				});
				
				j$("#headup-suggest-text").click(function(){
					j$(this).next("ul").slideToggle("fast");
				});

				j$("#headup div.headup-suggest-menu ul li a").click(function() {
					j$("#headup div.headup-suggest-menu ul").hide();
					if (j$(this).text() == j$("#headup-suggest-text").text()) // check if already selected
						return false;

					j$("#headup-suggest-text").text( j$(this).text() ); // set the selecated value

					_headup.initIframe( j$(this).data("uri") );
				});
		}else
			menuObj.removeClass("headup-has-items");
	}
	catch (err){
		this.ShowLog('initSuggestMenu', err);
	}
}

HeadupAnnotation.prototype.satisfy = function(items){
	try{
		j$("#headup-satisfy button").live('click',function(){
			var term = _headup.hoveredElement['headup'].Senses[0].Term;
			var quality = this.className, parentDiv = j$(this.parentNode);
			_headup.writeStatistic({ satisfy:quality });
			j$("#headup-satisfy").html('Is this helpful? - Thanks!');
			_headup.satisfyTimer = setTimeout(function(){ j$("#headup-satisfy").fadeOut(400) }, 2000);
		});
	}
	catch (err){
		this.ShowLog('satisfy', err);
	}
}

HeadupAnnotation.prototype.dialogBox = {
	headup_wrap : null,
	objDisable : null,
	show : function( htmlCode ){
		try{
			this.objDisable = j$('<div/>').addClass('dialog');
			// insert the html code
			j$('<div/>').addClass('inner').html( htmlCode ).appendTo(this.objDisable);
			// put buttons
			var btnOk = j$('<button>ok</button>');
			var btnCancel = j$('<button>cancel</button>');
			j$('<div class="buttons"/>').append(btnOk).append(btnCancel).appendTo(this.objDisable);
			
			// bind buttons
			btnOk.bind('click',function(){ _headup.disableUserAnnotations() });
			btnCancel.bind('click',function(){ _headup.dialogBox.close() });
			
			this.headup_wrap = j$("#headup > div.headup-wrap").hide();
			_headup.headupFrame.prepend( this.objDisable );
			// center the dialog box
			this.objDisable.css({ marginTop:-this.objDisable.height()/2, position:'absolute', top:'50%', width:'100%' });
		}
		catch (err){
			this.ShowLog('dialogBox', err);
		}
	},
	close : function(){
		if( this.objDisable ){
			this.objDisable.remove();
			this.headup_wrap.show();
			this.objDisable = null;
		}
	}
}

HeadupAnnotation.prototype.disableUserAnnotations = function(){
	try{
		//var answer = confirm("Headup content will be turned off for this website.\nClear cookies to restore");
		//if( answer ){
		_headup.createCookie("headup-disable-annotations", true, 7);
		j$(".headupTerm").removeClass('headupTerm');
		var term = _headup.hoveredElement['headup'].Senses[0].Term;
		_headup.writeStatistic({ event:'disableAnnotations' });
		setTimeout( function(){ _headup.CloseHeadup(); }, 200 );
	}
	catch (err){
        this.ShowLog('disableUserAnnotations', err);
    }
}

var j$;

// remove duplicate values from given Array
// example: var arr = [1,2,2,3,3,4,5,6,2,3,7,8,5,9];
// var unique = arr.unique();
Array.prototype.unique = function(){
    var r = [];
	for(var i = 0; i < this.length; i++){
		if( j$.inArray(this[i], r) == -1 )
			r.push( this[i] );
	}
	return r;
}

// make sure there are no duplicates
if( typeof headup == 'undefined' || !headup ){
	headup = new HeadupAnnotation();
	headup.debug = headup.getQueryStrings.headup == 'debug' ? true : false;

	// check if user has disabled annotations for this domain
	var user_disable_annotation = headup.readCookie('headup-disable-annotations');

	if( (headup.getQueryStrings.headup != 'off' || headup.isDefined('bookmarklet')) && !user_disable_annotation ){
		headup.preLoad();
		/*
		headup.addEvent(window, 'load', function() { headup.preLoad(); });
		setTimeout(function() { headup.preLoad(); }, 1000);
		*/
	}
	else
		headup = null;
	
	// ignore firebug console when its not available
	if(!window.console || !console.firebug){
		var names = ["log","debug","info","warn","error","assert","dir","dirxml","group","groupEnd","time","timeEnd","count","trace","profile","profileEnd"];
		window.console = {};
		for (var i = 0; i < names.length; ++i)
			window.console[names[i]] = function() {}
	}
}