/*!
 * jQuery Corners 0.3
 * Copyright (c) 2008 David Turnbull, Steven Wittens
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 */

jQuery.fn.corners = function(options) {
  var doneClass = 'rounded_by_jQuery_corners'; /* To prevent double rounding */
  var settings = parseOptions(options);
  var webkitAvailable = false;
  try {
    webkitAvailable = (document.body.style.WebkitBorderRadius !== undefined);
    /* Google Chrome corners look awful */
    var versionIndex = navigator.userAgent.indexOf('Chrome');
    if (versionIndex >= 0) webkitAvailable = false;
  } catch(err) {}
  var mozillaAvailable = false;
  try {
    mozillaAvailable = (document.body.style.MozBorderRadius !== undefined);
    /* Firefox 2 corners look worse */
    var versionIndex = navigator.userAgent.indexOf('Firefox');
    if (versionIndex >= 0 && parseInt(navigator.userAgent.substring(versionIndex+8)) < 3) mozillaAvailable = false;
  } catch(err) {}
  return this.each(function(i,e){
    $e = jQuery(e);
    if ($e.hasClass(doneClass)) return;
    $e.addClass(doneClass);
    var classScan = /{(.*)}/.exec(e.className);
    var s = classScan ? parseOptions(classScan[1], settings) : settings;
    var nodeName = e.nodeName.toLowerCase();
    if (nodeName=='input') e = changeInput(e);
    if (webkitAvailable && s.webkit) roundWebkit(e, s);
    else if(mozillaAvailable && s.mozilla && (s.sizex == s.sizey)) roundMozilla(e, s);
    else {
      var bgColor = backgroundColor(e.parentNode);
      var fgColor = backgroundColor(e);
      switch (nodeName) {
        case 'a':
        case 'input':
          roundLink(e, s, bgColor, fgColor);
          break;
        default:
          roundDiv(e, s, bgColor, fgColor);
          break;
      }
    }
  });
  
  function roundWebkit(e, s) {
    var radius = '' + s.sizex + 'px ' + s.sizey + 'px';
    var $e = jQuery(e);
    if (s.tl) $e.css('WebkitBorderTopLeftRadius', radius);
    if (s.tr) $e.css('WebkitBorderTopRightRadius', radius);
    if (s.bl) $e.css('WebkitBorderBottomLeftRadius', radius);
    if (s.br) $e.css('WebkitBorderBottomRightRadius', radius);
  }
  
  function roundMozilla(e, s)
  {  
    var radius = '' + s.sizex + 'px';
    var $e = jQuery(e);
    if (s.tl) $e.css('-moz-border-radius-topleft', radius);
    if (s.tr) $e.css('-moz-border-radius-topright', radius);
    if (s.bl) $e.css('-moz-border-radius-bottomleft', radius);
    if (s.br) $e.css('-moz-border-radius-bottomright', radius);  
  }
  
  function roundLink(e, s, bgColor, fgColor) {
    var table = tableElement("table");
    var tbody = tableElement("tbody");
    table.appendChild(tbody);
    var tr1 = tableElement("tr");
    var td1 = tableElement("td", "top");
    tr1.appendChild(td1);
    var tr2 = tableElement("tr");
    var td2 = relocateContent(e, s, tableElement("td"));
    tr2.appendChild(td2);
    var tr3 = tableElement("tr");
    var td3 = tableElement("td", "bottom");
    tr3.appendChild(td3);
    if (s.tl||s.tr) {
      tbody.appendChild(tr1);
      addCorners(td1, s, bgColor, fgColor, true);
    }
    tbody.appendChild(tr2);
    if (s.bl||s.br) {
      tbody.appendChild(tr3);
      addCorners(td3, s, bgColor, fgColor, false);
    }
    e.appendChild(table);
    /* Clicking on $('a>table') in IE will trigger onclick but not the href  */
    if (jQuery.browser.msie) table.onclick = ieLinkBypass;
    /* Firefox 2 will render garbage unless we hide the overflow here */
    e.style.overflow = 'hidden';
  }
  
  function ieLinkBypass() {
    if (!this.parentNode.onclick) this.parentNode.click();
  }
  
  function changeInput(e) {
    var a1 = document.createElement("a");
    a1.id = e.id;
    a1.className = e.className;
    if (e.onclick) {
      a1.href = 'javascript:'
      a1.onclick = e.onclick;
    } else {
      jQuery(e).parent('form').each(function() {a1.href = this.action;});
      a1.onclick = submitForm;
    }
    var a2 = document.createTextNode(e.value);
    a1.appendChild(a2);
    e.parentNode.replaceChild(a1, e);
    return a1;
  }

  function submitForm() {
    jQuery(this).parent('form').each(function() {this.submit()});
    return false;
  }

  function roundDiv(e, s, bgColor, fgColor) {
    var div = relocateContent(e, s, document.createElement('div'));
    e.appendChild(div);
    if (s.tl||s.tr) addCorners(e, s, bgColor, fgColor, true);
    if (s.bl||s.br) addCorners(e, s, bgColor, fgColor, false);
  }
  
  function relocateContent(e, s, d) {
    var $e = jQuery(e);
    var c;
    while(c=e.firstChild) d.appendChild(c);
    if (e.style.height) {
      var h = parseInt($e.css('height'));
      d.style.height = h + 'px';
      h += parseInt($e.css('padding-top')) + parseInt($e.css('padding-bottom'));
      e.style.height = h + 'px';
    }
    if (e.style.width) {
      var w = parseInt($e.css('width'));
      d.style.width = w + 'px';
      w += parseInt($e.css('padding-left')) + parseInt($e.css('padding-right'));
      e.style.width = w + 'px';
    }
    d.style.paddingLeft = $e.css('padding-left');
    d.style.paddingRight = $e.css('padding-right');
    if (s.tl||s.tr) {
      d.style.paddingTop = adjustedPadding(e, s, $e.css('padding-top'), true);
    } else {
      d.style.paddingTop = $e.css('padding-top');
    }
    if (s.bl||s.br) {
      d.style.paddingBottom = adjustedPadding(e, s, $e.css('padding-bottom'), false);
    } else {
      d.style.paddingBottom = $e.css('padding-bottom');
    }
    e.style.padding = 0;
    return d;
  }
  
  function adjustedPadding(e, s, pad, top) {
    if (pad.indexOf("px") < 0) {
      try {
        //TODO Make this check work otherwise remove it
        console.error('%s padding not in pixels', (top ? 'top' : 'bottom'), e);
      }
      catch(err) {}
      pad = s.sizey + 'px';
    }
    pad = parseInt(pad);
    if (pad - s.sizey < 0) {
      try {
        console.error('%s padding is %ipx for %ipx corner:', (top ? 'top' : 'bottom'), pad, s.sizey, e);
      }
      catch(err) {}
      pad = s.sizey;
    }
    return pad - s.sizey + 'px';
  }

  function tableElement(kind, valign) {
    var e = document.createElement(kind)
    e.style.border = 'none';
    e.style.borderCollapse = 'collapse';
    e.style.borderSpacing = 0;
    e.style.padding = 0;
    e.style.margin = 0;
    if (valign) e.style.verticalAlign = valign;
    return e;
  }
  
  function backgroundColor(e) {
    try {
      var c = jQuery.css(e, "background-color");
      if ( c.match(/^(transparent|rgba\(0,\s*0,\s*0,\s*0\))$/i) && e.parentNode )
         return backgroundColor(e.parentNode);
      if (c==null)
        return "#ffffff";
      if (c.indexOf("rgb") > -1)
    	  c = rgb2hex(c);
      if (c.length == 4)
  	    c = hexShort2hex(c);
      return c;
    } catch(err) {
      return "#ffffff";
    }
  }
  
  function hexShort2hex(c) {
    return '#' +
    c.substring(1,2) +
    c.substring(1,2) +
    c.substring(2,3) +
    c.substring(2,3) +
    c.substring(3,4) +
    c.substring(3,4);
  }

  function rgb2hex(c) {
  	var x = 255;
  	var hex = '';
  	var i;
  	var regexp=/([0-9]+)[, ]+([0-9]+)[, ]+([0-9]+)/;
  	var array=regexp.exec(c);
  	for(i=1;i<4;i++) hex += ('0'+parseInt(array[i]).toString(16)).slice(-2);
  	return '#'+hex;
  }
  
  function parseOptions(options, settings) {
    var options = options || '';
    var s = {sizex:5, sizey:5, tl: false, tr: false, bl: false, br: false, webkit:true, mozilla: true, transparent:false};
    if (settings) {
      s.sizex = settings.sizex;
      s.sizey = settings.sizey;
      s.webkit = settings.webkit;
      s.transparent = settings.transparent;
      s.mozilla = settings.mozilla;
    }
    var sizex_set = false;
    var corner_set = false;
    jQuery.each(options.split(' '), function(idx, option) {
      option = option.toLowerCase();
      var i = parseInt(option);
      if (i > 0 && option == i + 'px') {
        s.sizey = i;
        if (!sizex_set) s.sizex = i;
        sizex_set = true;
      } else switch (option) {
        case 'no-native': s.webkit = s.mozilla = false; break;
        case 'webkit': s.webkit = true; break;
        case 'no-webkit': s.webkit = false; break;
        case 'mozilla': s.mozilla = true; break;
        case 'no-mozilla': s.mozilla = false; break;
        case 'anti-alias': s.transparent = false; break;
        case 'transparent': s.transparent = true; break;
        case 'top': corner_set = s.tl = s.tr = true; break;
        case 'right': corner_set = s.tr = s.br = true; break;
        case 'bottom': corner_set = s.bl = s.br = true; break;
        case 'left': corner_set = s.tl = s.bl = true; break;
        case 'top-left': corner_set = s.tl = true; break;
        case 'top-right': corner_set = s.tr = true; break;
        case 'bottom-left': corner_set = s.bl = true; break;
        case 'bottom-right': corner_set = s.br = true; break;
      }
    });
    if (!corner_set) {
      if (!settings) {
        s.tl = s.tr = s.bl = s.br = true;
      } else {
        s.tl = settings.tl;
        s.tr = settings.tr;
        s.bl = settings.bl;
        s.br = settings.br;
      }
    }
    return s;
  }
  
  function alphaBlend(a, b, alpha) {
    var ca = Array(
      parseInt('0x' + a.substring(1, 3)),
      parseInt('0x' + a.substring(3, 5)),
      parseInt('0x' + a.substring(5, 7))
    );
    var cb = Array(
      parseInt('0x' + b.substring(1, 3)),
      parseInt('0x' + b.substring(3, 5)),
      parseInt('0x' + b.substring(5, 7))
    );
    r = '0' + Math.round(ca[0] + (cb[0] - ca[0])*alpha).toString(16);
    g = '0' + Math.round(ca[1] + (cb[1] - ca[1])*alpha).toString(16);
    b = '0' + Math.round(ca[2] + (cb[2] - ca[2])*alpha).toString(16);
    return '#'
      + r.substring(r.length - 2)
      + g.substring(g.length - 2)
      + b.substring(b.length - 2);
  }

  function addCorners(e, s, bgColor, fgColor, top) {
    if (s.transparent) addTransparentCorners(e, s, bgColor, top);
    else addAntiAliasedCorners(e, s, bgColor, fgColor, top);
  }
  
  function addAntiAliasedCorners(e, s, bgColor, fgColor, top) {
    var i, j;
    var d = document.createElement("div");
    d.style.fontSize = '1px';
    d.style.backgroundColor = bgColor;
    var lastarc = 0;
    for (i = 1; i <= s.sizey; i++) {
      var coverage, arc2, arc3;
      // Find intersection of arc with bottom of pixel row
      var arc = Math.sqrt(1.0 - Math.pow(1.0 - i / s.sizey, 2)) * s.sizex;
      // Calculate how many pixels are bg, fg and blended.
      var n_bg = s.sizex - Math.ceil(arc);
      var n_fg = Math.floor(lastarc);
      var n_aa = s.sizex - n_bg - n_fg;
      // Create pixel row wrapper
      var x = document.createElement("div");
      var y = d;
      x.style.margin = "0px " + n_bg + "px";
      x.style.height = '1px';
      x.style.overflow = 'hidden';
      // Create the pixel divs for a row (at least one)
      for (j = 1; j <= n_aa; j++) {
        // Calculate coverage per pixel (approximates arc within the pixel)
        if (j == 1) {
          if (j == n_aa) {
            // Single pixel
            coverage = ((arc + lastarc) * .5) - n_fg;
          }
          else {
            // First in a run
            arc2 = Math.sqrt(1.0 - Math.pow(1.0 - (n_bg + 1) / s.sizex, 2)) * s.sizey;
            coverage = (arc2 - (s.sizey - i)) * (arc - n_fg - n_aa + 1) * .5;
          }
        }
        else if (j == n_aa) {
          // Last in a run
          arc2 = Math.sqrt(1.0 - Math.pow((s.sizex - n_bg - j + 1) / s.sizex, 2)) * s.sizey;
          coverage = 1.0 - (1.0 - (arc2 - (s.sizey - i))) * (1.0 - (lastarc - n_fg)) * .5;
        }
        else {
          // Middle of a run
          arc3 = Math.sqrt(1.0 - Math.pow((s.sizex - n_bg - j) / s.sizex, 2)) * s.sizey;
          arc2 = Math.sqrt(1.0 - Math.pow((s.sizex - n_bg - j + 1) / s.sizex, 2)) * s.sizey;
          coverage = ((arc2 + arc3) * .5) - (s.sizey - i);
        }
        
        addCornerDiv(s, x, y, top, alphaBlend(bgColor, fgColor, coverage));
        y = x;
        var x = y.cloneNode(false);
        x.style.margin = "0px 1px";
      }
      addCornerDiv(s, x, y, top, fgColor);
      lastarc = arc;
    }
    if (top)
      e.insertBefore(d, e.firstChild);
    else
      e.appendChild(d);
  }
  
  function addCornerDiv(s, x, y, top, color) {
    if (top && !s.tl) x.style.marginLeft = 0;
    if (top && !s.tr) x.style.marginRight = 0;
    if (!top && !s.bl) x.style.marginLeft = 0;
    if (!top && !s.br) x.style.marginRight = 0;
    x.style.backgroundColor = color;
    if (top)
      y.appendChild(x);
    else
      y.insertBefore(x, y.firstChild);
  }

  function addTransparentCorners(e, s, bgColor, top) {
    var d = document.createElement("div");
    d.style.fontSize = '1px';
    var strip = document.createElement('div');
    strip.style.overflow = 'hidden';
    strip.style.height = '1px';
    strip.style.borderColor = bgColor;
    strip.style.borderStyle = 'none solid';
    var sizex = s.sizex-1;
    var sizey = s.sizey-1;
    if (!sizey) sizey = 1; /* hint for 1x1 */
    for (var i=0; i < s.sizey; i++) {
      var w = sizex - Math.floor(Math.sqrt(1.0 - Math.pow(1.0 - i / sizey, 2)) * sizex);
      if (i==2 && s.sizex==6 && s.sizey==6) w = 2; /* hint for 6x6 */
      var x = strip.cloneNode(false);
      x.style.borderWidth = '0 '+ w +'px';
      if (top) x.style.borderWidth = '0 '+(s.tr?w:0)+'px 0 '+(s.tl?w:0)+'px';
      else x.style.borderWidth = '0 '+(s.br?w:0)+'px 0 '+(s.bl?w:0)+'px';
      top ? d.appendChild(x) : d.insertBefore(x, d.firstChild);
    } 
    if (top)
      e.insertBefore(d, e.firstChild);
    else
      e.appendChild(d);
  }


}
/*
 * Pretty date printing code
 * Copyright (c) 2008 Dean Landolt (deanlandolt.com)
 * Re-write by Zach Leatherman (zachleat.com)
 * 
 * Adopted from the John Resig's pretty.js
 * at http://ejohn.org/blog/javascript-pretty-date
 * and henrah's proposed modification 
 * at http://ejohn.org/blog/javascript-pretty-date/#comment-297458
 * 
 * Licensed under the MIT license.
 */

function prettyDate(date_str){
    var time_formats = [
        [60, 'Just Now'],
        [90, '1 Minute'], // 60*1.5
        [3600, 'Mins', 60], // 60*60, 60
        [5400, '1 Hour'], // 60*60*1.5
        [86400, 'Hours', 3600], // 60*60*24, 60*60
        [129600, '1 Day'], // 60*60*24*1.5
        [604800, 'Days', 86400], // 60*60*24*7, 60*60*24
        [907200, '1 Week'], // 60*60*24*7*1.5
        [2628000, 'Weeks', 604800], // 60*60*24*(365/12), 60*60*24*7
        [3942000, '1 Month'], // 60*60*24*(365/12)*1.5
        [31536000, 'Months', 2628000], // 60*60*24*365, 60*60*24*(365/12)
        [47304000, '1 Year'], // 60*60*24*365*1.5
        [3153600000, 'Years', 31536000], // 60*60*24*365*100, 60*60*24*365
        [4730400000, '1 Century'] // 60*60*24*365*100*1.5
    ];

    var seconds = Math.round( ((new Date()).getTime() - localTimeDelta - Date.parse(date_str)) / 1000 );
    var token = '';
    var i = 0;
    var format;
 
    if (seconds < 0) {
        seconds = 0;
        token = '';
    }

    // temp code to make bugs more obvious
    if( seconds < 0 ) {
        return seconds + " Secs" + token;
    } else {
        while ((format = time_formats[i++]) !== undefined) {
            if (seconds < format[0]) {
                if (format.length == 2) {
                    return format[1] + (i > 1 ? token : ''); // Conditional so we don't return Just Now Ago
                } else {
                    return Math.round(seconds / format[2]) + ' ' + format[1] + (i > 1 ? token : '');
                }
            }
        }
    }

    // overflow for centuries
    if(seconds > 4730400000) {
        return Math.round(seconds / 4730400000) + ' Centuries' + token;
    }

    return date_str;
}

jQuery.fn.prettyDate = function(){
    return this.each(function(){
        var date = prettyDate(this.title);
        if ( date ) {
            jQuery(this).text( date );
        }
    });
};


/*
 * jQuery UI Slider 1.8.1
 *
 * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Slider
 *
 * Depends:
 *	jquery.ui.core.js
 *	jquery.ui.mouse.js
 *	jquery.ui.widget.js
 */

(function( $ ) {

// number of pages in a slider
// (how many times can you page up/down to go through the whole range)
var numPages = 5;

$.widget( "ui.slider", $.ui.mouse, {

	widgetEventPrefix: "slide",

	options: {
		animate: false,
		distance: 0,
		max: 100,
		min: 0,
		orientation: "horizontal",
		range: false,
		step: 1,
		value: 0,
        minConstraint: false,
        maxConstraint: false,
        enforceConstraints: true,
		values: null
	},

	_create: function() {
		var self = this,
			o = this.options;

		this._keySliding = false;
		this._mouseSliding = false;
		this._animateOff = true;
		this._handleIndex = null;
		this._detectOrientation();
		this._mouseInit();

		this.element
			.addClass( "ui-slider" +
				" ui-slider-" + this.orientation +
				" ui-widget" +
				" ui-widget-content" +
				" ui-corner-all" );
		
		if ( o.disabled ) {
			this.element.addClass( "ui-slider-disabled ui-disabled" );
		}

		this.range = $([]);

		if ( o.range ) {
			if ( o.range === true ) {
				this.range = $( "<div></div>" );
				if ( !o.values ) {
					o.values = [ this._valueMin(), this._valueMin() ];
				}
				if ( o.values.length && o.values.length !== 2 ) {
					o.values = [ o.values[0], o.values[0] ];
				}
			} else {
				this.range = $( "<div></div>" );
			}

			this.range
				.appendTo( this.element )
				.addClass( "ui-slider-range" );

			if ( o.range === "min" || o.range === "max" ) {
				this.range.addClass( "ui-slider-range-" + o.range );
			}

			// note: this isn't the most fittingly semantic framework class for this element,
			// but worked best visually with a variety of themes
			this.range.addClass( "ui-widget-header" );
		}

		if ( $( ".ui-slider-handle", this.element ).length === 0 ) {
			$( "<a href='#'></a>" )
				.appendTo( this.element )
				.addClass( "ui-slider-handle" );
		}

		if ( o.values && o.values.length ) {
			while ( $(".ui-slider-handle", this.element).length < o.values.length ) {
				$( "<a href='#'></a>" )
					.appendTo( this.element )
					.addClass( "ui-slider-handle" );
			}
		}

		this.handles = $( ".ui-slider-handle", this.element )
			.addClass( "ui-state-default" +
				" ui-corner-all" );

		this.handle = this.handles.eq( 0 );

		this.handles.add( this.range ).filter( "a" )
			.click(function( event ) {
				event.preventDefault();
			})
			.hover(function() {
				if ( !o.disabled ) {
					$( this ).addClass( "ui-state-hover" );
				}
			}, function() {
				$( this ).removeClass( "ui-state-hover" );
			})
			.focus(function() {
				if ( !o.disabled ) {
					$( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
					$( this ).addClass( "ui-state-focus" );
				} else {
					$( this ).blur();
				}
			})
			.blur(function() {
				$( this ).removeClass( "ui-state-focus" );
			});

		this.handles.each(function( i ) {
			$( this ).data( "index.ui-slider-handle", i );
		});

		this.handles
			.keydown(function( event ) {
				var ret = true,
					index = $( this ).data( "index.ui-slider-handle" ),
					allowed,
					curVal,
					newVal,
					step;
	
				if ( self.options.disabled ) {
					return;
				}
	
				switch ( event.keyCode ) {
					case $.ui.keyCode.HOME:
					case $.ui.keyCode.END:
					case $.ui.keyCode.PAGE_UP:
					case $.ui.keyCode.PAGE_DOWN:
					case $.ui.keyCode.UP:
					case $.ui.keyCode.RIGHT:
					case $.ui.keyCode.DOWN:
					case $.ui.keyCode.LEFT:
						ret = false;
						if ( !self._keySliding ) {
							self._keySliding = true;
							$( this ).addClass( "ui-state-active" );
							allowed = self._start( event, index );
							if ( allowed === false ) {
								return;
							}
						}
						break;
				}
	
				step = self.options.step;
				if ( self.options.values && self.options.values.length ) {
					curVal = newVal = self.values( index );
				} else {
					curVal = newVal = self.value();
				}
	
				switch ( event.keyCode ) {
					case $.ui.keyCode.HOME:
						newVal = self._valueMin();
						break;
					case $.ui.keyCode.END:
						newVal = self._valueMax();
						break;
					case $.ui.keyCode.PAGE_UP:
						newVal = curVal + ( (self._valueMax() - self._valueMin()) / numPages );
						break;
					case $.ui.keyCode.PAGE_DOWN:
						newVal = curVal - ( (self._valueMax() - self._valueMin()) / numPages );
						break;
					case $.ui.keyCode.UP:
					case $.ui.keyCode.RIGHT:
						if ( curVal === self._valueMax() ) {
							return;
						}
						newVal = curVal + step;
						break;
					case $.ui.keyCode.DOWN:
					case $.ui.keyCode.LEFT:
						if ( curVal === self._valueMin() ) {
							return;
						}
						newVal = curVal - step;
						break;
				}
	
				self._slide( event, index, newVal );
	
				return ret;
	
			})
			.keyup(function( event ) {
				var index = $( this ).data( "index.ui-slider-handle" );
	
				if ( self._keySliding ) {
					self._keySliding = false;
					self._stop( event, index );
					self._change( event, index );
					$( this ).removeClass( "ui-state-active" );
				}
	
			});

		this._refreshValue();

		this._animateOff = false;

        if( o.enforceConstraints ) {
            this.constraintRange = $("<div></div>");
            this.constraintRange
                .appendTo(this.element)
                .addClass("ui-slider-range");
            this.constraintRange.addClass("ui-widget-header");
            this.constraints([this.options.minConstraint, this.options.maxConstraint]);
        }
	},

	destroy: function() {
		this.handles.remove();
		this.range.remove();

		this.element
			.removeClass( "ui-slider" +
				" ui-slider-horizontal" +
				" ui-slider-vertical" +
				" ui-slider-disabled" +
				" ui-widget" +
				" ui-widget-content" +
				" ui-corner-all" )
			.removeData( "slider" )
			.unbind( ".slider" );

		this._mouseDestroy();

		return this;
	},

	_mouseCapture: function( event ) {
		var o = this.options,
			position,
			normValue,
			distance,
			closestHandle,
			self,
			index,
			allowed,
			offset,
			mouseOverHandle;

		if ( o.disabled ) {
			return false;
		}

		this.elementSize = {
			width: this.element.outerWidth(),
			height: this.element.outerHeight()
		};
		this.elementOffset = this.element.offset();

		position = { x: event.pageX, y: event.pageY };
		normValue = this._normValueFromMouse( position );
		distance = this._valueMax() - this._valueMin() + 1;
		self = this;
		this.handles.each(function( i ) {
			var thisDistance = Math.abs( normValue - self.values(i) );
			if ( distance > thisDistance ) {
				distance = thisDistance;
				closestHandle = $( this );
				index = i;
			}
		});

		// workaround for bug #3736 (if both handles of a range are at 0,
		// the first is always used as the one with least distance,
		// and moving it is obviously prevented by preventing negative ranges)
		if( o.range === true && this.values(1) === o.min ) {
			index += 1;
			closestHandle = $( this.handles[index] );
		}

		allowed = this._start( event, index );
		if ( allowed === false ) {
			return false;
		}
		this._mouseSliding = true;

		self._handleIndex = index;

		closestHandle
			.addClass( "ui-state-active" )
			.focus();
		
		offset = closestHandle.offset();
		mouseOverHandle = !$( event.target ).parents().andSelf().is( ".ui-slider-handle" );
		this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
			left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
			top: event.pageY - offset.top -
				( closestHandle.height() / 2 ) -
				( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
				( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
				( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
		};

		normValue = this._normValueFromMouse( position );
		this._slide( event, index, normValue );
		this._animateOff = true;
		return true;
	},

	_mouseStart: function( event ) {
		return true;
	},

	_mouseDrag: function( event ) {
		var position = { x: event.pageX, y: event.pageY },
			normValue = this._normValueFromMouse( position );
		
		this._slide( event, this._handleIndex, normValue );

		return false;
	},

	_mouseStop: function( event ) {
		this.handles.removeClass( "ui-state-active" );
		this._mouseSliding = false;

		this._stop( event, this._handleIndex );
		this._change( event, this._handleIndex );

		this._handleIndex = null;
		this._clickOffset = null;
		this._animateOff = false;

		return false;
	},
	
	_detectOrientation: function() {
		this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
	},

	_normValueFromMouse: function( position ) {
		var pixelTotal,
			pixelMouse,
			percentMouse,
			valueTotal,
			valueMouse;

		if ( this.orientation === "horizontal" ) {
			pixelTotal = this.elementSize.width;
			pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
		} else {
			pixelTotal = this.elementSize.height;
			pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
		}

		percentMouse = ( pixelMouse / pixelTotal );
		if ( percentMouse > 1 ) {
			percentMouse = 1;
		}
		if ( percentMouse < 0 ) {
			percentMouse = 0;
		}
		if ( this.orientation === "vertical" ) {
			percentMouse = 1 - percentMouse;
		}

		valueTotal = this._valueMax() - this._valueMin();
		valueMouse = this._valueMin() + percentMouse * valueTotal;

		return this._trimAlignValue( valueMouse );
	},

	_start: function( event, index ) {
		var uiHash = {
			handle: this.handles[ index ],
			value: this.value()
		};
		if ( this.options.values && this.options.values.length ) {
			uiHash.value = this.values( index );
			uiHash.values = this.values();
		}
		return this._trigger( "start", event, uiHash );
	},

	_slide: function( event, index, newVal ) {
		var otherVal,
			newValues,
			allowed;

        if (this.options.enforceConstraints) {
            if (this.options.minConstraint !== false && newVal < this.options.minConstraint) {newVal = this.options.minConstraint;}
            if (this.options.maxConstraint !== false && newVal > this.options.maxConstraint) {newVal = this.options.maxConstraint;}
        }

		if ( this.options.values && this.options.values.length ) {
			otherVal = this.values( index ? 0 : 1 );

			if ( ( this.options.values.length === 2 && this.options.range === true ) && 
					( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
				) {
				newVal = otherVal;
			}

			if ( newVal !== this.values( index ) ) {
				newValues = this.values();
				newValues[ index ] = newVal;
				// A slide can be canceled by returning false from the slide callback
				allowed = this._trigger( "slide", event, {
					handle: this.handles[ index ],
					value: newVal,
					values: newValues
				} );
				otherVal = this.values( index ? 0 : 1 );
				if ( allowed !== false ) {
					this.values( index, newVal, true );
				}
			}
		} else {
			if ( newVal !== this.value() ) {
				// A slide can be canceled by returning false from the slide callback
				allowed = this._trigger( "slide", event, {
					handle: this.handles[ index ],
					value: newVal
				} );
				if ( allowed !== false ) {
					this.value( newVal );
				}
			}
		}
	},

	_stop: function( event, index ) {
		var uiHash = {
			handle: this.handles[ index ],
			value: this.value()
		};
		if ( this.options.values && this.options.values.length ) {
			uiHash.value = this.values( index );
			uiHash.values = this.values();
		}

		this._trigger( "stop", event, uiHash );
	},

	_change: function( event, index ) {
		if ( !this._keySliding && !this._mouseSliding ) {
			var uiHash = {
				handle: this.handles[ index ],
				value: this.value()
			};
			if ( this.options.values && this.options.values.length ) {
				uiHash.value = this.values( index );
				uiHash.values = this.values();
			}

			this._trigger( "change", event, uiHash );
		}
	},

	value: function( newValue ) {
		if ( arguments.length ) {
			this.options.value = this._trimAlignValue( newValue );
			this._refreshValue();
			this._change( null, 0 );
		}

		return this._value();
	},

	values: function( index, newValue ) {
		var vals,
			newValues,
			i;

		if ( arguments.length > 1 ) {
			this.options.values[ index ] = this._trimAlignValue( newValue );
			this._refreshValue();
			this._change( null, index );
		}

		if ( arguments.length ) {
			if ( $.isArray( arguments[ 0 ] ) ) {
				vals = this.options.values;
				newValues = arguments[ 0 ];
				for ( i = 0; i < vals.length; i += 1 ) {
					vals[ i ] = this._trimAlignValue( newValues[ i ] );
					this._change( null, i );
				}
				this._refreshValue();
			} else {
				if ( this.options.values && this.options.values.length ) {
					return this._values( index );
				} else {
					return this.value();
				}
			}
		} else {
			return this._values();
		}
	},

	_setOption: function( key, value ) {
		var i,
			valsLength = 0;

		if ( $.isArray( this.options.values ) ) {
			valsLength = this.options.values.length;
		}

        if (this.options.enforceConstraints) {
            if (this.options.minConstraint !== false && value < this.options.minConstraint) {value = this.options.minConstraint;}
            if (this.options.maxConstraint !== false && value > this.options.maxConstraint) {value = this.options.maxConstraint;}
        }

		$.Widget.prototype._setOption.apply( this, arguments );

		switch ( key ) {
			case "disabled":
				if ( value ) {
					this.handles.filter( ".ui-state-focus" ).blur();
					this.handles.removeClass( "ui-state-hover" );
					this.handles.attr( "disabled", "disabled" );
					this.element.addClass( "ui-disabled" );
				} else {
					this.handles.removeAttr( "disabled" );
					this.element.removeClass( "ui-disabled" );
				}
				break;
			case "orientation":
				this._detectOrientation();
				this.element
					.removeClass( "ui-slider-horizontal ui-slider-vertical" )
					.addClass( "ui-slider-" + this.orientation );
				this._refreshValue();
				break;
			case "value":
				this._animateOff = true;
				this._refreshValue();
				this._change( null, 0 );
				this._animateOff = false;
				break;
			case "values":
				this._animateOff = true;
				this._refreshValue();
				for ( i = 0; i < valsLength; i += 1 ) {
					this._change( null, i );
				}
				this._animateOff = false;
				break;
		}
	},

	//internal value getter
	// _value() returns value trimmed by min and max, aligned by step
	_value: function() {
		var val = this.options.value;
		val = this._trimAlignValue( val );

		return val;
	},

	//internal values getter
	// _values() returns array of values trimmed by min and max, aligned by step
	// _values( index ) returns single value trimmed by min and max, aligned by step
	_values: function( index ) {
		var val,
			vals,
			i;

		if ( arguments.length ) {
			val = this.options.values[ index ];
			val = this._trimAlignValue( val );

			return val;
		} else {
			// .slice() creates a copy of the array
			// this copy gets trimmed by min and max and then returned
			vals = this.options.values.slice();
			for ( i = 0; i < vals.length; i+= 1) {
				vals[ i ] = this._trimAlignValue( vals[ i ] );
			}

			return vals;
		}
	},
	
	// returns the step-aligned value that val is closest to, between (inclusive) min and max
	_trimAlignValue: function( val ) {
		if ( val < this._valueMin() ) {
			return this._valueMin();
		}
		if ( val > this._valueMax() ) {
			return this._valueMax();
		}
		var step = this.options.step,
			valModStep = val % step,
			alignValue = val - valModStep;

		if ( valModStep >= ( step / 2 ) ) {
			alignValue += step;
		}

		// Since JavaScript has problems with large floats, round
		// the final value to 5 digits after the decimal point (see #4124)
		return parseFloat( alignValue.toFixed(5) );
	},

	_valueMin: function() {
		return this.options.min;
	},

	_valueMax: function() {
		return this.options.max;
	},
	
	_refreshValue: function() {
		var oRange = this.options.range,
			o = this.options,
			self = this,
			animate = ( !this._animateOff ) ? o.animate : false,
			valPercent,
			_set = {},
			lastValPercent,
			value,
			valueMin,
			valueMax;

		if ( this.options.values && this.options.values.length ) {
			this.handles.each(function( i, j ) {
				valPercent = ( self.values(i) - self._valueMin() ) / ( self._valueMax() - self._valueMin() ) * 100;
				_set[ self.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
				$( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
				if ( self.options.range === true ) {
					if ( self.orientation === "horizontal" ) {
						if ( i === 0 ) {
							self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
						}
						if ( i === 1 ) {
							self.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
						}
					} else {
						if ( i === 0 ) {
							self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
						}
						if ( i === 1 ) {
							self.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
						}
					}
				}
				lastValPercent = valPercent;
			});
		} else {
			value = this.value();
			valueMin = this._valueMin();
			valueMax = this._valueMax();
			valPercent = ( valueMax !== valueMin ) ?
					( value - valueMin ) / ( valueMax - valueMin ) * 100 :
					0;
			_set[ self.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
			this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );

			if ( oRange === "min" && this.orientation === "horizontal" ) {
				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
			}
			if ( oRange === "max" && this.orientation === "horizontal" ) {
				this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
			}
			if ( oRange === "min" && this.orientation === "vertical" ) {
				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
			}
			if ( oRange === "max" && this.orientation === "vertical" ) {
				this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
			}
		}
	},

    constraints: function(constraints) {
         if (!arguments.length) {
             return [this.options.minConstraint, this.options.maxConstraint];
         }

         var valueMin = this._valueMin();
         var valueMax = this._valueMax();
         var valueDiff = valueMax - valueMin;
         constraints[0] = constraints[0] !== false ? constraints[0] : valueMin;
         constraints[1] = constraints[1] !== false ? constraints[1] : valueMax;
         constraints[0] = constraints[0] < valueMin ? valueMin : constraints[0];
         constraints[1] = constraints[1] > valueMax ? valueMax : constraints[1];
         this.options.minConstraint = constraints[0];
         this.options.maxConstraint = constraints[1];
         this.constraintRange.css(this.orientation == 'horizontal' ? 'left' : 'bottom', constraints[0]*100/valueDiff+'%');
         this.constraintRange.css(this.orientation == 'horizontal' ? 'width' : 'height', (constraints[1] - constraints[0])*100/valueDiff+'%');
    }

});

$.extend( $.ui.slider, {
	version: "1.8.1"
});

}(jQuery));
/*
 * jQuery UI Sortable 1.8.1
 *
 * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Sortables
 *
 * Depends:
 *	jquery.ui.core.js
 *	jquery.ui.mouse.js
 *	jquery.ui.widget.js
 */
(function($) {

$.widget("ui.sortable", $.ui.mouse, {
	widgetEventPrefix: "sort",
	options: {
		appendTo: "parent",
		axis: false,
		connectWith: false,
		containment: false,
		cursor: 'auto',
		cursorAt: false,
		dropOnEmpty: true,
		forcePlaceholderSize: false,
		forceHelperSize: false,
		grid: false,
		handle: false,
		helper: "original",
		items: '> *',
		opacity: false,
		placeholder: false,
		revert: false,
		scroll: true,
		scrollSensitivity: 20,
		scrollSpeed: 20,
		scope: "default",
		tolerance: "intersect",
		zIndex: 1000
	},
	_create: function() {

		var o = this.options;
		this.containerCache = {};
		this.element.addClass("ui-sortable");

		//Get the items
		this.refresh();

		//Let's determine if the items are floating
		this.floating = this.items.length ? (/left|right/).test(this.items[0].item.css('float')) : false;

		//Let's determine the parent's offset
		this.offset = this.element.offset();

		//Initialize mouse events for interaction
		this._mouseInit();

	},

	destroy: function() {
		this.element
			.removeClass("ui-sortable ui-sortable-disabled")
			.removeData("sortable")
			.unbind(".sortable");
		this._mouseDestroy();

		for ( var i = this.items.length - 1; i >= 0; i-- )
			this.items[i].item.removeData("sortable-item");

		return this;
	},

	_setOption: function(key, value){
		if ( key === "disabled" ) {
			this.options[ key ] = value;
	
			this.widget()
				[ value ? "addClass" : "removeClass"]( "ui-sortable-disabled" );
		} else {
			// Don't call widget base _setOption for disable as it adds ui-state-disabled class
			$.Widget.prototype._setOption.apply(this, arguments);
		}
	},

	_mouseCapture: function(event, overrideHandle) {

		if (this.reverting) {
			return false;
		}

		if(this.options.disabled || this.options.type == 'static') return false;

		//We have to refresh the items data once first
		this._refreshItems(event);

		//Find out if the clicked node (or one of its parents) is a actual item in this.items
		var currentItem = null, self = this, nodes = $(event.target).parents().each(function() {
			if($.data(this, 'sortable-item') == self) {
				currentItem = $(this);
				return false;
			}
		});
		if($.data(event.target, 'sortable-item') == self) currentItem = $(event.target);

		if(!currentItem) return false;
		if(this.options.handle && !overrideHandle) {
			var validHandle = false;

			$(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == event.target) validHandle = true; });
			if(!validHandle) return false;
		}

		this.currentItem = currentItem;
		this._removeCurrentsFromItems();
		return true;

	},

	_mouseStart: function(event, overrideHandle, noActivation) {

		var o = this.options, self = this;
		this.currentContainer = this;

		//We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
		this.refreshPositions();

		//Create and append the visible helper
		this.helper = this._createHelper(event);

		//Cache the helper size
		this._cacheHelperProportions();

		/*
		 * - Position generation -
		 * This block generates everything position related - it's the core of draggables.
		 */

		//Cache the margins of the original element
		this._cacheMargins();

		//Get the next scrolling parent
		this.scrollParent = this.helper.scrollParent();

		//The element's absolute position on the page minus margins
		this.offset = this.currentItem.offset();
		this.offset = {
			top: this.offset.top - this.margins.top,
			left: this.offset.left - this.margins.left
		};

		// Only after we got the offset, we can change the helper's position to absolute
		// TODO: Still need to figure out a way to make relative sorting possible
		this.helper.css("position", "absolute");
		this.cssPosition = this.helper.css("position");

		$.extend(this.offset, {
			click: { //Where the click happened, relative to the element
				left: event.pageX - this.offset.left,
				top: event.pageY - this.offset.top
			},
			parent: this._getParentOffset(),
			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
		});

		//Generate the original position
		this.originalPosition = this._generatePosition(event);
		this.originalPageX = event.pageX;
		this.originalPageY = event.pageY;

		//Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));

		//Cache the former DOM position
		this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };

		//If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
		if(this.helper[0] != this.currentItem[0]) {
			this.currentItem.hide();
		}

		//Create the placeholder
		this._createPlaceholder();

		//Set a containment if given in the options
		if(o.containment)
			this._setContainment();

		if(o.cursor) { // cursor option
			if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor");
			$('body').css("cursor", o.cursor);
		}

		if(o.opacity) { // opacity option
			if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity");
			this.helper.css("opacity", o.opacity);
		}

		if(o.zIndex) { // zIndex option
			if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex");
			this.helper.css("zIndex", o.zIndex);
		}

		//Prepare scrolling
		if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML')
			this.overflowOffset = this.scrollParent.offset();

		//Call callbacks
		this._trigger("start", event, this._uiHash());

		//Recache the helper size
		if(!this._preserveHelperProportions)
			this._cacheHelperProportions();


		//Post 'activate' events to possible containers
		if(!noActivation) {
			 for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, self._uiHash(this)); }
		}

		//Prepare possible droppables
		if($.ui.ddmanager)
			$.ui.ddmanager.current = this;

		if ($.ui.ddmanager && !o.dropBehaviour)
			$.ui.ddmanager.prepareOffsets(this, event);

		this.dragging = true;

		this.helper.addClass("ui-sortable-helper");
		this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
		return true;

	},

	_mouseDrag: function(event) {

		//Compute the helpers position
		this.position = this._generatePosition(event);
		this.positionAbs = this._convertPositionTo("absolute");

		if (!this.lastPositionAbs) {
			this.lastPositionAbs = this.positionAbs;
		}

		//Do scrolling
		if(this.options.scroll) {
			var o = this.options, scrolled = false;
			if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {

				if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
				else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;

				if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
				else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;

			} else {

				if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
				else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);

				if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
				else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);

			}

			if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
				$.ui.ddmanager.prepareOffsets(this, event);
		}

		//Regenerate the absolute position used for position checks
		this.positionAbs = this._convertPositionTo("absolute");

		//Set the helper position
		if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
		if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';

		//Rearrange
		for (var i = this.items.length - 1; i >= 0; i--) {

			//Cache variables and intersection, continue if no intersection
			var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
			if (!intersection) continue;

			if(itemElement != this.currentItem[0] //cannot intersect with itself
				&&	this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
				&&	!$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
				&& (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true)
				//&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
			) {

				this.direction = intersection == 1 ? "down" : "up";

				if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
					this._rearrange(event, item);
				} else {
					break;
				}

				this._trigger("change", event, this._uiHash());
				break;
			}
		}

		//Post events to containers
		this._contactContainers(event);

		//Interconnect with droppables
		if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);

		//Call callbacks
		this._trigger('sort', event, this._uiHash());

		this.lastPositionAbs = this.positionAbs;
		return false;

	},

	_mouseStop: function(event, noPropagation) {

		if(!event) return;

		//If we are using droppables, inform the manager about the drop
		if ($.ui.ddmanager && !this.options.dropBehaviour)
			$.ui.ddmanager.drop(this, event);

		if(this.options.revert) {
			var self = this;
			var cur = self.placeholder.offset();

			self.reverting = true;

			$(this.helper).animate({
				left: cur.left - this.offset.parent.left - self.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
				top: cur.top - this.offset.parent.top - self.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
			}, parseInt(this.options.revert, 10) || 500, function() {
				self._clear(event);
			});
		} else {
			this._clear(event, noPropagation);
		}

		return false;

	},

	cancel: function() {

		var self = this;

		if(this.dragging) {

			this._mouseUp();

			if(this.options.helper == "original")
				this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
			else
				this.currentItem.show();

			//Post deactivating events to containers
			for (var i = this.containers.length - 1; i >= 0; i--){
				this.containers[i]._trigger("deactivate", null, self._uiHash(this));
				if(this.containers[i].containerCache.over) {
					this.containers[i]._trigger("out", null, self._uiHash(this));
					this.containers[i].containerCache.over = 0;
				}
			}

		}

		//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
		if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
		if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove();

		$.extend(this, {
			helper: null,
			dragging: false,
			reverting: false,
			_noFinalSort: null
		});

		if(this.domPosition.prev) {
			$(this.domPosition.prev).after(this.currentItem);
		} else {
			$(this.domPosition.parent).prepend(this.currentItem);
		}

		return this;

	},

	serialize: function(o) {

		var items = this._getItemsAsjQuery(o && o.connected);
		var str = []; o = o || {};

		$(items).each(function() {
			var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
			if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
		});

		return str.join('&');

	},

	toArray: function(o) {

		var items = this._getItemsAsjQuery(o && o.connected);
		var ret = []; o = o || {};

		items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); });
		return ret;

	},

	/* Be careful with the following core functions */
	_intersectsWith: function(item) {

		var x1 = this.positionAbs.left,
			x2 = x1 + this.helperProportions.width,
			y1 = this.positionAbs.top,
			y2 = y1 + this.helperProportions.height;

		var l = item.left,
			r = l + item.width,
			t = item.top,
			b = t + item.height;

		var dyClick = this.offset.click.top,
			dxClick = this.offset.click.left;

		var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;

		if(	   this.options.tolerance == "pointer"
			|| this.options.forcePointerForContainers
			|| (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height'])
		) {
			return isOverElement;
		} else {

			return (l < x1 + (this.helperProportions.width / 2) // Right Half
				&& x2 - (this.helperProportions.width / 2) < r // Left Half
				&& t < y1 + (this.helperProportions.height / 2) // Bottom Half
				&& y2 - (this.helperProportions.height / 2) < b ); // Top Half

		}
	},

	_intersectsWithPointer: function(item) {

		var isOverElementHeight = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
			isOverElementWidth = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
			isOverElement = isOverElementHeight && isOverElementWidth,
			verticalDirection = this._getDragVerticalDirection(),
			horizontalDirection = this._getDragHorizontalDirection();

		if (!isOverElement)
			return false;

		return this.floating ?
			( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 )
			: ( verticalDirection && (verticalDirection == "down" ? 2 : 1) );

	},

	_intersectsWithSides: function(item) {

		var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
			isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
			verticalDirection = this._getDragVerticalDirection(),
			horizontalDirection = this._getDragHorizontalDirection();

		if (this.floating && horizontalDirection) {
			return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf));
		} else {
			return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf));
		}

	},

	_getDragVerticalDirection: function() {
		var delta = this.positionAbs.top - this.lastPositionAbs.top;
		return delta != 0 && (delta > 0 ? "down" : "up");
	},

	_getDragHorizontalDirection: function() {
		var delta = this.positionAbs.left - this.lastPositionAbs.left;
		return delta != 0 && (delta > 0 ? "right" : "left");
	},

	refresh: function(event) {
		this._refreshItems(event);
		this.refreshPositions();
		return this;
	},

	_connectWith: function() {
		var options = this.options;
		return options.connectWith.constructor == String
			? [options.connectWith]
			: options.connectWith;
	},
	
	_getItemsAsjQuery: function(connected) {

		var self = this;
		var items = [];
		var queries = [];
		var connectWith = this._connectWith();

		if(connectWith && connected) {
			for (var i = connectWith.length - 1; i >= 0; i--){
				var cur = $(connectWith[i]);
				for (var j = cur.length - 1; j >= 0; j--){
					var inst = $.data(cur[j], 'sortable');
					if(inst && inst != this && !inst.options.disabled) {
						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), inst]);
					}
				};
			};
		}

		queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), this]);

		for (var i = queries.length - 1; i >= 0; i--){
			queries[i][0].each(function() {
				items.push(this);
			});
		};

		return $(items);

	},

	_removeCurrentsFromItems: function() {

		var list = this.currentItem.find(":data(sortable-item)");

		for (var i=0; i < this.items.length; i++) {

			for (var j=0; j < list.length; j++) {
				if(list[j] == this.items[i].item[0])
					this.items.splice(i,1);
			};

		};

	},

	_refreshItems: function(event) {

		this.items = [];
		this.containers = [this];
		var items = this.items;
		var self = this;
		var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]];
		var connectWith = this._connectWith();

		if(connectWith) {
			for (var i = connectWith.length - 1; i >= 0; i--){
				var cur = $(connectWith[i]);
				for (var j = cur.length - 1; j >= 0; j--){
					var inst = $.data(cur[j], 'sortable');
					if(inst && inst != this && !inst.options.disabled) {
						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
						this.containers.push(inst);
					}
				};
			};
		}

		for (var i = queries.length - 1; i >= 0; i--) {
			var targetData = queries[i][1];
			var _queries = queries[i][0];

			for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
				var item = $(_queries[j]);

				item.data('sortable-item', targetData); // Data for target checking (mouse manager)

				items.push({
					item: item,
					instance: targetData,
					width: 0, height: 0,
					left: 0, top: 0
				});
			};
		};

	},

	refreshPositions: function(fast) {

		//This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
		if(this.offsetParent && this.helper) {
			this.offset.parent = this._getParentOffset();
		}

		for (var i = this.items.length - 1; i >= 0; i--){
			var item = this.items[i];

			var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;

			if (!fast) {
				item.width = t.outerWidth();
				item.height = t.outerHeight();
			}

			var p = t.offset();
			item.left = p.left;
			item.top = p.top;
		};

		if(this.options.custom && this.options.custom.refreshContainers) {
			this.options.custom.refreshContainers.call(this);
		} else {
			for (var i = this.containers.length - 1; i >= 0; i--){
				var p = this.containers[i].element.offset();
				this.containers[i].containerCache.left = p.left;
				this.containers[i].containerCache.top = p.top;
				this.containers[i].containerCache.width	= this.containers[i].element.outerWidth();
				this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
			};
		}

		return this;
	},

	_createPlaceholder: function(that) {

		var self = that || this, o = self.options;

		if(!o.placeholder || o.placeholder.constructor == String) {
			var className = o.placeholder;
			o.placeholder = {
				element: function() {

					var el = $(document.createElement(self.currentItem[0].nodeName))
						.addClass(className || self.currentItem[0].className+" ui-sortable-placeholder")
						.removeClass("ui-sortable-helper")[0];

					if(!className)
						el.style.visibility = "hidden";

					return el;
				},
				update: function(container, p) {

					// 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
					// 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
					if(className && !o.forcePlaceholderSize) return;

					//If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
					if(!p.height()) { p.height(self.currentItem.innerHeight() - parseInt(self.currentItem.css('paddingTop')||0, 10) - parseInt(self.currentItem.css('paddingBottom')||0, 10)); };
					if(!p.width()) { p.width(self.currentItem.innerWidth() - parseInt(self.currentItem.css('paddingLeft')||0, 10) - parseInt(self.currentItem.css('paddingRight')||0, 10)); };
				}
			};
		}

		//Create the placeholder
		self.placeholder = $(o.placeholder.element.call(self.element, self.currentItem));

		//Append it after the actual current item
		self.currentItem.after(self.placeholder);

		//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
		o.placeholder.update(self, self.placeholder);

	},

	_contactContainers: function(event) {
		
		// get innermost container that intersects with item 
		var innermostContainer = null, innermostIndex = null;		
		
		
		for (var i = this.containers.length - 1; i >= 0; i--){

			// never consider a container that's located within the item itself 
			if($.ui.contains(this.currentItem[0], this.containers[i].element[0]))
				continue;

			if(this._intersectsWith(this.containers[i].containerCache)) {

				// if we've already found a container and it's more "inner" than this, then continue 
				if(innermostContainer && $.ui.contains(this.containers[i].element[0], innermostContainer.element[0]))
					continue;

				innermostContainer = this.containers[i]; 
				innermostIndex = i;
					
			} else {
				// container doesn't intersect. trigger "out" event if necessary 
				if(this.containers[i].containerCache.over) {
					this.containers[i]._trigger("out", event, this._uiHash(this));
					this.containers[i].containerCache.over = 0;
				}
			}

		}
		
		// if no intersecting containers found, return 
		if(!innermostContainer) return; 

		// move the item into the container if it's not there already
		if(this.containers.length === 1) {
			this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
			this.containers[innermostIndex].containerCache.over = 1;
		} else if(this.currentContainer != this.containers[innermostIndex]) { 

			//When entering a new container, we will find the item with the least distance and append our item near it 
			var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[innermostIndex].floating ? 'left' : 'top']; 
			for (var j = this.items.length - 1; j >= 0; j--) { 
				if(!$.ui.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) continue; 
				var cur = this.items[j][this.containers[innermostIndex].floating ? 'left' : 'top']; 
				if(Math.abs(cur - base) < dist) { 
					dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; 
				} 
			} 

			if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled 
				return; 

			this.currentContainer = this.containers[innermostIndex]; 
			itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); 
			this._trigger("change", event, this._uiHash()); 
			this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); 

			//Update the placeholder 
			this.options.placeholder.update(this.currentContainer, this.placeholder); 
		
			this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); 
			this.containers[innermostIndex].containerCache.over = 1;
		} 
	
		
	},

	_createHelper: function(event) {

		var o = this.options;
		var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem);

		if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already
			$(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);

		if(helper[0] == this.currentItem[0])
			this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };

		if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width());
		if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height());

		return helper;

	},

	_adjustOffsetFromHelper: function(obj) {
		if (typeof obj == 'string') {
			obj = obj.split(' ');
		}
		if ($.isArray(obj)) {
			obj = {left: +obj[0], top: +obj[1] || 0};
		}
		if ('left' in obj) {
			this.offset.click.left = obj.left + this.margins.left;
		}
		if ('right' in obj) {
			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
		}
		if ('top' in obj) {
			this.offset.click.top = obj.top + this.margins.top;
		}
		if ('bottom' in obj) {
			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
		}
	},

	_getParentOffset: function() {


		//Get the offsetParent and cache its position
		this.offsetParent = this.helper.offsetParent();
		var po = this.offsetParent.offset();

		// This is a special case where we need to modify a offset calculated on start, since the following happened:
		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
		if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
			po.left += this.scrollParent.scrollLeft();
			po.top += this.scrollParent.scrollTop();
		}

		if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
		|| (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
			po = { top: 0, left: 0 };

		return {
			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
		};

	},

	_getRelativeOffset: function() {

		if(this.cssPosition == "relative") {
			var p = this.currentItem.position();
			return {
				top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
				left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
			};
		} else {
			return { top: 0, left: 0 };
		}

	},

	_cacheMargins: function() {
		this.margins = {
			left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
			top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
		};
	},

	_cacheHelperProportions: function() {
		this.helperProportions = {
			width: this.helper.outerWidth(),
			height: this.helper.outerHeight()
		};
	},

	_setContainment: function() {

		var o = this.options;
		if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
		if(o.containment == 'document' || o.containment == 'window') this.containment = [
			0 - this.offset.relative.left - this.offset.parent.left,
			0 - this.offset.relative.top - this.offset.parent.top,
			$(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
			($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
		];

		if(!(/^(document|window|parent)$/).test(o.containment)) {
			var ce = $(o.containment)[0];
			var co = $(o.containment).offset();
			var over = ($(ce).css("overflow") != 'hidden');

			this.containment = [
				co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
				co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
				co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
				co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
			];
		}

	},

	_convertPositionTo: function(d, pos) {

		if(!pos) pos = this.position;
		var mod = d == "absolute" ? 1 : -1;
		var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);

		return {
			top: (
				pos.top																	// The absolute mouse position
				+ this.offset.relative.top * mod										// Only for relative positioned nodes: Relative offset from element to offset parent
				+ this.offset.parent.top * mod											// The offsetParent's offset without borders (offset + border)
				- ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
			),
			left: (
				pos.left																// The absolute mouse position
				+ this.offset.relative.left * mod										// Only for relative positioned nodes: Relative offset from element to offset parent
				+ this.offset.parent.left * mod											// The offsetParent's offset without borders (offset + border)
				- ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
			)
		};

	},

	_generatePosition: function(event) {

		var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);

		// This is another very weird special case that only happens for relative elements:
		// 1. If the css position is relative
		// 2. and the scroll parent is the document or similar to the offset parent
		// we have to refresh the relative offset during the scroll so there are no jumps
		if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) {
			this.offset.relative = this._getRelativeOffset();
		}

		var pageX = event.pageX;
		var pageY = event.pageY;

		/*
		 * - Position constraining -
		 * Constrain the position to a mix of grid, containment.
		 */

		if(this.originalPosition) { //If we are not dragging yet, we won't check for options

			if(this.containment) {
				if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
				if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
				if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
				if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
			}

			if(o.grid) {
				var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
				pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;

				var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
				pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
			}

		}

		return {
			top: (
				pageY																// The absolute mouse position
				- this.offset.click.top													// Click offset (relative to the element)
				- this.offset.relative.top												// Only for relative positioned nodes: Relative offset from element to offset parent
				- this.offset.parent.top												// The offsetParent's offset without borders (offset + border)
				+ ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
			),
			left: (
				pageX																// The absolute mouse position
				- this.offset.click.left												// Click offset (relative to the element)
				- this.offset.relative.left												// Only for relative positioned nodes: Relative offset from element to offset parent
				- this.offset.parent.left												// The offsetParent's offset without borders (offset + border)
				+ ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
			)
		};

	},

	_rearrange: function(event, i, a, hardRefresh) {

		a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction == 'down' ? i.item[0] : i.item[0].nextSibling));

		//Various things done here to improve the performance:
		// 1. we create a setTimeout, that calls refreshPositions
		// 2. on the instance, we have a counter variable, that get's higher after every append
		// 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
		// 4. this lets only the last addition to the timeout stack through
		this.counter = this.counter ? ++this.counter : 1;
		var self = this, counter = this.counter;

		window.setTimeout(function() {
			if(counter == self.counter) self.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
		},0);

	},

	_clear: function(event, noPropagation) {

		this.reverting = false;
		// We delay all events that have to be triggered to after the point where the placeholder has been removed and
		// everything else normalized again
		var delayedTriggers = [], self = this;

		// We first have to update the dom position of the actual currentItem
		// Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
		if(!this._noFinalSort && this.currentItem[0].parentNode) this.placeholder.before(this.currentItem);
		this._noFinalSort = null;

		if(this.helper[0] == this.currentItem[0]) {
			for(var i in this._storedCSS) {
				if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = '';
			}
			this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
		} else {
			this.currentItem.show();
		}

		if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
		if((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
		if(!$.ui.contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element
			if(!noPropagation) delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
			for (var i = this.containers.length - 1; i >= 0; i--){
				if($.ui.contains(this.containers[i].element[0], this.currentItem[0]) && !noPropagation) {
					delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
					delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this));  }; }).call(this, this.containers[i]));
				}
			};
		};

		//Post events to containers
		for (var i = this.containers.length - 1; i >= 0; i--){
			if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
			if(this.containers[i].containerCache.over) {
				delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
				this.containers[i].containerCache.over = 0;
			}
		}

		//Do what was originally in plugins
		if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor
		if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset opacity
		if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index

		this.dragging = false;
		if(this.cancelHelperRemoval) {
			if(!noPropagation) {
				this._trigger("beforeStop", event, this._uiHash());
				for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
				this._trigger("stop", event, this._uiHash());
			}
			return false;
		}

		if(!noPropagation) this._trigger("beforeStop", event, this._uiHash());

		//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
		this.placeholder[0].parentNode.removeChild(this.placeholder[0]);

		if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null;

		if(!noPropagation) {
			for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
			this._trigger("stop", event, this._uiHash());
		}

		this.fromOutside = false;
		return true;

	},

	_trigger: function() {
		if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
			this.cancel();
		}
	},

	_uiHash: function(inst) {
		var self = inst || this;
		return {
			helper: self.helper,
			placeholder: self.placeholder || $([]),
			position: self.position,
			originalPosition: self.originalPosition,
			offset: self.positionAbs,
			item: self.currentItem,
			sender: inst ? inst.element : null
		};
	}

});

$.extend($.ui.sortable, {
	version: "1.8.1"
});

})(jQuery);
/*

 SoundManager 2: Javascript Sound for the Web
 --------------------------------------------
 http://schillmania.com/projects/soundmanager2/

 Copyright (c) 2007, Scott Schiller. All rights reserved.
 Code provided under the BSD License:
 http://schillmania.com/projects/soundmanager2/license.txt

 V2.96a.20100520
*/
(function(i){function sa(L,T){function ca(){if(b.debugURLParam.test(i.location.href.toString()))b.debugMode=true;var c,a,f,h;if(b.debugMode){c=document.createElement("div");c.id=b.debugID+"-toggle";a={position:"fixed",bottom:"0px",right:"0px",width:"1.2em",height:"1.2em",lineHeight:"1.2em",margin:"2px",textAlign:"center",border:"1px solid #999",cursor:"pointer",background:"#fff",color:"#333",zIndex:10001};c.appendChild(document.createTextNode("-"));c.onclick=ta;c.title="Toggle SM2 debug console";
if(C.match(/msie 6/i)){c.style.position="absolute";c.style.cursor="hand"}for(h in a)if(a.hasOwnProperty(h))c.style[h]=a[h]}if(b.debugMode&&!s(b.debugID)&&(!da||!b.useConsole||b.useConsole&&da&&!b.consoleOnly)){a=document.createElement("div");a.id=b.debugID;a.style.display=b.debugMode?"block":"none";if(b.debugMode&&!s(c.id)){try{f=ea();f.appendChild(c)}catch(e){throw new Error(o("appXHTML"));}f.appendChild(a)}}f=null;ca=function(){}}this.flashVersion=8;this.debugMode=true;this.debugFlash=false;this.useConsole=
true;this.waitForWindowLoad=this.consoleOnly=false;this.nullURL="null.mp3";this.allowPolling=true;this.useMovieStar=this.useFastPolling=false;this.bgColor="#ffffff";this.useHighPerformance=false;this.flashLoadTimeout=1E3;this.wmode=null;this.allowFullScreen=true;this.allowScriptAccess="always";this.useHTML5Audio=this.useFlashBlock=false;this.html5Test=/^probably$/i;this.audioFormats={mp3:{type:['audio/mpeg; codecs="mp3"',"audio/mpeg","audio/mp3","audio/MPA","audio/mpa-robust"],required:true},mp4:{related:["aac",
"m4a"],type:['audio/mp4; codecs="mp4a.40.2"',"audio/aac","audio/x-m4a","audio/MP4A-LATM","audio/mpeg4-generic"],required:true},ogg:{type:["audio/ogg; codecs=vorbis"],required:false},wav:{type:['audio/wav; codecs="1"',"audio/wav","audio/wave","audio/x-wav"],required:false}};if(this.audioFormats.mp4.required)this.flashVersion=9;this.defaultOptions={autoLoad:false,stream:true,autoPlay:false,loops:1,onid3:null,onload:null,whileloading:null,onplay:null,onpause:null,onresume:null,whileplaying:null,onstop:null,
onfinish:null,onbeforefinish:null,onbeforefinishtime:5E3,onbeforefinishcomplete:null,onjustbeforefinish:null,onjustbeforefinishtime:200,multiShot:true,multiShotEvents:false,position:null,pan:0,volume:100};this.flash9Options={isMovieStar:null,usePeakData:false,useWaveformData:false,useEQData:false,onbufferchange:null,ondataerror:null};this.movieStarOptions={onmetadata:null,useVideo:false,bufferTime:3};this.version=null;this.versionNumber="V2.96a.20100520";this.movieURL=null;this.url=L||null;this.altURL=
null;this.enabled=this.swfLoaded=false;this.o=null;this.movieID="sm2-container";this.id=T||"sm2movie";this.swfCSS={swfDefault:"movieContainer",swfError:"swf_error",swfTimedout:"swf_timedout",swfUnblocked:"swf_unblocked",sm2Debug:"sm2_debug",highPerf:"high_performance",flashDebug:"flash_debug"};this.oMC=null;this.sounds={};this.soundIDs=[];this.isFullScreen=this.muted=false;this.isIE=navigator.userAgent.match(/MSIE/i);this.isSafari=navigator.userAgent.match(/safari/i);this.debugID="soundmanager-debug";
this.debugURLParam=/([#?&])debug=1/i;this.didFlashBlock=this.specialWmodeCase=false;this.filePattern=null;this.filePatterns={flash8:/\.mp3(\?\.*)?$/i,flash9:/\.mp3(\?\.*)?$/i};this.baseMimeTypes=/^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i;this.netStreamMimeTypes=/^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i;this.netStreamTypes=["aac","flv","mov","mp4","m4v","f4v","m4a","mp4v","3gp","3g2"];this.netStreamPattern=new RegExp("\\.("+this.netStreamTypes.join("|")+")(\\?.*)?$","i");this.mimePattern=this.baseMimeTypes;
this.features={buffering:false,peakData:false,waveformData:false,eqData:false,movieStar:false};this.sandbox={type:null,types:{remote:"remote (domain-based) rules",localWithFile:"local with file access (no internet access)",localWithNetwork:"local with network (internet access only, no local access)",localTrusted:"local, trusted (local+internet access)"},description:null,noRemote:null,noLocal:null};this.hasHTML5=null;this.html5={};this.ignoreFlash=false;var fa,b=this,s,C=navigator.userAgent,ua,U,E=
[],ga=true,u,F=false,M=false,r=false,y=false,ha=false,l,va,N,t,wa,G,H,V,ia,ja,D,xa,W,X,Y,ya,ea,Z,za,Ha=["log","info","warn","error"],Aa,O,Ba,P=null,ka=null,o,la,Q,ta,$,ma,q,aa=false,na=false,Ca,Da,I=true,Ea,oa,z,J,A,pa,Fa;L=C.match(/pre\//i);T=C.match(/(ipad|iphone)/i);var Ia=C.match(/mobile/i)||L||T,da=typeof console!=="undefined"&&typeof console.log!=="undefined",R=document.location?document.location.protocol.match(/http/i):null,qa=typeof document.hasFocus!=="undefined"?document.hasFocus():null,
K=typeof document.hasFocus==="undefined"&&this.isSafari,Ga=!K;this.useAltURL=!R;if(T||L){b.useHTML5Audio=true;b.ignoreFlash=true}if(L)b.html5Test=/^(probably|maybe)$/i;(function(){var c=i.location.href.toString(),a=null;if(c.indexOf("#sm2-usehtml5audio=")!==-1){a=c.substr(c.indexOf("#sm2-usehtml5audio=")+19)==="1";if(typeof console!=="undefined"&&typeof console.log!=="undefined")console.log((a?"Enabling ":"Disabling ")+"useHTML5Audio via URL parameter");b.useHTML5Audio=a}})();this.supported=function(){return I?
r&&!y:b.useHTML5Audio&&b.hasHTML5};this.getMovie=function(c){return b.isIE?i[c]:b.isSafari?s(c)||document[c]:s(c)};this.loadFromXML=function(c){try{b.o._loadFromXML(c)}catch(a){O();return true}};this.createSound=function(c){function a(){f=$(f);b.sounds[e.id]=new fa(e);b.soundIDs.push(e.id);return b.sounds[e.id]}var f=null,h=null,e=null;if(!r)throw ma("soundManager.createSound(): "+o("notReady"),arguments.callee.caller);if(arguments.length===2)c={id:arguments[0],url:arguments[1]};e=f=t(c);e.id.toString().charAt(0).match(/^[0-9]$/)&&
b._wD("soundManager.createSound(): "+o("badID",e.id),2);b._wD("soundManager.createSound(): "+e.id+" ("+e.url+")",1);if(q(e.id,true)){b._wD("soundManager.createSound(): "+e.id+" exists",1);return b.sounds[e.id]}if(J(e.url)){h=a();b._wD("Loading sound "+e.id+" from HTML5");h._setup_html5(e)}else{if(b.flashVersion>8&&b.useMovieStar){if(e.isMovieStar===null)e.isMovieStar=e.url.match(b.netStreamPattern)?true:false;e.isMovieStar&&b._wD("soundManager.createSound(): using MovieStar handling");if(e.isMovieStar){if(e.usePeakData){l("noPeak");
e.usePeakData=false}e.loops>1&&l("noNSLoop")}}h=a();b.flashVersion===8?b.o._createSound(e.id,e.onjustbeforefinishtime,e.loops||1):b.o._createSound(e.id,e.url,e.onjustbeforefinishtime,e.usePeakData,e.useWaveformData,e.useEQData,e.isMovieStar,e.isMovieStar?e.useVideo:false,e.isMovieStar?e.bufferTime:false,e.loops||1)}if(e.autoLoad||e.autoPlay)if(h)if(b.isHTML5){h.autobuffer="auto";h.preload="auto"}else h.load(e);e.autoPlay&&h.play();return h};this.createVideo=function(c){if(arguments.length===2)c={id:arguments[0],
url:arguments[1]};if(b.flashVersion>=9){c.isMovieStar=true;c.useVideo=true}else{b._wD("soundManager.createVideo(): "+o("f9Vid"),2);return false}b.useMovieStar||b._wD("soundManager.createVideo(): "+o("noMS"),2);return b.createSound(c)};this.destroyVideo=this.destroySound=function(c,a){if(!q(c))return false;for(var f=0;f<b.soundIDs.length;f++)b.soundIDs[f]===c&&b.soundIDs.splice(f,1);b.sounds[c].unload();a||b.sounds[c].destruct();delete b.sounds[c]};this.load=function(c,a){if(!q(c))return false;b.sounds[c].load(a)};
this.unload=function(c){if(!q(c))return false;b.sounds[c].unload()};this.start=this.play=function(c,a){if(!r)throw ma("soundManager.play(): "+o("notReady"),arguments.callee.caller);if(!q(c)){a instanceof Object||(a={url:a});if(a&&a.url){b._wD('soundManager.play(): attempting to create "'+c+'"',1);a.id=c;return b.createSound(a).play()}else return false}b.sounds[c].play(a)};this.setPosition=function(c,a){if(!q(c))return false;b.sounds[c].setPosition(a)};this.stop=function(c){if(!q(c))return false;b._wD("soundManager.stop("+
c+")",1);b.sounds[c].stop()};this.stopAll=function(){b._wD("soundManager.stopAll()",1);for(var c in b.sounds)b.sounds[c]instanceof fa&&b.sounds[c].stop()};this.pause=function(c){if(!q(c))return false;b.sounds[c].pause()};this.pauseAll=function(){for(var c=b.soundIDs.length;c--;)b.sounds[b.soundIDs[c]].pause()};this.resume=function(c){if(!q(c))return false;b.sounds[c].resume()};this.resumeAll=function(){for(var c=b.soundIDs.length;c--;)b.sounds[b.soundIDs[c]].resume()};this.togglePause=function(c){if(!q(c))return false;
b.sounds[c].togglePause()};this.setPan=function(c,a){if(!q(c))return false;b.sounds[c].setPan(a)};this.setVolume=function(c,a){if(!q(c))return false;b.sounds[c].setVolume(a)};this.mute=function(c){var a=0;if(typeof c!=="string")c=null;if(c){if(!q(c))return false;b._wD('soundManager.mute(): Muting "'+c+'"');b.sounds[c].mute()}else{b._wD("soundManager.mute(): Muting all sounds");for(a=b.soundIDs.length;a--;)b.sounds[b.soundIDs[a]].mute();b.muted=true}};this.muteAll=function(){b.mute()};this.unmute=
function(c){if(typeof c!=="string")c=null;if(c){if(!q(c))return false;b._wD('soundManager.unmute(): Unmuting "'+c+'"');b.sounds[c].unmute()}else{b._wD("soundManager.unmute(): Unmuting all sounds");for(c=b.soundIDs.length;c--;)b.sounds[b.soundIDs[c]].unmute();b.muted=false}};this.unmuteAll=function(){b.unmute()};this.toggleMute=function(c){if(!q(c))return false;b.sounds[c].toggleMute()};this.getMemoryUse=function(){if(b.flashVersion===8)return 0;if(b.o)return parseInt(b.o._getMemoryUse(),10)};this.disable=
function(c){if(typeof c==="undefined")c=false;if(y)return false;y=true;l("shutdown",1);for(var a=b.soundIDs.length;a--;)Aa(b.sounds[b.soundIDs[a]]);N(c);i.removeEventListener&&i.removeEventListener("load",H,false)};this.canPlayMIME=function(c){var a;if(b.hasHTML5)a=J({type:c});return!I||a?a:c?c.match(b.mimePattern)?true:false:null};this.canPlayURL=function(c){var a;if(b.hasHTML5)a=J(c);return!I||a?a:c?c.match(b.filePattern)?true:false:null};this.canPlayLink=function(c){if(typeof c.type!=="undefined"&&
c.type)if(b.canPlayMIME(c.type))return true;return b.canPlayURL(c.href)};this.getSoundById=function(c,a){if(!c)throw new Error("SoundManager.getSoundById(): sID is null/undefined");var f=b.sounds[c];!f&&!a&&b._wD('"'+c+'" is an invalid sound ID.',2);return f};this.onready=function(c,a){if(c&&c instanceof Function){r&&l("queue");a||(a=i);wa(c,a);G();return true}else throw o("needFunction");};this.oninitmovie=function(){};this.onload=function(){b._wD("soundManager.onload()",1)};this.onerror=function(){};
this.getMoviePercent=function(){return b.o&&typeof b.o.PercentLoaded!=="undefined"?b.o.PercentLoaded():null};this._wD=this._writeDebug=function(c,a,f){var h,e;if(!b.debugMode)return false;if(typeof f!=="undefined"&&f)c=c+" | "+(new Date).getTime();if(da&&b.useConsole){f=Ha[a];typeof console[f]!=="undefined"?console[f](c):console.log(c);if(b.useConsoleOnly)return true}try{h=s("soundmanager-debug");if(!h)return false;e=document.createElement("div");if(++va%2===0)e.className="sm2-alt";a=typeof a==="undefined"?
0:parseInt(a,10);e.appendChild(document.createTextNode(c));if(a){if(a>=2)e.style.fontWeight="bold";if(a===3)e.style.color="#ff3333"}h.insertBefore(e,h.firstChild)}catch(m){}};this._debug=function(){l("currentObj",1);for(var c=0,a=b.soundIDs.length;c<a;c++)b.sounds[b.soundIDs[c]]._debug()};this.reboot=function(){b._wD("soundManager.reboot()");b.soundIDs.length&&b._wD("Destroying "+b.soundIDs.length+" SMSound objects...");for(var c=b.soundIDs.length;c--;)b.sounds[b.soundIDs[c]].destruct();try{if(b.isIE)ka=
b.o.innerHTML;P=b.o.parentNode.removeChild(b.o);b._wD("Flash movie removed.")}catch(a){l("badRemove",2)}P=ka=null;y=M=F=na=aa=r=b.enabled=false;b.swfLoaded=false;b.soundIDs={};b.sounds=[];b.o=null;for(c=E.length;c--;)E[c].fired=false;b._wD("soundManager: Rebooting...");i.setTimeout(function(){b.beginDelayedInit()},20)};this.destruct=function(){b._wD("soundManager.destruct()");b.disable(true)};this.beginDelayedInit=function(){ha=true;setTimeout(ia,500);setTimeout(xa,20)};J=function(c){if(!b.useHTML5Audio||
!b.hasHTML5)return false;var a,f=b.audioFormats;if(!A){A=[];for(a in f)if(f.hasOwnProperty(a)){A.push(a);if(f[a].related)A=A.concat(f[a].related)}A=new RegExp("\\.("+A.join("|")+")","i")}a=typeof c.type!=="undefined"?c.type:null;c=typeof c==="string"?c.match(A):null;if(!c||!c.length){if(!a)return false}else c=c[0].substr(1);if(c&&typeof b.html5[c]!=="undefined")return b.html5[c];else{if(!a)if(c&&b.html5[c])return b.html5[c];else a="audio/"+c;a=b.html5.canPlayType(a);return b.html5[c]=a}};Fa=function(){function c(j){var v,
S,B=false;if(!f||typeof f.canPlayType!=="function")return false;if(j instanceof Array){v=0;for(S=j.length;v<S&&!B;v++)if(b.html5[j[v]]||f.canPlayType(j[v]).match(b.html5Test)){B=true;b.html5[j[v]]=true}return B}else return(j=f&&typeof f.canPlayType==="function"?f.canPlayType(j):false)&&(j.match(b.html5Test)?true:false)}function a(j,v){function S(){if(m>=e&&!oa){oa=true;ya&&V()}}function B(w){if(!ra){ra=true;m++;n[j]=w;v(w);S()}}var x,ra=false;e++;if(Ia){m++;v();S();return false}if(typeof n[j]!=="undefined"){ra=
true;v(n[j])}else{x=new Audio(h[j]);x.addEventListener("canplay",function(w){B(true,w);x=null},false);x.addEventListener("canplaythrough",function(w){B(true,w);x=null},false);x.addEventListener("error",function(w){B(false,this.error?this.error:w);x=null},false);x.addEventListener("stalled",function(w){B(false,w);x=null},false);x.load()}}if(!b.useHTML5Audio||typeof Audio==="undefined")return false;var f=typeof Audio!=="undefined"?new Audio:null,h={mp3:"data:audio/mpeg;base64,/+MYxAALOAHgCAAAAD////////////v6OGAfB8HwfAgIAgCAYB8HwfB8CAgCAIAgD4Pg+D4OAgCAIP9Xt6vb1CV0qLA0DQND/+MYxA4FcAHcAAAAAISgqCtvV7eqTEFNRTMuOTguNKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+MYxDMAAANIAAAAAKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
wav:"data:audio/wave;base64,UklGRiYAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQIAAAD//w=="},e=0,m=0,n={},d,g={},k,p;k=b.audioFormats;for(d in k)if(k.hasOwnProperty(d)){g[d]=c(k[d].type);if(k[d]&&k[d].related)for(p=0;p<k[d].related.length;p++)b.html5[k[d].related[p]]=g[d]}g.canPlayType=f?c:null;b.html5=t(b.html5,g);b.html5.mp3||a("mp3",function(j){if(j)b.html5.mp3=j});b.html5.wav||a("wav",function(j){if(j)b.html5.wav=j})};W={notReady:"Not loaded yet - wait for soundManager.onload() before calling sound-related methods",
appXHTML:"soundManager::createMovie(): appendChild/innerHTML set failed. May be app/xhtml+xml DOM-related.",spcWmode:"soundManager::createMovie(): Removing wmode, preventing win32 below-the-fold SWF loading issue",swf404:"soundManager: Verify that %s is a valid path.",tryDebug:"Try soundManager.debugFlash = true for more security details (output goes to SWF.)",checkSWF:"See SWF output for more debug info.",localFail:"soundManager: Non-HTTP page ("+document.location.protocol+" URL?) Review Flash player security settings for this special case:\nhttp://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html\nMay need to add/allow path, eg. c:/sm2/ or /users/me/sm2/",
waitFocus:"soundManager: Special case: Waiting for focus-related event..",waitImpatient:"soundManager: Getting impatient, still waiting for Flash%s...",waitForever:"soundManager: Waiting indefinitely for Flash (will recover if unblocked)...",needFunction:"soundManager.onready(): Function object expected",badID:'Warning: Sound ID "%s" should be a string, starting with a non-numeric character',fl9Vid:"flash 9 required for video. Exiting.",noMS:"MovieStar mode not enabled. Exiting.",currentObj:"--- soundManager._debug(): Current sound objects ---",
waitEI:"soundManager::initMovie(): Waiting for ExternalInterface call from Flash..",waitOnload:"soundManager: Waiting for window.onload()",docLoaded:"soundManager: Document already loaded",onload:"soundManager::initComplete(): calling soundManager.onload()",onloadOK:"soundManager.onload() complete",init:"-- soundManager::init() --",didInit:"soundManager::init(): Already called?",flashJS:"soundManager: Attempting to call Flash from JS..",noPolling:"soundManager: Polling (whileloading()/whileplaying() support) is disabled.",
secNote:"Flash security note: Network/internet URLs will not load due to security restrictions. Access can be configured via Flash Player Global Security Settings Page: http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html",badRemove:"Warning: Failed to remove flash movie.",noPeak:"Warning: peakData features unsupported for movieStar formats",shutdown:"soundManager.disable(): Shutting down",queue:"soundManager.onready(): Queueing handler",smFail:"soundManager: Failed to initialise.",
smError:"SMSound.load(): Exception: JS-Flash communication failed, or JS error.",fbTimeout:"No flash response, applying ."+b.swfCSS.swfTimedout+" CSS..",fbLoaded:"Flash loaded",manURL:"SMSound.load(): Using manually-assigned URL",onURL:"soundManager.load(): current URL already assigned.",badFV:'soundManager.flashVersion must be 8 or 9. "%s" is invalid. Reverting to %s.',as2loop:"Note: Setting stream:false so looping can work (flash 8 limitation)",noNSLoop:"Note: Looping not implemented for MovieStar formats"};
s=function(c){return document.getElementById(c)};va=0;o=function(){var c=Array.prototype.slice.call(arguments),a=c.shift();a=W&&W[a]?W[a]:"";var f,h;if(a&&c&&c.length){f=0;for(h=c.length;f<h;f++)a=a.replace("%s",c[f])}return a};$=function(c){if(b.flashVersion===8&&c.loops>1&&c.stream){l("as2loop");c.stream=false}return c};ma=function(c,a){if(!a)return new Error("Error: "+c);typeof console!=="undefined"&&typeof console.trace!=="undefined"&&console.trace();c="Error: "+c+". \nCaller: "+a.toString();
return new Error(c)};ua=function(){return false};Aa=function(c){for(var a in c)if(c.hasOwnProperty(a)&&typeof c[a]==="function")c[a]=ua};O=function(c){if(typeof c==="undefined")c=false;if(y||c){l("smFail",2);b.disable(c)}};Ba=function(c){var a=null;if(c)if(c.match(/\.swf(\?\.*)?$/i)){if(a=c.substr(c.toLowerCase().lastIndexOf(".swf?")+4))return c}else if(c.lastIndexOf("/")!==c.length-1)c+="/";return(c&&c.lastIndexOf("/")!==-1?c.substr(0,c.lastIndexOf("/")+1):"./")+b.movieURL};ja=function(){if(b.flashVersion!==
8&&b.flashVersion!==9){alert(o("badFV",b.flashVersion,8));b.flashVersion=8}var c=b.debugMode||b.debugFlash?"_debug.swf":".swf";b.version=b.versionNumber+(z?" (HTML5-only mode)":b.flashVersion===9?" (AS3/Flash 9)":" (AS2/Flash 8)");if(b.flashVersion>8){b.defaultOptions=t(b.defaultOptions,b.flash9Options);b.features.buffering=true}if(b.flashVersion>8&&b.useMovieStar){b.defaultOptions=t(b.defaultOptions,b.movieStarOptions);b.filePatterns.flash9=new RegExp("\\.(mp3|"+b.netStreamTypes.join("|")+")(\\?.*)?$",
"i");b.mimePattern=b.netStreamMimeTypes;b.features.movieStar=true}else b.features.movieStar=false;b.filePattern=b.filePatterns[b.flashVersion!==8?"flash9":"flash8"];b.movieURL=(b.flashVersion===8?"soundmanager2.swf":"soundmanager2_flash9.swf").replace(".swf",c);b.features.peakData=b.features.waveformData=b.features.eqData=b.flashVersion>8};ea=function(){return document.body?document.body:document.documentElement?document.documentElement:document.getElementsByTagName("div")[0]};za=function(c,a){if(!b.o||
!b.allowPolling)return false;b.o._setPolling(c,a)};Z=function(c,a){function f(){b._wD("-- SoundManager 2 "+b.version+(!z&&b.useHTML5Audio?b.hasHTML5?" + HTML5 audio":", no HTML5 audio support":"")+(b.useMovieStar?", MovieStar mode":"")+(b.useHighPerformance?", high performance mode, ":", ")+((b.useFastPolling?"fast":"normal")+" polling")+(b.wmode?", wmode: "+b.wmode:"")+(b.debugFlash?", flash debug mode":"")+(b.useFlashBlock?", flashBlock mode":"")+" --",1)}var h=null;a=a?a:b.url;var e=b.altURL?b.altURL:
a,m,n,d,g;c=typeof c==="undefined"?b.id:c;if(F&&M)return false;if(z){ja();f();b.oMC=s(b.movieID);U();M=F=true;return false}F=true;ja();b.url=Ba(R?a:e);a=b.url;if(b.useHighPerformance&&b.useMovieStar&&b.defaultOptions.useVideo===true){h="soundManager note: disabling highPerformance, not applicable with movieStar mode+useVideo";b.useHighPerformance=false}b.wmode=!b.wmode&&b.useHighPerformance&&!b.useMovieStar?"transparent":b.wmode;if(b.wmode!==null&&!b.isIE&&!b.useHighPerformance&&navigator.platform.match(/win32/i)){b.specialWmodeCase=
true;l("spcWmode");b.wmode=null}if(b.flashVersion===8)b.allowFullScreen=false;m={name:c,id:c,src:a,width:"100%",height:"100%",quality:"high",allowScriptAccess:b.allowScriptAccess,bgcolor:b.bgColor,pluginspage:"http://www.macromedia.com/go/getflashplayer",type:"application/x-shockwave-flash",wmode:b.wmode,allowfullscreen:b.allowFullScreen?"true":"false"};if(b.debugFlash)m.FlashVars="debug=1";b.wmode||delete m.wmode;if(b.isIE){e=document.createElement("div");d='<object id="'+c+'" data="'+a+'" type="'+
m.type+'" width="'+m.width+'" height="'+m.height+'"><param name="movie" value="'+a+'" /><param name="AllowScriptAccess" value="'+b.allowScriptAccess+'" /><param name="quality" value="'+m.quality+'" />'+(b.wmode?'<param name="wmode" value="'+b.wmode+'" /> ':"")+'<param name="bgcolor" value="'+b.bgColor+'" /><param name="allowFullScreen" value="'+m.allowFullScreen+'" />'+(b.debugFlash?'<param name="FlashVars" value="'+m.FlashVars+'" />':"")+"<!-- --\></object>"}else{e=document.createElement("embed");
for(n in m)m.hasOwnProperty(n)&&e.setAttribute(n,m[n])}ca();n=Q();if(c=ea()){b.oMC=s(b.movieID)?s(b.movieID):document.createElement("div");if(b.oMC.id){c=b.oMC.className;b.oMC.className=(c?c+" ":b.swfCSS.swfDefault)+(n?" "+n:"");b.oMC.appendChild(e);if(b.isIE){n=b.oMC.appendChild(document.createElement("div"));n.className="sm2-object-box";n.innerHTML=d}M=true}else{b.oMC.id=b.movieID;b.oMC.className=b.swfCSS.swfDefault+" "+n;n=m=null;b.useFlashBlock||(m=b.useHighPerformance?{position:"fixed",width:"8px",
height:"8px",bottom:"0px",left:"0px",overflow:"hidden"}:{position:"absolute",width:"6px",height:"6px",top:"-9999px",left:"-9999px"});g=null;if(!b.debugFlash)for(g in m)if(m.hasOwnProperty(g))b.oMC.style[g]=m[g];try{b.isIE||b.oMC.appendChild(e);c.appendChild(b.oMC);if(b.isIE){n=b.oMC.appendChild(document.createElement("div"));n.className="sm2-object-box";n.innerHTML=d}M=true}catch(k){throw new Error(o("appXHTML"));}}}h&&b._wD(h);f();b._wD("soundManager::createMovie(): Trying to load "+a+(!R&&b.altURL?
" (alternate URL)":""),1)};q=this.getSoundById;l=function(c,a){return c?b._wD(o(c),a):""};if(i.location.href.indexOf("debug=alert")+1&&b.debugMode)b._wD=function(c){alert(c)};ta=function(){var c=s(b.debugID),a=s(b.debugID+"-toggle");if(!c)return false;if(ga){a.innerHTML="+";c.style.display="none"}else{a.innerHTML="-";c.style.display="block"}ga=!ga};u=function(c,a,f){if(typeof sm2Debugger!=="undefined")try{sm2Debugger.handleEvent(c,a,f)}catch(h){}};t=function(c,a){var f={},h,e;for(h in c)if(c.hasOwnProperty(h))f[h]=
c[h];c=typeof a==="undefined"?b.defaultOptions:a;for(e in c)if(c.hasOwnProperty(e)&&typeof f[e]==="undefined")f[e]=c[e];return f};X=function(){if(z){Z();return false}if(b.o)return false;b.o=b.getMovie(b.id);if(!b.o){if(P){if(b.isIE)b.oMC.innerHTML=ka;else b.oMC.appendChild(P);P=null;F=true}else Z(b.id,b.url);b.o=b.getMovie(b.id)}if(b.o){b._wD("soundManager::initMovie(): Got "+b.o.nodeName+" element ("+(F?"created via JS":"static HTML")+")");l("waitEI")}typeof b.oninitmovie==="function"&&setTimeout(b.oninitmovie,
1)};V=function(c){if(c)b.url=c;X()};ia=function(){if(aa)return false;aa=true;if(K&&!qa){l("waitFocus");return false}var c;if(!r){c=b.getMoviePercent();b._wD(o("waitImpatient",c===100?" (SWF loaded)":c>0?" (SWF "+c+"% loaded)":""))}setTimeout(function(){c=b.getMoviePercent();if(!r){b._wD("soundManager: No Flash response within expected time.\nLikely causes: "+(c===0?"Loading "+b.movieURL+" may have failed (and/or Flash "+b.flashVersion+"+ not present?), ":"")+"Flash blocked or JS-Flash security error."+
(b.debugFlash?" "+o("checkSWF"):""),2);if(!R&&c){l("localFail",2);b.debugFlash||l("tryDebug",2)}c===0&&b._wD(o("swf404",b.url));u("flashtojs",false,": Timed out"+R?" (Check flash security or flash blockers)":" (No plugin/missing SWF?)")}if(!r&&Ga)if(c===null)if(b.useFlashBlock||b.flashLoadTimeout===0){b.useFlashBlock&&la();l("waitForever")}else O(true);else b.flashLoadTimeout===0?l("waitForever"):O(true)},b.flashLoadTimeout)};Q=function(){var c=[];b.debugMode&&c.push(b.swfCSS.sm2Debug);b.debugFlash&&
c.push(b.swfCSS.flashDebug);b.useHighPerformance&&c.push(b.swfCSS.highPerf);return c.join(" ")};la=function(){var c=b.getMoviePercent();if(b.supported()){b.didFlashBlock&&b._wD("soundManager::flashBlockHandler(): Unblocked");if(b.oMC)b.oMC.className=Q()+" "+b.swfCSS.swfDefault+(" "+b.swfCSS.swfUnblocked)}else{if(I){b.oMC.className=Q()+" "+b.swfCSS.swfDefault+" "+(c===null?b.swfCSS.swfTimedout:b.swfCSS.swfError);b._wD("soundManager::flashBlockHandler(): "+o("fbTimeout")+(c?" ("+o("fbLoaded")+")":""))}G(true);
b.onerror instanceof Function&&b.onerror.apply(i);b.didFlashBlock=true}};D=function(){if(qa||!K)return true;qa=Ga=true;b._wD("soundManager::handleFocus()");K&&i.removeEventListener("mousemove",D,false);aa=false;setTimeout(ia,500);if(i.removeEventListener)i.removeEventListener("focus",D,false);else i.detachEvent&&i.detachEvent("onfocus",D)};N=function(c){if(r)return false;if(z){b._wD("-- SoundManager 2: loaded --");r=true;G();H();return true}b.useFlashBlock&&b.flashLoadTimeout&&!b.getMoviePercent()||
(r=true);b._wD("-- SoundManager 2 "+(y?"failed to load":"loaded")+" ("+(y?"security/load error":"OK")+") --",1);if(y||c){if(b.useFlashBlock)b.oMC.className=Q()+" "+(b.getMoviePercent()===null?b.swfCSS.swfTimedout:b.swfCSS.swfError);G();u("onload",false);b.onerror instanceof Function&&b.onerror.apply(i);return false}else u("onload",true);if(b.waitForWindowLoad&&!ha){l("waitOnload");if(i.addEventListener)i.addEventListener("load",H,false);else i.attachEvent&&i.attachEvent("onload",H);return false}else{b.waitForWindowLoad&&
ha&&l("docLoaded");H()}};wa=function(c,a){E.push({method:c,scope:a||null,fired:false})};G=function(c){if(!r&&!c)return false;c={success:c?b.supported():!y};var a=[],f,h,e=!b.useFlashBlock||b.useFlashBlock&&!b.supported();f=0;for(h=E.length;f<h;f++)E[f].fired!==true&&a.push(E[f]);if(a.length){b._wD("soundManager: Firing "+a.length+" onready() item"+(a.length>1?"s":""));f=0;for(h=a.length;f<h;f++){a[f].scope?a[f].method.apply(a[f].scope,[c]):a[f].method(c);if(!e)a[f].fired=true}}};H=function(){i.setTimeout(function(){b.useFlashBlock&&
la();G();l("onload",1);b.onload.apply(i);l("onloadOK",1)},1)};Ea=function(){var c,a,f=b.isSafari&&C.match(/OS X 10_6_3/i)&&C.match(/531\.22\.7/i);if(C.match(/iphone os (1|2|3_0|3_1)/i)?true:false){b.hasHTML5=false;z=true;if(b.oMC)b.oMC.style.display="none";return false}if(b.useHTML5Audio){if(!b.html5||!b.html5.canPlayType){b._wD("SoundManager: No HTML5 Audio() support detected.");b.hasHTML5=false;return true}else b.hasHTML5=true;if(f){b._wD("Note: Buggy HTML5 in this version of Safari, see https://bugs.webkit.org/show_bug.cgi?id=32159 - disabling HTML5",
1);b.useHTML5Audio=false;b.hasHTML5=false;return true}}else return true;for(a in b.audioFormats)if(b.audioFormats.hasOwnProperty(a))if(b.audioFormats[a].required&&!b.html5.canPlayType(b.audioFormats[a].type))c=true;if(b.ignoreFlash)c=false;z=b.useHTML5Audio&&b.hasHTML5&&!c;return c};U=function(){function c(){if(i.removeEventListener)i.removeEventListener("load",b.beginDelayedInit,false);else i.detachEvent&&i.detachEvent("onload",b.beginDelayedInit)}var a,f=[];l("init");if(r){l("didInit");return false}if(b.hasHTML5){for(a in b.audioFormats)b.audioFormats.hasOwnProperty(a)&&
f.push(a+": "+b.html5[a]);b._wD("-- SoundManager 2: HTML5 support tests ("+b.html5Test+"): "+f.join(", ")+" --",1)}if(z){if(!r){c();b.enabled=true;N()}return true}X();try{l("flashJS");b.o._externalInterfaceTest(false);b.allowPolling?za(true,b.useFastPolling?true:false):l("noPolling",1);b.debugMode||b.o._disableDebug();b.enabled=true;u("jstoflash",true)}catch(h){b._wD("js/flash exception: "+h.toString());u("jstoflash",false);O(true);N();return false}N();c()};xa=function(){if(na)return false;Z();X();
return na=true};Y=function(){ca();Fa();I=Ea();ya=true;if(b.useHTML5Audio&&b.hasHTML5)oa&&V();else V()};Ca=function(c){if(!c._hasTimer)c._hasTimer=true};Da=function(c){if(c._hasTimer)c._hasTimer=false};this._setSandboxType=function(c){var a=b.sandbox;a.type=c;a.description=a.types[typeof a.types[c]!=="undefined"?c:"unknown"];b._wD("Flash security sandbox type: "+a.type);if(a.type==="localWithFile"){a.noRemote=true;a.noLocal=false;l("secNote",2)}else if(a.type==="localWithNetwork"){a.noRemote=false;
a.noLocal=true}else if(a.type==="localTrusted"){a.noRemote=false;a.noLocal=false}};this._externalInterfaceOK=function(c){if(b.swfLoaded)return false;var a=(new Date).getTime();b._wD("soundManager::externalInterfaceOK()"+(c?" (~"+(a-c)+" ms)":""));u("swf",true);u("flashtojs",true);b.swfLoaded=true;K=false;b.isIE?setTimeout(U,100):U()};this._onfullscreenchange=function(c){b._wD("onfullscreenchange(): "+c);b.isFullScreen=c===1?true:false;if(!b.isFullScreen)try{i.focus();b._wD("window.focus()")}catch(a){}};
fa=function(c){var a=this,f,h,e,m,n;this.sID=c.id;this.url=c.url;this._iO=this.instanceOptions=this.options=t(c);this.pan=this.options.pan;this.volume=this.options.volume;this._lastURL=null;this.isHTML5=false;this.id3={};this._debug=function(){if(b.debugMode){var d=null,g=[],k,p;for(d in a.options)if(a.options[d]!==null)if(a.options[d]instanceof Function){k=a.options[d].toString();k=k.replace(/\s\s+/g," ");p=k.indexOf("{");g.push(" "+d+": {"+k.substr(p+1,Math.min(Math.max(k.indexOf("\n")-1,64),64)).replace(/\n/g,
"")+"... }")}else g.push(" "+d+": "+a.options[d]);b._wD("SMSound() merged options: {\n"+g.join(", \n")+"\n}")}};this._debug();this.load=function(d){if(typeof d!=="undefined"){a._iO=t(d);a.instanceOptions=a._iO}else{d=a.options;a._iO=d;a.instanceOptions=a._iO;if(a._lastURL&&a._lastURL!==a.url){l("manURL");a._iO.url=a.url;a.url=null}}if(typeof a._iO.url==="undefined")a._iO.url=a.url;b._wD("soundManager.load(): "+a._iO.url,1);if(a._iO.url===a.url&&a.readyState!==0&&a.readyState!==2){l("onURL",1);return false}a.url=
a._iO.url;a._lastURL=a._iO.url;a.loaded=false;a.readyState=1;a.playState=0;if(J(a._iO.url)){b._wD("HTML 5 load: "+a._iO.url);a._setup_html5(a._iO);a._iO.autoPlay&&a.play()}else try{a.isHTML5=false;a._iO=$(a._iO);if(b.flashVersion===8)b.o._load(a.sID,a._iO.url,a._iO.stream,a._iO.autoPlay,a._iO.whileloading?1:0,a._iO.loops||1);else{b.o._load(a.sID,a._iO.url,a._iO.stream?true:false,a._iO.autoPlay?true:false,a._iO.loops||1);a._iO.isMovieStar&&a._iO.autoLoad&&!a._iO.autoPlay&&a.pause()}}catch(g){l("smError",
2);u("onload",false);b.onerror();b.disable()}};this.unload=function(){if(a.readyState!==0){b._wD('SMSound.unload(): "'+a.sID+'"');a.readyState!==2&&a.setPosition(0,true);if(a.isHTML5){e();if(a.__element){a.__element.pause();a.__element.src="about:blank";a.__element.load();a.__element=null}}else b.o._unload(a.sID,b.nullURL);f()}};this.destruct=function(){b._wD('SMSound.destruct(): "'+a.sID+'"');if(a.isHTML5){e();if(a.__element){a.__element.pause();a.__element.src="about:blank";a.__element.load();a.__element=
null}}else b.o._destroySound(a.sID);b.destroySound(a.sID,true)};this.start=this.play=function(d){d||(d={});a._iO=t(d,a._iO);a._iO=t(a._iO,a.options);a.instanceOptions=a._iO;if(J(a._iO.url)){a._setup_html5(a._iO);m()}if(a.playState===1)if(d=a._iO.multiShot){b._wD('SMSound.play(): "'+a.sID+'" already playing (multi-shot)',1);a.isHTML5&&a.setPosition(a._iO.position)}else{b._wD('SMSound.play(): "'+a.sID+'" already playing (one-shot)',1);return false}if(a.loaded)b._wD('SMSound.play(): "'+a.sID+'"');else if(a.readyState===
0){b._wD('SMSound.play(): Attempting to load "'+a.sID+'"',1);if(a.isHTML5)a.readyState=1;else{a._iO.autoPlay=true;a.load(a._iO)}}else if(a.readyState===2){b._wD('SMSound.play(): Could not load "'+a.sID+'" - exiting',2);return false}else b._wD('SMSound.play(): "'+a.sID+'" is loading - attempting to play..',1);if(a.paused){b._wD('SMSound.play(): "'+a.sID+'" is resuming from paused state',1);a.resume()}else{b._wD('SMSound.play(): "'+a.sID+'" is starting to play');a.playState=1;if(!a.instanceCount||b.flashVersion>
8&&!a.isHTML5)a.instanceCount++;a.position=typeof a._iO.position!=="undefined"&&!isNaN(a._iO.position)?a._iO.position:0;a._iO=$(a._iO);a._iO.onplay&&a._iO.onplay.apply(a);a.setVolume(a._iO.volume,true);a.setPan(a._iO.pan,true);if(a.isHTML5){m();a._setup_html5().play()}else b.o._start(a.sID,a._iO.loops||1,b.flashVersion===9?a.position:a.position/1E3)}};this.stop=function(d){if(a.playState===1){a._onbufferchange(0);if(!a.isHTML5)a.playState=0;a.paused=false;a._iO.onstop&&a._iO.onstop.apply(a);if(a.isHTML5){if(a.__element){a.setPosition(0);
a.__element.pause();a.playState=0;a._onTimer();e();a.unload();a.__element=null}}else b.o._stop(a.sID,d);a.instanceCount=0;a._iO={}}};this.setPosition=function(d){if(typeof d==="undefined")d=0;d=a.isHTML5?Math.max(d,0):Math.min(a.duration,Math.max(d,0));a._iO.position=d;if(a.isHTML5){if(a.__element){b._wD("setPosition(): setting position to "+a._iO.position/1E3);if(a.playState)try{a.__element.currentTime=a._iO.position/1E3}catch(g){b._wD("setPosition("+a._iO.position+"): WARN: Caught exception: "+
g.message,2)}else b._wD("HTML 5 warning: cannot set position while playState == 0 (not playing)",2);a.paused&&a._onTimer(true)}}else b.o._setPosition(a.sID,b.flashVersion===9?a._iO.position:a._iO.position/1E3,a.paused||!a.playState)};this.pause=function(){if(a.paused||a.playState===0)return false;b._wD("SMSound.pause()");a.paused=true;if(a.isHTML5){a._setup_html5().pause();e()}else b.o._pause(a.sID);a._iO.onpause&&a._iO.onpause.apply(a)};this.resume=function(){if(!a.paused||a.playState===0)return false;
b._wD("SMSound.resume()");a.paused=false;if(a.isHTML5){a._setup_html5().play();m()}else b.o._pause(a.sID);a._iO.onresume&&a._iO.onresume.apply(a)};this.togglePause=function(){b._wD("SMSound.togglePause()");if(a.playState===0){a.play({position:b.flashVersion===9&&!a.isHTML5?a.position:a.position/1E3});return false}a.paused?a.resume():a.pause()};this.setPan=function(d,g){if(typeof d==="undefined")d=0;if(typeof g==="undefined")g=false;a.isHTML5||b.o._setPan(a.sID,d);a._iO.pan=d;if(!g)a.pan=d};this.setVolume=
function(d,g){if(typeof d==="undefined")d=100;if(typeof g==="undefined")g=false;if(a.isHTML5){if(a.__element)a.__element.volume=d/100}else b.o._setVolume(a.sID,b.muted&&!a.muted||a.muted?0:d);a._iO.volume=d;if(!g)a.volume=d};this.mute=function(){a.muted=true;if(a.isHTML5){if(a.__element)a.__element.muted=true}else b.o._setVolume(a.sID,0)};this.unmute=function(){a.muted=false;var d=typeof a._iO.volume!=="undefined";if(a.isHTML5){if(a.__element)a.__element.muted=false}else b.o._setVolume(a.sID,d?a._iO.volume:
a.options.volume)};this.toggleMute=function(){a.muted?a.unmute():a.mute()};this._onTimer=function(d){if(a._hasTimer||d){var g;if(a.__element&&(d||(a.playState>0||a.readyState===1)&&!a.paused)){g=a.__element;a.duration=n();a.durationEstimate=a.duration;d=g.currentTime?g.currentTime*1E3:0;a._whileplaying(d,{},{},{},{});return true}else{b._wD('_onTimer: Warn for "'+a.sID+'": '+(!g?"Could not find element. ":"")+(a.playState===0?"playState bad, 0?":"playState = "+a.playState+", OK"));return false}}};
n=function(){var d=a.__element?a.__element.duration*1E3:undefined;if(d)return!isNaN(d)?d:null};m=function(){a.isHTML5&&Ca(a)};e=function(){a.isHTML5&&Da(a)};f=function(){a._hasTimer=null;a._added_events=null;a.__element=null;a.bytesLoaded=null;a.bytesTotal=null;a.position=null;a.duration=null;a.durationEstimate=null;a.loaded=false;a.playState=0;a.paused=false;a.readyState=0;a.muted=false;a.didBeforeFinish=false;a.didJustBeforeFinish=false;a.isBuffering=false;a.instanceOptions={};a.instanceCount=0;
a.peakData={left:0,right:0};a.waveformData={left:[],right:[]};a.eqData=[];a.eqData.left=[];a.eqData.right=[]};f();this._setup_html5=function(d){d=t(a._iO,d);if(a.__element){if(a.url!==d.url){b._wD("setting new URL on existing object: "+d.url);a.__element.src=d.url}}else{b._wD("creating HTML 5 audio element with URL: "+d.url);a.__element=new Audio(d.url);a.isHTML5=true;h()}a.__element.loop=d.loops>1?"loop":"";return a.__element};h=function(){function d(g,k,p){return a.__element?a.__element.addEventListener(g,
k,p||false):null}if(a._added_events)return false;a._added_events=true;d("load",function(){var g=a.__element;b._wD("HTML5::load: "+a.sID);if(g){a._onbufferchange(0);a._whileloading(a.bytesTotal,a.bytesTotal,n());a._onload(1)}},false);d("canplay",function(){b._wD("HTML5::canplay: "+a.sID);a._onbufferchange(0)},false);d("waiting",function(){b._wD("HTML5::waiting: "+a.sID);a._onbufferchange(1)},false);d("progress",function(g){var k=a.__element;b._wD("HTML5::progress: "+a.sID+": loaded/total: "+(g.loaded||
0)+","+(g.total||1));if(!a.loaded&&k){a._onbufferchange(0);a._whileloading(g.loaded||0,g.total||1,n())}},false);d("end",function(){b._wD("HTML5::end: "+a.sID);a._onfinish()},false);d("error",function(){if(a.__element){b._wD("HTML5::error: "+a.__element.error.code);a._onload(0)}},false);d("loadstart",function(){b._wD("HTML5::loadstart: "+a.sID);a._onbufferchange(1)},false);d("play",function(){b._wD("HTML5::play: "+a.sID);a._onbufferchange(0)},false);d("playing",function(){b._wD("HTML5::playing: "+
a.sID);a._onbufferchange(0)},false);a.__element.addEventListener("timeupdate",function(){a._onTimer()},false);setTimeout(function(){a&&a.__element&&d("ended",function(){b._wD("HTML5::ended: "+a.sID);a._onfinish()},false)},250)};this._whileloading=function(d,g,k){if(a._iO.isMovieStar){a.bytesLoaded=d;a.bytesTotal=g;a.duration=Math.floor(k);a.durationEstimate=a.duration}else{a.bytesLoaded=d;a.bytesTotal=g;a.duration=Math.floor(k);a.durationEstimate=parseInt(a.bytesTotal/a.bytesLoaded*a.duration,10);
if(a.durationEstimate===undefined)a.durationEstimate=a.duration}a.readyState!==3&&a._iO.whileloading&&a._iO.whileloading.apply(a)};this._onid3=function(d,g){b._wD('SMSound._onid3(): "'+this.sID+'" ID3 data received.');var k=[],p,j;p=0;for(j=d.length;p<j;p++)k[d[p]]=g[p];a.id3=t(a.id3,k);a._iO.onid3&&a._iO.onid3.apply(a)};this._whileplaying=function(d,g,k,p,j){if(isNaN(d)||d===null)return false;if(a.playState===0&&d>0)d=0;a.position=d;if(b.flashVersion>8&&!a.isHTML5){if(a._iO.usePeakData&&typeof g!==
"undefined"&&g)a.peakData={left:g.leftPeak,right:g.rightPeak};if(a._iO.useWaveformData&&typeof k!=="undefined"&&k)a.waveformData={left:k.split(","),right:p.split(",")};if(a._iO.useEQData)if(typeof j!=="undefined"&&j&&j.leftEQ){d=j.leftEQ.split(",");a.eqData=d;a.eqData.left=d;if(typeof j.rightEQ!=="undefined"&&j.rightEQ)a.eqData.right=j.rightEQ.split(",")}}if(a.playState===1){!a.isHTML5&&a.isBuffering&&a._onbufferchange(0);a._iO.whileplaying&&a._iO.whileplaying.apply(a);if(a.loaded&&a._iO.onbeforefinish&&
a._iO.onbeforefinishtime&&!a.didBeforeFinish&&a.duration-a.position<=a._iO.onbeforefinishtime){b._wD("duration-position &lt;= onbeforefinishtime: "+a.duration+" - "+a.position+" &lt= "+a._iO.onbeforefinishtime+" ("+(a.duration-a.position)+")");a._onbeforefinish()}}};this._onload=function(d){d=d===1?true:false;b._wD('SMSound._onload(): "'+a.sID+'"'+(d?" loaded.":" failed to load? - "+a.url),d?1:2);if(!d&&!a.isHTML5){b.sandbox.noRemote===true&&b._wD("SMSound._onload(): "+o("noNet"),1);b.sandbox.noLocal===
true&&b._wD("SMSound._onload(): "+o("noLocal"),1)}a.loaded=d;a.readyState=d?3:2;a._iO.onload&&a._iO.onload.apply(a)};this._onbeforefinish=function(){if(!a.didBeforeFinish){a.didBeforeFinish=true;if(a._iO.onbeforefinish){b._wD('SMSound._onbeforefinish(): "'+a.sID+'"');a._iO.onbeforefinish.apply(a)}}};this._onjustbeforefinish=function(){if(!a.didJustBeforeFinish){a.didJustBeforeFinish=true;if(a._iO.onjustbeforefinish){b._wD('SMSound._onjustbeforefinish(): "'+a.sID+'"');a._iO.onjustbeforefinish.apply(a)}}};
this._onfinish=function(){a._onbufferchange(0);a._iO.onbeforefinishcomplete&&a._iO.onbeforefinishcomplete.apply(a);a.didBeforeFinish=false;a.didJustBeforeFinish=false;if(a.instanceCount){a.instanceCount--;if(!a.instanceCount){a.playState=0;a.paused=false;a.instanceCount=0;a.instanceOptions={};e()}if(!a.instanceCount||a._iO.multiShotEvents)if(a._iO.onfinish){b._wD('SMSound._onfinish(): "'+a.sID+'"');a._iO.onfinish.apply(a)}if(a.isHTML5){a.unload();a.__element=null}}};this._onmetadata=function(d){b._wD("SMSound.onmetadata()");
if(!d.width&&!d.height){l("noWH");d.width=320;d.height=240}a.metadata=d;a.width=d.width;a.height=d.height;if(a._iO.onmetadata){b._wD('SMSound.onmetadata(): "'+a.sID+'"');a._iO.onmetadata.apply(a)}b._wD("SMSound.onmetadata() complete")};this._onbufferchange=function(d){if(a.playState===0)return false;if(d&&a.isBuffering||!d&&!a.isBuffering)return false;a.isBuffering=d===1?true:false;if(a._iO.onbufferchange){b._wD("SMSound._onbufferchange(): "+d);a._iO.onbufferchange.apply(a)}};this._ondataerror=function(d){if(a.playState>
0){b._wD("SMSound._ondataerror(): "+d);a._iO.ondataerror&&a._iO.ondataerror.apply(a)}}};if(!b.hasHTML5||I)if(i.addEventListener){i.addEventListener("focus",D,false);i.addEventListener("load",b.beginDelayedInit,false);i.addEventListener("unload",b.destruct,false);K&&i.addEventListener("mousemove",D,false)}else if(i.attachEvent){i.attachEvent("onfocus",D);i.attachEvent("onload",b.beginDelayedInit);i.attachEvent("unload",b.destruct)}else{u("onload",false);ba.onerror();ba.disable()}pa=function(){if(document.readyState===
"complete"){Y();document.detachEvent("onreadystatechange",pa)}};if(document.addEventListener)document.addEventListener("DOMContentLoaded",Y,false);else document.attachEvent&&document.attachEvent("onreadystatechange",pa);document.readyState==="complete"&&setTimeout(Y,100)}var ba=null;if(typeof SM2_DEFER==="undefined"||!SM2_DEFER)ba=new sa;i.SoundManager=sa;i.soundManager=ba})(window);
// HTML5 placeholder plugin version 0.3
// Copyright (c) 2010-The End of Time, Mike Taylor, http://miketaylr.com
// MIT Licensed: http://www.opensource.org/licenses/mit-license.php

(function(b){b.fn.placeholder=function(g){var c=b.extend(b.fn.placeholder.defaults,g),h=c.placeholderCSS.left,e=function(){return b.browser.opera};return"placeholder"in document.createElement("input")?this:this.each(function(){var a=b(this),i=b.trim(a.val()),j=a.width(),k=a.height(),f=a.attr("id")!==""?a.attr("id"):+new Date,d=a.attr("placeholder");d=b("<label for="+f+">"+d+"</label>");c.placeholderCSS.width=j;c.placeholderCSS.height=k;c.placeholderCSS.left=e()&&this.getAttribute("type")==="email"||
e()&&this.getAttribute("type")==="url"?"11%":h;d.css(c.placeholderCSS);if(!i){a.wrap(c.inputWrapper);a.attr("id",f).after(d)}a.focus(function(){b.trim(a.val())||a.next().hide()});a.blur(function(){b.trim(a.val())||a.next().show()})})};b.fn.placeholder.defaults={inputWrapper:'<span style="position:relative"></span>',placeholderCSS:{font:"0.75em sans-serif",color:"#bababa",position:"absolute",left:"5px",top:"20%","overflow-x":"hidden"}}})(jQuery);/****
Misc functions for AG
*****/
if( typeof AG === 'undefined' ) {
    AG = {};
}

// helper function
$.fn.reverseCollection = [].reverse;

/*
This work is licensed under Creative Commons GNU GPL License
http://creativecommons.org/licenses/GPL/2.0/
and based on code by Russel Lindsay www.weetbixthecat.com
Copyright Kostik Naumov www.piterpen.net
(c) 20070108
*/

Array.prototype._sortPrep = function ( field ) {
    var i;
    if (!this.maximum) {
        this.maximum = this[0][field];
        i = this.length;
        while(i--) {
            if (this.maximum < this[i][field]) {this.maximum = this[i][field];}
        }
    }
    if (this.maximum) {
        var fill = [];
        var length = this.maximum.toString().length;
        i = length; var zs = "";
        while (i--) {
            fill.push(zs); zs += "0";
        }
        for ( i=0; i < this.length; i++ ) {
            this[i][field] = (this[i][field].toString().length < length ? fill[length - this[i][field].toString().length] : "") + this[i][field];
        }
    }
};


Array.prototype.sortAsc = function(field) {
    var saveO = Object.prototype.toString;
    var saveA = Array.prototype.toString;
    Object.prototype.toString = function(){ return this[field]; };
    Array.prototype.toString = function(){ return this[field]; };
    this.sort();
    Array.prototype.toString = saveA;
    Object.prototype.toString = saveO;
};

Array.prototype.sortDesc = function(field) {
    var saveO = Object.prototype.toString;
    var saveA = Array.prototype.toString;
    Object.prototype.toString = function(){ return this[field]; };
    Array.prototype.toString = function(){ return this[field]; };
    this.sort();
    this.reverse();
    Array.prototype.toString = saveA;
    Object.prototype.toString = saveO;
};

/////

AG.Constants = function(){
    
    return {
        CTRL_KEY: 17,
        ALT_KEY: 18,
        SHIFT_KEY: 16,
        META_KEY: 91,
        SignedInUserFront: '/music?',
        VolumeCookieName: "playerVolume",
        ENCTYPE_MP3: "1",
        ENCTYPE_AAC: "2"
    };

}();

AG.Misc = function(){
    var specialKeys = { CTRL_KEY: false, SHIFT_KEY: false, ALT_KEY: false, META_KEY: false };

    $(document).keydown(function(e){
        if( e.keyCode === AG.Constants.CTRL_KEY ) { specialKeys.CTRL_KEY = true; }
        if( e.keyCode === AG.Constants.SHIFT_KEY ){ specialKeys.SHIFT_KEY = true; }
        if( e.keyCode === AG.Constants.ALT_KEY )  { specialKeys.ALT_KEY = true; }
        if( e.keyCode === AG.Constants.META_KEY ) { specialKeys.META_KEY = true; }
    }).keyup(function(e){
        if( e.keyCode === AG.Constants.CTRL_KEY ) { specialKeys.CTRL_KEY = false; }
        if( e.keyCode === AG.Constants.SHIFT_KEY ){ specialKeys.SHIFT_KEY = false; }
        if( e.keyCode === AG.Constants.ALT_KEY )  { specialKeys.ALT_KEY = false; }
        if( e.keyCode === AG.Constants.META_KEY ) { specialKeys.META_KEY = false; }
    });
   
    function styleCloseLinks() {
        $(".popup_close_link").click( function() {
            $(this).parents( ".popup" ).remove();
        });
    }

    return {
        

        trapSpecialKeysForSelector: function(selector) {
            $(selector).unbind('keydown').unbind('keyup')
            .keydown(function(e){
                if( e.keyCode === AG.Constants.CTRL_KEY ) { specialKeys.CTRL_KEY = true; }
                if( e.keyCode === AG.Constants.SHIFT_KEY ){ specialKeys.SHIFT_KEY = true; }
                if( e.keyCode === AG.Constants.ALT_KEY )  { specialKeys.ALT_KEY = true; }
                if( e.keyCode === AG.Constants.META_KEY ) { specialKeys.META_KEY = true; }
            }).keyup(function(e){
                if( e.keyCode === AG.Constants.CTRL_KEY ) { specialKeys.CTRL_KEY = false; }
                if( e.keyCode === AG.Constants.SHIFT_KEY ){ specialKeys.SHIFT_KEY = false; }
                if( e.keyCode === AG.Constants.ALT_KEY )  { specialKeys.ALT_KEY = false; }
                if( e.keyCode === AG.Constants.META_KEY ) { specialKeys.META_KEY = false; }
            });
        },

        isKeyPressed: function(key){
            switch(key) {
                case AG.Constants.CTRL_KEY: return specialKeys.CTRL_KEY;
                case AG.Constants.SHIFT_KEY: return specialKeys.SHIFT_KEY;
                case AG.Constants.ALT_KEY: return specialKeys.ALT_KEY;
                case AG.Constants.META_KEY: return specialKeys.META_KEY;
            }
        },

        isSpecialKeyPressed: function() {
            return ( specialKeys.CTRL_KEY || specialKeys.SHIFT_KEY || specialKeys.ALT_KEY || specialKeys.META_KEY );      
        },
    
        sendToSignedInPage: function(){ 
            window.location.href = "/";
            window.location.hash = AG.Constants.SignedInUserFront;
        },

        extractID: function (el) {
            var str = $(el).attr('id');
            if( str === undefined || str === null ) { return 0; }
            var toks = str.split(/_/); 
            if( toks.length < 2 ) {
                return 0;
            }

            return toks[1];
        },
        
        extractID2: function (el) {
            var str = $(el).attr('id');
            if( str === undefined || str === null ) { return 0; }
            var toks = str.split(/_/); 
            if( toks.length < 3 ) {
                return 0;
            }

            return toks[2];
        },

        getTimeStringFromSec: function(secs, appendQualifier) {
            var days = Math.floor( secs / 86400 );
            secs = secs - ( days * 86400 );
            var hours = Math.floor( secs / 3600 );
            secs = secs - ( hours * 3600 );
            var minutes = Math.floor( secs / 60 );
            var seconds = Math.round( secs % 60 );

            var str = '';
            if( days > 0 ) { 
                if( minutes < 10 ) { minutes = '0' + minutes; }
                str += days + 'd ' + hours + 'h ' + minutes;
            }
            else if( hours > 0 ) {
                if( minutes < 10 ) { minutes = '0' + minutes; }
                str += hours + 'h ' + minutes;
            }
            else {
                if( seconds < 10 ) { seconds = '0' + seconds; }
                str += minutes + ':' + seconds;
            }
            
            if( appendQualifier === true ) {
                str += " minutes";
            }

            return str;
        },
        
        getTimeStringFromMSec: function(ms, appendQualifier) {
            var totalSeconds = Math.round( ms / 1000 );
            return AG.Misc.getTimeStringFromSec( totalSeconds, appendQualifier );
        },
        
        closeSaveWindow: function(){
            $("#save_pls_pane").remove();
        },

        startSavePlaylist: function( el, commentID, hasiTunes, songIDs, cfIDs, updatePlaylistID, playTimeSecs, onSavePlaylistLoaded ) {

            AG.Misc.closeSaveWindow();
            var rootPos = $(el).offset();

            var html = '<div id="save_pls_pane" style="display: none;" class="popup">Loading...</div>';
            $("body").append( html );

            var elWidth = $("#save_pls_pane").outerWidth();

            $("#save_pls_pane").css( {
                position: 'absolute',
                display: 'block',
                zIndex: 100,
                left: (rootPos.left - elWidth  ) + "px",
                top: (rootPos.top) + "px"
            } );

            var savePlaylistUrl = userUrlBase + "savePlaylist?";

            $("#save_pls_pane").load( savePlaylistUrl, { 
                commentID: commentID, 
                hasiTunes: (hasiTunes === true || hasiTunes === 1) ? "true" : "false", 
                updatePlaylist: updatePlaylistID, 
                songIDs: songIDs.join(","),
                cfIDs: cfIDs.join(","),
                playTimeSecs: playTimeSecs }, 
                function() {
                    styleCloseLinks();
                    fixupNewHtml();
                    if( onSavePlaylistLoaded !== undefined ) {
                        onSavePlaylistLoaded();
                    }
                }
            );
        },

        finishSavePlaylist: function( songIDs, cfIDs, commentID, playTimeSecs, updatePlaylistID ) {
            if( $("#playlistName").val() === "" ) {
                $("#titleRequired").effect("highlight", { color: 'red' }, 1500);
                return false; 
            }

            var tags = $("#playlistTags").val();
            if( tags.split(';').length > 5 ) {
                $("#saveTagsLabel").effect("highlight", { color: 'red' }, 1500);
                return false; 
            }

            $("#savePlaylistError").empty();
            var title = $("#playlistName").val();
            $("#playlistName").attr("disabled", "true");
            
            AG.Misc.sendSavePlaylist( songIDs, cfIDs, title, commentID, updatePlaylistID, tags, playTimeSecs, true, function( data ) {
                if( data !== undefined && data.success ) {
                    if( !data.savedToClient ) {
                        $("#savePlaylistError")
                            .text("Your playlist was saved to the server but not created on your computer because Audiogalaxy Helper was not running")
                            .effect( "highlight", { color: '#ffffff' }, 1500, function() { closeSaveWindow(); } );
                    }
                    else {
                        AG.Misc.closeSaveWindow();
                    }
                }
                else if ( data.errMessage !== undefined ) {
                    $("#savePlaylistError").text(data.errMessage).effect("highlight", { color: '#ffffff' }, 1500 );
                }

                $("#playlistName").removeAttr("disabled");
            } ); 
        },

        sendSavePlaylist: function( songIDs, cfIDs, title, commentID, updatePlaylistID, tags, playTimeSecs, reloadOnSave, onPlaylistSaved ) {

            var savePlaylistUrl = userUrlBase + "savePlaylist?";
         
            $.post( encodeURI(savePlaylistUrl), { 
                songIDs: songIDs, 
                cfIDs: cfIDs,
                title: title,
                updatePlaylist: updatePlaylistID,
                commentID: commentID,
                tags: tags,
                playTimeSecs: playTimeSecs
            }, function(data) {
                if( data !== undefined && data.success ) {
                    
                    if( reloadOnSave ) { 
                        // refresh the playlist pane if it exists
                        if( $("#playlistListing").is(":visible") ) {
                            AG.Collection.refreshM3uPlaylists( data );
                        }
                        else {
                            location.hash = "/playlists?";
                        }
                    } 
                }
                onPlaylistSaved( data );
            }, "json" );
        },

        setCookie: function ( name, value, expiredays) {
            if( expiredays !== null ) {
                var exdate = new Date();
                exdate.setDate(exdate.getDate() + expiredays);
            }

            document.cookie = name + "=" + escape(value) + ( (expiredays=== null) ? "" : ";expires=" + exdate.toGMTString());
        },

        getCookie: function (name) {
            if (document.cookie.length > 0 ) {
               var start = document.cookie.indexOf( name + "=" );
               if ( start !== -1) {
                   start = start + name.length + 1;
                   var end = document.cookie.indexOf(";", start);
                   if ( end === -1) {
                       end = document.cookie.length;
                   }
                   
                   return unescape( document.cookie.substring( start, end) );
               }
            }
            return "";
        },

        debugLog: function(message){
            if( typeof console !== 'undefined' && console !== undefined ){
                console.log(message);
            }
        }
    };
}();




AG.DragDrop = function(){

    // get the object that's used under the cursor when dragging items. We create a div on the fly and use that
    function getDraggableItemsContainer(selected, forceGroup, ddContext){
   
        // IE workaround - if the container is already created and visible, return it
        if( $("#draggingContainer:visible").length ){
            return $("#draggingContainer");
        }

        var container = $('<table/>').attr('id', 'draggingContainer').removeClass().empty();
        container.addClass(ddContext.groupClass);

        if( selected.hasClass( ddContext.parentClass ) ) {
            container.append( selected.clone() );
        }
        else {
           container.append(selected.parents('.' + ddContext.parentClass).clone().reverseCollection());
        }

        container.css('width', selected.eq(0).outerWidth() );
        if( selected.length > 10 || forceGroup ) {
            $("tr",container).hide();
            container.append('<tr class="groupedDragging ' + ddContext.parentClass + '"><td class="middle playbuttonSmall"></td><td class="middle infoText" style="text-align: left;">Add ' + selected.length + ' ' + ddContext.groupDescriptor + '</td></tr>');
        }

        if( selected.length > 1 ) {
            AG.Track.record(AG.Track.Cat.DragDrop,AG.Track.Act.DDMultiselect);
        }

        return container; 
    }

    function helperDragSongs(){
        
        var selected = $('.ui-selected:visible', songDragDrop.selectable);
        if (selected.length === 0) {
            selected = $(this);
        }

        return getDraggableItemsContainer(selected, false, songDragDrop );
    }

    function helperDragArtists(){
        
        var selected = $('.ui-selected:visible', artistDragDrop.selectable);
        if (selected.length === 0) {
            selected = $(this);
        }

        return getDraggableItemsContainer(selected, false, artistDragDrop );
    }
   
    function helperDragPlaylists(){
        
        var selected = $('.ui-selected:visible', playlistDragDrop.selectable);
        if (selected.length === 0) {
            selected = $(this);
        }

        return getDraggableItemsContainer(selected, false, playlistDragDrop );
    }
    
    // this function is invoked when a selectable item is selected. It makes the item draggable
    function makeSelected( event, ui, ddContext ){

        $(ui.selected).draggable({

            appendTo: '#draggableParent',
            helper: function(){
                $( ddContext.selectable ).selectable('disable');  // IE doesn't like this enabled while dragging
                return ddContext.draggingHelper();
            },
            stop: function(){
                $( ddContext.selectable ).selectable('enable');    // Fix for IE 
            },
            drag: function(event, ui){
                AG.PlayerControl.checkForDraggingOverPlayer( ui.offset ); 
            },
            cursor: 'pointer',
            cursorAt: { top: 0, left: 0 },
            zIndex: 2000        
        });

        // we do this so that we can get click events for selected rows. By default, draggable prevents click events from
        // flowing to selectable so we have to implement this manually
        $( ddContext.draggable + ".ui-draggable").live( 'click.selectedRow', function() { 
            handleSelectedClicked(this, ddContext ); 
            return false; 
        });
    }

    // unselect item. it's not draggable anymore
    function makeUnselected( event, ui, ddContext ){
        $(ui.unselected).draggable('destroy');
    }

    // this implements what the selectable plugin does. We use it when we hijack clicks
    function toggleSelection(jqElement, ddContext ){
        if( jqElement.hasClass('ui-selected') ){
            jqElement.removeClass('ui-selected');
            if( jqElement.hasClass('ui-draggable') ){
                jqElement.draggable('destroy');
            }
        }
        else {
            jqElement.addClass('ui-selected');
            jqElement.die('click.selectedRow');
            makeSelected( null, {selected: jqElement}, ddContext );
        }
    }

    // selects all rows from the previously selected row
    function doShiftSelectForRow( thisRowID, ddContext ){
        var previousExists = false;
        var previousToSelect = [];
        var nextExists = false;
        var nextToSelect = [];
        
        var allItems = $( ddContext.draggable, ddContext.selectable );
        allItems.each( function(){
            
            if( AG.Misc.extractID( this) === thisRowID ) {
                return false;
            }

            if( !previousExists && $(this).is(".ui-unselecting, .ui-selected") ){
                previousExists = true;
            }    
        
            if( previousExists ) {
                previousToSelect.push( this );
            }
        } );

        if( !previousExists ) {
            allItems.reverseCollection();
            allItems.each(function() {

                if( AG.Misc.extractID( this) === thisRowID ) {
                    return false;
                }
                
                if( !nextExists && $(this).is(".ui-unselecting, .ui-selected") ) {
                    nextExists = true;
                }

                if( nextExists ) {
                    nextToSelect.push( this );
                }
            });
        }

        // if there was a previous selected, unselect everything and select all rows from the previous to the current
        if( previousExists || nextExists ){
            $( ddContext.draggable + ".ui-selected").each( function(){ 
                toggleSelection( $(this), ddContext );
            } );
        
            $(previousToSelect).each( function() {
                toggleSelection( $(this), ddContext );
            });
            
            $(nextToSelect).each( function() {
                toggleSelection( $(this), ddContext );
            });
        }
        else {
            
            $( ddContext.draggable, ddContext.selectable ).each( function (){
                if( AG.Misc.extractID( this ) === thisRowID ){
                    return false;
                }

                toggleSelection( $(this), ddContext );
            });
        }
    }

    // handle the case where a row is clicked when the shift key is pressed
    function handleShiftClicked(el, ddContext ){
        if( el === undefined ){
            el = this;
        }

        doShiftSelectForRow( AG.Misc.extractID( el ), ddContext );

        toggleSelection( $(ddContext.draggable, this), ddContext );
        return false;
    }

    function checkForShiftClicked( selecting, ddContext ){
        var shiftPressed = AG.Misc.isKeyPressed(AG.Constants.SHIFT_KEY);
        if( shiftPressed ){
            $(selecting).each( function() {
                handleShiftClicked( this, ddContext );
            });

            return true;
        }

        return false;
    }

    // invoked when a selected row is clicked. We hijack this from the selectable plugin so we can do interesting things like handling
    // shift+click, which the plugin doesn't handle
    function handleSelectedClicked( el, ddContext ){
        
        var ctrlPressed = AG.Misc.isKeyPressed(AG.Constants.CTRL_KEY);
        var shiftPressed = AG.Misc.isKeyPressed(AG.Constants.SHIFT_KEY);
        if( ctrlPressed || ( $(el).hasClass('ui-selected') && $(".ui-selected").length === 1 ) ) {
            toggleSelection( $(el), ddContext );
        }
        else if( shiftPressed ){
            handleShiftClicked(el, ddContext );
            toggleSelection( $(el), ddContext );
        }
        else {
            $( ddContext.draggable + ".ui-selected" ).removeClass("ui-selected");
            $( ddContext.draggable + ".ui-draggable").draggable('destroy');
            $(el).die('click.selectedRow');
            $(el).addClass('ui-selected');
            makeSelected( null, {selected: $(el) }, ddContext );
        }
    }


    function configureDragAndDrop( selectorContext, ddContext ){

        // this makes just the song name draggable. Pick just this song
        $(ddContext.draggable, selectorContext).draggable({

            appendTo: '#draggableParent',
            helper: function(){
                $(this).addClass('ui-selected');
                return getDraggableItemsContainer( $(this), false, ddContext );
            },
            stop: function(){
                $(this).removeClass('ui-selected');
                //$(ddContext.selectable).selectable('enable');    // Fix for IE 
            },
            drag: function(event, ui){
                AG.PlayerControl.checkForDraggingOverPlayer( ui.offset ); 
            },
            cursor: 'pointer',
            cursorAt: { top: 0, left: 0 },
            zIndex: 2000
        });
    }

    function configureMultiSelectDragAndDrop( ddContext, onSingleRowSelected ){
        
        AG.Misc.trapSpecialKeysForSelector( ddContext.selectable );

        // for the lasso-effect in the songs pane. When selected, the individual rows are drag-enabled
        $(ddContext.selectable).selectable( {

            cancel: ddContext.disableSelection,
            filter: ddContext.draggable,
            selecting: function(event, ui){
                checkForShiftClicked( ui.selecting, ddContext );
            },
            selected: function(event, ui) {
                makeSelected( event, ui, ddContext);
                
                if( onSingleRowSelected !== undefined && $( ddContext.draggable + '.ui-draggable').length === 1 ){
                    // this is the first/only selected row.
                    onSingleRowSelected( ui.selected ); 
                }
                
            },
            unselected: function(event, ui) {
                makeUnselected( event, ui, ddContext );
            }
        });
        
        if( $.browser.msie ) {
            // IE doesn't invoke the draggable behavior from selected rows. So this is an IE special case to deal with this
            // shortcoming
            $(ddContext.selectable).selectable('option', 'delay', 10);
            $(ddContext.draggable).unbind('click.iehack');
            $(ddContext.draggable).bind('click.iehack', function() {
                handleSelectedClicked( $(this), ddContext ); 
                return false; 
            });
        }
        
        // make the song name work as if it's selectable. It's not a jQuery UI selectable because we need to be 
        // able to drag it, so it's on the 'cancel' option for the default .songsSelectable div. We handle toggling it
        // ourself. If clicked and dragged, this function isn't invoked
        $(ddContext.titleDraggable).unbind('click.dragsong');
        $(ddContext.titleDraggable).bind('click.dragsong', function() { 
            
            handleSelectedClicked( $(this).parent(), ddContext ); 
            
            if( onSingleRowSelected !== undefined && $( ddContext.draggable + '.ui-draggable' ).length <= 1 ){
                // select this row instead - someone just clicked
                onSingleRowSelected( $(this).parent() ); 
            }
            
            return false; 
        } );
        
        // this makes just the song name draggable. Pick just this song, or anything else that's selected
        $(ddContext.titleDraggable).draggable({

            appendTo: '#draggableParent',
            helper: function(){
                $(ddContext.selectable).selectable('disable');  // IE doesn't like this enabled while dragging

                // if not selected select this row and unselect all others
                // we want to drag only the single row
                if( !$(this).parents(ddContext.draggable).hasClass('ui-selected') ){
                    $('.ui-selected').each( function(){
                        toggleSelection($(this), ddContext ); 
                    });

                    $(this).parents(ddContext.draggable).addClass('ui-selected');
                }

                // pick all selected rows
                var selected = $('.ui-selected:visible');
                if (selected.length === 0) {
                    selected = $(this);
                }
            
                return getDraggableItemsContainer( selected, false, ddContext );
            },
            stop: function(){
                $(ddContext.selectable).selectable('enable');    // Fix for IE 
            },
            drag: function(event, ui){
                AG.PlayerControl.checkForDraggingOverPlayer( ui.offset ); 
            },
            cursor: 'pointer',
            cursorAt: { top: 0, left: 0 },
            zIndex: 2000,
            opacity: 0.6
        });
    }

    function configureCollectionDragDrop() {
        // special cases
        $(collectionDragDrop.draggable).addClass('no-select');
        
        // pull in the entire right pane from the header (name of artist/playlist)
        $(collectionDragDrop.draggable).draggable({

            appendTo: '#draggableParent',
            helper: function(){
                $(songDragDrop.selectable).selectable('disable');  // IE doesn't like this enabled while dragging

                // unselect all selected items
                $('.ui-selected').each( function(){
                    toggleSelection($(this), songDragDrop ); 
                });

                // pick all song rows
                var selected = $(songDragDrop.draggable);
                return getDraggableItemsContainer( selected, true, songDragDrop );
            },
            stop: function(){
                $(songDragDrop.selectable).selectable('enable');    // Fix for IE 
            },
            drag: function(event, ui){
                AG.PlayerControl.checkForDraggingOverPlayer( ui.offset ); 
            },
            zIndex: 2000,
            cursor: 'pointer',
            cursorAt: { top: 0, left: 0 },
            opacity: 0.6
        });
    }

    var songDragDrop = {
        parentClass: 'songRow',
        parentSelector: '.songRow',
        draggable: '.songRowDraggable',
        selectable: '.songsSelectable',
        draggingHelper: helperDragSongs,
        groupClass: 'hasSongs',
        groupDescriptor: 'songs',
        titleDraggable: '.songTitleDraggable',
        disableSelection: '.songButton,.plsPlay,.songTitleDraggable,.collectionDraggable,.plsExport,.albumName, .songHoverOption'
    };

    var artistDragDrop = {
        parentClass: 'artRow',
        parentSelector: '.artRow',
        draggable: '.artistRowDraggable',
        selectable: '.artistsSelectable',
        draggingHelper: helperDragArtists,
        groupClass: 'hasArtists', 
        groupDescriptor: 'artists',
        titleDraggable: '.artistTitleDraggable',
        disableSelection: '.artistTitleDraggable'
    };

    var playlistDragDrop = {
        parentClass: 'plsRow',
        parentSelector: '.plsRow',
        draggable: '.plsRowDraggable',
        selectable: '.playlistsSelectable',
        draggingHelper: helperDragPlaylists,
        groupClass: 'hasPlaylists', 
        groupDescriptor: 'playlists',
        titleDraggable: '.plsTitleDraggable',
        disableSelection: '.plsTitleDraggable,.plsModifier,.plsDeletable'
    };

    var collectionDragDrop = {
        draggable: '.collectionDraggable'
    };

    return {
   
        refreshSongsPane: function() {
            $(".songRow").hover( 
                function() {
                    $(".songHoverOption img", this).show();
                },
                function() {
                    $(".songHoverOption img", this).hide();
                });
            
            if( $.browser.msie && $.browser.version === "7.0" ) { return; }

            configureMultiSelectDragAndDrop( songDragDrop );
            configureCollectionDragDrop(); 
        },

        refreshPlaylistSongsPane: function() {
            $(".plsSortable").addClass('no-select');
            $(".plsSortable").sortable({
                handle: '.handle',
                helper: 'original',
                items: '.songRow',
                axis: 'y',
                opacity: 0.8,
                placeholder: 'sortablePlaceholder',
                forcePlaceholderSize: true,
                forceHelperSize: true,
                containment: "#detailsColumn",
                start: function(event, ui){
                    $(ui.item).addClass('issorting');
                    $(".songHoverOption", ui.item).remove();
                    $(ui.placeholder).append('<td colspan="2" style="height: 20px;"></td>');
                },
                stop: function(event, ui){
                    $(ui.item).removeClass('issorting').effect('highlight', {}, 1500);
                    AG.Track.record(AG.Track.Cat.DragDrop,AG.Track.Act.PlsSort);
                },
                update: function(event, ui){
                    AG.Collection.saveCurrentPlaylistToCloud();
                }

            });
            
            configureMultiSelectDragAndDrop( songDragDrop );
            configureCollectionDragDrop(); 
            $(".songRow").hover( 
                function() {
                    $(".songHoverOption img", this).show();
                },
                function() {
                    $(".songHoverOption img", this).hide();
                });
        },

        refreshArtistsPane: function( selectorContext ){
            if( !$.browser.msie ) {
                configureDragAndDrop( selectorContext, artistDragDrop );
            }
        },

        destroyDragAndDrop: function(selector) {
            $( selector + " .ui-draggable").draggable('destroy');
        },
       
        refreshPlaylistsPane: function( selectorContext ){
            if( !$.browser.msie ) {
                configureDragAndDrop( selectorContext, playlistDragDrop );
            }
        },

        // get the selector that the player should use to attract droppable stuff
        getDropTargets: function(){
            var draggableElements = [];
            draggableElements.push(songDragDrop.draggable);
            draggableElements.push(songDragDrop.titleDraggable);
            draggableElements.push(collectionDragDrop.draggable);
            draggableElements.push(artistDragDrop.draggable);
            draggableElements.push(artistDragDrop.titleDraggable);
            draggableElements.push(playlistDragDrop.draggable);
            draggableElements.push(playlistDragDrop.titleDraggable);

            return draggableElements.join(',');
        }
    };
}();

/****
Functions related to google analytics
*****/
if( typeof AG === 'undefined' ) {
    AG = {};
}

AG.Track = function() {

    return {
        Cat: { 
            "Signup":"Signup", "Signin":"Signin", "Install":"Install", "Player":"Player", "DragDrop":"DragDrop"
        },

        Act: { 
            "Click":"Click", "Cancel":"Cancel", "Submit":"Submit", "Success":"Success", "Error":"Error", "Mobile":"Mobile",
            "Show":"Show", "Unsupported":"Unsupported", "Force":"Force", "Download":"Download", "CancelWaitInstall":"CancelWaitInstall", "WaitInstall":"WaitInstall", "WaitSongs":"WaitSongs", "InstallTimeout":"InstallTimeout", "WaitTimeout":"WaitTimeout", "SongsFound":"SongsFound", "NoSongs":"NoSongs", "Help":"Help",
            "Play":"Play", "Pause":"Pause", "Shuffle":"Shuffle", "Repeat":"Repeat", "Seek":"Seek", "Next":"Next", "Prev":"Prev", "SavePls":"SavePls", "Undo":"Undo", "Redo":"Redo","KeyboardNav":"KeyboardNav", "CollShuffle":"CollShuffle", "PlayArtist":"PlayArtist", "PlayPlaylist":"PlayPlaylist", "PlaySongs":"PlaySongs", "RemoveSong":"RemoveSong",
            "DDArtists":"DDArtists", "DDSongs":"DDSongs", "DDPlaylists":"DDPlaylists", "PlayerSort":"PlayerSort", "PlsSort":"PlsSort", "DDMultiselect":"DDMultiselect"
        },

        Label: {
            "Init":"Init", "ReInst":"ReInst", "On":"On", "Off":"Off"
        },

        record: function(category, action, label, value) {
            _gaq.push(['_trackEvent', category, action, label, value]); 
        }
    };
}();
/*****
 FB Connect impl 
*****/
if( typeof AG === 'undefined' ) {
    AG = {};
}

AG.FBConnect = function() {
    var fbInitialized = false;
    var apiKey = "627e9363a9fe3bce8713644e2c764ec5";
    var isConnected = false;
    var loggedInUser;
    var firstName;
    var lastName;
    var emailAddress;
    var pictureLink;
    var connectedCB;
    var isSignupOrSignin = false;

    function handleConnectedForSignin() {
        isSignupOrSignin = true;
        $.post( encodeURI("/login?"), { fbConnect: 1 }, function(data) {
            if( data.success === true ) {
                AG.Track.record(AG.Track.Cat.Signin,"FB" + AG.Track.Act.Success);
                AG.Misc.sendToSignedInPage();
            } else {
                // unable to sign in -- send to sign up
                window.location.href='/?fbSignup=true';
            }
        },
        "json" ); 
    }

    return {
        initFB: function() {
            FB_RequireFeatures( ["XFBML"], function() {  
                FB.init( 
                    apiKey, 
                    "/fbConnect/xd_receiver.htm", 
                    {
                        permsToRequestOnConnect: "email",
                        ifUserConnected: AG.FBConnect.handleConnectedUser
                    });

                FB.ensureInit(function() {
                    fbInitialized = true;
                });
            } );
        },

        invokeIfConnected: function( callback ) {
            if( isConnected ) {
                callback();
            }
            else {
                connectedCB = callback;
            }
        },

        doSignin: function() {
            FB.ensureInit( function() {
                AG.Track.record(AG.Track.Cat.Signin,"FB" + AG.Track.Act.Submit);
                FB.Connect.requireSession( function() {
                    handleConnectedForSignin();
                });
            });
        },
        
        connectNow: function( callback ) {
            if( isConnected ){
                callback();
            }
            else {
                FB.ensureInit( function() {
                    FB.Connect.requireSession( function() {
                        AG.FBConnect.handleConnectedUser();
                        callback();
                    });
                });
            }
        },

        handleConnectedUser: function() {
            FB.ensureInit( function() {
                loggedInUser = FB.Connect.get_loggedInUser();
                if( loggedInUser === null ) {
                    return;
                }
                var userInfoNeeded = ['first_name', 'last_name', 'email', 'pic_square'];
                FB.Facebook.apiClient.users_getInfo( loggedInUser, userInfoNeeded, function( response, ex ) {
                    if( response !== null ) {
                        isConnected = true;
                        firstName = response[0]['first_name'];
                        lastName = response[0]['last_name'];
                        emailAddress = response[0]['email'];
                        pictureLink = response[0]['pic_square'];
                       
                        if( !isSignupOrSignin && connectedCB !== undefined ) {
                            connectedCB();
                        }
                    }
                } );
            });
        },

        handleConnectedForSignup: function() {
            AG.Track.record(AG.Track.Cat.Signup,"FB1" + AG.Track.Act.Success);
            isSignupOrSignin = true;
            // simply reload the page and let the server handle figuring out
            // the user's FB info
            window.location.href='/?fbSignup=true';
        },
        

        refreshML: function( element ) {
            try {
                if( element === undefined ) {
                    FB.XFBML.Host.parseDomTree();
                }
                else {
                    FB.XFBML.Host.parseDomElement( element );
                }
            }
            catch( err ) {}
        },

        isConnected: function() {
            return isConnected;
        },

        logoutUser: function( callback ) {
            try {
                FB.Connect.logout( callback );
            }                   
            catch( err ) {
                callback();
            }
        },

        publishConnected: function( callback ) {
            var message = "I'm hooked onto Audiogalaxy - you should be too!";
            var attachment = {
                'name' : 'Audiogalaxy is Back!',
                'href' : 'http://www.audiogalaxy.com',
                'caption' : '{*actor*} is rocking it to Audiogalaxy',
                'description' : 'Audiogalaxy - DRM Free MP3s, No BS!',
                'media' : [ {
                    'type': 'image',
                    'src' : 'http://beta.audiogalaxy.com/images/misc/avatar-online.png',
                    'href': 'http://www.audiogalaxy.com' } ]
            };

            var action_links = [ {
                'text': 'Discover Audiogalaxy',
                'href': 'http://www.audiogalaxy.com' } ];

            try {
                FB.Connect.streamPublish( message, attachment, action_links, null, null, callback );
            }
            catch( err ) {
                alert( 'FB Publish err: ' + err );
            }
        },

        userLoggedIn: function() { return loggedInUser; },
        userFirstName: function() { return firstName; },
        userLastName: function() { return lastName; },
        userEmail: function() { return emailAddress; },
        userPicture: function() { return pictureLink; }
    };
}();


/****
Cache and fetch information about the user's collection
For now, just pass thru to the cloud
*****/
if( typeof AG === 'undefined' ) {
    AG = {};
}


AG.Collection = function(){
    
    var queryUrl = userUrlBase + 'queryCollection?';

    var artistSortInfo = { 
        byDate: [], 
        byLetter: {}, 
        htmlByDate: "",
        htmlByLetter: "",
        lettersPresent: [], 
        displayingByDate: true,
        query: { getArtists: 1 },
        selector: "#artistListing",
        insertSelector: "#artistListing table",
        rowId: "#artRow_",
        parentClass: ".artRow",
        draggable: ".artistRowDraggable",
        refreshFunction: AG.DragDrop.refreshArtistsPane
    };

    var playlistSortInfo = {
        byDate: [], 
        byLetter: {}, 
        htmlByDate: "",
        htmlByLetter: "",
        lettersPresent: [], 
        displayingByDate: true,
        query: { getPlaylists: 1 },
        selector: "#playlistListing",
        insertSelector: "#playlistListing table",
        rowId: "#plsRow_",
        parentClass: ".plsRow",
        draggable: ".plsRowDraggable",
        refreshFunction: AG.DragDrop.refreshPlaylistsPane
    };

    function addToBuckets( sortObj, objToAdd ){
        if( objToAdd.name !== undefined && objToAdd.name.length > 0 ){
            var startsWith = objToAdd.name.charAt(0).toUpperCase();
            if( startsWith < 'A' || startsWith > 'Z' ) {
                startsWith = '0';
            }
            if( sortObj.byLetter[startsWith] === undefined ){
                sortObj.byLetter[startsWith] = [];
                sortObj.lettersPresent.push(startsWith);
            }
                
            sortObj.byLetter[startsWith].push(objToAdd);
            sortObj.byDate.push(objToAdd);

            return startsWith;
        }

        return null;
    }

    function removeFromBuckets( sortObj, id, name ) {
        if( name !== undefined && name.length > 0 ) {
            var startsWith = name.charAt(0).toUpperCase();
            if( startsWith < 'A' || startsWith > 'Z' ) {
                startsWith = '0';
            }

            if( sortObj.byLetter[startsWith] !== undefined ) {
                var arr = sortObj.byLetter[startsWith];
                for( var i = 0; i < arr.length; i++ ) {
                    if( arr[i].id === id ) { 
                        arr.splice(i, 1);
                        if( arr.length === 0 ) {
                            delete sortObj.byLetter[startsWith];
                            var letters = sortObj.lettersPresent;
                            for( var j = 0; j < letters.length; j++ ) {
                                if( letters[j] === startsWith ) {
                                    letters.splice(j, 1);
                                    break;
                                }
                            }
                            $("#collGroup_" + startsWith).remove();
                            disableLetter( startsWith );
                        }
                        break;
                    }
                }
            }

            for( var k = 0; k < sortObj.byDate.length; k++ ) {
                if( sortObj.byDate[k].id === id ) {
                    sortObj.byDate.splice(k, 1);
                    break;
                }
            }

            sortObj.htmlByDate = "";
            sortObj.htmlByLetter = "";
        }
    }

    function scrollToLetter( sortObj, el ){
        var letter = AG.Misc.extractID( el ); 
        $(sortObj.selector).scrollTo("#collGroup_" + letter, { 
            easing: 'swing', 
            duration: 1000,
            onAfter: function() { $("#collGroup_" + letter ).effect('highlight',{},1500); }           
            } );
    }

    function enableLetter( letter, sortObj ) {
        $("#filter_" + letter).removeClass("filterbarDisabled").addClass("filterbarEnabled").click( function () {
            return (function() { 
                showAlphabetically( sortObj );
                scrollToLetter( sortObj, this );
                setTimeout( function() { sortObj.refreshFunction(); }, 1200 );
                return false;
            });
        }());
    }

    function disableLetter( letter ) {
        $("#filter_" + letter).removeClass("filterbarEnabled").addClass("filterbarDisabled").unbind('click');
    }

    function setupFilterBar( sortObj ){
        for( var i = 0; i < sortObj.lettersPresent.length; i++ ){
            enableLetter( sortObj.lettersPresent[i].toUpperCase(), sortObj );
        }
        
        $("#filter_date").removeClass("filterbarDisabled").addClass("filterbarEnabled").click( function () {
            showByDate( sortObj );
            $(sortObj.selector).scrollTo(".rowSelected", { easing: 'swing', duration: 1000, offset: { top: -250, left: 0}, margin: true });
            setTimeout( function() { sortObj.refreshFunction(); }, 1200 );
            return false;
        });

    }

    function resetSortInfo( sortObj ) {
        sortObj.displayingByDate = true;
        sortObj.byDate = [];
        sortObj.byLetter = {};
        sortObj.lettersPresent = [];

        $(".filterbarItem").removeClass("filterbarEnabled").addClass("filterbarDisabled").unbind('click');
    }

    function fetchAllInternal( sortObj, startOffset, onNewPageFetched, onLoadComplete ) {
        
        sortObj.query.offset = startOffset;
        $.get( 
            encodeURI( queryUrl ),
            sortObj.query,
            function( data ) {
                if( data !== undefined && data !== null && data.success ){
                    if( !$(sortObj.selector).is(':visible') ) {
                        // dont invoke any CBs. This is done
                        return;
                    }
                   
                    var numReceived = startOffset + data.numInBatch;
                   
                    if( data.numInBatch > 0 && data.haveMore ) {
                        // we have more to fetch
                        fetchAllInternal( sortObj, numReceived, onNewPageFetched, onLoadComplete );
                    }

                    var newHtmlStr = '';
                    for( var i = 0; i < data.details.length; i++ ){
                        newHtmlStr += data.details[i].html;
                        var obj = data.details[i];
                        addToBuckets( sortObj, obj );
                    }
                    
                    var newHtml = $(newHtmlStr).appendTo( $( sortObj.insertSelector ) );
                    onNewPageFetched( newHtml ); 
                    
                    if( !data.haveMore ) {
                        onLoadComplete();
                        
                        for( var j = 0; j < sortObj.lettersPresent.length; j++ ){
                            var letter = sortObj.lettersPresent[j];
                            var thisList = sortObj.byLetter[letter].sortAsc('name');
                        }
                        sortObj.lettersPresent.isSorted = true;

                        setupFilterBar( sortObj );
                        sortObj.htmlByDate = $(sortObj.selector).html();
                    }
                }
            },
            "json" );
    }

    function showByDate( sortObj ){
        if( !$(sortObj.selector).is(':visible') || sortObj.displayingByDate ) {
            return;
        }
        
        var tempHtml = '';
        if( sortObj.htmlByDate !== "" ) {
            tempHtml = sortObj.htmlByDate;
        }
        else {
            for( var i = 0; i < sortObj.byDate.length; i++ ){
                var artistID = sortObj.byDate[i].id;
                tempHtml += sortObj.byDate[i].html;
            }

            sortObj.htmlByDate = tempHtml;
        }

        sortObj.displayingByDate = true;
        var selectedID = AG.Misc.extractID( $(".rowSelected " + sortObj.draggable, sortObj.insertSelector) );
        $(sortObj.insertSelector).html(tempHtml);
        $(sortObj.rowId + selectedID).parents( sortObj.parentClass ).addClass("rowSelected"); 
    }

    function showAlphabetically( sortObj ) {
        if( !$(sortObj.selector).is(':visible') || !sortObj.displayingByDate ) {
            return;
        }
        
        var tempHtml = '';
        if( sortObj.htmlByLetter !== "" ) {
            tempHtml = sortObj.htmlByLetter;
        }
        else {
            var needsSort = true;
            if( sortObj.byLetter.isSorted !== undefined || !sortObj.byLetter.isSorted ) {
                needsSort = false;
            }
            
            // AG.DragDrop.destroyDragAndDrop( sortObj.selector );

            sortObj.lettersPresent.sort();
            
            for( var i = 0; i < sortObj.lettersPresent.length; i++ ) {

                var letter = sortObj.lettersPresent[i];
                var thisList = sortObj.byLetter[letter];
                if( needsSort ) {
                    thisList.sortAsc('name');
                }
                
                tempHtml += getHeadingHtml( letter );
                
                for( var j in thisList ) {
                    if( thisList.hasOwnProperty(j) ) {
                        tempHtml += thisList[j].html;
                    }
                }
            }
            
            sortObj.byLetter.isSorted = true;
            sortObj.htmlByLetter = tempHtml;
        }

        sortObj.displayingByDate = false;
        var selectedID = AG.Misc.extractID( $(".rowSelected " + sortObj.draggable, sortObj.insertSelector) );
        $(sortObj.insertSelector).html(tempHtml);
        $(sortObj.rowId + selectedID).parents( sortObj.parentClass ).addClass("rowSelected"); 
    }
   
    function fetchAllArtists( startOffset, onNewPageFetched, onLoadComplete ) {
        resetSortInfo( artistSortInfo );
        return fetchAllInternal( artistSortInfo, startOffset, onNewPageFetched, onLoadComplete );
    }

    function fetchAllPlaylists( startOffset, onNewPageFetched, onLoadComplete ) {
        resetSortInfo( playlistSortInfo );
        return fetchAllInternal( playlistSortInfo, startOffset, onNewPageFetched, onLoadComplete );
    }

    /***********************************************
    Manipulate the Songs/Playlists page 
    ************************************************/
 
    function setImgHover( el, hoverImg, outImg ){
        $(el).hover( 
            function(){
                $(this).attr('src', hoverImg );    
            },
            function() {
                $(this).attr('src', outImg );
            }
        );
    }

    // we simulate the click on the draggable child of the parent, like how the mouse does
    function bindKeyboard( rowSelector, childDraggable, isArtist ) {
        
        $(document).unbind('keydown.collection');

        $(document).bind('keydown.collection', function (e) {
            
            if( $("#filtered_coll").is(":visible") ) {
                return;
            }

            var elem = null;
            var parentRow = $(".rowSelected");
            if( !parentRow.length ) {
                return;
            }

            switch( e.keyCode ) {
                case 13:    // enter
                    if( $("#save_pls_pane").is(':visible') ) { return; }
                    var id;
                    var name;
                    if( isArtist ){
                        id = AG.Misc.extractID( $("div", parentRow) );
                        name = $("#artistName_" + id).text();
                        AG.PlayerControl.queueSongsByArtists( id, name, false );
                        return false;
                    }
                    else {
                        id = AG.Misc.extractID( $("div", parentRow) );
                        name = $("#plsName_" + id ).text();
                        var tags = $("#plsTags_" + id ).text();
                        AG.PlayerControl.queuePlaylist( id, name, tags, false );
                        return false;
                    }
                    break; 

                case 38:    // up
                    elem = parentRow.prev( rowSelector );
                    break;

                case 40:    // down
                    elem = parentRow.next( rowSelector );
                    break;

                default:
                    break;
            }

            if( elem !== null && elem.length !== 0 ){
                $("div",parentRow).removeClass("ui-selected");
                $(childDraggable, elem).trigger("click");
                return false;
            }
        } );
    }

    function setSongsAndPlaytime(){
        var playTimeSecs = 0;
        $(".playTimeSecs").each( function() {
            playTimeSecs += parseInt( $(this).text(), 10 );
        } );
        
        $("#songsPaneNumSongs").data('playTimeSecs', playTimeSecs );
        var numSongs = $(".songRow").length;
        if( numSongs === 0 ) { $("#pls_save").hide(); }
        
        if( numSongs !== 1 ) { numSongs = numSongs + " Songs"; }
        else { numSongs = numSongs + " Song"; }

        $("#songsPaneNumSongs").text( numSongs + ' - ' + AG.Misc.getTimeStringFromSec( playTimeSecs, true ) );
    }

    function selectFirstArtistRow(){
        $(".artistRowDraggable:first").trigger("click");
    }

    function hideOpenArtistSongs() {
        $("#songlistHeader").hide();
        $("#artistAutoPls").hide();
        $("#songlist").hide();
        $("#collRightArtistDetails").removeClass("collRightArtistWithArt").addClass("collRightArtistWithoutArt");
        $("#artist_art").hide();
        
        var id = $("#songlist").data('id');
        if( id === undefined ) {
            return;
        }
        
        return false;
    }

    function hideOpenPlaylist() {
        
        $("#playlistHeader").html('');
        $("#playlist_tracks").html('');
        $("#pls_save").hide();
        $("#plsTags").html('').hide();
        $("#songlistPlay").hide();
        
        var id = $("#playlist_tracks").data('id');
        if( id === undefined ) {
            return false;
        }

        return false;
    }

    function selectFirstPlaylistRow() {
        $(".plsRowDraggable:first").trigger("click");
    }

    function refreshAllPlaylists () {
        var expandedId = $("#playlist_tracks").data('id');
        $("#playlistListing table").html('');
        var plsSelected = false;

        fetchAllPlaylists( 0,
            function( newHtml ) {
                if( !plsSelected && expandedId === undefined ) {
                    selectFirstPlaylistRow();
                    AG.DragDrop.refreshPlaylistSongsPane();
                    plsSelected = true;
                }
                else if ( !plsSelected && $("#plsRow_" + expandedId).is(':visible') ) {
                    $("#plsRow_" + expandedId).trigger('click');
                    AG.DragDrop.refreshPlaylistSongsPane();
                    plsSelected = true;
                }
                
                AG.DragDrop.refreshPlaylistsPane( newHtml );
            },
            function() {
                if( !plsSelected ) {
                    selectFirstPlaylistRow();
                    AG.DragDrop.refreshPlaylistSongsPane();
                    plsSelected = true;
                }
                updateNumPlaylists();
            });
    }

    function updateNumArtists(){
        var numArtists = $(".artRow").length;
        if( numArtists !== 1 ) {
            numArtists = numArtists + " Artists";
        }
        else {
            numArtists = numArtists + " Artist";
        }
        $("#artistPaneNumArtists").text( numArtists );
    }

    function updateNumPlaylists() {
        var numPlaylists = $(".plsRow").length;
        if( numPlaylists !== 1 ) {
            numPlaylists = numPlaylists + " Playlists";
        }
        else { 
            numPlaylists = numPlaylists + " Playlist";
        }
        $("#playlistPaneNumPlaylists").text( numPlaylists );
    }

    function setM3uSig( sig ){
    }

    function getHeadingHtml( letter ) {
        var disp = letter;
        if( letter === '0' ) { disp = '*'; }
        return '<tr id="collGroup_' + letter + '" class="collGroup"><td colspan="2" class="collGroupLetter">' + disp + '</td></tr>' ;
    }

    function showArtistSongsByID( id ) {
        hideOpenArtistSongs();
        $("#artistLoading").show();
       
        AG.Collection.queryCollectionForHtml( {getAlbumsByArtist: id}, function(success, html) {
            if( success && html !== undefined ) {
                $("#artistLoading").hide();

                var artName = $("#artistName_" + id).text();
                $("#songlistHeader").text(artName).attr('title', artName).show();

                $("#songlist").data('id', id );
                $("#songlist").html( html ).show();
                $("#artistAutoPls").show();
               
                setSongsAndPlaytime();

                fixupNewHtml();
                AG.DragDrop.refreshSongsPane();
            }
        });

        return false;
    }

    function addArtist( id, name, html ) {
        var startsWith = addToBuckets( artistSortInfo, { id: id, name: name, html: html } );

        if( startsWith === null ) {
            return;
        }

        enableLetter( startsWith, artistSortInfo );

        if( artistSortInfo.displayingByDate ) {
            if( $(".artRow").length === 0 ) {
                // this is the first row
                $(".artistsSelectable table").html(html);
            }
            else {
                $("tr.artRow:first").before(html);
            }
            artistSortInfo.htmlByDate = $(artistSortInfo.selector).html();

            //invalidate the byLetter html since we won't find the right spot for it
            artistSortInfo.htmlByLetter = "";
        } else {
            // add our letter heading if we don't have it
            if( $("#collGroup_" + startsWith ).length === 0 ) {
                insertedLetter = false;
                $(".collGroup").each( function() {
                    var letter = $(this).attr("ID").split(/_/)[1]; 
                    if( letter > startsWith ) { 
                        $("#collGroup_" + letter).before( getHeadingHtml( startsWith ) );
                        insertedLetter = true;
                        return false;
                    }
                });

                if( !insertedLetter ) {
                    $(artistSortInfo.insertSelector).append( getHeadingHtml(startsWith) );
                }
            }

            // at this point, we should definitely have a heading.  Now we put the artist 
            // into the correct position in it
            var prev = null;
            var bucket = artistSortInfo.byLetter[startsWith];
            bucket.sortAsc('name');
            for( var i in bucket ) {
                if( bucket.hasOwnProperty(i) ) {
                    if( bucket[i].id === id ) {
                        if( prev === null ) {
                            $("#collGroup_" + startsWith ).after( html );
                            break;
                        } else {
                            $("#artRow_" + prev.id ).parents("tr.artRow").after( html );
                            break;
                        }
                    }

                    prev = bucket[i];
                }
            }
            
            artistSortInfo.htmlByLetter = $(artistSortInfo.selector).html();

            //invalidate the byDate html
            artistSortInfo.htmlByDate = "";
        }

        AG.DragDrop.refreshArtistsPane( $("#artRow_" + id).parents("tr.artRow") );
    }

    function refreshAllArtists() {
        
        var artistSelected = null;
        if( $(".rowSelected").length ) {
            artistSelected = AG.Misc.extractID( $(".rowSelected .artistRowDraggable") ); 
        }

        var artistRowSelected = false;
        fetchAllArtists( 0, 
            function( newHtml ) { 
                if( !artistRowSelected ) {
                    if( artistSelected === null ) {
                        selectFirstArtistRow();
                        artistRowSelected = true;
                    }
                    else if( $("#artRow_" + artistSelected ).length ) {
                        $("#artRow_" + artistSelected).parents( ".artRow" ).addClass("rowSelected"); 
                        artistRowSelected = true;
                    }
                }

                AG.DragDrop.refreshArtistsPane( newHtml );
                updateNumArtists();    
            },
            function() {
            } );

    }

    /***********************************************/

    return {
        
        queryCollection: function( query, callback ) {
            $.get( 
                encodeURI( queryUrl ),
                query,
                function( data ) {
                    if(data !== undefined && data !== null && data.success && data.songList !== undefined ){
                        // cache this
                        callback( true, data.songList );
                    }
                    else {
                        callback( false );
                    }
                },
                "json" );
        },

        queryCollectionForHtml: function( query, callback ) {
            query.html = 1;
            $.get(
                encodeURI( queryUrl ),
                query,
                function( data ) {
                    if(data !== undefined && data !== null && data.success && data.html !== undefined ){
                        if( data.songList !== undefined ) {
                            // cache this
                        }

                        callback( true, data.html, query );
                    }
                    else {
                        callback( false );
                    }
                },
                "json" );
        },
        
        getCollectionSig: function() {
            return "collection" + 
                "-" + $("#collectionBody").data("collectionSig") + 
                "-" + $("#collectionBody").data("artistsClock") + 
                "-" + $("#songlist").data("id") + 
                "-" + $("#collectionBody").data("curArtistSig") +
                "-" + $("#collectionBody").data("numProcessing") + 
                "-" + $("#collectionBody").data("loginID");
        },

        refreshMusicCollection: function( vars ) {
            $("#numFiles").text( vars.numFiles );
            $("#numUnmatched").text( vars.numUnmatched );

            if( vars.curArtistID == $("#songlist").data("id") && 
                vars.curArtistSig != $("#collectionBody").data("curArtistSig") ) {
                showArtistSongsByID( vars.curArtistID );
            }

            $("#collectionBody").data( "collectionSig", vars.collectionSig );
            $("#collectionBody").data( "artistsClock", vars.artistsClock );
            $("#collectionBody").data( "curArtistSig", vars.curArtistSig );
            $("#collectionBody").data( "numProcessing", vars.numProcessing );
            $("#collectionBody").data( "loginID", vars.loginID );
 
            if( vars.numProcessing > 0 ) {
                $("#stillWorking").show();
            } else {
                $("#stillWorking").hide();
            }
            
            if( !vars.updateCollection ) {
                return;
            }
            
            if( vars.refreshAll ) 
            {
                $(".artistsSelectable table").html('');
                refreshAllArtists();
            }
            else
            {
                for( var i = 0; i < vars.adds.length; i++ ) {
                    if( $("#artRow_" + vars.adds[i].id ).length === 0 ) {
                        addArtist( vars.adds[i].id, vars.adds[i].name, vars.adds[i].html );
                    }
                }

                for( i = 0; i < vars.deletes.length; i++ ) {
                    var artistName = $("#artistName_" + vars.deletes[i] ).text();
                    removeFromBuckets( artistSortInfo, vars.deletes[i].toString(), artistName ); 
                    $("#artRow_" + vars.deletes[i] ).parents( "tr.artRow" ).remove();
                }
                
                if( $(".rowSelected").length === 0 ) {
                    selectFirstArtistRow();
                }
                
                updateNumArtists();
            }
        },
    
        onCollectionPageLoaded: function() {
            refreshAllArtists();    
            setImgHover( $("#artistAutoPls"), "/images/misc/playPlaylistHover.png", "/images/misc/playPlaylist.png" );
            bindKeyboard( ".artRow", ".artistRowDraggable", true );
        },

        showArtistSongs: function( el ) {
            var id = AG.Misc.extractID( el );

            $(".artRow.rowSelected").removeClass("rowSelected");
            $(el).parents(".artRow").addClass("rowSelected");
            
            if( $("#songlist").data('id') === id ) {
                return;
            }

            showArtistSongsByID( id );
            return false;
        },

        getM3uPlaylistsSig: function() {
            return "m3uPlaylists-" + $("#fb_body_m3u").data("playlistsSig");
        },

        getAutoPlaylistsSig: function() {
            return "autoPlaylists-" + $("#fb_body_auto").data("autoPlaylistsSig");
        },

        refreshAutoPlaylists: function( stats ) {
            $("#fb_body_auto").data( "autoPlaylistsSig", stats.sig );

            refreshAllPlaylists();
        },
        
        refreshM3uPlaylists: function( stats ) {
            $("#numM3uPlaylists").text( stats.numM3uPlaylists );
            $("#fb_body_m3u").data( "playlistsSig", stats.sig );

            refreshAllPlaylists();
        },

        showPlaylist: function( el ) {
            var id = AG.Misc.extractID( el );
            
            $(".plsRow.rowSelected").removeClass("rowSelected");
            $(el).parents(".plsRow").addClass("rowSelected");

            
            if( $("#playlist_tracks").data('id') === id ) {
                return;
            }
            
            hideOpenPlaylist();

            var type = 'playlists';
            if( $(el).hasClass('autoPlaylists') ){
                type = 'autoPlaylists';
            }

            $("#playlistLoading").show();
            AG.Collection.queryCollectionForHtml( { playlistIDs: id }, function( success, html ) {
                if( success && html !== undefined ) {
                    $("#playlistLoading").hide();
                    $("#playlist_tracks").data('id', id );
                    $("#playlist_tracks").data('type', type );
                    $("#playlist_tracks").html( html ).show();
                    
                    $("#playlistHeader").html( $("#plsName_" + id).text() );
                    $("#playlistOptions").show();
                    $("#pls_save").show();
                    $("#songlistPlay").show();
                    $("#viewMarkup").show();
                    $("#hideMarkup").hide();
                   
                    if( type === 'autoPlaylists' ) {
                        $("#plsTags").append( '<span class="plsTagName">automatic playlist</span>' );
                    } 
                    else {
                        var tags = $("#plsTags_" + id).text();
                        if( tags !== '' ) {
                            tags = tags.split(';');
                            for( var i = 0; i < tags.length; i++ ) {
                                if( tags[i] !== '' ) {
                                    $("#plsTags").append( '<span class="plsTagName">' + tags[i] + '</span>' );
                                }
                            }
                        }
                    }
                    $("#plsTags").show();

                    setSongsAndPlaytime();
                    fixupNewHtml();
                    AG.DragDrop.refreshPlaylistSongsPane();
                }
            });

            return false;
        },

        deletePlaylist: function( id ) {
            
            $("#playlist_tracks").data('plsToDelete', id);
            $("#delete_playlist_dialog").dialog('open');

            return false;
        },

        deletePlaylistConfirmed: function( id ) {
            var expandedId = $("#playlist_tracks").data('id');
            var url = userUrlBase + "playlists/?";
            $.post( encodeURI( url ), 
                    { deletePls: id }, 
                    function(data) {
                        if( data !== undefined && data.success === true ){
                            if( expandedId === id ) {
                                selectFirstPlaylistRow();
                            }
                            else {
                                $("#plsRow_" + expandedId).trigger('click');
                            }

                            $("#plsRow_" + id).remove();
                            $("#fb_body_m3u").data("playlistsSig", data.sig );

                            updateNumPlaylists();
                            if( !data.deletedFromClient ) {
                                alert( "The playlist could not be deleted from your computer" );
                            }
                        }
                    },
                    "json" );
        },

        onPlaylistPageLoaded: function(){
            var firstPlsRowSelected = false;
            fetchAllPlaylists( 0,
                function( newHtml ) {
                    if( !firstPlsRowSelected ) {
                        selectFirstPlaylistRow();
                        AG.DragDrop.refreshPlaylistSongsPane();
                        firstPlsRowSelected = true;
                    }

                    AG.DragDrop.refreshPlaylistsPane( newHtml );
                    updateNumPlaylists();
                },
                function() {
                } );

            setImgHover( $("#songlistPlay"), "/images/misc/playPlaylistHover.png", "/images/misc/playPlaylist.png" ); 
            bindKeyboard( ".plsRow", ".plsRowDraggable", false );
        },

        saveCurrentPlaylistToCloud: function(){
            var id = $("#playlist_tracks").data('id');
            var type = $("#playlist_tracks").data('type');

            if( type === 'autoPlaylists' ) {
                return;
            }

            var songList = [];
            var cfIDList = [];
            $("#playlist_tracks").find("[id^=song_]").each( function(){
                songList.push(AG.Misc.extractID( this ));
                cfIDList.push(AG.Misc.extractID2( this ));
            });
            
            var playTimeSecs = $("#songsPaneNumSongs").data('playTimeSecs');
            var plsName = $("#plsName_" + id).text();
            var plsTags = $("#plsTags_" + id).text();

            AG.Misc.sendSavePlaylist(songList.join(","), cfIDList.join(","), plsName, 0, id, plsTags, playTimeSecs, false, function(data) {
                if( data === undefined || !data.success ) {
                    alert( "Your playlist could not be saved at this time. Please try again later" );
                }
                else {
                    $("#fb_body_m3u").data("playlistsSig", data.sig );
                }
            });
        },

        removeSongFromPlaylist: function( el ) {
            $(el).parents(".songRow").remove();
            AG.Collection.saveCurrentPlaylistToCloud();
        }
    };
}();

/****
Functions related to player controls
*****/
if( typeof AG === 'undefined' ) {
    AG = {};
}

// IE workaround
if(!Array.indexOf){
    Array.prototype.indexOf = function(obj){
        for(var i=0; i<this.length; i++){
            if(this[i]===obj){
                return i;
            }
        }
        return -1;
    };
}

// helper function
Array.prototype.remove = function(from, to){
    this.splice(from,
            !to ||
            1 + to - from + (!(to < 0 ^ from >= 0) && (to < 0 || -1) * this.length));
    return this.length;
};


AG.PlayerControl = function() {
    var PlayerState = { "Ready":0, "Playing": 1, "Paused": 2 };
    var ActionType = { "Add":0, "Remove":1, "Clear":2, "QueryCollection":3, "Reorder":4, "ToggleRandom":5 };

    var defaultHeader = "Current Playlist";

    var isOnline = true;
    var currentHtmlID = null;
    var soundObj = null;
    var onFinishedCB = null;
    var sliding = false;
    var state = PlayerState.Ready;
    var volume = 80;
    var maxNumUndo = 500;
    var numSongsInList = 0;
    var songCounter = 0;
    var playlistLoaded = 0;
    var currentPlaylistTags = '';
    var maxSongsInPlaylist = 350;
    var maxDropSongs = 350;
    var maxDropArtists = 50;
    var maxDropPlaylists = 50;
    var currentHeaderText = '';
    var currentPlsPlayTimeSecs = 0;
    var startSavingUnsavedSongs = false;
    var localhostRunning = false;

    var paneOffset = {};
    var scrollUpOffset = {};
    var scrollDownOffset = {};

    var scrolling = false;
    var scrollList = false;
   
    var shuffleEnabled = false;
    var repeatEnabled = false;

    var playingRandom = false;
    var numRandomsPerFetch = 20;
   
    var undoStack = [];
    var redoStack = [];
    var playedSongs = [];
    var randomSongs = [];

    var placeholderRow = null;
    var placeholderRowHtml = '<tr class="pointer"><td class="waiting" style="width: 15px; height: 20px;"></td><td colspan="2" class="songInPls">Fetching song(s)...</td></tr>';
    var dropLocationHtml = '<tr class="tempRow sortablePlaceholder"><td style="height: 20px;" colspan="3" class="songInPls">Drop here...</td></tr>';

    function getSongIDFromID(str) {
        var toks = str.split(/_/); 
        if( toks.length < 3 ) {
            return '';
        }
        
        // ids can be foo_count_songID_cfID or foo_songID_cfID
        return toks[ toks.length - 2 ];
    }

    function getCFIDFromID(str) {
        var toks = str.split(/_/); 
        if( toks.length < 3 ) {
            return '';
        }
        
        // ids can be foo_count_songID_cfID or foo_songID_cfID
        return toks[ toks.length - 1 ];
    }
    
    function getResourceID(str) {
        var toks = str.split(/_/); 
        if( toks.length != 2 ) {
            return 0;
        }
        
        return toks[1];
    }

    function getHtmlID(str) {
        var toks = str.split(/_/); 
        if( toks.length < 4 ) {
            return 0;
        }

        return toks[1] + '_' + toks[2] + '_' + toks[3];
    }

    function makeSortable(){
        $(".playerSortable").addClass('no-select');
        $(".playerSortable").sortable({
            handle: '.playbuttonSmall, .pausebuttonSmall, .playbuttonMissingSmall',
            helper: 'original',
            items: '.nowPlayingRow, .missingRow',
            axis: 'y',
            opacity: 0.8,
            placeholder: 'sortablePlaceholder',
            forcePlaceholderSize: true,
            forceHelperSize: true,
            start: function(event, ui){
                $(ui.item).data('origOffset', $(ui.item).offset());
                $(ui.item).addClass('issorting');
                $(ui.placeholder).append('<td colspan="3" style="height: 20px;"></td>');
            },
            stop: function(event, ui){
                $(ui.item).removeClass('issorting').effect('highlight', {}, 1500);
                AG.Track.record(AG.Track.Cat.DragDrop,AG.Track.Act.PlayerSort);
            },
            update: function(event, ui){
                var origOffset = $(ui.item).data('origOffset');
                var newOffset = ui.offset;
                if( Math.abs(newOffset.top - origOffset.top) > 15 ){
                    addToUndoStack( { action: ActionType.Reorder, id: $(ui.item).attr('id'), origOffset: origOffset, newOffset: newOffset } );
                }
            }

        });
    }

    function moveElement( id, toOffset ){
        
        var removed = $("#" + id).remove();
        var closestElements = findClosestElements( toOffset );
        if( closestElements.previousElement !== null ){
            removed.insertAfter($(closestElements.previousElement));
        }
        else if (closestElements.nextElement !== null ){
            removed.insertBefore($(closestElements.nextElement));
        }
        else {
            $("#nowPlayingList").append( removed );
        }

        $("#" + id).effect("highlight", {}, 1500);
    }

    function addToUndoStack( obj ){
        while( undoStack.length > maxNumUndo ){
            undoStack.shift();
        }
        
        undoStack.push(obj);
        $("#undoDisabled").hide();
        $("#undoEnabled").show();
    }

    function addToRedoStack( obj ){
        while( redoStack.length > maxNumUndo ){
            redoStack.shift();
        }

        redoStack.push(obj);
        $("#redoDisabled").hide();
        $("#redoEnabled").show();
    }

    function restoreSingleSong( itemHtml, playTimeSecs, offset ){
        
        var added = false;

        var closestElements = findClosestElements( offset );
        if( closestElements.previousElement !== null ){
            $(itemHtml).insertAfter($(closestElements.previousElement)).effect("highlight",{},1500);
            added = true;
        }
        else if ( closestElements.nextElement !== null ) {
            $(itemHtml).insertBefore($(closestElements.nextElement)).effect("highlight",{},1500);
            added = true;
        }
    
        if ( !added ) {
            $(itemHtml).appendTo( $("#nowPlayingList") ).effect("highlight",{},1500);
        }
       
        updateNumSongsInList( 1 ); 
        updatePlaylistTime( true, playTimeSecs );
    }

    function restoreClearedSongs( songList, numSongs, plsPlaytimeSec ){
       $("#nowPlayingList").html(songList);
       currentPlsPlayTimeSecs = plsPlaytimeSec;
       AG.PlayerControl.playSong();
       $("#savePlsOptionEnabled, #plsInfo").show();
       $("#savePlsOptionDisabled").hide();

       resetNumSongsInList();
       updateNumSongsInList( numSongs );
    }

    function getPreviousSongToPlay(){

        if( !shuffleEnabled && !playingRandom ){
            var prevSongToPlay = $(".activeSongRow").prevAll('.nowPlayingRow:visible');
            if( prevSongToPlay.length === 0 && repeatEnabled ) {
                prevSongToPlay = $(".nowPlayingRow:last");
            }
            
            if ( prevSongToPlay.length ){
                return getHtmlID( prevSongToPlay.attr('id') );             
            }
        }
        else {
            playedSongs.pop(); // remove the last one played
            if( playedSongs.length ){
                return playedSongs.pop();
            }
            else {
                return null;
            }
        }

        return null;
    }

    function playPreviousSong() {
        var prevSongID = getPreviousSongToPlay();
        if ( prevSongID !== null ){
            AG.PlayerControl.toggleSongInList( prevSongID );             
        }
        else {
            $(".nowPlayingRow").removeClass("activeSongRow").css('background-color', '' );
            playedSongs = [];
        }
    }
   
    function getFirstPlayableSongObj(){
        return $(".nowPlayingRow:first");
    
    }

    function getNextSongToPlay(){
        var nextSongID = null;
        
        if( !shuffleEnabled ){
            var nextSongToPlay = $(".activeSongRow").nextAll('.nowPlayingRow:visible');
            if( nextSongToPlay.length === 0 && repeatEnabled ) {
                nextSongToPlay = getFirstPlayableSongObj(); 
            }

            if( nextSongToPlay.length ) {
                nextSongID = getHtmlID( nextSongToPlay.attr('id') );
            }
        }
        else {
            var allSongs = $(".nowPlayingRow:visible");
            if( allSongs.length <= playedSongs.length && !repeatEnabled ){
                return null;
            }
            
            var lastSongIDPlayed = null;
            if( playedSongs.length ){
                lastSongIDPlayed = playedSongs[ playedSongs.length - 1 ];
            }

            var allSongHtmlIDs = [];
            allSongs.each( function(){
                var htmlID = getHtmlID($(this).attr('id'));
                if( repeatEnabled || playedSongs.indexOf(htmlID) === -1 ){
                    allSongHtmlIDs.push( htmlID );
                }
            });

            var found = false;
            while( !found ){
                var rand = Math.floor( Math.random() * allSongHtmlIDs.length );
                nextSongID = allSongHtmlIDs[rand];
                // if this was the last song played and we have options, then find another
                if( allSongHtmlIDs.length > 1 && lastSongIDPlayed === nextSongID ){
                    nextSongID = null;
                    found = false;
                }
                else {
                    found = true;
                }
            }            
        }

        return nextSongID;
    }

    function playNextSong(){
        
        if( playingRandom ) {
            playNextRandom();
            return;
        }

        var nextSongID = getNextSongToPlay();
        if( nextSongID !== null ) {
            AG.PlayerControl.toggleSongInList( nextSongID );                    
        }
        else {
            $(".nowPlayingRow").removeClass("activeSongRow").css('background-color', '' );
            playedSongs = [];
        }
    }

    function recordSongPlayed(htmlID){
        playedSongs.push(htmlID);
        $.post( encodeURI(statusUrl), { logPlaySongs: getSongIDFromID(htmlID), logPlayCFID: getCFIDFromID(htmlID) }, function(data) {} );
        AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.Play);
    }

    function handleKeyDown(e){
        if( $("input.hasFocus").length || $("textarea.hasFocus").length ) {
            return;
        }

        var elem = null;
        var handled = false;
        var curr = $(".activeSongRow");

        if( curr.length ) {
            switch( e.keyCode ) {
                case 37: // left
                    elem = curr.prev(".nowPlayingRow");
                    handled = true;
                    break;

                case 39: //right
                    elem = curr.next(".nowPlayingRow");
                    handled = true;
                    break;
                
                case 32: //space
                    AG.PlayerControl.toggleSongInList( currentHtmlID );
                    handled = true;
                    break;

                case 83: //'s' or 'S'
                    if( AG.Misc.isKeyPressed(AG.Constants.CTRL_KEY) || AG.Misc.isKeyPressed(AG.Constants.META_KEY) ){
                        AG.PlayerControl.saveAsPlaylist( $("#savePlsOptionEnabled") ); 
                        handled = true;
                    }
                    break;

                case 27: // esc
                    if( $("#save_pls_pane").is(":visible") ){
                        $(".popup_close_link", $("#save_pls_pane")).click();
                        handled = true;
                    }
                    break;

                default:
                    break;
            }

            if( elem !== null && elem.length !== 0 ){
                var htmlID = getHtmlID(elem.attr('id'));
                AG.PlayerControl.toggleSongInList( htmlID );
            }

            if( handled ){
                AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.KeyboardNav);
                return false;
            }
        }
    }

    function setVolumeClass(value){
        var classSuffix = 0;
        if( value >= 66 ) { classSuffix = 3; }
        else if (value >= 33 ) { classSuffix = 2; }
        else if (value > 0 ) { classSuffix = 1; }

        $("#volumeControl").removeClass().addClass("volumeLevel" + classSuffix );
        $("#volumeControl, #volSlider").attr( 'title', "volume: " + value + '%' );
    }

    function flipToPlay() {
        
        // main control
        $("#controlSong").removeClass("pauseSong").addClass("playSong").attr('title', "Play (Spacebar key)").unbind( 'click' );
        $("#controlSong").click( function(){ 
            AG.PlayerControl.playSong(); 
        });
      
        if( currentHtmlID !== null ) {
            // button in row 
            var el = "#npButton" + currentHtmlID;
            if( $(el).hasClass("pausebuttonSmall") ) {
                $(el).removeClass("pausebuttonSmall").addClass("playbuttonSmall");
            } 

            // other AG pages
            el = "#playCFID_" + getCFIDFromID(currentHtmlID);
            toggleToPlay( el ); // flip any control for this songID on artist/song/collection to Play
            toggleToPlay(".pausebutton"); // flip any pause to Play
        }
    }

    function flipToPause ( ) {
        
        // main control 
        $("#controlSong").removeClass("playSong").addClass("pauseSong").attr('title', "Pause (Spacebar key)").unbind( 'click' );
        $("#controlSong").click( function(){ 
            AG.PlayerControl.toggleSongInList( currentHtmlID ); 
        });
        
        // button in row
        var el = "#npButton" + currentHtmlID;
        if( $(el).hasClass("playbuttonSmall") ){
            $(el).removeClass("playbuttonSmall").addClass("pausebuttonSmall");
        }

        // other AG pages which might be opn
        el = "#playCFID_" + getCFIDFromID(currentHtmlID);
        toggleToStop(el);
    }

    function findClosestElements( offset ){
       
        var insidePane = false;
        var previousElement = null;
        var nextElement = null;
        var scrollDown = false;
        var scrollUp = false;
       
        if( ( offset.left > scrollUpOffset.left ) && ( offset.left < scrollUpOffset.rightMax ) &&
            ( offset.top > scrollUpOffset.top ) && ( offset.top < scrollUpOffset.bottomMax ) ) {
            scrollUp = true;
        }
        
        if( ( offset.left > scrollDownOffset.left ) && ( offset.left < scrollDownOffset.rightMax ) && 
            ( offset.top > scrollDownOffset.top ) && ( offset.top < scrollDownOffset.bottomMax ) ) {
            scrollDown = true;
        }

        if( offset.left > paneOffset.left && offset.left < paneOffset.rightMax && offset.top > paneOffset.top ) { // ignore height for now
            insidePane = true;
            $("#nowPlayingPane .nowPlayingRow").each( function() {
                var thisOffset = $(this).offset();
                if( thisOffset.top < offset.top ){
                    previousElement = this;
                }
                else if (nextElement === null ) {
                    nextElement = this;
                }

                if (previousElement !== null && nextElement !== null ){
                    return false;
                }
            });
        }

        return { previousElement: previousElement, nextElement: nextElement, scrollUp: scrollUp, scrollDown: scrollDown, insidePane: insidePane };
    }
    
    function enableDropZones(){

        $("#nowPlayingPane, #songDetailsBlock").droppable({
            accept: AG.DragDrop.getDropTargets(),
            activeClass: 'attractDraggable',
            tolerance: 'pointer',
            activate: function( event, ui ){
            },
            deactivate: function( event, ui ){
                $(".tempRow").remove();
                scrollList = false;
            },
            drop: function( event, ui ){

                if( $("#draggingContainer").hasClass("hasSongs") ) {
                    
                    var songIDList = [];
                    var cfIDList = [];
                    $("#draggingContainer .songRowDraggable").each( function() {
                        var songID = getSongIDFromID($(this).attr('id'));
                        var cfID = getCFIDFromID($(this).attr('id'));
                        if( songID !== undefined && songID !== '' && cfID !== undefined && cfID !== '' ){
                            songIDList.push(songID);
                            cfIDList.push(cfID);
                        }
                    });
                    
                    if( songIDList.length > maxDropSongs ) {
                        alert( "Sorry, you can only add a maximum of " + maxDropSongs + " songs at a time." );
                        return;
                    }
                    
                    AG.PlayerControl.queueSongs( songIDList.join(','), cfIDList.join(','), false, true, ui.offset ); 
                    AG.Track.record(AG.Track.Cat.DragDrop,AG.Track.Act.DDSongs);
                }
                else if( $("#draggingContainer").hasClass("hasArtists") ){
                    
                    var artistIDList = [];
                    $("#draggingContainer .artistRowDraggable").each( function() {
                        var artistID = getResourceID( $(this).attr('id') );
                        if( artistID !== undefined || artistID !== '' ){
                            artistIDList.push(artistID);
                        }
                    });

                    if( artistIDList.length > maxDropArtists ) {
                        alert( "Sorry, you can only add a maximum of " + maxDropArtists + " artists at a time." );
                        return;
                    }
                    
                    AG.PlayerControl.queueSongsByArtists( artistIDList.join(','), "", false, ui.offset );
                    AG.Track.record(AG.Track.Cat.DragDrop,AG.Track.Act.Artists);
                }
                else if( $("#draggingContainer").hasClass("hasPlaylists") ){
                    
                    var plsIDList = [];
                    $("#draggingContainer .plsRowDraggable").each( function() {
                        var plsID = getResourceID( $(this).attr('id') );
                        if( plsID !== undefined || plsID !== '' ){
                            plsIDList.push(plsID);
                        }
                    });

                    if( plsIDList.length > maxDropPlaylists ) {
                        alert( "Sorry, you can only add a maximum of " + maxDropPlaylists + " playlists at a time." );
                        return;
                    }
                    
                    var plsName = '';
                    var plsTags = '';
                    if( plsIDList.length === 1 ){
                        plsName = $("#plsName_" + plsIDList[0], "#draggingContainer").text();
                        plsTags = $("#plsTags_" + plsIDList[0], "#draggingContainer").text();
                        playlistLoaded = plsIDList[0];
                        currentPlaylistTags = plsTags;
                    }

                    queuePlaylistsInternal( plsIDList.join(','), plsName, plsTags, false, ui.offset );
                    AG.Track.record(AG.Track.Cat.DragDrop,AG.Track.Act.DDPlaylists);
                }
            
            }
        });
    }

    function addPlaceholderRow( offset ){
        if( placeholderRow === null ) {
          
            var added = false;
            if( offset !== undefined && offset !== null ) {
                var closestElements = findClosestElements( offset );
                if( closestElements.previousElement !== null ){
                    placeholderRow = $(placeholderRowHtml).insertAfter($(closestElements.previousElement));
                    added = true;
                }
                else if ( closestElements.nextElement !== null ) {
                    placeholderRow = $(placeholderRowHtml).insertBefore($(closestElements.nextElement));
                    added = true;
                }
            }

            if ( !added ) {
                placeholderRow = $(placeholderRowHtml).appendTo( $("#nowPlayingList") );
            }
        }
    }

    function removePlaceholderRow(){
        if( placeholderRow !== null ){
            placeholderRow.remove();
            placeholderRow = null;
        }
    }

    function isSongOnline( songClass ) {
        return ( songClass.indexOf('Missing') === -1 );
    }

    function setMissingSongs() {
        $(".nowPlayingRow").removeClass("nowPlayingRow").addClass("missingRow");
    }

    function startRandomSongs() {
        AG.PlayerControl.stopSong( true );
        $("#shuffleSong").hide();
        $("#repeatSongs").hide();
        playingRandom = true;
        $("#toggle_shuffle_all").text("Stop Collection Shuffle");
        fetchRandomSongs( function() {
            clearAllInternal( "Shuffling Collection", true );
            setHeaderText( "Shuffling Collection" );
            playNextRandom();
        });
    }

    function fetchRandomSongs( onFetchSucceeded ) {
        
        AG.Collection.queryCollection( { randoms: numRandomsPerFetch }, function( success, songList ) {
            if( success ) {
                randomSongs = songList;
                onFetchSucceeded();
            }
            else {
                stopRandomSongs();
            }
        });
    }

    function stopRandomSongs() {
        AG.PlayerControl.stopSong( true );
        randomSongs = [];
        $("#toggle_shuffle_all").text("Shuffle Collection");
        playingRandom = false;
        setHeaderText( defaultHeader );
        $("#shuffleSong").show();
        $("#repeatSongs").show();
    }

    function playNextRandom(){
        
        // first check if a song is already queued
        // could have been queue'd by the user
        var nextSongToPlay = $(".activeSongRow").nextAll('.nowPlayingRow:visible');
        if( nextSongToPlay.length !== 0 ) {
            nextSongID = getHtmlID( nextSongToPlay.attr('id') );
            if( nextSongID !== null ) {
                AG.PlayerControl.toggleSongInList( nextSongID );                   
                return;
            }
        }

        // else, play a random song
        if( randomSongs.length > 0 ) {
            var songInfo = randomSongs[0];
            randomSongs.remove(0);
            addSongToList( songInfo.sid, songInfo.s, songInfo.a, songInfo.sClass, songInfo.playTimeSecs, songInfo.albumName, songInfo.img, songInfo.bitRate, songInfo.encType, songInfo.cfID, true, false, null, false );

            if( randomSongs.length === 0 ) {
                fetchRandomSongs( function(){} );
            }
        }
        else {
            stopRandomSongs();
        }
    }

    function checkMissingSongs() {
        
        var missingSongs = {};
        var songIDs = [];
        var cfIDs = [];
        $(".missingRow").each( function() {
                var htmlID = $(this).attr('id');
                var songID = getSongIDFromID(htmlID);
                var cfID = getCFIDFromID(htmlID);
                if( songID !== undefined && songID !== '' && cfID !== undefined && cfID !== '' ) {
                    if( missingSongs[songID] === undefined ) { 
                        missingSongs[songID] = [];
                    }
                    missingSongs[songID].push(htmlID);
                    songIDs.push(songID);
                    cfIDs.push(cfID);
                }
            });
        
        AG.Collection.queryCollection( { songIDs: songIDs.join(','), cfIDs: cfIDs.join(',') }, function( success, songList ) {
            if( success ){
                for( var i = 0; i < songList.length; i++ ) { 
                    var songInfo = songList[i];
                    if( isSongOnline( songInfo.sClass ) ) {
                        for( var j = 0; j < missingSongs[songInfo.sid].length; j++ ) {
                            var elID = missingSongs[songInfo.sid][j];
                            $("#" + elID).removeClass("missingRow").addClass("nowPlayingRow");
                            $("#npButton" + getHtmlID( elID )).addClass(songInfo.sClass);
                        }
                    }
                }
            }
        });
    }

    function addSongToList( songID, songName, artistName, songClass, playTimeSecs, albumName, imgUrl, bitRate, encType, cfID, playNow, recordAction, offset, isAutoLoad ){
        
        AG.Misc.closeSaveWindow();
        var htmlID = songCounter++ + '_' + songID + '_' + cfID;
        
        var title = songName + ' - ' + artistName;
        var songOnline = isSongOnline(songClass);
        var rowClass = songOnline ? "nowPlayingRow" : "missingRow";
        var buttonClass = songOnline ? songClass : "";
        var html = '<tr class="' + rowClass + ' pointer" id="np_' + htmlID + '">' +
                   '<td id="npButton' + htmlID + '" class="middle ' + songClass + '"></td>' +
                   '<td onclick="AG.PlayerControl.toggleSongInList(\'' + htmlID + '\', this);"><div class="songInPls" title="' + title + '" ><span id="sLink_' + htmlID + '">' + songName + '</span> - <span id="aLink_' + htmlID + '">' + artistName + '</span></div></td>' +
                   '<td class="pointer nosortable trashSong" title="Remove" onclick="AG.PlayerControl.removeSongFromList(\'' + htmlID + '\'); return false;"><img class="middle" src="/images/misc/trashMenu.png"/></td>' +
                   '<td style="display: none;">' + 
                      '<span id="playTime_' + htmlID + '">' + playTimeSecs + '</span>' +
                      '<span id="album_' + htmlID + '">' + albumName + '</span>' +
                      '<span id="imgUrl_' + htmlID + '">' + imgUrl + '</span>' +
                      '<span id="bitRate_' + htmlID + '">' + bitRate + '</span>' +
                      '<span id="enc_' + htmlID + '">' + encType + '</span>' + 
                   '</td></tr>';
        
        if( placeholderRow !== null ) {
            $(html).insertBefore( placeholderRow );
        }
        else {
            $(html).appendTo($("#nowPlayingList"));
        }
        
        $("#np_" + htmlID).effect("highlight",{},1500);

        updateNumSongsInList( 1 );
        updatePlaylistTime(true, playTimeSecs);

        fixupNewHtml();
       
        if( recordAction === undefined || recordAction) {
            addToUndoStack( { action: ActionType.Add, id: htmlID, offset: offset } );
        }
        if( !isAutoLoad && ( currentHtmlID === null || playNow ) ){
            AG.PlayerControl.toggleSongInList( htmlID ) ;
        }
        
        $("#savePlsOptionEnabled, #plsInfo").show();
        $("#savePlsOptionDisabled").hide();

    }

    function pauseSong( htmlID ) {
        if( state === PlayerState.Playing ) {
            soundObj.pause();
            state = PlayerState.Paused;
            
            flipToPlay();
            AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.Pause);
        }
    }
    
    function resetNumSongsInList() {
        numSongsInList = 0;
        $("#plsNumSongs").text(numSongsInList);
        
        //$("#nowPlayingPane").hide();
        //$("#plsOptionsPane").hide();
        $("#playerTip").show();
        AG.PlayerControl.saveUnsavedSongs();
    }

    function updateNumSongsInList( delta ) {

        if( numSongsInList === 0 ) {
            $("#playerTip").hide();
            //$("#nowPlayingPane").show();
            //$("#plsOptionsPane").show();
            computeOffsets();
        }
        numSongsInList += delta;
        $("#plsNumSongs").text(numSongsInList);

        if( numSongsInList === 0 ) {
            resetNumSongsInList();
        }
    }
    
    function queryCollectionAndQueue( query, title, clearExisting, playNow, recordAction, offset, isAutoLoad ) {
        if( clearExisting ) {
            AG.PlayerControl.clearAllSongs( title, true );
        }

        if( numSongsInList === 0 ){
            setHeaderText( title );
            //$("#nowPlayingPane").show();
        }
       
        addPlaceholderRow( offset );
        AG.Collection.queryCollection( query, function( success, songList ) {
            if( success ){
                for( var i = 0; i < songList.length; i++ ) { 
                    var songInfo = songList[i];
                    addSongToList( songInfo.sid, songInfo.s, songInfo.a, songInfo.sClass, songInfo.playTimeSecs, songInfo.albumName, songInfo.img, songInfo.bitRate, songInfo.encType, songInfo.cfID, playNow, recordAction, offset, isAutoLoad );
                }
               
                if( recordAction && songList.length > 0 ) {
                    addToUndoStack( {action: ActionType.QueryCollection, name: title, clear: clearExisting, query: query, numSongs: songList.length, offset: offset } );
                }
            }
            removePlaceholderRow();
            
            // if no songs got added
            if( numSongsInList === 0 ){
                //$("#nowPlayingPane").hide();
            }
            else if( !startSavingUnsavedSongs ) {
                startSavingUnsavedSongs = true;
                AG.PlayerControl.saveUnsavedSongs(); 
            }
        });
    }

    function queuePlaylistsInternal( playlistIDs, playlistName, playlistTags, clearExisting, offset ) {
        queryCollectionAndQueue( { playlistIDs: playlistIDs }, playlistName, clearExisting, false, true, offset, false );
    }

    function doScroll( direction, target, duration ) {
        
        scrolling = true;
        var numLoops = 0;
        $(".tempRow").remove();
        $("#nowPlayingPane").scrollTo(
            direction + target + "px", { axis: 'y', duration: duration, queue: false, easing: 'swing', onAfter: function() { 
                scrolling = false;
                if( scrollList === true ) {
                    //duration = duration > 200 ? duration -= 200 : duration;
                    numLoops++;
                    if( numLoops % 2 === 0 ) {
                        target += 5;
                    }
                    doScroll( direction, target, duration );
                }
            } 
        });
    }

    function computeOffsets(){
        paneOffset = $("#nowPlayingPane").offset();
        paneOffset.rightMax = paneOffset.left + $("#nowPlayingPane").outerWidth();
        paneOffset.bottomMax = paneOffset.top + $("#nowPlayingPane").outerHeight();
        
        scrollDownOffset = $(".scrollDownPane").offset();
        scrollDownOffset.rightMax = scrollDownOffset.left + $(".scrollDownPane").outerWidth();
        scrollDownOffset.bottomMax = scrollDownOffset.top + $(".scrollDownPane").outerHeight();
        
        scrollUpOffset = $(".scrollUpPane").offset();
        scrollUpOffset.rightMax = scrollUpOffset.left + $(".scrollUpPane").outerWidth();
        scrollUpOffset.bottomMax = scrollUpOffset.top + $(".scrollUpPane").outerHeight();
    }

    function resetSongTimes(){
        $("#songTimeTotal").text("0:00");
        $("#songTimeElapsed").text("0:00");
    }

    function resetPlaylistTime(){
        $("#plsPlaytime").text("0:00 minutes");
        currentPlsPlayTimeSecs = 0;
    }

    function updatePlaylistTime( add, playTimeSecs ){
        if( playTimeSecs === undefined || isNaN( playTimeSecs ) ) {
            return; // for missing songs
        }

        if( add ) {
            currentPlsPlayTimeSecs += playTimeSecs;
        }
        else {
            currentPlsPlayTimeSecs -= playTimeSecs;
        }

        var plsPlaytimeStr = AG.Misc.getTimeStringFromSec( currentPlsPlayTimeSecs, true );
        $("#plsPlaytime").text(plsPlaytimeStr);
    }

    function setHeaderText( headerText ) {
        if( headerText === undefined || headerText === "" ) {
            headerText = defaultHeader;
        }
    
        currentHeaderText = headerText;
        if( headerText.length > 30 ) {
            headerText = headerText.substring(0, 27) + '...';
        }
        $("#playerTitle").text(headerText).attr('title', currentHeaderText);
    }

    function clearAllInternal( headerText, recordAction ) {
        if( numSongsInList === 0 ){
            return;
        }

        if( recordAction === undefined ) {
            recordAction = true;
        }

        AG.Misc.closeSaveWindow();
        AG.PlayerControl.stopSong( true );
        var songList = $("#nowPlayingList").html();
        if( recordAction === undefined || recordAction ) {
            addToUndoStack( { action: ActionType.Clear, songList: songList, numSongs: numSongsInList, plsPlaytimeSec: currentPlsPlayTimeSecs } );
        }

        $(".nowPlayingRow").remove();
        $("#nowPlayingList").html('');
        playedSongs = [];
        playlistLoaded = 0;
        currentPlaylistTags = '';
        resetNumSongsInList(); 
        setHeaderText( headerText );
        resetPlaylistTime();

        $("#savePlsOptionEnabled, #plsInfo").hide();
        $("#savePlsOptionDisabled").show();
    }

    function getExtensionFromID( htmlID ) {
        var encType = $("#enc_" + htmlID ).html();
        if( encType === AG.Constants.ENCTYPE_AAC ) {
            return ".m4a";
        }
            
        return ".mp3";
    }

    return {

        init: function(){
            
            $("#dialog_flash_needed").dialog({ 
                bgiframe: true,
                autoOpen: false,
                height: 'auto',
                width: 350,
                modal: true 
            });
            
            setHeaderText();
            resetPlaylistTime();
            $("#songSlider").slider( 
                    { 
                      min: 0,
                      max: 100,
                      value: 0,
                      minConstraint: 0,
                      maxConstraint: 0,
                      enforceConstraints: true,
                      stop: AG.PlayerControl.handleSongPositionSeek,
                      start: AG.PlayerControl.startSongPositionSlide,
                      slide: AG.PlayerControl.handleSongPositionSlide
                    }            
                );

            var cookieVolume = AG.Misc.getCookie( AG.Constants.VolumeCookieName );
            if( cookieVolume !== "" ) {
                volume = cookieVolume;
            }
            
            $("#volSlider").slider(
                    { 
                      range: 'min',
                      max: 100,
                      value: volume,
                      enforceConstraints: false,
                      slide: AG.PlayerControl.handleVolumeChange
                    }
                );
            setVolumeClass(volume);
            $(document).keydown(handleKeyDown);
            enableDropZones();
            makeSortable();
            computeOffsets();
            $(window).resize( function() {
                computeOffsets();
            });
            
            flipToPlay();
        },

        setHelperOnline: function( helperOnline ){
            isOnline = helperOnline;
            if( !isOnline ) {
                AG.PlayerControl.stopSong( true );
                stopRandomSongs();
                setMissingSongs();
            }
            else {
                checkMissingSongs();
            }
        },

        handleVolumeChange: function(event, ui){
            
            volume = ui.value;
            if( soundObj !== null ){
                soundObj.setVolume( volume );
            }
            
            AG.Misc.setCookie( AG.Constants.VolumeCookieName, volume, null );
            setVolumeClass(volume);
        },

        startSongPositionSlide: function(event, ui){
            sliding = true;
        },

        handleSongPositionSlide: function(event, ui){
            if( soundObj !== null && state === PlayerState.Playing ) {
                var newPosition = Math.round( ( ui.value * soundObj.durationEstimate ) / 100 );
                $("#songTimeElapsed").text(AG.Misc.getTimeStringFromMSec(newPosition, false) );
            }
        },

        handleSongPositionSeek: function(event, ui){
            sliding = false;
            
            if( soundObj !== null && state === PlayerState.Playing ) {
                var newPosition = Math.round( ( ui.value * soundObj.durationEstimate ) / 100 );
                soundObj.setPosition( newPosition );
                AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.Seek);
            }
        },

        isCurrentlyPlaying: function(){
            return state === PlayerState.Playing;      
        },

        currentCFID: function() {
            if( AG.PlayerControl.isCurrentlyPlaying() ) {
                return getCFIDFromID(currentHtmlID);
            }

            return '';
        },
        
        stopSong: function( forceStop ){
            if( currentHtmlID !== null ) {
                flipToPlay();
            }

            if( soundObj !== null ) {
                soundObj.stop();
                soundObj.destruct();
               
                resetSongTimes();
                $("#songSlider").slider('option', 'value', 0 );
                $("#currentSongInfo").hide();
            }
             
            soundObj = null;
            currentHtmlID = null;
            state = PlayerState.Ready;
            
            if( !forceStop && onFinishedCB !== null && jQuery.isFunction(onFinishedCB) ){
                onFinishedCB();
            }
        },

        selectAsActive: function(htmlID){
            $(".nowPlayingRow").removeClass("activeSongRow").css('background-color', '' );
            $("#np_" + htmlID).addClass("activeSongRow");
            if( !scrolling ) {
                $("#nowPlayingPane").scrollTo("#np_" + htmlID, { easing: 'swing', duration: 1000, offset: { top: -130, left: 0}, margin: true });
            }
        },

        playSong: function( htmlID, callback ) {
            
            if ( !isOnline ) {
                alert( "Your Audiogalaxy Helper is not running on any computer. Please start the Helper or install it by downloading it from your Settings page" );
                AG.PlayerControl.stopSong( true );
                return;
            }
            

            if( !soundManager.supported() ) {
                $("#dialog_flash_needed").dialog('open');
            }
            else {

                if( htmlID === undefined ){
                    // if paused, re-start the song. Else, pick the next in the list if there is one
                    if( state === PlayerState.Paused ){
                        htmlID = currentHtmlID;
                    }
                    else {
                        if( shuffleEnabled ){
                            htmlID = getNextSongToPlay();
                        }
                        else {
                            var firstSong = getFirstPlayableSongObj(); 
                            if( firstSong.length > 0 ) {
                                htmlID = getHtmlID( firstSong.attr('id') );
                            }
                        }
                    }
                }
                
                if( htmlID === undefined ) {
                    return;
                }

                var songID = getSongIDFromID(htmlID);
                var ext = getExtensionFromID(htmlID);
                var cfID = getCFIDFromID(htmlID); 
                
                // if still undefined, there's no song to play. return;
                if (songID === undefined || songID === null || songID === '' ) {
                    return;
                }
               
                var songUrl = "";
                if( localhostRunning ) {
                    songUrl = "http://127.0.0.1:" + localListenPort + "/" + songID + ext + "?agsalt=" + sessionSalt + "&cfID=" + cfID;
                }
                else {
                    songUrl = userUrlBase + "songs/" + songID + "/play" + ext + "?cfID=" + cfID;
                }

                var loadAttempts = 0;
                
                if( state === PlayerState.Paused && soundObj !== null ) {
                    soundObj.resume();
                }
                else {
                    soundObj = soundManager.createSound( {
                        id: 'mySong' + htmlID,
                        url: songUrl,
                        stream: true,
                        volume: volume,
                       
                        whileloading: function(){
                            if( this.bytesLoaded === 0 ){
                                loadAttempts++;
                            }
                            if( loadAttempts > 30 ){
                                AG.PlayerControl.stopSong( true );
                                alert("Unable to play your song. Please make sure that your Audiogalaxy Helper is running and connected to the internet");
                            }
                            else {
                                var newMax = Math.round( ( soundObj.bytesLoaded / soundObj.bytesTotal ) * 100 );
                                $("#songSlider").slider( 'constraints', [0, newMax] );
                            }
                        },
                        
                        whileplaying: function(){
                            // advance the slider
                            try {
                                if( !sliding ) {
                                    var totalDuration = soundObj.durationEstimate;
                                    var newPos = this.position / totalDuration;
                                    if( isNaN( totalDuration ) || isNaN( newPos ) ) {
                                        return;
                                    }
                                    
                                    $("#songTimeElapsed").text(AG.Misc.getTimeStringFromMSec(this.position, false));
                                    $("#songSlider").slider('value', Math.round( newPos * 100 ) );
                                }
                            }
                            catch(e){   // TODO - remove
                                alert(e);
                            }
                        },

                        onfinish: function(){
                            AG.PlayerControl.stopSong( false );
                        }
                    } );
        
                    if( callback !== undefined ) {
                        onFinishedCB = callback;
                    }
                    else {
                        onFinishedCB = playNextSong;
                    }

                    soundObj.play();
                    AG.PlayerControl.selectAsActive(htmlID);
                    currentHtmlID = htmlID; 
                  
                    var currentSong = $("#sLink_" + htmlID).html();
                    $("#currentSongName").html(currentSong).attr('title', currentSong);
                    var currentArtist = $("#aLink_" + htmlID).html();
                    $("#currentArtistName").html(currentArtist).attr('title', currentArtist);
                    var currentBitRate = $("#bitRate_" + htmlID).html() + ' kbps';
                    $("#currentBitRate").html(currentBitRate).attr('title', currentBitRate);
                    var currentAlbum = $("#album_" + htmlID ).html();
                    $("#currentAlbumName").html(currentAlbum).attr('title', currentAlbum);
                    var art = $("#imgUrl_" + htmlID ).text();
                    if( art === '' ) {
                        art = "/images/misc/defaultPlayerArt.png";
                    }
                    $("#currentSongArt img").attr('src', art);
                    $("#currentSongArt").show();
                    
                    recordSongPlayed( htmlID );
                }

                state = PlayerState.Playing; 
                flipToPause();
                $("#songTimeTotal").text( AG.Misc.getTimeStringFromSec( $("#playTime_" + htmlID).text(), false ) );
                $("#currentSongInfo").show();
            }
        },

        nextSong: function(){
            if( state !== PlayerState.Ready ) {
                AG.PlayerControl.stopSong( true ); 
            }
            
            playNextSong();
            AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.Next);
        },

        prevSong: function(){
            if( state !== PlayerState.Ready ) {
                AG.PlayerControl.stopSong( true );
            }

            playPreviousSong();
            AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.Prev);
        },

        pauseCurrentSong: function( songID, cfID ){
            if( getSongIDFromID(currentHtmlID) === songID && getCFIDFromID(currentHtmlID) === cfID ) {
                AG.PlayerControl.toggleSongInList(currentHtmlID);
            }
        },

        toggleSongInList: function( htmlID ){
            if( $("#np_" + htmlID).hasClass("missingRow") ) { 
                return; 
            }

            var el = "#npButton" + htmlID;

            if( $(el).hasClass("playbuttonSmall") ) {
                if( currentHtmlID !== htmlID ) {
                    AG.PlayerControl.stopSong( true );
                }
                AG.PlayerControl.playSong( htmlID, playNextSong );
            }
            else {
                pauseSong( htmlID );
            }
            if( state === PlayerState.Playing ) {
                $("#np_" + htmlID).addClass("playing");
            }
            else {
                $("#np_" + htmlID).removeClass("playing");
            }
        },

        clearAllSongs: function( headerText, recordAction ){
            clearAllInternal( headerText, recordAction );
            stopRandomSongs();
        },

        queueSongsByArtists: function( artistIDs, artistName, clearExisting, offset ) {
            queryCollectionAndQueue( { artistIDs: artistIDs }, artistName, clearExisting, false, true, offset, false );
            AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.PlayArtist);
        },
        
        queuePlaylist: function( playlistID, playlistName, playlistTags, clearExisting, offset ) {
            queuePlaylistsInternal( playlistID, playlistName, playlistTags, clearExisting, offset );
            if( numSongsInList === 0 ) {
                playlistLoaded = playlistID;
                currentPlaylistTags = playlistTags;
            }
            AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.PlayPlaylist);
        },

        queueSongs: function( songIDs, cfIDs, playNow, recordAction, offset ){
            queryCollectionAndQueue( { songIDs: songIDs, cfIDs: cfIDs }, "", false, playNow, recordAction, offset, false );
            AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.PlaySongs);
        },

        queueSong: function( songID, cfID, playNow, recordAction, offset ) {
            
            // if paused and this is the current song, just play it (for controls on the collection view)
            if( state === PlayerState.Paused && getSongIDFromID(currentHtmlID) === songID && getCFIDFromID(currentHtmlID) === cfID ){
                AG.PlayerControl.toggleSongInList( currentHtmlID );
                return;
            }
            
            AG.PlayerControl.queueSongs( songID, cfID, playNow, recordAction, offset );
        },

        removeSongFromList: function( htmlID, recordAction ) {
            
            AG.Misc.closeSaveWindow();
            if( currentHtmlID === htmlID ) {
                AG.PlayerControl.stopSong( false );
            }
            
            var playTimeSecs = parseInt( $("#playTime_" + htmlID ).text(), 10 );
            var idToRemove = $("#np_" + htmlID);
            var offset = idToRemove.offset();

            var temp = $("<div/>").append( idToRemove.clone() );
            var html = temp.html();
            idToRemove.remove();

            if(recordAction === undefined || recordAction ) {
                addToUndoStack( { action: ActionType.Remove, id: htmlID, html: html, playTimeSecs: playTimeSecs, offset: offset } );
            }
            updateNumSongsInList( -1 ); 
            updatePlaylistTime( false, playTimeSecs );

            if( numSongsInList === 0 ){
                $("#savePlsOptionEnabled, #plsInfo").hide();
                $("#savePlsOptionDisabled").show();
                setHeaderText();
            }
            AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.RemoveSong);
        },

        toggleShuffle: function(){

            shuffleEnabled = !shuffleEnabled;
            if( shuffleEnabled ){
                $("#shuffleSong").addClass("shuffleSongEnabled");
                AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.Shuffle,AG.Track.Label.On);
            }
            else {
                $("#shuffleSong").removeClass("shuffleSongEnabled");
                AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.Shuffle,AG.Track.Label.Off);
            }
        },
   
        toggleRepeat: function(){
            var wasEnabled = repeatEnabled;
            repeatEnabled = !repeatEnabled;
            if( repeatEnabled ){
                $("#repeatSongs").addClass("repeatSongsEnabled");
                AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.Repeat,AG.Track.Label.On);
            }
            else{
                $("#repeatSongs").removeClass("repeatSongsEnabled");
                AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.Repeat,AG.Track.Label.Off);
            }
        },

        saveAsPlaylist: function(el){
           
            AG.Misc.closeSaveWindow(); 
            if (numSongsInList > maxSongsInPlaylist ) {
                alert( "Sorry, you can only create playlists with a maximum of " + maxSongsInPlaylist + " songs. You currently have " + numSongsInList + "." );
                return;
            }

            var songIDList = [];
            var cfIDList = [];
            $("#nowPlayingList").find("[id^=np_]").each( function(){
                songIDList.push( getSongIDFromID($(this).attr('id')) ); 
                cfIDList.push( getCFIDFromID($(this).attr('id')) );
            });

            if(songIDList.length) {
                // it's always a new playlist. Users edit Playlists from the playlist songs pane
                AG.Misc.startSavePlaylist( el, 0, true, songIDList, cfIDList, 0, currentPlsPlayTimeSecs );
                AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.SavePls);
            }
        },

        loadUnsavedSongs: function(){
            // restore previous songs, if any
            if( numSongsInList === 0 || !playingRandom ) {
                queryCollectionAndQueue( { unsavedSongs: 1 }, "", false, false, true, null, true );
            }
        },
    
        saveUnsavedSongs: function() {

            if( !startSavingUnsavedSongs ) {
                return;
            }

            var url = userUrlBase + "playlists/?";
            
            var songIDList = [];
            var cfIDList = [];
            $("#nowPlayingList").find("[id^=np_]").each( function(){
                var songID = getSongIDFromID($(this).attr("id")); 
                var cfID = getCFIDFromID($(this).attr("id"));
                songIDList.push(songID);
                cfIDList.push(cfID);
            });

            $.post( encodeURI( url ), { unsavedSongs: songIDList.join(','), unsavedCFIDs: cfIDList.join(',') } );
        },

        setLocalhostRunning: function( isLocalhostRunning ) {
            localhostRunning = isLocalhostRunning;
        },

        undoLastAction: function( allowRedo ){
            if( undoStack.length ){
                var obj = undoStack.pop();
                if( !undoStack.length ){
                    $("#undoEnabled").hide();
                    $("#undoDisabled").show();
                }

                switch ( obj.action ){
                    case ActionType.Add:
                        AG.PlayerControl.removeSongFromList( obj.id, false );
                        songCounter--;
                        break;
                    
                    case ActionType.Remove:
                        restoreSingleSong( obj.html, obj.playTimeSecs, obj.offset );
                        break;

                    case ActionType.Clear:
                        restoreClearedSongs( obj.songList, obj.numSongs, obj.plsPlaytimeSec );
                        break;

                    case ActionType.QueryCollection:
                        for( var i = 0; i < obj.numSongs; i++ ){
                            AG.PlayerControl.undoLastAction( false );
                        }
                        if( obj.clear ){
                            AG.PlayerControl.undoLastAction( false );
                        }
                        break;

                    case ActionType.Reorder:
                        moveElement( obj.id, obj.origOffset );
                        break;

                    case ActionType.ToggleRandom:
                        AG.PlayerControl.toggleRandom(false);
                        if( playingRandom === false ) {
                            AG.PlayerControl.undoLastAction(false); // restore previous songs
                        }
                        break;
                }
                
                if( allowRedo ) {
                    addToRedoStack( obj );
                }
                AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.Undo);
            } 
        },

        redoLastUndo: function(){
            if( redoStack.length ){
                var obj = redoStack.pop();
                if( !redoStack.length ){
                    $("#redoEnabled").hide();
                    $("#redoDisabled").show();
                }
                
                switch ( obj.action ){
                    case ActionType.Remove:
                        AG.PlayerControl.removeSongFromList( obj.id, true );
                        break;
                    
                    case ActionType.Add:
                        AG.PlayerControl.queueSong( getSongIDFromID(obj.id), getCFIDFromID(obj.id), false, true, obj.offset );
                        break;

                    case ActionType.Clear:
                        AG.PlayerControl.clearAllSongs( defaultHeader, true );
                        break;

                    case ActionType.QueryCollection:
                        queryCollectionAndQueue( obj.query, obj.name, obj.clear, false, true, obj.offset, false );
                        break;

                    case ActionType.Reorder:
                        moveElement( obj.id, obj.newOffset );
                        addToUndoStack( obj );
                        break;

                    case ActionType.ToggleRandom:
                        AG.PlayerControl.toggleRandom(true);
                        break;
                }
                
                AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.Redo);
            } 
        },

        checkForDraggingOverPlayer: function( offset ) {
            var closestElements = findClosestElements( offset );

            // clear existing temp rows incase we've moved out
            if( closestElements.previousElement === null && closestElements.nextElement === null ) {
                $(".tempRow").remove();
            }

            // case 1 -- scroll up or down
            if( !closestElements.scrollUp && !closestElements.scrollDown ) {
                scrollList = false;
            }
            else if ( closestElements.scrollUp && !scrolling ) {
                scrollList = true;
                doScroll( "-=", "50", 200 );
                return;
            }
            else if ( closestElements.scrollDown && !scrolling ) {
                scrollList = true;
                doScroll( "+=", "50", 200 );
                return;
            }

            // now check if we're in bounds and have a closest row
            var tempRow; 
            if( closestElements.previousElement !== null ){
                prevId = 'p' + $(closestElements.previousElement).attr('id');
                if( $(".tempRow." + prevId).length ){
                    // already exists, do nothing
                    return;
                }
                
                $(".tempRow").remove();
                tempRow = $(dropLocationHtml);
                tempRow.addClass(prevId);
                tempRow.insertAfter($(closestElements.previousElement));
            }
            else if ( closestElements.nextElement !== null ) {
                nextId = 'n' + $(closestElements.nextElement).attr('id');
                if( $(".tempRow." + nextId).length ){
                    // already exists, do nothing
                    return;
                }

                $(".tempRow").remove();
                tempRow = $(dropLocationHtml);
                tempRow.addClass(nextId);
                tempRow.insertBefore($(closestElements.nextElement));
            }
            else if( closestElements.insidePane ) {
                nextId = 'last';
                if( $(".tempRow." + nextId).length ){
                    // already exists, do nothing
                    return;
                }

                $(".tempRow").remove();
                tempRow = $(dropLocationHtml);
                tempRow.addClass(nextId);
                tempRow.appendTo( $("#nowPlayingList") );
            }
        },

        toggleRandom: function( recordAction ) {
            if( !playingRandom ) {
                startRandomSongs(); 
            }
            else {
                stopRandomSongs();
            }
                
            if( recordAction ) {
                addToUndoStack( { action: ActionType.ToggleRandom } );
                AG.Track.record(AG.Track.Cat.Player,AG.Track.Act.CollShuffle);
            }
        }
 
    };
}();

/**
 * jQuery.ScrollTo - Easy element scrolling using jQuery.
 * Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 * Dual licensed under MIT and GPL.
 * Date: 5/25/2009
 * @author Ariel Flesler
 * @version 1.4.2
 *
 * http://flesler.blogspot.com/2007/10/jqueryscrollto.html
 */
;(function(d){var k=d.scrollTo=function(a,i,e){d(window).scrollTo(a,i,e)};k.defaults={axis:'xy',duration:parseFloat(d.fn.jquery)>=1.3?0:1};k.window=function(a){return d(window)._scrollable()};d.fn._scrollable=function(){return this.map(function(){var a=this,i=!a.nodeName||d.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!i)return a;var e=(a.contentWindow||a).document||a.ownerDocument||a;return d.browser.safari||e.compatMode=='BackCompat'?e.body:e.documentElement})};d.fn.scrollTo=function(n,j,b){if(typeof j=='object'){b=j;j=0}if(typeof b=='function')b={onAfter:b};if(n=='max')n=9e9;b=d.extend({},k.defaults,b);j=j||b.speed||b.duration;b.queue=b.queue&&b.axis.length>1;if(b.queue)j/=2;b.offset=p(b.offset);b.over=p(b.over);return this._scrollable().each(function(){var q=this,r=d(q),f=n,s,g={},u=r.is('html,body');switch(typeof f){case'number':case'string':if(/^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(f)){f=p(f);break}f=d(f,this);case'object':if(f.is||f.style)s=(f=d(f)).offset()}d.each(b.axis.split(''),function(a,i){var e=i=='x'?'Left':'Top',h=e.toLowerCase(),c='scroll'+e,l=q[c],m=k.max(q,i);if(s){g[c]=s[h]+(u?0:l-r.offset()[h]);if(b.margin){g[c]-=parseInt(f.css('margin'+e))||0;g[c]-=parseInt(f.css('border'+e+'Width'))||0}g[c]+=b.offset[h]||0;if(b.over[h])g[c]+=f[i=='x'?'width':'height']()*b.over[h]}else{var o=f[h];g[c]=o.slice&&o.slice(-1)=='%'?parseFloat(o)/100*m:o}if(/^\d+$/.test(g[c]))g[c]=g[c]<=0?0:Math.min(g[c],m);if(!a&&b.queue){if(l!=g[c])t(b.onAfterFirst);delete g[c]}});t(b.onAfter);function t(a){r.animate(g,j,b.easing,a&&function(){a.call(this,n,b)})}}).end()};k.max=function(a,i){var e=i=='x'?'Width':'Height',h='scroll'+e;if(!d(a).is('html,body'))return a[h]-d(a)[e.toLowerCase()]();var c='client'+e,l=a.ownerDocument.documentElement,m=a.ownerDocument.body;return Math.max(l[h],m[h])-Math.min(l[c],m[c])};function p(a){return typeof a=='object'?a:{top:a,left:a}}})(jQuery);function ajaxifyLinks()
{
    var base = top.location.protocol + "//" + top.location.host + "/";

    $("a").each( function() {
        if( typeof this.href !== "undefined" &&
            this.href !== undefined &&
            this.rel !== 'noajax' && 
            this.href.length > base.length &&
            this.href.substring( 0, base.length ) === base ) {

            var relLink = this.href.substring( base.length );

            if( relLink.indexOf( '#' ) === -1 ) {
                $(this).attr("href", "#/" + relLink );
            }
        }
    } );
}

/* POPUPS/EVENTS
function emptyAndClosePopupBoxWithoutAck()
{
    $("#note_popup_contents").empty();
    $("#note_popup").hide();
}

function updateNumUnseenEvents( newNum )
{
    var prev = numUnseenEvents;
    numUnseenEvents = newNum;
    if( numUnseenEvents <= 0 )
    {
        $("#num_unseen_events").css( "display", "none" );
    }
    else
    {
        $("#num_unseen_events").html("(" + numUnseenEvents + ")" );
        $("#num_unseen_events").css( "display", "inline" );
    }

    if(prev !== numUnseenEvents) {
        var bracket = document.title.lastIndexOf(" (");
        if( numUnseenEvents <= 0 ) {
            if( bracket !== -1 && bracket > 1 ) {
                document.title = document.title.substring(0, bracket);
            }
        }
        else {
            var prefix = document.title;
            if( bracket !== -1 && bracket > 1 ) {
                prefix = document.title.substring(0, bracket);
            }

            document.title = prefix + " (" + numUnseenEvents + ")";
        }
    }
}
*/

function RestorePlayingCfIDs( type, str ) {
    if( str.length === 0 ) {
        return;
    }

    var ids = str.split(/,/);

    for( var i = 0; i < ids.length; i++ )
    {
        var sel = "#" + type + "_" + ids[i];
        toggleToStop( sel );
    }
}

// needs to be called after any new HTML is added to a page
function fixupNewHtml()
{

    $(".liveTime").prettyDate();
    $(".rounded").corners("10px");

    if( !$('#default_commentText').hasClass( 'markItUpEditor' ) ) {
        $('#default_commentText').markItUp(myMarkdownSettings);
    }

    if( !$('#pls_commentText').hasClass( 'markItUpEditor' ) ) {
        $('#pls_commentText').markItUp(myMarkdownSettings);
    }

                
    RestorePlayingCfIDs( "playCFID", AG.PlayerControl.currentCFID() );
    ajaxifyLinks();
}

function setContent( content ) {
    $("#loading_tag").hide();
    if( popupEvents === undefined || popupEvents === "-1" ) {  
        popupEvents = ""; // reset the var when changing from pages that disallow popups
    }

    //$("#content").empty(); //this may reduce memory usage over time.  Need some tests
    $("#content").html( content );
    $("#content").refreshFBML();
    fixupNewHtml();
    $("#mainpage").show();
    //scroll(0,0);
}

function createDummyCookie() {
    var date = new Date();
    date.setTime(date.getTime() + (2 * 60 * 1000)); 
    document.cookie = "dummy=AG; expires=" + date.toGMTString() + "; path=/";
}

function readDummyCookie() {
    var testName = "dummy=";
    var cookies = document.cookie.split(';');
    for (var i = 0; i < cookies.length; i++) {
        var c = cookies[i];
        while (c.charAt(0) == ' ') {
            c = c.substring(1, c.length);
        }
        if (c.indexOf(testName) === 0) {
            return c.substring(testName.length, c.length);
        }
    }

    return null;
}

function removeDummyCookie() {
    document.cookie = "dummy=0; expires=0; path=/";
}

function areCookiesEnabled() {
    var result = false;
    createDummyCookie();
    var cookie = readDummyCookie();
    if (cookie !== null && cookie === "AG") {
        result = true;
        removeDummyCookie();
    }

    return result;
}

$(document).ready( function() { 
    setInterval(function(){ $(".liveTime").prettyDate(); }, 1000);

    $("#mainpage").hide();
    fixupNewHtml();

    $(window).bind( 'hashchange', function(e) {
        hash = location.hash.replace( /^#/, '' );

        page = hash.replace( /\?.*/, '' );

        if( typeof e.originalEvent !== 'undefined' ) {
            // special cases
            if( page === '/music' && $("#collectionBody").length > 0 ) {
                $(".collection_block").hide();
                $("#collectionBody").show();
                return;
            }

            if( page === '/playlists' && $("#playlistBody").length > 0 ) {
                $(".collection_block").hide();
                $("#playlistBody").show();
                return;
            }

            if( page === '/search' && $("#filtered_coll").length > 0 ) {
                $(".collection_block").hide();
                $("#filtered_coll").show();
                return;
            }
        }

        $("#loading_tag").show();

        if( hash ) {
            $.ajax( { 
                type: "GET",
                url: encodeURI(hash), 
                dataType: "html",
                data: { wrapContent: false }, 
                success: function( data ) {
                    setContent( data );
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    if( XMLHttpRequest.status == 404 ) {
                        setContent( "<div style='padding:40px 0;' class='infoText'>Sorry -- the page you requested isn't there anymore.</div>" );
                    } else {
                        setContent( "Sorry -- there was an error loading this page." );
                    }
                }
             });
         } else {
            // this is what will happen when the user clicks back to a page with no hash.  
            $("#loading_tag").hide();
            $("#mainpage").show();
         }
    });


    // if we don't have a hash, the callback isn't called initially, so
    // we need show our main panel here.  This can happen if the user just types
    // the website into the url and doesn't include a hash (this used to happen on
    // login, but now they should be redirected to /#/)
    if( location.hash === "" ) {
        if( location.pathname.replace(/\?.*$/, '') == "/" && statusUrl !== "") {
            location.hash = AG.Constants.SignedInUserFront;
        }
        else {
            // show the current page
            $("#mainpage").show();
        }
    } else {
        $(window).trigger( 'hashchange' );
    }

    $.ajaxSetup({cache: false });

    $(document).ajaxSend( function( event, obj, options) { 
        obj.setRequestHeader("ajax", "1"); 
        obj.setRequestHeader("HeaderNonce", headerNonce);
    });
    
    $(document).ajaxComplete( function( event, obj, options) { 
        if( obj !== undefined && obj.status === 401 ) { 
            authFailed = true;
            if( !g_signingOut ) {
                alert( "You logged in/out of another location. Please login again" );
            }
            window.onbeforeunload = function(){};
            top.location.reload(); 
        } 
    });    


    $(".ag-button, .ag-button-small")
    .live('mouseenter', function(){ 
            $(this).addClass("ui-state-hover"); 
        })
    .live('mouseleave', function(){ 
            $(this).removeClass("ui-state-hover"); 
        })
    .live('mousedown', function(){
        $(this).addClass("ui-state-active"); 
    })
    .live('mouseup', function(){
            $(this).removeClass("ui-state-active");
    });
        
    $('input[placeholder]').placeholder();
    $("input[type='text'], textarea")
        .live('focus.ag', function() { 
            $(this).addClass('hasFocus'); } )
        .live('blur.ag', function() { 
            $(this).removeClass('hasFocus'); } );

});

AG.Search = function(){
    
        var timeout = null;
        var prev = "";
        var delayMS = 50;
        var curSearch = "";

        function startFilter( ) {

            if( curSearch.length <= 2 ) { return; }

            AG.Collection.queryCollectionForHtml( { q: curSearch }, function( success, html, queryParam ) {
                if( success && html !== undefined && queryParam.q === curSearch ) {
                    $("#searchLoading").hide();
                    $("#searchExplain").hide();
                    $("#filtered_songs").html( html );
                    AG.DragDrop.refreshSongsPane();
                }
            });
        }

        function onChange() {
            var v = $("#coll_q").val();

            if( v.length === 0 ) {
                $("#closeSearch").hide();
            } else {
                $("#closeSearch").show();
            }

            if( v.length === 2 ) {
                v += ' ';
            }

            curSearch = v;

            if( v.length <= prev.length ) {
                $("#filtered_songs").html('');
            }

            if( v.length > 0 && $("#filtered_songs").html().length === 0 ) {
                $("#searchExplain").text( "Searching your music" ).show();
                $("#searchLoading").show();
            }
           
            prev = v;
            if( v.length === 0 ) {
                $("#closeSearch").hide();
                $("#filtered_coll").hide();

                location.hash = "/music";
            }
            else if (v.length >= 2) {
                startFilter();
            } 
            else {
                $("#searchExplain").text( "Come on, give me a real search!" ).show();
            }
        }

        function setupKeyboardNav(){
            
            $("#coll_q").keydown(function(e) {
                hash = location.hash.replace( /^#/, '' );
                page = hash.replace( /\?.*/, '' );

                if( page !== "/search" ) {
                    location.hash = "/search";
                }

                // track last key pressed
                switch(e.keyCode) {
                    case 9:  // tab
                        return false;

                    case 27: //esc
                        $("#coll_q").val('');
                        $("#closeSearch").click();
                        return false;

                    case 13: // return
                        if (timeout) {
                            clearTimeout(timeout);
                        }
                        timeout = null;
                        onChange();
                        break;

                    default:
                        if (timeout) {
                            clearTimeout(timeout);
                        }
                        timeout = setTimeout(function(){onChange();}, delayMS);
                        break;
                }
            });

            AG.Misc.trapSpecialKeysForSelector( "#filtered_coll" );
            $(document).unbind('keydown.searchNav');
            $(document).bind('keydown.searchNav', function (e) {

                if( !$("#filtered_coll").is(":visible") ) {
                    return;
                }
                
                switch(e.keyCode) {
                    case 38:    // up
                    case 40:    // down
                        // if shift/ctrl/meta are pressed then ignore this. This causes conflict with the mouse click + shift/ctrl keys
                        if( AG.Misc.isSpecialKeyPressed() ){
                            return;
                        }

                        if( $(".songRow.ui-selected").length !== 1 ) {
                            // other rows selected, or no rows selected; so select the first. It'll clear out the others
                            $(".songRow:first .searchResultArtist", $("#filtered_songs")).trigger('click');
                            return false;
                        }
                        else {
                            var rowToSelect;
                            if( e.keyCode === 40 ) {
                                rowToSelect = $(".songRow.ui-selected").next(".songRow");
                            }
                            else {
                                rowToSelect = $(".songRow.ui-selected").prev(".songRow");
                            }

                            if( rowToSelect.length ) {
                                $(".searchResultArtist", rowToSelect).trigger('click.dragsong');
                                return false;
                            }
                        }
                        break;

                    case 13:    // enter
                        var selectedSongs = [];
                        var selectedCFIDs = [];
                        $(".songRow.ui-selected").each( function() {
                            selectedSongs.push(AG.Misc.extractID( this ) );
                            selectedCFIDs.push(AG.Misc.extractID2( this ) );
                        });
                        
                        if( selectedSongs.length ) {
                            AG.PlayerControl.queueSongs( selectedSongs.join(","), selectedCFIDs.join(","), false, true );
                            return false;
                        }
                        break;
                }
            });
        }


        return {
            
            init: function() {
                timeout = null;
                prev = "";
        
                $("#closeSearch").click(function() {
                    AG.Search.clearAndHideSearch();

                    location.hash = "/music";
                    return false;
                });

                $("#coll_q").focus( function() {
                    if( $("#coll_q").val().length > 0 ) {
                        hash = location.hash.replace( /^#/, '' );
                        page = hash.replace( /\?.*/, '' );

                        if( page !== "/search" ) {
                            location.hash = "/search";
                        }
                    }
                });

                setupKeyboardNav();
            },

            clearAndHideSearch: function() {
                $("#coll_q").val('');
                $("#closeSearch").hide();
                $("#filtered_coll").hide();
            }
            
        };
    
}();


$.fn.extend({
    refreshFBML: function(){
        return this.each( function() {
            AG.FBConnect.refreshML( this );
        });
    }
});

function connectWithFacebook(){
    
    AG.FBConnect.connectNow( function() {
        $.post( encodeURI( statusUrl ), { linkFBAccount: 1 }, function(data) {
            if( data !== undefined ){
                if( data.success === true ) {
                    if( data.newLink ) {
                        $("#dialog_fbConnectResult").html("Your account has been successfully linked with Facebook. You may now log into Audiogalaxy using your Facebook credentials");
                        $("#dialog_fbConnectResult").dialog("open");
                    }

                    $("#fbProfilePic").html(data.fbProfilePicLink);
                    $("#logoutLink").unbind('click');
                    $("#logoutLink").click( logoutFB );
                    $("#linkToFBSetting").hide();
                    $("#fbProfilePic").refreshFBML();
                }
                else if (data.errorMessage !== undefined ) {
                    $("#dialog_fbConnectResult").html(data.errorMessage);
                    $("#dialog_fbConnectResult").dialog("open");
                }

            }
        },
        'json');
    });
}

/*
function addPopupToPage( id, contents )
{
    $("#" + id ).remove();

    var html = '<div id="' + id + '" ' + 
        '>'+ 
        '<div class="contents">' + contents + '</div>' +
        '</div>';

    $("body").append( html );
}

function IgnoreUser( url, userID ) {
    $.post( encodeURI(statusUrl), { ignoredUserID: userID }, function(data) {
        pageload( url );
    }); 
}

function UnignoreUser( url, userID) {
    $.post( encodeURI(statusUrl), { unignoredUserID: userID }, function(data) {
        pageload( url );
    });
}
*/
 
function submitBrowseSetting(url, el){
    $("input[type=submit]", el).attr( "disabled", "disabled" ); 
    var allowBrowse = $("#allowBrowseCheckbox").attr('checked') ? "on" : "off";
    
    $.post( encodeURI(url), { allowBrowse: allowBrowse }, function(data){
        if(data === 'success') {
            $("input[type=submit]", el).removeAttr("disabled"); 
        }
    },
    "json");
}

function setupFavoritesToggle() {
    // set up any favoriting links
    $(".fave_toggle").each( function() {
        function setFaveOnServer( el, action )
        {
            // should look like fave_artist_$i
            var toks = $(el).attr("ID").split(/_/); 

            if( toks.length < 3 ) {
                return;
            }
 
            $.post( encodeURI(statusUrl), { 
                faveType: toks[1],
                faveID: toks[2],
                faveAction: action
                }, 
                function(data) {});
        }

        var save = function() {
            $(this).html( $("#fave_unsave_template").html() );
            $(this).one( "click", {}, unsave );
            setFaveOnServer( this, "fave" );
            return false;
        };

        var unsave = function() {
            $(this).html( $("#fave_save_template").html() );
            $(this).one( "click", {}, save );
            setFaveOnServer( this, "unfave" );
            return false;
        };

        if( $(this).hasClass( "fave" ) ) {
            $(this).html( $("#fave_unsave_template").html() );
            $(this).click( unsave );
        } else {
            $(this).html( $("#fave_save_template").html() );
            $(this).click( save );
        }
    });
}

function extractClassIDs( selector, classLabel )
{
    var keys = [];
    var ids = [];
    $( selector ).map( function() {
        var classes = $(this).attr("class").split(/ /); 
        for( var i = 0; i < classes.length; i++ ) {
            var toks = classes[i].split(/_/); 
            if( toks.length === 2 && toks[0] === classLabel ) {
                if( keys[ classes[i] ] === undefined ) {
                    keys[ classes[i] ] = true;
                    ids[ ids.length ] = toks[1];
                }
            }
        }
    });

    if( ids.length === 0 ) {
        return "";
    }

    return ids.join(",");
}

function isPlayable( sel ) {
    return ( $(sel).hasClass("playbutton") || $(sel).hasClass("playbuttonSmall") );
}

function isStopped( sel ) {
    return ( $(sel).hasClass("pausebutton") || $(sel).hasClass("pausebuttonSmall") );
}

function isQueued( sel ) {
    return ( $(sel).hasClass("qbutton") || $(sel).hasClass("qbuttonSmall") );
}

function toggleToStop( sel ) {
    var classString = $(sel).attr('class');
    if ( classString !== undefined && classString !== null ) {
        if( classString.indexOf("Small") === -1 ) {
            $(sel).addClass( "pausebutton" ).removeClass( "playbutton" ).attr( 'title', 'stop song' );
        }
        else {
            $(sel).addClass( "pausebuttonSmall" ).removeClass( "playbuttonSmall" ).attr( 'title', 'stop song' );
        }
    }
}

function toggleToPlay( sel ) {
    var classString = $(sel).attr('class');
    if ( classString !== undefined && classString !== null ) {
        if( classString.indexOf("Small") === -1 ) {
            $(sel).addClass( "playbutton" ).removeClass( "pausebutton" ).attr( 'title', 'play song' );
        }
        else {
            $(sel).addClass( "playbuttonSmall" ).removeClass( "pausebuttonSmall" ).attr( 'title', 'play song' );
        }
    }
}

function makePlayable( sel ) {
    var classString = $(sel).attr('class');
    if ( classString !== undefined && classString !== null ) {
        if( classString.indexOf("Small") === -1 ) {
            $(sel).addClass( "playbutton" ).attr( 'title', 'play song' );
            $(sel).removeClass( "qbutton" );
        }
        else {
            $(sel).addClass( "playbuttonSmall" ).attr( 'title', 'play song' );
            $(sel).removeClass( "qbuttonSmall" );
        }
    }
}


function lessSongsDrop(el) {
    var aID = AG.Misc.extractID( el );
    
    $(el).hide();
    $("#artist_" + aID + "_more").empty();
    $("#artist_" + aID + "_expand").show("");

    return false;
}

function moreSongsDrop(el) {
    var aID = AG.Misc.extractID( el );
    $(el).hide();
    $("#artist_" + aID + "_wait").show();

    var url = "/artists/" + aID + "/songs/?";

    var q = '';
    if( q === '' ) {
        q = window.location.hash.replace( /^.*q=/, '' );
        q = q.replace( /&.*$/, '' );
    }

    $.get( encodeURI(url), {form: "inline", q: q, offset: 1 }, function(data) {
        $("#artist_" + aID + "_more").empty();
        $("#artist_" + aID + "_more").html(data).slideDown('fast');
        $("#artist_" + aID + "_wait").hide();
        $("#artist_" + aID + "_collapse").show();
        fixupNewHtml();
    });

    return false;
}

function clearNewEvents(){
    $('.newEvent').animate( {backgroundColor: '#fafafa' }, 2000, 'linear', function() {
        $('.newEvent').removeClass('newEvent'); }
    );
}

function runPreDownload( dlLink ){

    $("#preparingDL").show();
    setTimeout( function() { $("#preparingDL").hide(); }, 4500 );
    
    // ugly hack; chrome kills in-flight ajax requests when the DL starts, preventing more status polls.
    setTimeout( function() { if( g_openStatusRequests > 0 ) g_openStatusRequests = 0; }, 10000 );

    top.location.href = dlLink;

    return false;
}

function OnSongClick( el, songID, cfID )
{
    if( isPlayable( el ) ) {
        AG.PlayerControl.queueSong( songID, cfID, true, true, null );
    } else if( isStopped( el ) ) {
        AG.PlayerControl.pauseCurrentSong( songID, cfID );
    } else if( isQueued( el ) ) {
        //nothing to do when they click on this
    } else if( $(el).hasClass( "disallowedbutton" ) ) {
        // nothing to do
    } else if( $(el).hasClass( "queueable" ) ) { // dont allow queueableSmall
        $(el).css( "cursor", "wait" );
        RequestSong( songID, el );
    }
    
    return false;
}

function HandleClickPlaylist( id, plsName, plsTags, type ) {
  
    el = ".play_" + id;
    if( isStopped( el ) ) {
        AG.Playlist.stopSong();
    }
    else {
        AG.PlayerControl.queuePlaylist( id, plsName, plsTags, true, null );
    }
}

function HandleClickArtistSongs( artistID, artistName ) {
  
    el = ".play_" + artistID;
    if( isStopped( el ) ) {
        AG.Playlist.stopSong();
    }
    else {
        AG.PlayerControl.queueSongsByArtists( artistID, "", false, null );
    }
}

function selectGroupToSend( groupID ) {
    $("#groupSendSongName").text( $("#groupDetailsSongName_" + groupID).text() );
    $("#groupSendSongNumMembers").text( $("#groupDetailsGroupSize_" + groupID).text() );
    $("#groupSendSongGroupName").text( $("#groupDetailsGroupName_" + groupID).text() );
}

function sendSongToGroup( songID ) {
    
    if( $("#sendSongNote").val() === "" ) {
        $("#noteRequired").effect("highlight",{},1500);
        return false; 
    }

    var toks = $('input[name=targetGroup]:checked').attr("ID").split( "_" );

    if( toks.length !== 2 ) {
        return false;
    }

    var groupID = toks[1];
 
    $("#sendSongError").empty();
    $("#sendSongNote").attr("disabled", "true");
    $("#sendSongButton").attr("disabled", "true");
    $.post( encodeURI(sendUrl), { 
        songID: songID, 
        songNote: $("#sendSongNote").val(),
        groupID: groupID
    }, function(data) {
        $("#sendSongNote").attr("disabled", "");
        $("#sendSongButton").attr("disabled", "");
        if( data.success === true ) {
            $("#send_pane").remove();
        } else {
            $("#sendSongError").text(data.errMessage).effect("highlight",{},1500);
        }
    }, "json" );
}

function createIgnoreInstallCookie() {
    var date = new Date();
    date.setTime(date.getTime() + (24 * 60 * 1000)); 
    document.cookie = "ignoreHelper=true; expires=" + date.toGMTString() + "; path=/";
}

function InitSoundManager(){
    
    soundManager = new SoundManager();
    soundManager.url = '/plugins/'; // directory where the SWF files live
    soundManager.allowScriptAccess = 'always';
    soundManager.useFastPolling = true;
    soundManager.flashVersion = 9;
    soundManager.flashLoadTimeout = 3000;
    soundManager.useMovieStar = true;

    // remove for debugging
    soundManager.debugMode = false;
    soundManager.onerror = function() { 
       
        var siteReportUrl = "/siteReport?";
        var errorSummary = "SoundManager initialization failed";

        // something went wrong during init -- try once more
        setTimeout( function() {
            soundManager.flashLoadTimeout = 0;
            soundManager.onerror = function(){
                if( __debug ) {
                    alert( errorSummary );
                }
                else { 
                    $.post( encodeURI(siteReportUrl), {
                            summary: errorSummary,
                            useragent: navigator.userAgent 
                        },
                        function(data) {} );
                }
            };
            soundManager.reboot();
        }, 3000 );
    };

    soundManager.beginDelayedInit();
    soundManager.onready( function(oStatus) {
        flashDetected = oStatus.success;
        if( oStatus.success ) {
            if( userUrlBase !== undefined && userUrlBase !== "" ) {
                // give the localhost detection and other initialization some time to happen
                setTimeout( function() { 
                    AG.PlayerControl.loadUnsavedSongs();
                }, 2000 );
            }
        }
    } );
}

function SetupErrorHandling(){
    

    var siteReportUrl = "/siteReport?";
    window.onerror = function( message, url, lineNumber) {
        if( url === undefined || (url !== undefined && ( url.indexOf( 'http://127.0.0.1' ) !== -1 ) ) ) {
            return true;
        }
        
        var summary = 'Error ' + message + ' @ ' + url + ':' + lineNumber + '(' + top.location + ', hash: ' + top.location.hash + ')';

        if( __debug ) {
            alert( summary );
            return false;
        }
        
        $.post( encodeURI(siteReportUrl), {
                username: g_username,
                summary: summary,
                useragent: navigator.userAgent 
            },
            function(data) {} );

        return false;
    };
}

function CheckConfirmClose( e ){
    
    if( AG.PlayerControl.isCurrentlyPlaying()){
        return "Your song will stop playing...";
    }

    return;
}

function InitSecondary() {

    if( g_headerSuppressed ) { 
        return;
    }

    $("#dialog_idle").dialog({
        bgiframe: true,
        autoOpen: false,
        height: 150,
        width: 200,
        modal: true,
        resizable: false
    });

    SetupErrorHandling();
    InitSoundManager();
    AG.FBConnect.initFB();
    window.onbeforeunload = CheckConfirmClose;

    if( statusUrl !== "" && $("#playerPane").length ) {
        AG.PlayerControl.init();
    }
  
    var idleTimeSecs = 300;
    var awayTimeSecs = 1200;
    var idlePollingDelaySecs = 60;
    var activePollingDelaySecs = 5;
    var unsavedSongsUploadIntervalSecs = 600;
    var hotPollingDelaySecs = 1;
    var hotTimeSpanSecs = 60;
    var localhostDelaySecs = 5;
    var localhostCheckTimeoutSecs = 10;
    var localhostLastSuccessTime = new Date();

    var lastPollTime = new Date() - (activePollingDelaySecs * 1000);
    var lastLocalhostCheckTime = new Date() - (localhostDelaySecs * 1000);
    var lastUnsavedSongsUploadTime = new Date() - (unsavedSongsUploadIntervalSecs * 1000);
    var lastActivityTime = new Date();

    function IsIdle() {
        var now = new Date();
        if( now - lastActivityTime > idleTimeSecs * 1000 ) {
            return true;
        }

        return false;
    }

    function IsAway() {
        var now = new Date();
        if( now - lastActivityTime > awayTimeSecs * 1000 ) {
            return true;
        }

        return false;
    }

    function updateLastActivityTime() {
        var wasIdle = IsIdle();

        lastActivityTime = new Date();

        if( wasIdle && !IsIdle() ) {
            $("#dialog_idle").dialog('close');
            checkForUpdatesFromCloud();
        }
    }

    function checkLocalhostRunning()
    {
        var now = new Date();
        if( typeof g_username === "undefined" || now - lastLocalhostCheckTime < localhostDelaySecs * 1000 ) {
            return;
        }

        if( $("#userStatus").hasClass("avatarOffline") ){
            return;
        }

        if( now - localhostLastSuccessTime > localhostCheckTimeoutSecs * 1000 ) {
            // we've gotten no response from the client for > timeout, assume not running
            AG.PlayerControl.setLocalhostRunning( false );
            g_localhostRunning = false;
        }

        lastLocalhostCheckTime = now;

        var identifyUrl = "http://127.0.0.1:" + localListenPort + "/identify?agsalt=" + sessionSalt;
        $.getScript( identifyUrl, function(data, textStatus) {
            try {
                if( typeof localUsername !== "undefined" || localUsername !== undefined || localUsername === "" ) {
                    if( localUsername === g_username ) { 
                        localUsername = "";
                        AG.PlayerControl.setLocalhostRunning( true );
                        g_localhostRunning = true;
                    } 
                    else {
                        AG.PlayerControl.setLocalhostRunning( false );
                        g_localhostRunning = false;
                    }
                }
            }
            catch( err ) {
            }
            
            localhostLastSuccessTime = new Date();
            localUsername = "";
        });
    }

    function changeOnlineState(users, from, to, newTitle)
    {
        for( i = 0; i < users.length; i++ ) {
            var onLabel = ".user_" + users[i];
            $(onLabel).addClass( to + "user" );
            $(onLabel).removeClass( from + "user" );
            $(onLabel).attr('title', newTitle);

            if( $(onLabel).hasClass( from + "Image" ) ) {
                $(onLabel).removeClass( from + "Image" );
                $(onLabel).addClass( to + "Image" );
            }
            else if( $(onLabel).hasClass( from + "ImageLarge" )) {
                $(onLabel).removeClass( from + "ImageLarge" );
                $(onLabel).addClass( to + "ImageLarge" );
            }
        }
    }

    function checkForUpdatesFromCloud()
    {
        if( authFailed ) {
            return;
        }

        if( statusUrl.length === 0 ) {
            return;
        }

        if( g_openStatusRequests > 0 ) {
            return;
        }

        var pollingDelaySecs = activePollingDelaySecs;
        if( IsIdle() ) {
            
            if( IsAway() ) {
                $("#dialog_idle").dialog('open');
                return;
            }

            pollingDelaySecs = idlePollingDelaySecs;
        } 
        
        var now = new Date();

        // poll more frequently if they have recently requested a song
        if( g_lastRequestTime !== undefined && 
            now - g_lastRequestTime < hotTimeSpanSecs * 1000 ) {
            pollingDelaySecs = hotPollingDelaySecs;
        }

        if( now - lastUnsavedSongsUploadTime > unsavedSongsUploadIntervalSecs * 1000 ) {
            lastUnsavedSongsUploadTime = now;
            AG.PlayerControl.saveUnsavedSongs();
        }

        if( now - lastPollTime < pollingDelaySecs * 1000 ) {
            return;
        }

        lastPollTime = now;

        var queuedIDs = extractClassIDs(".qbutton", "song" ).concat(extractClassIDs(".qbuttonSmall", "song" ) );
        var offlineUsers = extractClassIDs(".offlineuser", "user" );
        var onlineUsers = extractClassIDs(".onlineuser", "user" );

        // Find elements on the page that have marked themselves pollable
        // (meaning they can be updated with live data from the server). 
        // Use the function they've registered to get a signature that
        // we can send to the server for updates
        var p2 = $(".pollable").map( function() {
            var getSig = $(this).data("getSig");
            return getSig();
        }).get().join( "," );

        g_openStatusRequests++;
        $.ajax( {
            type: "POST",
            url: encodeURI(statusUrl), 
            data: { 
                p2: p2,
                queuedIDs: queuedIDs,
                offlineUsers: offlineUsers,
                onlineUsers: onlineUsers
                // popupEvents: popupEvents
            },
            dataType: "json",
            complete: function(obj, textStatus ) {
                if( g_openStatusRequests > 0 ) {
                    g_openStatusRequests--;
                }
            },
            error: function(obj, textStatus, errorThrown) {

                if( window.console !== undefined ) {                    
                    window.console.log( "error hitting server: " + textStatus );
                }
            },
            success: function(data) {
                if( data === null ) {
                    return;
                }
                
                if( data.err ) {
                    // nonce mismatch detected. Refresh the page, and make the user sign in again
                    alert( "You logged in/out of another location. Please login again" );
                    AG.PlayerControl.stopSong( true );
                    top.location.reload();
                    return;
                }

                var oldClass = $("#userStatus").attr('class');
                if( data.avatarClass !== undefined && !$("#userStatus").hasClass( data.avatarClass ) ) {
                    $("#userStatus")
                        .removeClass("avatarOffline")
                        .removeClass( "avatarOnline")
                        .removeClass("avatarTransferring")
                        .addClass( data.avatarClass );

                    if( data.avatarClass === "avatarOnline" ) {
                       
                        $("#collectionNavOptions").show();

                        AG.PlayerControl.setHelperOnline( true );
                    }
                    else {
                        AG.PlayerControl.setHelperOnline( false );
                        $("#collectionNavOptions").hide();
                    }

                    // for simplicity while we develop the new site, reload the hash 
                    // whenever satellite changes online state
                    $(window).trigger( 'hashchange' );
                }
                
                if( data.numSongs !== undefined ) {
                    var numSongsStr = data.numSongs === 1 ? data.numSongs + " Song" : data.numSongs + " Songs";
                    $("#numSongs").text(numSongsStr);
                }
                else {
                    $("#numSongs").text('0 Songs (Helper is offline)');
                }
                
                if( data.localListenPort !== undefined ) {
                    localListenPort = data.localListenPort;
                }

                if( data.sessionSalt !== undefined ) {
                    sessionSalt = data.sessionSalt;
                }

                /* FOR POPUPS/NOTES 
                if( data.popup !== undefined ) {
                    if( data.popup === "" ) {
                        $("#note_popup").css("display", "none");
                    } else {
                        $(data.popup).appendTo($("#note_popup_contents")).refreshFBML();
                        fixupNewHtml();
                        $("#note_popup").slideDown("slow");
                        $("#note_popup_contents").slideDown("slow");
                    }
                }
                */

                $(".pollable").map( function() {
                    var getSig = $(this).data("getSig");
                    var curSig = getSig();

                    var changes = data[ curSig ];

                    // this can happen if sig changed while the call to the server
                    // was happening.  Don't want to update in that case 
                    if( changes !== undefined ) {
                        if( changes.editType === "json" ) {
                            if( changes.renderer === "renderGroupNotes" ) {
                                renderGroupNotes(changes.newContent);
                            } else if( changes.renderer === "refreshMusicCollection" ) {
                                AG.Collection.refreshMusicCollection( changes.newContent );
                            } else if( changes.renderer === "refreshQueue" ) {
                                refreshQueue( changes.newContent );
                            } else if( changes.renderer === "refreshAutoPlaylists" ) {
                                AG.Collection.refreshAutoPlaylists( changes.newContent );
                            } else if( changes.renderer === "refreshM3uPlaylists" ) {
                                AG.Collection.refreshM3uPlaylists( changes.newContent );
                            } else if( changes.renderer === "renderFeedItems" ) {
                                renderFeedItems( changes.newContent.newItems );
                                latestEventID = changes.newContent.newLastSeenID;
                            } else if( changes.renderer === "renderConvItems" ) {
                                renderConvItems( changes.newContent.newItems );
                                lastConvID = changes.newContent.newLastConvID;
                            } else if (changes.renderer === "renderComments" ) {
                                renderComments( changes.newContent );
                            } else if (changes.renderer === "refreshThreads" ) {
                                refreshThreads( changes.newContent );
                            } else if (changes.renderer === "renderNavGroups" ) {
                                renderNavGroups( changes.newContent );
                            } else {
                                if( window.console !== undefined ) {                    
                                    window.console.log( "unknown render function: " + changes.renderer );
                                }
                            }
                        }
                    }

                    fixupNewHtml();
                });

                /*
                // this will contain any songIDs that were in the queuedIDs
                // array that was sent to the server that have finished 
                // downloading.
                if( data.playIDs !== undefined ) {
                    for( var i = 0; i < data.playIDs.length; i++ ) {
                        var songClass = ".song_" + data.playIDs[i];
                        makePlayable(songClass);
                    }
                }
                */

                if( data.nowOff !== undefined ) {
                    changeOnlineState( data.nowOff, "online", "offline", "currently offline" );                
                }

                if( data.nowOn !== undefined ) {
                    changeOnlineState( data.nowOn, "offline", "online", "currently online" );
                }
                
                /* POPUPS/EVENTS
                if( data.popupEvents !== undefined ) {
                    popupEvents = data.popupEvents;
                }

                if( data.l1Unseen !== undefined && data.l2Unseen !== undefined ) {
                    updateNumUnseenEvents( data.l1Unseen + data.l2Unseen );
                }
                */

           } 
       }); 
    }

    //$(window).mousemove( function(e) { updateLastActivityTime(); }); //seems buggy in chrome.  
    $(window).mousedown( function(e) { updateLastActivityTime(); });
    $(window).keydown( function(e) { updateLastActivityTime(); });
    $(window).scroll( function(e) { updateLastActivityTime(); });
    $(window).focus( function(e) { updateLastActivityTime(); });
    
    $(document).everyTime("1s", "heartbeat", function(i) {
        var now = new Date();

        $("#debug_footer").text( "isIdle: " + IsIdle() + 
            ", open reqs: " + g_openStatusRequests +
            ", secs since last poll: " + ((now - lastPollTime) / 1000) );

        if( i % 10 === 0 && !IsIdle() )
        {
            clearNewEvents();
        }
       
        checkLocalhostRunning();
        checkForUpdatesFromCloud();
    } );
}

