// JSLint global variables
/*global window */
/*global document */
/*global console */
/*global $ */

var RS = RS || {};
var _gaq = _gaq || [];

RS.cancel = function (e) {
	return false;
};

function dbg(v) {
	if (window.console) {
		console.log(v);
	}
}

/**
 * Reusable method caller for jQuery plugins
 */
var JQMtd = function (methods) {
	return function (m) {
		if (methods[m]) {
			return methods[m].apply(this, Array.prototype.slice.call(arguments, 1));
		} else if (typeof m === 'object' || !m) {
			return methods.init.apply(this, arguments);
		}
	};
};

/**
 * Get current millisecond since 1970...
 *
 * @return int
 */
RS.getMilliseconds = function () {
	return (new Date()).getTime();
};

$(function () {
	RS.pageLoaded = RS.getMilliseconds();
});

/**
 * Track an event with google analytics
 */
RS.logEvent = function (testKey, eventName, variantKey) {
	if (RS.pageLoaded) {
		var secondsSinceLoad
			= Math.ceil((RS.getMilliseconds() - RS.pageLoaded) / 1000);
	}
	else {
		var secondsSinceLoad = 0;
	}
	
	_gaq.push(
		['_trackEvent', testKey, eventName, variantKey, secondsSinceLoad]
	);
};

$(function () {
	RS.b = $(document.body);
	
	$.fn.extend({
		/**
		 * Detect changes in input field with smart delay
		 */
		delayedChange: function () {
			var input = $(this), val, to;
			
			function check() {
				var newVal = $.trim(input.val());
				
				if (val !== newVal) {
					val = newVal;
					input.trigger('delayedChange');
				}
			}
			
			input.keydown(function () {
				window.clearTimeout(to);
				to = window.setTimeout(check, 200);
			}).attr('autocomplete', 'off');
			
			check();
			return input;
		},
    
    /**
     * Scroll to the vertical position of this element
     */
	scrollTo: function (time) {
		var offset = $(this).offset();
		if (offset) {
			$('html,body').animate({ scrollTop: $(this).offset().top - 30 }, time == undefined ? 'fast' : time);
		}
		return this;
    },
    
    /**
     * Focuses a form field and intializes for writing. IE6 safe.
     */
    focusForWrite: function () {
			setTimeout($.proxy(function (){ this.focus(); }, this), 100);
			return this;
    },

		// Arrow navigation for autocomplete search
		arrowNav: function (container) {
			var sel, code, input = this;

			input.keydown(function (e) {
				if (sel && sel.closest('body').size()) {
					sel.removeClass('selected');
				}
				else {
					sel = false;
				}

				switch(code = e.keyCode || e.which) {
					case 38: // Up
					case 40: // Down
						var n = sel ? sel[code == 40 ? 'next' : 'prev']() : container.find((code == 40 ? ':first' : ':last') + '-child');
						sel = n.size() ? n.addClass('selected') : null;
						e.preventDefault();
					break;

					case 39: // Right
					case 13: // Enter
						if (sel) {
							input.trigger('option-select', [sel]);
							e.preventDefault();
							return;
						}
					break;

					default:
						sel = false;
				}
			});
			
			return this;
		},
    
    /**
     * Search autocomplete
     */
    autoSearch: function (path, appendTo) {
      var input = this, cont = $('<div class="autosearch" />').hide().appendTo(appendTo), prev_val, val, foc = 0;
			
			$('a', cont[0]).live('click', function () {
				input.trigger('option-select', [$(this)]);
				return false;
			});
			
      input.bind({
				'option-select': function (e,a) {
					window.location = a.attr('href');
				},
				
        focus: function () {
					foc = 1;
          RS.b.mousedown(hide_search);
					cont.mousedown(RS.cancel);
					input.mousedown(function (e) { e.stopPropagation(); }).trigger('delayedChange');
        },
        
				blur: function () {
					foc = 0;
          RS.b.unbind('mousedown', hide_search);
					input.unbind('mousedown', RS.cancel);
					cont.unbind('mousedown', RS.cancel);
        },
				
				delayedChange: function () {
					var val = input.val();

					if (val.length <= 1 || val.length >= 50) {
		        hide_search();
		        return;
		      }

					input.addClass('loading');
	        $.getJSON('/' + path, { term: val }, parse_results);
	      }
      }).delayedChange().arrowNav(cont);
    
      function parse_results(results) {
				input.removeClass('loading');
        if (!results || !results.length || !foc) {
          hide_search();
          return;
        }
        
        cont.empty().show();
    		
        $(results).each(function () {
          $('<a/>', { href: this.path, html: this.label }).data('data', this).appendTo(cont);
        });
      };
      
      var sliding = false;
      function hide_search() {
        if (cont.is(':visible') && !sliding) {
          sliding = true;
          cont.slideUp('fast', function () {
            sliding = false;	
          });
        }
      };
			
			return input;
    }
  });
	
	// Only add autosearch in main layout, when clicking box
	if (!RS.b.hasClass('frame') && window.location.href.indexOf('site/search') == -1) {
		$('#search input[name=q]').one('focus', function () {
			$(this).autoSearch('site/searchCallback', '.search-field').focus();
		});
	}
	
	// Collapsible fieldsets
	$('.collapsible').each(function () {
		var fs = $(this).wrapInner('<div class="inner"/>');
		fs.find('legend:first').prependTo(fs).wrapInner('<span class="link"/>');
	});
	
	// Spotlight link tracking
	$('.spotlight').delegate('.website', 'click', function (e) {
		var ws = $(this), listing = ws.closest('.listing'), h = listing.attr('href');
		
		// Set main spotlight link's href to prevent popup blocker
		listing.attr('href', ws.data('url'));
		
		$.getJSON('/spotlight/stats', { id: listing.data('id') });
		
		// Then reset the origin href
		setTimeout(function () {
			listing.attr('href', h);
		}, 10);
		
		// Don't handle top container click
		e.stopPropagation();
	}).delegate('a', 'click', function () {
		var id = $(this).closest('.listing').data('id');
		$(document).trigger('trackEvent', ['#' + id, 'sc']);
		$.get('/spotlight/stats', { id: id });
	});
	
	function clearGrayTxt() {
		$(this).closest('form').find('.graytxtjs.gray').val('');
	}
	
	// Event handlers
	RS.b
		
		// gray text in text fields
		.delegate('.graytxtjs', 'focus', function () {
			var t = $(this);
		
			if (t.hasClass('gray')) {
				this.defaultText = this.value;
				t.removeClass('gray');
			}
		
			if (t.val() == this.defaultText) t.val('');
		})
		
		// Clear gray text fields when form submitted
		.delegate('form:has(.graytxtjs)', 'submit', clearGrayTxt)
		.delegate('form:has(.graytxtjs) :submit', 'click', clearGrayTxt)

		.delegate('fieldset.collapsible > legend', 'click', function () {
			var fs = $(this).closest('.collapsible'), k = 'collapsed', inner = fs.find('.inner:first');
			
			if (fs.hasClass(k)) {
				inner.slideDown('fast', function () { fs.removeClass(k); fs.hide().show(); });
			}
			else {
				inner.slideUp('fast', function () { fs.addClass(k); });
			}
		})
		
		// Voting links
		.delegate('.vote', 'click', function () {
			var me = $(this);
			me.closest('.vote-cont').load(me.data('action'));
	  })
		
		// Send to friend
		.delegate('.forward', 'click', function () {
			var el = $(this);
			$('<div/>').iframePopup({
			    src: '/forward?model=' + el.data('model') + '&id=' + el.data('id'),
			    animateFrom: this,
			    id: 'forward-form-frame'
			});
		})
		
		// Lead form popup
		.delegate('.lead-form', 'click', function () {
			var t = $(this);

			$('<div/>').iframePopup({
				src: '/lead/form?' + $.param({ id: t.data('dr-id'), offer_id: t.data('offer-id') }),
				animateFrom: this,
				width: $(this).hasClass('offers') ? 600 : 450
			});
		})
		
		// Prevent mulitple submit of various forms
		.delegate('form :submit.loading', 'click', RS.cancel)
		
		.delegate('.p-bdg', 'mouseenter', function () {
			RS.b.undelegate('.p-bdg');
			$(this).addClass('hover').append('<span class="s-hover hover-info">Learn more</span>').hide().show().click(function () {
				window.location = '/partners';
			});
		});
	
	if (!RS.role && window == self.top) {
		// Track page flow and actions (last 10)
		$(document).bind('trackEvent', function (e, val, type) {
			var c_val;
			
			$(document.cookie.split(';')).each(function () { // Extract cookie value
				var parts = this.split('=');
				
				if ($.trim(parts[0]) == 'flow') {
					c_val = $.trim(parts[1]);
				}
			});
			
			// Extract array of events
			if (c_val) {
				c_val = unescape(c_val).split('&');
			}
			else {
				c_val = [];
			}
			
			if (!val) {
				var l = window.location;
				val = l.pathname + l.search + l.hash
			}
			
			// Add this event
			c_val.push((type ? type : 'url') + '=' + escape(val));
			
			// Pop earliest event if more than 10
			if (c_val.length > 10) {
				c_val.shift();
			}
			
			// Set cookie
			var d = new Date();
			d.setDate(d.getDate() + 1);
			document.cookie = "flow=" + escape(c_val.join('&')) + "; expires="+d.toUTCString() + '; path=/';
		});
		
		$(document).trigger('trackEvent');
	}
	
	RS.flags();
	
	RS.drBadgeHoverText();
});

/**
 * Hover text for top doctor and opinion leader icons
 *
 * @return void
 */
RS.drBadgeHoverText = function() {
	var hoverEl, hoverText;
	
	hoverText = {
	    'top-doctor-icon':
	        'This is a RealSelf Top Doctor (TOP), a recognition awarded to less '
	        + 'than 10% of doctors on RealSelf. This status is earned by achieving '
	        + 'high patient satisfaction (as reported in RealSelf reviews), getting '
	        + 'favorable feedback on their expert answers, and after investing '
	        + 'significant time in Q&A and other doctor activity.',
        'opinion-leader-icon':
            'This is a RealSelf Opinion Leader (ROL), a recognition bestowed by '
            + 'RealSelf and it\'s medical advisors to doctors who are '
            + 'internationally recognized thought-leaders in their specialty. '
            + 'These doctors conduct research, present at major medical '
            + 'conferences, and are viewed by colleagues as leading experts.'
    }
	
	RS.b.delegate('.top-doctor-icon, .opinion-leader-icon', 'mouseenter', function() {
	    var $this = $(this), offset = $this.offset();
	    
	    if (!hoverEl) {
	        hoverEl = $('<div class="s-hover hover-info badge-hover"></div>')
	            .css({ display: 'none', visibility: 'visible' })
	            .appendTo(RS.b);
        }
        
        $.each(hoverText, function(klass, text) {
           if ($this.hasClass(klass)) {
               hoverEl.text(text);  
           }
        });
        
        hoverEl.css({
            top: offset.top + 20,
            left: offset.left,
            display: 'block'
        });
    }).delegate('.top-doctor-icon, .opinion-leader-icon', 'mouseleave', function() {
	    hoverEl.hide();
	});
}

var st = 'This doctor is a board certified surgeon who is an active member of the ';
RS.partners = {
	isc: {
		img: 'injectable-safety.gif',
		l: 'injectablesafety.org',
		body: 'This doctor is active in the Physicians Coalition for Injectable Safety, an alliance of specialty physician organizations dedicated to cosmetic injectable safety.'
	},
	asaps: {
		img: 'asaps-member.gif',
		l: 'surgery.org',
		body: st + 'American Society for Aesthetic Plastic Surgery (ASAPS).<br /> ASAPS is a leading association for aesthetic plastic surgeons.'
	},
	fps: {
		l: 'aafprs.org',
		body: 'This facial plastic surgeon is a member of the American Academy of Facial Plastic and Reconstructive Surgery (AAFPRS), the world\'s largest association of facial plastic and reconstructive surgeons.'
	},
	asds: {
		l: 'asds.net',
		body: 'This board-certified dermatologist is an active member of the American Society for Dermatologic Surgery (ASDS). The ASDS and its more than 5,000 members are at the forefront of the development of safe, in-office procedures to help maintain the health and beauty of the skin.'
	},
	asoprs: {
		img: 'asoprs.gif',
		l: 'asoprs.org',
		body: 'This board-certified ophthalmic plastic and reconstructive surgeon is an active member of the American Society of Ophthalmic Plastic and Reconstructive Surgery (ASOPRS).'
	},
	asps: {
		l: 'realself.com/asps',
		body: 'This board-certified plastic surgeon is an active member of the American Society of Plastic Surgeons (ASPS).'
	},
	csaps: {
		l: 'csaps.ca',
		body: st + 'Canadian Society for Aesthetic (Cosmetic) Plastic Surgery (CSAPS). CSAPS is a leading association for aesthetic plastic surgeons.'
	},
	asbps: {
		l: 'asbps.org',
		body: st + 'American Society of Bariatric Plastic Surgeons (ASBPS).'
	},
	isps: {
		l: 'isaps.org',
		body: st + 'International Society Of Aesthetic Plastic Surgery (ISAPS).'
	},
	obexp: {
		img: 'obesityexpert-icon.gif'
	}
};

RS.flags = function () {
	var pf = $('.flags:not(.loaded)').each(function () {
		var t = $(this).addClass('oh loaded'), imgs = t.hasClass('imgs'), f, d, hBody;
		
		for (type in RS.partners) {
			d = RS.partners[type];
			f = t.find('.flag.' + type);
			
			if (f.size()) {
				if (d.body) {
					f.addClass('hover').append(
						'<span class="s-hover">' +
							d.body + '<br/>' +
							'<a href="http://www.' + d.l + '" target="_blank">Learn more</a>' +
						'</span>'
					);
				}
				
				if (imgs) {
					if (d.img) {
						f.prepend('<img src="/images/flags/' + d.img + '">');
					}
					else {
						f.addClass('sprite icon-' + type)
					}
				}
			};
		}
	});
	
	if (pf.size()) {	
		RS.b.delegate('.flag a', 'click', function () {
			if (self._gaq) self._gaq.push(['_trackPageview', '/asaps_click/']);
		});
	}
};

$(function () {
	if (!RS.user_id) {
		/**
		 * Receive Facebook logins
		 */
		window.fbReceive = function (data) {
			if (data.error) {
				AjaxLogin.load({ fb_error: data.error });
				return;
			}
			
			if ($('#ajax-login-frame').is(':visible')) {
			    AjaxLogin.handle(data);
			}
			else {
			    window.location.reload();
			}
		}
		
		/**
		 * Ajax login
		 */
		window.AjaxLogin = (function () {
		  var me = {
				/**
			   * Loads the AjaxLogin window
			   */
				load: function (args, target) {
					if (!args) args = {};
					args.frame = 1;
					var src = '/user/signin' + (args ? '?' + $.param(args) : '');
					me.target = target;
					
					var frame = $('<div/>').iframePopup({
						width: 738,
						animateFrom: target,
						src: src,
						onJson: function (data) {
							frame.iframePopup('destroy');
							me.handle(data);
						},
						attributes: { id: 'ajax-login-frame' }
					});
			  },
			
				handle: function (data) {
					$(me.target || document).trigger('ajax_login', [data]);
				}
		  };
  
		  // Bind to links with a class
		  RS.b.delegate('.ajax-login', 'click', function () {
		    me.load(null, this);
		    return false;
		  });
  
		  // Default action - override by attaching object to ajax_login event and cancelling buble
		  $(document).bind('ajax_login', function (e, params) {
				params.type == 'login' ? window.location.reload() : window.location = '/user/thankYou';
		  });

		  return me;
		})();
	}
});

/**
 * Generic pager for AJAX paging
 */
RS.pager = function (total, page, per_page) {
	var page_count = Math.ceil(total / per_page), output = '', i;
	
	if (page_count < 2) return '';
	
	page = parseInt(page);
	var from_page = page < 6 ? 1 : Math.max(2, page - 4), to_page = Math.min(from_page + 9, page_count);

	output += '<span data-page="' + (page - 1) + '" ' + (page > 1 ? '' : 'style="display: none"') + ' class="prev link">&lt; previous</span>';

	for (i = from_page; i <= to_page; i++) {
		output += i == page ? '<span class="current-page">' + i + '</span>' : '<span data-page="' + i + '" class="page link">' + i + '</span>';
	}

	if (page < page_count) {
		output += '<span data-page="' + (page + 1) + '" class="next link">next &gt;</span>';
	}
	
	return output;
};


/**
 * This file gets a single group of ads fetched by script in a view and
 * Splits into groups of two, set in global variable 'ad_groups'
 *
 * To place an ad, insert script such as <script type="text/javascript">document.write(ad_group())</script>
 *
 * Ad call must come before all of these scripts
 */
var google_adnum = 0;
function google_ad_request_done(ga) {
  var s = '', i;
  
  if (ga.length == 0) {   /* quit if no ads found */
  	return;
  }
	
	for (i = 0; i < ga.length; ++i) {
		switch(ga[i].type) {
			case 'text':
				if (ga[i].visible_url != undefined) {
					ads.text.push(
						'<a style="text-decoration:none;color:#193779" href="' +
						ga[i].url + '" onmouseout="window.status=\'\'" onmouseover="window.status=\'go to ' +
						ga[i].visible_url + '\';return true"> <span style="text-decoration:underline;font-size:14px;"> <b>' +
						ga[i].line1 + '</b></span></a><br><span style="color:#000;">' +
						ga[i].line2 + ' ' +
						ga[i].line3 + '</span> <a style="color:#999;font-size:13px;text-decoration:none" href="' +
						ga[i].url + '" onmouseout="window.status=\'\'" onmouseover="window.status=\'go to ' +
						ga[i].visible_url + '\';return true"><span>' +
						ga[i].visible_url + '</span></a>'
					);
				}
				
				break;
			
			case 'image':
				ads.image.push(
					s += '<a href="' +
				  ga[i].url + '" target="_top" title="go to ' +
				  ga[i].visible_url + '" onmouseout="window.status=\'\'" onmouseover="window.status=\'go to ' +
				  ga[i].visible_url + '\';return true"><img border="0" src="' +
				  ga[i].image_url + '"width="' +
				  ga[i].image_width + '"height="' +
				  ga[i].image_height + '"></a>'
				);
				
				break;
			
			case 'flash':
				ads.flash.push(
					'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' +
					' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" WIDTH="' + 
					ga[i].image_width +	'" HEIGHT="' + 
					ga[i].image_height + '"> <PARAM NAME="movie" VALUE="' + 
					ga[i].image_url + '">' + 
					'<PARAM NAME="quality" VALUE="high">' + 
					'<PARAM NAME="AllowScriptAccess" VALUE="never">' + 
					'<EMBED src="' + 
					ga[i].image_url + '" WIDTH="' + 
					ga[i].image_width + '" HEIGHT="' + 
					ga[i].image_height + 
					'" TYPE="application/x-shockwave-flash"' + 
					' AllowScriptAccess="never" ' + 
					' PLUGINSPAGE="http://www.macromedia.com/go/getflashplayer"></EMBED></OBJECT>'
				);
				
				break;
  	}
	}
}

/**
 * Returns a group of ads
 */
var ads, last_ad = 0;

// Clears ads array
function clear_ads() {
	ads = {text:[],image:[],flash:[]};
}
clear_ads();

function ad_group(num, type) {
	var groups = [], num = num || 2, type = type || 'text';
	
	if (type == 'all') { // All types of ads
		$.each(ads, function (type, op, i) {
			$(op).each(function (i) {
				if (i < num) groups.push(this);
			});
		});
		
		clear_ads();
	}
	else {
		groups = ads[type].splice(last_ad, num);
	}
	
	if (groups.length) {
		return '<div class="ga">'
		    + '<a href=\"' + google_info.feedback_url + '\" style="color:#000000;font-size:12px" target="_blank">Ads by Google</a><br/>'
            + groups.join('<br/>')
            + '</div>';
	}
	return '';
}

// Iframe popup
(function ($) {
	var d = 'iframePopup',
	
	defaults = {
		// src: iframe src
		width: 400,
		height: 260, // Optional estimate for animation
		top: 100,
		overlay: 1,
		modal: 1,
		// animateFrom: null,
		// onLoad: custom onLoad handler for frame
		// onJson: handler when iframe contents is json
		// id: popup id
		attributes: { // iframe attributes
			frameBorder: 0,
			border: 0,
			allowtransparency: 'true'
		}
	},
	
	methods = {
		init: function (options) {
			// Initialize data
			var data = { options: $.extend(true, {}, defaults, options) };
			
			var $this = this.addClass('popup floaty')
				.delegate('.c-btn', 'click', methods.close)
				.appendTo(document.body).data(d, data);
			
			if (data.options.id) $this.attr('id', data.options.id);
			
			// Setup iframe
			$('<iframe/>').width(data.options.width)
				.bind('load.iframePopup', methods.onLoad)
				.attr($.extend({ src: data.options.src }, data.options.attributes)).appendTo($this);
			
			
			var win = $(window), css = {
				width: data.options.width,
				left: Math.floor((win.width() - data.options.width) / 2),
				top: data.options.top + win.scrollTop()
			};
			
			$this.show();
			
			var fnlz = function () {
				$this.css('height', 'auto').append('<div class="c-btn"><span>x</span></div>')
			}
			
			if (data.options.animateFrom) { // Animate to position
				css.height = data.options.height;
				
				$this.css($(data.options.animateFrom).offset()).animate(css, 'fast', fnlz);
			}
			else { // Show in position
				$this.css(css);
				fnlz();
			}
			
			if (data.options.overlay) {
				data.overlayDiv = $('<div class="overlay"/>').css({ opacity: 0, height: $(document).height() }).appendTo(document.body).fadeTo('fast', 0.1);
			}
			
			// Allow clicking outside of popup to close
			if (!data.options.modal) {
				(data.overlayDiv ? data.overlayDiv : RS.b).bind('click.iframePopup', function () {;
					$this.iframePopup('destroy');
				});
			}
			
			return this;
		},
		
		onLoad: function () {
			var $this = $(this), popup = $this.closest('.popup'), data = popup.data(d), contents = $this.contents();
			
			// Open untargetted links in top frame
			contents.find('body').delegate('a', 'click', methods.linkClick);
			
			try { // If frame body is JSON
				var json = $.parseJSON(contents.text());
				data.options.onJson.apply(this, [json]);
				popup.iframePopup('destroy');
			}
			catch(er) { // Otherwise, display as HTML
				popup.iframePopup('setHeight');
			}
			
			// Run custom onload handlers
			if (data.options.onLoad) {
				data.options.onLoad.apply(this);
			}
		},
		
		/**
		 * Make frame links open in top window. Set link target to _self otherwise.
		 */
		linkClick: function () {
			if (!this.target) {
			self.top.location = this.href;
			return false;
		      }
		},
		
		/**
		 * Resize iframe to fit contents. Called for HTML onLoad. Call manually for dynamic height changes.
		 */
		setHeight: function () {
			var heightTries = 0, lastHeight, data = this.data(d), frame = this.find('iframe'), cont = frame.contents(), height,
			
			resize = function () {
				try {
					height = Math.max(cont.height(), cont.find('body > div').outerHeight());
					
					if (height) {
						if (height == lastHeight) {
							heightTries++;
						}
						else {
							frame.height(height);
							
							if (data.overlayDiv) {
								data.overlayDiv.height($(document).height());
							}
							
							lastHeight = height;
						}
					}
				}
				catch(er) {
					heightTries++;
				};

				if (heightTries >= 5) {
					clearInterval(interval);
				}
			},
			
			interval = setInterval(resize, 80);
			
			resize();
		},
		
		/**
		 * Event handlers to destroy popup
		 */
		close: function () {
			$(this).closest('.popup').iframePopup('destroy');
		},
		
		destroy: function () {
			var data = this.data(d);
			
			if (data.overlayDiv) {
				data.overlayDiv.remove();
			}
			
			this.remove();
			RS.b.unbind('.iframePopup');
		}
	}
	
	$.fn.iframePopup = JQMtd(methods);
})(jQuery);

// Commenting
(function ($) {
	var config = {}, query = { offset: 0 }, cont, comments, $w = $(window), scrollPos, trigPos, loaded, reqActive,
	
	methods = {
		init: function (options) {
			config = $.extend(config, options);
			
			query.content_id = config.content_id;
			query.sort = config.sort;
			
			cont = $('#comments')
				.delegate('.reply-link', 'click', methods.clickReplyLink)
				.delegate('#comment-sort', 'change', methods.sortChange);
			
			comments = cont.find('.comment-list');
			
			methods.setScrollTriggerPos();
			methods.setScroll();
			
			$('#comments .add-comment').click(function () {
				$(this).remove();
				$('<div/>').insertBefore(comments).load('/comment/create?content_id=' + query.content_id, methods.attachForm);
			});
			
			// If comment passed in hash, load all and go to
			var m = /(comment-\d+)/.exec(window.location.hash);
			if (m) {
			    var sel = '#' + m[1];
			    
			    if ($(sel).size() == 0) {
    				methods.loadAll(function () {

    					var count = 0, interval = setInterval(function () {
    						window.scrollTo(0, $(sel).offset().top);

    						if (count++ > 5) {
    						    clearInterval(interval);
    					    }
    					}, 10);
    				});
    			}
			}
			
			methods.attachValidation($('#comment-form'));
		},
		
		attachForm: function () {
			var form = $(this).find('form'), ta = form.find('textarea');
			
			try {
				CKEDITOR.instances[ta.attr('id')].focus();
			}
			catch(er) {
				ta.focusForWrite();
			}
			
			methods.attachValidation(form);
			methods.setScrollTriggerPos();
		},
		
		setScroll: function () {
			if (config.comment_count > config.per_page) {
				$w.scroll(methods.onScroll);
			}
		},
		
		loadAll: function (cb) {
			query.all = 1;
			query.offset = 0;
			$w.unbind('scroll');
			comments.empty();
			methods.runQuery(cb);
		},
		
		/**
		 * When sort option changed, clear and get a new page of comments
		 */
		sortChange: function () {
			query.sort = this.value;
			query.offset = 0;
			comments.empty();
			$w.unbind('scroll').scroll(methods.onScroll);
			methods.runQuery();
		},
		
		/**
		 * When window scroll reaches a certain point, get more comments
		 */
		onScroll: function () {
			scrollPos = $w.scrollTop() + $w.height();
			
			if (scrollPos > trigPos) {
				if (!reqActive) {
					query.offset += config.per_page;
					
					reqActive = 1;
					
					methods.runQuery(function () {
						reqActive = 0;
					});
					
					if (query.offset + config.per_page >= config.comment_count) {
						$w.unbind('scroll');
					}
				}
			}
		},
		
		/**
		 * Fetch comments from the server with the query object
		 */
		runQuery: function (cb) {
			$.get('/comment/fetch', query, function (d) {
				delete query.all;
				
				comments.append(d);
				methods.setScrollTriggerPos();
				
				if (cb) cb();
			});
		},
		
		/**
		 * Determine the next scroll position that triggers fetching of new comments
		 */
		setScrollTriggerPos: function () {
			trigPos = comments.offset().top + comments.height() - 200;
		},
		
		/**
		 * Attach ajax validation to this form
		 */
		attachValidation: function (form) {
			if (!form.size()) return;
			
			var attr = [], m, name;
			form.find(':input').each(function () {
				m = /\[([^\]]+)/.exec($(this).attr('name'));
				if (m) {
					name = m[1];
					
					attr.push({
						'id':'Comment_' + name,
						'inputID':this.id,
						'errorID':this.id + '_em',
						'model':'Comment',
						'name':name,
						'enableAjaxValidation':true
					});
				}
			});
			
			form.yiiactiveform({'validateOnSubmit':true,'validateOnChange':false,'beforeValidate':methods.beforeValidate,'afterValidate':methods.afterValidate,'attributes':attr});
		},
		
		/**
		 * Load the reply form for a comment when a reply link is clicked
		 */
		clickReplyLink: function () {
			var link = $(this), comment = link.closest('.comment');
			
			link.remove();
			
			$('<div class="reply-form"/>').load('/comment/create?content_id=' + query.content_id + '&replying_to=' + comment.data('id'), methods.attachForm).appendTo(comment);
		},
		
		/**
		 * Increment comment count and show hidden elements after a new comment is posted
		 */
		setNewComment: function () {
			var cc = $('.comment-count');
			cc.html(parseInt(cc.html()) + 1);
			$('.show-on-comment').show();
		},
		
		/**
		 * Before validating comment form, make sure ckeditor has updated the textarea
		 */
		beforeValidate: function (form) {
			form.find(':submit').addClass('loading');
			
			try {
				CKEDITOR.instances[form.find('textarea').attr('id')].updateElement();
			}
			catch(e){};
			
			return true;
		},
		
		/**
		 * Submit a comment via AJAX, then insert it into DOM
		 */
		afterValidate: function (f, data, hasError) {
			f.find(':submit').removeClass('loading');
			
			if (hasError) {
				return false;
			}
			else if(RS.user_id) { // For signed-in users, submit with Ajax
				f.find(':submit').addClass('loading');
				
				$.post(f.attr('action'), f.serialize(), function (data) {
				    if (window._gaq) {
				        _gaq.push(
				            ['_trackEvent', 'comments', 'submit' , 'signed in']
				        );
				    }
				    
					methods.ckDestroy.apply(f);
					f.remove();
					
					methods.setNewComment();
					
					query.postedCommentId = data.comment_id;
					
					methods.loadAll(function () {
						$('.comment[data-id=' + data.comment_id + ']').scrollTo(0);
						
						delete query.postedCommentId;
					});
				}, 'json');
			}
			else {
				return true; // For anonymous users, submit normally
			}
		},
		
		/**
		 * Destroy and CKEditor instances below an element
		 */
		ckDestroy: function () {
			this.find('textarea').each(function () {
				try {
					CKEDITOR.instances[this.id].destroy();
				}catch(er){}
			});
		}
	};
	
	$.fn.commenting = JQMtd(methods);
})(jQuery);

// Fixed scrolling
(function ($) {
	var d = 'fixed', instances = $(), init = 0, st, footerH = 358, $w,
	
	methods = {
		init: function (options) {
			$(this).each(function () {
				var $this = $(this);
				
				$w = $(window);
				
				$this.addClass('fixed').parent().css('position', 'relative');
				
				$this.css(
				    { width: $this.width(), top: 0, zIndex: 1 }
				).show().data(d, {
					top: $this.offset().top - 10,
					state: 0,
					states: [
						{ position: 'absolute', top: 0 },
						{ position: 'fixed', top: 10 },
						{ position: 'absolute' } // top set dynamically
					]
				});
			
				instances = instances.add(this);
				
				if (!init) {
					init = 1;
				
					$w.scroll(methods.onScroll);
					methods.onScroll();
				}
			});
			
			return this;
		},
		
		onScroll: function () {
			st = $w.scrollTop();
			instances.each(methods.detectState);
		},
		
		/**
		 * Toggle between position fixed and absolute depending on scroll state
		 */
		detectState: function () {
			var $this = $(this), data = $this.data(d), new_state;
			
			switch(data.state) {
				case 0: // Detect change to state 1
					if (st > data.top) {
						new_state = 1;
					}
					break;
					
				case 1: // Detect change to 0 or 2
					if (st <= data.top) {
						new_state = 0;
					}
					else {
						var bh = RS.b.height(), oh = $this.outerHeight();
						if (st + oh + 20 > bh - footerH) {
							data.states[2].top = bh - footerH - $this.parent().offset().top - oh;
							new_state = 2;
						}
					}
					break;
				case 2: // Detect change to state 1
					var bh = RS.b.height(), oh = $this.outerHeight();
					
					if (st + oh <= bh - footerH) {
						new_state = 1;
					}
					break;
			}
			
			if (new_state != undefined) {
			    methods.setState.apply($this, [new_state]);
			}
		},
		
		setState: function (state) {
			var data = this.data(d);
			
			this.css(data.states[state]);
			data.state = state;
			
			this.data(d, data);
		},
		
		destroy: function () {
			instances = instances.not(this);
			this.removeData(d);
		}
	};
	
	$.fn[d] = JQMtd(methods);
})(jQuery);

