/**
 * OfMeet API
 */
 
var ofmeet = (function(of)
{
	/**
	 * interfaceConfig
	 */

	var interfaceConfig = 
	{
	    CANVAS_EXTRA: 104,
	    CANVAS_RADIUS: 7,
	    SHADOW_COLOR: '#ffffff',
	    INITIAL_TOOLBAR_TIMEOUT: 20000,
	    TOOLBAR_TIMEOUT: 4000,
	    DEFAULT_REMOTE_DISPLAY_NAME: "Change Me",
	    DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME: "Speaker",
	    DEFAULT_LOCAL_DISPLAY_NAME: "me",
	    SHOW_JITSI_WATERMARK: false,
	    JITSI_WATERMARK_LINK: "",
	    SHOW_BRAND_WATERMARK: false,
	    BRAND_WATERMARK_LINK: "",
	    SHOW_POWERED_BY: false,
	    GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
	    APP_NAME: "Openfire Meetings",
	    ACTIVE_SPEAKER_AVATAR_SIZE: 100    
	};
	

	/*! jQuery v2.1.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */

	!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.1",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="<div class='a'></div><div class='a i'></div>",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="<select msallowclip=''><option selected=''></option></select>",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=lb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=mb(b);function pb(){}pb.prototype=d.filters=d.pseudos,d.setFilters=new pb,g=fb.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fb.error(a):z(a,i).slice(0)};function qb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+Math.random()}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)
	},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=L.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var Q=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,R=["Top","Right","Bottom","Left"],S=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)},T=/^(?:checkbox|radio)$/i;!function(){var a=l.createDocumentFragment(),b=a.appendChild(l.createElement("div")),c=l.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||l,d=c.documentElement,e=c.body,a.pageX=b.clientX+(d&&d.scrollLeft||e&&e.scrollLeft||0)-(d&&d.clientLeft||e&&e.clientLeft||0),a.pageY=b.clientY+(d&&d.scrollTop||e&&e.scrollTop||0)-(d&&d.clientTop||e&&e.clientTop||0)),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},fix:function(a){if(a[n.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=W.test(e)?this.mouseHooks:V.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new n.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=l),3===a.target.nodeType&&(a.target=a.target.parentNode),g.filter?g.filter(a,f):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==_()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===_()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&n.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=n.extend(new n.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?n.event.trigger(e,null,b):n.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},n.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?Z:$):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={isDefaultPrevented:$,isPropagationStopped:$,isImmediatePropagationStopped:$,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=Z,a&&a.preventDefault&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=Z,a&&a.stopPropagation&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=Z,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},n.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!n.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.focusinBubbles||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a),!0)};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=L.access(d,b);e||d.addEventListener(a,c,!0),L.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=L.access(d,b)-1;e?L.access(d,b,e):(d.removeEventListener(a,c,!0),L.remove(d,b))}}}),n.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(g in a)this.on(g,b,c,a[g],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=$;else if(!d)return this;return 1===e&&(f=d,d=function(a){return n().off(a),f.apply(this,arguments)},d.guid=f.guid||(f.guid=n.guid++)),this.each(function(){n.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=$),this.each(function(){n.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}});var ab=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ib={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1></$2>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,"script"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||n("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=qb[0].contentDocument,b.write(),b.close(),c=sb(a,b),qb.detach()),rb[a]=c),c}var ub=/^margin/,vb=new RegExp("^("+Q+")(?!px)[a-z%]+$","i"),wb=function(a){return a.ownerDocument.defaultView.getComputedStyle(a,null)};function xb(a,b,c){var d,e,f,g,h=a.style;return c=c||wb(a),c&&(g=c.getPropertyValue(b)||c[b]),c&&(""!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),vb.test(g)&&ub.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function yb(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d=l.documentElement,e=l.createElement("div"),f=l.createElement("div");if(f.style){f.style.backgroundClip="content-box",f.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===f.style.backgroundClip,e.style.cssText="border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;position:absolute",e.appendChild(f);function g(){f.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",f.innerHTML="",d.appendChild(e);var g=a.getComputedStyle(f,null);b="1%"!==g.top,c="4px"===g.width,d.removeChild(e)}a.getComputedStyle&&n.extend(k,{pixelPosition:function(){return g(),b},boxSizingReliable:function(){return null==c&&g(),c},reliableMarginRight:function(){var b,c=f.appendChild(l.createElement("div"));return c.style.cssText=f.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",c.style.marginRight=c.style.width="0",f.style.width="1px",d.appendChild(e),b=!parseFloat(a.getComputedStyle(c,null).marginRight),d.removeChild(e),b}})}}(),n.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var zb=/^(none|table(?!-c[ea]).+)/,Ab=new RegExp("^("+Q+")(.*)$","i"),Bb=new RegExp("^([+-])=("+Q+")","i"),Cb={position:"absolute",visibility:"hidden",display:"block"},Db={letterSpacing:"0",fontWeight:"400"},Eb=["Webkit","O","Moz","ms"];function Fb(a,b){if(b in a)return b;var c=b[0].toUpperCase()+b.slice(1),d=b,e=Eb.length;while(e--)if(b=Eb[e]+c,b in a)return b;return d}function Gb(a,b,c){var d=Ab.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Hb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+R[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+R[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+R[f]+"Width",!0,e))):(g+=n.css(a,"padding"+R[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+R[f]+"Width",!0,e)));return g}function Ib(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=wb(a),g="border-box"===n.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=xb(a,b,f),(0>e||null==e)&&(e=a.style[b]),vb.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Hb(a,b,c||(g?"border":"content"),d,f)+"px"}function Jb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=L.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&S(d)&&(f[g]=L.access(d,"olddisplay",tb(d.nodeName)))):(e=S(d),"none"===c&&e||L.set(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=xb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;return b=n.cssProps[h]||(n.cssProps[h]=Fb(i,h)),g=n.cssHooks[b]||n.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=Bb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(n.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||n.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Fb(a.style,h)),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=xb(a,b,d)),"normal"===e&&b in Db&&(e=Db[b]),""===c||c?(f=parseFloat(e),c===!0||n.isNumeric(f)?f||0:e):e}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?zb.test(n.css(a,"display"))&&0===a.offsetWidth?n.swap(a,Cb,function(){return Ib(a,b,d)}):Ib(a,b,d):void 0},set:function(a,c,d){var e=d&&wb(a);return Gb(a,c,d?Hb(a,b,d,"border-box"===n.css(a,"boxSizing",!1,e),e):0)}}}),n.cssHooks.marginRight=yb(k.reliableMarginRight,function(a,b){return b?n.swap(a,{display:"inline-block"},xb,[a,"marginRight"]):void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+R[d]+b]=f[d]||f[d-2]||f[0];return e}},ub.test(a)||(n.cssHooks[a+b].set=Gb)}),n.fn.extend({css:function(a,b){return J(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=wb(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return Jb(this,!0)},hide:function(){return Jb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){S(this)?n(this).show():n(this).hide()})}});function Kb(a,b,c,d,e){return new Kb.prototype.init(a,b,c,d,e)}n.Tween=Kb,Kb.prototype={constructor:Kb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=Kb.propHooks[this.prop];return a&&a.get?a.get(this):Kb.propHooks._default.get(this)},run:function(a){var b,c=Kb.propHooks[this.prop];return this.pos=b=this.options.duration?n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Kb.propHooks._default.set(this),this}},Kb.prototype.init.prototype=Kb.prototype,Kb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[n.cssProps[a.prop]]||n.cssHooks[a.prop])?n.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Kb.propHooks.scrollTop=Kb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},n.fx=Kb.prototype.init,n.fx.step={};var Lb,Mb,Nb=/^(?:toggle|show|hide)$/,Ob=new RegExp("^(?:([+-])=|)("+Q+")([a-z%]*)$","i"),Pb=/queueHooks$/,Qb=[Vb],Rb={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=Ob.exec(b),f=e&&e[3]||(n.cssNumber[a]?"":"px"),g=(n.cssNumber[a]||"px"!==f&&+d)&&Ob.exec(n.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,n.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function Sb(){return setTimeout(function(){Lb=void 0}),Lb=n.now()}function Tb(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=R[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ub(a,b,c){for(var d,e=(Rb[b]||[]).concat(Rb["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Vb(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeType&&S(a),q=L.get(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,l.always(function(){l.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=n.css(a,"display"),k="none"===j?L.get(a,"olddisplay")||tb(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(o.display="inline-block")),c.overflow&&(o.overflow="hidden",l.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Nb.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}m[d]=q&&q[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(m))"inline"===("none"===j?tb(a.nodeName):j)&&(o.display=j);else{q?"hidden"in q&&(p=q.hidden):q=L.access(a,"fxshow",{}),f&&(q.hidden=!p),p?n(a).show():l.done(function(){n(a).hide()}),l.done(function(){var b;L.remove(a,"fxshow");for(b in m)n.style(a,b,m[b])});for(d in m)g=Ub(p?q[d]:0,d,l),d in q||(q[d]=g.start,p&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function Wb(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function Xb(a,b,c){var d,e,f=0,g=Qb.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Lb||Sb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:Lb||Sb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(Wb(k,j.opts.specialEasing);g>f;f++)if(d=Qb[f].call(j,a,k,j.opts))return d;return n.map(k,Ub,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(Xb,{tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],Rb[c]=Rb[c]||[],Rb[c].unshift(b)},prefilter:function(a,b){b?Qb.unshift(a):Qb.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(S).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=Xb(this,n.extend({},a),f);(e||L.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=L.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Pb.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=L.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Tb(b,!0),a,d,e)}}),n.each({slideDown:Tb("show"),slideUp:Tb("hide"),slideToggle:Tb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=0,c=n.timers;for(Lb=n.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||n.fx.stop(),Lb=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){Mb||(Mb=setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){clearInterval(Mb),Mb=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(a,b){return a=n.fx?n.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a=l.createElement("input"),b=l.createElement("select"),c=b.appendChild(l.createElement("option"));a.type="checkbox",k.checkOn=""!==a.value,k.optSelected=c.selected,b.disabled=!0,k.optDisabled=!c.disabled,a=l.createElement("input"),a.value="t",a.type="radio",k.radioValue="t"===a.value}();var Yb,Zb,$b=n.expr.attrHandle;n.fn.extend({attr:function(a,b){return J(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===U?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),d=n.attrHooks[b]||(n.expr.match.bool.test(b)?Zb:Yb)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=n.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void n.removeAttr(a,b))
	},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),Zb={set:function(a,b,c){return b===!1?n.removeAttr(a,c):a.setAttribute(c,c),c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=$b[b]||n.find.attr;$b[b]=function(a,b,d){var e,f;return d||(f=$b[b],$b[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,$b[b]=f),e}});var _b=/^(?:input|select|textarea|button)$/i;n.fn.extend({prop:function(a,b){return J(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[n.propFix[a]||a]})}}),n.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!n.isXMLDoc(a),f&&(b=n.propFix[b]||b,e=n.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){return a.hasAttribute("tabindex")||_b.test(a.nodeName)||a.href?a.tabIndex:-1}}}}),k.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var ac=/[\t\r\n\f]/g;n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h="string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=n.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0===arguments.length||"string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?n.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(n.isFunction(a)?function(c){n(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=n(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===U||"boolean"===c)&&(this.className&&L.set(this,"__className__",this.className),this.className=this.className||a===!1?"":L.get(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ac," ").indexOf(b)>=0)return!0;return!1}});var bc=/\r/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(bc,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(d.value,f)>=0)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>=0:void 0}},k.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var cc=n.now(),dc=/\?/;n.parseJSON=function(a){return JSON.parse(a+"")},n.parseXML=function(a){var b,c;if(!a||"string"!=typeof a)return null;try{c=new DOMParser,b=c.parseFromString(a,"text/xml")}catch(d){b=void 0}return(!b||b.getElementsByTagName("parsererror").length)&&n.error("Invalid XML: "+a),b};var ec,fc,gc=/#.*$/,hc=/([?&])_=[^&]*/,ic=/^(.*?):[ \t]*([^\r\n]*)$/gm,jc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,kc=/^(?:GET|HEAD)$/,lc=/^\/\//,mc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,nc={},oc={},pc="*/".concat("*");try{fc=location.href}catch(qc){fc=l.createElement("a"),fc.href="",fc=fc.href}ec=mc.exec(fc.toLowerCase())||[];function rc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(n.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function sc(a,b,c,d){var e={},f=a===oc;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function tc(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&n.extend(!0,a,d),a}function uc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function vc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:fc,type:"GET",isLocal:jc.test(ec[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":pc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?tc(tc(a,n.ajaxSettings),b):tc(n.ajaxSettings,a)},ajaxPrefilter:rc(nc),ajaxTransport:rc(oc),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=n.ajaxSetup({},b),l=k.context||k,m=k.context&&(l.nodeType||l.jquery)?n(l):n.event,o=n.Deferred(),p=n.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!f){f={};while(b=ic.exec(e))f[b[1].toLowerCase()]=b[2]}b=f[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?e:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return c&&c.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||fc)+"").replace(gc,"").replace(lc,ec[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=n.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(h=mc.exec(k.url.toLowerCase()),k.crossDomain=!(!h||h[1]===ec[1]&&h[2]===ec[2]&&(h[3]||("http:"===h[1]?"80":"443"))===(ec[3]||("http:"===ec[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=n.param(k.data,k.traditional)),sc(nc,k,b,v),2===t)return v;i=k.global,i&&0===n.active++&&n.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!kc.test(k.type),d=k.url,k.hasContent||(k.data&&(d=k.url+=(dc.test(d)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=hc.test(d)?d.replace(hc,"$1_="+cc++):d+(dc.test(d)?"&":"?")+"_="+cc++)),k.ifModified&&(n.lastModified[d]&&v.setRequestHeader("If-Modified-Since",n.lastModified[d]),n.etag[d]&&v.setRequestHeader("If-None-Match",n.etag[d])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+pc+"; q=0.01":""):k.accepts["*"]);for(j in k.headers)v.setRequestHeader(j,k.headers[j]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(j in{success:1,error:1,complete:1})v[j](k[j]);if(c=sc(oc,k,b,v)){v.readyState=1,i&&m.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,c.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,f,h){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),c=void 0,e=h||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,f&&(u=uc(k,v,f)),u=vc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(n.lastModified[d]=w),w=v.getResponseHeader("etag"),w&&(n.etag[d]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,i&&m.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),i&&(m.trigger("ajaxComplete",[v,k]),--n.active||n.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){var b;return n.isFunction(a)?this.each(function(b){n(this).wrapAll(a.call(this,b))}):(this[0]&&(b=n(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return this.each(n.isFunction(a)?function(b){n(this).wrapInner(a.call(this,b))}:function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0},n.expr.filters.visible=function(a){return!n.expr.filters.hidden(a)};var wc=/%20/g,xc=/\[\]$/,yc=/\r?\n/g,zc=/^(?:submit|button|image|reset|file)$/i,Ac=/^(?:input|select|textarea|keygen)/i;function Bc(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||xc.test(a)?d(a,e):Bc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)Bc(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Bc(c,a[c],b,e);return d.join("&").replace(wc,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&Ac.test(this.nodeName)&&!zc.test(a)&&(this.checked||!T.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(yc,"\r\n")}}):{name:b.name,value:c.replace(yc,"\r\n")}}).get()}}),n.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(a){}};var Cc=0,Dc={},Ec={0:200,1223:204},Fc=n.ajaxSettings.xhr();a.ActiveXObject&&n(a).on("unload",function(){for(var a in Dc)Dc[a]()}),k.cors=!!Fc&&"withCredentials"in Fc,k.ajax=Fc=!!Fc,n.ajaxTransport(function(a){var b;return k.cors||Fc&&!a.crossDomain?{send:function(c,d){var e,f=a.xhr(),g=++Cc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)f.setRequestHeader(e,c[e]);b=function(a){return function(){b&&(delete Dc[g],b=f.onload=f.onerror=null,"abort"===a?f.abort():"error"===a?d(f.status,f.statusText):d(Ec[f.status]||f.status,f.statusText,"string"==typeof f.responseText?{text:f.responseText}:void 0,f.getAllResponseHeaders()))}},f.onload=b(),f.onerror=b("error"),b=Dc[g]=b("abort");try{f.send(a.hasContent&&a.data||null)}catch(h){if(b)throw h}},abort:function(){b&&b()}}:void 0}),n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(d,e){b=n("<script>").prop({async:!0,charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&e("error"===a.type?404:200,a.type)}),l.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Gc=[],Hc=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Gc.pop()||n.expando+"_"+cc++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Hc.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Hc.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Hc,"$1"+e):b.jsonp!==!1&&(b.url+=(dc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Gc.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||l;var d=v.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=n.buildFragment([a],b,e),e&&e.length&&n(e).remove(),n.merge([],d.childNodes))};var Ic=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&Ic)return Ic.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=n.trim(a.slice(h)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&n.ajax({url:a,type:e,dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,f||[a.responseText,b,a])}),this},n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};var Jc=a.document.documentElement;function Kc(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,n.contains(b,d)?(typeof d.getBoundingClientRect!==U&&(e=d.getBoundingClientRect()),c=Kc(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===n.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(d=a.offset()),d.top+=n.css(a[0],"borderTopWidth",!0),d.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-n.css(c,"marginTop",!0),left:b.left-d.left-n.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||Jc;while(a&&!n.nodeName(a,"html")&&"static"===n.css(a,"position"))a=a.offsetParent;return a||Jc})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(b,c){var d="pageYOffset"===c;n.fn[b]=function(e){return J(this,function(b,e,f){var g=Kc(b);return void 0===f?g?g[c]:b[e]:void(g?g.scrollTo(d?a.pageXOffset:f,d?f:a.pageYOffset):b[e]=f)},b,e,arguments.length,null)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=yb(k.pixelPosition,function(a,c){return c?(c=xb(a,b),vb.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return J(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.size=function(){return this.length},n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var Lc=a.jQuery,Mc=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=Mc),b&&a.jQuery===n&&(a.jQuery=Lc),n},typeof b===U&&(a.jQuery=a.$=n),n});



	/*! jQuery UI - v1.10.4 - 2014-01-17
	* http://jqueryui.com
	* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.effect.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js, jquery.ui.menu.js, jquery.ui.progressbar.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.slider.js, jquery.ui.sortable.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js
	* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT 
	*/

	(function( $, undefined ) {

	var uuid = 0,
		runiqueId = /^ui-id-\d+$/;

	// $.ui might exist from components with no dependencies, e.g., $.ui.position
	$.ui = $.ui || {};

	$.extend( $.ui, {
		version: "1.10.4",

		keyCode: {
			BACKSPACE: 8,
			COMMA: 188,
			DELETE: 46,
			DOWN: 40,
			END: 35,
			ENTER: 13,
			ESCAPE: 27,
			HOME: 36,
			LEFT: 37,
			NUMPAD_ADD: 107,
			NUMPAD_DECIMAL: 110,
			NUMPAD_DIVIDE: 111,
			NUMPAD_ENTER: 108,
			NUMPAD_MULTIPLY: 106,
			NUMPAD_SUBTRACT: 109,
			PAGE_DOWN: 34,
			PAGE_UP: 33,
			PERIOD: 190,
			RIGHT: 39,
			SPACE: 32,
			TAB: 9,
			UP: 38
		}
	});

	// plugins
	$.fn.extend({
		focus: (function( orig ) {
			return function( delay, fn ) {
				return typeof delay === "number" ?
					this.each(function() {
						var elem = this;
						setTimeout(function() {
							$( elem ).focus();
							if ( fn ) {
								fn.call( elem );
							}
						}, delay );
					}) :
					orig.apply( this, arguments );
			};
		})( $.fn.focus ),

		scrollParent: function() {
			var scrollParent;
			if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) {
				scrollParent = this.parents().filter(function() {
					return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
				}).eq(0);
			} else {
				scrollParent = this.parents().filter(function() {
					return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
				}).eq(0);
			}

			return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent;
		},

		zIndex: function( zIndex ) {
			if ( zIndex !== undefined ) {
				return this.css( "zIndex", zIndex );
			}

			if ( this.length ) {
				var elem = $( this[ 0 ] ), position, value;
				while ( elem.length && elem[ 0 ] !== document ) {
					// Ignore z-index if position is set to a value where z-index is ignored by the browser
					// This makes behavior of this function consistent across browsers
					// WebKit always returns auto if the element is positioned
					position = elem.css( "position" );
					if ( position === "absolute" || position === "relative" || position === "fixed" ) {
						// IE returns 0 when zIndex is not specified
						// other browsers return a string
						// we ignore the case of nested elements with an explicit value of 0
						// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
						value = parseInt( elem.css( "zIndex" ), 10 );
						if ( !isNaN( value ) && value !== 0 ) {
							return value;
						}
					}
					elem = elem.parent();
				}
			}

			return 0;
		},

		uniqueId: function() {
			return this.each(function() {
				if ( !this.id ) {
					this.id = "ui-id-" + (++uuid);
				}
			});
		},

		removeUniqueId: function() {
			return this.each(function() {
				if ( runiqueId.test( this.id ) ) {
					$( this ).removeAttr( "id" );
				}
			});
		}
	});

	// selectors
	function focusable( element, isTabIndexNotNaN ) {
		var map, mapName, img,
			nodeName = element.nodeName.toLowerCase();
		if ( "area" === nodeName ) {
			map = element.parentNode;
			mapName = map.name;
			if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
				return false;
			}
			img = $( "img[usemap=#" + mapName + "]" )[0];
			return !!img && visible( img );
		}
		return ( /input|select|textarea|button|object/.test( nodeName ) ?
			!element.disabled :
			"a" === nodeName ?
				element.href || isTabIndexNotNaN :
				isTabIndexNotNaN) &&
			// the element and all of its ancestors must be visible
			visible( element );
	}

	function visible( element ) {
		return $.expr.filters.visible( element ) &&
			!$( element ).parents().addBack().filter(function() {
				return $.css( this, "visibility" ) === "hidden";
			}).length;
	}

	$.extend( $.expr[ ":" ], {
		data: $.expr.createPseudo ?
			$.expr.createPseudo(function( dataName ) {
				return function( elem ) {
					return !!$.data( elem, dataName );
				};
			}) :
			// support: jQuery <1.8
			function( elem, i, match ) {
				return !!$.data( elem, match[ 3 ] );
			},

		focusable: function( element ) {
			return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
		},

		tabbable: function( element ) {
			var tabIndex = $.attr( element, "tabindex" ),
				isTabIndexNaN = isNaN( tabIndex );
			return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
		}
	});

	// support: jQuery <1.8
	if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
		$.each( [ "Width", "Height" ], function( i, name ) {
			var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
				type = name.toLowerCase(),
				orig = {
					innerWidth: $.fn.innerWidth,
					innerHeight: $.fn.innerHeight,
					outerWidth: $.fn.outerWidth,
					outerHeight: $.fn.outerHeight
				};

			function reduce( elem, size, border, margin ) {
				$.each( side, function() {
					size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
					if ( border ) {
						size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
					}
					if ( margin ) {
						size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
					}
				});
				return size;
			}

			$.fn[ "inner" + name ] = function( size ) {
				if ( size === undefined ) {
					return orig[ "inner" + name ].call( this );
				}

				return this.each(function() {
					$( this ).css( type, reduce( this, size ) + "px" );
				});
			};

			$.fn[ "outer" + name] = function( size, margin ) {
				if ( typeof size !== "number" ) {
					return orig[ "outer" + name ].call( this, size );
				}

				return this.each(function() {
					$( this).css( type, reduce( this, size, true, margin ) + "px" );
				});
			};
		});
	}

	// support: jQuery <1.8
	if ( !$.fn.addBack ) {
		$.fn.addBack = function( selector ) {
			return this.add( selector == null ?
				this.prevObject : this.prevObject.filter( selector )
			);
		};
	}

	// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
	if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
		$.fn.removeData = (function( removeData ) {
			return function( key ) {
				if ( arguments.length ) {
					return removeData.call( this, $.camelCase( key ) );
				} else {
					return removeData.call( this );
				}
			};
		})( $.fn.removeData );
	}





	// deprecated
	$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );

	$.support.selectstart = "onselectstart" in document.createElement( "div" );
	$.fn.extend({
		disableSelection: function() {
			return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
				".ui-disableSelection", function( event ) {
					event.preventDefault();
				});
		},

		enableSelection: function() {
			return this.unbind( ".ui-disableSelection" );
		}
	});

	$.extend( $.ui, {
		// $.ui.plugin is deprecated. Use $.widget() extensions instead.
		plugin: {
			add: function( module, option, set ) {
				var i,
					proto = $.ui[ module ].prototype;
				for ( i in set ) {
					proto.plugins[ i ] = proto.plugins[ i ] || [];
					proto.plugins[ i ].push( [ option, set[ i ] ] );
				}
			},
			call: function( instance, name, args ) {
				var i,
					set = instance.plugins[ name ];
				if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
					return;
				}

				for ( i = 0; i < set.length; i++ ) {
					if ( instance.options[ set[ i ][ 0 ] ] ) {
						set[ i ][ 1 ].apply( instance.element, args );
					}
				}
			}
		},

		// only used by resizable
		hasScroll: function( el, a ) {

			//If overflow is hidden, the element might have extra content, but the user wants to hide it
			if ( $( el ).css( "overflow" ) === "hidden") {
				return false;
			}

			var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
				has = false;

			if ( el[ scroll ] > 0 ) {
				return true;
			}

			// TODO: determine which cases actually cause this to happen
			// if the element doesn't have the scroll set, see if it's possible to
			// set the scroll
			el[ scroll ] = 1;
			has = ( el[ scroll ] > 0 );
			el[ scroll ] = 0;
			return has;
		}
	});

	})( jQuery );
	(function( $, undefined ) {

	var uuid = 0,
		slice = Array.prototype.slice,
		_cleanData = $.cleanData;
	$.cleanData = function( elems ) {
		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
			try {
				$( elem ).triggerHandler( "remove" );
			// http://bugs.jquery.com/ticket/8235
			} catch( e ) {}
		}
		_cleanData( elems );
	};

	$.widget = function( name, base, prototype ) {
		var fullName, existingConstructor, constructor, basePrototype,
			// proxiedPrototype allows the provided prototype to remain unmodified
			// so that it can be used as a mixin for multiple widgets (#8876)
			proxiedPrototype = {},
			namespace = name.split( "." )[ 0 ];

		name = name.split( "." )[ 1 ];
		fullName = namespace + "-" + name;

		if ( !prototype ) {
			prototype = base;
			base = $.Widget;
		}

		// create selector for plugin
		$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
			return !!$.data( elem, fullName );
		};

		$[ namespace ] = $[ namespace ] || {};
		existingConstructor = $[ namespace ][ name ];
		constructor = $[ namespace ][ name ] = function( options, element ) {
			// allow instantiation without "new" keyword
			if ( !this._createWidget ) {
				return new constructor( options, element );
			}

			// allow instantiation without initializing for simple inheritance
			// must use "new" keyword (the code above always passes args)
			if ( arguments.length ) {
				this._createWidget( options, element );
			}
		};
		// extend with the existing constructor to carry over any static properties
		$.extend( constructor, existingConstructor, {
			version: prototype.version,
			// copy the object used to create the prototype in case we need to
			// redefine the widget later
			_proto: $.extend( {}, prototype ),
			// track widgets that inherit from this widget in case this widget is
			// redefined after a widget inherits from it
			_childConstructors: []
		});

		basePrototype = new base();
		// we need to make the options hash a property directly on the new instance
		// otherwise we'll modify the options hash on the prototype that we're
		// inheriting from
		basePrototype.options = $.widget.extend( {}, basePrototype.options );
		$.each( prototype, function( prop, value ) {
			if ( !$.isFunction( value ) ) {
				proxiedPrototype[ prop ] = value;
				return;
			}
			proxiedPrototype[ prop ] = (function() {
				var _super = function() {
						return base.prototype[ prop ].apply( this, arguments );
					},
					_superApply = function( args ) {
						return base.prototype[ prop ].apply( this, args );
					};
				return function() {
					var __super = this._super,
						__superApply = this._superApply,
						returnValue;

					this._super = _super;
					this._superApply = _superApply;

					returnValue = value.apply( this, arguments );

					this._super = __super;
					this._superApply = __superApply;

					return returnValue;
				};
			})();
		});
		constructor.prototype = $.widget.extend( basePrototype, {
			// TODO: remove support for widgetEventPrefix
			// always use the name + a colon as the prefix, e.g., draggable:start
			// don't prefix for widgets that aren't DOM-based
			widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
		}, proxiedPrototype, {
			constructor: constructor,
			namespace: namespace,
			widgetName: name,
			widgetFullName: fullName
		});

		// If this widget is being redefined then we need to find all widgets that
		// are inheriting from it and redefine all of them so that they inherit from
		// the new version of this widget. We're essentially trying to replace one
		// level in the prototype chain.
		if ( existingConstructor ) {
			$.each( existingConstructor._childConstructors, function( i, child ) {
				var childPrototype = child.prototype;

				// redefine the child widget using the same prototype that was
				// originally used, but inherit from the new version of the base
				$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
			});
			// remove the list of existing child constructors from the old constructor
			// so the old child constructors can be garbage collected
			delete existingConstructor._childConstructors;
		} else {
			base._childConstructors.push( constructor );
		}

		$.widget.bridge( name, constructor );
	};

	$.widget.extend = function( target ) {
		var input = slice.call( arguments, 1 ),
			inputIndex = 0,
			inputLength = input.length,
			key,
			value;
		for ( ; inputIndex < inputLength; inputIndex++ ) {
			for ( key in input[ inputIndex ] ) {
				value = input[ inputIndex ][ key ];
				if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
					// Clone objects
					if ( $.isPlainObject( value ) ) {
						target[ key ] = $.isPlainObject( target[ key ] ) ?
							$.widget.extend( {}, target[ key ], value ) :
							// Don't extend strings, arrays, etc. with objects
							$.widget.extend( {}, value );
					// Copy everything else by reference
					} else {
						target[ key ] = value;
					}
				}
			}
		}
		return target;
	};

	$.widget.bridge = function( name, object ) {
		var fullName = object.prototype.widgetFullName || name;
		$.fn[ name ] = function( options ) {
			var isMethodCall = typeof options === "string",
				args = slice.call( arguments, 1 ),
				returnValue = this;

			// allow multiple hashes to be passed on init
			options = !isMethodCall && args.length ?
				$.widget.extend.apply( null, [ options ].concat(args) ) :
				options;

			if ( isMethodCall ) {
				this.each(function() {
					var methodValue,
						instance = $.data( this, fullName );
					if ( !instance ) {
						return $.error( "cannot call methods on " + name + " prior to initialization; " +
							"attempted to call method '" + options + "'" );
					}
					if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
						return $.error( "no such method '" + options + "' for " + name + " widget instance" );
					}
					methodValue = instance[ options ].apply( instance, args );
					if ( methodValue !== instance && methodValue !== undefined ) {
						returnValue = methodValue && methodValue.jquery ?
							returnValue.pushStack( methodValue.get() ) :
							methodValue;
						return false;
					}
				});
			} else {
				this.each(function() {
					var instance = $.data( this, fullName );
					if ( instance ) {
						instance.option( options || {} )._init();
					} else {
						$.data( this, fullName, new object( options, this ) );
					}
				});
			}

			return returnValue;
		};
	};

	$.Widget = function( /* options, element */ ) {};
	$.Widget._childConstructors = [];

	$.Widget.prototype = {
		widgetName: "widget",
		widgetEventPrefix: "",
		defaultElement: "<div>",
		options: {
			disabled: false,

			// callbacks
			create: null
		},
		_createWidget: function( options, element ) {
			element = $( element || this.defaultElement || this )[ 0 ];
			this.element = $( element );
			this.uuid = uuid++;
			this.eventNamespace = "." + this.widgetName + this.uuid;
			this.options = $.widget.extend( {},
				this.options,
				this._getCreateOptions(),
				options );

			this.bindings = $();
			this.hoverable = $();
			this.focusable = $();

			if ( element !== this ) {
				$.data( element, this.widgetFullName, this );
				this._on( true, this.element, {
					remove: function( event ) {
						if ( event.target === element ) {
							this.destroy();
						}
					}
				});
				this.document = $( element.style ?
					// element within the document
					element.ownerDocument :
					// element is window or document
					element.document || element );
				this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
			}

			this._create();
			this._trigger( "create", null, this._getCreateEventData() );
			this._init();
		},
		_getCreateOptions: $.noop,
		_getCreateEventData: $.noop,
		_create: $.noop,
		_init: $.noop,

		destroy: function() {
			this._destroy();
			// we can probably remove the unbind calls in 2.0
			// all event bindings should go through this._on()
			this.element
				.unbind( this.eventNamespace )
				// 1.9 BC for #7810
				// TODO remove dual storage
				.removeData( this.widgetName )
				.removeData( this.widgetFullName )
				// support: jquery <1.6.3
				// http://bugs.jquery.com/ticket/9413
				.removeData( $.camelCase( this.widgetFullName ) );
			this.widget()
				.unbind( this.eventNamespace )
				.removeAttr( "aria-disabled" )
				.removeClass(
					this.widgetFullName + "-disabled " +
					"ui-state-disabled" );

			// clean up events and states
			this.bindings.unbind( this.eventNamespace );
			this.hoverable.removeClass( "ui-state-hover" );
			this.focusable.removeClass( "ui-state-focus" );
		},
		_destroy: $.noop,

		widget: function() {
			return this.element;
		},

		option: function( key, value ) {
			var options = key,
				parts,
				curOption,
				i;

			if ( arguments.length === 0 ) {
				// don't return a reference to the internal hash
				return $.widget.extend( {}, this.options );
			}

			if ( typeof key === "string" ) {
				// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
				options = {};
				parts = key.split( "." );
				key = parts.shift();
				if ( parts.length ) {
					curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
					for ( i = 0; i < parts.length - 1; i++ ) {
						curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
						curOption = curOption[ parts[ i ] ];
					}
					key = parts.pop();
					if ( arguments.length === 1 ) {
						return curOption[ key ] === undefined ? null : curOption[ key ];
					}
					curOption[ key ] = value;
				} else {
					if ( arguments.length === 1 ) {
						return this.options[ key ] === undefined ? null : this.options[ key ];
					}
					options[ key ] = value;
				}
			}

			this._setOptions( options );

			return this;
		},
		_setOptions: function( options ) {
			var key;

			for ( key in options ) {
				this._setOption( key, options[ key ] );
			}

			return this;
		},
		_setOption: function( key, value ) {
			this.options[ key ] = value;

			if ( key === "disabled" ) {
				this.widget()
					.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
					.attr( "aria-disabled", value );
				this.hoverable.removeClass( "ui-state-hover" );
				this.focusable.removeClass( "ui-state-focus" );
			}

			return this;
		},

		enable: function() {
			return this._setOption( "disabled", false );
		},
		disable: function() {
			return this._setOption( "disabled", true );
		},

		_on: function( suppressDisabledCheck, element, handlers ) {
			var delegateElement,
				instance = this;

			// no suppressDisabledCheck flag, shuffle arguments
			if ( typeof suppressDisabledCheck !== "boolean" ) {
				handlers = element;
				element = suppressDisabledCheck;
				suppressDisabledCheck = false;
			}

			// no element argument, shuffle and use this.element
			if ( !handlers ) {
				handlers = element;
				element = this.element;
				delegateElement = this.widget();
			} else {
				// accept selectors, DOM elements
				element = delegateElement = $( element );
				this.bindings = this.bindings.add( element );
			}

			$.each( handlers, function( event, handler ) {
				function handlerProxy() {
					// allow widgets to customize the disabled handling
					// - disabled as an array instead of boolean
					// - disabled class as method for disabling individual parts
					if ( !suppressDisabledCheck &&
							( instance.options.disabled === true ||
								$( this ).hasClass( "ui-state-disabled" ) ) ) {
						return;
					}
					return ( typeof handler === "string" ? instance[ handler ] : handler )
						.apply( instance, arguments );
				}

				// copy the guid so direct unbinding works
				if ( typeof handler !== "string" ) {
					handlerProxy.guid = handler.guid =
						handler.guid || handlerProxy.guid || $.guid++;
				}

				var match = event.match( /^(\w+)\s*(.*)$/ ),
					eventName = match[1] + instance.eventNamespace,
					selector = match[2];
				if ( selector ) {
					delegateElement.delegate( selector, eventName, handlerProxy );
				} else {
					element.bind( eventName, handlerProxy );
				}
			});
		},

		_off: function( element, eventName ) {
			eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
			element.unbind( eventName ).undelegate( eventName );
		},

		_delay: function( handler, delay ) {
			function handlerProxy() {
				return ( typeof handler === "string" ? instance[ handler ] : handler )
					.apply( instance, arguments );
			}
			var instance = this;
			return setTimeout( handlerProxy, delay || 0 );
		},

		_hoverable: function( element ) {
			this.hoverable = this.hoverable.add( element );
			this._on( element, {
				mouseenter: function( event ) {
					$( event.currentTarget ).addClass( "ui-state-hover" );
				},
				mouseleave: function( event ) {
					$( event.currentTarget ).removeClass( "ui-state-hover" );
				}
			});
		},

		_focusable: function( element ) {
			this.focusable = this.focusable.add( element );
			this._on( element, {
				focusin: function( event ) {
					$( event.currentTarget ).addClass( "ui-state-focus" );
				},
				focusout: function( event ) {
					$( event.currentTarget ).removeClass( "ui-state-focus" );
				}
			});
		},

		_trigger: function( type, event, data ) {
			var prop, orig,
				callback = this.options[ type ];

			data = data || {};
			event = $.Event( event );
			event.type = ( type === this.widgetEventPrefix ?
				type :
				this.widgetEventPrefix + type ).toLowerCase();
			// the original event may come from any element
			// so we need to reset the target on the new event
			event.target = this.element[ 0 ];

			// copy original event properties over to the new event
			orig = event.originalEvent;
			if ( orig ) {
				for ( prop in orig ) {
					if ( !( prop in event ) ) {
						event[ prop ] = orig[ prop ];
					}
				}
			}

			this.element.trigger( event, data );
			return !( $.isFunction( callback ) &&
				callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
				event.isDefaultPrevented() );
		}
	};

	$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
		$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
			if ( typeof options === "string" ) {
				options = { effect: options };
			}
			var hasOptions,
				effectName = !options ?
					method :
					options === true || typeof options === "number" ?
						defaultEffect :
						options.effect || defaultEffect;
			options = options || {};
			if ( typeof options === "number" ) {
				options = { duration: options };
			}
			hasOptions = !$.isEmptyObject( options );
			options.complete = callback;
			if ( options.delay ) {
				element.delay( options.delay );
			}
			if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
				element[ method ]( options );
			} else if ( effectName !== method && element[ effectName ] ) {
				element[ effectName ]( options.duration, options.easing, callback );
			} else {
				element.queue(function( next ) {
					$( this )[ method ]();
					if ( callback ) {
						callback.call( element[ 0 ] );
					}
					next();
				});
			}
		};
	});

	})( jQuery );
	(function( $, undefined ) {

	var mouseHandled = false;
	$( document ).mouseup( function() {
		mouseHandled = false;
	});

	$.widget("ui.mouse", {
		version: "1.10.4",
		options: {
			cancel: "input,textarea,button,select,option",
			distance: 1,
			delay: 0
		},
		_mouseInit: function() {
			var that = this;

			this.element
				.bind("mousedown."+this.widgetName, function(event) {
					return that._mouseDown(event);
				})
				.bind("click."+this.widgetName, function(event) {
					if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
						$.removeData(event.target, that.widgetName + ".preventClickEvent");
						event.stopImmediatePropagation();
						return false;
					}
				});

			this.started = false;
		},

		// TODO: make sure destroying one instance of mouse doesn't mess with
		// other instances of mouse
		_mouseDestroy: function() {
			this.element.unbind("."+this.widgetName);
			if ( this._mouseMoveDelegate ) {
				$(document)
					.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
					.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
			}
		},

		_mouseDown: function(event) {
			// don't let more than one widget handle mouseStart
			if( mouseHandled ) { return; }

			// we may have missed mouseup (out of window)
			(this._mouseStarted && this._mouseUp(event));

			this._mouseDownEvent = event;

			var that = this,
				btnIsLeft = (event.which === 1),
				// event.target.nodeName works around a bug in IE 8 with
				// disabled inputs (#7620)
				elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
			if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
				return true;
			}

			this.mouseDelayMet = !this.options.delay;
			if (!this.mouseDelayMet) {
				this._mouseDelayTimer = setTimeout(function() {
					that.mouseDelayMet = true;
				}, this.options.delay);
			}

			if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
				this._mouseStarted = (this._mouseStart(event) !== false);
				if (!this._mouseStarted) {
					event.preventDefault();
					return true;
				}
			}

			// Click event may never have fired (Gecko & Opera)
			if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
				$.removeData(event.target, this.widgetName + ".preventClickEvent");
			}

			// these delegates are required to keep context
			this._mouseMoveDelegate = function(event) {
				return that._mouseMove(event);
			};
			this._mouseUpDelegate = function(event) {
				return that._mouseUp(event);
			};
			$(document)
				.bind("mousemove."+this.widgetName, this._mouseMoveDelegate)
				.bind("mouseup."+this.widgetName, this._mouseUpDelegate);

			event.preventDefault();

			mouseHandled = true;
			return true;
		},

		_mouseMove: function(event) {
			// IE mouseup check - mouseup happened when mouse was out of window
			if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) {
				return this._mouseUp(event);
			}

			if (this._mouseStarted) {
				this._mouseDrag(event);
				return event.preventDefault();
			}

			if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
				this._mouseStarted =
					(this._mouseStart(this._mouseDownEvent, event) !== false);
				(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
			}

			return !this._mouseStarted;
		},

		_mouseUp: function(event) {
			$(document)
				.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
				.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);

			if (this._mouseStarted) {
				this._mouseStarted = false;

				if (event.target === this._mouseDownEvent.target) {
					$.data(event.target, this.widgetName + ".preventClickEvent", true);
				}

				this._mouseStop(event);
			}

			return false;
		},

		_mouseDistanceMet: function(event) {
			return (Math.max(
					Math.abs(this._mouseDownEvent.pageX - event.pageX),
					Math.abs(this._mouseDownEvent.pageY - event.pageY)
				) >= this.options.distance
			);
		},

		_mouseDelayMet: function(/* event */) {
			return this.mouseDelayMet;
		},

		// These are placeholder methods, to be overriden by extending plugin
		_mouseStart: function(/* event */) {},
		_mouseDrag: function(/* event */) {},
		_mouseStop: function(/* event */) {},
		_mouseCapture: function(/* event */) { return true; }
	});

	})(jQuery);
	(function( $, undefined ) {

	$.ui = $.ui || {};

	var cachedScrollbarWidth,
		max = Math.max,
		abs = Math.abs,
		round = Math.round,
		rhorizontal = /left|center|right/,
		rvertical = /top|center|bottom/,
		roffset = /[\+\-]\d+(\.[\d]+)?%?/,
		rposition = /^\w+/,
		rpercent = /%$/,
		_position = $.fn.position;

	function getOffsets( offsets, width, height ) {
		return [
			parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
			parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
		];
	}

	function parseCss( element, property ) {
		return parseInt( $.css( element, property ), 10 ) || 0;
	}

	function getDimensions( elem ) {
		var raw = elem[0];
		if ( raw.nodeType === 9 ) {
			return {
				width: elem.width(),
				height: elem.height(),
				offset: { top: 0, left: 0 }
			};
		}
		if ( $.isWindow( raw ) ) {
			return {
				width: elem.width(),
				height: elem.height(),
				offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
			};
		}
		if ( raw.preventDefault ) {
			return {
				width: 0,
				height: 0,
				offset: { top: raw.pageY, left: raw.pageX }
			};
		}
		return {
			width: elem.outerWidth(),
			height: elem.outerHeight(),
			offset: elem.offset()
		};
	}

	$.position = {
		scrollbarWidth: function() {
			if ( cachedScrollbarWidth !== undefined ) {
				return cachedScrollbarWidth;
			}
			var w1, w2,
				div = $( "<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
				innerDiv = div.children()[0];

			$( "body" ).append( div );
			w1 = innerDiv.offsetWidth;
			div.css( "overflow", "scroll" );

			w2 = innerDiv.offsetWidth;

			if ( w1 === w2 ) {
				w2 = div[0].clientWidth;
			}

			div.remove();

			return (cachedScrollbarWidth = w1 - w2);
		},
		getScrollInfo: function( within ) {
			var overflowX = within.isWindow || within.isDocument ? "" :
					within.element.css( "overflow-x" ),
				overflowY = within.isWindow || within.isDocument ? "" :
					within.element.css( "overflow-y" ),
				hasOverflowX = overflowX === "scroll" ||
					( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
				hasOverflowY = overflowY === "scroll" ||
					( overflowY === "auto" && within.height < within.element[0].scrollHeight );
			return {
				width: hasOverflowY ? $.position.scrollbarWidth() : 0,
				height: hasOverflowX ? $.position.scrollbarWidth() : 0
			};
		},
		getWithinInfo: function( element ) {
			var withinElement = $( element || window ),
				isWindow = $.isWindow( withinElement[0] ),
				isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9;
			return {
				element: withinElement,
				isWindow: isWindow,
				isDocument: isDocument,
				offset: withinElement.offset() || { left: 0, top: 0 },
				scrollLeft: withinElement.scrollLeft(),
				scrollTop: withinElement.scrollTop(),
				width: isWindow ? withinElement.width() : withinElement.outerWidth(),
				height: isWindow ? withinElement.height() : withinElement.outerHeight()
			};
		}
	};

	$.fn.position = function( options ) {
		if ( !options || !options.of ) {
			return _position.apply( this, arguments );
		}

		// make a copy, we don't want to modify arguments
		options = $.extend( {}, options );

		var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
			target = $( options.of ),
			within = $.position.getWithinInfo( options.within ),
			scrollInfo = $.position.getScrollInfo( within ),
			collision = ( options.collision || "flip" ).split( " " ),
			offsets = {};

		dimensions = getDimensions( target );
		if ( target[0].preventDefault ) {
			// force left top to allow flipping
			options.at = "left top";
		}
		targetWidth = dimensions.width;
		targetHeight = dimensions.height;
		targetOffset = dimensions.offset;
		// clone to reuse original targetOffset later
		basePosition = $.extend( {}, targetOffset );

		// force my and at to have valid horizontal and vertical positions
		// if a value is missing or invalid, it will be converted to center
		$.each( [ "my", "at" ], function() {
			var pos = ( options[ this ] || "" ).split( " " ),
				horizontalOffset,
				verticalOffset;

			if ( pos.length === 1) {
				pos = rhorizontal.test( pos[ 0 ] ) ?
					pos.concat( [ "center" ] ) :
					rvertical.test( pos[ 0 ] ) ?
						[ "center" ].concat( pos ) :
						[ "center", "center" ];
			}
			pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
			pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";

			// calculate offsets
			horizontalOffset = roffset.exec( pos[ 0 ] );
			verticalOffset = roffset.exec( pos[ 1 ] );
			offsets[ this ] = [
				horizontalOffset ? horizontalOffset[ 0 ] : 0,
				verticalOffset ? verticalOffset[ 0 ] : 0
			];

			// reduce to just the positions without the offsets
			options[ this ] = [
				rposition.exec( pos[ 0 ] )[ 0 ],
				rposition.exec( pos[ 1 ] )[ 0 ]
			];
		});

		// normalize collision option
		if ( collision.length === 1 ) {
			collision[ 1 ] = collision[ 0 ];
		}

		if ( options.at[ 0 ] === "right" ) {
			basePosition.left += targetWidth;
		} else if ( options.at[ 0 ] === "center" ) {
			basePosition.left += targetWidth / 2;
		}

		if ( options.at[ 1 ] === "bottom" ) {
			basePosition.top += targetHeight;
		} else if ( options.at[ 1 ] === "center" ) {
			basePosition.top += targetHeight / 2;
		}

		atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
		basePosition.left += atOffset[ 0 ];
		basePosition.top += atOffset[ 1 ];

		return this.each(function() {
			var collisionPosition, using,
				elem = $( this ),
				elemWidth = elem.outerWidth(),
				elemHeight = elem.outerHeight(),
				marginLeft = parseCss( this, "marginLeft" ),
				marginTop = parseCss( this, "marginTop" ),
				collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
				collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
				position = $.extend( {}, basePosition ),
				myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );

			if ( options.my[ 0 ] === "right" ) {
				position.left -= elemWidth;
			} else if ( options.my[ 0 ] === "center" ) {
				position.left -= elemWidth / 2;
			}

			if ( options.my[ 1 ] === "bottom" ) {
				position.top -= elemHeight;
			} else if ( options.my[ 1 ] === "center" ) {
				position.top -= elemHeight / 2;
			}

			position.left += myOffset[ 0 ];
			position.top += myOffset[ 1 ];

			// if the browser doesn't support fractions, then round for consistent results
			if ( !$.support.offsetFractions ) {
				position.left = round( position.left );
				position.top = round( position.top );
			}

			collisionPosition = {
				marginLeft: marginLeft,
				marginTop: marginTop
			};

			$.each( [ "left", "top" ], function( i, dir ) {
				if ( $.ui.position[ collision[ i ] ] ) {
					$.ui.position[ collision[ i ] ][ dir ]( position, {
						targetWidth: targetWidth,
						targetHeight: targetHeight,
						elemWidth: elemWidth,
						elemHeight: elemHeight,
						collisionPosition: collisionPosition,
						collisionWidth: collisionWidth,
						collisionHeight: collisionHeight,
						offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
						my: options.my,
						at: options.at,
						within: within,
						elem : elem
					});
				}
			});

			if ( options.using ) {
				// adds feedback as second argument to using callback, if present
				using = function( props ) {
					var left = targetOffset.left - position.left,
						right = left + targetWidth - elemWidth,
						top = targetOffset.top - position.top,
						bottom = top + targetHeight - elemHeight,
						feedback = {
							target: {
								element: target,
								left: targetOffset.left,
								top: targetOffset.top,
								width: targetWidth,
								height: targetHeight
							},
							element: {
								element: elem,
								left: position.left,
								top: position.top,
								width: elemWidth,
								height: elemHeight
							},
							horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
							vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
						};
					if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
						feedback.horizontal = "center";
					}
					if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
						feedback.vertical = "middle";
					}
					if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
						feedback.important = "horizontal";
					} else {
						feedback.important = "vertical";
					}
					options.using.call( this, props, feedback );
				};
			}

			elem.offset( $.extend( position, { using: using } ) );
		});
	};

	$.ui.position = {
		fit: {
			left: function( position, data ) {
				var within = data.within,
					withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
					outerWidth = within.width,
					collisionPosLeft = position.left - data.collisionPosition.marginLeft,
					overLeft = withinOffset - collisionPosLeft,
					overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
					newOverRight;

				// element is wider than within
				if ( data.collisionWidth > outerWidth ) {
					// element is initially over the left side of within
					if ( overLeft > 0 && overRight <= 0 ) {
						newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
						position.left += overLeft - newOverRight;
					// element is initially over right side of within
					} else if ( overRight > 0 && overLeft <= 0 ) {
						position.left = withinOffset;
					// element is initially over both left and right sides of within
					} else {
						if ( overLeft > overRight ) {
							position.left = withinOffset + outerWidth - data.collisionWidth;
						} else {
							position.left = withinOffset;
						}
					}
				// too far left -> align with left edge
				} else if ( overLeft > 0 ) {
					position.left += overLeft;
				// too far right -> align with right edge
				} else if ( overRight > 0 ) {
					position.left -= overRight;
				// adjust based on position and margin
				} else {
					position.left = max( position.left - collisionPosLeft, position.left );
				}
			},
			top: function( position, data ) {
				var within = data.within,
					withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
					outerHeight = data.within.height,
					collisionPosTop = position.top - data.collisionPosition.marginTop,
					overTop = withinOffset - collisionPosTop,
					overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
					newOverBottom;

				// element is taller than within
				if ( data.collisionHeight > outerHeight ) {
					// element is initially over the top of within
					if ( overTop > 0 && overBottom <= 0 ) {
						newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
						position.top += overTop - newOverBottom;
					// element is initially over bottom of within
					} else if ( overBottom > 0 && overTop <= 0 ) {
						position.top = withinOffset;
					// element is initially over both top and bottom of within
					} else {
						if ( overTop > overBottom ) {
							position.top = withinOffset + outerHeight - data.collisionHeight;
						} else {
							position.top = withinOffset;
						}
					}
				// too far up -> align with top
				} else if ( overTop > 0 ) {
					position.top += overTop;
				// too far down -> align with bottom edge
				} else if ( overBottom > 0 ) {
					position.top -= overBottom;
				// adjust based on position and margin
				} else {
					position.top = max( position.top - collisionPosTop, position.top );
				}
			}
		},
		flip: {
			left: function( position, data ) {
				var within = data.within,
					withinOffset = within.offset.left + within.scrollLeft,
					outerWidth = within.width,
					offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
					collisionPosLeft = position.left - data.collisionPosition.marginLeft,
					overLeft = collisionPosLeft - offsetLeft,
					overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
					myOffset = data.my[ 0 ] === "left" ?
						-data.elemWidth :
						data.my[ 0 ] === "right" ?
							data.elemWidth :
							0,
					atOffset = data.at[ 0 ] === "left" ?
						data.targetWidth :
						data.at[ 0 ] === "right" ?
							-data.targetWidth :
							0,
					offset = -2 * data.offset[ 0 ],
					newOverRight,
					newOverLeft;

				if ( overLeft < 0 ) {
					newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
					if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
						position.left += myOffset + atOffset + offset;
					}
				}
				else if ( overRight > 0 ) {
					newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
					if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
						position.left += myOffset + atOffset + offset;
					}
				}
			},
			top: function( position, data ) {
				var within = data.within,
					withinOffset = within.offset.top + within.scrollTop,
					outerHeight = within.height,
					offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
					collisionPosTop = position.top - data.collisionPosition.marginTop,
					overTop = collisionPosTop - offsetTop,
					overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
					top = data.my[ 1 ] === "top",
					myOffset = top ?
						-data.elemHeight :
						data.my[ 1 ] === "bottom" ?
							data.elemHeight :
							0,
					atOffset = data.at[ 1 ] === "top" ?
						data.targetHeight :
						data.at[ 1 ] === "bottom" ?
							-data.targetHeight :
							0,
					offset = -2 * data.offset[ 1 ],
					newOverTop,
					newOverBottom;
				if ( overTop < 0 ) {
					newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
					if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
						position.top += myOffset + atOffset + offset;
					}
				}
				else if ( overBottom > 0 ) {
					newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
					if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
						position.top += myOffset + atOffset + offset;
					}
				}
			}
		},
		flipfit: {
			left: function() {
				$.ui.position.flip.left.apply( this, arguments );
				$.ui.position.fit.left.apply( this, arguments );
			},
			top: function() {
				$.ui.position.flip.top.apply( this, arguments );
				$.ui.position.fit.top.apply( this, arguments );
			}
		}
	};

	// fraction support test
	(function () {
		var testElement, testElementParent, testElementStyle, offsetLeft, i,
			body = document.getElementsByTagName( "body" )[ 0 ],
			div = document.createElement( "div" );

		//Create a "fake body" for testing based on method used in jQuery.support
		testElement = document.createElement( body ? "div" : "body" );
		testElementStyle = {
			visibility: "hidden",
			width: 0,
			height: 0,
			border: 0,
			margin: 0,
			background: "none"
		};
		if ( body ) {
			$.extend( testElementStyle, {
				position: "absolute",
				left: "-1000px",
				top: "-1000px"
			});
		}
		for ( i in testElementStyle ) {
			testElement.style[ i ] = testElementStyle[ i ];
		}
		testElement.appendChild( div );
		testElementParent = body || document.documentElement;
		testElementParent.insertBefore( testElement, testElementParent.firstChild );

		div.style.cssText = "position: absolute; left: 10.7432222px;";

		offsetLeft = $( div ).offset().left;
		$.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;

		testElement.innerHTML = "";
		testElementParent.removeChild( testElement );
	})();

	}( jQuery ) );
	(function( $, undefined ) {

	var uid = 0,
		hideProps = {},
		showProps = {};

	hideProps.height = hideProps.paddingTop = hideProps.paddingBottom =
		hideProps.borderTopWidth = hideProps.borderBottomWidth = "hide";
	showProps.height = showProps.paddingTop = showProps.paddingBottom =
		showProps.borderTopWidth = showProps.borderBottomWidth = "show";

	$.widget( "ui.accordion", {
		version: "1.10.4",
		options: {
			active: 0,
			animate: {},
			collapsible: false,
			event: "click",
			header: "> li > :first-child,> :not(li):even",
			heightStyle: "auto",
			icons: {
				activeHeader: "ui-icon-triangle-1-s",
				header: "ui-icon-triangle-1-e"
			},

			// callbacks
			activate: null,
			beforeActivate: null
		},

		_create: function() {
			var options = this.options;
			this.prevShow = this.prevHide = $();
			this.element.addClass( "ui-accordion ui-widget ui-helper-reset" )
				// ARIA
				.attr( "role", "tablist" );

			// don't allow collapsible: false and active: false / null
			if ( !options.collapsible && (options.active === false || options.active == null) ) {
				options.active = 0;
			}

			this._processPanels();
			// handle negative values
			if ( options.active < 0 ) {
				options.active += this.headers.length;
			}
			this._refresh();
		},

		_getCreateEventData: function() {
			return {
				header: this.active,
				panel: !this.active.length ? $() : this.active.next(),
				content: !this.active.length ? $() : this.active.next()
			};
		},

		_createIcons: function() {
			var icons = this.options.icons;
			if ( icons ) {
				$( "<span>" )
					.addClass( "ui-accordion-header-icon ui-icon " + icons.header )
					.prependTo( this.headers );
				this.active.children( ".ui-accordion-header-icon" )
					.removeClass( icons.header )
					.addClass( icons.activeHeader );
				this.headers.addClass( "ui-accordion-icons" );
			}
		},

		_destroyIcons: function() {
			this.headers
				.removeClass( "ui-accordion-icons" )
				.children( ".ui-accordion-header-icon" )
					.remove();
		},

		_destroy: function() {
			var contents;

			// clean up main element
			this.element
				.removeClass( "ui-accordion ui-widget ui-helper-reset" )
				.removeAttr( "role" );

			// clean up headers
			this.headers
				.removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
				.removeAttr( "role" )
				.removeAttr( "aria-expanded" )
				.removeAttr( "aria-selected" )
				.removeAttr( "aria-controls" )
				.removeAttr( "tabIndex" )
				.each(function() {
					if ( /^ui-accordion/.test( this.id ) ) {
						this.removeAttribute( "id" );
					}
				});
			this._destroyIcons();

			// clean up content panels
			contents = this.headers.next()
				.css( "display", "" )
				.removeAttr( "role" )
				.removeAttr( "aria-hidden" )
				.removeAttr( "aria-labelledby" )
				.removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" )
				.each(function() {
					if ( /^ui-accordion/.test( this.id ) ) {
						this.removeAttribute( "id" );
					}
				});
			if ( this.options.heightStyle !== "content" ) {
				contents.css( "height", "" );
			}
		},

		_setOption: function( key, value ) {
			if ( key === "active" ) {
				// _activate() will handle invalid values and update this.options
				this._activate( value );
				return;
			}

			if ( key === "event" ) {
				if ( this.options.event ) {
					this._off( this.headers, this.options.event );
				}
				this._setupEvents( value );
			}

			this._super( key, value );

			// setting collapsible: false while collapsed; open first panel
			if ( key === "collapsible" && !value && this.options.active === false ) {
				this._activate( 0 );
			}

			if ( key === "icons" ) {
				this._destroyIcons();
				if ( value ) {
					this._createIcons();
				}
			}

			// #5332 - opacity doesn't cascade to positioned elements in IE
			// so we need to add the disabled class to the headers and panels
			if ( key === "disabled" ) {
				this.headers.add( this.headers.next() )
					.toggleClass( "ui-state-disabled", !!value );
			}
		},

		_keydown: function( event ) {
			if ( event.altKey || event.ctrlKey ) {
				return;
			}

			var keyCode = $.ui.keyCode,
				length = this.headers.length,
				currentIndex = this.headers.index( event.target ),
				toFocus = false;

			switch ( event.keyCode ) {
				case keyCode.RIGHT:
				case keyCode.DOWN:
					toFocus = this.headers[ ( currentIndex + 1 ) % length ];
					break;
				case keyCode.LEFT:
				case keyCode.UP:
					toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
					break;
				case keyCode.SPACE:
				case keyCode.ENTER:
					this._eventHandler( event );
					break;
				case keyCode.HOME:
					toFocus = this.headers[ 0 ];
					break;
				case keyCode.END:
					toFocus = this.headers[ length - 1 ];
					break;
			}

			if ( toFocus ) {
				$( event.target ).attr( "tabIndex", -1 );
				$( toFocus ).attr( "tabIndex", 0 );
				toFocus.focus();
				event.preventDefault();
			}
		},

		_panelKeyDown : function( event ) {
			if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
				$( event.currentTarget ).prev().focus();
			}
		},

		refresh: function() {
			var options = this.options;
			this._processPanels();

			// was collapsed or no panel
			if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) {
				options.active = false;
				this.active = $();
			// active false only when collapsible is true
			} else if ( options.active === false ) {
				this._activate( 0 );
			// was active, but active panel is gone
			} else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
				// all remaining panel are disabled
				if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) {
					options.active = false;
					this.active = $();
				// activate previous panel
				} else {
					this._activate( Math.max( 0, options.active - 1 ) );
				}
			// was active, active panel still exists
			} else {
				// make sure active index is correct
				options.active = this.headers.index( this.active );
			}

			this._destroyIcons();

			this._refresh();
		},

		_processPanels: function() {
			this.headers = this.element.find( this.options.header )
				.addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" );

			this.headers.next()
				.addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" )
				.filter(":not(.ui-accordion-content-active)")
				.hide();
		},

		_refresh: function() {
			var maxHeight,
				options = this.options,
				heightStyle = options.heightStyle,
				parent = this.element.parent(),
				accordionId = this.accordionId = "ui-accordion-" +
					(this.element.attr( "id" ) || ++uid);

			this.active = this._findActive( options.active )
				.addClass( "ui-accordion-header-active ui-state-active ui-corner-top" )
				.removeClass( "ui-corner-all" );
			this.active.next()
				.addClass( "ui-accordion-content-active" )
				.show();

			this.headers
				.attr( "role", "tab" )
				.each(function( i ) {
					var header = $( this ),
						headerId = header.attr( "id" ),
						panel = header.next(),
						panelId = panel.attr( "id" );
					if ( !headerId ) {
						headerId = accordionId + "-header-" + i;
						header.attr( "id", headerId );
					}
					if ( !panelId ) {
						panelId = accordionId + "-panel-" + i;
						panel.attr( "id", panelId );
					}
					header.attr( "aria-controls", panelId );
					panel.attr( "aria-labelledby", headerId );
				})
				.next()
					.attr( "role", "tabpanel" );

			this.headers
				.not( this.active )
				.attr({
					"aria-selected": "false",
					"aria-expanded": "false",
					tabIndex: -1
				})
				.next()
					.attr({
						"aria-hidden": "true"
					})
					.hide();

			// make sure at least one header is in the tab order
			if ( !this.active.length ) {
				this.headers.eq( 0 ).attr( "tabIndex", 0 );
			} else {
				this.active.attr({
					"aria-selected": "true",
					"aria-expanded": "true",
					tabIndex: 0
				})
				.next()
					.attr({
						"aria-hidden": "false"
					});
			}

			this._createIcons();

			this._setupEvents( options.event );

			if ( heightStyle === "fill" ) {
				maxHeight = parent.height();
				this.element.siblings( ":visible" ).each(function() {
					var elem = $( this ),
						position = elem.css( "position" );

					if ( position === "absolute" || position === "fixed" ) {
						return;
					}
					maxHeight -= elem.outerHeight( true );
				});

				this.headers.each(function() {
					maxHeight -= $( this ).outerHeight( true );
				});

				this.headers.next()
					.each(function() {
						$( this ).height( Math.max( 0, maxHeight -
							$( this ).innerHeight() + $( this ).height() ) );
					})
					.css( "overflow", "auto" );
			} else if ( heightStyle === "auto" ) {
				maxHeight = 0;
				this.headers.next()
					.each(function() {
						maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() );
					})
					.height( maxHeight );
			}
		},

		_activate: function( index ) {
			var active = this._findActive( index )[ 0 ];

			// trying to activate the already active panel
			if ( active === this.active[ 0 ] ) {
				return;
			}

			// trying to collapse, simulate a click on the currently active header
			active = active || this.active[ 0 ];

			this._eventHandler({
				target: active,
				currentTarget: active,
				preventDefault: $.noop
			});
		},

		_findActive: function( selector ) {
			return typeof selector === "number" ? this.headers.eq( selector ) : $();
		},

		_setupEvents: function( event ) {
			var events = {
				keydown: "_keydown"
			};
			if ( event ) {
				$.each( event.split(" "), function( index, eventName ) {
					events[ eventName ] = "_eventHandler";
				});
			}

			this._off( this.headers.add( this.headers.next() ) );
			this._on( this.headers, events );
			this._on( this.headers.next(), { keydown: "_panelKeyDown" });
			this._hoverable( this.headers );
			this._focusable( this.headers );
		},

		_eventHandler: function( event ) {
			var options = this.options,
				active = this.active,
				clicked = $( event.currentTarget ),
				clickedIsActive = clicked[ 0 ] === active[ 0 ],
				collapsing = clickedIsActive && options.collapsible,
				toShow = collapsing ? $() : clicked.next(),
				toHide = active.next(),
				eventData = {
					oldHeader: active,
					oldPanel: toHide,
					newHeader: collapsing ? $() : clicked,
					newPanel: toShow
				};

			event.preventDefault();

			if (
					// click on active header, but not collapsible
					( clickedIsActive && !options.collapsible ) ||
					// allow canceling activation
					( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
				return;
			}

			options.active = collapsing ? false : this.headers.index( clicked );

			// when the call to ._toggle() comes after the class changes
			// it causes a very odd bug in IE 8 (see #6720)
			this.active = clickedIsActive ? $() : clicked;
			this._toggle( eventData );

			// switch classes
			// corner classes on the previously active header stay after the animation
			active.removeClass( "ui-accordion-header-active ui-state-active" );
			if ( options.icons ) {
				active.children( ".ui-accordion-header-icon" )
					.removeClass( options.icons.activeHeader )
					.addClass( options.icons.header );
			}

			if ( !clickedIsActive ) {
				clicked
					.removeClass( "ui-corner-all" )
					.addClass( "ui-accordion-header-active ui-state-active ui-corner-top" );
				if ( options.icons ) {
					clicked.children( ".ui-accordion-header-icon" )
						.removeClass( options.icons.header )
						.addClass( options.icons.activeHeader );
				}

				clicked
					.next()
					.addClass( "ui-accordion-content-active" );
			}
		},

		_toggle: function( data ) {
			var toShow = data.newPanel,
				toHide = this.prevShow.length ? this.prevShow : data.oldPanel;

			// handle activating a panel during the animation for another activation
			this.prevShow.add( this.prevHide ).stop( true, true );
			this.prevShow = toShow;
			this.prevHide = toHide;

			if ( this.options.animate ) {
				this._animate( toShow, toHide, data );
			} else {
				toHide.hide();
				toShow.show();
				this._toggleComplete( data );
			}

			toHide.attr({
				"aria-hidden": "true"
			});
			toHide.prev().attr( "aria-selected", "false" );
			// if we're switching panels, remove the old header from the tab order
			// if we're opening from collapsed state, remove the previous header from the tab order
			// if we're collapsing, then keep the collapsing header in the tab order
			if ( toShow.length && toHide.length ) {
				toHide.prev().attr({
					"tabIndex": -1,
					"aria-expanded": "false"
				});
			} else if ( toShow.length ) {
				this.headers.filter(function() {
					return $( this ).attr( "tabIndex" ) === 0;
				})
				.attr( "tabIndex", -1 );
			}

			toShow
				.attr( "aria-hidden", "false" )
				.prev()
					.attr({
						"aria-selected": "true",
						tabIndex: 0,
						"aria-expanded": "true"
					});
		},

		_animate: function( toShow, toHide, data ) {
			var total, easing, duration,
				that = this,
				adjust = 0,
				down = toShow.length &&
					( !toHide.length || ( toShow.index() < toHide.index() ) ),
				animate = this.options.animate || {},
				options = down && animate.down || animate,
				complete = function() {
					that._toggleComplete( data );
				};

			if ( typeof options === "number" ) {
				duration = options;
			}
			if ( typeof options === "string" ) {
				easing = options;
			}
			// fall back from options to animation in case of partial down settings
			easing = easing || options.easing || animate.easing;
			duration = duration || options.duration || animate.duration;

			if ( !toHide.length ) {
				return toShow.animate( showProps, duration, easing, complete );
			}
			if ( !toShow.length ) {
				return toHide.animate( hideProps, duration, easing, complete );
			}

			total = toShow.show().outerHeight();
			toHide.animate( hideProps, {
				duration: duration,
				easing: easing,
				step: function( now, fx ) {
					fx.now = Math.round( now );
				}
			});
			toShow
				.hide()
				.animate( showProps, {
					duration: duration,
					easing: easing,
					complete: complete,
					step: function( now, fx ) {
						fx.now = Math.round( now );
						if ( fx.prop !== "height" ) {
							adjust += fx.now;
						} else if ( that.options.heightStyle !== "content" ) {
							fx.now = Math.round( total - toHide.outerHeight() - adjust );
							adjust = 0;
						}
					}
				});
		},

		_toggleComplete: function( data ) {
			var toHide = data.oldPanel;

			toHide
				.removeClass( "ui-accordion-content-active" )
				.prev()
					.removeClass( "ui-corner-top" )
					.addClass( "ui-corner-all" );

			// Work around for rendering bug in IE (#5421)
			if ( toHide.length ) {
				toHide.parent()[0].className = toHide.parent()[0].className;
			}
			this._trigger( "activate", null, data );
		}
	});

	})( jQuery );
	(function( $, undefined ) {

	$.widget( "ui.autocomplete", {
		version: "1.10.4",
		defaultElement: "<input>",
		options: {
			appendTo: null,
			autoFocus: false,
			delay: 300,
			minLength: 1,
			position: {
				my: "left top",
				at: "left bottom",
				collision: "none"
			},
			source: null,

			// callbacks
			change: null,
			close: null,
			focus: null,
			open: null,
			response: null,
			search: null,
			select: null
		},

		requestIndex: 0,
		pending: 0,

		_create: function() {
			// Some browsers only repeat keydown events, not keypress events,
			// so we use the suppressKeyPress flag to determine if we've already
			// handled the keydown event. #7269
			// Unfortunately the code for & in keypress is the same as the up arrow,
			// so we use the suppressKeyPressRepeat flag to avoid handling keypress
			// events when we know the keydown event was used to modify the
			// search term. #7799
			var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
				nodeName = this.element[0].nodeName.toLowerCase(),
				isTextarea = nodeName === "textarea",
				isInput = nodeName === "input";

			this.isMultiLine =
				// Textareas are always multi-line
				isTextarea ? true :
				// Inputs are always single-line, even if inside a contentEditable element
				// IE also treats inputs as contentEditable
				isInput ? false :
				// All other element types are determined by whether or not they're contentEditable
				this.element.prop( "isContentEditable" );

			this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
			this.isNewMenu = true;

			this.element
				.addClass( "ui-autocomplete-input" )
				.attr( "autocomplete", "off" );

			this._on( this.element, {
				keydown: function( event ) {
					if ( this.element.prop( "readOnly" ) ) {
						suppressKeyPress = true;
						suppressInput = true;
						suppressKeyPressRepeat = true;
						return;
					}

					suppressKeyPress = false;
					suppressInput = false;
					suppressKeyPressRepeat = false;
					var keyCode = $.ui.keyCode;
					switch( event.keyCode ) {
					case keyCode.PAGE_UP:
						suppressKeyPress = true;
						this._move( "previousPage", event );
						break;
					case keyCode.PAGE_DOWN:
						suppressKeyPress = true;
						this._move( "nextPage", event );
						break;
					case keyCode.UP:
						suppressKeyPress = true;
						this._keyEvent( "previous", event );
						break;
					case keyCode.DOWN:
						suppressKeyPress = true;
						this._keyEvent( "next", event );
						break;
					case keyCode.ENTER:
					case keyCode.NUMPAD_ENTER:
						// when menu is open and has focus
						if ( this.menu.active ) {
							// #6055 - Opera still allows the keypress to occur
							// which causes forms to submit
							suppressKeyPress = true;
							event.preventDefault();
							this.menu.select( event );
						}
						break;
					case keyCode.TAB:
						if ( this.menu.active ) {
							this.menu.select( event );
						}
						break;
					case keyCode.ESCAPE:
						if ( this.menu.element.is( ":visible" ) ) {
							this._value( this.term );
							this.close( event );
							// Different browsers have different default behavior for escape
							// Single press can mean undo or clear
							// Double press in IE means clear the whole form
							event.preventDefault();
						}
						break;
					default:
						suppressKeyPressRepeat = true;
						// search timeout should be triggered before the input value is changed
						this._searchTimeout( event );
						break;
					}
				},
				keypress: function( event ) {
					if ( suppressKeyPress ) {
						suppressKeyPress = false;
						if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
							event.preventDefault();
						}
						return;
					}
					if ( suppressKeyPressRepeat ) {
						return;
					}

					// replicate some key handlers to allow them to repeat in Firefox and Opera
					var keyCode = $.ui.keyCode;
					switch( event.keyCode ) {
					case keyCode.PAGE_UP:
						this._move( "previousPage", event );
						break;
					case keyCode.PAGE_DOWN:
						this._move( "nextPage", event );
						break;
					case keyCode.UP:
						this._keyEvent( "previous", event );
						break;
					case keyCode.DOWN:
						this._keyEvent( "next", event );
						break;
					}
				},
				input: function( event ) {
					if ( suppressInput ) {
						suppressInput = false;
						event.preventDefault();
						return;
					}
					this._searchTimeout( event );
				},
				focus: function() {
					this.selectedItem = null;
					this.previous = this._value();
				},
				blur: function( event ) {
					if ( this.cancelBlur ) {
						delete this.cancelBlur;
						return;
					}

					clearTimeout( this.searching );
					this.close( event );
					this._change( event );
				}
			});

			this._initSource();
			this.menu = $( "<ul>" )
				.addClass( "ui-autocomplete ui-front" )
				.appendTo( this._appendTo() )
				.menu({
					// disable ARIA support, the live region takes care of that
					role: null
				})
				.hide()
				.data( "ui-menu" );

			this._on( this.menu.element, {
				mousedown: function( event ) {
					// prevent moving focus out of the text field
					event.preventDefault();

					// IE doesn't prevent moving focus even with event.preventDefault()
					// so we set a flag to know when we should ignore the blur event
					this.cancelBlur = true;
					this._delay(function() {
						delete this.cancelBlur;
					});

					// clicking on the scrollbar causes focus to shift to the body
					// but we can't detect a mouseup or a click immediately afterward
					// so we have to track the next mousedown and close the menu if
					// the user clicks somewhere outside of the autocomplete
					var menuElement = this.menu.element[ 0 ];
					if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
						this._delay(function() {
							var that = this;
							this.document.one( "mousedown", function( event ) {
								if ( event.target !== that.element[ 0 ] &&
										event.target !== menuElement &&
										!$.contains( menuElement, event.target ) ) {
									that.close();
								}
							});
						});
					}
				},
				menufocus: function( event, ui ) {
					// support: Firefox
					// Prevent accidental activation of menu items in Firefox (#7024 #9118)
					if ( this.isNewMenu ) {
						this.isNewMenu = false;
						if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
							this.menu.blur();

							this.document.one( "mousemove", function() {
								$( event.target ).trigger( event.originalEvent );
							});

							return;
						}
					}

					var item = ui.item.data( "ui-autocomplete-item" );
					if ( false !== this._trigger( "focus", event, { item: item } ) ) {
						// use value to match what will end up in the input, if it was a key event
						if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
							this._value( item.value );
						}
					} else {
						// Normally the input is populated with the item's value as the
						// menu is navigated, causing screen readers to notice a change and
						// announce the item. Since the focus event was canceled, this doesn't
						// happen, so we update the live region so that screen readers can
						// still notice the change and announce it.
						this.liveRegion.text( item.value );
					}
				},
				menuselect: function( event, ui ) {
					var item = ui.item.data( "ui-autocomplete-item" ),
						previous = this.previous;

					// only trigger when focus was lost (click on menu)
					if ( this.element[0] !== this.document[0].activeElement ) {
						this.element.focus();
						this.previous = previous;
						// #6109 - IE triggers two focus events and the second
						// is asynchronous, so we need to reset the previous
						// term synchronously and asynchronously :-(
						this._delay(function() {
							this.previous = previous;
							this.selectedItem = item;
						});
					}

					if ( false !== this._trigger( "select", event, { item: item } ) ) {
						this._value( item.value );
					}
					// reset the term after the select event
					// this allows custom select handling to work properly
					this.term = this._value();

					this.close( event );
					this.selectedItem = item;
				}
			});

			this.liveRegion = $( "<span>", {
					role: "status",
					"aria-live": "polite"
				})
				.addClass( "ui-helper-hidden-accessible" )
				.insertBefore( this.element );

			// turning off autocomplete prevents the browser from remembering the
			// value when navigating through history, so we re-enable autocomplete
			// if the page is unloaded before the widget is destroyed. #7790
			this._on( this.window, {
				beforeunload: function() {
					this.element.removeAttr( "autocomplete" );
				}
			});
		},

		_destroy: function() {
			clearTimeout( this.searching );
			this.element
				.removeClass( "ui-autocomplete-input" )
				.removeAttr( "autocomplete" );
			this.menu.element.remove();
			this.liveRegion.remove();
		},

		_setOption: function( key, value ) {
			this._super( key, value );
			if ( key === "source" ) {
				this._initSource();
			}
			if ( key === "appendTo" ) {
				this.menu.element.appendTo( this._appendTo() );
			}
			if ( key === "disabled" && value && this.xhr ) {
				this.xhr.abort();
			}
		},

		_appendTo: function() {
			var element = this.options.appendTo;

			if ( element ) {
				element = element.jquery || element.nodeType ?
					$( element ) :
					this.document.find( element ).eq( 0 );
			}

			if ( !element ) {
				element = this.element.closest( ".ui-front" );
			}

			if ( !element.length ) {
				element = this.document[0].body;
			}

			return element;
		},

		_initSource: function() {
			var array, url,
				that = this;
			if ( $.isArray(this.options.source) ) {
				array = this.options.source;
				this.source = function( request, response ) {
					response( $.ui.autocomplete.filter( array, request.term ) );
				};
			} else if ( typeof this.options.source === "string" ) {
				url = this.options.source;
				this.source = function( request, response ) {
					if ( that.xhr ) {
						that.xhr.abort();
					}
					that.xhr = $.ajax({
						url: url,
						data: request,
						dataType: "json",
						success: function( data ) {
							response( data );
						},
						error: function() {
							response( [] );
						}
					});
				};
			} else {
				this.source = this.options.source;
			}
		},

		_searchTimeout: function( event ) {
			clearTimeout( this.searching );
			this.searching = this._delay(function() {
				// only search if the value has changed
				if ( this.term !== this._value() ) {
					this.selectedItem = null;
					this.search( null, event );
				}
			}, this.options.delay );
		},

		search: function( value, event ) {
			value = value != null ? value : this._value();

			// always save the actual value, not the one passed as an argument
			this.term = this._value();

			if ( value.length < this.options.minLength ) {
				return this.close( event );
			}

			if ( this._trigger( "search", event ) === false ) {
				return;
			}

			return this._search( value );
		},

		_search: function( value ) {
			this.pending++;
			this.element.addClass( "ui-autocomplete-loading" );
			this.cancelSearch = false;

			this.source( { term: value }, this._response() );
		},

		_response: function() {
			var index = ++this.requestIndex;

			return $.proxy(function( content ) {
				if ( index === this.requestIndex ) {
					this.__response( content );
				}

				this.pending--;
				if ( !this.pending ) {
					this.element.removeClass( "ui-autocomplete-loading" );
				}
			}, this );
		},

		__response: function( content ) {
			if ( content ) {
				content = this._normalize( content );
			}
			this._trigger( "response", null, { content: content } );
			if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
				this._suggest( content );
				this._trigger( "open" );
			} else {
				// use ._close() instead of .close() so we don't cancel future searches
				this._close();
			}
		},

		close: function( event ) {
			this.cancelSearch = true;
			this._close( event );
		},

		_close: function( event ) {
			if ( this.menu.element.is( ":visible" ) ) {
				this.menu.element.hide();
				this.menu.blur();
				this.isNewMenu = true;
				this._trigger( "close", event );
			}
		},

		_change: function( event ) {
			if ( this.previous !== this._value() ) {
				this._trigger( "change", event, { item: this.selectedItem } );
			}
		},

		_normalize: function( items ) {
			// assume all items have the right format when the first item is complete
			if ( items.length && items[0].label && items[0].value ) {
				return items;
			}
			return $.map( items, function( item ) {
				if ( typeof item === "string" ) {
					return {
						label: item,
						value: item
					};
				}
				return $.extend({
					label: item.label || item.value,
					value: item.value || item.label
				}, item );
			});
		},

		_suggest: function( items ) {
			var ul = this.menu.element.empty();
			this._renderMenu( ul, items );
			this.isNewMenu = true;
			this.menu.refresh();

			// size and position menu
			ul.show();
			this._resizeMenu();
			ul.position( $.extend({
				of: this.element
			}, this.options.position ));

			if ( this.options.autoFocus ) {
				this.menu.next();
			}
		},

		_resizeMenu: function() {
			var ul = this.menu.element;
			ul.outerWidth( Math.max(
				// Firefox wraps long text (possibly a rounding bug)
				// so we add 1px to avoid the wrapping (#7513)
				ul.width( "" ).outerWidth() + 1,
				this.element.outerWidth()
			) );
		},

		_renderMenu: function( ul, items ) {
			var that = this;
			$.each( items, function( index, item ) {
				that._renderItemData( ul, item );
			});
		},

		_renderItemData: function( ul, item ) {
			return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
		},

		_renderItem: function( ul, item ) {
			return $( "<li>" )
				.append( $( "<a>" ).text( item.label ) )
				.appendTo( ul );
		},

		_move: function( direction, event ) {
			if ( !this.menu.element.is( ":visible" ) ) {
				this.search( null, event );
				return;
			}
			if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
					this.menu.isLastItem() && /^next/.test( direction ) ) {
				this._value( this.term );
				this.menu.blur();
				return;
			}
			this.menu[ direction ]( event );
		},

		widget: function() {
			return this.menu.element;
		},

		_value: function() {
			return this.valueMethod.apply( this.element, arguments );
		},

		_keyEvent: function( keyEvent, event ) {
			if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
				this._move( keyEvent, event );

				// prevents moving cursor to beginning/end of the text field in some browsers
				event.preventDefault();
			}
		}
	});

	$.extend( $.ui.autocomplete, {
		escapeRegex: function( value ) {
			return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
		},
		filter: function(array, term) {
			var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
			return $.grep( array, function(value) {
				return matcher.test( value.label || value.value || value );
			});
		}
	});


	// live region extension, adding a `messages` option
	// NOTE: This is an experimental API. We are still investigating
	// a full solution for string manipulation and internationalization.
	$.widget( "ui.autocomplete", $.ui.autocomplete, {
		options: {
			messages: {
				noResults: "No search results.",
				results: function( amount ) {
					return amount + ( amount > 1 ? " results are" : " result is" ) +
						" available, use up and down arrow keys to navigate.";
				}
			}
		},

		__response: function( content ) {
			var message;
			this._superApply( arguments );
			if ( this.options.disabled || this.cancelSearch ) {
				return;
			}
			if ( content && content.length ) {
				message = this.options.messages.results( content.length );
			} else {
				message = this.options.messages.noResults;
			}
			this.liveRegion.text( message );
		}
	});

	}( jQuery ));
	(function( $, undefined ) {

	var lastActive,
		baseClasses = "ui-button ui-widget ui-state-default ui-corner-all",
		typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",
		formResetHandler = function() {
			var form = $( this );
			setTimeout(function() {
				form.find( ":ui-button" ).button( "refresh" );
			}, 1 );
		},
		radioGroup = function( radio ) {
			var name = radio.name,
				form = radio.form,
				radios = $( [] );
			if ( name ) {
				name = name.replace( /'/g, "\\'" );
				if ( form ) {
					radios = $( form ).find( "[name='" + name + "']" );
				} else {
					radios = $( "[name='" + name + "']", radio.ownerDocument )
						.filter(function() {
							return !this.form;
						});
				}
			}
			return radios;
		};

	$.widget( "ui.button", {
		version: "1.10.4",
		defaultElement: "<button>",
		options: {
			disabled: null,
			text: true,
			label: null,
			icons: {
				primary: null,
				secondary: null
			}
		},
		_create: function() {
			this.element.closest( "form" )
				.unbind( "reset" + this.eventNamespace )
				.bind( "reset" + this.eventNamespace, formResetHandler );

			if ( typeof this.options.disabled !== "boolean" ) {
				this.options.disabled = !!this.element.prop( "disabled" );
			} else {
				this.element.prop( "disabled", this.options.disabled );
			}

			this._determineButtonType();
			this.hasTitle = !!this.buttonElement.attr( "title" );

			var that = this,
				options = this.options,
				toggleButton = this.type === "checkbox" || this.type === "radio",
				activeClass = !toggleButton ? "ui-state-active" : "";

			if ( options.label === null ) {
				options.label = (this.type === "input" ? this.buttonElement.val() : this.buttonElement.html());
			}

			this._hoverable( this.buttonElement );

			this.buttonElement
				.addClass( baseClasses )
				.attr( "role", "button" )
				.bind( "mouseenter" + this.eventNamespace, function() {
					if ( options.disabled ) {
						return;
					}
					if ( this === lastActive ) {
						$( this ).addClass( "ui-state-active" );
					}
				})
				.bind( "mouseleave" + this.eventNamespace, function() {
					if ( options.disabled ) {
						return;
					}
					$( this ).removeClass( activeClass );
				})
				.bind( "click" + this.eventNamespace, function( event ) {
					if ( options.disabled ) {
						event.preventDefault();
						event.stopImmediatePropagation();
					}
				});

			// Can't use _focusable() because the element that receives focus
			// and the element that gets the ui-state-focus class are different
			this._on({
				focus: function() {
					this.buttonElement.addClass( "ui-state-focus" );
				},
				blur: function() {
					this.buttonElement.removeClass( "ui-state-focus" );
				}
			});

			if ( toggleButton ) {
				this.element.bind( "change" + this.eventNamespace, function() {
					that.refresh();
				});
			}

			if ( this.type === "checkbox" ) {
				this.buttonElement.bind( "click" + this.eventNamespace, function() {
					if ( options.disabled ) {
						return false;
					}
				});
			} else if ( this.type === "radio" ) {
				this.buttonElement.bind( "click" + this.eventNamespace, function() {
					if ( options.disabled ) {
						return false;
					}
					$( this ).addClass( "ui-state-active" );
					that.buttonElement.attr( "aria-pressed", "true" );

					var radio = that.element[ 0 ];
					radioGroup( radio )
						.not( radio )
						.map(function() {
							return $( this ).button( "widget" )[ 0 ];
						})
						.removeClass( "ui-state-active" )
						.attr( "aria-pressed", "false" );
				});
			} else {
				this.buttonElement
					.bind( "mousedown" + this.eventNamespace, function() {
						if ( options.disabled ) {
							return false;
						}
						$( this ).addClass( "ui-state-active" );
						lastActive = this;
						that.document.one( "mouseup", function() {
							lastActive = null;
						});
					})
					.bind( "mouseup" + this.eventNamespace, function() {
						if ( options.disabled ) {
							return false;
						}
						$( this ).removeClass( "ui-state-active" );
					})
					.bind( "keydown" + this.eventNamespace, function(event) {
						if ( options.disabled ) {
							return false;
						}
						if ( event.keyCode === $.ui.keyCode.SPACE || event.keyCode === $.ui.keyCode.ENTER ) {
							$( this ).addClass( "ui-state-active" );
						}
					})
					// see #8559, we bind to blur here in case the button element loses
					// focus between keydown and keyup, it would be left in an "active" state
					.bind( "keyup" + this.eventNamespace + " blur" + this.eventNamespace, function() {
						$( this ).removeClass( "ui-state-active" );
					});

				if ( this.buttonElement.is("a") ) {
					this.buttonElement.keyup(function(event) {
						if ( event.keyCode === $.ui.keyCode.SPACE ) {
							// TODO pass through original event correctly (just as 2nd argument doesn't work)
							$( this ).click();
						}
					});
				}
			}

			// TODO: pull out $.Widget's handling for the disabled option into
			// $.Widget.prototype._setOptionDisabled so it's easy to proxy and can
			// be overridden by individual plugins
			this._setOption( "disabled", options.disabled );
			this._resetButton();
		},

		_determineButtonType: function() {
			var ancestor, labelSelector, checked;

			if ( this.element.is("[type=checkbox]") ) {
				this.type = "checkbox";
			} else if ( this.element.is("[type=radio]") ) {
				this.type = "radio";
			} else if ( this.element.is("input") ) {
				this.type = "input";
			} else {
				this.type = "button";
			}

			if ( this.type === "checkbox" || this.type === "radio" ) {
				// we don't search against the document in case the element
				// is disconnected from the DOM
				ancestor = this.element.parents().last();
				labelSelector = "label[for='" + this.element.attr("id") + "']";
				this.buttonElement = ancestor.find( labelSelector );
				if ( !this.buttonElement.length ) {
					ancestor = ancestor.length ? ancestor.siblings() : this.element.siblings();
					this.buttonElement = ancestor.filter( labelSelector );
					if ( !this.buttonElement.length ) {
						this.buttonElement = ancestor.find( labelSelector );
					}
				}
				this.element.addClass( "ui-helper-hidden-accessible" );

				checked = this.element.is( ":checked" );
				if ( checked ) {
					this.buttonElement.addClass( "ui-state-active" );
				}
				this.buttonElement.prop( "aria-pressed", checked );
			} else {
				this.buttonElement = this.element;
			}
		},

		widget: function() {
			return this.buttonElement;
		},

		_destroy: function() {
			this.element
				.removeClass( "ui-helper-hidden-accessible" );
			this.buttonElement
				.removeClass( baseClasses + " ui-state-active " + typeClasses )
				.removeAttr( "role" )
				.removeAttr( "aria-pressed" )
				.html( this.buttonElement.find(".ui-button-text").html() );

			if ( !this.hasTitle ) {
				this.buttonElement.removeAttr( "title" );
			}
		},

		_setOption: function( key, value ) {
			this._super( key, value );
			if ( key === "disabled" ) {
				this.element.prop( "disabled", !!value );
				if ( value ) {
					this.buttonElement.removeClass( "ui-state-focus" );
				}
				return;
			}
			this._resetButton();
		},

		refresh: function() {
			//See #8237 & #8828
			var isDisabled = this.element.is( "input, button" ) ? this.element.is( ":disabled" ) : this.element.hasClass( "ui-button-disabled" );

			if ( isDisabled !== this.options.disabled ) {
				this._setOption( "disabled", isDisabled );
			}
			if ( this.type === "radio" ) {
				radioGroup( this.element[0] ).each(function() {
					if ( $( this ).is( ":checked" ) ) {
						$( this ).button( "widget" )
							.addClass( "ui-state-active" )
							.attr( "aria-pressed", "true" );
					} else {
						$( this ).button( "widget" )
							.removeClass( "ui-state-active" )
							.attr( "aria-pressed", "false" );
					}
				});
			} else if ( this.type === "checkbox" ) {
				if ( this.element.is( ":checked" ) ) {
					this.buttonElement
						.addClass( "ui-state-active" )
						.attr( "aria-pressed", "true" );
				} else {
					this.buttonElement
						.removeClass( "ui-state-active" )
						.attr( "aria-pressed", "false" );
				}
			}
		},

		_resetButton: function() {
			if ( this.type === "input" ) {
				if ( this.options.label ) {
					this.element.val( this.options.label );
				}
				return;
			}
			var buttonElement = this.buttonElement.removeClass( typeClasses ),
				buttonText = $( "<span></span>", this.document[0] )
					.addClass( "ui-button-text" )
					.html( this.options.label )
					.appendTo( buttonElement.empty() )
					.text(),
				icons = this.options.icons,
				multipleIcons = icons.primary && icons.secondary,
				buttonClasses = [];

			if ( icons.primary || icons.secondary ) {
				if ( this.options.text ) {
					buttonClasses.push( "ui-button-text-icon" + ( multipleIcons ? "s" : ( icons.primary ? "-primary" : "-secondary" ) ) );
				}

				if ( icons.primary ) {
					buttonElement.prepend( "<span class='ui-button-icon-primary ui-icon " + icons.primary + "'></span>" );
				}

				if ( icons.secondary ) {
					buttonElement.append( "<span class='ui-button-icon-secondary ui-icon " + icons.secondary + "'></span>" );
				}

				if ( !this.options.text ) {
					buttonClasses.push( multipleIcons ? "ui-button-icons-only" : "ui-button-icon-only" );

					if ( !this.hasTitle ) {
						buttonElement.attr( "title", $.trim( buttonText ) );
					}
				}
			} else {
				buttonClasses.push( "ui-button-text-only" );
			}
			buttonElement.addClass( buttonClasses.join( " " ) );
		}
	});

	$.widget( "ui.buttonset", {
		version: "1.10.4",
		options: {
			items: "button, input[type=button], input[type=submit], input[type=reset], input[type=checkbox], input[type=radio], a, :data(ui-button)"
		},

		_create: function() {
			this.element.addClass( "ui-buttonset" );
		},

		_init: function() {
			this.refresh();
		},

		_setOption: function( key, value ) {
			if ( key === "disabled" ) {
				this.buttons.button( "option", key, value );
			}

			this._super( key, value );
		},

		refresh: function() {
			var rtl = this.element.css( "direction" ) === "rtl";

			this.buttons = this.element.find( this.options.items )
				.filter( ":ui-button" )
					.button( "refresh" )
				.end()
				.not( ":ui-button" )
					.button()
				.end()
				.map(function() {
					return $( this ).button( "widget" )[ 0 ];
				})
					.removeClass( "ui-corner-all ui-corner-left ui-corner-right" )
					.filter( ":first" )
						.addClass( rtl ? "ui-corner-right" : "ui-corner-left" )
					.end()
					.filter( ":last" )
						.addClass( rtl ? "ui-corner-left" : "ui-corner-right" )
					.end()
				.end();
		},

		_destroy: function() {
			this.element.removeClass( "ui-buttonset" );
			this.buttons
				.map(function() {
					return $( this ).button( "widget" )[ 0 ];
				})
					.removeClass( "ui-corner-left ui-corner-right" )
				.end()
				.button( "destroy" );
		}
	});

	}( jQuery ) );
	(function( $, undefined ) {

	$.extend($.ui, { datepicker: { version: "1.10.4" } });

	var PROP_NAME = "datepicker",
		instActive;

	/* Date picker manager.
	   Use the singleton instance of this class, $.datepicker, to interact with the date picker.
	   Settings for (groups of) date pickers are maintained in an instance object,
	   allowing multiple different settings on the same page. */

	function Datepicker() {
		this._curInst = null; // The current instance in use
		this._keyEvent = false; // If the last event was a key event
		this._disabledInputs = []; // List of date picker inputs that have been disabled
		this._datepickerShowing = false; // True if the popup picker is showing , false if not
		this._inDialog = false; // True if showing within a "dialog", false if not
		this._mainDivId = "ui-datepicker-div"; // The ID of the main datepicker division
		this._inlineClass = "ui-datepicker-inline"; // The name of the inline marker class
		this._appendClass = "ui-datepicker-append"; // The name of the append marker class
		this._triggerClass = "ui-datepicker-trigger"; // The name of the trigger marker class
		this._dialogClass = "ui-datepicker-dialog"; // The name of the dialog marker class
		this._disableClass = "ui-datepicker-disabled"; // The name of the disabled covering marker class
		this._unselectableClass = "ui-datepicker-unselectable"; // The name of the unselectable cell marker class
		this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class
		this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class
		this.regional = []; // Available regional settings, indexed by language code
		this.regional[""] = { // Default regional settings
			closeText: "Done", // Display text for close link
			prevText: "Prev", // Display text for previous month link
			nextText: "Next", // Display text for next month link
			currentText: "Today", // Display text for current month link
			monthNames: ["January","February","March","April","May","June",
				"July","August","September","October","November","December"], // Names of months for drop-down and formatting
			monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], // For formatting
			dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], // For formatting
			dayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], // For formatting
			dayNamesMin: ["Su","Mo","Tu","We","Th","Fr","Sa"], // Column headings for days starting at Sunday
			weekHeader: "Wk", // Column header for week of the year
			dateFormat: "mm/dd/yy", // See format options on parseDate
			firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
			isRTL: false, // True if right-to-left language, false if left-to-right
			showMonthAfterYear: false, // True if the year select precedes month, false for month then year
			yearSuffix: "" // Additional text to append to the year in the month headers
		};
		this._defaults = { // Global defaults for all the date picker instances
			showOn: "focus", // "focus" for popup on focus,
				// "button" for trigger button, or "both" for either
			showAnim: "fadeIn", // Name of jQuery animation for popup
			showOptions: {}, // Options for enhanced animations
			defaultDate: null, // Used when field is blank: actual date,
				// +/-number for offset from today, null for today
			appendText: "", // Display text following the input box, e.g. showing the format
			buttonText: "...", // Text for trigger button
			buttonImage: "", // URL for trigger button image
			buttonImageOnly: false, // True if the image appears alone, false if it appears on a button
			hideIfNoPrevNext: false, // True to hide next/previous month links
				// if not applicable, false to just disable them
			navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links
			gotoCurrent: false, // True if today link goes back to current selection instead
			changeMonth: false, // True if month can be selected directly, false if only prev/next
			changeYear: false, // True if year can be selected directly, false if only prev/next
			yearRange: "c-10:c+10", // Range of years to display in drop-down,
				// either relative to today's year (-nn:+nn), relative to currently displayed year
				// (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n)
			showOtherMonths: false, // True to show dates in other months, false to leave blank
			selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable
			showWeek: false, // True to show week of the year, false to not show it
			calculateWeek: this.iso8601Week, // How to calculate the week of the year,
				// takes a Date and returns the number of the week for it
			shortYearCutoff: "+10", // Short year values < this are in the current century,
				// > this are in the previous century,
				// string value starting with "+" for current year + value
			minDate: null, // The earliest selectable date, or null for no limit
			maxDate: null, // The latest selectable date, or null for no limit
			duration: "fast", // Duration of display/closure
			beforeShowDay: null, // Function that takes a date and returns an array with
				// [0] = true if selectable, false if not, [1] = custom CSS class name(s) or "",
				// [2] = cell title (optional), e.g. $.datepicker.noWeekends
			beforeShow: null, // Function that takes an input field and
				// returns a set of custom settings for the date picker
			onSelect: null, // Define a callback function when a date is selected
			onChangeMonthYear: null, // Define a callback function when the month or year is changed
			onClose: null, // Define a callback function when the datepicker is closed
			numberOfMonths: 1, // Number of months to show at a time
			showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0)
			stepMonths: 1, // Number of months to step back/forward
			stepBigMonths: 12, // Number of months to step back/forward for the big links
			altField: "", // Selector for an alternate field to store selected dates into
			altFormat: "", // The date format to use for the alternate field
			constrainInput: true, // The input is constrained by the current date format
			showButtonPanel: false, // True to show button panel, false to not show it
			autoSize: false, // True to size the input for the date format, false to leave as is
			disabled: false // The initial disabled state
		};
		$.extend(this._defaults, this.regional[""]);
		this.dpDiv = bindHover($("<div id='" + this._mainDivId + "' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>"));
	}

	$.extend(Datepicker.prototype, {
		/* Class name added to elements to indicate already configured with a date picker. */
		markerClassName: "hasDatepicker",

		//Keep track of the maximum number of rows displayed (see #7043)
		maxRows: 4,

		// TODO rename to "widget" when switching to widget factory
		_widgetDatepicker: function() {
			return this.dpDiv;
		},

		/* Override the default settings for all instances of the date picker.
		 * @param  settings  object - the new settings to use as defaults (anonymous object)
		 * @return the manager object
		 */
		setDefaults: function(settings) {
			extendRemove(this._defaults, settings || {});
			return this;
		},

		/* Attach the date picker to a jQuery selection.
		 * @param  target	element - the target input field or division or span
		 * @param  settings  object - the new settings to use for this date picker instance (anonymous)
		 */
		_attachDatepicker: function(target, settings) {
			var nodeName, inline, inst;
			nodeName = target.nodeName.toLowerCase();
			inline = (nodeName === "div" || nodeName === "span");
			if (!target.id) {
				this.uuid += 1;
				target.id = "dp" + this.uuid;
			}
			inst = this._newInst($(target), inline);
			inst.settings = $.extend({}, settings || {});
			if (nodeName === "input") {
				this._connectDatepicker(target, inst);
			} else if (inline) {
				this._inlineDatepicker(target, inst);
			}
		},

		/* Create a new instance object. */
		_newInst: function(target, inline) {
			var id = target[0].id.replace(/([^A-Za-z0-9_\-])/g, "\\\\$1"); // escape jQuery meta chars
			return {id: id, input: target, // associated target
				selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection
				drawMonth: 0, drawYear: 0, // month being drawn
				inline: inline, // is datepicker inline or not
				dpDiv: (!inline ? this.dpDiv : // presentation div
				bindHover($("<div class='" + this._inlineClass + " ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>")))};
		},

		/* Attach the date picker to an input field. */
		_connectDatepicker: function(target, inst) {
			var input = $(target);
			inst.append = $([]);
			inst.trigger = $([]);
			if (input.hasClass(this.markerClassName)) {
				return;
			}
			this._attachments(input, inst);
			input.addClass(this.markerClassName).keydown(this._doKeyDown).
				keypress(this._doKeyPress).keyup(this._doKeyUp);
			this._autoSize(inst);
			$.data(target, PROP_NAME, inst);
			//If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665)
			if( inst.settings.disabled ) {
				this._disableDatepicker( target );
			}
		},

		/* Make attachments based on settings. */
		_attachments: function(input, inst) {
			var showOn, buttonText, buttonImage,
				appendText = this._get(inst, "appendText"),
				isRTL = this._get(inst, "isRTL");

			if (inst.append) {
				inst.append.remove();
			}
			if (appendText) {
				inst.append = $("<span class='" + this._appendClass + "'>" + appendText + "</span>");
				input[isRTL ? "before" : "after"](inst.append);
			}

			input.unbind("focus", this._showDatepicker);

			if (inst.trigger) {
				inst.trigger.remove();
			}

			showOn = this._get(inst, "showOn");
			if (showOn === "focus" || showOn === "both") { // pop-up date picker when in the marked field
				input.focus(this._showDatepicker);
			}
			if (showOn === "button" || showOn === "both") { // pop-up date picker when button clicked
				buttonText = this._get(inst, "buttonText");
				buttonImage = this._get(inst, "buttonImage");
				inst.trigger = $(this._get(inst, "buttonImageOnly") ?
					$("<img/>").addClass(this._triggerClass).
						attr({ src: buttonImage, alt: buttonText, title: buttonText }) :
					$("<button type='button'></button>").addClass(this._triggerClass).
						html(!buttonImage ? buttonText : $("<img/>").attr(
						{ src:buttonImage, alt:buttonText, title:buttonText })));
				input[isRTL ? "before" : "after"](inst.trigger);
				inst.trigger.click(function() {
					if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) {
						$.datepicker._hideDatepicker();
					} else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) {
						$.datepicker._hideDatepicker();
						$.datepicker._showDatepicker(input[0]);
					} else {
						$.datepicker._showDatepicker(input[0]);
					}
					return false;
				});
			}
		},

		/* Apply the maximum length for the date format. */
		_autoSize: function(inst) {
			if (this._get(inst, "autoSize") && !inst.inline) {
				var findMax, max, maxI, i,
					date = new Date(2009, 12 - 1, 20), // Ensure double digits
					dateFormat = this._get(inst, "dateFormat");

				if (dateFormat.match(/[DM]/)) {
					findMax = function(names) {
						max = 0;
						maxI = 0;
						for (i = 0; i < names.length; i++) {
							if (names[i].length > max) {
								max = names[i].length;
								maxI = i;
							}
						}
						return maxI;
					};
					date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ?
						"monthNames" : "monthNamesShort"))));
					date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ?
						"dayNames" : "dayNamesShort"))) + 20 - date.getDay());
				}
				inst.input.attr("size", this._formatDate(inst, date).length);
			}
		},

		/* Attach an inline date picker to a div. */
		_inlineDatepicker: function(target, inst) {
			var divSpan = $(target);
			if (divSpan.hasClass(this.markerClassName)) {
				return;
			}
			divSpan.addClass(this.markerClassName).append(inst.dpDiv);
			$.data(target, PROP_NAME, inst);
			this._setDate(inst, this._getDefaultDate(inst), true);
			this._updateDatepicker(inst);
			this._updateAlternate(inst);
			//If disabled option is true, disable the datepicker before showing it (see ticket #5665)
			if( inst.settings.disabled ) {
				this._disableDatepicker( target );
			}
			// Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements
			// http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height
			inst.dpDiv.css( "display", "block" );
		},

		/* Pop-up the date picker in a "dialog" box.
		 * @param  input element - ignored
		 * @param  date	string or Date - the initial date to display
		 * @param  onSelect  function - the function to call when a date is selected
		 * @param  settings  object - update the dialog date picker instance's settings (anonymous object)
		 * @param  pos int[2] - coordinates for the dialog's position within the screen or
		 *					event - with x/y coordinates or
		 *					leave empty for default (screen centre)
		 * @return the manager object
		 */
		_dialogDatepicker: function(input, date, onSelect, settings, pos) {
			var id, browserWidth, browserHeight, scrollX, scrollY,
				inst = this._dialogInst; // internal instance

			if (!inst) {
				this.uuid += 1;
				id = "dp" + this.uuid;
				this._dialogInput = $("<input type='text' id='" + id +
					"' style='position: absolute; top: -100px; width: 0px;'/>");
				this._dialogInput.keydown(this._doKeyDown);
				$("body").append(this._dialogInput);
				inst = this._dialogInst = this._newInst(this._dialogInput, false);
				inst.settings = {};
				$.data(this._dialogInput[0], PROP_NAME, inst);
			}
			extendRemove(inst.settings, settings || {});
			date = (date && date.constructor === Date ? this._formatDate(inst, date) : date);
			this._dialogInput.val(date);

			this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null);
			if (!this._pos) {
				browserWidth = document.documentElement.clientWidth;
				browserHeight = document.documentElement.clientHeight;
				scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
				scrollY = document.documentElement.scrollTop || document.body.scrollTop;
				this._pos = // should use actual width/height below
					[(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY];
			}

			// move input on screen for focus, but hidden behind dialog
			this._dialogInput.css("left", (this._pos[0] + 20) + "px").css("top", this._pos[1] + "px");
			inst.settings.onSelect = onSelect;
			this._inDialog = true;
			this.dpDiv.addClass(this._dialogClass);
			this._showDatepicker(this._dialogInput[0]);
			if ($.blockUI) {
				$.blockUI(this.dpDiv);
			}
			$.data(this._dialogInput[0], PROP_NAME, inst);
			return this;
		},

		/* Detach a datepicker from its control.
		 * @param  target	element - the target input field or division or span
		 */
		_destroyDatepicker: function(target) {
			var nodeName,
				$target = $(target),
				inst = $.data(target, PROP_NAME);

			if (!$target.hasClass(this.markerClassName)) {
				return;
			}

			nodeName = target.nodeName.toLowerCase();
			$.removeData(target, PROP_NAME);
			if (nodeName === "input") {
				inst.append.remove();
				inst.trigger.remove();
				$target.removeClass(this.markerClassName).
					unbind("focus", this._showDatepicker).
					unbind("keydown", this._doKeyDown).
					unbind("keypress", this._doKeyPress).
					unbind("keyup", this._doKeyUp);
			} else if (nodeName === "div" || nodeName === "span") {
				$target.removeClass(this.markerClassName).empty();
			}
		},

		/* Enable the date picker to a jQuery selection.
		 * @param  target	element - the target input field or division or span
		 */
		_enableDatepicker: function(target) {
			var nodeName, inline,
				$target = $(target),
				inst = $.data(target, PROP_NAME);

			if (!$target.hasClass(this.markerClassName)) {
				return;
			}

			nodeName = target.nodeName.toLowerCase();
			if (nodeName === "input") {
				target.disabled = false;
				inst.trigger.filter("button").
					each(function() { this.disabled = false; }).end().
					filter("img").css({opacity: "1.0", cursor: ""});
			} else if (nodeName === "div" || nodeName === "span") {
				inline = $target.children("." + this._inlineClass);
				inline.children().removeClass("ui-state-disabled");
				inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
					prop("disabled", false);
			}
			this._disabledInputs = $.map(this._disabledInputs,
				function(value) { return (value === target ? null : value); }); // delete entry
		},

		/* Disable the date picker to a jQuery selection.
		 * @param  target	element - the target input field or division or span
		 */
		_disableDatepicker: function(target) {
			var nodeName, inline,
				$target = $(target),
				inst = $.data(target, PROP_NAME);

			if (!$target.hasClass(this.markerClassName)) {
				return;
			}

			nodeName = target.nodeName.toLowerCase();
			if (nodeName === "input") {
				target.disabled = true;
				inst.trigger.filter("button").
					each(function() { this.disabled = true; }).end().
					filter("img").css({opacity: "0.5", cursor: "default"});
			} else if (nodeName === "div" || nodeName === "span") {
				inline = $target.children("." + this._inlineClass);
				inline.children().addClass("ui-state-disabled");
				inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
					prop("disabled", true);
			}
			this._disabledInputs = $.map(this._disabledInputs,
				function(value) { return (value === target ? null : value); }); // delete entry
			this._disabledInputs[this._disabledInputs.length] = target;
		},

		/* Is the first field in a jQuery collection disabled as a datepicker?
		 * @param  target	element - the target input field or division or span
		 * @return boolean - true if disabled, false if enabled
		 */
		_isDisabledDatepicker: function(target) {
			if (!target) {
				return false;
			}
			for (var i = 0; i < this._disabledInputs.length; i++) {
				if (this._disabledInputs[i] === target) {
					return true;
				}
			}
			return false;
		},

		/* Retrieve the instance data for the target control.
		 * @param  target  element - the target input field or division or span
		 * @return  object - the associated instance data
		 * @throws  error if a jQuery problem getting data
		 */
		_getInst: function(target) {
			try {
				return $.data(target, PROP_NAME);
			}
			catch (err) {
				throw "Missing instance data for this datepicker";
			}
		},

		/* Update or retrieve the settings for a date picker attached to an input field or division.
		 * @param  target  element - the target input field or division or span
		 * @param  name	object - the new settings to update or
		 *				string - the name of the setting to change or retrieve,
		 *				when retrieving also "all" for all instance settings or
		 *				"defaults" for all global defaults
		 * @param  value   any - the new value for the setting
		 *				(omit if above is an object or to retrieve a value)
		 */
		_optionDatepicker: function(target, name, value) {
			var settings, date, minDate, maxDate,
				inst = this._getInst(target);

			if (arguments.length === 2 && typeof name === "string") {
				return (name === "defaults" ? $.extend({}, $.datepicker._defaults) :
					(inst ? (name === "all" ? $.extend({}, inst.settings) :
					this._get(inst, name)) : null));
			}

			settings = name || {};
			if (typeof name === "string") {
				settings = {};
				settings[name] = value;
			}

			if (inst) {
				if (this._curInst === inst) {
					this._hideDatepicker();
				}

				date = this._getDateDatepicker(target, true);
				minDate = this._getMinMaxDate(inst, "min");
				maxDate = this._getMinMaxDate(inst, "max");
				extendRemove(inst.settings, settings);
				// reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided
				if (minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined) {
					inst.settings.minDate = this._formatDate(inst, minDate);
				}
				if (maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined) {
					inst.settings.maxDate = this._formatDate(inst, maxDate);
				}
				if ( "disabled" in settings ) {
					if ( settings.disabled ) {
						this._disableDatepicker(target);
					} else {
						this._enableDatepicker(target);
					}
				}
				this._attachments($(target), inst);
				this._autoSize(inst);
				this._setDate(inst, date);
				this._updateAlternate(inst);
				this._updateDatepicker(inst);
			}
		},

		// change method deprecated
		_changeDatepicker: function(target, name, value) {
			this._optionDatepicker(target, name, value);
		},

		/* Redraw the date picker attached to an input field or division.
		 * @param  target  element - the target input field or division or span
		 */
		_refreshDatepicker: function(target) {
			var inst = this._getInst(target);
			if (inst) {
				this._updateDatepicker(inst);
			}
		},

		/* Set the dates for a jQuery selection.
		 * @param  target element - the target input field or division or span
		 * @param  date	Date - the new date
		 */
		_setDateDatepicker: function(target, date) {
			var inst = this._getInst(target);
			if (inst) {
				this._setDate(inst, date);
				this._updateDatepicker(inst);
				this._updateAlternate(inst);
			}
		},

		/* Get the date(s) for the first entry in a jQuery selection.
		 * @param  target element - the target input field or division or span
		 * @param  noDefault boolean - true if no default date is to be used
		 * @return Date - the current date
		 */
		_getDateDatepicker: function(target, noDefault) {
			var inst = this._getInst(target);
			if (inst && !inst.inline) {
				this._setDateFromField(inst, noDefault);
			}
			return (inst ? this._getDate(inst) : null);
		},

		/* Handle keystrokes. */
		_doKeyDown: function(event) {
			var onSelect, dateStr, sel,
				inst = $.datepicker._getInst(event.target),
				handled = true,
				isRTL = inst.dpDiv.is(".ui-datepicker-rtl");

			inst._keyEvent = true;
			if ($.datepicker._datepickerShowing) {
				switch (event.keyCode) {
					case 9: $.datepicker._hideDatepicker();
							handled = false;
							break; // hide on tab out
					case 13: sel = $("td." + $.datepicker._dayOverClass + ":not(." +
										$.datepicker._currentClass + ")", inst.dpDiv);
							if (sel[0]) {
								$.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]);
							}

							onSelect = $.datepicker._get(inst, "onSelect");
							if (onSelect) {
								dateStr = $.datepicker._formatDate(inst);

								// trigger custom callback
								onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);
							} else {
								$.datepicker._hideDatepicker();
							}

							return false; // don't submit the form
					case 27: $.datepicker._hideDatepicker();
							break; // hide on escape
					case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
								-$.datepicker._get(inst, "stepBigMonths") :
								-$.datepicker._get(inst, "stepMonths")), "M");
							break; // previous month/year on page up/+ ctrl
					case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
								+$.datepicker._get(inst, "stepBigMonths") :
								+$.datepicker._get(inst, "stepMonths")), "M");
							break; // next month/year on page down/+ ctrl
					case 35: if (event.ctrlKey || event.metaKey) {
								$.datepicker._clearDate(event.target);
							}
							handled = event.ctrlKey || event.metaKey;
							break; // clear on ctrl or command +end
					case 36: if (event.ctrlKey || event.metaKey) {
								$.datepicker._gotoToday(event.target);
							}
							handled = event.ctrlKey || event.metaKey;
							break; // current on ctrl or command +home
					case 37: if (event.ctrlKey || event.metaKey) {
								$.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), "D");
							}
							handled = event.ctrlKey || event.metaKey;
							// -1 day on ctrl or command +left
							if (event.originalEvent.altKey) {
								$.datepicker._adjustDate(event.target, (event.ctrlKey ?
									-$.datepicker._get(inst, "stepBigMonths") :
									-$.datepicker._get(inst, "stepMonths")), "M");
							}
							// next month/year on alt +left on Mac
							break;
					case 38: if (event.ctrlKey || event.metaKey) {
								$.datepicker._adjustDate(event.target, -7, "D");
							}
							handled = event.ctrlKey || event.metaKey;
							break; // -1 week on ctrl or command +up
					case 39: if (event.ctrlKey || event.metaKey) {
								$.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), "D");
							}
							handled = event.ctrlKey || event.metaKey;
							// +1 day on ctrl or command +right
							if (event.originalEvent.altKey) {
								$.datepicker._adjustDate(event.target, (event.ctrlKey ?
									+$.datepicker._get(inst, "stepBigMonths") :
									+$.datepicker._get(inst, "stepMonths")), "M");
							}
							// next month/year on alt +right
							break;
					case 40: if (event.ctrlKey || event.metaKey) {
								$.datepicker._adjustDate(event.target, +7, "D");
							}
							handled = event.ctrlKey || event.metaKey;
							break; // +1 week on ctrl or command +down
					default: handled = false;
				}
			} else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home
				$.datepicker._showDatepicker(this);
			} else {
				handled = false;
			}

			if (handled) {
				event.preventDefault();
				event.stopPropagation();
			}
		},

		/* Filter entered characters - based on date format. */
		_doKeyPress: function(event) {
			var chars, chr,
				inst = $.datepicker._getInst(event.target);

			if ($.datepicker._get(inst, "constrainInput")) {
				chars = $.datepicker._possibleChars($.datepicker._get(inst, "dateFormat"));
				chr = String.fromCharCode(event.charCode == null ? event.keyCode : event.charCode);
				return event.ctrlKey || event.metaKey || (chr < " " || !chars || chars.indexOf(chr) > -1);
			}
		},

		/* Synchronise manual entry and field/alternate field. */
		_doKeyUp: function(event) {
			var date,
				inst = $.datepicker._getInst(event.target);

			if (inst.input.val() !== inst.lastVal) {
				try {
					date = $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"),
						(inst.input ? inst.input.val() : null),
						$.datepicker._getFormatConfig(inst));

					if (date) { // only if valid
						$.datepicker._setDateFromField(inst);
						$.datepicker._updateAlternate(inst);
						$.datepicker._updateDatepicker(inst);
					}
				}
				catch (err) {
				}
			}
			return true;
		},

		/* Pop-up the date picker for a given input field.
		 * If false returned from beforeShow event handler do not show.
		 * @param  input  element - the input field attached to the date picker or
		 *					event - if triggered by focus
		 */
		_showDatepicker: function(input) {
			input = input.target || input;
			if (input.nodeName.toLowerCase() !== "input") { // find from button/image trigger
				input = $("input", input.parentNode)[0];
			}

			if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput === input) { // already here
				return;
			}

			var inst, beforeShow, beforeShowSettings, isFixed,
				offset, showAnim, duration;

			inst = $.datepicker._getInst(input);
			if ($.datepicker._curInst && $.datepicker._curInst !== inst) {
				$.datepicker._curInst.dpDiv.stop(true, true);
				if ( inst && $.datepicker._datepickerShowing ) {
					$.datepicker._hideDatepicker( $.datepicker._curInst.input[0] );
				}
			}

			beforeShow = $.datepicker._get(inst, "beforeShow");
			beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {};
			if(beforeShowSettings === false){
				return;
			}
			extendRemove(inst.settings, beforeShowSettings);

			inst.lastVal = null;
			$.datepicker._lastInput = input;
			$.datepicker._setDateFromField(inst);

			if ($.datepicker._inDialog) { // hide cursor
				input.value = "";
			}
			if (!$.datepicker._pos) { // position below input
				$.datepicker._pos = $.datepicker._findPos(input);
				$.datepicker._pos[1] += input.offsetHeight; // add the height
			}

			isFixed = false;
			$(input).parents().each(function() {
				isFixed |= $(this).css("position") === "fixed";
				return !isFixed;
			});

			offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]};
			$.datepicker._pos = null;
			//to avoid flashes on Firefox
			inst.dpDiv.empty();
			// determine sizing offscreen
			inst.dpDiv.css({position: "absolute", display: "block", top: "-1000px"});
			$.datepicker._updateDatepicker(inst);
			// fix width for dynamic number of date pickers
			// and adjust position before showing
			offset = $.datepicker._checkOffset(inst, offset, isFixed);
			inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ?
				"static" : (isFixed ? "fixed" : "absolute")), display: "none",
				left: offset.left + "px", top: offset.top + "px"});

			if (!inst.inline) {
				showAnim = $.datepicker._get(inst, "showAnim");
				duration = $.datepicker._get(inst, "duration");
				inst.dpDiv.zIndex($(input).zIndex()+1);
				$.datepicker._datepickerShowing = true;

				if ( $.effects && $.effects.effect[ showAnim ] ) {
					inst.dpDiv.show(showAnim, $.datepicker._get(inst, "showOptions"), duration);
				} else {
					inst.dpDiv[showAnim || "show"](showAnim ? duration : null);
				}

				if ( $.datepicker._shouldFocusInput( inst ) ) {
					inst.input.focus();
				}

				$.datepicker._curInst = inst;
			}
		},

		/* Generate the date picker content. */
		_updateDatepicker: function(inst) {
			this.maxRows = 4; //Reset the max number of rows being displayed (see #7043)
			instActive = inst; // for delegate hover events
			inst.dpDiv.empty().append(this._generateHTML(inst));
			this._attachHandlers(inst);
			inst.dpDiv.find("." + this._dayOverClass + " a").mouseover();

			var origyearshtml,
				numMonths = this._getNumberOfMonths(inst),
				cols = numMonths[1],
				width = 17;

			inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");
			if (cols > 1) {
				inst.dpDiv.addClass("ui-datepicker-multi-" + cols).css("width", (width * cols) + "em");
			}
			inst.dpDiv[(numMonths[0] !== 1 || numMonths[1] !== 1 ? "add" : "remove") +
				"Class"]("ui-datepicker-multi");
			inst.dpDiv[(this._get(inst, "isRTL") ? "add" : "remove") +
				"Class"]("ui-datepicker-rtl");

			if (inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) {
				inst.input.focus();
			}

			// deffered render of the years select (to avoid flashes on Firefox)
			if( inst.yearshtml ){
				origyearshtml = inst.yearshtml;
				setTimeout(function(){
					//assure that inst.yearshtml didn't change.
					if( origyearshtml === inst.yearshtml && inst.yearshtml ){
						inst.dpDiv.find("select.ui-datepicker-year:first").replaceWith(inst.yearshtml);
					}
					origyearshtml = inst.yearshtml = null;
				}, 0);
			}
		},

		// #6694 - don't focus the input if it's already focused
		// this breaks the change event in IE
		// Support: IE and jQuery <1.9
		_shouldFocusInput: function( inst ) {
			return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" );
		},

		/* Check positioning to remain on screen. */
		_checkOffset: function(inst, offset, isFixed) {
			var dpWidth = inst.dpDiv.outerWidth(),
				dpHeight = inst.dpDiv.outerHeight(),
				inputWidth = inst.input ? inst.input.outerWidth() : 0,
				inputHeight = inst.input ? inst.input.outerHeight() : 0,
				viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft()),
				viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop());

			offset.left -= (this._get(inst, "isRTL") ? (dpWidth - inputWidth) : 0);
			offset.left -= (isFixed && offset.left === inst.input.offset().left) ? $(document).scrollLeft() : 0;
			offset.top -= (isFixed && offset.top === (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0;

			// now check if datepicker is showing outside window viewport - move to a better place if so.
			offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
				Math.abs(offset.left + dpWidth - viewWidth) : 0);
			offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
				Math.abs(dpHeight + inputHeight) : 0);

			return offset;
		},

		/* Find an object's position on the screen. */
		_findPos: function(obj) {
			var position,
				inst = this._getInst(obj),
				isRTL = this._get(inst, "isRTL");

			while (obj && (obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden(obj))) {
				obj = obj[isRTL ? "previousSibling" : "nextSibling"];
			}

			position = $(obj).offset();
			return [position.left, position.top];
		},

		/* Hide the date picker from view.
		 * @param  input  element - the input field attached to the date picker
		 */
		_hideDatepicker: function(input) {
			var showAnim, duration, postProcess, onClose,
				inst = this._curInst;

			if (!inst || (input && inst !== $.data(input, PROP_NAME))) {
				return;
			}

			if (this._datepickerShowing) {
				showAnim = this._get(inst, "showAnim");
				duration = this._get(inst, "duration");
				postProcess = function() {
					$.datepicker._tidyDialog(inst);
				};

				// DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed
				if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) {
					inst.dpDiv.hide(showAnim, $.datepicker._get(inst, "showOptions"), duration, postProcess);
				} else {
					inst.dpDiv[(showAnim === "slideDown" ? "slideUp" :
						(showAnim === "fadeIn" ? "fadeOut" : "hide"))]((showAnim ? duration : null), postProcess);
				}

				if (!showAnim) {
					postProcess();
				}
				this._datepickerShowing = false;

				onClose = this._get(inst, "onClose");
				if (onClose) {
					onClose.apply((inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ""), inst]);
				}

				this._lastInput = null;
				if (this._inDialog) {
					this._dialogInput.css({ position: "absolute", left: "0", top: "-100px" });
					if ($.blockUI) {
						$.unblockUI();
						$("body").append(this.dpDiv);
					}
				}
				this._inDialog = false;
			}
		},

		/* Tidy up after a dialog display. */
		_tidyDialog: function(inst) {
			inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar");
		},

		/* Close date picker if clicked elsewhere. */
		_checkExternalClick: function(event) {
			if (!$.datepicker._curInst) {
				return;
			}

			var $target = $(event.target),
				inst = $.datepicker._getInst($target[0]);

			if ( ( ( $target[0].id !== $.datepicker._mainDivId &&
					$target.parents("#" + $.datepicker._mainDivId).length === 0 &&
					!$target.hasClass($.datepicker.markerClassName) &&
					!$target.closest("." + $.datepicker._triggerClass).length &&
					$.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) ||
				( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst !== inst ) ) {
					$.datepicker._hideDatepicker();
			}
		},

		/* Adjust one of the date sub-fields. */
		_adjustDate: function(id, offset, period) {
			var target = $(id),
				inst = this._getInst(target[0]);

			if (this._isDisabledDatepicker(target[0])) {
				return;
			}
			this._adjustInstDate(inst, offset +
				(period === "M" ? this._get(inst, "showCurrentAtPos") : 0), // undo positioning
				period);
			this._updateDatepicker(inst);
		},

		/* Action for current link. */
		_gotoToday: function(id) {
			var date,
				target = $(id),
				inst = this._getInst(target[0]);

			if (this._get(inst, "gotoCurrent") && inst.currentDay) {
				inst.selectedDay = inst.currentDay;
				inst.drawMonth = inst.selectedMonth = inst.currentMonth;
				inst.drawYear = inst.selectedYear = inst.currentYear;
			} else {
				date = new Date();
				inst.selectedDay = date.getDate();
				inst.drawMonth = inst.selectedMonth = date.getMonth();
				inst.drawYear = inst.selectedYear = date.getFullYear();
			}
			this._notifyChange(inst);
			this._adjustDate(target);
		},

		/* Action for selecting a new month/year. */
		_selectMonthYear: function(id, select, period) {
			var target = $(id),
				inst = this._getInst(target[0]);

			inst["selected" + (period === "M" ? "Month" : "Year")] =
			inst["draw" + (period === "M" ? "Month" : "Year")] =
				parseInt(select.options[select.selectedIndex].value,10);

			this._notifyChange(inst);
			this._adjustDate(target);
		},

		/* Action for selecting a day. */
		_selectDay: function(id, month, year, td) {
			var inst,
				target = $(id);

			if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) {
				return;
			}

			inst = this._getInst(target[0]);
			inst.selectedDay = inst.currentDay = $("a", td).html();
			inst.selectedMonth = inst.currentMonth = month;
			inst.selectedYear = inst.currentYear = year;
			this._selectDate(id, this._formatDate(inst,
				inst.currentDay, inst.currentMonth, inst.currentYear));
		},

		/* Erase the input field and hide the date picker. */
		_clearDate: function(id) {
			var target = $(id);
			this._selectDate(target, "");
		},

		/* Update the input field with the selected date. */
		_selectDate: function(id, dateStr) {
			var onSelect,
				target = $(id),
				inst = this._getInst(target[0]);

			dateStr = (dateStr != null ? dateStr : this._formatDate(inst));
			if (inst.input) {
				inst.input.val(dateStr);
			}
			this._updateAlternate(inst);

			onSelect = this._get(inst, "onSelect");
			if (onSelect) {
				onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);  // trigger custom callback
			} else if (inst.input) {
				inst.input.trigger("change"); // fire the change event
			}

			if (inst.inline){
				this._updateDatepicker(inst);
			} else {
				this._hideDatepicker();
				this._lastInput = inst.input[0];
				if (typeof(inst.input[0]) !== "object") {
					inst.input.focus(); // restore focus
				}
				this._lastInput = null;
			}
		},

		/* Update any alternate field to synchronise with the main field. */
		_updateAlternate: function(inst) {
			var altFormat, date, dateStr,
				altField = this._get(inst, "altField");

			if (altField) { // update alternate field too
				altFormat = this._get(inst, "altFormat") || this._get(inst, "dateFormat");
				date = this._getDate(inst);
				dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst));
				$(altField).each(function() { $(this).val(dateStr); });
			}
		},

		/* Set as beforeShowDay function to prevent selection of weekends.
		 * @param  date  Date - the date to customise
		 * @return [boolean, string] - is this date selectable?, what is its CSS class?
		 */
		noWeekends: function(date) {
			var day = date.getDay();
			return [(day > 0 && day < 6), ""];
		},

		/* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
		 * @param  date  Date - the date to get the week for
		 * @return  number - the number of the week within the year that contains this date
		 */
		iso8601Week: function(date) {
			var time,
				checkDate = new Date(date.getTime());

			// Find Thursday of this week starting on Monday
			checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));

			time = checkDate.getTime();
			checkDate.setMonth(0); // Compare with Jan 1
			checkDate.setDate(1);
			return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
		},

		/* Parse a string value into a date object.
		 * See formatDate below for the possible formats.
		 *
		 * @param  format string - the expected format of the date
		 * @param  value string - the date in the above format
		 * @param  settings Object - attributes include:
		 *					shortYearCutoff  number - the cutoff year for determining the century (optional)
		 *					dayNamesShort	string[7] - abbreviated names of the days from Sunday (optional)
		 *					dayNames		string[7] - names of the days from Sunday (optional)
		 *					monthNamesShort string[12] - abbreviated names of the months (optional)
		 *					monthNames		string[12] - names of the months (optional)
		 * @return  Date - the extracted date value or null if value is blank
		 */
		parseDate: function (format, value, settings) {
			if (format == null || value == null) {
				throw "Invalid arguments";
			}

			value = (typeof value === "object" ? value.toString() : value + "");
			if (value === "") {
				return null;
			}

			var iFormat, dim, extra,
				iValue = 0,
				shortYearCutoffTemp = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff,
				shortYearCutoff = (typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp :
					new Date().getFullYear() % 100 + parseInt(shortYearCutoffTemp, 10)),
				dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort,
				dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames,
				monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort,
				monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames,
				year = -1,
				month = -1,
				day = -1,
				doy = -1,
				literal = false,
				date,
				// Check whether a format character is doubled
				lookAhead = function(match) {
					var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
					if (matches) {
						iFormat++;
					}
					return matches;
				},
				// Extract a number from the string value
				getNumber = function(match) {
					var isDoubled = lookAhead(match),
						size = (match === "@" ? 14 : (match === "!" ? 20 :
						(match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))),
						digits = new RegExp("^\\d{1," + size + "}"),
						num = value.substring(iValue).match(digits);
					if (!num) {
						throw "Missing number at position " + iValue;
					}
					iValue += num[0].length;
					return parseInt(num[0], 10);
				},
				// Extract a name from the string value and convert to an index
				getName = function(match, shortNames, longNames) {
					var index = -1,
						names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) {
							return [ [k, v] ];
						}).sort(function (a, b) {
							return -(a[1].length - b[1].length);
						});

					$.each(names, function (i, pair) {
						var name = pair[1];
						if (value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) {
							index = pair[0];
							iValue += name.length;
							return false;
						}
					});
					if (index !== -1) {
						return index + 1;
					} else {
						throw "Unknown name at position " + iValue;
					}
				},
				// Confirm that a literal character matches the string value
				checkLiteral = function() {
					if (value.charAt(iValue) !== format.charAt(iFormat)) {
						throw "Unexpected literal at position " + iValue;
					}
					iValue++;
				};

			for (iFormat = 0; iFormat < format.length; iFormat++) {
				if (literal) {
					if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
						literal = false;
					} else {
						checkLiteral();
					}
				} else {
					switch (format.charAt(iFormat)) {
						case "d":
							day = getNumber("d");
							break;
						case "D":
							getName("D", dayNamesShort, dayNames);
							break;
						case "o":
							doy = getNumber("o");
							break;
						case "m":
							month = getNumber("m");
							break;
						case "M":
							month = getName("M", monthNamesShort, monthNames);
							break;
						case "y":
							year = getNumber("y");
							break;
						case "@":
							date = new Date(getNumber("@"));
							year = date.getFullYear();
							month = date.getMonth() + 1;
							day = date.getDate();
							break;
						case "!":
							date = new Date((getNumber("!") - this._ticksTo1970) / 10000);
							year = date.getFullYear();
							month = date.getMonth() + 1;
							day = date.getDate();
							break;
						case "'":
							if (lookAhead("'")){
								checkLiteral();
							} else {
								literal = true;
							}
							break;
						default:
							checkLiteral();
					}
				}
			}

			if (iValue < value.length){
				extra = value.substr(iValue);
				if (!/^\s+/.test(extra)) {
					throw "Extra/unparsed characters found in date: " + extra;
				}
			}

			if (year === -1) {
				year = new Date().getFullYear();
			} else if (year < 100) {
				year += new Date().getFullYear() - new Date().getFullYear() % 100 +
					(year <= shortYearCutoff ? 0 : -100);
			}

			if (doy > -1) {
				month = 1;
				day = doy;
				do {
					dim = this._getDaysInMonth(year, month - 1);
					if (day <= dim) {
						break;
					}
					month++;
					day -= dim;
				} while (true);
			}

			date = this._daylightSavingAdjust(new Date(year, month - 1, day));
			if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) {
				throw "Invalid date"; // E.g. 31/02/00
			}
			return date;
		},

		/* Standard date formats. */
		ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601)
		COOKIE: "D, dd M yy",
		ISO_8601: "yy-mm-dd",
		RFC_822: "D, d M y",
		RFC_850: "DD, dd-M-y",
		RFC_1036: "D, d M y",
		RFC_1123: "D, d M yy",
		RFC_2822: "D, d M yy",
		RSS: "D, d M y", // RFC 822
		TICKS: "!",
		TIMESTAMP: "@",
		W3C: "yy-mm-dd", // ISO 8601

		_ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) +
			Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000),

		/* Format a date object into a string value.
		 * The format can be combinations of the following:
		 * d  - day of month (no leading zero)
		 * dd - day of month (two digit)
		 * o  - day of year (no leading zeros)
		 * oo - day of year (three digit)
		 * D  - day name short
		 * DD - day name long
		 * m  - month of year (no leading zero)
		 * mm - month of year (two digit)
		 * M  - month name short
		 * MM - month name long
		 * y  - year (two digit)
		 * yy - year (four digit)
		 * @ - Unix timestamp (ms since 01/01/1970)
		 * ! - Windows ticks (100ns since 01/01/0001)
		 * "..." - literal text
		 * '' - single quote
		 *
		 * @param  format string - the desired format of the date
		 * @param  date Date - the date value to format
		 * @param  settings Object - attributes include:
		 *					dayNamesShort	string[7] - abbreviated names of the days from Sunday (optional)
		 *					dayNames		string[7] - names of the days from Sunday (optional)
		 *					monthNamesShort string[12] - abbreviated names of the months (optional)
		 *					monthNames		string[12] - names of the months (optional)
		 * @return  string - the date in the above format
		 */
		formatDate: function (format, date, settings) {
			if (!date) {
				return "";
			}

			var iFormat,
				dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort,
				dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames,
				monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort,
				monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames,
				// Check whether a format character is doubled
				lookAhead = function(match) {
					var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
					if (matches) {
						iFormat++;
					}
					return matches;
				},
				// Format a number, with leading zero if necessary
				formatNumber = function(match, value, len) {
					var num = "" + value;
					if (lookAhead(match)) {
						while (num.length < len) {
							num = "0" + num;
						}
					}
					return num;
				},
				// Format a name, short or long as requested
				formatName = function(match, value, shortNames, longNames) {
					return (lookAhead(match) ? longNames[value] : shortNames[value]);
				},
				output = "",
				literal = false;

			if (date) {
				for (iFormat = 0; iFormat < format.length; iFormat++) {
					if (literal) {
						if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
							literal = false;
						} else {
							output += format.charAt(iFormat);
						}
					} else {
						switch (format.charAt(iFormat)) {
							case "d":
								output += formatNumber("d", date.getDate(), 2);
								break;
							case "D":
								output += formatName("D", date.getDay(), dayNamesShort, dayNames);
								break;
							case "o":
								output += formatNumber("o",
									Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3);
								break;
							case "m":
								output += formatNumber("m", date.getMonth() + 1, 2);
								break;
							case "M":
								output += formatName("M", date.getMonth(), monthNamesShort, monthNames);
								break;
							case "y":
								output += (lookAhead("y") ? date.getFullYear() :
									(date.getYear() % 100 < 10 ? "0" : "") + date.getYear() % 100);
								break;
							case "@":
								output += date.getTime();
								break;
							case "!":
								output += date.getTime() * 10000 + this._ticksTo1970;
								break;
							case "'":
								if (lookAhead("'")) {
									output += "'";
								} else {
									literal = true;
								}
								break;
							default:
								output += format.charAt(iFormat);
						}
					}
				}
			}
			return output;
		},

		/* Extract all possible characters from the date format. */
		_possibleChars: function (format) {
			var iFormat,
				chars = "",
				literal = false,
				// Check whether a format character is doubled
				lookAhead = function(match) {
					var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
					if (matches) {
						iFormat++;
					}
					return matches;
				};

			for (iFormat = 0; iFormat < format.length; iFormat++) {
				if (literal) {
					if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
						literal = false;
					} else {
						chars += format.charAt(iFormat);
					}
				} else {
					switch (format.charAt(iFormat)) {
						case "d": case "m": case "y": case "@":
							chars += "0123456789";
							break;
						case "D": case "M":
							return null; // Accept anything
						case "'":
							if (lookAhead("'")) {
								chars += "'";
							} else {
								literal = true;
							}
							break;
						default:
							chars += format.charAt(iFormat);
					}
				}
			}
			return chars;
		},

		/* Get a setting value, defaulting if necessary. */
		_get: function(inst, name) {
			return inst.settings[name] !== undefined ?
				inst.settings[name] : this._defaults[name];
		},

		/* Parse existing date and initialise date picker. */
		_setDateFromField: function(inst, noDefault) {
			if (inst.input.val() === inst.lastVal) {
				return;
			}

			var dateFormat = this._get(inst, "dateFormat"),
				dates = inst.lastVal = inst.input ? inst.input.val() : null,
				defaultDate = this._getDefaultDate(inst),
				date = defaultDate,
				settings = this._getFormatConfig(inst);

			try {
				date = this.parseDate(dateFormat, dates, settings) || defaultDate;
			} catch (event) {
				dates = (noDefault ? "" : dates);
			}
			inst.selectedDay = date.getDate();
			inst.drawMonth = inst.selectedMonth = date.getMonth();
			inst.drawYear = inst.selectedYear = date.getFullYear();
			inst.currentDay = (dates ? date.getDate() : 0);
			inst.currentMonth = (dates ? date.getMonth() : 0);
			inst.currentYear = (dates ? date.getFullYear() : 0);
			this._adjustInstDate(inst);
		},

		/* Retrieve the default date shown on opening. */
		_getDefaultDate: function(inst) {
			return this._restrictMinMax(inst,
				this._determineDate(inst, this._get(inst, "defaultDate"), new Date()));
		},

		/* A date may be specified as an exact value or a relative one. */
		_determineDate: function(inst, date, defaultDate) {
			var offsetNumeric = function(offset) {
					var date = new Date();
					date.setDate(date.getDate() + offset);
					return date;
				},
				offsetString = function(offset) {
					try {
						return $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"),
							offset, $.datepicker._getFormatConfig(inst));
					}
					catch (e) {
						// Ignore
					}

					var date = (offset.toLowerCase().match(/^c/) ?
						$.datepicker._getDate(inst) : null) || new Date(),
						year = date.getFullYear(),
						month = date.getMonth(),
						day = date.getDate(),
						pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,
						matches = pattern.exec(offset);

					while (matches) {
						switch (matches[2] || "d") {
							case "d" : case "D" :
								day += parseInt(matches[1],10); break;
							case "w" : case "W" :
								day += parseInt(matches[1],10) * 7; break;
							case "m" : case "M" :
								month += parseInt(matches[1],10);
								day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
								break;
							case "y": case "Y" :
								year += parseInt(matches[1],10);
								day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
								break;
						}
						matches = pattern.exec(offset);
					}
					return new Date(year, month, day);
				},
				newDate = (date == null || date === "" ? defaultDate : (typeof date === "string" ? offsetString(date) :
					(typeof date === "number" ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime()))));

			newDate = (newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate);
			if (newDate) {
				newDate.setHours(0);
				newDate.setMinutes(0);
				newDate.setSeconds(0);
				newDate.setMilliseconds(0);
			}
			return this._daylightSavingAdjust(newDate);
		},

		/* Handle switch to/from daylight saving.
		 * Hours may be non-zero on daylight saving cut-over:
		 * > 12 when midnight changeover, but then cannot generate
		 * midnight datetime, so jump to 1AM, otherwise reset.
		 * @param  date  (Date) the date to check
		 * @return  (Date) the corrected date
		 */
		_daylightSavingAdjust: function(date) {
			if (!date) {
				return null;
			}
			date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0);
			return date;
		},

		/* Set the date(s) directly. */
		_setDate: function(inst, date, noChange) {
			var clear = !date,
				origMonth = inst.selectedMonth,
				origYear = inst.selectedYear,
				newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date()));

			inst.selectedDay = inst.currentDay = newDate.getDate();
			inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth();
			inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear();
			if ((origMonth !== inst.selectedMonth || origYear !== inst.selectedYear) && !noChange) {
				this._notifyChange(inst);
			}
			this._adjustInstDate(inst);
			if (inst.input) {
				inst.input.val(clear ? "" : this._formatDate(inst));
			}
		},

		/* Retrieve the date(s) directly. */
		_getDate: function(inst) {
			var startDate = (!inst.currentYear || (inst.input && inst.input.val() === "") ? null :
				this._daylightSavingAdjust(new Date(
				inst.currentYear, inst.currentMonth, inst.currentDay)));
				return startDate;
		},

		/* Attach the onxxx handlers.  These are declared statically so
		 * they work with static code transformers like Caja.
		 */
		_attachHandlers: function(inst) {
			var stepMonths = this._get(inst, "stepMonths"),
				id = "#" + inst.id.replace( /\\\\/g, "\\" );
			inst.dpDiv.find("[data-handler]").map(function () {
				var handler = {
					prev: function () {
						$.datepicker._adjustDate(id, -stepMonths, "M");
					},
					next: function () {
						$.datepicker._adjustDate(id, +stepMonths, "M");
					},
					hide: function () {
						$.datepicker._hideDatepicker();
					},
					today: function () {
						$.datepicker._gotoToday(id);
					},
					selectDay: function () {
						$.datepicker._selectDay(id, +this.getAttribute("data-month"), +this.getAttribute("data-year"), this);
						return false;
					},
					selectMonth: function () {
						$.datepicker._selectMonthYear(id, this, "M");
						return false;
					},
					selectYear: function () {
						$.datepicker._selectMonthYear(id, this, "Y");
						return false;
					}
				};
				$(this).bind(this.getAttribute("data-event"), handler[this.getAttribute("data-handler")]);
			});
		},

		/* Generate the HTML for the current state of the date picker. */
		_generateHTML: function(inst) {
			var maxDraw, prevText, prev, nextText, next, currentText, gotoDate,
				controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin,
				monthNames, monthNamesShort, beforeShowDay, showOtherMonths,
				selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate,
				cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows,
				printDate, dRow, tbody, daySettings, otherMonth, unselectable,
				tempDate = new Date(),
				today = this._daylightSavingAdjust(
					new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())), // clear time
				isRTL = this._get(inst, "isRTL"),
				showButtonPanel = this._get(inst, "showButtonPanel"),
				hideIfNoPrevNext = this._get(inst, "hideIfNoPrevNext"),
				navigationAsDateFormat = this._get(inst, "navigationAsDateFormat"),
				numMonths = this._getNumberOfMonths(inst),
				showCurrentAtPos = this._get(inst, "showCurrentAtPos"),
				stepMonths = this._get(inst, "stepMonths"),
				isMultiMonth = (numMonths[0] !== 1 || numMonths[1] !== 1),
				currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) :
					new Date(inst.currentYear, inst.currentMonth, inst.currentDay))),
				minDate = this._getMinMaxDate(inst, "min"),
				maxDate = this._getMinMaxDate(inst, "max"),
				drawMonth = inst.drawMonth - showCurrentAtPos,
				drawYear = inst.drawYear;

			if (drawMonth < 0) {
				drawMonth += 12;
				drawYear--;
			}
			if (maxDate) {
				maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(),
					maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate()));
				maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw);
				while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) {
					drawMonth--;
					if (drawMonth < 0) {
						drawMonth = 11;
						drawYear--;
					}
				}
			}
			inst.drawMonth = drawMonth;
			inst.drawYear = drawYear;

			prevText = this._get(inst, "prevText");
			prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText,
				this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)),
				this._getFormatConfig(inst)));

			prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ?
				"<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" +
				" title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>" :
				(hideIfNoPrevNext ? "" : "<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='"+ prevText +"'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>"));

			nextText = this._get(inst, "nextText");
			nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText,
				this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)),
				this._getFormatConfig(inst)));

			next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ?
				"<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" +
				" title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>" :
				(hideIfNoPrevNext ? "" : "<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='"+ nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>"));

			currentText = this._get(inst, "currentText");
			gotoDate = (this._get(inst, "gotoCurrent") && inst.currentDay ? currentDate : today);
			currentText = (!navigationAsDateFormat ? currentText :
				this.formatDate(currentText, gotoDate, this._getFormatConfig(inst)));

			controls = (!inst.inline ? "<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>" +
				this._get(inst, "closeText") + "</button>" : "");

			buttonPanel = (showButtonPanel) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + (isRTL ? controls : "") +
				(this._isInRange(inst, gotoDate) ? "<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'" +
				">" + currentText + "</button>" : "") + (isRTL ? "" : controls) + "</div>" : "";

			firstDay = parseInt(this._get(inst, "firstDay"),10);
			firstDay = (isNaN(firstDay) ? 0 : firstDay);

			showWeek = this._get(inst, "showWeek");
			dayNames = this._get(inst, "dayNames");
			dayNamesMin = this._get(inst, "dayNamesMin");
			monthNames = this._get(inst, "monthNames");
			monthNamesShort = this._get(inst, "monthNamesShort");
			beforeShowDay = this._get(inst, "beforeShowDay");
			showOtherMonths = this._get(inst, "showOtherMonths");
			selectOtherMonths = this._get(inst, "selectOtherMonths");
			defaultDate = this._getDefaultDate(inst);
			html = "";
			dow;
			for (row = 0; row < numMonths[0]; row++) {
				group = "";
				this.maxRows = 4;
				for (col = 0; col < numMonths[1]; col++) {
					selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay));
					cornerClass = " ui-corner-all";
					calender = "";
					if (isMultiMonth) {
						calender += "<div class='ui-datepicker-group";
						if (numMonths[1] > 1) {
							switch (col) {
								case 0: calender += " ui-datepicker-group-first";
									cornerClass = " ui-corner-" + (isRTL ? "right" : "left"); break;
								case numMonths[1]-1: calender += " ui-datepicker-group-last";
									cornerClass = " ui-corner-" + (isRTL ? "left" : "right"); break;
								default: calender += " ui-datepicker-group-middle"; cornerClass = ""; break;
							}
						}
						calender += "'>";
					}
					calender += "<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix" + cornerClass + "'>" +
						(/all|left/.test(cornerClass) && row === 0 ? (isRTL ? next : prev) : "") +
						(/all|right/.test(cornerClass) && row === 0 ? (isRTL ? prev : next) : "") +
						this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate,
						row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers
						"</div><table class='ui-datepicker-calendar'><thead>" +
						"<tr>";
					thead = (showWeek ? "<th class='ui-datepicker-week-col'>" + this._get(inst, "weekHeader") + "</th>" : "");
					for (dow = 0; dow < 7; dow++) { // days of the week
						day = (dow + firstDay) % 7;
						thead += "<th" + ((dow + firstDay + 6) % 7 >= 5 ? " class='ui-datepicker-week-end'" : "") + ">" +
							"<span title='" + dayNames[day] + "'>" + dayNamesMin[day] + "</span></th>";
					}
					calender += thead + "</tr></thead><tbody>";
					daysInMonth = this._getDaysInMonth(drawYear, drawMonth);
					if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) {
						inst.selectedDay = Math.min(inst.selectedDay, daysInMonth);
					}
					leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7;
					curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate
					numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043)
					this.maxRows = numRows;
					printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays));
					for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows
						calender += "<tr>";
						tbody = (!showWeek ? "" : "<td class='ui-datepicker-week-col'>" +
							this._get(inst, "calculateWeek")(printDate) + "</td>");
						for (dow = 0; dow < 7; dow++) { // create date picker days
							daySettings = (beforeShowDay ?
								beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, ""]);
							otherMonth = (printDate.getMonth() !== drawMonth);
							unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] ||
								(minDate && printDate < minDate) || (maxDate && printDate > maxDate);
							tbody += "<td class='" +
								((dow + firstDay + 6) % 7 >= 5 ? " ui-datepicker-week-end" : "") + // highlight weekends
								(otherMonth ? " ui-datepicker-other-month" : "") + // highlight days from other months
								((printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent) || // user pressed key
								(defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime()) ?
								// or defaultDate is current printedDate and defaultDate is selectedDate
								" " + this._dayOverClass : "") + // highlight selected day
								(unselectable ? " " + this._unselectableClass + " ui-state-disabled": "") +  // highlight unselectable days
								(otherMonth && !showOtherMonths ? "" : " " + daySettings[1] + // highlight custom dates
								(printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "") + // highlight selected day
								(printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "")) + "'" + // highlight today (if different)
								((!otherMonth || showOtherMonths) && daySettings[2] ? " title='" + daySettings[2].replace(/'/g, "&#39;") + "'" : "") + // cell title
								(unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'") + ">" + // actions
								(otherMonth && !showOtherMonths ? "&#xa0;" : // display for other months
								(unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" +
								(printDate.getTime() === today.getTime() ? " ui-state-highlight" : "") +
								(printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "") + // highlight selected day
								(otherMonth ? " ui-priority-secondary" : "") + // distinguish dates from other months
								"' href='#'>" + printDate.getDate() + "</a>")) + "</td>"; // display selectable date
							printDate.setDate(printDate.getDate() + 1);
							printDate = this._daylightSavingAdjust(printDate);
						}
						calender += tbody + "</tr>";
					}
					drawMonth++;
					if (drawMonth > 11) {
						drawMonth = 0;
						drawYear++;
					}
					calender += "</tbody></table>" + (isMultiMonth ? "</div>" +
								((numMonths[0] > 0 && col === numMonths[1]-1) ? "<div class='ui-datepicker-row-break'></div>" : "") : "");
					group += calender;
				}
				html += group;
			}
			html += buttonPanel;
			inst._keyEvent = false;
			return html;
		},

		/* Generate the month and year header. */
		_generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate,
				secondary, monthNames, monthNamesShort) {

			var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear,
				changeMonth = this._get(inst, "changeMonth"),
				changeYear = this._get(inst, "changeYear"),
				showMonthAfterYear = this._get(inst, "showMonthAfterYear"),
				html = "<div class='ui-datepicker-title'>",
				monthHtml = "";

			// month selection
			if (secondary || !changeMonth) {
				monthHtml += "<span class='ui-datepicker-month'>" + monthNames[drawMonth] + "</span>";
			} else {
				inMinYear = (minDate && minDate.getFullYear() === drawYear);
				inMaxYear = (maxDate && maxDate.getFullYear() === drawYear);
				monthHtml += "<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>";
				for ( month = 0; month < 12; month++) {
					if ((!inMinYear || month >= minDate.getMonth()) && (!inMaxYear || month <= maxDate.getMonth())) {
						monthHtml += "<option value='" + month + "'" +
							(month === drawMonth ? " selected='selected'" : "") +
							">" + monthNamesShort[month] + "</option>";
					}
				}
				monthHtml += "</select>";
			}

			if (!showMonthAfterYear) {
				html += monthHtml + (secondary || !(changeMonth && changeYear) ? "&#xa0;" : "");
			}

			// year selection
			if ( !inst.yearshtml ) {
				inst.yearshtml = "";
				if (secondary || !changeYear) {
					html += "<span class='ui-datepicker-year'>" + drawYear + "</span>";
				} else {
					// determine range of years to display
					years = this._get(inst, "yearRange").split(":");
					thisYear = new Date().getFullYear();
					determineYear = function(value) {
						var year = (value.match(/c[+\-].*/) ? drawYear + parseInt(value.substring(1), 10) :
							(value.match(/[+\-].*/) ? thisYear + parseInt(value, 10) :
							parseInt(value, 10)));
						return (isNaN(year) ? thisYear : year);
					};
					year = determineYear(years[0]);
					endYear = Math.max(year, determineYear(years[1] || ""));
					year = (minDate ? Math.max(year, minDate.getFullYear()) : year);
					endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear);
					inst.yearshtml += "<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>";
					for (; year <= endYear; year++) {
						inst.yearshtml += "<option value='" + year + "'" +
							(year === drawYear ? " selected='selected'" : "") +
							">" + year + "</option>";
					}
					inst.yearshtml += "</select>";

					html += inst.yearshtml;
					inst.yearshtml = null;
				}
			}

			html += this._get(inst, "yearSuffix");
			if (showMonthAfterYear) {
				html += (secondary || !(changeMonth && changeYear) ? "&#xa0;" : "") + monthHtml;
			}
			html += "</div>"; // Close datepicker_header
			return html;
		},

		/* Adjust one of the date sub-fields. */
		_adjustInstDate: function(inst, offset, period) {
			var year = inst.drawYear + (period === "Y" ? offset : 0),
				month = inst.drawMonth + (period === "M" ? offset : 0),
				day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + (period === "D" ? offset : 0),
				date = this._restrictMinMax(inst, this._daylightSavingAdjust(new Date(year, month, day)));

			inst.selectedDay = date.getDate();
			inst.drawMonth = inst.selectedMonth = date.getMonth();
			inst.drawYear = inst.selectedYear = date.getFullYear();
			if (period === "M" || period === "Y") {
				this._notifyChange(inst);
			}
		},

		/* Ensure a date is within any min/max bounds. */
		_restrictMinMax: function(inst, date) {
			var minDate = this._getMinMaxDate(inst, "min"),
				maxDate = this._getMinMaxDate(inst, "max"),
				newDate = (minDate && date < minDate ? minDate : date);
			return (maxDate && newDate > maxDate ? maxDate : newDate);
		},

		/* Notify change of month/year. */
		_notifyChange: function(inst) {
			var onChange = this._get(inst, "onChangeMonthYear");
			if (onChange) {
				onChange.apply((inst.input ? inst.input[0] : null),
					[inst.selectedYear, inst.selectedMonth + 1, inst]);
			}
		},

		/* Determine the number of months to show. */
		_getNumberOfMonths: function(inst) {
			var numMonths = this._get(inst, "numberOfMonths");
			return (numMonths == null ? [1, 1] : (typeof numMonths === "number" ? [1, numMonths] : numMonths));
		},

		/* Determine the current maximum date - ensure no time components are set. */
		_getMinMaxDate: function(inst, minMax) {
			return this._determineDate(inst, this._get(inst, minMax + "Date"), null);
		},

		/* Find the number of days in a given month. */
		_getDaysInMonth: function(year, month) {
			return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate();
		},

		/* Find the day of the week of the first of a month. */
		_getFirstDayOfMonth: function(year, month) {
			return new Date(year, month, 1).getDay();
		},

		/* Determines if we should allow a "next/prev" month display change. */
		_canAdjustMonth: function(inst, offset, curYear, curMonth) {
			var numMonths = this._getNumberOfMonths(inst),
				date = this._daylightSavingAdjust(new Date(curYear,
				curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1));

			if (offset < 0) {
				date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth()));
			}
			return this._isInRange(inst, date);
		},

		/* Is the given date in the accepted range? */
		_isInRange: function(inst, date) {
			var yearSplit, currentYear,
				minDate = this._getMinMaxDate(inst, "min"),
				maxDate = this._getMinMaxDate(inst, "max"),
				minYear = null,
				maxYear = null,
				years = this._get(inst, "yearRange");
				if (years){
					yearSplit = years.split(":");
					currentYear = new Date().getFullYear();
					minYear = parseInt(yearSplit[0], 10);
					maxYear = parseInt(yearSplit[1], 10);
					if ( yearSplit[0].match(/[+\-].*/) ) {
						minYear += currentYear;
					}
					if ( yearSplit[1].match(/[+\-].*/) ) {
						maxYear += currentYear;
					}
				}

			return ((!minDate || date.getTime() >= minDate.getTime()) &&
				(!maxDate || date.getTime() <= maxDate.getTime()) &&
				(!minYear || date.getFullYear() >= minYear) &&
				(!maxYear || date.getFullYear() <= maxYear));
		},

		/* Provide the configuration settings for formatting/parsing. */
		_getFormatConfig: function(inst) {
			var shortYearCutoff = this._get(inst, "shortYearCutoff");
			shortYearCutoff = (typeof shortYearCutoff !== "string" ? shortYearCutoff :
				new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
			return {shortYearCutoff: shortYearCutoff,
				dayNamesShort: this._get(inst, "dayNamesShort"), dayNames: this._get(inst, "dayNames"),
				monthNamesShort: this._get(inst, "monthNamesShort"), monthNames: this._get(inst, "monthNames")};
		},

		/* Format the given date for display. */
		_formatDate: function(inst, day, month, year) {
			if (!day) {
				inst.currentDay = inst.selectedDay;
				inst.currentMonth = inst.selectedMonth;
				inst.currentYear = inst.selectedYear;
			}
			var date = (day ? (typeof day === "object" ? day :
				this._daylightSavingAdjust(new Date(year, month, day))) :
				this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
			return this.formatDate(this._get(inst, "dateFormat"), date, this._getFormatConfig(inst));
		}
	});

	/*
	 * Bind hover events for datepicker elements.
	 * Done via delegate so the binding only occurs once in the lifetime of the parent div.
	 * Global instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker.
	 */
	function bindHover(dpDiv) {
		var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";
		return dpDiv.delegate(selector, "mouseout", function() {
				$(this).removeClass("ui-state-hover");
				if (this.className.indexOf("ui-datepicker-prev") !== -1) {
					$(this).removeClass("ui-datepicker-prev-hover");
				}
				if (this.className.indexOf("ui-datepicker-next") !== -1) {
					$(this).removeClass("ui-datepicker-next-hover");
				}
			})
			.delegate(selector, "mouseover", function(){
				if (!$.datepicker._isDisabledDatepicker( instActive.inline ? dpDiv.parent()[0] : instActive.input[0])) {
					$(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");
					$(this).addClass("ui-state-hover");
					if (this.className.indexOf("ui-datepicker-prev") !== -1) {
						$(this).addClass("ui-datepicker-prev-hover");
					}
					if (this.className.indexOf("ui-datepicker-next") !== -1) {
						$(this).addClass("ui-datepicker-next-hover");
					}
				}
			});
	}

	/* jQuery extend now ignores nulls! */
	function extendRemove(target, props) {
		$.extend(target, props);
		for (var name in props) {
			if (props[name] == null) {
				target[name] = props[name];
			}
		}
		return target;
	}

	/* Invoke the datepicker functionality.
	   @param  options  string - a command, optionally followed by additional parameters or
						Object - settings for attaching new datepicker functionality
	   @return  jQuery object */
	$.fn.datepicker = function(options){

		/* Verify an empty collection wasn't passed - Fixes #6976 */
		if ( !this.length ) {
			return this;
		}

		/* Initialise the date picker. */
		if (!$.datepicker.initialized) {
			$(document).mousedown($.datepicker._checkExternalClick);
			$.datepicker.initialized = true;
		}

		/* Append datepicker main container to body if not exist. */
		if ($("#"+$.datepicker._mainDivId).length === 0) {
			$("body").append($.datepicker.dpDiv);
		}

		var otherArgs = Array.prototype.slice.call(arguments, 1);
		if (typeof options === "string" && (options === "isDisabled" || options === "getDate" || options === "widget")) {
			return $.datepicker["_" + options + "Datepicker"].
				apply($.datepicker, [this[0]].concat(otherArgs));
		}
		if (options === "option" && arguments.length === 2 && typeof arguments[1] === "string") {
			return $.datepicker["_" + options + "Datepicker"].
				apply($.datepicker, [this[0]].concat(otherArgs));
		}
		return this.each(function() {
			typeof options === "string" ?
				$.datepicker["_" + options + "Datepicker"].
					apply($.datepicker, [this].concat(otherArgs)) :
				$.datepicker._attachDatepicker(this, options);
		});
	};

	$.datepicker = new Datepicker(); // singleton instance
	$.datepicker.initialized = false;
	$.datepicker.uuid = new Date().getTime();
	$.datepicker.version = "1.10.4";

	})(jQuery);
	(function( $, undefined ) {

	var sizeRelatedOptions = {
			buttons: true,
			height: true,
			maxHeight: true,
			maxWidth: true,
			minHeight: true,
			minWidth: true,
			width: true
		},
		resizableRelatedOptions = {
			maxHeight: true,
			maxWidth: true,
			minHeight: true,
			minWidth: true
		};

	$.widget( "ui.dialog", {
		version: "1.10.4",
		options: {
			appendTo: "body",
			autoOpen: true,
			buttons: [],
			closeOnEscape: true,
			closeText: "close",
			dialogClass: "",
			draggable: true,
			hide: null,
			height: "auto",
			maxHeight: null,
			maxWidth: null,
			minHeight: 150,
			minWidth: 150,
			modal: false,
			position: {
				my: "center",
				at: "center",
				of: window,
				collision: "fit",
				// Ensure the titlebar is always visible
				using: function( pos ) {
					var topOffset = $( this ).css( pos ).offset().top;
					if ( topOffset < 0 ) {
						$( this ).css( "top", pos.top - topOffset );
					}
				}
			},
			resizable: true,
			show: null,
			title: null,
			width: 300,

			// callbacks
			beforeClose: null,
			close: null,
			drag: null,
			dragStart: null,
			dragStop: null,
			focus: null,
			open: null,
			resize: null,
			resizeStart: null,
			resizeStop: null
		},

		_create: function() {
			this.originalCss = {
				display: this.element[0].style.display,
				width: this.element[0].style.width,
				minHeight: this.element[0].style.minHeight,
				maxHeight: this.element[0].style.maxHeight,
				height: this.element[0].style.height
			};
			this.originalPosition = {
				parent: this.element.parent(),
				index: this.element.parent().children().index( this.element )
			};
			this.originalTitle = this.element.attr("title");
			this.options.title = this.options.title || this.originalTitle;

			this._createWrapper();

			this.element
				.show()
				.removeAttr("title")
				.addClass("ui-dialog-content ui-widget-content")
				.appendTo( this.uiDialog );

			this._createTitlebar();
			this._createButtonPane();

			if ( this.options.draggable && $.fn.draggable ) {
				this._makeDraggable();
			}
			if ( this.options.resizable && $.fn.resizable ) {
				this._makeResizable();
			}

			this._isOpen = false;
		},

		_init: function() {
			if ( this.options.autoOpen ) {
				this.open();
			}
		},

		_appendTo: function() {
			var element = this.options.appendTo;
			if ( element && (element.jquery || element.nodeType) ) {
				return $( element );
			}
			return this.document.find( element || "body" ).eq( 0 );
		},

		_destroy: function() {
			var next,
				originalPosition = this.originalPosition;

			this._destroyOverlay();

			this.element
				.removeUniqueId()
				.removeClass("ui-dialog-content ui-widget-content")
				.css( this.originalCss )
				// Without detaching first, the following becomes really slow
				.detach();

			this.uiDialog.stop( true, true ).remove();

			if ( this.originalTitle ) {
				this.element.attr( "title", this.originalTitle );
			}

			next = originalPosition.parent.children().eq( originalPosition.index );
			// Don't try to place the dialog next to itself (#8613)
			if ( next.length && next[0] !== this.element[0] ) {
				next.before( this.element );
			} else {
				originalPosition.parent.append( this.element );
			}
		},

		widget: function() {
			return this.uiDialog;
		},

		disable: $.noop,
		enable: $.noop,

		close: function( event ) {
			var activeElement,
				that = this;

			if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) {
				return;
			}

			this._isOpen = false;
			this._destroyOverlay();

			if ( !this.opener.filter(":focusable").focus().length ) {

				// support: IE9
				// IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
				try {
					activeElement = this.document[ 0 ].activeElement;

					// Support: IE9, IE10
					// If the <body> is blurred, IE will switch windows, see #4520
					if ( activeElement && activeElement.nodeName.toLowerCase() !== "body" ) {

						// Hiding a focused element doesn't trigger blur in WebKit
						// so in case we have nothing to focus on, explicitly blur the active element
						// https://bugs.webkit.org/show_bug.cgi?id=47182
						$( activeElement ).blur();
					}
				} catch ( error ) {}
			}

			this._hide( this.uiDialog, this.options.hide, function() {
				that._trigger( "close", event );
			});
		},

		isOpen: function() {
			return this._isOpen;
		},

		moveToTop: function() {
			this._moveToTop();
		},

		_moveToTop: function( event, silent ) {
			var moved = !!this.uiDialog.nextAll(":visible").insertBefore( this.uiDialog ).length;
			if ( moved && !silent ) {
				this._trigger( "focus", event );
			}
			return moved;
		},

		open: function() {
			var that = this;
			if ( this._isOpen ) {
				if ( this._moveToTop() ) {
					this._focusTabbable();
				}
				return;
			}

			this._isOpen = true;
			this.opener = $( this.document[0].activeElement );

			this._size();
			this._position();
			this._createOverlay();
			this._moveToTop( null, true );
			this._show( this.uiDialog, this.options.show, function() {
				that._focusTabbable();
				that._trigger("focus");
			});

			this._trigger("open");
		},

		_focusTabbable: function() {
			// Set focus to the first match:
			// 1. First element inside the dialog matching [autofocus]
			// 2. Tabbable element inside the content element
			// 3. Tabbable element inside the buttonpane
			// 4. The close button
			// 5. The dialog itself
			var hasFocus = this.element.find("[autofocus]");
			if ( !hasFocus.length ) {
				hasFocus = this.element.find(":tabbable");
			}
			if ( !hasFocus.length ) {
				hasFocus = this.uiDialogButtonPane.find(":tabbable");
			}
			if ( !hasFocus.length ) {
				hasFocus = this.uiDialogTitlebarClose.filter(":tabbable");
			}
			if ( !hasFocus.length ) {
				hasFocus = this.uiDialog;
			}
			hasFocus.eq( 0 ).focus();
		},

		_keepFocus: function( event ) {
			function checkFocus() {
				var activeElement = this.document[0].activeElement,
					isActive = this.uiDialog[0] === activeElement ||
						$.contains( this.uiDialog[0], activeElement );
				if ( !isActive ) {
					this._focusTabbable();
				}
			}
			event.preventDefault();
			checkFocus.call( this );
			// support: IE
			// IE <= 8 doesn't prevent moving focus even with event.preventDefault()
			// so we check again later
			this._delay( checkFocus );
		},

		_createWrapper: function() {
			this.uiDialog = $("<div>")
				.addClass( "ui-dialog ui-widget ui-widget-content ui-corner-all ui-front " +
					this.options.dialogClass )
				.hide()
				.attr({
					// Setting tabIndex makes the div focusable
					tabIndex: -1,
					role: "dialog"
				})
				.appendTo( this._appendTo() );

			this._on( this.uiDialog, {
				keydown: function( event ) {
					if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
							event.keyCode === $.ui.keyCode.ESCAPE ) {
						event.preventDefault();
						this.close( event );
						return;
					}

					// prevent tabbing out of dialogs
					if ( event.keyCode !== $.ui.keyCode.TAB ) {
						return;
					}
					var tabbables = this.uiDialog.find(":tabbable"),
						first = tabbables.filter(":first"),
						last  = tabbables.filter(":last");

					if ( ( event.target === last[0] || event.target === this.uiDialog[0] ) && !event.shiftKey ) {
						first.focus( 1 );
						event.preventDefault();
					} else if ( ( event.target === first[0] || event.target === this.uiDialog[0] ) && event.shiftKey ) {
						last.focus( 1 );
						event.preventDefault();
					}
				},
				mousedown: function( event ) {
					if ( this._moveToTop( event ) ) {
						this._focusTabbable();
					}
				}
			});

			// We assume that any existing aria-describedby attribute means
			// that the dialog content is marked up properly
			// otherwise we brute force the content as the description
			if ( !this.element.find("[aria-describedby]").length ) {
				this.uiDialog.attr({
					"aria-describedby": this.element.uniqueId().attr("id")
				});
			}
		},

		_createTitlebar: function() {
			var uiDialogTitle;

			this.uiDialogTitlebar = $("<div>")
				.addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix")
				.prependTo( this.uiDialog );
			this._on( this.uiDialogTitlebar, {
				mousedown: function( event ) {
					// Don't prevent click on close button (#8838)
					// Focusing a dialog that is partially scrolled out of view
					// causes the browser to scroll it into view, preventing the click event
					if ( !$( event.target ).closest(".ui-dialog-titlebar-close") ) {
						// Dialog isn't getting focus when dragging (#8063)
						this.uiDialog.focus();
					}
				}
			});

			// support: IE
			// Use type="button" to prevent enter keypresses in textboxes from closing the
			// dialog in IE (#9312)
			this.uiDialogTitlebarClose = $( "<button type='button'></button>" )
				.button({
					label: this.options.closeText,
					icons: {
						primary: "ui-icon-closethick"
					},
					text: false
				})
				.addClass("ui-dialog-titlebar-close")
				.appendTo( this.uiDialogTitlebar );
			this._on( this.uiDialogTitlebarClose, {
				click: function( event ) {
					event.preventDefault();
					this.close( event );
				}
			});

			uiDialogTitle = $("<span>")
				.uniqueId()
				.addClass("ui-dialog-title")
				.prependTo( this.uiDialogTitlebar );
			this._title( uiDialogTitle );

			this.uiDialog.attr({
				"aria-labelledby": uiDialogTitle.attr("id")
			});
		},

		_title: function( title ) {
			if ( !this.options.title ) {
				title.html("&#160;");
			}
			title.text( this.options.title );
		},

		_createButtonPane: function() {
			this.uiDialogButtonPane = $("<div>")
				.addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix");

			this.uiButtonSet = $("<div>")
				.addClass("ui-dialog-buttonset")
				.appendTo( this.uiDialogButtonPane );

			this._createButtons();
		},

		_createButtons: function() {
			var that = this,
				buttons = this.options.buttons;

			// if we already have a button pane, remove it
			this.uiDialogButtonPane.remove();
			this.uiButtonSet.empty();

			if ( $.isEmptyObject( buttons ) || ($.isArray( buttons ) && !buttons.length) ) {
				this.uiDialog.removeClass("ui-dialog-buttons");
				return;
			}

			$.each( buttons, function( name, props ) {
				var click, buttonOptions;
				props = $.isFunction( props ) ?
					{ click: props, text: name } :
					props;
				// Default to a non-submitting button
				props = $.extend( { type: "button" }, props );
				// Change the context for the click callback to be the main element
				click = props.click;
				props.click = function() {
					click.apply( that.element[0], arguments );
				};
				buttonOptions = {
					icons: props.icons,
					text: props.showText
				};
				delete props.icons;
				delete props.showText;
				$( "<button></button>", props )
					.button( buttonOptions )
					.appendTo( that.uiButtonSet );
			});
			this.uiDialog.addClass("ui-dialog-buttons");
			this.uiDialogButtonPane.appendTo( this.uiDialog );
		},

		_makeDraggable: function() {
			var that = this,
				options = this.options;

			function filteredUi( ui ) {
				return {
					position: ui.position,
					offset: ui.offset
				};
			}

			this.uiDialog.draggable({
				cancel: ".ui-dialog-content, .ui-dialog-titlebar-close",
				handle: ".ui-dialog-titlebar",
				containment: "document",
				start: function( event, ui ) {
					$( this ).addClass("ui-dialog-dragging");
					that._blockFrames();
					that._trigger( "dragStart", event, filteredUi( ui ) );
				},
				drag: function( event, ui ) {
					that._trigger( "drag", event, filteredUi( ui ) );
				},
				stop: function( event, ui ) {
					options.position = [
						ui.position.left - that.document.scrollLeft(),
						ui.position.top - that.document.scrollTop()
					];
					$( this ).removeClass("ui-dialog-dragging");
					that._unblockFrames();
					that._trigger( "dragStop", event, filteredUi( ui ) );
				}
			});
		},

		_makeResizable: function() {
			var that = this,
				options = this.options,
				handles = options.resizable,
				// .ui-resizable has position: relative defined in the stylesheet
				// but dialogs have to use absolute or fixed positioning
				position = this.uiDialog.css("position"),
				resizeHandles = typeof handles === "string" ?
					handles	:
					"n,e,s,w,se,sw,ne,nw";

			function filteredUi( ui ) {
				return {
					originalPosition: ui.originalPosition,
					originalSize: ui.originalSize,
					position: ui.position,
					size: ui.size
				};
			}

			this.uiDialog.resizable({
				cancel: ".ui-dialog-content",
				containment: "document",
				alsoResize: this.element,
				maxWidth: options.maxWidth,
				maxHeight: options.maxHeight,
				minWidth: options.minWidth,
				minHeight: this._minHeight(),
				handles: resizeHandles,
				start: function( event, ui ) {
					$( this ).addClass("ui-dialog-resizing");
					that._blockFrames();
					that._trigger( "resizeStart", event, filteredUi( ui ) );
				},
				resize: function( event, ui ) {
					that._trigger( "resize", event, filteredUi( ui ) );
				},
				stop: function( event, ui ) {
					options.height = $( this ).height();
					options.width = $( this ).width();
					$( this ).removeClass("ui-dialog-resizing");
					that._unblockFrames();
					that._trigger( "resizeStop", event, filteredUi( ui ) );
				}
			})
			.css( "position", position );
		},

		_minHeight: function() {
			var options = this.options;

			return options.height === "auto" ?
				options.minHeight :
				Math.min( options.minHeight, options.height );
		},

		_position: function() {
			// Need to show the dialog to get the actual offset in the position plugin
			var isVisible = this.uiDialog.is(":visible");
			if ( !isVisible ) {
				this.uiDialog.show();
			}
			this.uiDialog.position( this.options.position );
			if ( !isVisible ) {
				this.uiDialog.hide();
			}
		},

		_setOptions: function( options ) {
			var that = this,
				resize = false,
				resizableOptions = {};

			$.each( options, function( key, value ) {
				that._setOption( key, value );

				if ( key in sizeRelatedOptions ) {
					resize = true;
				}
				if ( key in resizableRelatedOptions ) {
					resizableOptions[ key ] = value;
				}
			});

			if ( resize ) {
				this._size();
				this._position();
			}
			if ( this.uiDialog.is(":data(ui-resizable)") ) {
				this.uiDialog.resizable( "option", resizableOptions );
			}
		},

		_setOption: function( key, value ) {
			var isDraggable, isResizable,
				uiDialog = this.uiDialog;

			if ( key === "dialogClass" ) {
				uiDialog
					.removeClass( this.options.dialogClass )
					.addClass( value );
			}

			if ( key === "disabled" ) {
				return;
			}

			this._super( key, value );

			if ( key === "appendTo" ) {
				this.uiDialog.appendTo( this._appendTo() );
			}

			if ( key === "buttons" ) {
				this._createButtons();
			}

			if ( key === "closeText" ) {
				this.uiDialogTitlebarClose.button({
					// Ensure that we always pass a string
					label: "" + value
				});
			}

			if ( key === "draggable" ) {
				isDraggable = uiDialog.is(":data(ui-draggable)");
				if ( isDraggable && !value ) {
					uiDialog.draggable("destroy");
				}

				if ( !isDraggable && value ) {
					this._makeDraggable();
				}
			}

			if ( key === "position" ) {
				this._position();
			}

			if ( key === "resizable" ) {
				// currently resizable, becoming non-resizable
				isResizable = uiDialog.is(":data(ui-resizable)");
				if ( isResizable && !value ) {
					uiDialog.resizable("destroy");
				}

				// currently resizable, changing handles
				if ( isResizable && typeof value === "string" ) {
					uiDialog.resizable( "option", "handles", value );
				}

				// currently non-resizable, becoming resizable
				if ( !isResizable && value !== false ) {
					this._makeResizable();
				}
			}

			if ( key === "title" ) {
				this._title( this.uiDialogTitlebar.find(".ui-dialog-title") );
			}
		},

		_size: function() {
			// If the user has resized the dialog, the .ui-dialog and .ui-dialog-content
			// divs will both have width and height set, so we need to reset them
			var nonContentHeight, minContentHeight, maxContentHeight,
				options = this.options;

			// Reset content sizing
			this.element.show().css({
				width: "auto",
				minHeight: 0,
				maxHeight: "none",
				height: 0
			});

			if ( options.minWidth > options.width ) {
				options.width = options.minWidth;
			}

			// reset wrapper sizing
			// determine the height of all the non-content elements
			nonContentHeight = this.uiDialog.css({
					height: "auto",
					width: options.width
				})
				.outerHeight();
			minContentHeight = Math.max( 0, options.minHeight - nonContentHeight );
			maxContentHeight = typeof options.maxHeight === "number" ?
				Math.max( 0, options.maxHeight - nonContentHeight ) :
				"none";

			if ( options.height === "auto" ) {
				this.element.css({
					minHeight: minContentHeight,
					maxHeight: maxContentHeight,
					height: "auto"
				});
			} else {
				this.element.height( Math.max( 0, options.height - nonContentHeight ) );
			}

			if (this.uiDialog.is(":data(ui-resizable)") ) {
				this.uiDialog.resizable( "option", "minHeight", this._minHeight() );
			}
		},

		_blockFrames: function() {
			this.iframeBlocks = this.document.find( "iframe" ).map(function() {
				var iframe = $( this );

				return $( "<div>" )
					.css({
						position: "absolute",
						width: iframe.outerWidth(),
						height: iframe.outerHeight()
					})
					.appendTo( iframe.parent() )
					.offset( iframe.offset() )[0];
			});
		},

		_unblockFrames: function() {
			if ( this.iframeBlocks ) {
				this.iframeBlocks.remove();
				delete this.iframeBlocks;
			}
		},

		_allowInteraction: function( event ) {
			if ( $( event.target ).closest(".ui-dialog").length ) {
				return true;
			}

			// TODO: Remove hack when datepicker implements
			// the .ui-front logic (#8989)
			return !!$( event.target ).closest(".ui-datepicker").length;
		},

		_createOverlay: function() {
			if ( !this.options.modal ) {
				return;
			}

			var that = this,
				widgetFullName = this.widgetFullName;
			if ( !$.ui.dialog.overlayInstances ) {
				// Prevent use of anchors and inputs.
				// We use a delay in case the overlay is created from an
				// event that we're going to be cancelling. (#2804)
				this._delay(function() {
					// Handle .dialog().dialog("close") (#4065)
					if ( $.ui.dialog.overlayInstances ) {
						this.document.bind( "focusin.dialog", function( event ) {
							if ( !that._allowInteraction( event ) ) {
								event.preventDefault();
								$(".ui-dialog:visible:last .ui-dialog-content")
									.data( widgetFullName )._focusTabbable();
							}
						});
					}
				});
			}

			this.overlay = $("<div>")
				.addClass("ui-widget-overlay ui-front")
				.appendTo( this._appendTo() );
			this._on( this.overlay, {
				mousedown: "_keepFocus"
			});
			$.ui.dialog.overlayInstances++;
		},

		_destroyOverlay: function() {
			if ( !this.options.modal ) {
				return;
			}

			if ( this.overlay ) {
				$.ui.dialog.overlayInstances--;

				if ( !$.ui.dialog.overlayInstances ) {
					this.document.unbind( "focusin.dialog" );
				}
				this.overlay.remove();
				this.overlay = null;
			}
		}
	});

	$.ui.dialog.overlayInstances = 0;

	// DEPRECATED
	if ( $.uiBackCompat !== false ) {
		// position option with array notation
		// just override with old implementation
		$.widget( "ui.dialog", $.ui.dialog, {
			_position: function() {
				var position = this.options.position,
					myAt = [],
					offset = [ 0, 0 ],
					isVisible;

				if ( position ) {
					if ( typeof position === "string" || (typeof position === "object" && "0" in position ) ) {
						myAt = position.split ? position.split(" ") : [ position[0], position[1] ];
						if ( myAt.length === 1 ) {
							myAt[1] = myAt[0];
						}

						$.each( [ "left", "top" ], function( i, offsetPosition ) {
							if ( +myAt[ i ] === myAt[ i ] ) {
								offset[ i ] = myAt[ i ];
								myAt[ i ] = offsetPosition;
							}
						});

						position = {
							my: myAt[0] + (offset[0] < 0 ? offset[0] : "+" + offset[0]) + " " +
								myAt[1] + (offset[1] < 0 ? offset[1] : "+" + offset[1]),
							at: myAt.join(" ")
						};
					}

					position = $.extend( {}, $.ui.dialog.prototype.options.position, position );
				} else {
					position = $.ui.dialog.prototype.options.position;
				}

				// need to show the dialog to get the actual offset in the position plugin
				isVisible = this.uiDialog.is(":visible");
				if ( !isVisible ) {
					this.uiDialog.show();
				}
				this.uiDialog.position( position );
				if ( !isVisible ) {
					this.uiDialog.hide();
				}
			}
		});
	}

	}( jQuery ) );
	(function( $, undefined ) {

	$.widget("ui.draggable", $.ui.mouse, {
		version: "1.10.4",
		widgetEventPrefix: "drag",
		options: {
			addClasses: true,
			appendTo: "parent",
			axis: false,
			connectToSortable: false,
			containment: false,
			cursor: "auto",
			cursorAt: false,
			grid: false,
			handle: false,
			helper: "original",
			iframeFix: false,
			opacity: false,
			refreshPositions: false,
			revert: false,
			revertDuration: 500,
			scope: "default",
			scroll: true,
			scrollSensitivity: 20,
			scrollSpeed: 20,
			snap: false,
			snapMode: "both",
			snapTolerance: 20,
			stack: false,
			zIndex: false,

			// callbacks
			drag: null,
			start: null,
			stop: null
		},
		_create: function() {

			if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) {
				this.element[0].style.position = "relative";
			}
			if (this.options.addClasses){
				this.element.addClass("ui-draggable");
			}
			if (this.options.disabled){
				this.element.addClass("ui-draggable-disabled");
			}

			this._mouseInit();

		},

		_destroy: function() {
			this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
			this._mouseDestroy();
		},

		_mouseCapture: function(event) {

			var o = this.options;

			// among others, prevent a drag on a resizable-handle
			if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) {
				return false;
			}

			//Quit if we're not on a valid handle
			this.handle = this._getHandle(event);
			if (!this.handle) {
				return false;
			}

			$(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
				$("<div class='ui-draggable-iframeFix' style='background: #fff;'></div>")
				.css({
					width: this.offsetWidth+"px", height: this.offsetHeight+"px",
					position: "absolute", opacity: "0.001", zIndex: 1000
				})
				.css($(this).offset())
				.appendTo("body");
			});

			return true;

		},

		_mouseStart: function(event) {

			var o = this.options;

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

			this.helper.addClass("ui-draggable-dragging");

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

			//If ddmanager is used for droppables, set the global draggable
			if($.ui.ddmanager) {
				$.ui.ddmanager.current = this;
			}

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

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

			//Store the helper's css position
			this.cssPosition = this.helper.css( "position" );
			this.scrollParent = this.helper.scrollParent();
			this.offsetParent = this.helper.offsetParent();
			this.offsetParentCssPosition = this.offsetParent.css( "position" );

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

			//Reset scroll cache
			this.offset.scroll = false;

			$.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.position = 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));

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

			//Trigger event + callbacks
			if(this._trigger("start", event) === false) {
				this._clear();
				return false;
			}

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

			//Prepare the droppable offsets
			if ($.ui.ddmanager && !o.dropBehaviour) {
				$.ui.ddmanager.prepareOffsets(this, event);
			}


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

			//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
			if ( $.ui.ddmanager ) {
				$.ui.ddmanager.dragStart(this, event);
			}

			return true;
		},

		_mouseDrag: function(event, noPropagation) {
			// reset any necessary cached properties (see #5009)
			if ( this.offsetParentCssPosition === "fixed" ) {
				this.offset.parent = this._getParentOffset();
			}

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

			//Call plugins and callbacks and use the resulting position if something is returned
			if (!noPropagation) {
				var ui = this._uiHash();
				if(this._trigger("drag", event, ui) === false) {
					this._mouseUp({});
					return false;
				}
				this.position = ui.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";
			}
			if($.ui.ddmanager) {
				$.ui.ddmanager.drag(this, event);
			}

			return false;
		},

		_mouseStop: function(event) {

			//If we are using droppables, inform the manager about the drop
			var that = this,
				dropped = false;
			if ($.ui.ddmanager && !this.options.dropBehaviour) {
				dropped = $.ui.ddmanager.drop(this, event);
			}

			//if a drop comes from outside (a sortable)
			if(this.dropped) {
				dropped = this.dropped;
				this.dropped = false;
			}

			//if the original element is no longer in the DOM don't bother to continue (see #8269)
			if ( this.options.helper === "original" && !$.contains( this.element[ 0 ].ownerDocument, this.element[ 0 ] ) ) {
				return false;
			}

			if((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
				$(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
					if(that._trigger("stop", event) !== false) {
						that._clear();
					}
				});
			} else {
				if(this._trigger("stop", event) !== false) {
					this._clear();
				}
			}

			return false;
		},

		_mouseUp: function(event) {
			//Remove frame helpers
			$("div.ui-draggable-iframeFix").each(function() {
				this.parentNode.removeChild(this);
			});

			//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
			if( $.ui.ddmanager ) {
				$.ui.ddmanager.dragStop(this, event);
			}

			return $.ui.mouse.prototype._mouseUp.call(this, event);
		},

		cancel: function() {

			if(this.helper.is(".ui-draggable-dragging")) {
				this._mouseUp({});
			} else {
				this._clear();
			}

			return this;

		},

		_getHandle: function(event) {
			return this.options.handle ?
				!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
				true;
		},

		_createHelper: function(event) {

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

			if(!helper.parents("body").length) {
				helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo));
			}

			if(helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) {
				helper.css("position", "absolute");
			}

			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
			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 && $.contains(this.scrollParent[0], this.offsetParent[0])) {
				po.left += this.scrollParent.scrollLeft();
				po.top += this.scrollParent.scrollTop();
			}

			//This needs to be actually done for all browsers, since pageX/pageY includes this information
			//Ugly IE fix
			if((this.offsetParent[0] === document.body) ||
				(this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
				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.element.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.element.css("marginLeft"),10) || 0),
				top: (parseInt(this.element.css("marginTop"),10) || 0),
				right: (parseInt(this.element.css("marginRight"),10) || 0),
				bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
			};
		},

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

		_setContainment: function() {

			var over, c, ce,
				o = this.options;

			if ( !o.containment ) {
				this.containment = null;
				return;
			}

			if ( o.containment === "window" ) {
				this.containment = [
					$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
					$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
					$( window ).scrollLeft() + $( window ).width() - this.helperProportions.width - this.margins.left,
					$( window ).scrollTop() + ( $( window ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
				];
				return;
			}

			if ( o.containment === "document") {
				this.containment = [
					0,
					0,
					$( document ).width() - this.helperProportions.width - this.margins.left,
					( $( document ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
				];
				return;
			}

			if ( o.containment.constructor === Array ) {
				this.containment = o.containment;
				return;
			}

			if ( o.containment === "parent" ) {
				o.containment = this.helper[ 0 ].parentNode;
			}

			c = $( o.containment );
			ce = c[ 0 ];

			if( !ce ) {
				return;
			}

			over = c.css( "overflow" ) !== "hidden";

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

		_convertPositionTo: function(d, pos) {

			if(!pos) {
				pos = this.position;
			}

			var mod = d === "absolute" ? 1 : -1,
				scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent;

			//Cache the scroll
			if (!this.offset.scroll) {
				this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()};
			}

			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)
					( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top ) * 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)
					( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left ) * mod )
				)
			};

		},

		_generatePosition: function(event) {

			var containment, co, top, left,
				o = this.options,
				scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent,
				pageX = event.pageX,
				pageY = event.pageY;

			//Cache the scroll
			if (!this.offset.scroll) {
				this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()};
			}

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

			// If we are not dragging yet, we won't check for options
			if ( this.originalPosition ) {
				if ( this.containment ) {
					if ( this.relative_container ){
						co = this.relative_container.offset();
						containment = [
							this.containment[ 0 ] + co.left,
							this.containment[ 1 ] + co.top,
							this.containment[ 2 ] + co.left,
							this.containment[ 3 ] + co.top
						];
					}
					else {
						containment = this.containment;
					}

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

				if(o.grid) {
					//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
					top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
					pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;

					left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
					pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= 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)
					( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top )
				),
				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)
					( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left )
				)
			};

		},

		_clear: function() {
			this.helper.removeClass("ui-draggable-dragging");
			if(this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) {
				this.helper.remove();
			}
			this.helper = null;
			this.cancelHelperRemoval = false;
		},

		// From now on bulk stuff - mainly helpers

		_trigger: function(type, event, ui) {
			ui = ui || this._uiHash();
			$.ui.plugin.call(this, type, [event, ui]);
			//The absolute position has to be recalculated after plugins
			if(type === "drag") {
				this.positionAbs = this._convertPositionTo("absolute");
			}
			return $.Widget.prototype._trigger.call(this, type, event, ui);
		},

		plugins: {},

		_uiHash: function() {
			return {
				helper: this.helper,
				position: this.position,
				originalPosition: this.originalPosition,
				offset: this.positionAbs
			};
		}

	});

	$.ui.plugin.add("draggable", "connectToSortable", {
		start: function(event, ui) {

			var inst = $(this).data("ui-draggable"), o = inst.options,
				uiSortable = $.extend({}, ui, { item: inst.element });
			inst.sortables = [];
			$(o.connectToSortable).each(function() {
				var sortable = $.data(this, "ui-sortable");
				if (sortable && !sortable.options.disabled) {
					inst.sortables.push({
						instance: sortable,
						shouldRevert: sortable.options.revert
					});
					sortable.refreshPositions();	// Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
					sortable._trigger("activate", event, uiSortable);
				}
			});

		},
		stop: function(event, ui) {

			//If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
			var inst = $(this).data("ui-draggable"),
				uiSortable = $.extend({}, ui, { item: inst.element });

			$.each(inst.sortables, function() {
				if(this.instance.isOver) {

					this.instance.isOver = 0;

					inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
					this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)

					//The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: "valid/invalid"
					if(this.shouldRevert) {
						this.instance.options.revert = this.shouldRevert;
					}

					//Trigger the stop of the sortable
					this.instance._mouseStop(event);

					this.instance.options.helper = this.instance.options._helper;

					//If the helper has been the original item, restore properties in the sortable
					if(inst.options.helper === "original") {
						this.instance.currentItem.css({ top: "auto", left: "auto" });
					}

				} else {
					this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
					this.instance._trigger("deactivate", event, uiSortable);
				}

			});

		},
		drag: function(event, ui) {

			var inst = $(this).data("ui-draggable"), that = this;

			$.each(inst.sortables, function() {

				var innermostIntersecting = false,
					thisSortable = this;

				//Copy over some variables to allow calling the sortable's native _intersectsWith
				this.instance.positionAbs = inst.positionAbs;
				this.instance.helperProportions = inst.helperProportions;
				this.instance.offset.click = inst.offset.click;

				if(this.instance._intersectsWith(this.instance.containerCache)) {
					innermostIntersecting = true;
					$.each(inst.sortables, function () {
						this.instance.positionAbs = inst.positionAbs;
						this.instance.helperProportions = inst.helperProportions;
						this.instance.offset.click = inst.offset.click;
						if (this !== thisSortable &&
							this.instance._intersectsWith(this.instance.containerCache) &&
							$.contains(thisSortable.instance.element[0], this.instance.element[0])
						) {
							innermostIntersecting = false;
						}
						return innermostIntersecting;
					});
				}


				if(innermostIntersecting) {
					//If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
					if(!this.instance.isOver) {

						this.instance.isOver = 1;
						//Now we fake the start of dragging for the sortable instance,
						//by cloning the list group item, appending it to the sortable and using it as inst.currentItem
						//We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
						this.instance.currentItem = $(that).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item", true);
						this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
						this.instance.options.helper = function() { return ui.helper[0]; };

						event.target = this.instance.currentItem[0];
						this.instance._mouseCapture(event, true);
						this.instance._mouseStart(event, true, true);

						//Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
						this.instance.offset.click.top = inst.offset.click.top;
						this.instance.offset.click.left = inst.offset.click.left;
						this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
						this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;

						inst._trigger("toSortable", event);
						inst.dropped = this.instance.element; //draggable revert needs that
						//hack so receive/update callbacks work (mostly)
						inst.currentItem = inst.element;
						this.instance.fromOutside = inst;

					}

					//Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
					if(this.instance.currentItem) {
						this.instance._mouseDrag(event);
					}

				} else {

					//If it doesn't intersect with the sortable, and it intersected before,
					//we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
					if(this.instance.isOver) {

						this.instance.isOver = 0;
						this.instance.cancelHelperRemoval = true;

						//Prevent reverting on this forced stop
						this.instance.options.revert = false;

						// The out event needs to be triggered independently
						this.instance._trigger("out", event, this.instance._uiHash(this.instance));

						this.instance._mouseStop(event, true);
						this.instance.options.helper = this.instance.options._helper;

						//Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
						this.instance.currentItem.remove();
						if(this.instance.placeholder) {
							this.instance.placeholder.remove();
						}

						inst._trigger("fromSortable", event);
						inst.dropped = false; //draggable revert needs that
					}

				}

			});

		}
	});

	$.ui.plugin.add("draggable", "cursor", {
		start: function() {
			var t = $("body"), o = $(this).data("ui-draggable").options;
			if (t.css("cursor")) {
				o._cursor = t.css("cursor");
			}
			t.css("cursor", o.cursor);
		},
		stop: function() {
			var o = $(this).data("ui-draggable").options;
			if (o._cursor) {
				$("body").css("cursor", o._cursor);
			}
		}
	});

	$.ui.plugin.add("draggable", "opacity", {
		start: function(event, ui) {
			var t = $(ui.helper), o = $(this).data("ui-draggable").options;
			if(t.css("opacity")) {
				o._opacity = t.css("opacity");
			}
			t.css("opacity", o.opacity);
		},
		stop: function(event, ui) {
			var o = $(this).data("ui-draggable").options;
			if(o._opacity) {
				$(ui.helper).css("opacity", o._opacity);
			}
		}
	});

	$.ui.plugin.add("draggable", "scroll", {
		start: function() {
			var i = $(this).data("ui-draggable");
			if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") {
				i.overflowOffset = i.scrollParent.offset();
			}
		},
		drag: function( event ) {

			var i = $(this).data("ui-draggable"), o = i.options, scrolled = false;

			if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") {

				if(!o.axis || o.axis !== "x") {
					if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
						i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
					} else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) {
						i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
					}
				}

				if(!o.axis || o.axis !== "y") {
					if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
						i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
					} else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) {
						i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
					}
				}

			} else {

				if(!o.axis || o.axis !== "x") {
					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(!o.axis || o.axis !== "y") {
					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(i, event);
			}

		}
	});

	$.ui.plugin.add("draggable", "snap", {
		start: function() {

			var i = $(this).data("ui-draggable"),
				o = i.options;

			i.snapElements = [];

			$(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() {
				var $t = $(this),
					$o = $t.offset();
				if(this !== i.element[0]) {
					i.snapElements.push({
						item: this,
						width: $t.outerWidth(), height: $t.outerHeight(),
						top: $o.top, left: $o.left
					});
				}
			});

		},
		drag: function(event, ui) {

			var ts, bs, ls, rs, l, r, t, b, i, first,
				inst = $(this).data("ui-draggable"),
				o = inst.options,
				d = o.snapTolerance,
				x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
				y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;

			for (i = inst.snapElements.length - 1; i >= 0; i--){

				l = inst.snapElements[i].left;
				r = l + inst.snapElements[i].width;
				t = inst.snapElements[i].top;
				b = t + inst.snapElements[i].height;

				if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || !$.contains( inst.snapElements[ i ].item.ownerDocument, inst.snapElements[ i ].item ) ) {
					if(inst.snapElements[i].snapping) {
						(inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
					}
					inst.snapElements[i].snapping = false;
					continue;
				}

				if(o.snapMode !== "inner") {
					ts = Math.abs(t - y2) <= d;
					bs = Math.abs(b - y1) <= d;
					ls = Math.abs(l - x2) <= d;
					rs = Math.abs(r - x1) <= d;
					if(ts) {
						ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
					}
					if(bs) {
						ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
					}
					if(ls) {
						ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
					}
					if(rs) {
						ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
					}
				}

				first = (ts || bs || ls || rs);

				if(o.snapMode !== "outer") {
					ts = Math.abs(t - y1) <= d;
					bs = Math.abs(b - y2) <= d;
					ls = Math.abs(l - x1) <= d;
					rs = Math.abs(r - x2) <= d;
					if(ts) {
						ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
					}
					if(bs) {
						ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
					}
					if(ls) {
						ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
					}
					if(rs) {
						ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
					}
				}

				if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) {
					(inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
				}
				inst.snapElements[i].snapping = (ts || bs || ls || rs || first);

			}

		}
	});

	$.ui.plugin.add("draggable", "stack", {
		start: function() {
			var min,
				o = this.data("ui-draggable").options,
				group = $.makeArray($(o.stack)).sort(function(a,b) {
					return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
				});

			if (!group.length) { return; }

			min = parseInt($(group[0]).css("zIndex"), 10) || 0;
			$(group).each(function(i) {
				$(this).css("zIndex", min + i);
			});
			this.css("zIndex", (min + group.length));
		}
	});

	$.ui.plugin.add("draggable", "zIndex", {
		start: function(event, ui) {
			var t = $(ui.helper), o = $(this).data("ui-draggable").options;
			if(t.css("zIndex")) {
				o._zIndex = t.css("zIndex");
			}
			t.css("zIndex", o.zIndex);
		},
		stop: function(event, ui) {
			var o = $(this).data("ui-draggable").options;
			if(o._zIndex) {
				$(ui.helper).css("zIndex", o._zIndex);
			}
		}
	});

	})(jQuery);
	(function( $, undefined ) {

	function isOverAxis( x, reference, size ) {
		return ( x > reference ) && ( x < ( reference + size ) );
	}

	$.widget("ui.droppable", {
		version: "1.10.4",
		widgetEventPrefix: "drop",
		options: {
			accept: "*",
			activeClass: false,
			addClasses: true,
			greedy: false,
			hoverClass: false,
			scope: "default",
			tolerance: "intersect",

			// callbacks
			activate: null,
			deactivate: null,
			drop: null,
			out: null,
			over: null
		},
		_create: function() {

			var proportions,
				o = this.options,
				accept = o.accept;

			this.isover = false;
			this.isout = true;

			this.accept = $.isFunction(accept) ? accept : function(d) {
				return d.is(accept);
			};

			this.proportions = function( /* valueToWrite */ ) {
				if ( arguments.length ) {
					// Store the droppable's proportions
					proportions = arguments[ 0 ];
				} else {
					// Retrieve or derive the droppable's proportions
					return proportions ?
						proportions :
						proportions = {
							width: this.element[ 0 ].offsetWidth,
							height: this.element[ 0 ].offsetHeight
						};
				}
			};

			// Add the reference and positions to the manager
			$.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
			$.ui.ddmanager.droppables[o.scope].push(this);

			(o.addClasses && this.element.addClass("ui-droppable"));

		},

		_destroy: function() {
			var i = 0,
				drop = $.ui.ddmanager.droppables[this.options.scope];

			for ( ; i < drop.length; i++ ) {
				if ( drop[i] === this ) {
					drop.splice(i, 1);
				}
			}

			this.element.removeClass("ui-droppable ui-droppable-disabled");
		},

		_setOption: function(key, value) {

			if(key === "accept") {
				this.accept = $.isFunction(value) ? value : function(d) {
					return d.is(value);
				};
			}
			$.Widget.prototype._setOption.apply(this, arguments);
		},

		_activate: function(event) {
			var draggable = $.ui.ddmanager.current;
			if(this.options.activeClass) {
				this.element.addClass(this.options.activeClass);
			}
			if(draggable){
				this._trigger("activate", event, this.ui(draggable));
			}
		},

		_deactivate: function(event) {
			var draggable = $.ui.ddmanager.current;
			if(this.options.activeClass) {
				this.element.removeClass(this.options.activeClass);
			}
			if(draggable){
				this._trigger("deactivate", event, this.ui(draggable));
			}
		},

		_over: function(event) {

			var draggable = $.ui.ddmanager.current;

			// Bail if draggable and droppable are same element
			if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
				return;
			}

			if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
				if(this.options.hoverClass) {
					this.element.addClass(this.options.hoverClass);
				}
				this._trigger("over", event, this.ui(draggable));
			}

		},

		_out: function(event) {

			var draggable = $.ui.ddmanager.current;

			// Bail if draggable and droppable are same element
			if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
				return;
			}

			if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
				if(this.options.hoverClass) {
					this.element.removeClass(this.options.hoverClass);
				}
				this._trigger("out", event, this.ui(draggable));
			}

		},

		_drop: function(event,custom) {

			var draggable = custom || $.ui.ddmanager.current,
				childrenIntersection = false;

			// Bail if draggable and droppable are same element
			if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
				return false;
			}

			this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function() {
				var inst = $.data(this, "ui-droppable");
				if(
					inst.options.greedy &&
					!inst.options.disabled &&
					inst.options.scope === draggable.options.scope &&
					inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) &&
					$.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
				) { childrenIntersection = true; return false; }
			});
			if(childrenIntersection) {
				return false;
			}

			if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
				if(this.options.activeClass) {
					this.element.removeClass(this.options.activeClass);
				}
				if(this.options.hoverClass) {
					this.element.removeClass(this.options.hoverClass);
				}
				this._trigger("drop", event, this.ui(draggable));
				return this.element;
			}

			return false;

		},

		ui: function(c) {
			return {
				draggable: (c.currentItem || c.element),
				helper: c.helper,
				position: c.position,
				offset: c.positionAbs
			};
		}

	});

	$.ui.intersect = function(draggable, droppable, toleranceMode) {

		if (!droppable.offset) {
			return false;
		}

		var draggableLeft, draggableTop,
			x1 = (draggable.positionAbs || draggable.position.absolute).left,
			y1 = (draggable.positionAbs || draggable.position.absolute).top,
			x2 = x1 + draggable.helperProportions.width,
			y2 = y1 + draggable.helperProportions.height,
			l = droppable.offset.left,
			t = droppable.offset.top,
			r = l + droppable.proportions().width,
			b = t + droppable.proportions().height;

		switch (toleranceMode) {
			case "fit":
				return (l <= x1 && x2 <= r && t <= y1 && y2 <= b);
			case "intersect":
				return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half
					x2 - (draggable.helperProportions.width / 2) < r && // Left Half
					t < y1 + (draggable.helperProportions.height / 2) && // Bottom Half
					y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
			case "pointer":
				draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left);
				draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top);
				return isOverAxis( draggableTop, t, droppable.proportions().height ) && isOverAxis( draggableLeft, l, droppable.proportions().width );
			case "touch":
				return (
					(y1 >= t && y1 <= b) ||	// Top edge touching
					(y2 >= t && y2 <= b) ||	// Bottom edge touching
					(y1 < t && y2 > b)		// Surrounded vertically
				) && (
					(x1 >= l && x1 <= r) ||	// Left edge touching
					(x2 >= l && x2 <= r) ||	// Right edge touching
					(x1 < l && x2 > r)		// Surrounded horizontally
				);
			default:
				return false;
			}

	};

	/*
		This manager tracks offsets of draggables and droppables
	*/
	$.ui.ddmanager = {
		current: null,
		droppables: { "default": [] },
		prepareOffsets: function(t, event) {

			var i, j,
				m = $.ui.ddmanager.droppables[t.options.scope] || [],
				type = event ? event.type : null, // workaround for #2317
				list = (t.currentItem || t.element).find(":data(ui-droppable)").addBack();

			droppablesLoop: for (i = 0; i < m.length; i++) {

				//No disabled and non-accepted
				if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) {
					continue;
				}

				// Filter out elements in the current dragged item
				for (j=0; j < list.length; j++) {
					if(list[j] === m[i].element[0]) {
						m[i].proportions().height = 0;
						continue droppablesLoop;
					}
				}

				m[i].visible = m[i].element.css("display") !== "none";
				if(!m[i].visible) {
					continue;
				}

				//Activate the droppable if used directly from draggables
				if(type === "mousedown") {
					m[i]._activate.call(m[i], event);
				}

				m[ i ].offset = m[ i ].element.offset();
				m[ i ].proportions({ width: m[ i ].element[ 0 ].offsetWidth, height: m[ i ].element[ 0 ].offsetHeight });

			}

		},
		drop: function(draggable, event) {

			var dropped = false;
			// Create a copy of the droppables in case the list changes during the drop (#9116)
			$.each(($.ui.ddmanager.droppables[draggable.options.scope] || []).slice(), function() {

				if(!this.options) {
					return;
				}
				if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) {
					dropped = this._drop.call(this, event) || dropped;
				}

				if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
					this.isout = true;
					this.isover = false;
					this._deactivate.call(this, event);
				}

			});
			return dropped;

		},
		dragStart: function( draggable, event ) {
			//Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
			draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() {
				if( !draggable.options.refreshPositions ) {
					$.ui.ddmanager.prepareOffsets( draggable, event );
				}
			});
		},
		drag: function(draggable, event) {

			//If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
			if(draggable.options.refreshPositions) {
				$.ui.ddmanager.prepareOffsets(draggable, event);
			}

			//Run through all droppables and check their positions based on specific tolerance options
			$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {

				if(this.options.disabled || this.greedyChild || !this.visible) {
					return;
				}

				var parentInstance, scope, parent,
					intersects = $.ui.intersect(draggable, this, this.options.tolerance),
					c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null);
				if(!c) {
					return;
				}

				if (this.options.greedy) {
					// find droppable parents with same scope
					scope = this.options.scope;
					parent = this.element.parents(":data(ui-droppable)").filter(function () {
						return $.data(this, "ui-droppable").options.scope === scope;
					});

					if (parent.length) {
						parentInstance = $.data(parent[0], "ui-droppable");
						parentInstance.greedyChild = (c === "isover");
					}
				}

				// we just moved into a greedy child
				if (parentInstance && c === "isover") {
					parentInstance.isover = false;
					parentInstance.isout = true;
					parentInstance._out.call(parentInstance, event);
				}

				this[c] = true;
				this[c === "isout" ? "isover" : "isout"] = false;
				this[c === "isover" ? "_over" : "_out"].call(this, event);

				// we just moved out of a greedy child
				if (parentInstance && c === "isout") {
					parentInstance.isout = false;
					parentInstance.isover = true;
					parentInstance._over.call(parentInstance, event);
				}
			});

		},
		dragStop: function( draggable, event ) {
			draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" );
			//Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
			if( !draggable.options.refreshPositions ) {
				$.ui.ddmanager.prepareOffsets( draggable, event );
			}
		}
	};

	})(jQuery);
	(function($, undefined) {

	var dataSpace = "ui-effects-";

	$.effects = {
		effect: {}
	};

	/*!
	 * jQuery Color Animations v2.1.2
	 * https://github.com/jquery/jquery-color
	 *
	 * Copyright 2013 jQuery Foundation and other contributors
	 * Released under the MIT license.
	 * http://jquery.org/license
	 *
	 * Date: Wed Jan 16 08:47:09 2013 -0600
	 */
	(function( jQuery, undefined ) {

		var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",

		// plusequals test for += 100 -= 100
		rplusequals = /^([\-+])=\s*(\d+\.?\d*)/,
		// a set of RE's that can match strings and generate color tuples.
		stringParsers = [{
				re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
				parse: function( execResult ) {
					return [
						execResult[ 1 ],
						execResult[ 2 ],
						execResult[ 3 ],
						execResult[ 4 ]
					];
				}
			}, {
				re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
				parse: function( execResult ) {
					return [
						execResult[ 1 ] * 2.55,
						execResult[ 2 ] * 2.55,
						execResult[ 3 ] * 2.55,
						execResult[ 4 ]
					];
				}
			}, {
				// this regex ignores A-F because it's compared against an already lowercased string
				re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,
				parse: function( execResult ) {
					return [
						parseInt( execResult[ 1 ], 16 ),
						parseInt( execResult[ 2 ], 16 ),
						parseInt( execResult[ 3 ], 16 )
					];
				}
			}, {
				// this regex ignores A-F because it's compared against an already lowercased string
				re: /#([a-f0-9])([a-f0-9])([a-f0-9])/,
				parse: function( execResult ) {
					return [
						parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),
						parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),
						parseInt( execResult[ 3 ] + execResult[ 3 ], 16 )
					];
				}
			}, {
				re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
				space: "hsla",
				parse: function( execResult ) {
					return [
						execResult[ 1 ],
						execResult[ 2 ] / 100,
						execResult[ 3 ] / 100,
						execResult[ 4 ]
					];
				}
			}],

		// jQuery.Color( )
		color = jQuery.Color = function( color, green, blue, alpha ) {
			return new jQuery.Color.fn.parse( color, green, blue, alpha );
		},
		spaces = {
			rgba: {
				props: {
					red: {
						idx: 0,
						type: "byte"
					},
					green: {
						idx: 1,
						type: "byte"
					},
					blue: {
						idx: 2,
						type: "byte"
					}
				}
			},

			hsla: {
				props: {
					hue: {
						idx: 0,
						type: "degrees"
					},
					saturation: {
						idx: 1,
						type: "percent"
					},
					lightness: {
						idx: 2,
						type: "percent"
					}
				}
			}
		},
		propTypes = {
			"byte": {
				floor: true,
				max: 255
			},
			"percent": {
				max: 1
			},
			"degrees": {
				mod: 360,
				floor: true
			}
		},
		support = color.support = {},

		// element for support tests
		supportElem = jQuery( "<p>" )[ 0 ],

		// colors = jQuery.Color.names
		colors,

		// local aliases of functions called often
		each = jQuery.each;

	// determine rgba support immediately
	supportElem.style.cssText = "background-color:rgba(1,1,1,.5)";
	support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1;

	// define cache name and alpha properties
	// for rgba and hsla spaces
	each( spaces, function( spaceName, space ) {
		space.cache = "_" + spaceName;
		space.props.alpha = {
			idx: 3,
			type: "percent",
			def: 1
		};
	});

	function clamp( value, prop, allowEmpty ) {
		var type = propTypes[ prop.type ] || {};

		if ( value == null ) {
			return (allowEmpty || !prop.def) ? null : prop.def;
		}

		// ~~ is an short way of doing floor for positive numbers
		value = type.floor ? ~~value : parseFloat( value );

		// IE will pass in empty strings as value for alpha,
		// which will hit this case
		if ( isNaN( value ) ) {
			return prop.def;
		}

		if ( type.mod ) {
			// we add mod before modding to make sure that negatives values
			// get converted properly: -10 -> 350
			return (value + type.mod) % type.mod;
		}

		// for now all property types without mod have min and max
		return 0 > value ? 0 : type.max < value ? type.max : value;
	}

	function stringParse( string ) {
		var inst = color(),
			rgba = inst._rgba = [];

		string = string.toLowerCase();

		each( stringParsers, function( i, parser ) {
			var parsed,
				match = parser.re.exec( string ),
				values = match && parser.parse( match ),
				spaceName = parser.space || "rgba";

			if ( values ) {
				parsed = inst[ spaceName ]( values );

				// if this was an rgba parse the assignment might happen twice
				// oh well....
				inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ];
				rgba = inst._rgba = parsed._rgba;

				// exit each( stringParsers ) here because we matched
				return false;
			}
		});

		// Found a stringParser that handled it
		if ( rgba.length ) {

			// if this came from a parsed string, force "transparent" when alpha is 0
			// chrome, (and maybe others) return "transparent" as rgba(0,0,0,0)
			if ( rgba.join() === "0,0,0,0" ) {
				jQuery.extend( rgba, colors.transparent );
			}
			return inst;
		}

		// named colors
		return colors[ string ];
	}

	color.fn = jQuery.extend( color.prototype, {
		parse: function( red, green, blue, alpha ) {
			if ( red === undefined ) {
				this._rgba = [ null, null, null, null ];
				return this;
			}
			if ( red.jquery || red.nodeType ) {
				red = jQuery( red ).css( green );
				green = undefined;
			}

			var inst = this,
				type = jQuery.type( red ),
				rgba = this._rgba = [];

			// more than 1 argument specified - assume ( red, green, blue, alpha )
			if ( green !== undefined ) {
				red = [ red, green, blue, alpha ];
				type = "array";
			}

			if ( type === "string" ) {
				return this.parse( stringParse( red ) || colors._default );
			}

			if ( type === "array" ) {
				each( spaces.rgba.props, function( key, prop ) {
					rgba[ prop.idx ] = clamp( red[ prop.idx ], prop );
				});
				return this;
			}

			if ( type === "object" ) {
				if ( red instanceof color ) {
					each( spaces, function( spaceName, space ) {
						if ( red[ space.cache ] ) {
							inst[ space.cache ] = red[ space.cache ].slice();
						}
					});
				} else {
					each( spaces, function( spaceName, space ) {
						var cache = space.cache;
						each( space.props, function( key, prop ) {

							// if the cache doesn't exist, and we know how to convert
							if ( !inst[ cache ] && space.to ) {

								// if the value was null, we don't need to copy it
								// if the key was alpha, we don't need to copy it either
								if ( key === "alpha" || red[ key ] == null ) {
									return;
								}
								inst[ cache ] = space.to( inst._rgba );
							}

							// this is the only case where we allow nulls for ALL properties.
							// call clamp with alwaysAllowEmpty
							inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true );
						});

						// everything defined but alpha?
						if ( inst[ cache ] && jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) {
							// use the default of 1
							inst[ cache ][ 3 ] = 1;
							if ( space.from ) {
								inst._rgba = space.from( inst[ cache ] );
							}
						}
					});
				}
				return this;
			}
		},
		is: function( compare ) {
			var is = color( compare ),
				same = true,
				inst = this;

			each( spaces, function( _, space ) {
				var localCache,
					isCache = is[ space.cache ];
				if (isCache) {
					localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || [];
					each( space.props, function( _, prop ) {
						if ( isCache[ prop.idx ] != null ) {
							same = ( isCache[ prop.idx ] === localCache[ prop.idx ] );
							return same;
						}
					});
				}
				return same;
			});
			return same;
		},
		_space: function() {
			var used = [],
				inst = this;
			each( spaces, function( spaceName, space ) {
				if ( inst[ space.cache ] ) {
					used.push( spaceName );
				}
			});
			return used.pop();
		},
		transition: function( other, distance ) {
			var end = color( other ),
				spaceName = end._space(),
				space = spaces[ spaceName ],
				startColor = this.alpha() === 0 ? color( "transparent" ) : this,
				start = startColor[ space.cache ] || space.to( startColor._rgba ),
				result = start.slice();

			end = end[ space.cache ];
			each( space.props, function( key, prop ) {
				var index = prop.idx,
					startValue = start[ index ],
					endValue = end[ index ],
					type = propTypes[ prop.type ] || {};

				// if null, don't override start value
				if ( endValue === null ) {
					return;
				}
				// if null - use end
				if ( startValue === null ) {
					result[ index ] = endValue;
				} else {
					if ( type.mod ) {
						if ( endValue - startValue > type.mod / 2 ) {
							startValue += type.mod;
						} else if ( startValue - endValue > type.mod / 2 ) {
							startValue -= type.mod;
						}
					}
					result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop );
				}
			});
			return this[ spaceName ]( result );
		},
		blend: function( opaque ) {
			// if we are already opaque - return ourself
			if ( this._rgba[ 3 ] === 1 ) {
				return this;
			}

			var rgb = this._rgba.slice(),
				a = rgb.pop(),
				blend = color( opaque )._rgba;

			return color( jQuery.map( rgb, function( v, i ) {
				return ( 1 - a ) * blend[ i ] + a * v;
			}));
		},
		toRgbaString: function() {
			var prefix = "rgba(",
				rgba = jQuery.map( this._rgba, function( v, i ) {
					return v == null ? ( i > 2 ? 1 : 0 ) : v;
				});

			if ( rgba[ 3 ] === 1 ) {
				rgba.pop();
				prefix = "rgb(";
			}

			return prefix + rgba.join() + ")";
		},
		toHslaString: function() {
			var prefix = "hsla(",
				hsla = jQuery.map( this.hsla(), function( v, i ) {
					if ( v == null ) {
						v = i > 2 ? 1 : 0;
					}

					// catch 1 and 2
					if ( i && i < 3 ) {
						v = Math.round( v * 100 ) + "%";
					}
					return v;
				});

			if ( hsla[ 3 ] === 1 ) {
				hsla.pop();
				prefix = "hsl(";
			}
			return prefix + hsla.join() + ")";
		},
		toHexString: function( includeAlpha ) {
			var rgba = this._rgba.slice(),
				alpha = rgba.pop();

			if ( includeAlpha ) {
				rgba.push( ~~( alpha * 255 ) );
			}

			return "#" + jQuery.map( rgba, function( v ) {

				// default to 0 when nulls exist
				v = ( v || 0 ).toString( 16 );
				return v.length === 1 ? "0" + v : v;
			}).join("");
		},
		toString: function() {
			return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString();
		}
	});
	color.fn.parse.prototype = color.fn;

	// hsla conversions adapted from:
	// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021

	function hue2rgb( p, q, h ) {
		h = ( h + 1 ) % 1;
		if ( h * 6 < 1 ) {
			return p + (q - p) * h * 6;
		}
		if ( h * 2 < 1) {
			return q;
		}
		if ( h * 3 < 2 ) {
			return p + (q - p) * ((2/3) - h) * 6;
		}
		return p;
	}

	spaces.hsla.to = function ( rgba ) {
		if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) {
			return [ null, null, null, rgba[ 3 ] ];
		}
		var r = rgba[ 0 ] / 255,
			g = rgba[ 1 ] / 255,
			b = rgba[ 2 ] / 255,
			a = rgba[ 3 ],
			max = Math.max( r, g, b ),
			min = Math.min( r, g, b ),
			diff = max - min,
			add = max + min,
			l = add * 0.5,
			h, s;

		if ( min === max ) {
			h = 0;
		} else if ( r === max ) {
			h = ( 60 * ( g - b ) / diff ) + 360;
		} else if ( g === max ) {
			h = ( 60 * ( b - r ) / diff ) + 120;
		} else {
			h = ( 60 * ( r - g ) / diff ) + 240;
		}

		// chroma (diff) == 0 means greyscale which, by definition, saturation = 0%
		// otherwise, saturation is based on the ratio of chroma (diff) to lightness (add)
		if ( diff === 0 ) {
			s = 0;
		} else if ( l <= 0.5 ) {
			s = diff / add;
		} else {
			s = diff / ( 2 - add );
		}
		return [ Math.round(h) % 360, s, l, a == null ? 1 : a ];
	};

	spaces.hsla.from = function ( hsla ) {
		if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) {
			return [ null, null, null, hsla[ 3 ] ];
		}
		var h = hsla[ 0 ] / 360,
			s = hsla[ 1 ],
			l = hsla[ 2 ],
			a = hsla[ 3 ],
			q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s,
			p = 2 * l - q;

		return [
			Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ),
			Math.round( hue2rgb( p, q, h ) * 255 ),
			Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ),
			a
		];
	};


	each( spaces, function( spaceName, space ) {
		var props = space.props,
			cache = space.cache,
			to = space.to,
			from = space.from;

		// makes rgba() and hsla()
		color.fn[ spaceName ] = function( value ) {

			// generate a cache for this space if it doesn't exist
			if ( to && !this[ cache ] ) {
				this[ cache ] = to( this._rgba );
			}
			if ( value === undefined ) {
				return this[ cache ].slice();
			}

			var ret,
				type = jQuery.type( value ),
				arr = ( type === "array" || type === "object" ) ? value : arguments,
				local = this[ cache ].slice();

			each( props, function( key, prop ) {
				var val = arr[ type === "object" ? key : prop.idx ];
				if ( val == null ) {
					val = local[ prop.idx ];
				}
				local[ prop.idx ] = clamp( val, prop );
			});

			if ( from ) {
				ret = color( from( local ) );
				ret[ cache ] = local;
				return ret;
			} else {
				return color( local );
			}
		};

		// makes red() green() blue() alpha() hue() saturation() lightness()
		each( props, function( key, prop ) {
			// alpha is included in more than one space
			if ( color.fn[ key ] ) {
				return;
			}
			color.fn[ key ] = function( value ) {
				var vtype = jQuery.type( value ),
					fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ),
					local = this[ fn ](),
					cur = local[ prop.idx ],
					match;

				if ( vtype === "undefined" ) {
					return cur;
				}

				if ( vtype === "function" ) {
					value = value.call( this, cur );
					vtype = jQuery.type( value );
				}
				if ( value == null && prop.empty ) {
					return this;
				}
				if ( vtype === "string" ) {
					match = rplusequals.exec( value );
					if ( match ) {
						value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 );
					}
				}
				local[ prop.idx ] = value;
				return this[ fn ]( local );
			};
		});
	});

	// add cssHook and .fx.step function for each named hook.
	// accept a space separated string of properties
	color.hook = function( hook ) {
		var hooks = hook.split( " " );
		each( hooks, function( i, hook ) {
			jQuery.cssHooks[ hook ] = {
				set: function( elem, value ) {
					var parsed, curElem,
						backgroundColor = "";

					if ( value !== "transparent" && ( jQuery.type( value ) !== "string" || ( parsed = stringParse( value ) ) ) ) {
						value = color( parsed || value );
						if ( !support.rgba && value._rgba[ 3 ] !== 1 ) {
							curElem = hook === "backgroundColor" ? elem.parentNode : elem;
							while (
								(backgroundColor === "" || backgroundColor === "transparent") &&
								curElem && curElem.style
							) {
								try {
									backgroundColor = jQuery.css( curElem, "backgroundColor" );
									curElem = curElem.parentNode;
								} catch ( e ) {
								}
							}

							value = value.blend( backgroundColor && backgroundColor !== "transparent" ?
								backgroundColor :
								"_default" );
						}

						value = value.toRgbaString();
					}
					try {
						elem.style[ hook ] = value;
					} catch( e ) {
						// wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit'
					}
				}
			};
			jQuery.fx.step[ hook ] = function( fx ) {
				if ( !fx.colorInit ) {
					fx.start = color( fx.elem, hook );
					fx.end = color( fx.end );
					fx.colorInit = true;
				}
				jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) );
			};
		});

	};

	color.hook( stepHooks );

	jQuery.cssHooks.borderColor = {
		expand: function( value ) {
			var expanded = {};

			each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) {
				expanded[ "border" + part + "Color" ] = value;
			});
			return expanded;
		}
	};

	// Basic color names only.
	// Usage of any of the other color names requires adding yourself or including
	// jquery.color.svg-names.js.
	colors = jQuery.Color.names = {
		// 4.1. Basic color keywords
		aqua: "#00ffff",
		black: "#000000",
		blue: "#0000ff",
		fuchsia: "#ff00ff",
		gray: "#808080",
		green: "#008000",
		lime: "#00ff00",
		maroon: "#800000",
		navy: "#000080",
		olive: "#808000",
		purple: "#800080",
		red: "#ff0000",
		silver: "#c0c0c0",
		teal: "#008080",
		white: "#ffffff",
		yellow: "#ffff00",

		// 4.2.3. "transparent" color keyword
		transparent: [ null, null, null, 0 ],

		_default: "#ffffff"
	};

	})( jQuery );


	/******************************************************************************/
	/****************************** CLASS ANIMATIONS ******************************/
	/******************************************************************************/
	(function() {

	var classAnimationActions = [ "add", "remove", "toggle" ],
		shorthandStyles = {
			border: 1,
			borderBottom: 1,
			borderColor: 1,
			borderLeft: 1,
			borderRight: 1,
			borderTop: 1,
			borderWidth: 1,
			margin: 1,
			padding: 1
		};

	$.each([ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], function( _, prop ) {
		$.fx.step[ prop ] = function( fx ) {
			if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) {
				jQuery.style( fx.elem, prop, fx.end );
				fx.setAttr = true;
			}
		};
	});

	function getElementStyles( elem ) {
		var key, len,
			style = elem.ownerDocument.defaultView ?
				elem.ownerDocument.defaultView.getComputedStyle( elem, null ) :
				elem.currentStyle,
			styles = {};

		if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) {
			len = style.length;
			while ( len-- ) {
				key = style[ len ];
				if ( typeof style[ key ] === "string" ) {
					styles[ $.camelCase( key ) ] = style[ key ];
				}
			}
		// support: Opera, IE <9
		} else {
			for ( key in style ) {
				if ( typeof style[ key ] === "string" ) {
					styles[ key ] = style[ key ];
				}
			}
		}

		return styles;
	}


	function styleDifference( oldStyle, newStyle ) {
		var diff = {},
			name, value;

		for ( name in newStyle ) {
			value = newStyle[ name ];
			if ( oldStyle[ name ] !== value ) {
				if ( !shorthandStyles[ name ] ) {
					if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) {
						diff[ name ] = value;
					}
				}
			}
		}

		return diff;
	}

	// support: jQuery <1.8
	if ( !$.fn.addBack ) {
		$.fn.addBack = function( selector ) {
			return this.add( selector == null ?
				this.prevObject : this.prevObject.filter( selector )
			);
		};
	}

	$.effects.animateClass = function( value, duration, easing, callback ) {
		var o = $.speed( duration, easing, callback );

		return this.queue( function() {
			var animated = $( this ),
				baseClass = animated.attr( "class" ) || "",
				applyClassChange,
				allAnimations = o.children ? animated.find( "*" ).addBack() : animated;

			// map the animated objects to store the original styles.
			allAnimations = allAnimations.map(function() {
				var el = $( this );
				return {
					el: el,
					start: getElementStyles( this )
				};
			});

			// apply class change
			applyClassChange = function() {
				$.each( classAnimationActions, function(i, action) {
					if ( value[ action ] ) {
						animated[ action + "Class" ]( value[ action ] );
					}
				});
			};
			applyClassChange();

			// map all animated objects again - calculate new styles and diff
			allAnimations = allAnimations.map(function() {
				this.end = getElementStyles( this.el[ 0 ] );
				this.diff = styleDifference( this.start, this.end );
				return this;
			});

			// apply original class
			animated.attr( "class", baseClass );

			// map all animated objects again - this time collecting a promise
			allAnimations = allAnimations.map(function() {
				var styleInfo = this,
					dfd = $.Deferred(),
					opts = $.extend({}, o, {
						queue: false,
						complete: function() {
							dfd.resolve( styleInfo );
						}
					});

				this.el.animate( this.diff, opts );
				return dfd.promise();
			});

			// once all animations have completed:
			$.when.apply( $, allAnimations.get() ).done(function() {

				// set the final class
				applyClassChange();

				// for each animated element,
				// clear all css properties that were animated
				$.each( arguments, function() {
					var el = this.el;
					$.each( this.diff, function(key) {
						el.css( key, "" );
					});
				});

				// this is guarnteed to be there if you use jQuery.speed()
				// it also handles dequeuing the next anim...
				o.complete.call( animated[ 0 ] );
			});
		});
	};

	$.fn.extend({
		addClass: (function( orig ) {
			return function( classNames, speed, easing, callback ) {
				return speed ?
					$.effects.animateClass.call( this,
						{ add: classNames }, speed, easing, callback ) :
					orig.apply( this, arguments );
			};
		})( $.fn.addClass ),

		removeClass: (function( orig ) {
			return function( classNames, speed, easing, callback ) {
				return arguments.length > 1 ?
					$.effects.animateClass.call( this,
						{ remove: classNames }, speed, easing, callback ) :
					orig.apply( this, arguments );
			};
		})( $.fn.removeClass ),

		toggleClass: (function( orig ) {
			return function( classNames, force, speed, easing, callback ) {
				if ( typeof force === "boolean" || force === undefined ) {
					if ( !speed ) {
						// without speed parameter
						return orig.apply( this, arguments );
					} else {
						return $.effects.animateClass.call( this,
							(force ? { add: classNames } : { remove: classNames }),
							speed, easing, callback );
					}
				} else {
					// without force parameter
					return $.effects.animateClass.call( this,
						{ toggle: classNames }, force, speed, easing );
				}
			};
		})( $.fn.toggleClass ),

		switchClass: function( remove, add, speed, easing, callback) {
			return $.effects.animateClass.call( this, {
				add: add,
				remove: remove
			}, speed, easing, callback );
		}
	});

	})();

	/******************************************************************************/
	/*********************************** EFFECTS **********************************/
	/******************************************************************************/

	(function() {

	$.extend( $.effects, {
		version: "1.10.4",

		// Saves a set of properties in a data storage
		save: function( element, set ) {
			for( var i=0; i < set.length; i++ ) {
				if ( set[ i ] !== null ) {
					element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );
				}
			}
		},

		// Restores a set of previously saved properties from a data storage
		restore: function( element, set ) {
			var val, i;
			for( i=0; i < set.length; i++ ) {
				if ( set[ i ] !== null ) {
					val = element.data( dataSpace + set[ i ] );
					// support: jQuery 1.6.2
					// http://bugs.jquery.com/ticket/9917
					// jQuery 1.6.2 incorrectly returns undefined for any falsy value.
					// We can't differentiate between "" and 0 here, so we just assume
					// empty string since it's likely to be a more common value...
					if ( val === undefined ) {
						val = "";
					}
					element.css( set[ i ], val );
				}
			}
		},

		setMode: function( el, mode ) {
			if (mode === "toggle") {
				mode = el.is( ":hidden" ) ? "show" : "hide";
			}
			return mode;
		},

		// Translates a [top,left] array into a baseline value
		// this should be a little more flexible in the future to handle a string & hash
		getBaseline: function( origin, original ) {
			var y, x;
			switch ( origin[ 0 ] ) {
				case "top": y = 0; break;
				case "middle": y = 0.5; break;
				case "bottom": y = 1; break;
				default: y = origin[ 0 ] / original.height;
			}
			switch ( origin[ 1 ] ) {
				case "left": x = 0; break;
				case "center": x = 0.5; break;
				case "right": x = 1; break;
				default: x = origin[ 1 ] / original.width;
			}
			return {
				x: x,
				y: y
			};
		},

		// Wraps the element around a wrapper that copies position properties
		createWrapper: function( element ) {

			// if the element is already wrapped, return it
			if ( element.parent().is( ".ui-effects-wrapper" )) {
				return element.parent();
			}

			// wrap the element
			var props = {
					width: element.outerWidth(true),
					height: element.outerHeight(true),
					"float": element.css( "float" )
				},
				wrapper = $( "<div></div>" )
					.addClass( "ui-effects-wrapper" )
					.css({
						fontSize: "100%",
						background: "transparent",
						border: "none",
						margin: 0,
						padding: 0
					}),
				// Store the size in case width/height are defined in % - Fixes #5245
				size = {
					width: element.width(),
					height: element.height()
				},
				active = document.activeElement;

			// support: Firefox
			// Firefox incorrectly exposes anonymous content
			// https://bugzilla.mozilla.org/show_bug.cgi?id=561664
			try {
				active.id;
			} catch( e ) {
				active = document.body;
			}

			element.wrap( wrapper );

			// Fixes #7595 - Elements lose focus when wrapped.
			if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
				$( active ).focus();
			}

			wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually lose the reference to the wrapped element

			// transfer positioning properties to the wrapper
			if ( element.css( "position" ) === "static" ) {
				wrapper.css({ position: "relative" });
				element.css({ position: "relative" });
			} else {
				$.extend( props, {
					position: element.css( "position" ),
					zIndex: element.css( "z-index" )
				});
				$.each([ "top", "left", "bottom", "right" ], function(i, pos) {
					props[ pos ] = element.css( pos );
					if ( isNaN( parseInt( props[ pos ], 10 ) ) ) {
						props[ pos ] = "auto";
					}
				});
				element.css({
					position: "relative",
					top: 0,
					left: 0,
					right: "auto",
					bottom: "auto"
				});
			}
			element.css(size);

			return wrapper.css( props ).show();
		},

		removeWrapper: function( element ) {
			var active = document.activeElement;

			if ( element.parent().is( ".ui-effects-wrapper" ) ) {
				element.parent().replaceWith( element );

				// Fixes #7595 - Elements lose focus when wrapped.
				if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
					$( active ).focus();
				}
			}


			return element;
		},

		setTransition: function( element, list, factor, value ) {
			value = value || {};
			$.each( list, function( i, x ) {
				var unit = element.cssUnit( x );
				if ( unit[ 0 ] > 0 ) {
					value[ x ] = unit[ 0 ] * factor + unit[ 1 ];
				}
			});
			return value;
		}
	});

	// return an effect options object for the given parameters:
	function _normalizeArguments( effect, options, speed, callback ) {

		// allow passing all options as the first parameter
		if ( $.isPlainObject( effect ) ) {
			options = effect;
			effect = effect.effect;
		}

		// convert to an object
		effect = { effect: effect };

		// catch (effect, null, ...)
		if ( options == null ) {
			options = {};
		}

		// catch (effect, callback)
		if ( $.isFunction( options ) ) {
			callback = options;
			speed = null;
			options = {};
		}

		// catch (effect, speed, ?)
		if ( typeof options === "number" || $.fx.speeds[ options ] ) {
			callback = speed;
			speed = options;
			options = {};
		}

		// catch (effect, options, callback)
		if ( $.isFunction( speed ) ) {
			callback = speed;
			speed = null;
		}

		// add options to effect
		if ( options ) {
			$.extend( effect, options );
		}

		speed = speed || options.duration;
		effect.duration = $.fx.off ? 0 :
			typeof speed === "number" ? speed :
			speed in $.fx.speeds ? $.fx.speeds[ speed ] :
			$.fx.speeds._default;

		effect.complete = callback || options.complete;

		return effect;
	}

	function standardAnimationOption( option ) {
		// Valid standard speeds (nothing, number, named speed)
		if ( !option || typeof option === "number" || $.fx.speeds[ option ] ) {
			return true;
		}

		// Invalid strings - treat as "normal" speed
		if ( typeof option === "string" && !$.effects.effect[ option ] ) {
			return true;
		}

		// Complete callback
		if ( $.isFunction( option ) ) {
			return true;
		}

		// Options hash (but not naming an effect)
		if ( typeof option === "object" && !option.effect ) {
			return true;
		}

		// Didn't match any standard API
		return false;
	}

	$.fn.extend({
		effect: function( /* effect, options, speed, callback */ ) {
			var args = _normalizeArguments.apply( this, arguments ),
				mode = args.mode,
				queue = args.queue,
				effectMethod = $.effects.effect[ args.effect ];

			if ( $.fx.off || !effectMethod ) {
				// delegate to the original method (e.g., .show()) if possible
				if ( mode ) {
					return this[ mode ]( args.duration, args.complete );
				} else {
					return this.each( function() {
						if ( args.complete ) {
							args.complete.call( this );
						}
					});
				}
			}

			function run( next ) {
				var elem = $( this ),
					complete = args.complete,
					mode = args.mode;

				function done() {
					if ( $.isFunction( complete ) ) {
						complete.call( elem[0] );
					}
					if ( $.isFunction( next ) ) {
						next();
					}
				}

				// If the element already has the correct final state, delegate to
				// the core methods so the internal tracking of "olddisplay" works.
				if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) {
					elem[ mode ]();
					done();
				} else {
					effectMethod.call( elem[0], args, done );
				}
			}

			return queue === false ? this.each( run ) : this.queue( queue || "fx", run );
		},

		show: (function( orig ) {
			return function( option ) {
				if ( standardAnimationOption( option ) ) {
					return orig.apply( this, arguments );
				} else {
					var args = _normalizeArguments.apply( this, arguments );
					args.mode = "show";
					return this.effect.call( this, args );
				}
			};
		})( $.fn.show ),

		hide: (function( orig ) {
			return function( option ) {
				if ( standardAnimationOption( option ) ) {
					return orig.apply( this, arguments );
				} else {
					var args = _normalizeArguments.apply( this, arguments );
					args.mode = "hide";
					return this.effect.call( this, args );
				}
			};
		})( $.fn.hide ),

		toggle: (function( orig ) {
			return function( option ) {
				if ( standardAnimationOption( option ) || typeof option === "boolean" ) {
					return orig.apply( this, arguments );
				} else {
					var args = _normalizeArguments.apply( this, arguments );
					args.mode = "toggle";
					return this.effect.call( this, args );
				}
			};
		})( $.fn.toggle ),

		// helper functions
		cssUnit: function(key) {
			var style = this.css( key ),
				val = [];

			$.each( [ "em", "px", "%", "pt" ], function( i, unit ) {
				if ( style.indexOf( unit ) > 0 ) {
					val = [ parseFloat( style ), unit ];
				}
			});
			return val;
		}
	});

	})();

	/******************************************************************************/
	/*********************************** EASING ***********************************/
	/******************************************************************************/

	(function() {

	// based on easing equations from Robert Penner (http://www.robertpenner.com/easing)

	var baseEasings = {};

	$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) {
		baseEasings[ name ] = function( p ) {
			return Math.pow( p, i + 2 );
		};
	});

	$.extend( baseEasings, {
		Sine: function ( p ) {
			return 1 - Math.cos( p * Math.PI / 2 );
		},
		Circ: function ( p ) {
			return 1 - Math.sqrt( 1 - p * p );
		},
		Elastic: function( p ) {
			return p === 0 || p === 1 ? p :
				-Math.pow( 2, 8 * (p - 1) ) * Math.sin( ( (p - 1) * 80 - 7.5 ) * Math.PI / 15 );
		},
		Back: function( p ) {
			return p * p * ( 3 * p - 2 );
		},
		Bounce: function ( p ) {
			var pow2,
				bounce = 4;

			while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {}
			return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 );
		}
	});

	$.each( baseEasings, function( name, easeIn ) {
		$.easing[ "easeIn" + name ] = easeIn;
		$.easing[ "easeOut" + name ] = function( p ) {
			return 1 - easeIn( 1 - p );
		};
		$.easing[ "easeInOut" + name ] = function( p ) {
			return p < 0.5 ?
				easeIn( p * 2 ) / 2 :
				1 - easeIn( p * -2 + 2 ) / 2;
		};
	});

	})();

	})(jQuery);
	(function( $, undefined ) {

	var rvertical = /up|down|vertical/,
		rpositivemotion = /up|left|vertical|horizontal/;

	$.effects.effect.blind = function( o, done ) {
		// Create element
		var el = $( this ),
			props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
			mode = $.effects.setMode( el, o.mode || "hide" ),
			direction = o.direction || "up",
			vertical = rvertical.test( direction ),
			ref = vertical ? "height" : "width",
			ref2 = vertical ? "top" : "left",
			motion = rpositivemotion.test( direction ),
			animation = {},
			show = mode === "show",
			wrapper, distance, margin;

		// if already wrapped, the wrapper's properties are my property. #6245
		if ( el.parent().is( ".ui-effects-wrapper" ) ) {
			$.effects.save( el.parent(), props );
		} else {
			$.effects.save( el, props );
		}
		el.show();
		wrapper = $.effects.createWrapper( el ).css({
			overflow: "hidden"
		});

		distance = wrapper[ ref ]();
		margin = parseFloat( wrapper.css( ref2 ) ) || 0;

		animation[ ref ] = show ? distance : 0;
		if ( !motion ) {
			el
				.css( vertical ? "bottom" : "right", 0 )
				.css( vertical ? "top" : "left", "auto" )
				.css({ position: "absolute" });

			animation[ ref2 ] = show ? margin : distance + margin;
		}

		// start at 0 if we are showing
		if ( show ) {
			wrapper.css( ref, 0 );
			if ( ! motion ) {
				wrapper.css( ref2, margin + distance );
			}
		}

		// Animate
		wrapper.animate( animation, {
			duration: o.duration,
			easing: o.easing,
			queue: false,
			complete: function() {
				if ( mode === "hide" ) {
					el.hide();
				}
				$.effects.restore( el, props );
				$.effects.removeWrapper( el );
				done();
			}
		});

	};

	})(jQuery);
	(function( $, undefined ) {

	$.effects.effect.bounce = function( o, done ) {
		var el = $( this ),
			props = [ "position", "top", "bottom", "left", "right", "height", "width" ],

			// defaults:
			mode = $.effects.setMode( el, o.mode || "effect" ),
			hide = mode === "hide",
			show = mode === "show",
			direction = o.direction || "up",
			distance = o.distance,
			times = o.times || 5,

			// number of internal animations
			anims = times * 2 + ( show || hide ? 1 : 0 ),
			speed = o.duration / anims,
			easing = o.easing,

			// utility:
			ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
			motion = ( direction === "up" || direction === "left" ),
			i,
			upAnim,
			downAnim,

			// we will need to re-assemble the queue to stack our animations in place
			queue = el.queue(),
			queuelen = queue.length;

		// Avoid touching opacity to prevent clearType and PNG issues in IE
		if ( show || hide ) {
			props.push( "opacity" );
		}

		$.effects.save( el, props );
		el.show();
		$.effects.createWrapper( el ); // Create Wrapper

		// default distance for the BIGGEST bounce is the outer Distance / 3
		if ( !distance ) {
			distance = el[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3;
		}

		if ( show ) {
			downAnim = { opacity: 1 };
			downAnim[ ref ] = 0;

			// if we are showing, force opacity 0 and set the initial position
			// then do the "first" animation
			el.css( "opacity", 0 )
				.css( ref, motion ? -distance * 2 : distance * 2 )
				.animate( downAnim, speed, easing );
		}

		// start at the smallest distance if we are hiding
		if ( hide ) {
			distance = distance / Math.pow( 2, times - 1 );
		}

		downAnim = {};
		downAnim[ ref ] = 0;
		// Bounces up/down/left/right then back to 0 -- times * 2 animations happen here
		for ( i = 0; i < times; i++ ) {
			upAnim = {};
			upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;

			el.animate( upAnim, speed, easing )
				.animate( downAnim, speed, easing );

			distance = hide ? distance * 2 : distance / 2;
		}

		// Last Bounce when Hiding
		if ( hide ) {
			upAnim = { opacity: 0 };
			upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;

			el.animate( upAnim, speed, easing );
		}

		el.queue(function() {
			if ( hide ) {
				el.hide();
			}
			$.effects.restore( el, props );
			$.effects.removeWrapper( el );
			done();
		});

		// inject all the animations we just queued to be first in line (after "inprogress")
		if ( queuelen > 1) {
			queue.splice.apply( queue,
				[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
		}
		el.dequeue();

	};

	})(jQuery);
	(function( $, undefined ) {

	$.effects.effect.clip = function( o, done ) {
		// Create element
		var el = $( this ),
			props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
			mode = $.effects.setMode( el, o.mode || "hide" ),
			show = mode === "show",
			direction = o.direction || "vertical",
			vert = direction === "vertical",
			size = vert ? "height" : "width",
			position = vert ? "top" : "left",
			animation = {},
			wrapper, animate, distance;

		// Save & Show
		$.effects.save( el, props );
		el.show();

		// Create Wrapper
		wrapper = $.effects.createWrapper( el ).css({
			overflow: "hidden"
		});
		animate = ( el[0].tagName === "IMG" ) ? wrapper : el;
		distance = animate[ size ]();

		// Shift
		if ( show ) {
			animate.css( size, 0 );
			animate.css( position, distance / 2 );
		}

		// Create Animation Object:
		animation[ size ] = show ? distance : 0;
		animation[ position ] = show ? 0 : distance / 2;

		// Animate
		animate.animate( animation, {
			queue: false,
			duration: o.duration,
			easing: o.easing,
			complete: function() {
				if ( !show ) {
					el.hide();
				}
				$.effects.restore( el, props );
				$.effects.removeWrapper( el );
				done();
			}
		});

	};

	})(jQuery);
	(function( $, undefined ) {

	$.effects.effect.drop = function( o, done ) {

		var el = $( this ),
			props = [ "position", "top", "bottom", "left", "right", "opacity", "height", "width" ],
			mode = $.effects.setMode( el, o.mode || "hide" ),
			show = mode === "show",
			direction = o.direction || "left",
			ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
			motion = ( direction === "up" || direction === "left" ) ? "pos" : "neg",
			animation = {
				opacity: show ? 1 : 0
			},
			distance;

		// Adjust
		$.effects.save( el, props );
		el.show();
		$.effects.createWrapper( el );

		distance = o.distance || el[ ref === "top" ? "outerHeight": "outerWidth" ]( true ) / 2;

		if ( show ) {
			el
				.css( "opacity", 0 )
				.css( ref, motion === "pos" ? -distance : distance );
		}

		// Animation
		animation[ ref ] = ( show ?
			( motion === "pos" ? "+=" : "-=" ) :
			( motion === "pos" ? "-=" : "+=" ) ) +
			distance;

		// Animate
		el.animate( animation, {
			queue: false,
			duration: o.duration,
			easing: o.easing,
			complete: function() {
				if ( mode === "hide" ) {
					el.hide();
				}
				$.effects.restore( el, props );
				$.effects.removeWrapper( el );
				done();
			}
		});
	};

	})(jQuery);
	(function( $, undefined ) {

	$.effects.effect.explode = function( o, done ) {

		var rows = o.pieces ? Math.round( Math.sqrt( o.pieces ) ) : 3,
			cells = rows,
			el = $( this ),
			mode = $.effects.setMode( el, o.mode || "hide" ),
			show = mode === "show",

			// show and then visibility:hidden the element before calculating offset
			offset = el.show().css( "visibility", "hidden" ).offset(),

			// width and height of a piece
			width = Math.ceil( el.outerWidth() / cells ),
			height = Math.ceil( el.outerHeight() / rows ),
			pieces = [],

			// loop
			i, j, left, top, mx, my;

		// children animate complete:
		function childComplete() {
			pieces.push( this );
			if ( pieces.length === rows * cells ) {
				animComplete();
			}
		}

		// clone the element for each row and cell.
		for( i = 0; i < rows ; i++ ) { // ===>
			top = offset.top + i * height;
			my = i - ( rows - 1 ) / 2 ;

			for( j = 0; j < cells ; j++ ) { // |||
				left = offset.left + j * width;
				mx = j - ( cells - 1 ) / 2 ;

				// Create a clone of the now hidden main element that will be absolute positioned
				// within a wrapper div off the -left and -top equal to size of our pieces
				el
					.clone()
					.appendTo( "body" )
					.wrap( "<div></div>" )
					.css({
						position: "absolute",
						visibility: "visible",
						left: -j * width,
						top: -i * height
					})

				// select the wrapper - make it overflow: hidden and absolute positioned based on
				// where the original was located +left and +top equal to the size of pieces
					.parent()
					.addClass( "ui-effects-explode" )
					.css({
						position: "absolute",
						overflow: "hidden",
						width: width,
						height: height,
						left: left + ( show ? mx * width : 0 ),
						top: top + ( show ? my * height : 0 ),
						opacity: show ? 0 : 1
					}).animate({
						left: left + ( show ? 0 : mx * width ),
						top: top + ( show ? 0 : my * height ),
						opacity: show ? 1 : 0
					}, o.duration || 500, o.easing, childComplete );
			}
		}

		function animComplete() {
			el.css({
				visibility: "visible"
			});
			$( pieces ).remove();
			if ( !show ) {
				el.hide();
			}
			done();
		}
	};

	})(jQuery);
	(function( $, undefined ) {

	$.effects.effect.fade = function( o, done ) {
		var el = $( this ),
			mode = $.effects.setMode( el, o.mode || "toggle" );

		el.animate({
			opacity: mode
		}, {
			queue: false,
			duration: o.duration,
			easing: o.easing,
			complete: done
		});
	};

	})( jQuery );
	(function( $, undefined ) {

	$.effects.effect.fold = function( o, done ) {

		// Create element
		var el = $( this ),
			props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
			mode = $.effects.setMode( el, o.mode || "hide" ),
			show = mode === "show",
			hide = mode === "hide",
			size = o.size || 15,
			percent = /([0-9]+)%/.exec( size ),
			horizFirst = !!o.horizFirst,
			widthFirst = show !== horizFirst,
			ref = widthFirst ? [ "width", "height" ] : [ "height", "width" ],
			duration = o.duration / 2,
			wrapper, distance,
			animation1 = {},
			animation2 = {};

		$.effects.save( el, props );
		el.show();

		// Create Wrapper
		wrapper = $.effects.createWrapper( el ).css({
			overflow: "hidden"
		});
		distance = widthFirst ?
			[ wrapper.width(), wrapper.height() ] :
			[ wrapper.height(), wrapper.width() ];

		if ( percent ) {
			size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ];
		}
		if ( show ) {
			wrapper.css( horizFirst ? {
				height: 0,
				width: size
			} : {
				height: size,
				width: 0
			});
		}

		// Animation
		animation1[ ref[ 0 ] ] = show ? distance[ 0 ] : size;
		animation2[ ref[ 1 ] ] = show ? distance[ 1 ] : 0;

		// Animate
		wrapper
			.animate( animation1, duration, o.easing )
			.animate( animation2, duration, o.easing, function() {
				if ( hide ) {
					el.hide();
				}
				$.effects.restore( el, props );
				$.effects.removeWrapper( el );
				done();
			});

	};

	})(jQuery);
	(function( $, undefined ) {

	$.effects.effect.highlight = function( o, done ) {
		var elem = $( this ),
			props = [ "backgroundImage", "backgroundColor", "opacity" ],
			mode = $.effects.setMode( elem, o.mode || "show" ),
			animation = {
				backgroundColor: elem.css( "backgroundColor" )
			};

		if (mode === "hide") {
			animation.opacity = 0;
		}

		$.effects.save( elem, props );

		elem
			.show()
			.css({
				backgroundImage: "none",
				backgroundColor: o.color || "#ffff99"
			})
			.animate( animation, {
				queue: false,
				duration: o.duration,
				easing: o.easing,
				complete: function() {
					if ( mode === "hide" ) {
						elem.hide();
					}
					$.effects.restore( elem, props );
					done();
				}
			});
	};

	})(jQuery);
	(function( $, undefined ) {

	$.effects.effect.pulsate = function( o, done ) {
		var elem = $( this ),
			mode = $.effects.setMode( elem, o.mode || "show" ),
			show = mode === "show",
			hide = mode === "hide",
			showhide = ( show || mode === "hide" ),

			// showing or hiding leaves of the "last" animation
			anims = ( ( o.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ),
			duration = o.duration / anims,
			animateTo = 0,
			queue = elem.queue(),
			queuelen = queue.length,
			i;

		if ( show || !elem.is(":visible")) {
			elem.css( "opacity", 0 ).show();
			animateTo = 1;
		}

		// anims - 1 opacity "toggles"
		for ( i = 1; i < anims; i++ ) {
			elem.animate({
				opacity: animateTo
			}, duration, o.easing );
			animateTo = 1 - animateTo;
		}

		elem.animate({
			opacity: animateTo
		}, duration, o.easing);

		elem.queue(function() {
			if ( hide ) {
				elem.hide();
			}
			done();
		});

		// We just queued up "anims" animations, we need to put them next in the queue
		if ( queuelen > 1 ) {
			queue.splice.apply( queue,
				[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
		}
		elem.dequeue();
	};

	})(jQuery);
	(function( $, undefined ) {

	$.effects.effect.puff = function( o, done ) {
		var elem = $( this ),
			mode = $.effects.setMode( elem, o.mode || "hide" ),
			hide = mode === "hide",
			percent = parseInt( o.percent, 10 ) || 150,
			factor = percent / 100,
			original = {
				height: elem.height(),
				width: elem.width(),
				outerHeight: elem.outerHeight(),
				outerWidth: elem.outerWidth()
			};

		$.extend( o, {
			effect: "scale",
			queue: false,
			fade: true,
			mode: mode,
			complete: done,
			percent: hide ? percent : 100,
			from: hide ?
				original :
				{
					height: original.height * factor,
					width: original.width * factor,
					outerHeight: original.outerHeight * factor,
					outerWidth: original.outerWidth * factor
				}
		});

		elem.effect( o );
	};

	$.effects.effect.scale = function( o, done ) {

		// Create element
		var el = $( this ),
			options = $.extend( true, {}, o ),
			mode = $.effects.setMode( el, o.mode || "effect" ),
			percent = parseInt( o.percent, 10 ) ||
				( parseInt( o.percent, 10 ) === 0 ? 0 : ( mode === "hide" ? 0 : 100 ) ),
			direction = o.direction || "both",
			origin = o.origin,
			original = {
				height: el.height(),
				width: el.width(),
				outerHeight: el.outerHeight(),
				outerWidth: el.outerWidth()
			},
			factor = {
				y: direction !== "horizontal" ? (percent / 100) : 1,
				x: direction !== "vertical" ? (percent / 100) : 1
			};

		// We are going to pass this effect to the size effect:
		options.effect = "size";
		options.queue = false;
		options.complete = done;

		// Set default origin and restore for show/hide
		if ( mode !== "effect" ) {
			options.origin = origin || ["middle","center"];
			options.restore = true;
		}

		options.from = o.from || ( mode === "show" ? {
			height: 0,
			width: 0,
			outerHeight: 0,
			outerWidth: 0
		} : original );
		options.to = {
			height: original.height * factor.y,
			width: original.width * factor.x,
			outerHeight: original.outerHeight * factor.y,
			outerWidth: original.outerWidth * factor.x
		};

		// Fade option to support puff
		if ( options.fade ) {
			if ( mode === "show" ) {
				options.from.opacity = 0;
				options.to.opacity = 1;
			}
			if ( mode === "hide" ) {
				options.from.opacity = 1;
				options.to.opacity = 0;
			}
		}

		// Animate
		el.effect( options );

	};

	$.effects.effect.size = function( o, done ) {

		// Create element
		var original, baseline, factor,
			el = $( this ),
			props0 = [ "position", "top", "bottom", "left", "right", "width", "height", "overflow", "opacity" ],

			// Always restore
			props1 = [ "position", "top", "bottom", "left", "right", "overflow", "opacity" ],

			// Copy for children
			props2 = [ "width", "height", "overflow" ],
			cProps = [ "fontSize" ],
			vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ],
			hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ],

			// Set options
			mode = $.effects.setMode( el, o.mode || "effect" ),
			restore = o.restore || mode !== "effect",
			scale = o.scale || "both",
			origin = o.origin || [ "middle", "center" ],
			position = el.css( "position" ),
			props = restore ? props0 : props1,
			zero = {
				height: 0,
				width: 0,
				outerHeight: 0,
				outerWidth: 0
			};

		if ( mode === "show" ) {
			el.show();
		}
		original = {
			height: el.height(),
			width: el.width(),
			outerHeight: el.outerHeight(),
			outerWidth: el.outerWidth()
		};

		if ( o.mode === "toggle" && mode === "show" ) {
			el.from = o.to || zero;
			el.to = o.from || original;
		} else {
			el.from = o.from || ( mode === "show" ? zero : original );
			el.to = o.to || ( mode === "hide" ? zero : original );
		}

		// Set scaling factor
		factor = {
			from: {
				y: el.from.height / original.height,
				x: el.from.width / original.width
			},
			to: {
				y: el.to.height / original.height,
				x: el.to.width / original.width
			}
		};

		// Scale the css box
		if ( scale === "box" || scale === "both" ) {

			// Vertical props scaling
			if ( factor.from.y !== factor.to.y ) {
				props = props.concat( vProps );
				el.from = $.effects.setTransition( el, vProps, factor.from.y, el.from );
				el.to = $.effects.setTransition( el, vProps, factor.to.y, el.to );
			}

			// Horizontal props scaling
			if ( factor.from.x !== factor.to.x ) {
				props = props.concat( hProps );
				el.from = $.effects.setTransition( el, hProps, factor.from.x, el.from );
				el.to = $.effects.setTransition( el, hProps, factor.to.x, el.to );
			}
		}

		// Scale the content
		if ( scale === "content" || scale === "both" ) {

			// Vertical props scaling
			if ( factor.from.y !== factor.to.y ) {
				props = props.concat( cProps ).concat( props2 );
				el.from = $.effects.setTransition( el, cProps, factor.from.y, el.from );
				el.to = $.effects.setTransition( el, cProps, factor.to.y, el.to );
			}
		}

		$.effects.save( el, props );
		el.show();
		$.effects.createWrapper( el );
		el.css( "overflow", "hidden" ).css( el.from );

		// Adjust
		if (origin) { // Calculate baseline shifts
			baseline = $.effects.getBaseline( origin, original );
			el.from.top = ( original.outerHeight - el.outerHeight() ) * baseline.y;
			el.from.left = ( original.outerWidth - el.outerWidth() ) * baseline.x;
			el.to.top = ( original.outerHeight - el.to.outerHeight ) * baseline.y;
			el.to.left = ( original.outerWidth - el.to.outerWidth ) * baseline.x;
		}
		el.css( el.from ); // set top & left

		// Animate
		if ( scale === "content" || scale === "both" ) { // Scale the children

			// Add margins/font-size
			vProps = vProps.concat([ "marginTop", "marginBottom" ]).concat(cProps);
			hProps = hProps.concat([ "marginLeft", "marginRight" ]);
			props2 = props0.concat(vProps).concat(hProps);

			el.find( "*[width]" ).each( function(){
				var child = $( this ),
					c_original = {
						height: child.height(),
						width: child.width(),
						outerHeight: child.outerHeight(),
						outerWidth: child.outerWidth()
					};
				if (restore) {
					$.effects.save(child, props2);
				}

				child.from = {
					height: c_original.height * factor.from.y,
					width: c_original.width * factor.from.x,
					outerHeight: c_original.outerHeight * factor.from.y,
					outerWidth: c_original.outerWidth * factor.from.x
				};
				child.to = {
					height: c_original.height * factor.to.y,
					width: c_original.width * factor.to.x,
					outerHeight: c_original.height * factor.to.y,
					outerWidth: c_original.width * factor.to.x
				};

				// Vertical props scaling
				if ( factor.from.y !== factor.to.y ) {
					child.from = $.effects.setTransition( child, vProps, factor.from.y, child.from );
					child.to = $.effects.setTransition( child, vProps, factor.to.y, child.to );
				}

				// Horizontal props scaling
				if ( factor.from.x !== factor.to.x ) {
					child.from = $.effects.setTransition( child, hProps, factor.from.x, child.from );
					child.to = $.effects.setTransition( child, hProps, factor.to.x, child.to );
				}

				// Animate children
				child.css( child.from );
				child.animate( child.to, o.duration, o.easing, function() {

					// Restore children
					if ( restore ) {
						$.effects.restore( child, props2 );
					}
				});
			});
		}

		// Animate
		el.animate( el.to, {
			queue: false,
			duration: o.duration,
			easing: o.easing,
			complete: function() {
				if ( el.to.opacity === 0 ) {
					el.css( "opacity", el.from.opacity );
				}
				if( mode === "hide" ) {
					el.hide();
				}
				$.effects.restore( el, props );
				if ( !restore ) {

					// we need to calculate our new positioning based on the scaling
					if ( position === "static" ) {
						el.css({
							position: "relative",
							top: el.to.top,
							left: el.to.left
						});
					} else {
						$.each([ "top", "left" ], function( idx, pos ) {
							el.css( pos, function( _, str ) {
								var val = parseInt( str, 10 ),
									toRef = idx ? el.to.left : el.to.top;

								// if original was "auto", recalculate the new value from wrapper
								if ( str === "auto" ) {
									return toRef + "px";
								}

								return val + toRef + "px";
							});
						});
					}
				}

				$.effects.removeWrapper( el );
				done();
			}
		});

	};

	})(jQuery);
	(function( $, undefined ) {

	$.effects.effect.shake = function( o, done ) {

		var el = $( this ),
			props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
			mode = $.effects.setMode( el, o.mode || "effect" ),
			direction = o.direction || "left",
			distance = o.distance || 20,
			times = o.times || 3,
			anims = times * 2 + 1,
			speed = Math.round(o.duration/anims),
			ref = (direction === "up" || direction === "down") ? "top" : "left",
			positiveMotion = (direction === "up" || direction === "left"),
			animation = {},
			animation1 = {},
			animation2 = {},
			i,

			// we will need to re-assemble the queue to stack our animations in place
			queue = el.queue(),
			queuelen = queue.length;

		$.effects.save( el, props );
		el.show();
		$.effects.createWrapper( el );

		// Animation
		animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance;
		animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2;
		animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2;

		// Animate
		el.animate( animation, speed, o.easing );

		// Shakes
		for ( i = 1; i < times; i++ ) {
			el.animate( animation1, speed, o.easing ).animate( animation2, speed, o.easing );
		}
		el
			.animate( animation1, speed, o.easing )
			.animate( animation, speed / 2, o.easing )
			.queue(function() {
				if ( mode === "hide" ) {
					el.hide();
				}
				$.effects.restore( el, props );
				$.effects.removeWrapper( el );
				done();
			});

		// inject all the animations we just queued to be first in line (after "inprogress")
		if ( queuelen > 1) {
			queue.splice.apply( queue,
				[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
		}
		el.dequeue();

	};

	})(jQuery);
	(function( $, undefined ) {

	$.effects.effect.slide = function( o, done ) {

		// Create element
		var el = $( this ),
			props = [ "position", "top", "bottom", "left", "right", "width", "height" ],
			mode = $.effects.setMode( el, o.mode || "show" ),
			show = mode === "show",
			direction = o.direction || "left",
			ref = (direction === "up" || direction === "down") ? "top" : "left",
			positiveMotion = (direction === "up" || direction === "left"),
			distance,
			animation = {};

		// Adjust
		$.effects.save( el, props );
		el.show();
		distance = o.distance || el[ ref === "top" ? "outerHeight" : "outerWidth" ]( true );

		$.effects.createWrapper( el ).css({
			overflow: "hidden"
		});

		if ( show ) {
			el.css( ref, positiveMotion ? (isNaN(distance) ? "-" + distance : -distance) : distance );
		}

		// Animation
		animation[ ref ] = ( show ?
			( positiveMotion ? "+=" : "-=") :
			( positiveMotion ? "-=" : "+=")) +
			distance;

		// Animate
		el.animate( animation, {
			queue: false,
			duration: o.duration,
			easing: o.easing,
			complete: function() {
				if ( mode === "hide" ) {
					el.hide();
				}
				$.effects.restore( el, props );
				$.effects.removeWrapper( el );
				done();
			}
		});
	};

	})(jQuery);
	(function( $, undefined ) {

	$.effects.effect.transfer = function( o, done ) {
		var elem = $( this ),
			target = $( o.to ),
			targetFixed = target.css( "position" ) === "fixed",
			body = $("body"),
			fixTop = targetFixed ? body.scrollTop() : 0,
			fixLeft = targetFixed ? body.scrollLeft() : 0,
			endPosition = target.offset(),
			animation = {
				top: endPosition.top - fixTop ,
				left: endPosition.left - fixLeft ,
				height: target.innerHeight(),
				width: target.innerWidth()
			},
			startPosition = elem.offset(),
			transfer = $( "<div class='ui-effects-transfer'></div>" )
				.appendTo( document.body )
				.addClass( o.className )
				.css({
					top: startPosition.top - fixTop ,
					left: startPosition.left - fixLeft ,
					height: elem.innerHeight(),
					width: elem.innerWidth(),
					position: targetFixed ? "fixed" : "absolute"
				})
				.animate( animation, o.duration, o.easing, function() {
					transfer.remove();
					done();
				});
	};

	})(jQuery);
	(function( $, undefined ) {

	$.widget( "ui.menu", {
		version: "1.10.4",
		defaultElement: "<ul>",
		delay: 300,
		options: {
			icons: {
				submenu: "ui-icon-carat-1-e"
			},
			menus: "ul",
			position: {
				my: "left top",
				at: "right top"
			},
			role: "menu",

			// callbacks
			blur: null,
			focus: null,
			select: null
		},

		_create: function() {
			this.activeMenu = this.element;
			// flag used to prevent firing of the click handler
			// as the event bubbles up through nested menus
			this.mouseHandled = false;
			this.element
				.uniqueId()
				.addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
				.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length )
				.attr({
					role: this.options.role,
					tabIndex: 0
				})
				// need to catch all clicks on disabled menu
				// not possible through _on
				.bind( "click" + this.eventNamespace, $.proxy(function( event ) {
					if ( this.options.disabled ) {
						event.preventDefault();
					}
				}, this ));

			if ( this.options.disabled ) {
				this.element
					.addClass( "ui-state-disabled" )
					.attr( "aria-disabled", "true" );
			}

			this._on({
				// Prevent focus from sticking to links inside menu after clicking
				// them (focus should always stay on UL during navigation).
				"mousedown .ui-menu-item > a": function( event ) {
					event.preventDefault();
				},
				"click .ui-state-disabled > a": function( event ) {
					event.preventDefault();
				},
				"click .ui-menu-item:has(a)": function( event ) {
					var target = $( event.target ).closest( ".ui-menu-item" );
					if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) {
						this.select( event );

						// Only set the mouseHandled flag if the event will bubble, see #9469.
						if ( !event.isPropagationStopped() ) {
							this.mouseHandled = true;
						}

						// Open submenu on click
						if ( target.has( ".ui-menu" ).length ) {
							this.expand( event );
						} else if ( !this.element.is( ":focus" ) && $( this.document[ 0 ].activeElement ).closest( ".ui-menu" ).length ) {

							// Redirect focus to the menu
							this.element.trigger( "focus", [ true ] );

							// If the active item is on the top level, let it stay active.
							// Otherwise, blur the active item since it is no longer visible.
							if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
								clearTimeout( this.timer );
							}
						}
					}
				},
				"mouseenter .ui-menu-item": function( event ) {
					var target = $( event.currentTarget );
					// Remove ui-state-active class from siblings of the newly focused menu item
					// to avoid a jump caused by adjacent elements both having a class with a border
					target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" );
					this.focus( event, target );
				},
				mouseleave: "collapseAll",
				"mouseleave .ui-menu": "collapseAll",
				focus: function( event, keepActiveItem ) {
					// If there's already an active item, keep it active
					// If not, activate the first item
					var item = this.active || this.element.children( ".ui-menu-item" ).eq( 0 );

					if ( !keepActiveItem ) {
						this.focus( event, item );
					}
				},
				blur: function( event ) {
					this._delay(function() {
						if ( !$.contains( this.element[0], this.document[0].activeElement ) ) {
							this.collapseAll( event );
						}
					});
				},
				keydown: "_keydown"
			});

			this.refresh();

			// Clicks outside of a menu collapse any open menus
			this._on( this.document, {
				click: function( event ) {
					if ( !$( event.target ).closest( ".ui-menu" ).length ) {
						this.collapseAll( event );
					}

					// Reset the mouseHandled flag
					this.mouseHandled = false;
				}
			});
		},

		_destroy: function() {
			// Destroy (sub)menus
			this.element
				.removeAttr( "aria-activedescendant" )
				.find( ".ui-menu" ).addBack()
					.removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons" )
					.removeAttr( "role" )
					.removeAttr( "tabIndex" )
					.removeAttr( "aria-labelledby" )
					.removeAttr( "aria-expanded" )
					.removeAttr( "aria-hidden" )
					.removeAttr( "aria-disabled" )
					.removeUniqueId()
					.show();

			// Destroy menu items
			this.element.find( ".ui-menu-item" )
				.removeClass( "ui-menu-item" )
				.removeAttr( "role" )
				.removeAttr( "aria-disabled" )
				.children( "a" )
					.removeUniqueId()
					.removeClass( "ui-corner-all ui-state-hover" )
					.removeAttr( "tabIndex" )
					.removeAttr( "role" )
					.removeAttr( "aria-haspopup" )
					.children().each( function() {
						var elem = $( this );
						if ( elem.data( "ui-menu-submenu-carat" ) ) {
							elem.remove();
						}
					});

			// Destroy menu dividers
			this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" );
		},

		_keydown: function( event ) {
			var match, prev, character, skip, regex,
				preventDefault = true;

			function escape( value ) {
				return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
			}

			switch ( event.keyCode ) {
			case $.ui.keyCode.PAGE_UP:
				this.previousPage( event );
				break;
			case $.ui.keyCode.PAGE_DOWN:
				this.nextPage( event );
				break;
			case $.ui.keyCode.HOME:
				this._move( "first", "first", event );
				break;
			case $.ui.keyCode.END:
				this._move( "last", "last", event );
				break;
			case $.ui.keyCode.UP:
				this.previous( event );
				break;
			case $.ui.keyCode.DOWN:
				this.next( event );
				break;
			case $.ui.keyCode.LEFT:
				this.collapse( event );
				break;
			case $.ui.keyCode.RIGHT:
				if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
					this.expand( event );
				}
				break;
			case $.ui.keyCode.ENTER:
			case $.ui.keyCode.SPACE:
				this._activate( event );
				break;
			case $.ui.keyCode.ESCAPE:
				this.collapse( event );
				break;
			default:
				preventDefault = false;
				prev = this.previousFilter || "";
				character = String.fromCharCode( event.keyCode );
				skip = false;

				clearTimeout( this.filterTimer );

				if ( character === prev ) {
					skip = true;
				} else {
					character = prev + character;
				}

				regex = new RegExp( "^" + escape( character ), "i" );
				match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
					return regex.test( $( this ).children( "a" ).text() );
				});
				match = skip && match.index( this.active.next() ) !== -1 ?
					this.active.nextAll( ".ui-menu-item" ) :
					match;

				// If no matches on the current filter, reset to the last character pressed
				// to move down the menu to the first item that starts with that character
				if ( !match.length ) {
					character = String.fromCharCode( event.keyCode );
					regex = new RegExp( "^" + escape( character ), "i" );
					match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
						return regex.test( $( this ).children( "a" ).text() );
					});
				}

				if ( match.length ) {
					this.focus( event, match );
					if ( match.length > 1 ) {
						this.previousFilter = character;
						this.filterTimer = this._delay(function() {
							delete this.previousFilter;
						}, 1000 );
					} else {
						delete this.previousFilter;
					}
				} else {
					delete this.previousFilter;
				}
			}

			if ( preventDefault ) {
				event.preventDefault();
			}
		},

		_activate: function( event ) {
			if ( !this.active.is( ".ui-state-disabled" ) ) {
				if ( this.active.children( "a[aria-haspopup='true']" ).length ) {
					this.expand( event );
				} else {
					this.select( event );
				}
			}
		},

		refresh: function() {
			var menus,
				icon = this.options.icons.submenu,
				submenus = this.element.find( this.options.menus );

			this.element.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length );

			// Initialize nested menus
			submenus.filter( ":not(.ui-menu)" )
				.addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
				.hide()
				.attr({
					role: this.options.role,
					"aria-hidden": "true",
					"aria-expanded": "false"
				})
				.each(function() {
					var menu = $( this ),
						item = menu.prev( "a" ),
						submenuCarat = $( "<span>" )
							.addClass( "ui-menu-icon ui-icon " + icon )
							.data( "ui-menu-submenu-carat", true );

					item
						.attr( "aria-haspopup", "true" )
						.prepend( submenuCarat );
					menu.attr( "aria-labelledby", item.attr( "id" ) );
				});

			menus = submenus.add( this.element );

			// Don't refresh list items that are already adapted
			menus.children( ":not(.ui-menu-item):has(a)" )
				.addClass( "ui-menu-item" )
				.attr( "role", "presentation" )
				.children( "a" )
					.uniqueId()
					.addClass( "ui-corner-all" )
					.attr({
						tabIndex: -1,
						role: this._itemRole()
					});

			// Initialize unlinked menu-items containing spaces and/or dashes only as dividers
			menus.children( ":not(.ui-menu-item)" ).each(function() {
				var item = $( this );
				// hyphen, em dash, en dash
				if ( !/[^\-\u2014\u2013\s]/.test( item.text() ) ) {
					item.addClass( "ui-widget-content ui-menu-divider" );
				}
			});

			// Add aria-disabled attribute to any disabled menu item
			menus.children( ".ui-state-disabled" ).attr( "aria-disabled", "true" );

			// If the active item has been removed, blur the menu
			if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
				this.blur();
			}
		},

		_itemRole: function() {
			return {
				menu: "menuitem",
				listbox: "option"
			}[ this.options.role ];
		},

		_setOption: function( key, value ) {
			if ( key === "icons" ) {
				this.element.find( ".ui-menu-icon" )
					.removeClass( this.options.icons.submenu )
					.addClass( value.submenu );
			}
			this._super( key, value );
		},

		focus: function( event, item ) {
			var nested, focused;
			this.blur( event, event && event.type === "focus" );

			this._scrollIntoView( item );

			this.active = item.first();
			focused = this.active.children( "a" ).addClass( "ui-state-focus" );
			// Only update aria-activedescendant if there's a role
			// otherwise we assume focus is managed elsewhere
			if ( this.options.role ) {
				this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
			}

			// Highlight active parent menu item, if any
			this.active
				.parent()
				.closest( ".ui-menu-item" )
				.children( "a:first" )
				.addClass( "ui-state-active" );

			if ( event && event.type === "keydown" ) {
				this._close();
			} else {
				this.timer = this._delay(function() {
					this._close();
				}, this.delay );
			}

			nested = item.children( ".ui-menu" );
			if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) {
				this._startOpening(nested);
			}
			this.activeMenu = item.parent();

			this._trigger( "focus", event, { item: item } );
		},

		_scrollIntoView: function( item ) {
			var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
			if ( this._hasScroll() ) {
				borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0;
				paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0;
				offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
				scroll = this.activeMenu.scrollTop();
				elementHeight = this.activeMenu.height();
				itemHeight = item.height();

				if ( offset < 0 ) {
					this.activeMenu.scrollTop( scroll + offset );
				} else if ( offset + itemHeight > elementHeight ) {
					this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
				}
			}
		},

		blur: function( event, fromFocus ) {
			if ( !fromFocus ) {
				clearTimeout( this.timer );
			}

			if ( !this.active ) {
				return;
			}

			this.active.children( "a" ).removeClass( "ui-state-focus" );
			this.active = null;

			this._trigger( "blur", event, { item: this.active } );
		},

		_startOpening: function( submenu ) {
			clearTimeout( this.timer );

			// Don't open if already open fixes a Firefox bug that caused a .5 pixel
			// shift in the submenu position when mousing over the carat icon
			if ( submenu.attr( "aria-hidden" ) !== "true" ) {
				return;
			}

			this.timer = this._delay(function() {
				this._close();
				this._open( submenu );
			}, this.delay );
		},

		_open: function( submenu ) {
			var position = $.extend({
				of: this.active
			}, this.options.position );

			clearTimeout( this.timer );
			this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
				.hide()
				.attr( "aria-hidden", "true" );

			submenu
				.show()
				.removeAttr( "aria-hidden" )
				.attr( "aria-expanded", "true" )
				.position( position );
		},

		collapseAll: function( event, all ) {
			clearTimeout( this.timer );
			this.timer = this._delay(function() {
				// If we were passed an event, look for the submenu that contains the event
				var currentMenu = all ? this.element :
					$( event && event.target ).closest( this.element.find( ".ui-menu" ) );

				// If we found no valid submenu ancestor, use the main menu to close all sub menus anyway
				if ( !currentMenu.length ) {
					currentMenu = this.element;
				}

				this._close( currentMenu );

				this.blur( event );
				this.activeMenu = currentMenu;
			}, this.delay );
		},

		// With no arguments, closes the currently active menu - if nothing is active
		// it closes all menus.  If passed an argument, it will search for menus BELOW
		_close: function( startMenu ) {
			if ( !startMenu ) {
				startMenu = this.active ? this.active.parent() : this.element;
			}

			startMenu
				.find( ".ui-menu" )
					.hide()
					.attr( "aria-hidden", "true" )
					.attr( "aria-expanded", "false" )
				.end()
				.find( "a.ui-state-active" )
					.removeClass( "ui-state-active" );
		},

		collapse: function( event ) {
			var newItem = this.active &&
				this.active.parent().closest( ".ui-menu-item", this.element );
			if ( newItem && newItem.length ) {
				this._close();
				this.focus( event, newItem );
			}
		},

		expand: function( event ) {
			var newItem = this.active &&
				this.active
					.children( ".ui-menu " )
					.children( ".ui-menu-item" )
					.first();

			if ( newItem && newItem.length ) {
				this._open( newItem.parent() );

				// Delay so Firefox will not hide activedescendant change in expanding submenu from AT
				this._delay(function() {
					this.focus( event, newItem );
				});
			}
		},

		next: function( event ) {
			this._move( "next", "first", event );
		},

		previous: function( event ) {
			this._move( "prev", "last", event );
		},

		isFirstItem: function() {
			return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
		},

		isLastItem: function() {
			return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
		},

		_move: function( direction, filter, event ) {
			var next;
			if ( this.active ) {
				if ( direction === "first" || direction === "last" ) {
					next = this.active
						[ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
						.eq( -1 );
				} else {
					next = this.active
						[ direction + "All" ]( ".ui-menu-item" )
						.eq( 0 );
				}
			}
			if ( !next || !next.length || !this.active ) {
				next = this.activeMenu.children( ".ui-menu-item" )[ filter ]();
			}

			this.focus( event, next );
		},

		nextPage: function( event ) {
			var item, base, height;

			if ( !this.active ) {
				this.next( event );
				return;
			}
			if ( this.isLastItem() ) {
				return;
			}
			if ( this._hasScroll() ) {
				base = this.active.offset().top;
				height = this.element.height();
				this.active.nextAll( ".ui-menu-item" ).each(function() {
					item = $( this );
					return item.offset().top - base - height < 0;
				});

				this.focus( event, item );
			} else {
				this.focus( event, this.activeMenu.children( ".ui-menu-item" )
					[ !this.active ? "first" : "last" ]() );
			}
		},

		previousPage: function( event ) {
			var item, base, height;
			if ( !this.active ) {
				this.next( event );
				return;
			}
			if ( this.isFirstItem() ) {
				return;
			}
			if ( this._hasScroll() ) {
				base = this.active.offset().top;
				height = this.element.height();
				this.active.prevAll( ".ui-menu-item" ).each(function() {
					item = $( this );
					return item.offset().top - base + height > 0;
				});

				this.focus( event, item );
			} else {
				this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() );
			}
		},

		_hasScroll: function() {
			return this.element.outerHeight() < this.element.prop( "scrollHeight" );
		},

		select: function( event ) {
			// TODO: It should never be possible to not have an active item at this
			// point, but the tests don't trigger mouseenter before click.
			this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
			var ui = { item: this.active };
			if ( !this.active.has( ".ui-menu" ).length ) {
				this.collapseAll( event, true );
			}
			this._trigger( "select", event, ui );
		}
	});

	}( jQuery ));
	(function( $, undefined ) {

	$.widget( "ui.progressbar", {
		version: "1.10.4",
		options: {
			max: 100,
			value: 0,

			change: null,
			complete: null
		},

		min: 0,

		_create: function() {
			// Constrain initial value
			this.oldValue = this.options.value = this._constrainedValue();

			this.element
				.addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
				.attr({
					// Only set static values, aria-valuenow and aria-valuemax are
					// set inside _refreshValue()
					role: "progressbar",
					"aria-valuemin": this.min
				});

			this.valueDiv = $( "<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>" )
				.appendTo( this.element );

			this._refreshValue();
		},

		_destroy: function() {
			this.element
				.removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
				.removeAttr( "role" )
				.removeAttr( "aria-valuemin" )
				.removeAttr( "aria-valuemax" )
				.removeAttr( "aria-valuenow" );

			this.valueDiv.remove();
		},

		value: function( newValue ) {
			if ( newValue === undefined ) {
				return this.options.value;
			}

			this.options.value = this._constrainedValue( newValue );
			this._refreshValue();
		},

		_constrainedValue: function( newValue ) {
			if ( newValue === undefined ) {
				newValue = this.options.value;
			}

			this.indeterminate = newValue === false;

			// sanitize value
			if ( typeof newValue !== "number" ) {
				newValue = 0;
			}

			return this.indeterminate ? false :
				Math.min( this.options.max, Math.max( this.min, newValue ) );
		},

		_setOptions: function( options ) {
			// Ensure "value" option is set after other values (like max)
			var value = options.value;
			delete options.value;

			this._super( options );

			this.options.value = this._constrainedValue( value );
			this._refreshValue();
		},

		_setOption: function( key, value ) {
			if ( key === "max" ) {
				// Don't allow a max less than min
				value = Math.max( this.min, value );
			}

			this._super( key, value );
		},

		_percentage: function() {
			return this.indeterminate ? 100 : 100 * ( this.options.value - this.min ) / ( this.options.max - this.min );
		},

		_refreshValue: function() {
			var value = this.options.value,
				percentage = this._percentage();

			this.valueDiv
				.toggle( this.indeterminate || value > this.min )
				.toggleClass( "ui-corner-right", value === this.options.max )
				.width( percentage.toFixed(0) + "%" );

			this.element.toggleClass( "ui-progressbar-indeterminate", this.indeterminate );

			if ( this.indeterminate ) {
				this.element.removeAttr( "aria-valuenow" );
				if ( !this.overlayDiv ) {
					this.overlayDiv = $( "<div class='ui-progressbar-overlay'></div>" ).appendTo( this.valueDiv );
				}
			} else {
				this.element.attr({
					"aria-valuemax": this.options.max,
					"aria-valuenow": value
				});
				if ( this.overlayDiv ) {
					this.overlayDiv.remove();
					this.overlayDiv = null;
				}
			}

			if ( this.oldValue !== value ) {
				this.oldValue = value;
				this._trigger( "change" );
			}
			if ( value === this.options.max ) {
				this._trigger( "complete" );
			}
		}
	});

	})( jQuery );
	(function( $, undefined ) {

	function num(v) {
		return parseInt(v, 10) || 0;
	}

	function isNumber(value) {
		return !isNaN(parseInt(value, 10));
	}

	$.widget("ui.resizable", $.ui.mouse, {
		version: "1.10.4",
		widgetEventPrefix: "resize",
		options: {
			alsoResize: false,
			animate: false,
			animateDuration: "slow",
			animateEasing: "swing",
			aspectRatio: false,
			autoHide: false,
			containment: false,
			ghost: false,
			grid: false,
			handles: "e,s,se",
			helper: false,
			maxHeight: null,
			maxWidth: null,
			minHeight: 10,
			minWidth: 10,
			// See #7960
			zIndex: 90,

			// callbacks
			resize: null,
			start: null,
			stop: null
		},
		_create: function() {

			var n, i, handle, axis, hname,
				that = this,
				o = this.options;
			this.element.addClass("ui-resizable");

			$.extend(this, {
				_aspectRatio: !!(o.aspectRatio),
				aspectRatio: o.aspectRatio,
				originalElement: this.element,
				_proportionallyResizeElements: [],
				_helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null
			});

			//Wrap the element if it cannot hold child nodes
			if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) {

				//Create a wrapper element and set the wrapper to the new current internal element
				this.element.wrap(
					$("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({
						position: this.element.css("position"),
						width: this.element.outerWidth(),
						height: this.element.outerHeight(),
						top: this.element.css("top"),
						left: this.element.css("left")
					})
				);

				//Overwrite the original this.element
				this.element = this.element.parent().data(
					"ui-resizable", this.element.data("ui-resizable")
				);

				this.elementIsWrapper = true;

				//Move margins to the wrapper
				this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") });
				this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0});

				//Prevent Safari textarea resize
				this.originalResizeStyle = this.originalElement.css("resize");
				this.originalElement.css("resize", "none");

				//Push the actual element to our proportionallyResize internal array
				this._proportionallyResizeElements.push(this.originalElement.css({ position: "static", zoom: 1, display: "block" }));

				// avoid IE jump (hard set the margin)
				this.originalElement.css({ margin: this.originalElement.css("margin") });

				// fix handlers offset
				this._proportionallyResize();

			}

			this.handles = o.handles || (!$(".ui-resizable-handle", this.element).length ? "e,s,se" : { n: ".ui-resizable-n", e: ".ui-resizable-e", s: ".ui-resizable-s", w: ".ui-resizable-w", se: ".ui-resizable-se", sw: ".ui-resizable-sw", ne: ".ui-resizable-ne", nw: ".ui-resizable-nw" });
			if(this.handles.constructor === String) {

				if ( this.handles === "all") {
					this.handles = "n,e,s,w,se,sw,ne,nw";
				}

				n = this.handles.split(",");
				this.handles = {};

				for(i = 0; i < n.length; i++) {

					handle = $.trim(n[i]);
					hname = "ui-resizable-"+handle;
					axis = $("<div class='ui-resizable-handle " + hname + "'></div>");

					// Apply zIndex to all handles - see #7960
					axis.css({ zIndex: o.zIndex });

					//TODO : What's going on here?
					if ("se" === handle) {
						axis.addClass("ui-icon ui-icon-gripsmall-diagonal-se");
					}

					//Insert into internal handles object and append to element
					this.handles[handle] = ".ui-resizable-"+handle;
					this.element.append(axis);
				}

			}

			this._renderAxis = function(target) {

				var i, axis, padPos, padWrapper;

				target = target || this.element;

				for(i in this.handles) {

					if(this.handles[i].constructor === String) {
						this.handles[i] = $(this.handles[i], this.element).show();
					}

					//Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls)
					if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) {

						axis = $(this.handles[i], this.element);

						//Checking the correct pad and border
						padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();

						//The padding type i have to apply...
						padPos = [ "padding",
							/ne|nw|n/.test(i) ? "Top" :
							/se|sw|s/.test(i) ? "Bottom" :
							/^e$/.test(i) ? "Right" : "Left" ].join("");

						target.css(padPos, padWrapper);

						this._proportionallyResize();

					}

					//TODO: What's that good for? There's not anything to be executed left
					if(!$(this.handles[i]).length) {
						continue;
					}
				}
			};

			//TODO: make renderAxis a prototype function
			this._renderAxis(this.element);

			this._handles = $(".ui-resizable-handle", this.element)
				.disableSelection();

			//Matching axis name
			this._handles.mouseover(function() {
				if (!that.resizing) {
					if (this.className) {
						axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
					}
					//Axis, default = se
					that.axis = axis && axis[1] ? axis[1] : "se";
				}
			});

			//If we want to auto hide the elements
			if (o.autoHide) {
				this._handles.hide();
				$(this.element)
					.addClass("ui-resizable-autohide")
					.mouseenter(function() {
						if (o.disabled) {
							return;
						}
						$(this).removeClass("ui-resizable-autohide");
						that._handles.show();
					})
					.mouseleave(function(){
						if (o.disabled) {
							return;
						}
						if (!that.resizing) {
							$(this).addClass("ui-resizable-autohide");
							that._handles.hide();
						}
					});
			}

			//Initialize the mouse interaction
			this._mouseInit();

		},

		_destroy: function() {

			this._mouseDestroy();

			var wrapper,
				_destroy = function(exp) {
					$(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
						.removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove();
				};

			//TODO: Unwrap at same DOM position
			if (this.elementIsWrapper) {
				_destroy(this.element);
				wrapper = this.element;
				this.originalElement.css({
					position: wrapper.css("position"),
					width: wrapper.outerWidth(),
					height: wrapper.outerHeight(),
					top: wrapper.css("top"),
					left: wrapper.css("left")
				}).insertAfter( wrapper );
				wrapper.remove();
			}

			this.originalElement.css("resize", this.originalResizeStyle);
			_destroy(this.originalElement);

			return this;
		},

		_mouseCapture: function(event) {
			var i, handle,
				capture = false;

			for (i in this.handles) {
				handle = $(this.handles[i])[0];
				if (handle === event.target || $.contains(handle, event.target)) {
					capture = true;
				}
			}

			return !this.options.disabled && capture;
		},

		_mouseStart: function(event) {

			var curleft, curtop, cursor,
				o = this.options,
				iniPos = this.element.position(),
				el = this.element;

			this.resizing = true;

			// bugfix for http://dev.jquery.com/ticket/1749
			if ( (/absolute/).test( el.css("position") ) ) {
				el.css({ position: "absolute", top: el.css("top"), left: el.css("left") });
			} else if (el.is(".ui-draggable")) {
				el.css({ position: "absolute", top: iniPos.top, left: iniPos.left });
			}

			this._renderProxy();

			curleft = num(this.helper.css("left"));
			curtop = num(this.helper.css("top"));

			if (o.containment) {
				curleft += $(o.containment).scrollLeft() || 0;
				curtop += $(o.containment).scrollTop() || 0;
			}

			//Store needed variables
			this.offset = this.helper.offset();
			this.position = { left: curleft, top: curtop };
			this.size = this._helper ? { width: this.helper.width(), height: this.helper.height() } : { width: el.width(), height: el.height() };
			this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
			this.originalPosition = { left: curleft, top: curtop };
			this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() };
			this.originalMousePosition = { left: event.pageX, top: event.pageY };

			//Aspect Ratio
			this.aspectRatio = (typeof o.aspectRatio === "number") ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1);

			cursor = $(".ui-resizable-" + this.axis).css("cursor");
			$("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor);

			el.addClass("ui-resizable-resizing");
			this._propagate("start", event);
			return true;
		},

		_mouseDrag: function(event) {

			//Increase performance, avoid regex
			var data,
				el = this.helper, props = {},
				smp = this.originalMousePosition,
				a = this.axis,
				prevTop = this.position.top,
				prevLeft = this.position.left,
				prevWidth = this.size.width,
				prevHeight = this.size.height,
				dx = (event.pageX-smp.left)||0,
				dy = (event.pageY-smp.top)||0,
				trigger = this._change[a];

			if (!trigger) {
				return false;
			}

			// Calculate the attrs that will be change
			data = trigger.apply(this, [event, dx, dy]);

			// Put this in the mouseDrag handler since the user can start pressing shift while resizing
			this._updateVirtualBoundaries(event.shiftKey);
			if (this._aspectRatio || event.shiftKey) {
				data = this._updateRatio(data, event);
			}

			data = this._respectSize(data, event);

			this._updateCache(data);

			// plugins callbacks need to be called first
			this._propagate("resize", event);

			if (this.position.top !== prevTop) {
				props.top = this.position.top + "px";
			}
			if (this.position.left !== prevLeft) {
				props.left = this.position.left + "px";
			}
			if (this.size.width !== prevWidth) {
				props.width = this.size.width + "px";
			}
			if (this.size.height !== prevHeight) {
				props.height = this.size.height + "px";
			}
			el.css(props);

			if (!this._helper && this._proportionallyResizeElements.length) {
				this._proportionallyResize();
			}

			// Call the user callback if the element was resized
			if ( ! $.isEmptyObject(props) ) {
				this._trigger("resize", event, this.ui());
			}

			return false;
		},

		_mouseStop: function(event) {

			this.resizing = false;
			var pr, ista, soffseth, soffsetw, s, left, top,
				o = this.options, that = this;

			if(this._helper) {

				pr = this._proportionallyResizeElements;
				ista = pr.length && (/textarea/i).test(pr[0].nodeName);
				soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height;
				soffsetw = ista ? 0 : that.sizeDiff.width;

				s = { width: (that.helper.width()  - soffsetw), height: (that.helper.height() - soffseth) };
				left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null;
				top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null;

				if (!o.animate) {
					this.element.css($.extend(s, { top: top, left: left }));
				}

				that.helper.height(that.size.height);
				that.helper.width(that.size.width);

				if (this._helper && !o.animate) {
					this._proportionallyResize();
				}
			}

			$("body").css("cursor", "auto");

			this.element.removeClass("ui-resizable-resizing");

			this._propagate("stop", event);

			if (this._helper) {
				this.helper.remove();
			}

			return false;

		},

		_updateVirtualBoundaries: function(forceAspectRatio) {
			var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b,
				o = this.options;

			b = {
				minWidth: isNumber(o.minWidth) ? o.minWidth : 0,
				maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity,
				minHeight: isNumber(o.minHeight) ? o.minHeight : 0,
				maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity
			};

			if(this._aspectRatio || forceAspectRatio) {
				// We want to create an enclosing box whose aspect ration is the requested one
				// First, compute the "projected" size for each dimension based on the aspect ratio and other dimension
				pMinWidth = b.minHeight * this.aspectRatio;
				pMinHeight = b.minWidth / this.aspectRatio;
				pMaxWidth = b.maxHeight * this.aspectRatio;
				pMaxHeight = b.maxWidth / this.aspectRatio;

				if(pMinWidth > b.minWidth) {
					b.minWidth = pMinWidth;
				}
				if(pMinHeight > b.minHeight) {
					b.minHeight = pMinHeight;
				}
				if(pMaxWidth < b.maxWidth) {
					b.maxWidth = pMaxWidth;
				}
				if(pMaxHeight < b.maxHeight) {
					b.maxHeight = pMaxHeight;
				}
			}
			this._vBoundaries = b;
		},

		_updateCache: function(data) {
			this.offset = this.helper.offset();
			if (isNumber(data.left)) {
				this.position.left = data.left;
			}
			if (isNumber(data.top)) {
				this.position.top = data.top;
			}
			if (isNumber(data.height)) {
				this.size.height = data.height;
			}
			if (isNumber(data.width)) {
				this.size.width = data.width;
			}
		},

		_updateRatio: function( data ) {

			var cpos = this.position,
				csize = this.size,
				a = this.axis;

			if (isNumber(data.height)) {
				data.width = (data.height * this.aspectRatio);
			} else if (isNumber(data.width)) {
				data.height = (data.width / this.aspectRatio);
			}

			if (a === "sw") {
				data.left = cpos.left + (csize.width - data.width);
				data.top = null;
			}
			if (a === "nw") {
				data.top = cpos.top + (csize.height - data.height);
				data.left = cpos.left + (csize.width - data.width);
			}

			return data;
		},

		_respectSize: function( data ) {

			var o = this._vBoundaries,
				a = this.axis,
				ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
				isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height),
				dw = this.originalPosition.left + this.originalSize.width,
				dh = this.position.top + this.size.height,
				cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
			if (isminw) {
				data.width = o.minWidth;
			}
			if (isminh) {
				data.height = o.minHeight;
			}
			if (ismaxw) {
				data.width = o.maxWidth;
			}
			if (ismaxh) {
				data.height = o.maxHeight;
			}

			if (isminw && cw) {
				data.left = dw - o.minWidth;
			}
			if (ismaxw && cw) {
				data.left = dw - o.maxWidth;
			}
			if (isminh && ch) {
				data.top = dh - o.minHeight;
			}
			if (ismaxh && ch) {
				data.top = dh - o.maxHeight;
			}

			// fixing jump error on top/left - bug #2330
			if (!data.width && !data.height && !data.left && data.top) {
				data.top = null;
			} else if (!data.width && !data.height && !data.top && data.left) {
				data.left = null;
			}

			return data;
		},

		_proportionallyResize: function() {

			if (!this._proportionallyResizeElements.length) {
				return;
			}

			var i, j, borders, paddings, prel,
				element = this.helper || this.element;

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

				prel = this._proportionallyResizeElements[i];

				if (!this.borderDif) {
					this.borderDif = [];
					borders = [prel.css("borderTopWidth"), prel.css("borderRightWidth"), prel.css("borderBottomWidth"), prel.css("borderLeftWidth")];
					paddings = [prel.css("paddingTop"), prel.css("paddingRight"), prel.css("paddingBottom"), prel.css("paddingLeft")];

					for ( j = 0; j < borders.length; j++ ) {
						this.borderDif[ j ] = ( parseInt( borders[ j ], 10 ) || 0 ) + ( parseInt( paddings[ j ], 10 ) || 0 );
					}
				}

				prel.css({
					height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0,
					width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0
				});

			}

		},

		_renderProxy: function() {

			var el = this.element, o = this.options;
			this.elementOffset = el.offset();

			if(this._helper) {

				this.helper = this.helper || $("<div style='overflow:hidden;'></div>");

				this.helper.addClass(this._helper).css({
					width: this.element.outerWidth() - 1,
					height: this.element.outerHeight() - 1,
					position: "absolute",
					left: this.elementOffset.left +"px",
					top: this.elementOffset.top +"px",
					zIndex: ++o.zIndex //TODO: Don't modify option
				});

				this.helper
					.appendTo("body")
					.disableSelection();

			} else {
				this.helper = this.element;
			}

		},

		_change: {
			e: function(event, dx) {
				return { width: this.originalSize.width + dx };
			},
			w: function(event, dx) {
				var cs = this.originalSize, sp = this.originalPosition;
				return { left: sp.left + dx, width: cs.width - dx };
			},
			n: function(event, dx, dy) {
				var cs = this.originalSize, sp = this.originalPosition;
				return { top: sp.top + dy, height: cs.height - dy };
			},
			s: function(event, dx, dy) {
				return { height: this.originalSize.height + dy };
			},
			se: function(event, dx, dy) {
				return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
			},
			sw: function(event, dx, dy) {
				return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
			},
			ne: function(event, dx, dy) {
				return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
			},
			nw: function(event, dx, dy) {
				return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
			}
		},

		_propagate: function(n, event) {
			$.ui.plugin.call(this, n, [event, this.ui()]);
			(n !== "resize" && this._trigger(n, event, this.ui()));
		},

		plugins: {},

		ui: function() {
			return {
				originalElement: this.originalElement,
				element: this.element,
				helper: this.helper,
				position: this.position,
				size: this.size,
				originalSize: this.originalSize,
				originalPosition: this.originalPosition
			};
		}

	});

	/*
	 * Resizable Extensions
	 */

	$.ui.plugin.add("resizable", "animate", {

		stop: function( event ) {
			var that = $(this).data("ui-resizable"),
				o = that.options,
				pr = that._proportionallyResizeElements,
				ista = pr.length && (/textarea/i).test(pr[0].nodeName),
				soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height,
				soffsetw = ista ? 0 : that.sizeDiff.width,
				style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) },
				left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null,
				top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null;

			that.element.animate(
				$.extend(style, top && left ? { top: top, left: left } : {}), {
					duration: o.animateDuration,
					easing: o.animateEasing,
					step: function() {

						var data = {
							width: parseInt(that.element.css("width"), 10),
							height: parseInt(that.element.css("height"), 10),
							top: parseInt(that.element.css("top"), 10),
							left: parseInt(that.element.css("left"), 10)
						};

						if (pr && pr.length) {
							$(pr[0]).css({ width: data.width, height: data.height });
						}

						// propagating resize, and updating values for each animation step
						that._updateCache(data);
						that._propagate("resize", event);

					}
				}
			);
		}

	});

	$.ui.plugin.add("resizable", "containment", {

		start: function() {
			var element, p, co, ch, cw, width, height,
				that = $(this).data("ui-resizable"),
				o = that.options,
				el = that.element,
				oc = o.containment,
				ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc;

			if (!ce) {
				return;
			}

			that.containerElement = $(ce);

			if (/document/.test(oc) || oc === document) {
				that.containerOffset = { left: 0, top: 0 };
				that.containerPosition = { left: 0, top: 0 };

				that.parentData = {
					element: $(document), left: 0, top: 0,
					width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight
				};
			}

			// i'm a node, so compute top, left, right, bottom
			else {
				element = $(ce);
				p = [];
				$([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); });

				that.containerOffset = element.offset();
				that.containerPosition = element.position();
				that.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) };

				co = that.containerOffset;
				ch = that.containerSize.height;
				cw = that.containerSize.width;
				width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw );
				height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch);

				that.parentData = {
					element: ce, left: co.left, top: co.top, width: width, height: height
				};
			}
		},

		resize: function( event ) {
			var woset, hoset, isParent, isOffsetRelative,
				that = $(this).data("ui-resizable"),
				o = that.options,
				co = that.containerOffset, cp = that.position,
				pRatio = that._aspectRatio || event.shiftKey,
				cop = { top:0, left:0 }, ce = that.containerElement;

			if (ce[0] !== document && (/static/).test(ce.css("position"))) {
				cop = co;
			}

			if (cp.left < (that._helper ? co.left : 0)) {
				that.size.width = that.size.width + (that._helper ? (that.position.left - co.left) : (that.position.left - cop.left));
				if (pRatio) {
					that.size.height = that.size.width / that.aspectRatio;
				}
				that.position.left = o.helper ? co.left : 0;
			}

			if (cp.top < (that._helper ? co.top : 0)) {
				that.size.height = that.size.height + (that._helper ? (that.position.top - co.top) : that.position.top);
				if (pRatio) {
					that.size.width = that.size.height * that.aspectRatio;
				}
				that.position.top = that._helper ? co.top : 0;
			}

			that.offset.left = that.parentData.left+that.position.left;
			that.offset.top = that.parentData.top+that.position.top;

			woset = Math.abs( (that._helper ? that.offset.left - cop.left : (that.offset.left - cop.left)) + that.sizeDiff.width );
			hoset = Math.abs( (that._helper ? that.offset.top - cop.top : (that.offset.top - co.top)) + that.sizeDiff.height );

			isParent = that.containerElement.get(0) === that.element.parent().get(0);
			isOffsetRelative = /relative|absolute/.test(that.containerElement.css("position"));

			if ( isParent && isOffsetRelative ) {
				woset -= Math.abs( that.parentData.left );
			}

			if (woset + that.size.width >= that.parentData.width) {
				that.size.width = that.parentData.width - woset;
				if (pRatio) {
					that.size.height = that.size.width / that.aspectRatio;
				}
			}

			if (hoset + that.size.height >= that.parentData.height) {
				that.size.height = that.parentData.height - hoset;
				if (pRatio) {
					that.size.width = that.size.height * that.aspectRatio;
				}
			}
		},

		stop: function(){
			var that = $(this).data("ui-resizable"),
				o = that.options,
				co = that.containerOffset,
				cop = that.containerPosition,
				ce = that.containerElement,
				helper = $(that.helper),
				ho = helper.offset(),
				w = helper.outerWidth() - that.sizeDiff.width,
				h = helper.outerHeight() - that.sizeDiff.height;

			if (that._helper && !o.animate && (/relative/).test(ce.css("position"))) {
				$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
			}

			if (that._helper && !o.animate && (/static/).test(ce.css("position"))) {
				$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
			}

		}
	});

	$.ui.plugin.add("resizable", "alsoResize", {

		start: function () {
			var that = $(this).data("ui-resizable"),
				o = that.options,
				_store = function (exp) {
					$(exp).each(function() {
						var el = $(this);
						el.data("ui-resizable-alsoresize", {
							width: parseInt(el.width(), 10), height: parseInt(el.height(), 10),
							left: parseInt(el.css("left"), 10), top: parseInt(el.css("top"), 10)
						});
					});
				};

			if (typeof(o.alsoResize) === "object" && !o.alsoResize.parentNode) {
				if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); }
				else { $.each(o.alsoResize, function (exp) { _store(exp); }); }
			}else{
				_store(o.alsoResize);
			}
		},

		resize: function (event, ui) {
			var that = $(this).data("ui-resizable"),
				o = that.options,
				os = that.originalSize,
				op = that.originalPosition,
				delta = {
					height: (that.size.height - os.height) || 0, width: (that.size.width - os.width) || 0,
					top: (that.position.top - op.top) || 0, left: (that.position.left - op.left) || 0
				},

				_alsoResize = function (exp, c) {
					$(exp).each(function() {
						var el = $(this), start = $(this).data("ui-resizable-alsoresize"), style = {},
							css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ["width", "height"] : ["width", "height", "top", "left"];

						$.each(css, function (i, prop) {
							var sum = (start[prop]||0) + (delta[prop]||0);
							if (sum && sum >= 0) {
								style[prop] = sum || null;
							}
						});

						el.css(style);
					});
				};

			if (typeof(o.alsoResize) === "object" && !o.alsoResize.nodeType) {
				$.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); });
			}else{
				_alsoResize(o.alsoResize);
			}
		},

		stop: function () {
			$(this).removeData("resizable-alsoresize");
		}
	});

	$.ui.plugin.add("resizable", "ghost", {

		start: function() {

			var that = $(this).data("ui-resizable"), o = that.options, cs = that.size;

			that.ghost = that.originalElement.clone();
			that.ghost
				.css({ opacity: 0.25, display: "block", position: "relative", height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 })
				.addClass("ui-resizable-ghost")
				.addClass(typeof o.ghost === "string" ? o.ghost : "");

			that.ghost.appendTo(that.helper);

		},

		resize: function(){
			var that = $(this).data("ui-resizable");
			if (that.ghost) {
				that.ghost.css({ position: "relative", height: that.size.height, width: that.size.width });
			}
		},

		stop: function() {
			var that = $(this).data("ui-resizable");
			if (that.ghost && that.helper) {
				that.helper.get(0).removeChild(that.ghost.get(0));
			}
		}

	});

	$.ui.plugin.add("resizable", "grid", {

		resize: function() {
			var that = $(this).data("ui-resizable"),
				o = that.options,
				cs = that.size,
				os = that.originalSize,
				op = that.originalPosition,
				a = that.axis,
				grid = typeof o.grid === "number" ? [o.grid, o.grid] : o.grid,
				gridX = (grid[0]||1),
				gridY = (grid[1]||1),
				ox = Math.round((cs.width - os.width) / gridX) * gridX,
				oy = Math.round((cs.height - os.height) / gridY) * gridY,
				newWidth = os.width + ox,
				newHeight = os.height + oy,
				isMaxWidth = o.maxWidth && (o.maxWidth < newWidth),
				isMaxHeight = o.maxHeight && (o.maxHeight < newHeight),
				isMinWidth = o.minWidth && (o.minWidth > newWidth),
				isMinHeight = o.minHeight && (o.minHeight > newHeight);

			o.grid = grid;

			if (isMinWidth) {
				newWidth = newWidth + gridX;
			}
			if (isMinHeight) {
				newHeight = newHeight + gridY;
			}
			if (isMaxWidth) {
				newWidth = newWidth - gridX;
			}
			if (isMaxHeight) {
				newHeight = newHeight - gridY;
			}

			if (/^(se|s|e)$/.test(a)) {
				that.size.width = newWidth;
				that.size.height = newHeight;
			} else if (/^(ne)$/.test(a)) {
				that.size.width = newWidth;
				that.size.height = newHeight;
				that.position.top = op.top - oy;
			} else if (/^(sw)$/.test(a)) {
				that.size.width = newWidth;
				that.size.height = newHeight;
				that.position.left = op.left - ox;
			} else {
				if ( newHeight - gridY > 0 ) {
					that.size.height = newHeight;
					that.position.top = op.top - oy;
				} else {
					that.size.height = gridY;
					that.position.top = op.top + os.height - gridY;
				}
				if ( newWidth - gridX > 0 ) {
					that.size.width = newWidth;
					that.position.left = op.left - ox;
				} else {
					that.size.width = gridX;
					that.position.left = op.left + os.width - gridX;
				}
			}
		}

	});

	})(jQuery);
	(function( $, undefined ) {

	$.widget("ui.selectable", $.ui.mouse, {
		version: "1.10.4",
		options: {
			appendTo: "body",
			autoRefresh: true,
			distance: 0,
			filter: "*",
			tolerance: "touch",

			// callbacks
			selected: null,
			selecting: null,
			start: null,
			stop: null,
			unselected: null,
			unselecting: null
		},
		_create: function() {
			var selectees,
				that = this;

			this.element.addClass("ui-selectable");

			this.dragged = false;

			// cache selectee children based on filter
			this.refresh = function() {
				selectees = $(that.options.filter, that.element[0]);
				selectees.addClass("ui-selectee");
				selectees.each(function() {
					var $this = $(this),
						pos = $this.offset();
					$.data(this, "selectable-item", {
						element: this,
						$element: $this,
						left: pos.left,
						top: pos.top,
						right: pos.left + $this.outerWidth(),
						bottom: pos.top + $this.outerHeight(),
						startselected: false,
						selected: $this.hasClass("ui-selected"),
						selecting: $this.hasClass("ui-selecting"),
						unselecting: $this.hasClass("ui-unselecting")
					});
				});
			};
			this.refresh();

			this.selectees = selectees.addClass("ui-selectee");

			this._mouseInit();

			this.helper = $("<div class='ui-selectable-helper'></div>");
		},

		_destroy: function() {
			this.selectees
				.removeClass("ui-selectee")
				.removeData("selectable-item");
			this.element
				.removeClass("ui-selectable ui-selectable-disabled");
			this._mouseDestroy();
		},

		_mouseStart: function(event) {
			var that = this,
				options = this.options;

			this.opos = [event.pageX, event.pageY];

			if (this.options.disabled) {
				return;
			}

			this.selectees = $(options.filter, this.element[0]);

			this._trigger("start", event);

			$(options.appendTo).append(this.helper);
			// position helper (lasso)
			this.helper.css({
				"left": event.pageX,
				"top": event.pageY,
				"width": 0,
				"height": 0
			});

			if (options.autoRefresh) {
				this.refresh();
			}

			this.selectees.filter(".ui-selected").each(function() {
				var selectee = $.data(this, "selectable-item");
				selectee.startselected = true;
				if (!event.metaKey && !event.ctrlKey) {
					selectee.$element.removeClass("ui-selected");
					selectee.selected = false;
					selectee.$element.addClass("ui-unselecting");
					selectee.unselecting = true;
					// selectable UNSELECTING callback
					that._trigger("unselecting", event, {
						unselecting: selectee.element
					});
				}
			});

			$(event.target).parents().addBack().each(function() {
				var doSelect,
					selectee = $.data(this, "selectable-item");
				if (selectee) {
					doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass("ui-selected");
					selectee.$element
						.removeClass(doSelect ? "ui-unselecting" : "ui-selected")
						.addClass(doSelect ? "ui-selecting" : "ui-unselecting");
					selectee.unselecting = !doSelect;
					selectee.selecting = doSelect;
					selectee.selected = doSelect;
					// selectable (UN)SELECTING callback
					if (doSelect) {
						that._trigger("selecting", event, {
							selecting: selectee.element
						});
					} else {
						that._trigger("unselecting", event, {
							unselecting: selectee.element
						});
					}
					return false;
				}
			});

		},

		_mouseDrag: function(event) {

			this.dragged = true;

			if (this.options.disabled) {
				return;
			}

			var tmp,
				that = this,
				options = this.options,
				x1 = this.opos[0],
				y1 = this.opos[1],
				x2 = event.pageX,
				y2 = event.pageY;

			if (x1 > x2) { tmp = x2; x2 = x1; x1 = tmp; }
			if (y1 > y2) { tmp = y2; y2 = y1; y1 = tmp; }
			this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1});

			this.selectees.each(function() {
				var selectee = $.data(this, "selectable-item"),
					hit = false;

				//prevent helper from being selected if appendTo: selectable
				if (!selectee || selectee.element === that.element[0]) {
					return;
				}

				if (options.tolerance === "touch") {
					hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) );
				} else if (options.tolerance === "fit") {
					hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2);
				}

				if (hit) {
					// SELECT
					if (selectee.selected) {
						selectee.$element.removeClass("ui-selected");
						selectee.selected = false;
					}
					if (selectee.unselecting) {
						selectee.$element.removeClass("ui-unselecting");
						selectee.unselecting = false;
					}
					if (!selectee.selecting) {
						selectee.$element.addClass("ui-selecting");
						selectee.selecting = true;
						// selectable SELECTING callback
						that._trigger("selecting", event, {
							selecting: selectee.element
						});
					}
				} else {
					// UNSELECT
					if (selectee.selecting) {
						if ((event.metaKey || event.ctrlKey) && selectee.startselected) {
							selectee.$element.removeClass("ui-selecting");
							selectee.selecting = false;
							selectee.$element.addClass("ui-selected");
							selectee.selected = true;
						} else {
							selectee.$element.removeClass("ui-selecting");
							selectee.selecting = false;
							if (selectee.startselected) {
								selectee.$element.addClass("ui-unselecting");
								selectee.unselecting = true;
							}
							// selectable UNSELECTING callback
							that._trigger("unselecting", event, {
								unselecting: selectee.element
							});
						}
					}
					if (selectee.selected) {
						if (!event.metaKey && !event.ctrlKey && !selectee.startselected) {
							selectee.$element.removeClass("ui-selected");
							selectee.selected = false;

							selectee.$element.addClass("ui-unselecting");
							selectee.unselecting = true;
							// selectable UNSELECTING callback
							that._trigger("unselecting", event, {
								unselecting: selectee.element
							});
						}
					}
				}
			});

			return false;
		},

		_mouseStop: function(event) {
			var that = this;

			this.dragged = false;

			$(".ui-unselecting", this.element[0]).each(function() {
				var selectee = $.data(this, "selectable-item");
				selectee.$element.removeClass("ui-unselecting");
				selectee.unselecting = false;
				selectee.startselected = false;
				that._trigger("unselected", event, {
					unselected: selectee.element
				});
			});
			$(".ui-selecting", this.element[0]).each(function() {
				var selectee = $.data(this, "selectable-item");
				selectee.$element.removeClass("ui-selecting").addClass("ui-selected");
				selectee.selecting = false;
				selectee.selected = true;
				selectee.startselected = true;
				that._trigger("selected", event, {
					selected: selectee.element
				});
			});
			this._trigger("stop", event);

			this.helper.remove();

			return false;
		}

	});

	})(jQuery);
	(function( $, undefined ) {

	// 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, {
		version: "1.10.4",
		widgetEventPrefix: "slide",

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

			// callbacks
			change: null,
			slide: null,
			start: null,
			stop: null
		},

		_create: function() {
			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");

			this._refresh();
			this._setOption( "disabled", this.options.disabled );

			this._animateOff = false;
		},

		_refresh: function() {
			this._createRange();
			this._createHandles();
			this._setupEvents();
			this._refreshValue();
		},

		_createHandles: function() {
			var i, handleCount,
				options = this.options,
				existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
				handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
				handles = [];

			handleCount = ( options.values && options.values.length ) || 1;

			if ( existingHandles.length > handleCount ) {
				existingHandles.slice( handleCount ).remove();
				existingHandles = existingHandles.slice( 0, handleCount );
			}

			for ( i = existingHandles.length; i < handleCount; i++ ) {
				handles.push( handle );
			}

			this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );

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

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

		_createRange: function() {
			var options = this.options,
				classes = "";

			if ( options.range ) {
				if ( options.range === true ) {
					if ( !options.values ) {
						options.values = [ this._valueMin(), this._valueMin() ];
					} else if ( options.values.length && options.values.length !== 2 ) {
						options.values = [ options.values[0], options.values[0] ];
					} else if ( $.isArray( options.values ) ) {
						options.values = options.values.slice(0);
					}
				}

				if ( !this.range || !this.range.length ) {
					this.range = $( "<div></div>" )
						.appendTo( this.element );

					classes = "ui-slider-range" +
					// note: this isn't the most fittingly semantic framework class for this element,
					// but worked best visually with a variety of themes
					" ui-widget-header ui-corner-all";
				} else {
					this.range.removeClass( "ui-slider-range-min ui-slider-range-max" )
						// Handle range switching from true to min/max
						.css({
							"left": "",
							"bottom": ""
						});
				}

				this.range.addClass( classes +
					( ( options.range === "min" || options.range === "max" ) ? " ui-slider-range-" + options.range : "" ) );
			} else {
				if ( this.range ) {
					this.range.remove();
				}
				this.range = null;
			}
		},

		_setupEvents: function() {
			var elements = this.handles.add( this.range ).filter( "a" );
			this._off( elements );
			this._on( elements, this._handleEvents );
			this._hoverable( elements );
			this._focusable( elements );
		},

		_destroy: function() {
			this.handles.remove();
			if ( this.range ) {
				this.range.remove();
			}

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

			this._mouseDestroy();
		},

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

			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;
			this.handles.each(function( i ) {
				var thisDistance = Math.abs( normValue - that.values(i) );
				if (( distance > thisDistance ) ||
					( distance === thisDistance &&
						(i === that._lastChangedValue || that.values(i) === o.min ))) {
					distance = thisDistance;
					closestHandle = $( this );
					index = i;
				}
			});

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

			this._handleIndex = index;

			closestHandle
				.addClass( "ui-state-active" )
				.focus();

			offset = closestHandle.offset();
			mouseOverHandle = !$( event.target ).parents().addBack().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)
			};

			if ( !this.handles.hasClass( "ui-state-hover" ) ) {
				this._slide( event, index, normValue );
			}
			this._animateOff = true;
			return true;
		},

		_mouseStart: function() {
			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.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 );
					}
				}
			} 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();
				}

				//store the last changed value index for reference when handles overlap
				this._lastChangedValue = index;

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

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

			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 );
				return;
			}

			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 ( key === "range" && this.options.range === true ) {
				if ( value === "min" ) {
					this.options.value = this._values( 0 );
					this.options.values = null;
				} else if ( value === "max" ) {
					this.options.value = this._values( this.options.values.length-1 );
					this.options.values = null;
				}
			}

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

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

			switch ( key ) {
				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;
				case "min":
				case "max":
					this._animateOff = true;
					this._refreshValue();
					this._animateOff = false;
					break;
				case "range":
					this._animateOff = true;
					this._refresh();
					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 if ( this.options.values && this.options.values.length ) {
				// .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;
			} else {
				return [];
			}
		},

		// 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 > 0 ) ? this.options.step : 1,
				valModStep = (val - this._valueMin()) % step,
				alignValue = val - valModStep;

			if ( Math.abs(valModStep) * 2 >= step ) {
				alignValue += ( valModStep > 0 ) ? step : ( -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 lastValPercent, valPercent, value, valueMin, valueMax,
				oRange = this.options.range,
				o = this.options,
				that = this,
				animate = ( !this._animateOff ) ? o.animate : false,
				_set = {};

			if ( this.options.values && this.options.values.length ) {
				this.handles.each(function( i ) {
					valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100;
					_set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
					$( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
					if ( that.options.range === true ) {
						if ( that.orientation === "horizontal" ) {
							if ( i === 0 ) {
								that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
							}
							if ( i === 1 ) {
								that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
							}
						} else {
							if ( i === 0 ) {
								that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
							}
							if ( i === 1 ) {
								that.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[ this.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 } );
				}
			}
		},

		_handleEvents: {
			keydown: function( event ) {
				var allowed, curVal, newVal, step,
					index = $( event.target ).data( "ui-slider-handle-index" );

				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:
						event.preventDefault();
						if ( !this._keySliding ) {
							this._keySliding = true;
							$( event.target ).addClass( "ui-state-active" );
							allowed = this._start( event, index );
							if ( allowed === false ) {
								return;
							}
						}
						break;
				}

				step = this.options.step;
				if ( this.options.values && this.options.values.length ) {
					curVal = newVal = this.values( index );
				} else {
					curVal = newVal = this.value();
				}

				switch ( event.keyCode ) {
					case $.ui.keyCode.HOME:
						newVal = this._valueMin();
						break;
					case $.ui.keyCode.END:
						newVal = this._valueMax();
						break;
					case $.ui.keyCode.PAGE_UP:
						newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) );
						break;
					case $.ui.keyCode.PAGE_DOWN:
						newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) );
						break;
					case $.ui.keyCode.UP:
					case $.ui.keyCode.RIGHT:
						if ( curVal === this._valueMax() ) {
							return;
						}
						newVal = this._trimAlignValue( curVal + step );
						break;
					case $.ui.keyCode.DOWN:
					case $.ui.keyCode.LEFT:
						if ( curVal === this._valueMin() ) {
							return;
						}
						newVal = this._trimAlignValue( curVal - step );
						break;
				}

				this._slide( event, index, newVal );
			},
			click: function( event ) {
				event.preventDefault();
			},
			keyup: function( event ) {
				var index = $( event.target ).data( "ui-slider-handle-index" );

				if ( this._keySliding ) {
					this._keySliding = false;
					this._stop( event, index );
					this._change( event, index );
					$( event.target ).removeClass( "ui-state-active" );
				}
			}
		}

	});

	}(jQuery));
	(function( $, undefined ) {

	function isOverAxis( x, reference, size ) {
		return ( x > reference ) && ( x < ( reference + size ) );
	}

	function isFloating(item) {
		return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display"));
	}

	$.widget("ui.sortable", $.ui.mouse, {
		version: "1.10.4",
		widgetEventPrefix: "sort",
		ready: false,
		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,

			// callbacks
			activate: null,
			beforeStop: null,
			change: null,
			deactivate: null,
			out: null,
			over: null,
			receive: null,
			remove: null,
			sort: null,
			start: null,
			stop: null,
			update: null
		},
		_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 being displayed horizontally
			this.floating = this.items.length ? o.axis === "x" || isFloating(this.items[0].item) : false;

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

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

			//We're ready to go
			this.ready = true;

		},

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

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

			return this;
		},

		_setOption: function(key, value){
			if ( key === "disabled" ) {
				this.options[ key ] = value;

				this.widget().toggleClass( "ui-sortable-disabled", !!value );
			} 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) {
			var currentItem = null,
				validHandle = false,
				that = this;

			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
			$(event.target).parents().each(function() {
				if($.data(this, that.widgetName + "-item") === that) {
					currentItem = $(this);
					return false;
				}
			});
			if($.data(event.target, that.widgetName + "-item") === that) {
				currentItem = $(event.target);
			}

			if(!currentItem) {
				return false;
			}
			if(this.options.handle && !overrideHandle) {
				$(this.options.handle, currentItem).find("*").addBack().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 i, body,
				o = this.options;

			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
			};

			$.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
			});

			// 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");

			//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 && o.cursor !== "auto" ) { // cursor option
				body = this.document.find( "body" );

				// support: IE
				this.storedCursor = body.css( "cursor" );
				body.css( "cursor", o.cursor );

				this.storedStylesheet = $( "<style>*{ cursor: "+o.cursor+" !important; }</style>" ).appendTo( body );
			}

			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 ( i = this.containers.length - 1; i >= 0; i-- ) {
					this.containers[ i ]._trigger( "activate", event, this._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) {
			var i, item, itemElement, intersection,
				o = this.options,
				scrolled = false;

			//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) {
				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 (i = this.items.length - 1; i >= 0; i--) {

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

				// Only put the placeholder inside the current Container, skip all
				// items from other containers. This works because when moving
				// an item from one container to another the
				// currentContainer is switched before the placeholder is moved.
				//
				// Without this, moving items in "sub-sortables" can cause
				// the placeholder to jitter beetween the outer and inner container.
				if (item.instance !== this.currentContainer) {
					continue;
				}

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

					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 that = this,
					cur = this.placeholder.offset(),
					axis = this.options.axis,
					animation = {};

				if ( !axis || axis === "x" ) {
					animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft);
				}
				if ( !axis || axis === "y" ) {
					animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop);
				}
				this.reverting = true;
				$(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() {
					that._clear(event);
				});
			} else {
				this._clear(event, noPropagation);
			}

			return false;

		},

		cancel: function() {

			if(this.dragging) {

				this._mouseUp({ target: null });

				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, this._uiHash(this));
					if(this.containers[i].containerCache.over) {
						this.containers[i]._trigger("out", null, this._uiHash(this));
						this.containers[i].containerCache.over = 0;
					}
				}

			}

			if (this.placeholder) {
				//$(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),
				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]));
				}
			});

			if(!str.length && o.key) {
				str.push(o.key + "=");
			}

			return str.join("&");

		},

		toArray: function(o) {

			var items = this._getItemsAsjQuery(o && o.connected),
				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,
				l = item.left,
				r = l + item.width,
				t = item.top,
				b = t + item.height,
				dyClick = this.offset.click.top,
				dxClick = this.offset.click.left,
				isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ),
				isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ),
				isOverElement = isOverElementHeight && isOverElementWidth;

			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 = (this.options.axis === "x") || isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
				isOverElementWidth = (this.options.axis === "y") || 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 = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
				isOverRightHalf = 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 i, j, cur, inst,
				items = [],
				queries = [],
				connectWith = this._connectWith();

			if(connectWith && connected) {
				for (i = connectWith.length - 1; i >= 0; i--){
					cur = $(connectWith[i]);
					for ( j = cur.length - 1; j >= 0; j--){
						inst = $.data(cur[j], this.widgetFullName);
						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]);

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

			return $(items);

		},

		_removeCurrentsFromItems: function() {

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

			this.items = $.grep(this.items, function (item) {
				for (var j=0; j < list.length; j++) {
					if(list[j] === item.item[0]) {
						return false;
					}
				}
				return true;
			});

		},

		_refreshItems: function(event) {

			this.items = [];
			this.containers = [this];

			var i, j, cur, inst, targetData, _queries, item, queriesLength,
				items = this.items,
				queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]],
				connectWith = this._connectWith();

			if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
				for (i = connectWith.length - 1; i >= 0; i--){
					cur = $(connectWith[i]);
					for (j = cur.length - 1; j >= 0; j--){
						inst = $.data(cur[j], this.widgetFullName);
						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 (i = queries.length - 1; i >= 0; i--) {
				targetData = queries[i][1];
				_queries = queries[i][0];

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

					item.data(this.widgetName + "-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();
			}

			var i, item, t, p;

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

				//We ignore calculating positions of all connected containers when we're not over them
				if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) {
					continue;
				}

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

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

				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 (i = this.containers.length - 1; i >= 0; i--){
					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) {
			that = that || this;
			var className,
				o = that.options;

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

						var nodeName = that.currentItem[0].nodeName.toLowerCase(),
							element = $( "<" + nodeName + ">", that.document[0] )
								.addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
								.removeClass("ui-sortable-helper");

						if ( nodeName === "tr" ) {
							that.currentItem.children().each(function() {
								$( "<td>&#160;</td>", that.document[0] )
									.attr( "colspan", $( this ).attr( "colspan" ) || 1 )
									.appendTo( element );
							});
						} else if ( nodeName === "img" ) {
							element.attr( "src", that.currentItem.attr( "src" ) );
						}

						if ( !className ) {
							element.css( "visibility", "hidden" );
						}

						return element;
					},
					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(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); }
						if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); }
					}
				};
			}

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

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

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

		},

		_contactContainers: function(event) {
			var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, base, cur, nearBottom, floating,
				innermostContainer = null,
				innermostIndex = null;

			// get innermost container that intersects with item
			for (i = this.containers.length - 1; i >= 0; i--) {

				// never consider a container that's located within the item itself
				if($.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 && $.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) {
				if (!this.containers[innermostIndex].containerCache.over) {
					this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
					this.containers[innermostIndex].containerCache.over = 1;
				}
			} else {

				//When entering a new container, we will find the item with the least distance and append our item near it
				dist = 10000;
				itemWithLeastDistance = null;
				floating = innermostContainer.floating || isFloating(this.currentItem);
				posProperty = floating ? "left" : "top";
				sizeProperty = floating ? "width" : "height";
				base = this.positionAbs[posProperty] + this.offset.click[posProperty];
				for (j = this.items.length - 1; j >= 0; j--) {
					if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) {
						continue;
					}
					if(this.items[j].item[0] === this.currentItem[0]) {
						continue;
					}
					if (floating && !isOverAxis(this.positionAbs.top + this.offset.click.top, this.items[j].top, this.items[j].height)) {
						continue;
					}
					cur = this.items[j].item.offset()[posProperty];
					nearBottom = false;
					if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){
						nearBottom = true;
						cur += this.items[j][sizeProperty];
					}

					if(Math.abs(cur - base) < dist) {
						dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
						this.direction = nearBottom ? "up": "down";
					}
				}

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

				if(this.currentContainer === this.containers[innermostIndex]) {
					return;
				}

				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));
				this.currentContainer = this.containers[innermostIndex];

				//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,
				helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem);

			//Add the helper to the DOM if that didn't happen already
			if(!helper.parents("body").length) {
				$(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 && $.contains(this.scrollParent[0], this.offsetParent[0])) {
				po.left += this.scrollParent.scrollLeft();
				po.top += this.scrollParent.scrollTop();
			}

			// This needs to be actually done for all browsers, since pageX/pageY includes this information
			// with an ugly IE fix
			if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
				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 ce, co, over,
				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)) {
				ce = $(o.containment)[0];
				co = $(o.containment).offset();
				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,
				scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.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)
					( ( 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)
					( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
				)
			};

		},

		_generatePosition: function(event) {

			var top, left,
				o = this.options,
				pageX = event.pageX,
				pageY = event.pageY,
				scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.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();
			}

			/*
			 * - 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) {
					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;

					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)
					( ( 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)
					( ( 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 counter = this.counter;

			this._delay(function() {
				if(counter === this.counter) {
					this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
				}
			});

		},

		_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 i,
				delayedTriggers = [];

			// 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.parent().length) {
				this.placeholder.before(this.currentItem);
			}
			this._noFinalSort = null;

			if(this.helper[0] === this.currentItem[0]) {
				for(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
			}

			// Check if the items Container has Changed and trigger appropriate
			// events.
			if (this !== this.currentContainer) {
				if(!noPropagation) {
					delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
					delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); };  }).call(this, this.currentContainer));
					delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this));  }; }).call(this, this.currentContainer));
				}
			}


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

			//Do what was originally in plugins
			if ( this.storedCursor ) {
				this.document.find( "body" ).css( "cursor", this.storedCursor );
				this.storedStylesheet.remove();
			}
			if(this._storedOpacity) {
				this.helper.css("opacity", this._storedOpacity);
			}
			if(this._storedZIndex) {
				this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex);
			}

			this.dragging = false;
			if(this.cancelHelperRemoval) {
				if(!noPropagation) {
					this._trigger("beforeStop", event, this._uiHash());
					for (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 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 (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 inst = _inst || this;
			return {
				helper: inst.helper,
				placeholder: inst.placeholder || $([]),
				position: inst.position,
				originalPosition: inst.originalPosition,
				offset: inst.positionAbs,
				item: inst.currentItem,
				sender: _inst ? _inst.element : null
			};
		}

	});

	})(jQuery);
	(function( $ ) {

	function modifier( fn ) {
		return function() {
			var previous = this.element.val();
			fn.apply( this, arguments );
			this._refresh();
			if ( previous !== this.element.val() ) {
				this._trigger( "change" );
			}
		};
	}

	$.widget( "ui.spinner", {
		version: "1.10.4",
		defaultElement: "<input>",
		widgetEventPrefix: "spin",
		options: {
			culture: null,
			icons: {
				down: "ui-icon-triangle-1-s",
				up: "ui-icon-triangle-1-n"
			},
			incremental: true,
			max: null,
			min: null,
			numberFormat: null,
			page: 10,
			step: 1,

			change: null,
			spin: null,
			start: null,
			stop: null
		},

		_create: function() {
			// handle string values that need to be parsed
			this._setOption( "max", this.options.max );
			this._setOption( "min", this.options.min );
			this._setOption( "step", this.options.step );

			// Only format if there is a value, prevents the field from being marked
			// as invalid in Firefox, see #9573.
			if ( this.value() !== "" ) {
				// Format the value, but don't constrain.
				this._value( this.element.val(), true );
			}

			this._draw();
			this._on( this._events );
			this._refresh();

			// turning off autocomplete prevents the browser from remembering the
			// value when navigating through history, so we re-enable autocomplete
			// if the page is unloaded before the widget is destroyed. #7790
			this._on( this.window, {
				beforeunload: function() {
					this.element.removeAttr( "autocomplete" );
				}
			});
		},

		_getCreateOptions: function() {
			var options = {},
				element = this.element;

			$.each( [ "min", "max", "step" ], function( i, option ) {
				var value = element.attr( option );
				if ( value !== undefined && value.length ) {
					options[ option ] = value;
				}
			});

			return options;
		},

		_events: {
			keydown: function( event ) {
				if ( this._start( event ) && this._keydown( event ) ) {
					event.preventDefault();
				}
			},
			keyup: "_stop",
			focus: function() {
				this.previous = this.element.val();
			},
			blur: function( event ) {
				if ( this.cancelBlur ) {
					delete this.cancelBlur;
					return;
				}

				this._stop();
				this._refresh();
				if ( this.previous !== this.element.val() ) {
					this._trigger( "change", event );
				}
			},
			mousewheel: function( event, delta ) {
				if ( !delta ) {
					return;
				}
				if ( !this.spinning && !this._start( event ) ) {
					return false;
				}

				this._spin( (delta > 0 ? 1 : -1) * this.options.step, event );
				clearTimeout( this.mousewheelTimer );
				this.mousewheelTimer = this._delay(function() {
					if ( this.spinning ) {
						this._stop( event );
					}
				}, 100 );
				event.preventDefault();
			},
			"mousedown .ui-spinner-button": function( event ) {
				var previous;

				// We never want the buttons to have focus; whenever the user is
				// interacting with the spinner, the focus should be on the input.
				// If the input is focused then this.previous is properly set from
				// when the input first received focus. If the input is not focused
				// then we need to set this.previous based on the value before spinning.
				previous = this.element[0] === this.document[0].activeElement ?
					this.previous : this.element.val();
				function checkFocus() {
					var isActive = this.element[0] === this.document[0].activeElement;
					if ( !isActive ) {
						this.element.focus();
						this.previous = previous;
						// support: IE
						// IE sets focus asynchronously, so we need to check if focus
						// moved off of the input because the user clicked on the button.
						this._delay(function() {
							this.previous = previous;
						});
					}
				}

				// ensure focus is on (or stays on) the text field
				event.preventDefault();
				checkFocus.call( this );

				// support: IE
				// IE doesn't prevent moving focus even with event.preventDefault()
				// so we set a flag to know when we should ignore the blur event
				// and check (again) if focus moved off of the input.
				this.cancelBlur = true;
				this._delay(function() {
					delete this.cancelBlur;
					checkFocus.call( this );
				});

				if ( this._start( event ) === false ) {
					return;
				}

				this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
			},
			"mouseup .ui-spinner-button": "_stop",
			"mouseenter .ui-spinner-button": function( event ) {
				// button will add ui-state-active if mouse was down while mouseleave and kept down
				if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) {
					return;
				}

				if ( this._start( event ) === false ) {
					return false;
				}
				this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
			},
			// TODO: do we really want to consider this a stop?
			// shouldn't we just stop the repeater and wait until mouseup before
			// we trigger the stop event?
			"mouseleave .ui-spinner-button": "_stop"
		},

		_draw: function() {
			var uiSpinner = this.uiSpinner = this.element
				.addClass( "ui-spinner-input" )
				.attr( "autocomplete", "off" )
				.wrap( this._uiSpinnerHtml() )
				.parent()
					// add buttons
					.append( this._buttonHtml() );

			this.element.attr( "role", "spinbutton" );

			// button bindings
			this.buttons = uiSpinner.find( ".ui-spinner-button" )
				.attr( "tabIndex", -1 )
				.button()
				.removeClass( "ui-corner-all" );

			// IE 6 doesn't understand height: 50% for the buttons
			// unless the wrapper has an explicit height
			if ( this.buttons.height() > Math.ceil( uiSpinner.height() * 0.5 ) &&
					uiSpinner.height() > 0 ) {
				uiSpinner.height( uiSpinner.height() );
			}

			// disable spinner if element was already disabled
			if ( this.options.disabled ) {
				this.disable();
			}
		},

		_keydown: function( event ) {
			var options = this.options,
				keyCode = $.ui.keyCode;

			switch ( event.keyCode ) {
			case keyCode.UP:
				this._repeat( null, 1, event );
				return true;
			case keyCode.DOWN:
				this._repeat( null, -1, event );
				return true;
			case keyCode.PAGE_UP:
				this._repeat( null, options.page, event );
				return true;
			case keyCode.PAGE_DOWN:
				this._repeat( null, -options.page, event );
				return true;
			}

			return false;
		},

		_uiSpinnerHtml: function() {
			return "<span class='ui-spinner ui-widget ui-widget-content ui-corner-all'></span>";
		},

		_buttonHtml: function() {
			return "" +
				"<a class='ui-spinner-button ui-spinner-up ui-corner-tr'>" +
					"<span class='ui-icon " + this.options.icons.up + "'>&#9650;</span>" +
				"</a>" +
				"<a class='ui-spinner-button ui-spinner-down ui-corner-br'>" +
					"<span class='ui-icon " + this.options.icons.down + "'>&#9660;</span>" +
				"</a>";
		},

		_start: function( event ) {
			if ( !this.spinning && this._trigger( "start", event ) === false ) {
				return false;
			}

			if ( !this.counter ) {
				this.counter = 1;
			}
			this.spinning = true;
			return true;
		},

		_repeat: function( i, steps, event ) {
			i = i || 500;

			clearTimeout( this.timer );
			this.timer = this._delay(function() {
				this._repeat( 40, steps, event );
			}, i );

			this._spin( steps * this.options.step, event );
		},

		_spin: function( step, event ) {
			var value = this.value() || 0;

			if ( !this.counter ) {
				this.counter = 1;
			}

			value = this._adjustValue( value + step * this._increment( this.counter ) );

			if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false) {
				this._value( value );
				this.counter++;
			}
		},

		_increment: function( i ) {
			var incremental = this.options.incremental;

			if ( incremental ) {
				return $.isFunction( incremental ) ?
					incremental( i ) :
					Math.floor( i*i*i/50000 - i*i/500 + 17*i/200 + 1 );
			}

			return 1;
		},

		_precision: function() {
			var precision = this._precisionOf( this.options.step );
			if ( this.options.min !== null ) {
				precision = Math.max( precision, this._precisionOf( this.options.min ) );
			}
			return precision;
		},

		_precisionOf: function( num ) {
			var str = num.toString(),
				decimal = str.indexOf( "." );
			return decimal === -1 ? 0 : str.length - decimal - 1;
		},

		_adjustValue: function( value ) {
			var base, aboveMin,
				options = this.options;

			// make sure we're at a valid step
			// - find out where we are relative to the base (min or 0)
			base = options.min !== null ? options.min : 0;
			aboveMin = value - base;
			// - round to the nearest step
			aboveMin = Math.round(aboveMin / options.step) * options.step;
			// - rounding is based on 0, so adjust back to our base
			value = base + aboveMin;

			// fix precision from bad JS floating point math
			value = parseFloat( value.toFixed( this._precision() ) );

			// clamp the value
			if ( options.max !== null && value > options.max) {
				return options.max;
			}
			if ( options.min !== null && value < options.min ) {
				return options.min;
			}

			return value;
		},

		_stop: function( event ) {
			if ( !this.spinning ) {
				return;
			}

			clearTimeout( this.timer );
			clearTimeout( this.mousewheelTimer );
			this.counter = 0;
			this.spinning = false;
			this._trigger( "stop", event );
		},

		_setOption: function( key, value ) {
			if ( key === "culture" || key === "numberFormat" ) {
				var prevValue = this._parse( this.element.val() );
				this.options[ key ] = value;
				this.element.val( this._format( prevValue ) );
				return;
			}

			if ( key === "max" || key === "min" || key === "step" ) {
				if ( typeof value === "string" ) {
					value = this._parse( value );
				}
			}
			if ( key === "icons" ) {
				this.buttons.first().find( ".ui-icon" )
					.removeClass( this.options.icons.up )
					.addClass( value.up );
				this.buttons.last().find( ".ui-icon" )
					.removeClass( this.options.icons.down )
					.addClass( value.down );
			}

			this._super( key, value );

			if ( key === "disabled" ) {
				if ( value ) {
					this.element.prop( "disabled", true );
					this.buttons.button( "disable" );
				} else {
					this.element.prop( "disabled", false );
					this.buttons.button( "enable" );
				}
			}
		},

		_setOptions: modifier(function( options ) {
			this._super( options );
			this._value( this.element.val() );
		}),

		_parse: function( val ) {
			if ( typeof val === "string" && val !== "" ) {
				val = window.Globalize && this.options.numberFormat ?
					Globalize.parseFloat( val, 10, this.options.culture ) : +val;
			}
			return val === "" || isNaN( val ) ? null : val;
		},

		_format: function( value ) {
			if ( value === "" ) {
				return "";
			}
			return window.Globalize && this.options.numberFormat ?
				Globalize.format( value, this.options.numberFormat, this.options.culture ) :
				value;
		},

		_refresh: function() {
			this.element.attr({
				"aria-valuemin": this.options.min,
				"aria-valuemax": this.options.max,
				// TODO: what should we do with values that can't be parsed?
				"aria-valuenow": this._parse( this.element.val() )
			});
		},

		// update the value without triggering change
		_value: function( value, allowAny ) {
			var parsed;
			if ( value !== "" ) {
				parsed = this._parse( value );
				if ( parsed !== null ) {
					if ( !allowAny ) {
						parsed = this._adjustValue( parsed );
					}
					value = this._format( parsed );
				}
			}
			this.element.val( value );
			this._refresh();
		},

		_destroy: function() {
			this.element
				.removeClass( "ui-spinner-input" )
				.prop( "disabled", false )
				.removeAttr( "autocomplete" )
				.removeAttr( "role" )
				.removeAttr( "aria-valuemin" )
				.removeAttr( "aria-valuemax" )
				.removeAttr( "aria-valuenow" );
			this.uiSpinner.replaceWith( this.element );
		},

		stepUp: modifier(function( steps ) {
			this._stepUp( steps );
		}),
		_stepUp: function( steps ) {
			if ( this._start() ) {
				this._spin( (steps || 1) * this.options.step );
				this._stop();
			}
		},

		stepDown: modifier(function( steps ) {
			this._stepDown( steps );
		}),
		_stepDown: function( steps ) {
			if ( this._start() ) {
				this._spin( (steps || 1) * -this.options.step );
				this._stop();
			}
		},

		pageUp: modifier(function( pages ) {
			this._stepUp( (pages || 1) * this.options.page );
		}),

		pageDown: modifier(function( pages ) {
			this._stepDown( (pages || 1) * this.options.page );
		}),

		value: function( newVal ) {
			if ( !arguments.length ) {
				return this._parse( this.element.val() );
			}
			modifier( this._value ).call( this, newVal );
		},

		widget: function() {
			return this.uiSpinner;
		}
	});

	}( jQuery ) );
	(function( $, undefined ) {

	var tabId = 0,
		rhash = /#.*$/;

	function getNextTabId() {
		return ++tabId;
	}

	function isLocal( anchor ) {
		// support: IE7
		// IE7 doesn't normalize the href property when set via script (#9317)
		anchor = anchor.cloneNode( false );

		return anchor.hash.length > 1 &&
			decodeURIComponent( anchor.href.replace( rhash, "" ) ) ===
				decodeURIComponent( location.href.replace( rhash, "" ) );
	}

	$.widget( "ui.tabs", {
		version: "1.10.4",
		delay: 300,
		options: {
			active: null,
			collapsible: false,
			event: "click",
			heightStyle: "content",
			hide: null,
			show: null,

			// callbacks
			activate: null,
			beforeActivate: null,
			beforeLoad: null,
			load: null
		},

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

			this.running = false;

			this.element
				.addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" )
				.toggleClass( "ui-tabs-collapsible", options.collapsible )
				// Prevent users from focusing disabled tabs via click
				.delegate( ".ui-tabs-nav > li", "mousedown" + this.eventNamespace, function( event ) {
					if ( $( this ).is( ".ui-state-disabled" ) ) {
						event.preventDefault();
					}
				})
				// support: IE <9
				// Preventing the default action in mousedown doesn't prevent IE
				// from focusing the element, so if the anchor gets focused, blur.
				// We don't have to worry about focusing the previously focused
				// element since clicking on a non-focusable element should focus
				// the body anyway.
				.delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() {
					if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
						this.blur();
					}
				});

			this._processTabs();
			options.active = this._initialActive();

			// Take disabling tabs via class attribute from HTML
			// into account and update option properly.
			if ( $.isArray( options.disabled ) ) {
				options.disabled = $.unique( options.disabled.concat(
					$.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
						return that.tabs.index( li );
					})
				) ).sort();
			}

			// check for length avoids error when initializing empty list
			if ( this.options.active !== false && this.anchors.length ) {
				this.active = this._findActive( options.active );
			} else {
				this.active = $();
			}

			this._refresh();

			if ( this.active.length ) {
				this.load( options.active );
			}
		},

		_initialActive: function() {
			var active = this.options.active,
				collapsible = this.options.collapsible,
				locationHash = location.hash.substring( 1 );

			if ( active === null ) {
				// check the fragment identifier in the URL
				if ( locationHash ) {
					this.tabs.each(function( i, tab ) {
						if ( $( tab ).attr( "aria-controls" ) === locationHash ) {
							active = i;
							return false;
						}
					});
				}

				// check for a tab marked active via a class
				if ( active === null ) {
					active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) );
				}

				// no active tab, set to false
				if ( active === null || active === -1 ) {
					active = this.tabs.length ? 0 : false;
				}
			}

			// handle numbers: negative, out of range
			if ( active !== false ) {
				active = this.tabs.index( this.tabs.eq( active ) );
				if ( active === -1 ) {
					active = collapsible ? false : 0;
				}
			}

			// don't allow collapsible: false and active: false
			if ( !collapsible && active === false && this.anchors.length ) {
				active = 0;
			}

			return active;
		},

		_getCreateEventData: function() {
			return {
				tab: this.active,
				panel: !this.active.length ? $() : this._getPanelForTab( this.active )
			};
		},

		_tabKeydown: function( event ) {
			var focusedTab = $( this.document[0].activeElement ).closest( "li" ),
				selectedIndex = this.tabs.index( focusedTab ),
				goingForward = true;

			if ( this._handlePageNav( event ) ) {
				return;
			}

			switch ( event.keyCode ) {
				case $.ui.keyCode.RIGHT:
				case $.ui.keyCode.DOWN:
					selectedIndex++;
					break;
				case $.ui.keyCode.UP:
				case $.ui.keyCode.LEFT:
					goingForward = false;
					selectedIndex--;
					break;
				case $.ui.keyCode.END:
					selectedIndex = this.anchors.length - 1;
					break;
				case $.ui.keyCode.HOME:
					selectedIndex = 0;
					break;
				case $.ui.keyCode.SPACE:
					// Activate only, no collapsing
					event.preventDefault();
					clearTimeout( this.activating );
					this._activate( selectedIndex );
					return;
				case $.ui.keyCode.ENTER:
					// Toggle (cancel delayed activation, allow collapsing)
					event.preventDefault();
					clearTimeout( this.activating );
					// Determine if we should collapse or activate
					this._activate( selectedIndex === this.options.active ? false : selectedIndex );
					return;
				default:
					return;
			}

			// Focus the appropriate tab, based on which key was pressed
			event.preventDefault();
			clearTimeout( this.activating );
			selectedIndex = this._focusNextTab( selectedIndex, goingForward );

			// Navigating with control key will prevent automatic activation
			if ( !event.ctrlKey ) {
				// Update aria-selected immediately so that AT think the tab is already selected.
				// Otherwise AT may confuse the user by stating that they need to activate the tab,
				// but the tab will already be activated by the time the announcement finishes.
				focusedTab.attr( "aria-selected", "false" );
				this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );

				this.activating = this._delay(function() {
					this.option( "active", selectedIndex );
				}, this.delay );
			}
		},

		_panelKeydown: function( event ) {
			if ( this._handlePageNav( event ) ) {
				return;
			}

			// Ctrl+up moves focus to the current tab
			if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
				event.preventDefault();
				this.active.focus();
			}
		},

		// Alt+page up/down moves focus to the previous/next tab (and activates)
		_handlePageNav: function( event ) {
			if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
				this._activate( this._focusNextTab( this.options.active - 1, false ) );
				return true;
			}
			if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
				this._activate( this._focusNextTab( this.options.active + 1, true ) );
				return true;
			}
		},

		_findNextTab: function( index, goingForward ) {
			var lastTabIndex = this.tabs.length - 1;

			function constrain() {
				if ( index > lastTabIndex ) {
					index = 0;
				}
				if ( index < 0 ) {
					index = lastTabIndex;
				}
				return index;
			}

			while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
				index = goingForward ? index + 1 : index - 1;
			}

			return index;
		},

		_focusNextTab: function( index, goingForward ) {
			index = this._findNextTab( index, goingForward );
			this.tabs.eq( index ).focus();
			return index;
		},

		_setOption: function( key, value ) {
			if ( key === "active" ) {
				// _activate() will handle invalid values and update this.options
				this._activate( value );
				return;
			}

			if ( key === "disabled" ) {
				// don't use the widget factory's disabled handling
				this._setupDisabled( value );
				return;
			}

			this._super( key, value);

			if ( key === "collapsible" ) {
				this.element.toggleClass( "ui-tabs-collapsible", value );
				// Setting collapsible: false while collapsed; open first panel
				if ( !value && this.options.active === false ) {
					this._activate( 0 );
				}
			}

			if ( key === "event" ) {
				this._setupEvents( value );
			}

			if ( key === "heightStyle" ) {
				this._setupHeightStyle( value );
			}
		},

		_tabId: function( tab ) {
			return tab.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId();
		},

		_sanitizeSelector: function( hash ) {
			return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
		},

		refresh: function() {
			var options = this.options,
				lis = this.tablist.children( ":has(a[href])" );

			// get disabled tabs from class attribute from HTML
			// this will get converted to a boolean if needed in _refresh()
			options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
				return lis.index( tab );
			});

			this._processTabs();

			// was collapsed or no tabs
			if ( options.active === false || !this.anchors.length ) {
				options.active = false;
				this.active = $();
			// was active, but active tab is gone
			} else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
				// all remaining tabs are disabled
				if ( this.tabs.length === options.disabled.length ) {
					options.active = false;
					this.active = $();
				// activate previous tab
				} else {
					this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
				}
			// was active, active tab still exists
			} else {
				// make sure active index is correct
				options.active = this.tabs.index( this.active );
			}

			this._refresh();
		},

		_refresh: function() {
			this._setupDisabled( this.options.disabled );
			this._setupEvents( this.options.event );
			this._setupHeightStyle( this.options.heightStyle );

			this.tabs.not( this.active ).attr({
				"aria-selected": "false",
				tabIndex: -1
			});
			this.panels.not( this._getPanelForTab( this.active ) )
				.hide()
				.attr({
					"aria-expanded": "false",
					"aria-hidden": "true"
				});

			// Make sure one tab is in the tab order
			if ( !this.active.length ) {
				this.tabs.eq( 0 ).attr( "tabIndex", 0 );
			} else {
				this.active
					.addClass( "ui-tabs-active ui-state-active" )
					.attr({
						"aria-selected": "true",
						tabIndex: 0
					});
				this._getPanelForTab( this.active )
					.show()
					.attr({
						"aria-expanded": "true",
						"aria-hidden": "false"
					});
			}
		},

		_processTabs: function() {
			var that = this;

			this.tablist = this._getList()
				.addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
				.attr( "role", "tablist" );

			this.tabs = this.tablist.find( "> li:has(a[href])" )
				.addClass( "ui-state-default ui-corner-top" )
				.attr({
					role: "tab",
					tabIndex: -1
				});

			this.anchors = this.tabs.map(function() {
					return $( "a", this )[ 0 ];
				})
				.addClass( "ui-tabs-anchor" )
				.attr({
					role: "presentation",
					tabIndex: -1
				});

			this.panels = $();

			this.anchors.each(function( i, anchor ) {
				var selector, panel, panelId,
					anchorId = $( anchor ).uniqueId().attr( "id" ),
					tab = $( anchor ).closest( "li" ),
					originalAriaControls = tab.attr( "aria-controls" );

				// inline tab
				if ( isLocal( anchor ) ) {
					selector = anchor.hash;
					panel = that.element.find( that._sanitizeSelector( selector ) );
				// remote tab
				} else {
					panelId = that._tabId( tab );
					selector = "#" + panelId;
					panel = that.element.find( selector );
					if ( !panel.length ) {
						panel = that._createPanel( panelId );
						panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
					}
					panel.attr( "aria-live", "polite" );
				}

				if ( panel.length) {
					that.panels = that.panels.add( panel );
				}
				if ( originalAriaControls ) {
					tab.data( "ui-tabs-aria-controls", originalAriaControls );
				}
				tab.attr({
					"aria-controls": selector.substring( 1 ),
					"aria-labelledby": anchorId
				});
				panel.attr( "aria-labelledby", anchorId );
			});

			this.panels
				.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
				.attr( "role", "tabpanel" );
		},

		// allow overriding how to find the list for rare usage scenarios (#7715)
		_getList: function() {
			return this.tablist || this.element.find( "ol,ul" ).eq( 0 );
		},

		_createPanel: function( id ) {
			return $( "<div>" )
				.attr( "id", id )
				.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
				.data( "ui-tabs-destroy", true );
		},

		_setupDisabled: function( disabled ) {
			if ( $.isArray( disabled ) ) {
				if ( !disabled.length ) {
					disabled = false;
				} else if ( disabled.length === this.anchors.length ) {
					disabled = true;
				}
			}

			// disable tabs
			for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) {
				if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
					$( li )
						.addClass( "ui-state-disabled" )
						.attr( "aria-disabled", "true" );
				} else {
					$( li )
						.removeClass( "ui-state-disabled" )
						.removeAttr( "aria-disabled" );
				}
			}

			this.options.disabled = disabled;
		},

		_setupEvents: function( event ) {
			var events = {
				click: function( event ) {
					event.preventDefault();
				}
			};
			if ( event ) {
				$.each( event.split(" "), function( index, eventName ) {
					events[ eventName ] = "_eventHandler";
				});
			}

			this._off( this.anchors.add( this.tabs ).add( this.panels ) );
			this._on( this.anchors, events );
			this._on( this.tabs, { keydown: "_tabKeydown" } );
			this._on( this.panels, { keydown: "_panelKeydown" } );

			this._focusable( this.tabs );
			this._hoverable( this.tabs );
		},

		_setupHeightStyle: function( heightStyle ) {
			var maxHeight,
				parent = this.element.parent();

			if ( heightStyle === "fill" ) {
				maxHeight = parent.height();
				maxHeight -= this.element.outerHeight() - this.element.height();

				this.element.siblings( ":visible" ).each(function() {
					var elem = $( this ),
						position = elem.css( "position" );

					if ( position === "absolute" || position === "fixed" ) {
						return;
					}
					maxHeight -= elem.outerHeight( true );
				});

				this.element.children().not( this.panels ).each(function() {
					maxHeight -= $( this ).outerHeight( true );
				});

				this.panels.each(function() {
					$( this ).height( Math.max( 0, maxHeight -
						$( this ).innerHeight() + $( this ).height() ) );
				})
				.css( "overflow", "auto" );
			} else if ( heightStyle === "auto" ) {
				maxHeight = 0;
				this.panels.each(function() {
					maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
				}).height( maxHeight );
			}
		},

		_eventHandler: function( event ) {
			var options = this.options,
				active = this.active,
				anchor = $( event.currentTarget ),
				tab = anchor.closest( "li" ),
				clickedIsActive = tab[ 0 ] === active[ 0 ],
				collapsing = clickedIsActive && options.collapsible,
				toShow = collapsing ? $() : this._getPanelForTab( tab ),
				toHide = !active.length ? $() : this._getPanelForTab( active ),
				eventData = {
					oldTab: active,
					oldPanel: toHide,
					newTab: collapsing ? $() : tab,
					newPanel: toShow
				};

			event.preventDefault();

			if ( tab.hasClass( "ui-state-disabled" ) ||
					// tab is already loading
					tab.hasClass( "ui-tabs-loading" ) ||
					// can't switch durning an animation
					this.running ||
					// click on active header, but not collapsible
					( clickedIsActive && !options.collapsible ) ||
					// allow canceling activation
					( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
				return;
			}

			options.active = collapsing ? false : this.tabs.index( tab );

			this.active = clickedIsActive ? $() : tab;
			if ( this.xhr ) {
				this.xhr.abort();
			}

			if ( !toHide.length && !toShow.length ) {
				$.error( "jQuery UI Tabs: Mismatching fragment identifier." );
			}

			if ( toShow.length ) {
				this.load( this.tabs.index( tab ), event );
			}
			this._toggle( event, eventData );
		},

		// handles show/hide for selecting tabs
		_toggle: function( event, eventData ) {
			var that = this,
				toShow = eventData.newPanel,
				toHide = eventData.oldPanel;

			this.running = true;

			function complete() {
				that.running = false;
				that._trigger( "activate", event, eventData );
			}

			function show() {
				eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );

				if ( toShow.length && that.options.show ) {
					that._show( toShow, that.options.show, complete );
				} else {
					toShow.show();
					complete();
				}
			}

			// start out by hiding, then showing, then completing
			if ( toHide.length && this.options.hide ) {
				this._hide( toHide, this.options.hide, function() {
					eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
					show();
				});
			} else {
				eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
				toHide.hide();
				show();
			}

			toHide.attr({
				"aria-expanded": "false",
				"aria-hidden": "true"
			});
			eventData.oldTab.attr( "aria-selected", "false" );
			// If we're switching tabs, remove the old tab from the tab order.
			// If we're opening from collapsed state, remove the previous tab from the tab order.
			// If we're collapsing, then keep the collapsing tab in the tab order.
			if ( toShow.length && toHide.length ) {
				eventData.oldTab.attr( "tabIndex", -1 );
			} else if ( toShow.length ) {
				this.tabs.filter(function() {
					return $( this ).attr( "tabIndex" ) === 0;
				})
				.attr( "tabIndex", -1 );
			}

			toShow.attr({
				"aria-expanded": "true",
				"aria-hidden": "false"
			});
			eventData.newTab.attr({
				"aria-selected": "true",
				tabIndex: 0
			});
		},

		_activate: function( index ) {
			var anchor,
				active = this._findActive( index );

			// trying to activate the already active panel
			if ( active[ 0 ] === this.active[ 0 ] ) {
				return;
			}

			// trying to collapse, simulate a click on the current active header
			if ( !active.length ) {
				active = this.active;
			}

			anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
			this._eventHandler({
				target: anchor,
				currentTarget: anchor,
				preventDefault: $.noop
			});
		},

		_findActive: function( index ) {
			return index === false ? $() : this.tabs.eq( index );
		},

		_getIndex: function( index ) {
			// meta-function to give users option to provide a href string instead of a numerical index.
			if ( typeof index === "string" ) {
				index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) );
			}

			return index;
		},

		_destroy: function() {
			if ( this.xhr ) {
				this.xhr.abort();
			}

			this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" );

			this.tablist
				.removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
				.removeAttr( "role" );

			this.anchors
				.removeClass( "ui-tabs-anchor" )
				.removeAttr( "role" )
				.removeAttr( "tabIndex" )
				.removeUniqueId();

			this.tabs.add( this.panels ).each(function() {
				if ( $.data( this, "ui-tabs-destroy" ) ) {
					$( this ).remove();
				} else {
					$( this )
						.removeClass( "ui-state-default ui-state-active ui-state-disabled " +
							"ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" )
						.removeAttr( "tabIndex" )
						.removeAttr( "aria-live" )
						.removeAttr( "aria-busy" )
						.removeAttr( "aria-selected" )
						.removeAttr( "aria-labelledby" )
						.removeAttr( "aria-hidden" )
						.removeAttr( "aria-expanded" )
						.removeAttr( "role" );
				}
			});

			this.tabs.each(function() {
				var li = $( this ),
					prev = li.data( "ui-tabs-aria-controls" );
				if ( prev ) {
					li
						.attr( "aria-controls", prev )
						.removeData( "ui-tabs-aria-controls" );
				} else {
					li.removeAttr( "aria-controls" );
				}
			});

			this.panels.show();

			if ( this.options.heightStyle !== "content" ) {
				this.panels.css( "height", "" );
			}
		},

		enable: function( index ) {
			var disabled = this.options.disabled;
			if ( disabled === false ) {
				return;
			}

			if ( index === undefined ) {
				disabled = false;
			} else {
				index = this._getIndex( index );
				if ( $.isArray( disabled ) ) {
					disabled = $.map( disabled, function( num ) {
						return num !== index ? num : null;
					});
				} else {
					disabled = $.map( this.tabs, function( li, num ) {
						return num !== index ? num : null;
					});
				}
			}
			this._setupDisabled( disabled );
		},

		disable: function( index ) {
			var disabled = this.options.disabled;
			if ( disabled === true ) {
				return;
			}

			if ( index === undefined ) {
				disabled = true;
			} else {
				index = this._getIndex( index );
				if ( $.inArray( index, disabled ) !== -1 ) {
					return;
				}
				if ( $.isArray( disabled ) ) {
					disabled = $.merge( [ index ], disabled ).sort();
				} else {
					disabled = [ index ];
				}
			}
			this._setupDisabled( disabled );
		},

		load: function( index, event ) {
			index = this._getIndex( index );
			var that = this,
				tab = this.tabs.eq( index ),
				anchor = tab.find( ".ui-tabs-anchor" ),
				panel = this._getPanelForTab( tab ),
				eventData = {
					tab: tab,
					panel: panel
				};

			// not remote
			if ( isLocal( anchor[ 0 ] ) ) {
				return;
			}

			this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );

			// support: jQuery <1.8
			// jQuery <1.8 returns false if the request is canceled in beforeSend,
			// but as of 1.8, $.ajax() always returns a jqXHR object.
			if ( this.xhr && this.xhr.statusText !== "canceled" ) {
				tab.addClass( "ui-tabs-loading" );
				panel.attr( "aria-busy", "true" );

				this.xhr
					.success(function( response ) {
						// support: jQuery <1.8
						// http://bugs.jquery.com/ticket/11778
						setTimeout(function() {
							panel.html( response );
							that._trigger( "load", event, eventData );
						}, 1 );
					})
					.complete(function( jqXHR, status ) {
						// support: jQuery <1.8
						// http://bugs.jquery.com/ticket/11778
						setTimeout(function() {
							if ( status === "abort" ) {
								that.panels.stop( false, true );
							}

							tab.removeClass( "ui-tabs-loading" );
							panel.removeAttr( "aria-busy" );

							if ( jqXHR === that.xhr ) {
								delete that.xhr;
							}
						}, 1 );
					});
			}
		},

		_ajaxSettings: function( anchor, event, eventData ) {
			var that = this;
			return {
				url: anchor.attr( "href" ),
				beforeSend: function( jqXHR, settings ) {
					return that._trigger( "beforeLoad", event,
						$.extend( { jqXHR : jqXHR, ajaxSettings: settings }, eventData ) );
				}
			};
		},

		_getPanelForTab: function( tab ) {
			var id = $( tab ).attr( "aria-controls" );
			return this.element.find( this._sanitizeSelector( "#" + id ) );
		}
	});

	})( jQuery );
	(function( $ ) {

	var increments = 0;

	function addDescribedBy( elem, id ) {
		var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
		describedby.push( id );
		elem
			.data( "ui-tooltip-id", id )
			.attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
	}

	function removeDescribedBy( elem ) {
		var id = elem.data( "ui-tooltip-id" ),
			describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
			index = $.inArray( id, describedby );
		if ( index !== -1 ) {
			describedby.splice( index, 1 );
		}

		elem.removeData( "ui-tooltip-id" );
		describedby = $.trim( describedby.join( " " ) );
		if ( describedby ) {
			elem.attr( "aria-describedby", describedby );
		} else {
			elem.removeAttr( "aria-describedby" );
		}
	}

	$.widget( "ui.tooltip", {
		version: "1.10.4",
		options: {
			content: function() {
				// support: IE<9, Opera in jQuery <1.7
				// .text() can't accept undefined, so coerce to a string
				var title = $( this ).attr( "title" ) || "";
				// Escape title, since we're going from an attribute to raw HTML
				return $( "<a>" ).text( title ).html();
			},
			hide: true,
			// Disabled elements have inconsistent behavior across browsers (#8661)
			items: "[title]:not([disabled])",
			position: {
				my: "left top+15",
				at: "left bottom",
				collision: "flipfit flip"
			},
			show: true,
			tooltipClass: null,
			track: false,

			// callbacks
			close: null,
			open: null
		},

		_create: function() {
			this._on({
				mouseover: "open",
				focusin: "open"
			});

			// IDs of generated tooltips, needed for destroy
			this.tooltips = {};
			// IDs of parent tooltips where we removed the title attribute
			this.parents = {};

			if ( this.options.disabled ) {
				this._disable();
			}
		},

		_setOption: function( key, value ) {
			var that = this;

			if ( key === "disabled" ) {
				this[ value ? "_disable" : "_enable" ]();
				this.options[ key ] = value;
				// disable element style changes
				return;
			}

			this._super( key, value );

			if ( key === "content" ) {
				$.each( this.tooltips, function( id, element ) {
					that._updateContent( element );
				});
			}
		},

		_disable: function() {
			var that = this;

			// close open tooltips
			$.each( this.tooltips, function( id, element ) {
				var event = $.Event( "blur" );
				event.target = event.currentTarget = element[0];
				that.close( event, true );
			});

			// remove title attributes to prevent native tooltips
			this.element.find( this.options.items ).addBack().each(function() {
				var element = $( this );
				if ( element.is( "[title]" ) ) {
					element
						.data( "ui-tooltip-title", element.attr( "title" ) )
						.attr( "title", "" );
				}
			});
		},

		_enable: function() {
			// restore title attributes
			this.element.find( this.options.items ).addBack().each(function() {
				var element = $( this );
				if ( element.data( "ui-tooltip-title" ) ) {
					element.attr( "title", element.data( "ui-tooltip-title" ) );
				}
			});
		},

		open: function( event ) {
			var that = this,
				target = $( event ? event.target : this.element )
					// we need closest here due to mouseover bubbling,
					// but always pointing at the same event target
					.closest( this.options.items );

			// No element to show a tooltip for or the tooltip is already open
			if ( !target.length || target.data( "ui-tooltip-id" ) ) {
				return;
			}

			if ( target.attr( "title" ) ) {
				target.data( "ui-tooltip-title", target.attr( "title" ) );
			}

			target.data( "ui-tooltip-open", true );

			// kill parent tooltips, custom or native, for hover
			if ( event && event.type === "mouseover" ) {
				target.parents().each(function() {
					var parent = $( this ),
						blurEvent;
					if ( parent.data( "ui-tooltip-open" ) ) {
						blurEvent = $.Event( "blur" );
						blurEvent.target = blurEvent.currentTarget = this;
						that.close( blurEvent, true );
					}
					if ( parent.attr( "title" ) ) {
						parent.uniqueId();
						that.parents[ this.id ] = {
							element: this,
							title: parent.attr( "title" )
						};
						parent.attr( "title", "" );
					}
				});
			}

			this._updateContent( target, event );
		},

		_updateContent: function( target, event ) {
			var content,
				contentOption = this.options.content,
				that = this,
				eventType = event ? event.type : null;

			if ( typeof contentOption === "string" ) {
				return this._open( event, target, contentOption );
			}

			content = contentOption.call( target[0], function( response ) {
				// ignore async response if tooltip was closed already
				if ( !target.data( "ui-tooltip-open" ) ) {
					return;
				}
				// IE may instantly serve a cached response for ajax requests
				// delay this call to _open so the other call to _open runs first
				that._delay(function() {
					// jQuery creates a special event for focusin when it doesn't
					// exist natively. To improve performance, the native event
					// object is reused and the type is changed. Therefore, we can't
					// rely on the type being correct after the event finished
					// bubbling, so we set it back to the previous value. (#8740)
					if ( event ) {
						event.type = eventType;
					}
					this._open( event, target, response );
				});
			});
			if ( content ) {
				this._open( event, target, content );
			}
		},

		_open: function( event, target, content ) {
			var tooltip, events, delayedShow,
				positionOption = $.extend( {}, this.options.position );

			if ( !content ) {
				return;
			}

			// Content can be updated multiple times. If the tooltip already
			// exists, then just update the content and bail.
			tooltip = this._find( target );
			if ( tooltip.length ) {
				tooltip.find( ".ui-tooltip-content" ).html( content );
				return;
			}

			// if we have a title, clear it to prevent the native tooltip
			// we have to check first to avoid defining a title if none exists
			// (we don't want to cause an element to start matching [title])
			//
			// We use removeAttr only for key events, to allow IE to export the correct
			// accessible attributes. For mouse events, set to empty string to avoid
			// native tooltip showing up (happens only when removing inside mouseover).
			if ( target.is( "[title]" ) ) {
				if ( event && event.type === "mouseover" ) {
					target.attr( "title", "" );
				} else {
					target.removeAttr( "title" );
				}
			}

			tooltip = this._tooltip( target );
			addDescribedBy( target, tooltip.attr( "id" ) );
			tooltip.find( ".ui-tooltip-content" ).html( content );

			function position( event ) {
				positionOption.of = event;
				if ( tooltip.is( ":hidden" ) ) {
					return;
				}
				tooltip.position( positionOption );
			}
			if ( this.options.track && event && /^mouse/.test( event.type ) ) {
				this._on( this.document, {
					mousemove: position
				});
				// trigger once to override element-relative positioning
				position( event );
			} else {
				tooltip.position( $.extend({
					of: target
				}, this.options.position ) );
			}

			tooltip.hide();

			this._show( tooltip, this.options.show );
			// Handle tracking tooltips that are shown with a delay (#8644). As soon
			// as the tooltip is visible, position the tooltip using the most recent
			// event.
			if ( this.options.show && this.options.show.delay ) {
				delayedShow = this.delayedShow = setInterval(function() {
					if ( tooltip.is( ":visible" ) ) {
						position( positionOption.of );
						clearInterval( delayedShow );
					}
				}, $.fx.interval );
			}

			this._trigger( "open", event, { tooltip: tooltip } );

			events = {
				keyup: function( event ) {
					if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
						var fakeEvent = $.Event(event);
						fakeEvent.currentTarget = target[0];
						this.close( fakeEvent, true );
					}
				},
				remove: function() {
					this._removeTooltip( tooltip );
				}
			};
			if ( !event || event.type === "mouseover" ) {
				events.mouseleave = "close";
			}
			if ( !event || event.type === "focusin" ) {
				events.focusout = "close";
			}
			this._on( true, target, events );
		},

		close: function( event ) {
			var that = this,
				target = $( event ? event.currentTarget : this.element ),
				tooltip = this._find( target );

			// disabling closes the tooltip, so we need to track when we're closing
			// to avoid an infinite loop in case the tooltip becomes disabled on close
			if ( this.closing ) {
				return;
			}

			// Clear the interval for delayed tracking tooltips
			clearInterval( this.delayedShow );

			// only set title if we had one before (see comment in _open())
			if ( target.data( "ui-tooltip-title" ) ) {
				target.attr( "title", target.data( "ui-tooltip-title" ) );
			}

			removeDescribedBy( target );

			tooltip.stop( true );
			this._hide( tooltip, this.options.hide, function() {
				that._removeTooltip( $( this ) );
			});

			target.removeData( "ui-tooltip-open" );
			this._off( target, "mouseleave focusout keyup" );
			// Remove 'remove' binding only on delegated targets
			if ( target[0] !== this.element[0] ) {
				this._off( target, "remove" );
			}
			this._off( this.document, "mousemove" );

			if ( event && event.type === "mouseleave" ) {
				$.each( this.parents, function( id, parent ) {
					$( parent.element ).attr( "title", parent.title );
					delete that.parents[ id ];
				});
			}

			this.closing = true;
			this._trigger( "close", event, { tooltip: tooltip } );
			this.closing = false;
		},

		_tooltip: function( element ) {
			var id = "ui-tooltip-" + increments++,
				tooltip = $( "<div>" )
					.attr({
						id: id,
						role: "tooltip"
					})
					.addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
						( this.options.tooltipClass || "" ) );
			$( "<div>" )
				.addClass( "ui-tooltip-content" )
				.appendTo( tooltip );
			tooltip.appendTo( this.document[0].body );
			this.tooltips[ id ] = element;
			return tooltip;
		},

		_find: function( target ) {
			var id = target.data( "ui-tooltip-id" );
			return id ? $( "#" + id ) : $();
		},

		_removeTooltip: function( tooltip ) {
			tooltip.remove();
			delete this.tooltips[ tooltip.attr( "id" ) ];
		},

		_destroy: function() {
			var that = this;

			// close open tooltips
			$.each( this.tooltips, function( id, element ) {
				// Delegate to close method to handle common cleanup
				var event = $.Event( "blur" );
				event.target = event.currentTarget = element[0];
				that.close( event, true );

				// Remove immediately; destroying an open tooltip doesn't use the
				// hide animation
				$( "#" + id ).remove();

				// Restore the title
				if ( element.data( "ui-tooltip-title" ) ) {
					element.attr( "title", element.data( "ui-tooltip-title" ) );
					element.removeData( "ui-tooltip-title" );
				}
			});
		}
	});

	}( jQuery ) );


	/* jQuery-Impromptu - v5.1.1
	 * http://trentrichardson.com/Impromptu
	 * Copyright (c) 2013 Trent Richardson; Licensed MIT 
	 */

	(function($) {

		/**
		* setDefaults - Sets the default options
		* @param message String/Object - String of html or Object of states
		* @param options Object - Options to set the prompt
		* @return jQuery - container with overlay and prompt 
		*/
		$.prompt = function(message, options) {
			// only for backwards compat, to be removed in future version
			if(options !== undefined && options.classes !== undefined && typeof options.classes === 'string'){
				options = { box: options.classes };
			}

			$.prompt.options = $.extend({},$.prompt.defaults,options);
			$.prompt.currentPrefix = $.prompt.options.prefix;

			// Be sure any previous timeouts are destroyed
			if($.prompt.timeout){
				clearTimeout($.prompt.timeout);
			}
			$.prompt.timeout = false;

			var opts = $.prompt.options,
				$body = $(document.body),
				$window = $(window);

			//build the box and fade
			var msgbox = '<div class="'+ $.prompt.options.prefix +'box '+ opts.classes.box +'">';
			if(opts.useiframe && ($('object, applet').length > 0)) {
				msgbox += '<iframe src="javascript:false;" style="display:block;position:absolute;z-index:-1;" class="'+ opts.prefix +'fade '+ opts.classes.fade +'"></iframe>';
			} else {
				msgbox +='<div class="'+ opts.prefix +'fade '+ opts.classes.fade +'"></div>';
			}
			msgbox += '<div class="'+ opts.prefix +' '+ opts.classes.prompt +'">'+
						'<form action="javascript:false;" onsubmit="return false;" class="'+ opts.prefix +'form">'+
							'<div class="'+ opts.prefix +'close '+ opts.classes.close +'">'+ opts.closeText +'</div>'+
							'<div class="'+ opts.prefix +'states"></div>'+
						'</form>'+
					'</div>'+
				'</div>';

			$.prompt.jqib = $(msgbox).appendTo($body);
			$.prompt.jqi = $.prompt.jqib.children('.'+ opts.prefix);//.data('jqi',opts);
			$.prompt.jqif = $.prompt.jqib.children('.'+ opts.prefix +'fade');

			//if a string was passed, convert to a single state
			if(message.constructor === String){
				message = {
					state0: {
						title: opts.title,
						html: message,
						buttons: opts.buttons,
						position: opts.position,
						focus: opts.focus,
						submit: opts.submit
					}
				};
			}

			//build the states
			$.prompt.options.states = {};
			var k,v;
			for(k in message){
				v = $.extend({},$.prompt.defaults.state,{name:k},message[k]);
				$.prompt.addState(v.name, v);

				if($.prompt.currentStateName === ''){
					$.prompt.currentStateName = v.name;
				}
			}

			// Go ahead and transition to the first state. It won't be visible just yet though until we show the prompt
			var $firstState = $.prompt.jqi.find('.'+ opts.prefix +'states .'+ opts.prefix +'state').eq(0);
			$.prompt.goToState($firstState.data('jqi-name'));

			//Events
			$.prompt.jqi.on('click', '.'+ opts.prefix +'buttons button', function(e){
				var $t = $(this),
					$state = $t.parents('.'+ opts.prefix +'state'),
					stateobj = $.prompt.options.states[$state.data('jqi-name')],
					msg = $state.children('.'+ opts.prefix +'message'),
					clicked = stateobj.buttons[$t.text()] || stateobj.buttons[$t.html()],
					forminputs = {};

				// if for some reason we couldn't get the value
				if(clicked === undefined){
					for(var i in stateobj.buttons){
						if(stateobj.buttons[i].title === $t.text() || stateobj.buttons[i].title === $t.html()){
							clicked = stateobj.buttons[i].value;
						}
					}
				}

				//collect all form element values from all states.
				$.each($.prompt.jqi.children('form').serializeArray(),function(i,obj){
					if (forminputs[obj.name] === undefined) {
						forminputs[obj.name] = obj.value;
					} else if (typeof forminputs[obj.name] === Array || typeof forminputs[obj.name] === 'object') {
						forminputs[obj.name].push(obj.value);
					} else {
						forminputs[obj.name] = [forminputs[obj.name],obj.value];	
					} 
				});

				// trigger an event
				var promptsubmite = new $.Event('impromptu:submit');
				promptsubmite.stateName = stateobj.name;
				promptsubmite.state = $state;
				$state.trigger(promptsubmite, [clicked, msg, forminputs]);

				if(!promptsubmite.isDefaultPrevented()){
					$.prompt.close(true, clicked,msg,forminputs);
				}
			});

			// if the fade is clicked blink the prompt
			var fadeClicked = function(){
				if(opts.persistent){
					var offset = (opts.top.toString().indexOf('%') >= 0? ($window.height()*(parseInt(opts.top,10)/100)) : parseInt(opts.top,10)),
						top = parseInt($.prompt.jqi.css('top').replace('px',''),10) - offset;

					//$window.scrollTop(top);
					$('html,body').animate({ scrollTop: top }, 'fast', function(){
						var i = 0;
						$.prompt.jqib.addClass(opts.prefix +'warning');
						var intervalid = setInterval(function(){
							$.prompt.jqib.toggleClass(opts.prefix +'warning');
							if(i++ > 1){
								clearInterval(intervalid);
								$.prompt.jqib.removeClass(opts.prefix +'warning');
							}
						}, 100);
					});
				}
				else {
					$.prompt.close(true);
				}
			};

			// listen for esc or tab keys
			var keyPressEventHandler = function(e){
				var key = (window.event) ? event.keyCode : e.keyCode;

				//escape key closes
				if(key===27) {
					fadeClicked();	
				}

				//constrain tabs, tabs should iterate through the state and not leave
				if (key === 9){
					var $inputels = $('input,select,textarea,button',$.prompt.getCurrentState());
					var fwd = !e.shiftKey && e.target === $inputels[$inputels.length-1];
					var back = e.shiftKey && e.target === $inputels[0];
					if (fwd || back) {
						setTimeout(function(){ 
							if (!$inputels){
								return;
							}
							var el = $inputels[back===true ? $inputels.length-1 : 0];

							if (el){
								el.focus();
							}
						},10);
						return false;
					}
				}
			};

			$.prompt.position();
			$.prompt.style();

			$.prompt.jqif.click(fadeClicked);
			$window.resize({animate:false}, $.prompt.position);
			$.prompt.jqi.find('.'+ opts.prefix +'close').click($.prompt.close);
			$.prompt.jqib.on("keydown",keyPressEventHandler)
						.on('impromptu:loaded', opts.loaded)
						.on('impromptu:close', opts.close)
						.on('impromptu:statechanging', opts.statechanging)
						.on('impromptu:statechanged', opts.statechanged);

			// Show it
			$.prompt.jqif[opts.show](opts.overlayspeed);
			$.prompt.jqi[opts.show](opts.promptspeed, function(){
				$.prompt.jqib.trigger('impromptu:loaded');
			});

			// Timeout
			if(opts.timeout > 0){
				$.prompt.timeout = setTimeout(function(){ $.prompt.close(true); },opts.timeout);
			}

			return $.prompt.jqib;
		};

		$.prompt.defaults = {
			prefix:'jqi',
			classes: {
				box: '',
				fade: '',
				prompt: '',
				close: '',
				title: '',
				message: '',
				buttons: '',
				button: '',
				defaultButton: ''
			},
			title: '',
			closeText: '&times;',
			buttons: {
				Ok: true
			},
			loaded: function(e){},
			submit: function(e,v,m,f){},
			close: function(e,v,m,f){},
			statechanging: function(e, from, to){},
			statechanged: function(e, to){},
			opacity: 0.6,
			zIndex: 999,
			overlayspeed: 'slow',
			promptspeed: 'fast',
			show: 'fadeIn',
			focus: 0,
			defaultButton: 0,
			useiframe: false,
			top: '15%',
			position: { 
				container: null, 
				x: null, 
				y: null,
				arrow: null,
				width: null
			},
			persistent: true,
			timeout: 0,
			states: {},
			state: {
				name: null,
				title: '',
				html: '',
				buttons: {
					Ok: true
				},
				focus: 0,
				defaultButton: 0,
				position: { 
					container: null, 
					x: null, 
					y: null,
					arrow: null,
					width: null
				},
				submit: function(e,v,m,f){
					return true;
				}
			}
		};

		/**
		* currentPrefix String - At any time this show be the prefix 
		* of the current prompt ex: "jqi"
		*/
		$.prompt.currentPrefix = $.prompt.defaults.prefix;

		/**
		* currentStateName String - At any time this is the current state
		* of the current prompt ex: "state0"
		*/
		$.prompt.currentStateName = "";

		/**
		* setDefaults - Sets the default options
		* @param o Object - Options to set as defaults
		* @return void
		*/
		$.prompt.setDefaults = function(o) {
			$.prompt.defaults = $.extend({}, $.prompt.defaults, o);
		};

		/**
		* setStateDefaults - Sets the default options for a state
		* @param o Object - Options to set as defaults
		* @return void
		*/
		$.prompt.setStateDefaults = function(o) {
			$.prompt.defaults.state = $.extend({}, $.prompt.defaults.state, o);
		};

		/**
		* position - Repositions the prompt (Used internally)
		* @return void
		*/
		$.prompt.position = function(e){
			var restoreFx = $.fx.off,
				$state = $.prompt.getCurrentState(),
				stateObj = $.prompt.options.states[$state.data('jqi-name')],
				pos = stateObj? stateObj.position : undefined,
				$window = $(window),
				bodyHeight = document.body.scrollHeight, //$(document.body).outerHeight(true),
				windowHeight = $(window).height(),
				documentHeight = $(document).height(),
				height = bodyHeight > windowHeight ? bodyHeight : windowHeight,
				top = parseInt($window.scrollTop(),10) + ($.prompt.options.top.toString().indexOf('%') >= 0? 
						(windowHeight*(parseInt($.prompt.options.top,10)/100)) : parseInt($.prompt.options.top,10));

			// when resizing the window turn off animation
			if(e !== undefined && e.data.animate === false){
				$.fx.off = true;
			}

			$.prompt.jqib.css({
				position: "absolute",
				height: height,
				width: "100%",
				top: 0,
				left: 0,
				right: 0,
				bottom: 0
			});
			$.prompt.jqif.css({
				position: "fixed",
				height: height,
				width: "100%",
				top: 0,
				left: 0,
				right: 0,
				bottom: 0
			});

			// tour positioning
			if(pos && pos.container){
				var offset = $(pos.container).offset();

				if($.isPlainObject(offset) && offset.top !== undefined){
					$.prompt.jqi.css({
						position: "absolute"
					});
					$.prompt.jqi.animate({
						top: offset.top + pos.y,
						left: offset.left + pos.x,
						marginLeft: 0,
						width: (pos.width !== undefined)? pos.width : null
					});
					top = (offset.top + pos.y) - ($.prompt.options.top.toString().indexOf('%') >= 0? (windowHeight*(parseInt($.prompt.options.top,10)/100)) : parseInt($.prompt.options.top,10));
					$('html,body').animate({ scrollTop: top }, 'slow', 'swing', function(){});
				}
			}
			// custom state width animation
			else if(pos && pos.width){
				$.prompt.jqi.css({
						position: "absolute",
						left: '50%'
					});
				$.prompt.jqi.animate({
						top: pos.y || top,
						left: pos.x || '50%',
						marginLeft: ((pos.width/2)*-1),
						width: pos.width
					});
			}
			// standard prompt positioning
			else{
				$.prompt.jqi.css({
					position: "absolute",
					top: top,
					left: '50%',//$window.width()/2,
					marginLeft: (($.prompt.jqi.outerWidth(false)/2)*-1)
				});
			}

			// restore fx settings
			if(e !== undefined && e.data.animate === false){
				$.fx.off = restoreFx;
			}
		};

		/**
		* style - Restyles the prompt (Used internally)
		* @return void
		*/
		$.prompt.style = function(){
			$.prompt.jqif.css({
				zIndex: $.prompt.options.zIndex,
				display: "none",
				opacity: $.prompt.options.opacity
			});
			$.prompt.jqi.css({
				zIndex: $.prompt.options.zIndex+1,
				display: "none"
			});
			$.prompt.jqib.css({
				zIndex: $.prompt.options.zIndex
			});
		};

		/**
		* get - Get the prompt
		* @return jQuery - the prompt
		*/
		$.prompt.get = function(state) {
			return $('.'+ $.prompt.currentPrefix);
		};

		/**
		* addState - Injects a state into the prompt
		* @param statename String - Name of the state
		* @param stateobj Object - options for the state
		* @param afterState String - selector of the state to insert after
		* @return jQuery - the newly created state
		*/
		$.prompt.addState = function(statename, stateobj, afterState) {
			var state = "",
				$state = null,
				arrow = "",
				title = "",
				opts = $.prompt.options,
				$jqistates = $('.'+ $.prompt.currentPrefix +'states'),
				defbtn,k,v,i=0;

			stateobj = $.extend({},$.prompt.defaults.state, {name:statename}, stateobj);

			if(stateobj.position.arrow !== null){
				arrow = '<div class="'+ opts.prefix + 'arrow '+ opts.prefix + 'arrow'+ stateobj.position.arrow +'"></div>';
			}
			if(stateobj.title && stateobj.title !== ''){
				title = '<div class="lead '+ opts.prefix + 'title '+ opts.classes.title +'">'+  stateobj.title +'</div>';
			}
			state += '<div id="'+ opts.prefix +'state_'+ statename +'" class="'+ opts.prefix + 'state" data-jqi-name="'+ statename +'" style="display:none;">'+ 
						arrow + title +
						'<div class="'+ opts.prefix +'message '+ opts.classes.message +'">' + stateobj.html +'</div>'+
						'<div class="'+ opts.prefix +'buttons '+ opts.classes.buttons +'"'+ ($.isEmptyObject(stateobj.buttons)? 'style="display:none;"':'') +'>';

			for(k in stateobj.buttons){
				v = stateobj.buttons[k],
				defbtn = stateobj.focus === i || (isNaN(stateobj.focus) && stateobj.defaultButton === i) ? ($.prompt.currentPrefix + 'defaultbutton ' + opts.classes.defaultButton) : '';

				if(typeof v === 'object'){
					state += '<button class="'+ opts.classes.button +' '+ defbtn;

					if(typeof v.classes !== "undefined"){
						state += ' '+ ($.isArray(v.classes)? v.classes.join(' ') : v.classes) + ' ';
					}

					state += '" name="' + opts.prefix + '_' + statename + '_button' + v.title.replace(/[^a-z0-9]+/gi,'') + '" id="' + opts.prefix + '_' + statename + '_button' + v.title.replace(/[^a-z0-9]+/gi,'') + '" value="' + v.value + '">' + v.title + '</button>';

				} else {
					state += '<button class="'+ opts.classes.button +' '+ defbtn +'" name="' + opts.prefix + '_' + statename + '_button' + k + '" id="' + opts.prefix +  '_' + statename + '_button' + k + '" value="' + v + '">' + k + '</button>';

				}
				i++;
			}
			state += '</div></div>';

			$state = $(state);

			$state.on('impromptu:submit', stateobj.submit);

			if(afterState !== undefined){
				$jqistates.find('#'+ $.prompt.currentPrefix +'state_'+ afterState).after($state);
			}
			else{
				$jqistates.append($state);
			}

			$.prompt.options.states[statename] = stateobj;

			return $state;
		};

		/**
		* removeState - Removes a state from the promt
		* @param state String - Name of the state
		* @return Boolean - returns true on success, false on failure
		*/
		$.prompt.removeState = function(state) {
			var $state = $.prompt.getState(state),
				rm = function(){ $state.remove(); };

			if($state.length === 0){
				return false;
			}

			// transition away from it before deleting
			if($state.is(':visible')){
				if($state.next().length > 0){
					$.prompt.nextState(rm);
				}
				else{
					$.prompt.prevState(rm);
				}
			}
			else{
				$state.slideUp('slow', rm);
			}

			return true;
		};

		/**
		* getState - Get the state by its name
		* @param state String - Name of the state
		* @return jQuery - the state
		*/
		$.prompt.getState = function(state) {
			return $('#'+ $.prompt.currentPrefix +'state_'+ state);
		};
		$.prompt.getStateContent = function(state) {
			return $.prompt.getState(state);
		};

		/**
		* getCurrentState - Get the current visible state
		* @return jQuery - the current visible state
		*/
		$.prompt.getCurrentState = function() {
			return $.prompt.getState($.prompt.getCurrentStateName());
		};

		/**
		* getCurrentStateName - Get the name of the current visible state
		* @return String - the current visible state's name
		*/
		$.prompt.getCurrentStateName = function() {
			return $.prompt.currentStateName;
		};

		/**
		* goToState - Goto the specified state
		* @param state String - name of the state to transition to
		* @param subState Boolean - true to be a sub state within the currently open state
		* @param callback Function - called when the transition is complete
		* @return jQuery - the newly active state
		*/	
		$.prompt.goToState = function(state, subState, callback) {
			var $jqi = $.prompt.get(),
				jqiopts = $.prompt.options,
				$state = $.prompt.getState(state),
				stateobj = jqiopts.states[$state.data('jqi-name')],
				promptstatechanginge = new $.Event('impromptu:statechanging');

			// subState can be ommitted
			if(typeof subState === 'function'){
				callback = subState;
				subState = false;
			}

			$.prompt.jqib.trigger(promptstatechanginge, [$.prompt.getCurrentStateName(), state]);

			if(!promptstatechanginge.isDefaultPrevented() && $state.length > 0){
				$.prompt.jqi.find('.'+ $.prompt.currentPrefix +'parentstate').removeClass($.prompt.currentPrefix +'parentstate');

				if(subState){ // hide any open substates
					// get rid of any substates
					$.prompt.jqi.find('.'+ $.prompt.currentPrefix +'substate').not($state)
						.slideUp(jqiopts.promptspeed)
						.removeClass('.'+ $.prompt.currentPrefix +'substate')
						.find('.'+ $.prompt.currentPrefix +'arrow').hide();

					// add parent state class so it can be visible, but blocked
					$.prompt.jqi.find('.'+ $.prompt.currentPrefix +'state:visible').addClass($.prompt.currentPrefix +'parentstate');

					// add substate class so we know it will be smaller
					$state.addClass($.prompt.currentPrefix +'substate');
				}
				else{ // hide any open states
					$.prompt.jqi.find('.'+ $.prompt.currentPrefix +'state').not($state)
						.slideUp(jqiopts.promptspeed)
						.find('.'+ $.prompt.currentPrefix +'arrow').hide();
				}
				$.prompt.currentStateName = stateobj.name;

				$state.slideDown(jqiopts.promptspeed,function(){
					var $t = $(this);

					// if focus is a selector, find it, else its button index
					if(typeof(stateobj.focus) === 'string'){
						$t.find(stateobj.focus).eq(0).focus();
					}
					else{
						$t.find('.'+ $.prompt.currentPrefix +'defaultbutton').focus();
					}

					$t.find('.'+ $.prompt.currentPrefix +'arrow').show(jqiopts.promptspeed);

					if (typeof callback === 'function'){
						$.prompt.jqib.on('impromptu:statechanged', callback);
					}
					$.prompt.jqib.trigger('impromptu:statechanged', [state]);
					if (typeof callback === 'function'){
						$.prompt.jqib.off('impromptu:statechanged', callback);
					}
				});
				if(!subState){
					$.prompt.position();
				}
			}
			return $state;
		};

		/**
		* nextState - Transition to the next state
		* @param callback Function - called when the transition is complete
		* @return jQuery - the newly active state
		*/	
		$.prompt.nextState = function(callback) {
			var $next = $('#'+ $.prompt.currentPrefix +'state_'+ $.prompt.getCurrentStateName()).next();
			return $.prompt.goToState( $next.attr('id').replace($.prompt.currentPrefix +'state_',''), callback );
		};

		/**
		* prevState - Transition to the previous state
		* @param callback Function - called when the transition is complete
		* @return jQuery - the newly active state
		*/	
		$.prompt.prevState = function(callback) {
			var $prev = $('#'+ $.prompt.currentPrefix +'state_'+ $.prompt.getCurrentStateName()).prev();
			$.prompt.goToState( $prev.attr('id').replace($.prompt.currentPrefix +'state_',''), callback );
		};

		/**
		* close - Closes the prompt
		* @param callback Function - called when the transition is complete
		* @param clicked String - value of the button clicked (only used internally)
		* @param msg jQuery - The state message body (only used internally)
		* @param forvals Object - key/value pairs of all form field names and values (only used internally)
		* @return jQuery - the newly active state
		*/	
		$.prompt.close = function(callCallback, clicked, msg, formvals){
			if($.prompt.timeout){
				clearTimeout($.prompt.timeout);
				$.prompt.timeout = false;
			}

			$.prompt.jqib.fadeOut('fast',function(){

				if(callCallback) {
					$.prompt.jqib.trigger('impromptu:close', [clicked,msg,formvals]);
				}
				$.prompt.jqib.remove();

				$(window).off('resize',$.prompt.position);
			});
		};

		/**
		* Enable using $('.selector').prompt({});
		* This will grab the html within the prompt as the prompt message
		*/
		$.fn.prompt = function(options){
			if(options === undefined){
				options = {};
			}
			if(options.withDataAndEvents === undefined){
				options.withDataAndEvents = false;
			}

			$.prompt($(this).clone(options.withDataAndEvents).html(),options);
		};

	})(jQuery);


	/**
	 * Strophe
	 */

	var Base64 = (function() {
	    var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
	    var obj = {
		encode: function(input) {
		    var output = "";
		    var chr1, chr2, chr3;
		    var enc1, enc2, enc3, enc4;
		    var i = 0;
		    do {
			chr1 = input.charCodeAt(i++);
			chr2 = input.charCodeAt(i++);
			chr3 = input.charCodeAt(i++);
			enc1 = chr1 >> 2;
			enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
			enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
			enc4 = chr3 & 63;
			if (isNaN(chr2)) {
			    enc3 = enc4 = 64
			} else {
			    if (isNaN(chr3)) {
				enc4 = 64
			    }
			}
			output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4)
		    } while (i < input.length);
		    return output
		},
		decode: function(input) {
		    var output = "";
		    var chr1, chr2, chr3;
		    var enc1, enc2, enc3, enc4;
		    var i = 0;
		    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
		    do {
			enc1 = keyStr.indexOf(input.charAt(i++));
			enc2 = keyStr.indexOf(input.charAt(i++));
			enc3 = keyStr.indexOf(input.charAt(i++));
			enc4 = keyStr.indexOf(input.charAt(i++));
			chr1 = (enc1 << 2) | (enc2 >> 4);
			chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
			chr3 = ((enc3 & 3) << 6) | enc4;
			output = output + String.fromCharCode(chr1);
			if (enc3 != 64) {
			    output = output + String.fromCharCode(chr2)
			}
			if (enc4 != 64) {
			    output = output + String.fromCharCode(chr3)
			}
		    } while (i < input.length);
		    return output
		}
	    };
	    return obj
	})();

	function b64_sha1(s) {
	    return binb2b64(core_sha1(str2binb(s), s.length * 8))
	}

	function str_sha1(s) {
	    return binb2str(core_sha1(str2binb(s), s.length * 8))
	}

	function b64_hmac_sha1(key, data) {
	    return binb2b64(core_hmac_sha1(key, data))
	}

	function str_hmac_sha1(key, data) {
	    return binb2str(core_hmac_sha1(key, data))
	}

	function core_sha1(x, len) {
	    x[len >> 5] |= 128 << (24 - len % 32);
	    x[((len + 64 >> 9) << 4) + 15] = len;
	    var w = new Array(80);
	    var a = 1732584193;
	    var b = -271733879;
	    var c = -1732584194;
	    var d = 271733878;
	    var e = -1009589776;
	    var i, j, t, olda, oldb, oldc, oldd, olde;
	    for (i = 0; i < x.length; i += 16) {
		olda = a;
		oldb = b;
		oldc = c;
		oldd = d;
		olde = e;
		for (j = 0; j < 80; j++) {
		    if (j < 16) {
			w[j] = x[i + j]
		    } else {
			w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1)
		    }
		    t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), safe_add(safe_add(e, w[j]), sha1_kt(j)));
		    e = d;
		    d = c;
		    c = rol(b, 30);
		    b = a;
		    a = t
		}
		a = safe_add(a, olda);
		b = safe_add(b, oldb);
		c = safe_add(c, oldc);
		d = safe_add(d, oldd);
		e = safe_add(e, olde)
	    }
	    return [a, b, c, d, e]
	}

	function sha1_ft(t, b, c, d) {
	    if (t < 20) {
		return (b & c) | ((~b) & d)
	    }
	    if (t < 40) {
		return b ^ c ^ d
	    }
	    if (t < 60) {
		return (b & c) | (b & d) | (c & d)
	    }
	    return b ^ c ^ d
	}

	function sha1_kt(t) {
	    return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514
	}

	function core_hmac_sha1(key, data) {
	    var bkey = str2binb(key);
	    if (bkey.length > 16) {
		bkey = core_sha1(bkey, key.length * 8)
	    }
	    var ipad = new Array(16),
		opad = new Array(16);
	    for (var i = 0; i < 16; i++) {
		ipad[i] = bkey[i] ^ 909522486;
		opad[i] = bkey[i] ^ 1549556828
	    }
	    var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * 8);
	    return core_sha1(opad.concat(hash), 512 + 160)
	}

	function safe_add(x, y) {
	    var lsw = (x & 65535) + (y & 65535);
	    var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
	    return (msw << 16) | (lsw & 65535)
	}

	function rol(num, cnt) {
	    return (num << cnt) | (num >>> (32 - cnt))
	}

	function str2binb(str) {
	    var bin = [];
	    var mask = 255;
	    for (var i = 0; i < str.length * 8; i += 8) {
		bin[i >> 5] |= (str.charCodeAt(i / 8) & mask) << (24 - i % 32)
	    }
	    return bin
	}

	function binb2str(bin) {
	    var str = "";
	    var mask = 255;
	    for (var i = 0; i < bin.length * 32; i += 8) {
		str += String.fromCharCode((bin[i >> 5] >>> (24 - i % 32)) & mask)
	    }
	    return str
	}

	function binb2b64(binarray) {
	    var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	    var str = "";
	    var triplet, j;
	    for (var i = 0; i < binarray.length * 4; i += 3) {
		triplet = (((binarray[i >> 2] >> 8 * (3 - i % 4)) & 255) << 16) | (((binarray[i + 1 >> 2] >> 8 * (3 - (i + 1) % 4)) & 255) << 8) | ((binarray[i + 2 >> 2] >> 8 * (3 - (i + 2) % 4)) & 255);
		for (j = 0; j < 4; j++) {
		    if (i * 8 + j * 6 > binarray.length * 32) {
			str += "="
		    } else {
			str += tab.charAt((triplet >> 6 * (3 - j)) & 63)
		    }
		}
	    }
	    return str
	}
	var MD5 = (function() {
	    var safe_add = function(x, y) {
		var lsw = (x & 65535) + (y & 65535);
		var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
		return (msw << 16) | (lsw & 65535)
	    };
	    var bit_rol = function(num, cnt) {
		return (num << cnt) | (num >>> (32 - cnt))
	    };
	    var str2binl = function(str) {
		var bin = [];
		for (var i = 0; i < str.length * 8; i += 8) {
		    bin[i >> 5] |= (str.charCodeAt(i / 8) & 255) << (i % 32)
		}
		return bin
	    };
	    var binl2str = function(bin) {
		var str = "";
		for (var i = 0; i < bin.length * 32; i += 8) {
		    str += String.fromCharCode((bin[i >> 5] >>> (i % 32)) & 255)
		}
		return str
	    };
	    var binl2hex = function(binarray) {
		var hex_tab = "0123456789abcdef";
		var str = "";
		for (var i = 0; i < binarray.length * 4; i++) {
		    str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 15) + hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 15)
		}
		return str
	    };
	    var md5_cmn = function(q, a, b, x, s, t) {
		return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b)
	    };
	    var md5_ff = function(a, b, c, d, x, s, t) {
		return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t)
	    };
	    var md5_gg = function(a, b, c, d, x, s, t) {
		return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t)
	    };
	    var md5_hh = function(a, b, c, d, x, s, t) {
		return md5_cmn(b ^ c ^ d, a, b, x, s, t)
	    };
	    var md5_ii = function(a, b, c, d, x, s, t) {
		return md5_cmn(c ^ (b | (~d)), a, b, x, s, t)
	    };
	    var core_md5 = function(x, len) {
		x[len >> 5] |= 128 << ((len) % 32);
		x[(((len + 64) >>> 9) << 4) + 14] = len;
		var a = 1732584193;
		var b = -271733879;
		var c = -1732584194;
		var d = 271733878;
		var olda, oldb, oldc, oldd;
		for (var i = 0; i < x.length; i += 16) {
		    olda = a;
		    oldb = b;
		    oldc = c;
		    oldd = d;
		    a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936);
		    d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
		    c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
		    b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
		    a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
		    d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
		    c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
		    b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
		    a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
		    d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
		    c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
		    b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
		    a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
		    d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
		    c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
		    b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
		    a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
		    d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
		    c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
		    b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302);
		    a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
		    d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
		    c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
		    b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
		    a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
		    d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
		    c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
		    b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
		    a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
		    d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
		    c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
		    b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
		    a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
		    d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
		    c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
		    b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
		    a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
		    d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
		    c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
		    b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
		    a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
		    d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222);
		    c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
		    b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
		    a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
		    d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
		    c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
		    b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
		    a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844);
		    d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
		    c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
		    b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
		    a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
		    d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
		    c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
		    b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
		    a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
		    d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
		    c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
		    b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
		    a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
		    d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
		    c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
		    b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
		    a = safe_add(a, olda);
		    b = safe_add(b, oldb);
		    c = safe_add(c, oldc);
		    d = safe_add(d, oldd)
		}
		return [a, b, c, d]
	    };
	    var obj = {
		hexdigest: function(s) {
		    return binl2hex(core_md5(str2binl(s), s.length * 8))
		},
		hash: function(s) {
		    return binl2str(core_md5(str2binl(s), s.length * 8))
		}
	    };
	    return obj
	})();
	if (!Function.prototype.bind) {
	    Function.prototype.bind = function(obj) {
		var func = this;
		var _slice = Array.prototype.slice;
		var _concat = Array.prototype.concat;
		var _args = _slice.call(arguments, 1);
		return function() {
		    return func.apply(obj ? obj : this, _concat.call(_args, _slice.call(arguments, 0)))
		}
	    }
	}
	if (!Array.prototype.indexOf) {
	    Array.prototype.indexOf = function(elt) {
		var len = this.length;
		var from = Number(arguments[1]) || 0;
		from = (from < 0) ? Math.ceil(from) : Math.floor(from);
		if (from < 0) {
		    from += len
		}
		for (; from < len; from++) {
		    if (from in this && this[from] === elt) {
			return from
		    }
		}
		return -1
	    }
	}(function(callback) {
	    var Strophe;

	    function $build(name, attrs) {
		return new Strophe.Builder(name, attrs)
	    }

	    function $msg(attrs) {
		return new Strophe.Builder("message", attrs)
	    }

	    function $iq(attrs) {
		return new Strophe.Builder("iq", attrs)
	    }

	    function $pres(attrs) {
		return new Strophe.Builder("presence", attrs)
	    }
	    Strophe = {
		VERSION: "1.1.3",
		NS: {
		    HTTPBIND: "http://jabber.org/protocol/httpbind",
		    BOSH: "urn:xmpp:xbosh",
		    CLIENT: "jabber:client",
		    AUTH: "jabber:iq:auth",
		    ROSTER: "jabber:iq:roster",
		    PROFILE: "jabber:iq:profile",
		    DISCO_INFO: "http://jabber.org/protocol/disco#info",
		    DISCO_ITEMS: "http://jabber.org/protocol/disco#items",
		    MUC: "http://jabber.org/protocol/muc",
		    SASL: "urn:ietf:params:xml:ns:xmpp-sasl",
		    STREAM: "http://etherx.jabber.org/streams",
		    BIND: "urn:ietf:params:xml:ns:xmpp-bind",
		    SESSION: "urn:ietf:params:xml:ns:xmpp-session",
		    VERSION: "jabber:iq:version",
		    STANZAS: "urn:ietf:params:xml:ns:xmpp-stanzas",
		    XHTML_IM: "http://jabber.org/protocol/xhtml-im",
		    XHTML: "http://www.w3.org/1999/xhtml"
		},
		XHTML: {
		    tags: ["a", "blockquote", "br", "cite", "em", "img", "li", "ol", "p", "span", "strong", "ul", "body"],
		    attributes: {
			a: ["href"],
			blockquote: ["style"],
			br: [],
			cite: ["style"],
			em: [],
			img: ["src", "alt", "style", "height", "width"],
			li: ["style"],
			ol: ["style"],
			p: ["style"],
			span: ["style"],
			strong: [],
			ul: ["style"],
			body: []
		    },
		    css: ["background-color", "color", "font-family", "font-size", "font-style", "font-weight", "margin-left", "margin-right", "text-align", "text-decoration"],
		    validTag: function(tag) {
			for (var i = 0; i < Strophe.XHTML.tags.length; i++) {
			    if (tag == Strophe.XHTML.tags[i]) {
				return true
			    }
			}
			return false
		    },
		    validAttribute: function(tag, attribute) {
			if (typeof Strophe.XHTML.attributes[tag] !== "undefined" && Strophe.XHTML.attributes[tag].length > 0) {
			    for (var i = 0; i < Strophe.XHTML.attributes[tag].length; i++) {
				if (attribute == Strophe.XHTML.attributes[tag][i]) {
				    return true
				}
			    }
			}
			return false
		    },
		    validCSS: function(style) {
			for (var i = 0; i < Strophe.XHTML.css.length; i++) {
			    if (style == Strophe.XHTML.css[i]) {
				return true
			    }
			}
			return false
		    }
		},
		Status: {
		    ERROR: 0,
		    CONNECTING: 1,
		    CONNFAIL: 2,
		    AUTHENTICATING: 3,
		    AUTHFAIL: 4,
		    CONNECTED: 5,
		    DISCONNECTED: 6,
		    DISCONNECTING: 7,
		    ATTACHED: 8
		},
		LogLevel: {
		    DEBUG: 0,
		    INFO: 1,
		    WARN: 2,
		    ERROR: 3,
		    FATAL: 4
		},
		ElementType: {
		    NORMAL: 1,
		    TEXT: 3,
		    CDATA: 4,
		    FRAGMENT: 11
		},
		TIMEOUT: 1.1,
		SECONDARY_TIMEOUT: 0.1,
		addNamespace: function(name, value) {
		    Strophe.NS[name] = value
		},
		forEachChild: function(elem, elemName, func) {
		    var i, childNode;
		    for (i = 0; i < elem.childNodes.length; i++) {
			childNode = elem.childNodes[i];
			if (childNode.nodeType == Strophe.ElementType.NORMAL && (!elemName || this.isTagEqual(childNode, elemName))) {
			    func(childNode)
			}
		    }
		},
		isTagEqual: function(el, name) {
		    return el.tagName.toLowerCase() == name.toLowerCase()
		},
		_xmlGenerator: null,
		_makeGenerator: function() {
		    var doc;
		    if (document.implementation.createDocument === undefined || document.implementation.createDocument && document.documentMode && document.documentMode < 10) {
			doc = this._getIEXmlDom();
			doc.appendChild(doc.createElement("strophe"))
		    } else {
			doc = document.implementation.createDocument("jabber:client", "strophe", null)
		    }
		    return doc
		},
		xmlGenerator: function() {
		    if (!Strophe._xmlGenerator) {
			Strophe._xmlGenerator = Strophe._makeGenerator()
		    }
		    return Strophe._xmlGenerator
		},
		_getIEXmlDom: function() {
		    var doc = null;
		    var docStrings = ["Msxml2.DOMDocument.6.0", "Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "MSXML2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM"];
		    for (var d = 0; d < docStrings.length; d++) {
			if (doc === null) {
			    try {
				doc = new ActiveXObject(docStrings[d])
			    } catch (e) {
				doc = null
			    }
			} else {
			    break
			}
		    }
		    return doc
		},
		xmlElement: function(name) {
		    if (!name) {
			return null
		    }
		    var node = Strophe.xmlGenerator().createElement(name);
		    var a, i, k;
		    for (a = 1; a < arguments.length; a++) {
			if (!arguments[a]) {
			    continue
			}
			if (typeof(arguments[a]) == "string" || typeof(arguments[a]) == "number") {
			    node.appendChild(Strophe.xmlTextNode(arguments[a]))
			} else {
			    if (typeof(arguments[a]) == "object" && typeof(arguments[a].sort) == "function") {
				for (i = 0; i < arguments[a].length; i++) {
				    if (typeof(arguments[a][i]) == "object" && typeof(arguments[a][i].sort) == "function") {
					node.setAttribute(arguments[a][i][0], arguments[a][i][1])
				    }
				}
			    } else {
				if (typeof(arguments[a]) == "object") {
				    for (k in arguments[a]) {
					if (arguments[a].hasOwnProperty(k)) {
					    node.setAttribute(k, arguments[a][k])
					}
				    }
				}
			    }
			}
		    }
		    return node
		},
		xmlescape: function(text) {
		    text = text.replace(/\&/g, "&amp;");
		    text = text.replace(/</g, "&lt;");
		    text = text.replace(/>/g, "&gt;");
		    text = text.replace(/'/g, "&apos;");
		    text = text.replace(/"/g, "&quot;");
		    return text
		},
		xmlTextNode: function(text) {
		    return Strophe.xmlGenerator().createTextNode(text)
		},
		xmlHtmlNode: function(html) {
		    var node;
		    if (window.DOMParser) {
			var parser = new DOMParser();
			node = parser.parseFromString(html, "text/xml")
		    } else {
			node = new ActiveXObject("Microsoft.XMLDOM");
			node.async = "false";
			node.loadXML(html)
		    }
		    return node
		},
		getText: function(elem) {
		    if (!elem) {
			return null
		    }
		    var str = "";
		    if (elem.childNodes.length === 0 && elem.nodeType == Strophe.ElementType.TEXT) {
			str += elem.nodeValue
		    }
		    for (var i = 0; i < elem.childNodes.length; i++) {
			if (elem.childNodes[i].nodeType == Strophe.ElementType.TEXT) {
			    str += elem.childNodes[i].nodeValue
			}
		    }
		    return Strophe.xmlescape(str)
		},
		copyElement: function(elem) {
		    var i, el;
		    if (elem.nodeType == Strophe.ElementType.NORMAL) {
			el = Strophe.xmlElement(elem.tagName);
			for (i = 0; i < elem.attributes.length; i++) {
			    el.setAttribute(elem.attributes[i].nodeName.toLowerCase(), elem.attributes[i].value)
			}
			for (i = 0; i < elem.childNodes.length; i++) {
			    el.appendChild(Strophe.copyElement(elem.childNodes[i]))
			}
		    } else {
			if (elem.nodeType == Strophe.ElementType.TEXT) {
			    el = Strophe.xmlGenerator().createTextNode(elem.nodeValue)
			}
		    }
		    return el
		},
		createHtml: function(elem) {
		    var i, el, j, tag, attribute, value, css, cssAttrs, attr, cssName, cssValue;
		    if (elem.nodeType == Strophe.ElementType.NORMAL) {
			tag = elem.nodeName.toLowerCase();
			if (Strophe.XHTML.validTag(tag)) {
			    try {
				el = Strophe.xmlElement(tag);
				for (i = 0; i < Strophe.XHTML.attributes[tag].length; i++) {
				    attribute = Strophe.XHTML.attributes[tag][i];
				    value = elem.getAttribute(attribute);
				    if (typeof value == "undefined" || value === null || value === "" || value === false || value === 0) {
					continue
				    }
				    if (attribute == "style" && typeof value == "object") {
					if (typeof value.cssText != "undefined") {
					    value = value.cssText
					}
				    }
				    if (attribute == "style") {
					css = [];
					cssAttrs = value.split(";");
					for (j = 0; j < cssAttrs.length; j++) {
					    attr = cssAttrs[j].split(":");
					    cssName = attr[0].replace(/^\s*/, "").replace(/\s*$/, "").toLowerCase();
					    if (Strophe.XHTML.validCSS(cssName)) {
						cssValue = attr[1].replace(/^\s*/, "").replace(/\s*$/, "");
						css.push(cssName + ": " + cssValue)
					    }
					}
					if (css.length > 0) {
					    value = css.join("; ");
					    el.setAttribute(attribute, value)
					}
				    } else {
					el.setAttribute(attribute, value)
				    }
				}
				for (i = 0; i < elem.childNodes.length; i++) {
				    el.appendChild(Strophe.createHtml(elem.childNodes[i]))
				}
			    } catch (e) {
				el = Strophe.xmlTextNode("")
			    }
			} else {
			    el = Strophe.xmlGenerator().createDocumentFragment();
			    for (i = 0; i < elem.childNodes.length; i++) {
				el.appendChild(Strophe.createHtml(elem.childNodes[i]))
			    }
			}
		    } else {
			if (elem.nodeType == Strophe.ElementType.FRAGMENT) {
			    el = Strophe.xmlGenerator().createDocumentFragment();
			    for (i = 0; i < elem.childNodes.length; i++) {
				el.appendChild(Strophe.createHtml(elem.childNodes[i]))
			    }
			} else {
			    if (elem.nodeType == Strophe.ElementType.TEXT) {
				el = Strophe.xmlTextNode(elem.nodeValue)
			    }
			}
		    }
		    return el
		},
		escapeNode: function(node) {
		    return node.replace(/^\s+|\s+$/g, "").replace(/\\/g, "\\5c").replace(/ /g, "\\20").replace(/\"/g, "\\22").replace(/\&/g, "\\26").replace(/\'/g, "\\27").replace(/\//g, "\\2f").replace(/:/g, "\\3a").replace(/</g, "\\3c").replace(/>/g, "\\3e").replace(/@/g, "\\40")
		},
		unescapeNode: function(node) {
		    return node.replace(/\\20/g, " ").replace(/\\22/g, '"').replace(/\\26/g, "&").replace(/\\27/g, "'").replace(/\\2f/g, "/").replace(/\\3a/g, ":").replace(/\\3c/g, "<").replace(/\\3e/g, ">").replace(/\\40/g, "@").replace(/\\5c/g, "\\")
		},
		getNodeFromJid: function(jid) {
		    if (jid.indexOf("@") < 0) {
			return null
		    }
		    return jid.split("@")[0]
		},
		getDomainFromJid: function(jid) {
		    var bare = Strophe.getBareJidFromJid(jid);
		    if (bare.indexOf("@") < 0) {
			return bare
		    } else {
			var parts = bare.split("@");
			parts.splice(0, 1);
			return parts.join("@")
		    }
		},
		getResourceFromJid: function(jid) {
		    var s = jid.split("/");
		    if (s.length < 2) {
			return null
		    }
		    s.splice(0, 1);
		    return s.join("/")
		},
		getBareJidFromJid: function(jid) {
		    return jid ? jid.split("/")[0] : null
		},
		log: function(level, msg) {
		    return
		},
		debug: function(msg) {
		    this.log(this.LogLevel.DEBUG, msg)
		},
		info: function(msg) {
		    this.log(this.LogLevel.INFO, msg)
		},
		warn: function(msg) {
		    this.log(this.LogLevel.WARN, msg)
		},
		error: function(msg) {
		    this.log(this.LogLevel.ERROR, msg)
		},
		fatal: function(msg) {
		    this.log(this.LogLevel.FATAL, msg)
		},
		serialize: function(elem) {
		    var result;
		    if (!elem) {
			return null
		    }
		    if (typeof(elem.tree) === "function") {
			elem = elem.tree()
		    }
		    var nodeName = elem.nodeName;
		    var i, child;
		    if (elem.getAttribute("_realname")) {
			nodeName = elem.getAttribute("_realname")
		    }
		    result = "<" + nodeName;
		    for (i = 0; i < elem.attributes.length; i++) {
			if (elem.attributes[i].nodeName != "_realname") {
			    result += " " + elem.attributes[i].nodeName.toLowerCase() + "='" + elem.attributes[i].value.replace(/&/g, "&amp;").replace(/\'/g, "&apos;").replace(/>/g, "&gt;").replace(/</g, "&lt;") + "'"
			}
		    }
		    if (elem.childNodes.length > 0) {
			result += ">";
			for (i = 0; i < elem.childNodes.length; i++) {
			    child = elem.childNodes[i];
			    switch (child.nodeType) {
				case Strophe.ElementType.NORMAL:
				    result += Strophe.serialize(child);
				    break;
				case Strophe.ElementType.TEXT:
				    result += Strophe.xmlescape(child.nodeValue);
				    break;
				case Strophe.ElementType.CDATA:
				    result += "<![CDATA[" + child.nodeValue + "]]>"
			    }
			}
			result += "</" + nodeName + ">"
		    } else {
			result += "/>"
		    }
		    return result
		},
		_requestId: 0,
		_connectionPlugins: {},
		addConnectionPlugin: function(name, ptype) {
		    Strophe._connectionPlugins[name] = ptype
		}
	    };
	    Strophe.Builder = function(name, attrs) {
		if (name == "presence" || name == "message" || name == "iq") {
		    if (attrs && !attrs.xmlns) {
			attrs.xmlns = Strophe.NS.CLIENT
		    } else {
			if (!attrs) {
			    attrs = {
				xmlns: Strophe.NS.CLIENT
			    }
			}
		    }
		}
		this.nodeTree = Strophe.xmlElement(name, attrs);
		this.node = this.nodeTree
	    };
	    Strophe.Builder.prototype = {
		tree: function() {
		    return this.nodeTree
		},
		toString: function() {
		    return Strophe.serialize(this.nodeTree)
		},
		up: function() {
		    this.node = this.node.parentNode;
		    return this
		},
		attrs: function(moreattrs) {
		    for (var k in moreattrs) {
			if (moreattrs.hasOwnProperty(k)) {
			    this.node.setAttribute(k, moreattrs[k])
			}
		    }
		    return this
		},
		c: function(name, attrs, text) {
		    var child = Strophe.xmlElement(name, attrs, text);
		    this.node.appendChild(child);
		    if (!text) {
			this.node = child
		    }
		    return this
		},
		cnode: function(elem) {
		    var impNode;
		    var xmlGen = Strophe.xmlGenerator();
		    try {
			impNode = (xmlGen.importNode !== undefined)
		    } catch (e) {
			impNode = false
		    }
		    var newElem = impNode ? xmlGen.importNode(elem, true) : Strophe.copyElement(elem);
		    this.node.appendChild(newElem);
		    this.node = newElem;
		    return this
		},
		t: function(text) {
		    var child = Strophe.xmlTextNode(text);
		    this.node.appendChild(child);
		    return this
		},
		h: function(html) {
		    var fragment = document.createElement("body");
		    fragment.innerHTML = html;
		    var xhtml = Strophe.createHtml(fragment);
		    while (xhtml.childNodes.length > 0) {
			this.node.appendChild(xhtml.childNodes[0])
		    }
		    return this
		}
	    };
	    Strophe.Handler = function(handler, ns, name, type, id, from, options) {
		this.handler = handler;
		this.ns = ns;
		this.name = name;
		this.type = type;
		this.id = id;
		this.options = options || {
		    matchBare: false
		};
		if (!this.options.matchBare) {
		    this.options.matchBare = false
		}
		if (this.options.matchBare) {
		    this.from = from ? Strophe.getBareJidFromJid(from) : null
		} else {
		    this.from = from
		}
		this.user = true
	    };
	    Strophe.Handler.prototype = {
		isMatch: function(elem) {
		    var nsMatch;
		    var from = null;
		    if (this.options.matchBare) {
			from = Strophe.getBareJidFromJid(elem.getAttribute("from"))
		    } else {
			from = elem.getAttribute("from")
		    }
		    nsMatch = false;
		    if (!this.ns) {
			nsMatch = true
		    } else {
			var that = this;
			Strophe.forEachChild(elem, null, function(elem) {
			    if (elem.getAttribute("xmlns") == that.ns) {
				nsMatch = true
			    }
			});
			nsMatch = nsMatch || elem.getAttribute("xmlns") == this.ns
		    }
		    if (nsMatch && (!this.name || Strophe.isTagEqual(elem, this.name)) && (!this.type || elem.getAttribute("type") == this.type) && (!this.id || elem.getAttribute("id") == this.id) && (!this.from || from == this.from)) {
			return true
		    }
		    return false
		},
		run: function(elem) {
		    var result = null;
		    try {
			result = this.handler(elem)
		    } catch (e) {
			if (e.sourceURL) {
			    Strophe.fatal("error: " + this.handler + " " + e.sourceURL + ":" + e.line + " - " + e.name + ": " + e.message)
			} else {
			    if (e.fileName) {
				if (typeof(console) != "undefined") {
				    console.trace();
				    console.error(this.handler, " - error - ", e, e.message)
				}
				Strophe.fatal("error: " + this.handler + " " + e.fileName + ":" + e.lineNumber + " - " + e.name + ": " + e.message)
			    } else {
				Strophe.fatal("error: " + e.message + "\n" + e.stack)
			    }
			}
			throw e
		    }
		    return result
		},
		toString: function() {
		    return "{Handler: " + this.handler + "(" + this.name + "," + this.id + "," + this.ns + ")}"
		}
	    };
	    Strophe.TimedHandler = function(period, handler) {
		this.period = period;
		this.handler = handler;
		this.lastCalled = new Date().getTime();
		this.user = true
	    };
	    Strophe.TimedHandler.prototype = {
		run: function() {
		    this.lastCalled = new Date().getTime();
		    return this.handler()
		},
		reset: function() {
		    this.lastCalled = new Date().getTime()
		},
		toString: function() {
		    return "{TimedHandler: " + this.handler + "(" + this.period + ")}"
		}
	    };
	    Strophe.Connection = function(service, options) {
		this.service = service;
		this.options = options || {};
		var proto = this.options.protocol || "";
		if (service.indexOf("ws:") === 0 || service.indexOf("wss:") === 0 || proto.indexOf("ws") === 0) {
		    this._proto = new Strophe.Websocket(this)
		} else {
		    this._proto = new Strophe.Bosh(this)
		}
		this.jid = "";
		this.domain = null;
		this.features = null;
		this._sasl_data = {};
		this.do_session = false;
		this.do_bind = false;
		this.timedHandlers = [];
		this.handlers = [];
		this.removeTimeds = [];
		this.removeHandlers = [];
		this.addTimeds = [];
		this.addHandlers = [];
		this._authentication = {};
		this._idleTimeout = null;
		this._disconnectTimeout = null;
		this.do_authentication = true;
		this.authenticated = false;
		this.disconnecting = false;
		this.connected = false;
		this.errors = 0;
		this.paused = false;
		this._data = [];
		this._uniqueId = 0;
		this._sasl_success_handler = null;
		this._sasl_failure_handler = null;
		this._sasl_challenge_handler = null;
		this.maxRetries = 5;
		this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
		for (var k in Strophe._connectionPlugins) {
		    if (Strophe._connectionPlugins.hasOwnProperty(k)) {
			var ptype = Strophe._connectionPlugins[k];
			var F = function() {};
			F.prototype = ptype;
			this[k] = new F();
			this[k].init(this)
		    }
		}
	    };
	    Strophe.Connection.prototype = {
		reset: function() {
		    this._proto._reset();
		    this.do_session = false;
		    this.do_bind = false;
		    this.timedHandlers = [];
		    this.handlers = [];
		    this.removeTimeds = [];
		    this.removeHandlers = [];
		    this.addTimeds = [];
		    this.addHandlers = [];
		    this._authentication = {};
		    this.authenticated = false;
		    this.disconnecting = false;
		    this.connected = false;
		    this.errors = 0;
		    this._requests = [];
		    this._uniqueId = 0
		},
		pause: function() {
		    this.paused = true
		},
		resume: function() {
		    this.paused = false
		},
		getUniqueId: function(suffix) {
		    if (typeof(suffix) == "string" || typeof(suffix) == "number") {
			return ++this._uniqueId + ":" + suffix
		    } else {
			return ++this._uniqueId + ""
		    }
		},
		connect: function(jid, pass, callback, wait, hold, route) {
		    this.jid = jid;
		    this.authzid = Strophe.getBareJidFromJid(this.jid);
		    this.authcid = Strophe.getNodeFromJid(this.jid);
		    this.pass = pass;
		    this.servtype = "xmpp";
		    this.connect_callback = callback;
		    this.disconnecting = false;
		    this.connected = false;
		    this.authenticated = false;
		    this.errors = 0;
		    this.domain = Strophe.getDomainFromJid(this.jid);
		    this._changeConnectStatus(Strophe.Status.CONNECTING, null);
		    this._proto._connect(wait, hold, route)
		},
		attach: function(jid, sid, rid, callback, wait, hold, wind) {
		    this._proto._attach(jid, sid, rid, callback, wait, hold, wind)
		},
		xmlInput: function(elem) {
		    return
		},
		xmlOutput: function(elem) {
		    return
		},
		rawInput: function(data) {
		    return
		},
		rawOutput: function(data) {
		    return
		},
		send: function(elem) {
		    if (elem === null) {
			return
		    }
		    if (typeof(elem.sort) === "function") {
			for (var i = 0; i < elem.length; i++) {
			    this._queueData(elem[i])
			}
		    } else {
			if (typeof(elem.tree) === "function") {
			    this._queueData(elem.tree())
			} else {
			    this._queueData(elem)
			}
		    }
		    this._proto._send()
		},
		flush: function() {
		    clearTimeout(this._idleTimeout);
		    this._onIdle()
		},
		sendIQ: function(elem, callback, errback, timeout) {
		    var timeoutHandler = null;
		    var that = this;
		    if (typeof(elem.tree) === "function") {
			elem = elem.tree()
		    }
		    var id = elem.getAttribute("id");
		    if (!id) {
			id = this.getUniqueId("sendIQ");
			elem.setAttribute("id", id)
		    }
		    var handler = this.addHandler(function(stanza) {
			if (timeoutHandler) {
			    that.deleteTimedHandler(timeoutHandler)
			}
			var iqtype = stanza.getAttribute("type");
			if (iqtype == "result") {
			    if (callback) {
				callback(stanza)
			    }
			} else {
			    if (iqtype == "error") {
				if (errback) {
				    errback(stanza)
				}
			    } else {
				throw {
				    name: "StropheError",
				    message: "Got bad IQ type of " + iqtype
				}
			    }
			}
		    }, null, "iq", null, id);
		    if (timeout) {
			timeoutHandler = this.addTimedHandler(timeout, function() {
			    that.deleteHandler(handler);
			    if (errback) {
				errback(null)
			    }
			    return false
			})
		    }
		    this.send(elem);
		    return id
		},
		_queueData: function(element) {
		    if (element === null || !element.tagName || !element.childNodes) {
			throw {
			    name: "StropheError",
			    message: "Cannot queue non-DOMElement."
			}
		    }
		    this._data.push(element)
		},
		_sendRestart: function() {
		    this._data.push("restart");
		    this._proto._sendRestart();
		    this._idleTimeout = setTimeout(this._onIdle.bind(this), 100)
		},
		addTimedHandler: function(period, handler) {
		    var thand = new Strophe.TimedHandler(period, handler);
		    this.addTimeds.push(thand);
		    return thand
		},
		deleteTimedHandler: function(handRef) {
		    this.removeTimeds.push(handRef)
		},
		addHandler: function(handler, ns, name, type, id, from, options) {
		    var hand = new Strophe.Handler(handler, ns, name, type, id, from, options);
		    this.addHandlers.push(hand);
		    return hand
		},
		deleteHandler: function(handRef) {
		    this.removeHandlers.push(handRef)
		},
		disconnect: function(reason) {
		    this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason);
		    Strophe.info("Disconnect was called because: " + reason);
		    if (this.connected) {
			var pres = false;
			this.disconnecting = true;
			if (this.authenticated) {
			    pres = $pres({
				xmlns: Strophe.NS.CLIENT,
				type: "unavailable"
			    })
			}
			this._disconnectTimeout = this._addSysTimedHandler(3000, this._onDisconnectTimeout.bind(this));
			this._proto._disconnect(pres)
		    }
		},
		_changeConnectStatus: function(status, condition) {
		    for (var k in Strophe._connectionPlugins) {
			if (Strophe._connectionPlugins.hasOwnProperty(k)) {
			    var plugin = this[k];
			    if (plugin.statusChanged) {
				try {
				    plugin.statusChanged(status, condition)
				} catch (err) {
				    Strophe.error("" + k + " plugin caused an exception changing status: " + err)
				}
			    }
			}
		    }
		    if (this.connect_callback) {
			try {
			    this.connect_callback(status, condition)
			} catch (e) {
			    Strophe.error("User connection callback caused an exception: " + e)
			}
		    }
		},
		_doDisconnect: function() {
		    if (this._disconnectTimeout !== null) {
			this.deleteTimedHandler(this._disconnectTimeout);
			this._disconnectTimeout = null
		    }
		    Strophe.info("_doDisconnect was called");
		    this._proto._doDisconnect();
		    this.authenticated = false;
		    this.disconnecting = false;
		    this.handlers = [];
		    this.timedHandlers = [];
		    this.removeTimeds = [];
		    this.removeHandlers = [];
		    this.addTimeds = [];
		    this.addHandlers = [];
		    this._changeConnectStatus(Strophe.Status.DISCONNECTED, null);
		    this.connected = false
		},
		_dataRecv: function(req, raw) {
		    Strophe.info("_dataRecv called");
		    var elem = this._proto._reqToData(req);
		    if (elem === null) {
			return
		    }
		    if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) {
			if (elem.nodeName === this._proto.strip && elem.childNodes.length) {
			    this.xmlInput(elem.childNodes[0])
			} else {
			    this.xmlInput(elem)
			}
		    }
		    if (this.rawInput !== Strophe.Connection.prototype.rawInput) {
			if (raw) {
			    this.rawInput(raw)
			} else {
			    this.rawInput(Strophe.serialize(elem))
			}
		    }
		    var i, hand;
		    while (this.removeHandlers.length > 0) {
			hand = this.removeHandlers.pop();
			i = this.handlers.indexOf(hand);
			if (i >= 0) {
			    this.handlers.splice(i, 1)
			}
		    }
		    while (this.addHandlers.length > 0) {
			this.handlers.push(this.addHandlers.pop())
		    }
		    if (this.disconnecting && this._proto._emptyQueue()) {
			this._doDisconnect();
			return
		    }
		    var typ = elem.getAttribute("type");
		    var cond, conflict;
		    if (typ !== null && typ == "terminate") {
			if (this.disconnecting) {
			    return
			}
			cond = elem.getAttribute("condition");
			conflict = elem.getElementsByTagName("conflict");
			if (cond !== null) {
			    if (cond == "remote-stream-error" && conflict.length > 0) {
				cond = "conflict"
			    }
			    this._changeConnectStatus(Strophe.Status.CONNFAIL, cond)
			} else {
			    this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown")
			}
			this.disconnect("unknown stream-error");
			return
		    }
		    var that = this;
		    Strophe.forEachChild(elem, null, function(child) {
			var i, newList;
			newList = that.handlers;
			that.handlers = [];
			for (i = 0; i < newList.length; i++) {
			    var hand = newList[i];
			    try {
				if (hand.isMatch(child) && (that.authenticated || !hand.user)) {
				    if (hand.run(child)) {
					that.handlers.push(hand)
				    }
				} else {
				    that.handlers.push(hand)
				}
			    } catch (e) {
				Strophe.warn("Removing Strophe handlers due to uncaught exception: " + e.message)
			    }
			}
		    })
		},
		mechanisms: {},
		_connect_cb: function(req, _callback, raw) {
		    Strophe.info("_connect_cb was called");
		    this.connected = true;
		    var bodyWrap = this._proto._reqToData(req);
		    if (!bodyWrap) {
			return
		    }
		    if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) {
			if (bodyWrap.nodeName === this._proto.strip && bodyWrap.childNodes.length) {
			    this.xmlInput(bodyWrap.childNodes[0])
			} else {
			    this.xmlInput(bodyWrap)
			}
		    }
		    if (this.rawInput !== Strophe.Connection.prototype.rawInput) {
			if (raw) {
			    this.rawInput(raw)
			} else {
			    this.rawInput(Strophe.serialize(bodyWrap))
			}
		    }
		    var conncheck = this._proto._connect_cb(bodyWrap);
		    if (conncheck === Strophe.Status.CONNFAIL) {
			return
		    }
		    this._authentication.sasl_scram_sha1 = false;
		    this._authentication.sasl_plain = false;
		    this._authentication.sasl_digest_md5 = false;
		    this._authentication.sasl_anonymous = false;
		    this._authentication.legacy_auth = false;
		    var hasFeatures = bodyWrap.getElementsByTagName("stream:features").length > 0;
		    if (!hasFeatures) {
			hasFeatures = bodyWrap.getElementsByTagName("features").length > 0
		    }
		    var mechanisms = bodyWrap.getElementsByTagName("mechanism");
		    var matched = [];
		    var i, mech, found_authentication = false;
		    if (!hasFeatures) {
			this._proto._no_auth_received(_callback);
			return
		    }
		    if (mechanisms.length > 0) {
			for (i = 0; i < mechanisms.length; i++) {
			    mech = Strophe.getText(mechanisms[i]);
			    if (this.mechanisms[mech]) {
				matched.push(this.mechanisms[mech])
			    }
			}
		    }
		    this._authentication.legacy_auth = bodyWrap.getElementsByTagName("auth").length > 0;
		    found_authentication = this._authentication.legacy_auth || matched.length > 0;
		    if (!found_authentication) {
			this._proto._no_auth_received(_callback);
			return
		    }
		    if (this.do_authentication !== false) {
			this.authenticate(matched)
		    }
		},
		authenticate: function(matched) {
		    var i;
		    for (i = 0; i < matched.length - 1; ++i) {
			var higher = i;
			for (var j = i + 1; j < matched.length; ++j) {
			    if (matched[j].prototype.priority > matched[higher].prototype.priority) {
				higher = j
			    }
			}
			if (higher != i) {
			    var swap = matched[i];
			    matched[i] = matched[higher];
			    matched[higher] = swap
			}
		    }
		    var mechanism_found = false;
		    for (i = 0; i < matched.length; ++i) {
			if (!matched[i].test(this)) {
			    continue
			}
			this._sasl_success_handler = this._addSysHandler(this._sasl_success_cb.bind(this), null, "success", null, null);
			this._sasl_failure_handler = this._addSysHandler(this._sasl_failure_cb.bind(this), null, "failure", null, null);
			this._sasl_challenge_handler = this._addSysHandler(this._sasl_challenge_cb.bind(this), null, "challenge", null, null);
			this._sasl_mechanism = new matched[i]();
			this._sasl_mechanism.onStart(this);
			var request_auth_exchange = $build("auth", {
			    xmlns: Strophe.NS.SASL,
			    mechanism: this._sasl_mechanism.name
			});
			if (this._sasl_mechanism.isClientFirst) {
			    var response = this._sasl_mechanism.onChallenge(this, null);
			    request_auth_exchange.t(Base64.encode(response))
			}
			this.send(request_auth_exchange.tree());
			mechanism_found = true;
			break
		    }
		    if (!mechanism_found) {
			if (Strophe.getNodeFromJid(this.jid) === null) {
			    this._changeConnectStatus(Strophe.Status.CONNFAIL, "x-strophe-bad-non-anon-jid");
			    this.disconnect("x-strophe-bad-non-anon-jid")
			} else {
			    this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
			    this._addSysHandler(this._auth1_cb.bind(this), null, null, null, "_auth_1");
			    this.send($iq({
				type: "get",
				to: this.domain,
				id: "_auth_1"
			    }).c("query", {
				xmlns: Strophe.NS.AUTH
			    }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).tree())
			}
		    }
		},
		_sasl_challenge_cb: function(elem) {
		    var challenge = Base64.decode(Strophe.getText(elem));
		    var response = this._sasl_mechanism.onChallenge(this, challenge);
		    var stanza = $build("response", {
			xmlns: Strophe.NS.SASL
		    });
		    if (response !== "") {
			stanza.t(Base64.encode(response))
		    }
		    this.send(stanza.tree());
		    return true
		},
		_auth1_cb: function(elem) {
		    var iq = $iq({
			type: "set",
			id: "_auth_2"
		    }).c("query", {
			xmlns: Strophe.NS.AUTH
		    }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).up().c("password").t(this.pass);
		    if (!Strophe.getResourceFromJid(this.jid)) {
			this.jid = Strophe.getBareJidFromJid(this.jid) + "/strophe"
		    }
		    iq.up().c("resource", {}).t(Strophe.getResourceFromJid(this.jid));
		    this._addSysHandler(this._auth2_cb.bind(this), null, null, null, "_auth_2");
		    this.send(iq.tree());
		    return false
		},
		_sasl_success_cb: function(elem) {
		    if (this._sasl_data["server-signature"]) {
			var serverSignature;
			var success = Base64.decode(Strophe.getText(elem));
			var attribMatch = /([a-z]+)=([^,]+)(,|$)/;
			var matches = success.match(attribMatch);
			if (matches[1] == "v") {
			    serverSignature = matches[2]
			}
			if (serverSignature != this._sasl_data["server-signature"]) {
			    this.deleteHandler(this._sasl_failure_handler);
			    this._sasl_failure_handler = null;
			    if (this._sasl_challenge_handler) {
				this.deleteHandler(this._sasl_challenge_handler);
				this._sasl_challenge_handler = null
			    }
			    this._sasl_data = {};
			    return this._sasl_failure_cb(null)
			}
		    }
		    Strophe.info("SASL authentication succeeded.");
		    if (this._sasl_mechanism) {
			this._sasl_mechanism.onSuccess()
		    }
		    this.deleteHandler(this._sasl_failure_handler);
		    this._sasl_failure_handler = null;
		    if (this._sasl_challenge_handler) {
			this.deleteHandler(this._sasl_challenge_handler);
			this._sasl_challenge_handler = null
		    }
		    this._addSysHandler(this._sasl_auth1_cb.bind(this), null, "stream:features", null, null);
		    this._sendRestart();
		    return false
		},
		_sasl_auth1_cb: function(elem) {
		    this.features = elem;
		    var i, child;
		    for (i = 0; i < elem.childNodes.length; i++) {
			child = elem.childNodes[i];
			if (child.nodeName == "bind") {
			    this.do_bind = true
			}
			if (child.nodeName == "session") {
			    this.do_session = true
			}
		    }
		    if (!this.do_bind) {
			this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
			return false
		    } else {
			this._addSysHandler(this._sasl_bind_cb.bind(this), null, null, null, "_bind_auth_2");
			var resource = Strophe.getResourceFromJid(this.jid);
			if (resource) {
			    this.send($iq({
				type: "set",
				id: "_bind_auth_2"
			    }).c("bind", {
				xmlns: Strophe.NS.BIND
			    }).c("resource", {}).t(resource).tree())
			} else {
			    this.send($iq({
				type: "set",
				id: "_bind_auth_2"
			    }).c("bind", {
				xmlns: Strophe.NS.BIND
			    }).tree())
			}
		    }
		    return false
		},
		_sasl_bind_cb: function(elem) {
		    if (elem.getAttribute("type") == "error") {
			Strophe.info("SASL binding failed.");
			var conflict = elem.getElementsByTagName("conflict"),
			    condition;
			if (conflict.length > 0) {
			    condition = "conflict"
			}
			this._changeConnectStatus(Strophe.Status.AUTHFAIL, condition);
			return false
		    }
		    var bind = elem.getElementsByTagName("bind");
		    var jidNode;
		    if (bind.length > 0) {
			jidNode = bind[0].getElementsByTagName("jid");
			if (jidNode.length > 0) {
			    this.jid = Strophe.getText(jidNode[0]);
			    if (this.do_session) {
				this._addSysHandler(this._sasl_session_cb.bind(this), null, null, null, "_session_auth_2");
				this.send($iq({
				    type: "set",
				    id: "_session_auth_2"
				}).c("session", {
				    xmlns: Strophe.NS.SESSION
				}).tree())
			    } else {
				this.authenticated = true;
				this._changeConnectStatus(Strophe.Status.CONNECTED, null)
			    }
			}
		    } else {
			Strophe.info("SASL binding failed.");
			this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
			return false
		    }
		},
		_sasl_session_cb: function(elem) {
		    if (elem.getAttribute("type") == "result") {
			this.authenticated = true;
			this._changeConnectStatus(Strophe.Status.CONNECTED, null)
		    } else {
			if (elem.getAttribute("type") == "error") {
			    Strophe.info("Session creation failed.");
			    this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
			    return false
			}
		    }
		    return false
		},
		_sasl_failure_cb: function(elem) {
		    if (this._sasl_success_handler) {
			this.deleteHandler(this._sasl_success_handler);
			this._sasl_success_handler = null
		    }
		    if (this._sasl_challenge_handler) {
			this.deleteHandler(this._sasl_challenge_handler);
			this._sasl_challenge_handler = null
		    }
		    if (this._sasl_mechanism) {
			this._sasl_mechanism.onFailure()
		    }
		    this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
		    return false
		},
		_auth2_cb: function(elem) {
		    if (elem.getAttribute("type") == "result") {
			this.authenticated = true;
			this._changeConnectStatus(Strophe.Status.CONNECTED, null)
		    } else {
			if (elem.getAttribute("type") == "error") {
			    this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
			    this.disconnect("authentication failed")
			}
		    }
		    return false
		},
		_addSysTimedHandler: function(period, handler) {
		    var thand = new Strophe.TimedHandler(period, handler);
		    thand.user = false;
		    this.addTimeds.push(thand);
		    return thand
		},
		_addSysHandler: function(handler, ns, name, type, id) {
		    var hand = new Strophe.Handler(handler, ns, name, type, id);
		    hand.user = false;
		    this.addHandlers.push(hand);
		    return hand
		},
		_onDisconnectTimeout: function() {
		    Strophe.info("_onDisconnectTimeout was called");
		    this._proto._onDisconnectTimeout();
		    this._doDisconnect();
		    return false
		},
		_onIdle: function() {
		    var i, thand, since, newList;
		    while (this.addTimeds.length > 0) {
			this.timedHandlers.push(this.addTimeds.pop())
		    }
		    while (this.removeTimeds.length > 0) {
			thand = this.removeTimeds.pop();
			i = this.timedHandlers.indexOf(thand);
			if (i >= 0) {
			    this.timedHandlers.splice(i, 1)
			}
		    }
		    var now = new Date().getTime();
		    newList = [];
		    for (i = 0; i < this.timedHandlers.length; i++) {
			thand = this.timedHandlers[i];
			if (this.authenticated || !thand.user) {
			    since = thand.lastCalled + thand.period;
			    if (since - now <= 0) {
				if (thand.run()) {
				    newList.push(thand)
				}
			    } else {
				newList.push(thand)
			    }
			}
		    }
		    this.timedHandlers = newList;
		    clearTimeout(this._idleTimeout);
		    this._proto._onIdle();
		    if (this.connected) {
			this._idleTimeout = setTimeout(this._onIdle.bind(this), 100)
		    }
		}
	    };
	    if (callback) {
		callback(Strophe, $build, $msg, $iq, $pres)
	    }
	    Strophe.SASLMechanism = function(name, isClientFirst, priority) {
		this.name = name;
		this.isClientFirst = isClientFirst;
		this.priority = priority
	    };
	    Strophe.SASLMechanism.prototype = {
		test: function(connection) {
		    return true
		},
		onStart: function(connection) {
		    this._connection = connection
		},
		onChallenge: function(connection, challenge) {
		    throw new Error("You should implement challenge handling!")
		},
		onFailure: function() {
		    this._connection = null
		},
		onSuccess: function() {
		    this._connection = null
		}
	    };
	    Strophe.SASLAnonymous = function() {};
	    Strophe.SASLAnonymous.prototype = new Strophe.SASLMechanism("ANONYMOUS", false, 10);
	    Strophe.SASLAnonymous.test = function(connection) {
		return connection.authcid === null
	    };
	    Strophe.Connection.prototype.mechanisms[Strophe.SASLAnonymous.prototype.name] = Strophe.SASLAnonymous;
	    Strophe.SASLPlain = function() {};
	    Strophe.SASLPlain.prototype = new Strophe.SASLMechanism("PLAIN", true, 20);
	    Strophe.SASLPlain.test = function(connection) {
		return connection.authcid !== null
	    };
	    Strophe.SASLPlain.prototype.onChallenge = function(connection) {
		var auth_str = connection.authzid;
		auth_str = auth_str + "\u0000";
		auth_str = auth_str + connection.authcid;
		auth_str = auth_str + "\u0000";
		auth_str = auth_str + connection.pass;
		return auth_str
	    };
	    Strophe.Connection.prototype.mechanisms[Strophe.SASLPlain.prototype.name] = Strophe.SASLPlain;
	    Strophe.SASLSHA1 = function() {};
	    Strophe.SASLSHA1.prototype = new Strophe.SASLMechanism("SCRAM-SHA-1", true, 40);
	    Strophe.SASLSHA1.test = function(connection) {
		return connection.authcid !== null
	    };
	    Strophe.SASLSHA1.prototype.onChallenge = function(connection, challenge, test_cnonce) {
		var cnonce = test_cnonce || MD5.hexdigest(Math.random() * 1234567890);
		var auth_str = "n=" + connection.authcid;
		auth_str += ",r=";
		auth_str += cnonce;
		connection._sasl_data.cnonce = cnonce;
		connection._sasl_data["client-first-message-bare"] = auth_str;
		auth_str = "n,," + auth_str;
		this.onChallenge = function(connection, challenge) {
		    var nonce, salt, iter, Hi, U, U_old, i, k;
		    var clientKey, serverKey, clientSignature;
		    var responseText = "c=biws,";
		    var authMessage = connection._sasl_data["client-first-message-bare"] + "," + challenge + ",";
		    var cnonce = connection._sasl_data.cnonce;
		    var attribMatch = /([a-z]+)=([^,]+)(,|$)/;
		    while (challenge.match(attribMatch)) {
			var matches = challenge.match(attribMatch);
			challenge = challenge.replace(matches[0], "");
			switch (matches[1]) {
			    case "r":
				nonce = matches[2];
				break;
			    case "s":
				salt = matches[2];
				break;
			    case "i":
				iter = matches[2];
				break
			}
		    }
		    if (nonce.substr(0, cnonce.length) !== cnonce) {
			connection._sasl_data = {};
			return connection._sasl_failure_cb()
		    }
		    responseText += "r=" + nonce;
		    authMessage += responseText;
		    salt = Base64.decode(salt);
		    salt += "\x00\x00\x00\x01";
		    Hi = U_old = core_hmac_sha1(connection.pass, salt);
		    for (i = 1; i < iter; i++) {
			U = core_hmac_sha1(connection.pass, binb2str(U_old));
			for (k = 0; k < 5; k++) {
			    Hi[k] ^= U[k]
			}
			U_old = U
		    }
		    Hi = binb2str(Hi);
		    clientKey = core_hmac_sha1(Hi, "Client Key");
		    serverKey = str_hmac_sha1(Hi, "Server Key");
		    clientSignature = core_hmac_sha1(str_sha1(binb2str(clientKey)), authMessage);
		    connection._sasl_data["server-signature"] = b64_hmac_sha1(serverKey, authMessage);
		    for (k = 0; k < 5; k++) {
			clientKey[k] ^= clientSignature[k]
		    }
		    responseText += ",p=" + Base64.encode(binb2str(clientKey));
		    return responseText
		}.bind(this);
		return auth_str
	    };
	    Strophe.Connection.prototype.mechanisms[Strophe.SASLSHA1.prototype.name] = Strophe.SASLSHA1;
	    Strophe.SASLMD5 = function() {};
	    Strophe.SASLMD5.prototype = new Strophe.SASLMechanism("DIGEST-MD5", false, 30);
	    Strophe.SASLMD5.test = function(connection) {
		return connection.authcid !== null
	    };
	    Strophe.SASLMD5.prototype._quote = function(str) {
		return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'
	    };
	    Strophe.SASLMD5.prototype.onChallenge = function(connection, challenge, test_cnonce) {
		var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;
		var cnonce = test_cnonce || MD5.hexdigest("" + (Math.random() * 1234567890));
		var realm = "";
		var host = null;
		var nonce = "";
		var qop = "";
		var matches;
		while (challenge.match(attribMatch)) {
		    matches = challenge.match(attribMatch);
		    challenge = challenge.replace(matches[0], "");
		    matches[2] = matches[2].replace(/^"(.+)"$/, "$1");
		    switch (matches[1]) {
			case "realm":
			    realm = matches[2];
			    break;
			case "nonce":
			    nonce = matches[2];
			    break;
			case "qop":
			    qop = matches[2];
			    break;
			case "host":
			    host = matches[2];
			    break
		    }
		}
		var digest_uri = connection.servtype + "/" + connection.domain;
		if (host !== null) {
		    digest_uri = digest_uri + "/" + host
		}
		var A1 = MD5.hash(connection.authcid + ":" + realm + ":" + this._connection.pass) + ":" + nonce + ":" + cnonce;
		var A2 = "AUTHENTICATE:" + digest_uri;
		var responseText = "";
		responseText += "charset=utf-8,";
		responseText += "username=" + this._quote(connection.authcid) + ",";
		responseText += "realm=" + this._quote(realm) + ",";
		responseText += "nonce=" + this._quote(nonce) + ",";
		responseText += "nc=00000001,";
		responseText += "cnonce=" + this._quote(cnonce) + ",";
		responseText += "digest-uri=" + this._quote(digest_uri) + ",";
		responseText += "response=" + MD5.hexdigest(MD5.hexdigest(A1) + ":" + nonce + ":00000001:" + cnonce + ":auth:" + MD5.hexdigest(A2)) + ",";
		responseText += "qop=auth";
		this.onChallenge = function() {
		    return ""
		}.bind(this);
		return responseText
	    };
	    Strophe.Connection.prototype.mechanisms[Strophe.SASLMD5.prototype.name] = Strophe.SASLMD5
	})(function() {
	    window.Strophe = arguments[0];
	    window.$build = arguments[1];
	    window.$msg = arguments[2];
	    window.$iq = arguments[3];
	    window.$pres = arguments[4]
	});
	Strophe.Request = function(elem, func, rid, sends) {
	    this.id = ++Strophe._requestId;
	    this.xmlData = elem;
	    this.data = Strophe.serialize(elem);
	    this.origFunc = func;
	    this.func = func;
	    this.rid = rid;
	    this.date = NaN;
	    this.sends = sends || 0;
	    this.abort = false;
	    this.dead = null;
	    this.age = function() {
		if (!this.date) {
		    return 0
		}
		var now = new Date();
		return (now - this.date) / 1000
	    };
	    this.timeDead = function() {
		if (!this.dead) {
		    return 0
		}
		var now = new Date();
		return (now - this.dead) / 1000
	    };
	    this.xhr = this._newXHR()
	};
	Strophe.Request.prototype = {
	    getResponse: function() {
		var node = null;
		if (this.xhr.responseXML && this.xhr.responseXML.documentElement) {
		    node = this.xhr.responseXML.documentElement;
		    if (node.tagName == "parsererror") {
			Strophe.error("invalid response received");
			Strophe.error("responseText: " + this.xhr.responseText);
			Strophe.error("responseXML: " + Strophe.serialize(this.xhr.responseXML));
			throw "parsererror"
		    }
		} else {
		    if (this.xhr.responseText) {
			Strophe.error("invalid response received");
			Strophe.error("responseText: " + this.xhr.responseText);
			Strophe.error("responseXML: " + Strophe.serialize(this.xhr.responseXML))
		    }
		}
		return node
	    },
	    _newXHR: function() {
		var xhr = null;
		if (window.XMLHttpRequest) {
		    xhr = new XMLHttpRequest();
		    if (xhr.overrideMimeType) {
			xhr.overrideMimeType("text/xml")
		    }
		} else {
		    if (window.ActiveXObject) {
			xhr = new ActiveXObject("Microsoft.XMLHTTP")
		    }
		}
		xhr.onreadystatechange = this.func.bind(null, this);
		return xhr
	    }
	};
	Strophe.Bosh = function(connection) {
	    this._conn = connection;
	    this.rid = Math.floor(Math.random() * 4294967295);
	    this.sid = null;
	    this.hold = 1;
	    this.wait = 60;
	    this.window = 5;
	    this._requests = []
	};
	Strophe.Bosh.prototype = {
	    strip: null,
	    _buildBody: function() {
		var bodyWrap = $build("body", {
		    rid: this.rid++,
		    xmlns: Strophe.NS.HTTPBIND
		});
		if (this.sid !== null) {
		    bodyWrap.attrs({
			sid: this.sid
		    })
		}
		return bodyWrap
	    },
	    _reset: function() {
		this.rid = Math.floor(Math.random() * 4294967295);
		this.sid = null
	    },
	    _connect: function(wait, hold, route) {
		this.wait = wait || this.wait;
		this.hold = hold || this.hold;
		var body = this._buildBody().attrs({
		    to: this._conn.domain,
		    "xml:lang": "en",
		    wait: this.wait,
		    hold: this.hold,
		    content: "text/xml; charset=utf-8",
		    ver: "1.6",
		    "xmpp:version": "1.0",
		    "xmlns:xmpp": Strophe.NS.BOSH
		});
		if (route) {
		    body.attrs({
			route: route
		    })
		}
		var _connect_cb = this._conn._connect_cb;
		this._requests.push(new Strophe.Request(body.tree(), this._onRequestStateChange.bind(this, _connect_cb.bind(this._conn)), body.tree().getAttribute("rid")));
		this._throttledRequestHandler()
	    },
	    _attach: function(jid, sid, rid, callback, wait, hold, wind) {
		this._conn.jid = jid;
		this.sid = sid;
		this.rid = rid;
		this._conn.connect_callback = callback;
		this._conn.domain = Strophe.getDomainFromJid(this._conn.jid);
		this._conn.authenticated = true;
		this._conn.connected = true;
		this.wait = wait || this.wait;
		this.hold = hold || this.hold;
		this.window = wind || this.window;
		this._conn._changeConnectStatus(Strophe.Status.ATTACHED, null)
	    },
	    _connect_cb: function(bodyWrap) {
		var typ = bodyWrap.getAttribute("type");
		var cond, conflict;
		if (typ !== null && typ == "terminate") {
		    Strophe.error("BOSH-Connection failed: " + cond);
		    cond = bodyWrap.getAttribute("condition");
		    conflict = bodyWrap.getElementsByTagName("conflict");
		    if (cond !== null) {
			if (cond == "remote-stream-error" && conflict.length > 0) {
			    cond = "conflict"
			}
			this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, cond)
		    } else {
			this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown")
		    }
		    this._conn._doDisconnect();
		    return Strophe.Status.CONNFAIL
		}
		if (!this.sid) {
		    this.sid = bodyWrap.getAttribute("sid")
		}
		var wind = bodyWrap.getAttribute("requests");
		if (wind) {
		    this.window = parseInt(wind, 10)
		}
		var hold = bodyWrap.getAttribute("hold");
		if (hold) {
		    this.hold = parseInt(hold, 10)
		}
		var wait = bodyWrap.getAttribute("wait");
		if (wait) {
		    this.wait = parseInt(wait, 10)
		}
	    },
	    _disconnect: function(pres) {
		this._sendTerminate(pres)
	    },
	    _doDisconnect: function() {
		this.sid = null;
		this.rid = Math.floor(Math.random() * 4294967295)
	    },
	    _emptyQueue: function() {
		return this._requests.length === 0
	    },
	    _hitError: function(reqStatus) {
		this.errors++;
		Strophe.warn("request errored, status: " + reqStatus + ", number of errors: " + this.errors);
		if (this.errors > 4) {
		    this._onDisconnectTimeout()
		}
	    },
	    _no_auth_received: function(_callback) {
		if (_callback) {
		    _callback = _callback.bind(this._conn)
		} else {
		    _callback = this._conn._connect_cb.bind(this._conn)
		}
		var body = this._buildBody();
		this._requests.push(new Strophe.Request(body.tree(), this._onRequestStateChange.bind(this, _callback.bind(this._conn)), body.tree().getAttribute("rid")));
		this._throttledRequestHandler()
	    },
	    _onDisconnectTimeout: function() {
		var req;
		while (this._requests.length > 0) {
		    req = this._requests.pop();
		    req.abort = true;
		    req.xhr.abort();
		    req.xhr.onreadystatechange = function() {}
		}
	    },
	    _onIdle: function() {
		var data = this._conn._data;
		if (this._conn.authenticated && this._requests.length === 0 && data.length === 0 && !this._conn.disconnecting) {
		    Strophe.info("no requests during idle cycle, sending blank request");
		    data.push(null)
		}
		if (this._requests.length < 2 && data.length > 0 && !this._conn.paused) {
		    var body = this._buildBody();
		    for (var i = 0; i < data.length; i++) {
			if (data[i] !== null) {
			    if (data[i] === "restart") {
				body.attrs({
				    to: this._conn.domain,
				    "xml:lang": "en",
				    "xmpp:restart": "true",
				    "xmlns:xmpp": Strophe.NS.BOSH
				})
			    } else {
				body.cnode(data[i]).up()
			    }
			}
		    }
		    delete this._conn._data;
		    this._conn._data = [];
		    this._requests.push(new Strophe.Request(body.tree(), this._onRequestStateChange.bind(this, this._conn._dataRecv.bind(this._conn)), body.tree().getAttribute("rid")));
		    this._processRequest(this._requests.length - 1)
		}
		if (this._requests.length > 0) {
		    var time_elapsed = this._requests[0].age();
		    if (this._requests[0].dead !== null) {
			if (this._requests[0].timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) {
			    this._throttledRequestHandler()
			}
		    }
		    if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) {
			Strophe.warn("Request " + this._requests[0].id + " timed out, over " + Math.floor(Strophe.TIMEOUT * this.wait) + " seconds since last activity");
			this._throttledRequestHandler()
		    }
		}
	    },
	    _onRequestStateChange: function(func, req) {
		Strophe.debug("request id " + req.id + "." + req.sends + " state changed to " + req.xhr.readyState);
		if (req.abort) {
		    req.abort = false;
		    return
		}
		var reqStatus;
		if (req.xhr.readyState == 4) {
		    reqStatus = 0;
		    try {
			reqStatus = req.xhr.status
		    } catch (e) {}
		    if (typeof(reqStatus) == "undefined") {
			reqStatus = 0
		    }
		    if (this.disconnecting) {
			if (reqStatus >= 400) {
			    this._hitError(reqStatus);
			    return
			}
		    }
		    var reqIs0 = (this._requests[0] == req);
		    var reqIs1 = (this._requests[1] == req);
		    if ((reqStatus > 0 && reqStatus < 500) || req.sends > 5) {
			this._removeRequest(req);
			Strophe.debug("request id " + req.id + " should now be removed")
		    }
		    if (reqStatus == 200) {
			if (reqIs1 || (reqIs0 && this._requests.length > 0 && this._requests[0].age() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait))) {
			    this._restartRequest(0)
			}
			Strophe.debug("request id " + req.id + "." + req.sends + " got 200");
			func(req);
			this.errors = 0
		    } else {
			Strophe.error("request id " + req.id + "." + req.sends + " error " + reqStatus + " happened");
			if (reqStatus === 0 || (reqStatus >= 400 && reqStatus < 600) || reqStatus >= 12000) {
			    this._hitError(reqStatus);
			    if (reqStatus >= 400 && reqStatus < 500) {
				this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING, null);
				this._conn._doDisconnect()
			    }
			}
		    }
		    if (!((reqStatus > 0 && reqStatus < 500) || req.sends > 5)) {
			this._throttledRequestHandler()
		    }
		}
	    },
	    _processRequest: function(i) {
		var self = this;
		var req = this._requests[i];
		var reqStatus = -1;
		try {
		    if (req.xhr.readyState == 4) {
			reqStatus = req.xhr.status
		    }
		} catch (e) {
		    Strophe.error("caught an error in _requests[" + i + "], reqStatus: " + reqStatus)
		}
		if (typeof(reqStatus) == "undefined") {
		    reqStatus = -1
		}
		if (req.sends > this.maxRetries) {
		    this._onDisconnectTimeout();
		    return
		}
		var time_elapsed = req.age();
		var primaryTimeout = (!isNaN(time_elapsed) && time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait));
		var secondaryTimeout = (req.dead !== null && req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait));
		var requestCompletedWithServerError = (req.xhr.readyState == 4 && (reqStatus < 1 || reqStatus >= 500));
		if (primaryTimeout || secondaryTimeout || requestCompletedWithServerError) {
		    if (secondaryTimeout) {
			Strophe.error("Request " + this._requests[i].id + " timed out (secondary), restarting")
		    }
		    req.abort = true;
		    req.xhr.abort();
		    req.xhr.onreadystatechange = function() {};
		    this._requests[i] = new Strophe.Request(req.xmlData, req.origFunc, req.rid, req.sends);
		    req = this._requests[i]
		}
		if (req.xhr.readyState === 0) {
		    Strophe.debug("request id " + req.id + "." + req.sends + " posting");
		    try {
			req.xhr.open("POST", this._conn.service, this._conn.options.sync ? false : true)
		    } catch (e2) {
			Strophe.error("XHR open failed.");
			if (!this._conn.connected) {
			    this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "bad-service")
			}
			this._conn.disconnect();
			return
		    }
		    var sendFunc = function() {
			req.date = new Date();
			if (self._conn.options.customHeaders) {
			    var headers = self._conn.options.customHeaders;
			    for (var header in headers) {
				if (headers.hasOwnProperty(header)) {
				    req.xhr.setRequestHeader(header, headers[header])
				}
			    }
			}
			req.xhr.send(req.data)
		    };
		    if (req.sends > 1) {
			var backoff = Math.min(Math.floor(Strophe.TIMEOUT * this.wait), Math.pow(req.sends, 3)) * 1000;
			setTimeout(sendFunc, backoff)
		    } else {
			sendFunc()
		    }
		    req.sends++;
		    if (this._conn.xmlOutput !== Strophe.Connection.prototype.xmlOutput) {
			if (req.xmlData.nodeName === this.strip && req.xmlData.childNodes.length) {
			    this._conn.xmlOutput(req.xmlData.childNodes[0])
			} else {
			    this._conn.xmlOutput(req.xmlData)
			}
		    }
		    if (this._conn.rawOutput !== Strophe.Connection.prototype.rawOutput) {
			this._conn.rawOutput(req.data)
		    }
		} else {
		    Strophe.debug("_processRequest: " + (i === 0 ? "first" : "second") + " request has readyState of " + req.xhr.readyState)
		}
	    },
	    _removeRequest: function(req) {
		Strophe.debug("removing request");
		var i;
		for (i = this._requests.length - 1; i >= 0; i--) {
		    if (req == this._requests[i]) {
			this._requests.splice(i, 1)
		    }
		}
		req.xhr.onreadystatechange = function() {};
		this._throttledRequestHandler()
	    },
	    _restartRequest: function(i) {
		var req = this._requests[i];
		if (req.dead === null) {
		    req.dead = new Date()
		}
		this._processRequest(i)
	    },
	    _reqToData: function(req) {
		try {
		    return req.getResponse()
		} catch (e) {
		    if (e != "parsererror") {
			throw e
		    }
		    this._conn.disconnect("strophe-parsererror")
		}
	    },
	    _sendTerminate: function(pres) {
		Strophe.info("_sendTerminate was called");
		var body = this._buildBody().attrs({
		    type: "terminate"
		});
		if (pres) {
		    body.cnode(pres.tree())
		}
		var req = new Strophe.Request(body.tree(), this._onRequestStateChange.bind(this, this._conn._dataRecv.bind(this._conn)), body.tree().getAttribute("rid"));
		this._requests.push(req);
		this._throttledRequestHandler()
	    },
	    _send: function() {
		clearTimeout(this._conn._idleTimeout);
		this._throttledRequestHandler();
		this._conn._idleTimeout = setTimeout(this._conn._onIdle.bind(this._conn), 100)
	    },
	    _sendRestart: function() {
		this._throttledRequestHandler();
		clearTimeout(this._conn._idleTimeout)
	    },
	    _throttledRequestHandler: function() {
		if (!this._requests) {
		    Strophe.debug("_throttledRequestHandler called with undefined requests")
		} else {
		    Strophe.debug("_throttledRequestHandler called with " + this._requests.length + " requests")
		}
		if (!this._requests || this._requests.length === 0) {
		    return
		}
		if (this._requests.length > 0) {
		    this._processRequest(0)
		}
		if (this._requests.length > 1 && Math.abs(this._requests[0].rid - this._requests[1].rid) < this.window) {
		    this._processRequest(1)
		}
	    }
	};
	Strophe.Websocket = function(connection) {
	    this._conn = connection;
	    this.strip = "stream:stream";
	    var service = connection.service;
	    if (service.indexOf("ws:") !== 0 && service.indexOf("wss:") !== 0) {
		var new_service = "";
		if (connection.options.protocol === "ws" && window.location.protocol !== "https:") {
		    new_service += "ws"
		} else {
		    new_service += "wss"
		}
		new_service += "://" + window.location.host;
		if (service.indexOf("/") !== 0) {
		    new_service += window.location.pathname + service
		} else {
		    new_service += service
		}
		connection.service = new_service
	    }
	};
	Strophe.Websocket.prototype = {
	    _buildStream: function() {
		return $build("stream:stream", {
		    to: this._conn.domain,
		    xmlns: Strophe.NS.CLIENT,
		    "xmlns:stream": Strophe.NS.STREAM,
		    version: "1.0"
		})
	    },
	    _check_streamerror: function(bodyWrap, connectstatus) {
		var errors = bodyWrap.getElementsByTagName("stream:error");
		if (errors.length === 0) {
		    return false
		}
		var error = errors[0];
		var condition = "";
		var text = "";
		var ns = "urn:ietf:params:xml:ns:xmpp-streams";
		for (var i = 0; i < error.childNodes.length; i++) {
		    var e = error.childNodes[i];
		    if (e.getAttribute("xmlns") !== ns) {
			break
		    }
		    if (e.nodeName === "text") {
			text = e.textContent
		    } else {
			condition = e.nodeName
		    }
		}
		var errorString = "WebSocket stream error: ";
		if (condition) {
		    errorString += condition
		} else {
		    errorString += "unknown"
		}
		if (text) {
		    errorString += " - " + condition
		}
		Strophe.error(errorString);
		this._conn._changeConnectStatus(connectstatus, condition);
		this._conn._doDisconnect();
		return true
	    },
	    _reset: function() {
		return
	    },
	    _connect: function() {
		this._closeSocket();
		this.socket = new WebSocket(this._conn.service, "xmpp");
		this.socket.onopen = this._onOpen.bind(this);
		this.socket.onerror = this._onError.bind(this);
		this.socket.onclose = this._onClose.bind(this);
		this.socket.onmessage = this._connect_cb_wrapper.bind(this)
	    },
	    _connect_cb: function(bodyWrap) {
		var error = this._check_streamerror(bodyWrap, Strophe.Status.CONNFAIL);
		if (error) {
		    return Strophe.Status.CONNFAIL
		}
	    },
	    _handleStreamStart: function(message) {
		var error = false;
		var ns = message.getAttribute("xmlns");
		if (typeof ns !== "string") {
		    error = "Missing xmlns in stream:stream"
		} else {
		    if (ns !== Strophe.NS.CLIENT) {
			error = "Wrong xmlns in stream:stream: " + ns
		    }
		}
		var ns_stream = message.namespaceURI;
		if (typeof ns_stream !== "string") {
		    error = "Missing xmlns:stream in stream:stream"
		} else {
		    if (ns_stream !== Strophe.NS.STREAM) {
			error = "Wrong xmlns:stream in stream:stream: " + ns_stream
		    }
		}
		var ver = message.getAttribute("version");
		if (typeof ver !== "string") {
		    error = "Missing version in stream:stream"
		} else {
		    if (ver !== "1.0") {
			error = "Wrong version in stream:stream: " + ver
		    }
		}
		if (error) {
		    this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, error);
		    this._conn._doDisconnect();
		    return false
		}
		return true
	    },
	    _connect_cb_wrapper: function(message) {
		if (message.data.indexOf("<stream:stream ") === 0 || message.data.indexOf("<?xml") === 0) {
		    var data = message.data.replace(/^(<\?.*?\?>\s*)*/, "");
		    if (data === "") {
			return
		    }
		    data = message.data.replace(/<stream:stream (.*[^\/])>/, "<stream:stream $1/>");
		    var streamStart = new DOMParser().parseFromString(data, "text/xml").documentElement;
		    this._conn.xmlInput(streamStart);
		    this._conn.rawInput(message.data);
		    if (this._handleStreamStart(streamStart)) {
			this._connect_cb(streamStart);
			this.streamStart = message.data.replace(/^<stream:(.*)\/>$/, "<stream:$1>")
		    }
		} else {
		    if (message.data === "</stream:stream>") {
			this._conn.rawInput(message.data);
			this._conn.xmlInput(document.createElement("stream:stream"));
			this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Received closing stream");
			this._conn._doDisconnect();
			return
		    } else {
			var string = this._streamWrap(message.data);
			var elem = new DOMParser().parseFromString(string, "text/xml").documentElement;
			this.socket.onmessage = this._onMessage.bind(this);
			this._conn._connect_cb(elem, null, message.data)
		    }
		}
	    },
	    _disconnect: function(pres) {
		if (this.socket.readyState !== WebSocket.CLOSED) {
		    if (pres) {
			this._conn.send(pres)
		    }
		    var close = "</stream:stream>";
		    this._conn.xmlOutput(document.createElement("stream:stream"));
		    this._conn.rawOutput(close);
		    try {
			this.socket.send(close)
		    } catch (e) {
			Strophe.info("Couldn't send closing stream tag.")
		    }
		}
		this._conn._doDisconnect()
	    },
	    _doDisconnect: function() {
		Strophe.info("WebSockets _doDisconnect was called");
		this._closeSocket()
	    },
	    _streamWrap: function(stanza) {
		return this.streamStart + stanza + "</stream:stream>"
	    },
	    _closeSocket: function() {
		if (this.socket) {
		    try {
			this.socket.close()
		    } catch (e) {}
		}
		this.socket = null
	    },
	    _emptyQueue: function() {
		return true
	    },
	    _onClose: function() {
		if (this._conn.connected && !this._conn.disconnecting) {
		    Strophe.error("Websocket closed unexcectedly");
		    this._conn._doDisconnect()
		} else {
		    Strophe.info("Websocket closed")
		}
	    },
	    _no_auth_received: function(_callback) {
		Strophe.error("Server did not send any auth methods");
		this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Server did not send any auth methods");
		if (_callback) {
		    _callback = _callback.bind(this._conn);
		    _callback()
		}
		this._conn._doDisconnect()
	    },
	    _onDisconnectTimeout: function() {},
	    _onError: function(error) {
		Strophe.error("Websocket error " + error);
		this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "The WebSocket connection could not be established was disconnected.");
		this._disconnect()
	    },
	    _onIdle: function() {
		var data = this._conn._data;
		if (data.length > 0 && !this._conn.paused) {
		    for (var i = 0; i < data.length; i++) {
			if (data[i] !== null) {
			    var stanza, rawStanza;
			    if (data[i] === "restart") {
				stanza = this._buildStream();
				rawStanza = this._removeClosingTag(stanza);
				stanza = stanza.tree()
			    } else {
				stanza = data[i];
				rawStanza = Strophe.serialize(stanza)
			    }
			    this._conn.xmlOutput(stanza);
			    this._conn.rawOutput(rawStanza);
			    this.socket.send(rawStanza)
			}
		    }
		    this._conn._data = []
		}
	    },
	    _onMessage: function(message) {
		var elem, data;
		if (message.data === "</stream:stream>") {
		    var close = "</stream:stream>";
		    this._conn.rawInput(close);
		    this._conn.xmlInput(document.createElement("stream:stream"));
		    if (!this._conn.disconnecting) {
			this._conn._doDisconnect()
		    }
		    return
		} else {
		    if (message.data.search("<stream:stream ") === 0) {
			data = message.data.replace(/<stream:stream (.*[^\/])>/, "<stream:stream $1/>");
			elem = new DOMParser().parseFromString(data, "text/xml").documentElement;
			if (!this._handleStreamStart(elem)) {
			    return
			}
		    } else {
			data = this._streamWrap(message.data);
			elem = new DOMParser().parseFromString(data, "text/xml").documentElement
		    }
		}
		if (this._check_streamerror(elem, Strophe.Status.ERROR)) {
		    return
		}
		if (this._conn.disconnecting && elem.firstChild.nodeName === "presence" && elem.firstChild.getAttribute("type") === "unavailable") {
		    this._conn.xmlInput(elem);
		    this._conn.rawInput(Strophe.serialize(elem));
		    return
		}
		this._conn._dataRecv(elem, message.data)
	    },
	    _onOpen: function() {
		Strophe.info("Websocket open");
		var start = this._buildStream();
		this._conn.xmlOutput(start.tree());
		var startString = this._removeClosingTag(start);
		this._conn.rawOutput(startString);
		this.socket.send(startString)
	    },
	    _removeClosingTag: function(elem) {
		var string = Strophe.serialize(elem);
		string = string.replace(/<(stream:stream .*[^\/])\/>$/, "<$1>");
		return string
	    },
	    _reqToData: function(stanza) {
		return stanza
	    },
	    _send: function() {
		this._conn.flush()
	    },
	    _sendRestart: function() {
		clearTimeout(this._conn._idleTimeout);
		this._conn._onIdle.bind(this._conn)()
	    }
	};


	/**
	 * Base class for ColibriFocus and JingleSession.
	 * @param connection Strophe connection object
	 * @param sid my session identifier(resource)
	 * @constructor
	 */

	function SessionBase(connection, sid) {
	    this.connection = connection;
	    this.sid = sid;

	    /**
	     * The indicator which determines whether the (local) video has been muted
	     * in response to a user command in contrast to an automatic decision made
	     * by the application logic.
	     */
	    this.videoMuteByUser = false;
	}


	SessionBase.prototype.modifySources = function (successCallback) {
	    var self = this;
	    if(this.peerconnection)
		this.peerconnection.modifySources(function(){
		    $(document).trigger('setLocalDescription.jingle', [self.sid]);
		    if(successCallback) {
			successCallback();
		    }
		});
	};

	SessionBase.prototype.addSource = function (elem, fromJid) {

	    var self = this;
	    // FIXME: dirty waiting
	    if (!this.peerconnection.localDescription)
	    {
		console.warn("addSource - localDescription not ready yet")
		setTimeout(function()
		    {
			self.addSource(elem, fromJid);
		    },
		    200
		);
		return;
	    }

	    this.peerconnection.addSource(elem);

	    this.modifySources();
	};

	SessionBase.prototype.removeSource = function (elem, fromJid) {

	    var self = this;
	    // FIXME: dirty waiting
	    if (!this.peerconnection.localDescription)
	    {
		console.warn("removeSource - localDescription not ready yet")
		setTimeout(function()
		    {
			self.removeSource(elem, fromJid);
		    },
		    200
		);
		return;
	    }

	    this.peerconnection.removeSource(elem);

	    this.modifySources();
	};

	/**
	 * Switches video streams.
	 * @param new_stream new stream that will be used as video of this session.
	 * @param oldStream old video stream of this session.
	 * @param success_callback callback executed after successful stream switch.
	 */
	SessionBase.prototype.switchStreams = function (new_stream, oldStream, success_callback) {

	    var self = this;

	    // Stop the stream to trigger onended event for old stream
	    oldStream.stop();

	    // Remember SDP to figure out added/removed SSRCs
	    var oldSdp = null;
	    if(self.peerconnection) {
		if(self.peerconnection.localDescription) {
		    oldSdp = new SDP(self.peerconnection.localDescription.sdp);
		}
		self.peerconnection.removeStream(oldStream, true);
		self.peerconnection.addStream(new_stream);
	    }

	    self.connection.jingle.localVideo = new_stream;

	    self.connection.jingle.localStreams = [];

	    //in firefox we have only one stream object
	    if(self.connection.jingle.localAudio != self.connection.jingle.localVideo)
		self.connection.jingle.localStreams.push(self.connection.jingle.localAudio);
	    self.connection.jingle.localStreams.push(self.connection.jingle.localVideo);

	    // Conference is not active
	    if(!oldSdp || !self.peerconnection) {
		success_callback();
		return;
	    }

	    self.peerconnection.switchstreams = true;
	    self.modifySources(function() {
		//console.log('modify sources done');

		success_callback();

		var newSdp = new SDP(self.peerconnection.localDescription.sdp);
		//console.log("SDPs", oldSdp, newSdp);
		self.notifyMySSRCUpdate(oldSdp, newSdp);
	    });
	};

	/**
	 * Figures out added/removed ssrcs and send update IQs.
	 * @param old_sdp SDP object for old description.
	 * @param new_sdp SDP object for new description.
	 */
	SessionBase.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {

	    var old_media = old_sdp.getMediaSsrcMap();
	    var new_media = new_sdp.getMediaSsrcMap();
	    //console.log("old/new medias: ", old_media, new_media);

	    var toAdd = old_sdp.getNewMedia(new_sdp);
	    var toRemove = new_sdp.getNewMedia(old_sdp);
	    //console.log("to add", toAdd);
	    //console.log("to remove", toRemove);
	    if(Object.keys(toRemove).length > 0){
		this.sendSSRCUpdate(toRemove, null, false);
	    }
	    if(Object.keys(toAdd).length > 0){
		this.sendSSRCUpdate(toAdd, null, true);
	    }
	};

	/**
	 * Empty method that does nothing by default. It should send SSRC update IQs to session participants.
	 * @param sdpMediaSsrcs array of
	 * @param fromJid
	 * @param isAdd
	 */
	SessionBase.prototype.sendSSRCUpdate = function(sdpMediaSsrcs, fromJid, isAdd) {
	    //FIXME: put default implementation here(maybe from JingleSession?)
	}

	/**
	 * Sends SSRC update IQ.
	 * @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
	 * @param sid session identifier that will be put into the IQ.
	 * @param initiator initiator identifier.
	 * @param toJid destination Jid
	 * @param isAdd indicates if this is remove or add operation.
	 */
	SessionBase.prototype.sendSSRCUpdateIq = function(sdpMediaSsrcs, sid, initiator, toJid, isAdd) {

	    var self = this;
	    var modify = $iq({to: toJid, type: 'set'})
		.c('jingle', {
		    xmlns: 'urn:xmpp:jingle:1',
		    action: isAdd ? 'source-add' : 'source-remove',
		    initiator: initiator,
		    sid: sid
		}
	    );
	    // FIXME: only announce video ssrcs since we mix audio and dont need
	    //      the audio ssrcs therefore
	    var modified = false;
	    Object.keys(sdpMediaSsrcs).forEach(function(channelNum){
		modified = true;
		var channel = sdpMediaSsrcs[channelNum];
		modify.c('content', {name: channel.mediaType});

		modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: channel.mediaType});
		// FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
		// generate sources from lines
		Object.keys(channel.ssrcs).forEach(function(ssrcNum) {
		    var mediaSsrc = channel.ssrcs[ssrcNum];
		    modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
		    modify.attrs({ssrc: mediaSsrc.ssrc});
		    // iterate over ssrc lines
		    mediaSsrc.lines.forEach(function (line) {
			var idx = line.indexOf(' ');
			var kv = line.substr(idx + 1);
			modify.c('parameter');
			if (kv.indexOf(':') == -1) {
			    modify.attrs({ name: kv });
			} else {
			    modify.attrs({ name: kv.split(':', 2)[0] });
			    modify.attrs({ value: kv.split(':', 2)[1] });
			}
			modify.up(); // end of parameter
		    });
		    modify.up(); // end of source
		});

		// generate source groups from lines
		channel.ssrcGroups.forEach(function(ssrcGroup) {
		    if (ssrcGroup.ssrcs.length != 0) {

			modify.c('ssrc-group', {
			    semantics: ssrcGroup.semantics,
			    xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
			});

			ssrcGroup.ssrcs.forEach(function (ssrc) {
			    modify.c('source', { ssrc: ssrc })
				.up(); // end of source
			});
			modify.up(); // end of ssrc-group
		    }
		});

		modify.up(); // end of description
		modify.up(); // end of content
	    });
	    if (modified) {
		self.connection.sendIQ(modify,
		    function (res) {
			//console.info('got modify result', res);
		    },
		    function (err) {
			console.error('got modify error', err);
		    }
		);
	    } else {
		//console.log('modification not necessary');
	    }
	};

	/**
	 * Determines whether the (local) video is mute i.e. all video tracks are
	 * disabled.
	 *
	 * @return <tt>true</tt> if the (local) video is mute i.e. all video tracks are
	 * disabled; otherwise, <tt>false</tt>
	 */
	SessionBase.prototype.isVideoMute = function () {
	    var tracks = connection.jingle.localVideo.getVideoTracks();
	    var mute = true;

	    for (var i = 0; i < tracks.length; ++i) {
		if (tracks[i].enabled) {
		    mute = false;
		    break;
		}
	    }
	    return mute;
	};

	/**
	 * Mutes/unmutes the (local) video i.e. enables/disables all video tracks.
	 *
	 * @param mute <tt>true</tt> to mute the (local) video i.e. to disable all video
	 * tracks; otherwise, <tt>false</tt>
	 * @param callback a function to be invoked with <tt>mute</tt> after all video
	 * tracks have been enabled/disabled. The function may, optionally, return
	 * another function which is to be invoked after the whole mute/unmute operation
	 * has completed successfully.
	 * @param options an object which specifies optional arguments such as the
	 * <tt>boolean</tt> key <tt>byUser</tt> with default value <tt>true</tt> which
	 * specifies whether the method was initiated in response to a user command (in
	 * contrast to an automatic decision made by the application logic)
	 */
	SessionBase.prototype.setVideoMute = function (mute, callback, options) {
	    var byUser;

	    if (options) {
		byUser = options.byUser;
		if (typeof byUser === 'undefined') {
		    byUser = true;
		} 
	    } else {
		byUser = true;
	    }
	    // The user's command to mute the (local) video takes precedence over any
	    // automatic decision made by the application logic.
	    if (byUser) {
		this.videoMuteByUser = mute;
	    } else if (this.videoMuteByUser) {
		return;
	    }
	    if (mute == this.isVideoMute())
	    {
		// Even if no change occurs, the specified callback is to be executed.
		// The specified callback may, optionally, return a successCallback
		// which is to be executed as well.
		var successCallback = callback(mute);

		if (successCallback) {
		    successCallback();
		}
	    } else {
		var tracks = connection.jingle.localVideo.getVideoTracks();

		for (var i = 0; i < tracks.length; ++i) {
		    tracks[i].enabled = !mute;
		}

		if (this.peerconnection) {
		    this.peerconnection.hardMuteVideo(mute);
		}

		this.modifySources(callback(mute));
	    }
	};

	// SDP-based mute by going recvonly/sendrecv
	// FIXME: should probably black out the screen as well
	SessionBase.prototype.toggleVideoMute = function (callback) {
	    setVideoMute(isVideoMute(), callback);
	};


	/**
	 * Strophe.JingleSession
	 */


	JingleSession.prototype = Object.create(SessionBase.prototype);
	function JingleSession(me, sid, connection) {

	    SessionBase.call(this, connection, sid);

	    this.me = me;
	    this.initiator = null;
	    this.responder = null;
	    this.isInitiator = null;
	    this.peerjid = null;
	    this.state = null;
	    this.localSDP = null;
	    this.remoteSDP = null;
	    this.localStreams = [];
	    this.relayedStreams = [];
	    this.remoteStreams = [];
	    this.startTime = null;
	    this.stopTime = null;
	    this.media_constraints = null;
	    this.pc_constraints = null;
	    this.ice_config = {};
	    this.drip_container = [];

	    this.usetrickle = true;
	    this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
	    this.usedrip = false; // dripping is sending trickle candidates not one-by-one

	    this.hadstuncandidate = false;
	    this.hadturncandidate = false;
	    this.lasticecandidate = false;

	    this.statsinterval = null;

	    this.reason = null;

	    this.wait = true;
	    this.localStreamsSSRC = null;
	}

	JingleSession.prototype.initiate = function (peerjid, isInitiator) {
	    var self = this;
	    if (this.state !== null) {
		console.error('attempt to initiate on session ' + this.sid +
		    'in state ' + this.state);
		return;
	    }
	    this.isInitiator = isInitiator;
	    this.state = 'pending';
	    this.initiator = isInitiator ? this.me : peerjid;
	    this.responder = !isInitiator ? this.me : peerjid;
	    this.peerjid = peerjid;
	    this.hadstuncandidate = false;
	    this.hadturncandidate = false;
	    this.lasticecandidate = false;

	    this.peerconnection
		= new TraceablePeerConnection(
		    this.connection.jingle.ice_config,
		    this.connection.jingle.pc_constraints );

	    this.peerconnection.onicecandidate = function (event) {
		self.sendIceCandidate(event.candidate);
	    };
	    this.peerconnection.onaddstream = function (event) {
		self.remoteStreams.push(event.stream);
		//console.log("REMOTE STREAM ADDED: " + event.stream + " - " + event.stream.id);
		$(document).trigger('remotestreamadded.jingle', [event, self.sid]);
	    };
	    this.peerconnection.onremovestream = function (event) {
		// Remove the stream from remoteStreams
		var streamIdx = self.remoteStreams.indexOf(event.stream);
		if(streamIdx !== -1){
		    self.remoteStreams.splice(streamIdx, 1);
		}
		// FIXME: remotestreamremoved.jingle not defined anywhere(unused)
		$(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
	    };
	    this.peerconnection.onsignalingstatechange = function (event) {
		if (!(self && self.peerconnection)) return;
	    };
	    this.peerconnection.oniceconnectionstatechange = function (event) {
		if (!(self && self.peerconnection)) return;
		switch (self.peerconnection.iceConnectionState) {
		    case 'connected':
			this.startTime = new Date();
			break;
		    case 'disconnected':
			this.stopTime = new Date();
			break;
		}
		$(document).trigger('iceconnectionstatechange.jingle', [self.sid, self]);
	    };
	    // add any local and relayed stream
	    this.localStreams.forEach(function(stream) {
		self.peerconnection.addStream(stream);
	    });
	    this.relayedStreams.forEach(function(stream) {
		self.peerconnection.addStream(stream);
	    });
	};

	JingleSession.prototype.accept = function () {
	    var self = this;
	    this.state = 'active';

	    var pranswer = this.peerconnection.localDescription;
	    if (!pranswer || pranswer.type != 'pranswer') {
		return;
	    }
	    //console.log('going from pranswer to answer');
	    if (this.usetrickle) {
		// remove candidates already sent from session-accept
		var lines = SDPUtil.find_lines(pranswer.sdp, 'a=candidate:');
		for (var i = 0; i < lines.length; i++) {
		    pranswer.sdp = pranswer.sdp.replace(lines[i] + '\r\n', '');
		}
	    }
	    while (SDPUtil.find_line(pranswer.sdp, 'a=inactive')) {
		// FIXME: change any inactive to sendrecv or whatever they were originally
		pranswer.sdp = pranswer.sdp.replace('a=inactive', 'a=sendrecv');
	    }
	    pranswer = simulcast.reverseTransformLocalDescription(pranswer);
	    var prsdp = new SDP(pranswer.sdp);
	    var accept = $iq({to: this.peerjid,
		type: 'set'})
		.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
		    action: 'session-accept',
		    initiator: this.initiator,
		    responder: this.responder,
		    sid: this.sid });
	    prsdp.toJingle(accept, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
	    var sdp = this.peerconnection.localDescription.sdp;
	    while (SDPUtil.find_line(sdp, 'a=inactive')) {
		// FIXME: change any inactive to sendrecv or whatever they were originally
		sdp = sdp.replace('a=inactive', 'a=sendrecv');
	    }
	    this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: this.setBandwidth(sdp)}), // BAO
		function () {
		    //console.log('setLocalDescription success');
		    $(document).trigger('setLocalDescription.jingle', [self.sid]);

		    this.connection.sendIQ(accept,
			function () {
			    var ack = {};
			    ack.source = 'answer';
			    $(document).trigger('ack.jingle', [self.sid, ack]);
			},
			function (stanza) {
			    var error = ($(stanza).find('error').length) ? {
				code: $(stanza).find('error').attr('code'),
				reason: $(stanza).find('error :first')[0].tagName
			    }:{};
			    error.source = 'answer';
			    $(document).trigger('error.jingle', [self.sid, error]);
			});
		},
		function (e) {
		    console.error('setLocalDescription failed', e);
		}
	    );
	};

	/**
	 * Implements SessionBase.sendSSRCUpdate.
	 */
	JingleSession.prototype.sendSSRCUpdate = function(sdpMediaSsrcs, fromJid, isadd) {

	    var self = this;
	    //console.log('tell', self.peerjid, 'about ' + (isadd ? 'new' : 'removed') + ' ssrcs from' + self.me);

	    if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')){
		//console.log("Too early to send updates");
		return;
	    }

	    this.sendSSRCUpdateIq(sdpMediaSsrcs, self.sid, self.initiator, self.peerjid, isadd);
	};

	JingleSession.prototype.terminate = function (reason) {
	    this.state = 'ended';
	    this.reason = reason;
	    this.peerconnection.close();
	    if (this.statsinterval !== null) {
		window.clearInterval(this.statsinterval);
		this.statsinterval = null;
	    }
	};

	JingleSession.prototype.active = function () {
	    return this.state == 'active';
	};

	JingleSession.prototype.sendIceCandidate = function (candidate) {
	    var self = this;
	    if (candidate && !this.lasticecandidate) {
		var ice = SDPUtil.iceparams(this.localSDP.media[candidate.sdpMLineIndex], this.localSDP.session);
		var jcand = SDPUtil.candidateToJingle(candidate.candidate);
		if (!(ice && jcand)) {
		    console.error('failed to get ice && jcand');
		    return;
		}
		ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';

		if (jcand.type === 'srflx') {
		    this.hadstuncandidate = true;
		} else if (jcand.type === 'relay') {
		    this.hadturncandidate = true;
		}

		if (this.usetrickle) {
		    if (this.usedrip) {
			if (this.drip_container.length === 0) {
			    // start 20ms callout
			    window.setTimeout(function () {
				if (self.drip_container.length === 0) return;
				self.sendIceCandidates(self.drip_container);
				self.drip_container = [];
			    }, 20);

			}
			this.drip_container.push(candidate);
			return;
		    } else {
			self.sendIceCandidate([candidate]);
		    }
		}
	    } else {
		//console.log('sendIceCandidate: last candidate.');
		if (!this.usetrickle) {
		    //console.log('should send full offer now...');
		    var init = $iq({to: this.peerjid,
			type: 'set'})
			.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
			    action: this.peerconnection.localDescription.type == 'offer' ? 'session-initiate' : 'session-accept',
			    initiator: this.initiator,
			    sid: this.sid});
		    this.localSDP = new SDP(this.peerconnection.localDescription.sdp);
		    var self = this;
		    var sendJingle = function (ssrc) {
			if(!ssrc)
			    ssrc = {};
			self.localSDP.toJingle(init, self.initiator == self.me ? 'initiator' : 'responder', ssrc);
			self.connection.sendIQ(init,
			    function () {
				//console.log('session initiate ack');
				var ack = {};
				ack.source = 'offer';
				$(document).trigger('ack.jingle', [self.sid, ack]);
			    },
			    function (stanza) {
				self.state = 'error';
				self.peerconnection.close();
				var error = ($(stanza).find('error').length) ? {
				    code: $(stanza).find('error').attr('code'),
				    reason: $(stanza).find('error :first')[0].tagName,
				}:{};
				error.source = 'offer';
				$(document).trigger('error.jingle', [self.sid, error]);
			    });
		    }

		    RTC.getLocalSSRC(this, function (ssrcs) {
			if(ssrcs)
			{
			    sendJingle(ssrcs);
			    $(document).trigger("setLocalDescription.jingle", [self.sid]);
			}
			else
			{
			    sendJingle();
			}
		    });

		}
		this.lasticecandidate = true;
		//console.log('Have we encountered any srflx candidates? ' + this.hadstuncandidate);
		//console.log('Have we encountered any relay candidates? ' + this.hadturncandidate);

		if (!(this.hadstuncandidate || this.hadturncandidate) && this.peerconnection.signalingState != 'closed') {
		    $(document).trigger('nostuncandidates.jingle', [this.sid]);
		}
	    }
	};

	JingleSession.prototype.sendIceCandidates = function (candidates) {
	    //console.log('sendIceCandidates', candidates);
	    var cand = $iq({to: this.peerjid, type: 'set'})
		.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
		    action: 'transport-info',
		    initiator: this.initiator,
		    sid: this.sid});
	    for (var mid = 0; mid < this.localSDP.media.length; mid++) {
		var cands = candidates.filter(function (el) { return el.sdpMLineIndex == mid; });
		var mline = SDPUtil.parse_mline(this.localSDP.media[mid].split('\r\n')[0]);
		if (cands.length > 0) {
		    var ice = SDPUtil.iceparams(this.localSDP.media[mid], this.localSDP.session);
		    ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
		    cand.c('content', {creator: this.initiator == this.me ? 'initiator' : 'responder',
			name: (cands[0].sdpMid? cands[0].sdpMid : mline.media)
		    }).c('transport', ice);
		    for (var i = 0; i < cands.length; i++) {
			cand.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
		    }
		    // add fingerprint
		    if (SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)) {
			var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session));
			tmp.required = true;
			cand.c(
			    'fingerprint',
			    {xmlns: 'urn:xmpp:jingle:apps:dtls:0'})
			    .t(tmp.fingerprint);
			delete tmp.fingerprint;
			cand.attrs(tmp);
			cand.up();
		    }
		    cand.up(); // transport
		    cand.up(); // content
		}
	    }
	    // might merge last-candidate notification into this, but it is called alot later. See webrtc issue #2340
	    //console.log('was this the last candidate', this.lasticecandidate);
	    this.connection.sendIQ(cand,
		function () {
		    var ack = {};
		    ack.source = 'transportinfo';
		    $(document).trigger('ack.jingle', [this.sid, ack]);
		},
		function (stanza) {
		    var error = ($(stanza).find('error').length) ? {
			code: $(stanza).find('error').attr('code'),
			reason: $(stanza).find('error :first')[0].tagName,
		    }:{};
		    error.source = 'transportinfo';
		    $(document).trigger('error.jingle', [this.sid, error]);
		});
	};


	JingleSession.prototype.sendOffer = function () {
	    //console.log('sendOffer...');
	    var self = this;
	    this.peerconnection.createOffer(function (sdp) {
		    self.createdOffer(sdp);
		},
		function (e) {
		    console.error('createOffer failed', e);
		},
		this.media_constraints
	    );
	};

	JingleSession.prototype.createdOffer = function (sdp) {
	    //console.log('createdOffer', sdp);
	    var self = this;
	    this.localSDP = new SDP(sdp.sdp);
	    //this.localSDP.mangle();
	    var sendJingle = function () {
		var init = $iq({to: this.peerjid,
		    type: 'set'})
		    .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
			action: 'session-initiate',
			initiator: this.initiator,
			sid: this.sid});
		this.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
		this.connection.sendIQ(init,
		    function () {
			var ack = {};
			ack.source = 'offer';
			$(document).trigger('ack.jingle', [self.sid, ack]);
		    },
		    function (stanza) {
			self.state = 'error';
			self.peerconnection.close();
			var error = ($(stanza).find('error').length) ? {
			    code: $(stanza).find('error').attr('code'),
			    reason: $(stanza).find('error :first')[0].tagName,
			}:{};
			error.source = 'offer';
			$(document).trigger('error.jingle', [self.sid, error]);
		    });
	    }
	    sdp.sdp = this.setBandwidth(this.localSDP.raw); // BAO

	    this.peerconnection.setLocalDescription(sdp,
		function () {
		    if(this.usetrickle)
		    {
			RTC.getLocalSSRC(function(ssrc)
			{
			    sendJingle(ssrc);
			    $(document).trigger('setLocalDescription.jingle', [self.sid]);
			});
		    }
		    else
			$(document).trigger('setLocalDescription.jingle', [self.sid]);
		    //console.log('setLocalDescription success');
		},
		function (e) {
		    console.error('setLocalDescription failed', e);
		}
	    );
	    var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
	    for (var i = 0; i < cands.length; i++) {
		var cand = SDPUtil.parse_icecandidate(cands[i]);
		if (cand.type == 'srflx') {
		    this.hadstuncandidate = true;
		} else if (cand.type == 'relay') {
		    this.hadturncandidate = true;
		}
	    }
	};

	JingleSession.prototype.setRemoteDescription = function (elem, desctype) {
	    //console.log('setting remote description... ', desctype);
	    this.remoteSDP = new SDP('');
	    this.remoteSDP.fromJingle(elem);
	    if (this.peerconnection.remoteDescription !== null) {
		//console.log('setRemoteDescription when remote description is not null, should be pranswer', this.peerconnection.remoteDescription);
		if (this.peerconnection.remoteDescription.type == 'pranswer') {
		    var pranswer = new SDP(this.peerconnection.remoteDescription.sdp);
		    for (var i = 0; i < pranswer.media.length; i++) {
			// make sure we have ice ufrag and pwd
			if (!SDPUtil.find_line(this.remoteSDP.media[i], 'a=ice-ufrag:', this.remoteSDP.session)) {
			    if (SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session)) {
				this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session) + '\r\n';
			    } else {
				console.warn('no ice ufrag?');
			    }
			    if (SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session)) {
				this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session) + '\r\n';
			    } else {
				console.warn('no ice pwd?');
			    }
			}
			// copy over candidates
			var lines = SDPUtil.find_lines(pranswer.media[i], 'a=candidate:');
			for (var j = 0; j < lines.length; j++) {
			    this.remoteSDP.media[i] += lines[j] + '\r\n';
			}
		    }
		    this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
		}
	    }
	    var remotedesc = new RTCSessionDescription({type: desctype, sdp: this.remoteSDP.raw});

	    this.peerconnection.setRemoteDescription(remotedesc,
		function () {
		    //console.log('setRemoteDescription success');
		},
		function (e) {
		    console.error('setRemoteDescription error', e);
		    $(document).trigger('fatalError.jingle', [self, e]);
		}
	    );
	};

	JingleSession.prototype.addIceCandidate = function (elem) {
	    var self = this;
	    if (this.peerconnection.signalingState == 'closed') {
		return;
	    }
	    if (!this.peerconnection.remoteDescription && this.peerconnection.signalingState == 'have-local-offer') {
		//console.log('trickle ice candidate arriving before session accept...');
		// create a PRANSWER for setRemoteDescription
		if (!this.remoteSDP) {
		    var cobbled = 'v=0\r\n' +
			'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
			's=-\r\n' +
			't=0 0\r\n';
		    // first, take some things from the local description
		    for (var i = 0; i < this.localSDP.media.length; i++) {
			cobbled += SDPUtil.find_line(this.localSDP.media[i], 'm=') + '\r\n';
			cobbled += SDPUtil.find_lines(this.localSDP.media[i], 'a=rtpmap:').join('\r\n') + '\r\n';
			if (SDPUtil.find_line(this.localSDP.media[i], 'a=mid:')) {
			    cobbled += SDPUtil.find_line(this.localSDP.media[i], 'a=mid:') + '\r\n';
			}
			cobbled += 'a=inactive\r\n';
		    }
		    this.remoteSDP = new SDP(cobbled);
		}
		// then add things like ice and dtls from remote candidate
		elem.each(function () {
		    for (var i = 0; i < self.remoteSDP.media.length; i++) {
			if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
			    self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
			    if (!SDPUtil.find_line(self.remoteSDP.media[i], 'a=ice-ufrag:')) {
				var tmp = $(this).find('transport');
				self.remoteSDP.media[i] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
				self.remoteSDP.media[i] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n';
				tmp = $(this).find('transport>fingerprint');
				if (tmp.length) {
				    self.remoteSDP.media[i] += 'a=fingerprint:' + tmp.attr('hash') + ' ' + tmp.text() + '\r\n';
				} else {
				    //console.log('no dtls fingerprint (webrtc issue #1718?)');
				    self.remoteSDP.media[i] += 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:BAADBAADBAADBAADBAADBAADBAADBAADBAADBAAD\r\n';
				}
				break;
			    }
			}
		    }
		});
		this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');

		// we need a complete SDP with ice-ufrag/ice-pwd in all parts
		// this makes the assumption that the PRANSWER is constructed such that the ice-ufrag is in all mediaparts
		// but it could be in the session part as well. since the code above constructs this sdp this can't happen however
		var iscomplete = this.remoteSDP.media.filter(function (mediapart) {
		    return SDPUtil.find_line(mediapart, 'a=ice-ufrag:');
		}).length == this.remoteSDP.media.length;

		if (iscomplete) {
		    //console.log('setting pranswer');
		    try {
			this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'pranswer', sdp: this.remoteSDP.raw }),
			    function() {
			    },
			    function(e) {
				//console.log('setRemoteDescription pranswer failed', e.toString());
			    });
		    } catch (e) {
			console.error('setting pranswer failed', e);
		    }
		} else {
		    //console.log('not yet setting pranswer');
		}
	    }
	    // operate on each content element
	    elem.each(function () {
		// would love to deactivate this, but firefox still requires it
		var idx = -1;
		var i;
		for (i = 0; i < self.remoteSDP.media.length; i++) {
		    if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
			self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
			idx = i;
			break;
		    }
		}
		if (idx == -1) { // fall back to localdescription
		    for (i = 0; i < self.localSDP.media.length; i++) {
			if (SDPUtil.find_line(self.localSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
			    self.localSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
			    idx = i;
			    break;
			}
		    }
		}
		var name = $(this).attr('name');
		// TODO: check ice-pwd and ice-ufrag?
		$(this).find('transport>candidate').each(function () {
		    var line, candidate;
		    line = SDPUtil.candidateFromJingle(this);
		    candidate = new RTCIceCandidate({sdpMLineIndex: idx,
			sdpMid: name,
			candidate: line});
		    try {
			self.peerconnection.addIceCandidate(candidate);
		    } catch (e) {
			console.error('addIceCandidate failed', e.toString(), line);
		    }
		});
	    });
	};

	JingleSession.prototype.sendAnswer = function (provisional) {
	    //console.log('createAnswer', provisional);
	    var self = this;
	    this.peerconnection.createAnswer(
		function (sdp) {
		    self.createdAnswer(sdp, provisional);
		},
		function (e) {
		    console.error('createAnswer failed', e);
		},
		this.media_constraints
	    );
	};

	JingleSession.prototype.createdAnswer = function (sdp, provisional) {
	    //console.log('createAnswer callback');
	    var self = this;
	    this.localSDP = new SDP(sdp.sdp);
	    //this.localSDP.mangle();
	    this.usepranswer = provisional === true;
	    if (this.usetrickle) {
		if (this.usepranswer) {
		    sdp.type = 'pranswer';
		    for (var i = 0; i < this.localSDP.media.length; i++) {
			this.localSDP.media[i] = this.localSDP.media[i].replace('a=sendrecv\r\n', 'a=inactive\r\n');
		    }
		    this.localSDP.raw = this.localSDP.session + '\r\n' + this.localSDP.media.join('');
		}
	    }
	    var self = this;
	    var sendJingle = function (ssrcs) {

			var accept = $iq({to: self.peerjid,
			    type: 'set'})
			    .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
				action: 'session-accept',
				initiator: self.initiator,
				responder: self.responder,
				sid: self.sid });
			var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp);
			var publicLocalSDP = new SDP(publicLocalDesc.sdp);
			publicLocalSDP.toJingle(accept, self.initiator == self.me ? 'initiator' : 'responder', ssrcs);
			self.connection.sendIQ(accept,
			    function () {
				var ack = {};
				ack.source = 'answer';
				$(document).trigger('ack.jingle', [self.sid, ack]);
			    },
			    function (stanza) {
				var error = ($(stanza).find('error').length) ? {
				    code: $(stanza).find('error').attr('code'),
				    reason: $(stanza).find('error :first')[0].tagName,
				}:{};
				error.source = 'answer';
				$(document).trigger('error.jingle', [self.sid, error]);
			    });
	    }
	    sdp.sdp = this.setBandwidth(this.localSDP.raw); //BAO
	    this.peerconnection.setLocalDescription(sdp,
		function () {

		    //console.log('setLocalDescription success');
		    if (self.usetrickle && !self.usepranswer) {
			RTC.getLocalSSRC(self, function (ssrc) {
			    sendJingle(ssrc);
			    $(document).trigger('setLocalDescription.jingle', [self.sid]);
			});
		    }
		    else
			$(document).trigger('setLocalDescription.jingle', [self.sid]);
		},
		function (e) {
		    console.error('setLocalDescription failed', e);
		}
	    );
	    var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
	    for (var j = 0; j < cands.length; j++) {
		var cand = SDPUtil.parse_icecandidate(cands[j]);
		if (cand.type == 'srflx') {
		    this.hadstuncandidate = true;
		} else if (cand.type == 'relay') {
		    this.hadturncandidate = true;
		}
	    }
	};

	JingleSession.prototype.sendTerminate = function (reason, text) {
	    var self = this,
		term = $iq({to: this.peerjid,
		    type: 'set'})
		    .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
			action: 'session-terminate',
			initiator: this.initiator,
			sid: this.sid})
		    .c('reason')
		    .c(reason || 'success');

	    if (text) {
		term.up().c('text').t(text);
	    }

	    this.connection.sendIQ(term,
		function () {
		    self.peerconnection.close();
		    self.peerconnection = null;
		    self.terminate();
		    var ack = {};
		    ack.source = 'terminate';
		    $(document).trigger('ack.jingle', [self.sid, ack]);
		},
		function (stanza) {
		    var error = ($(stanza).find('error').length) ? {
			code: $(stanza).find('error').attr('code'),
			reason: $(stanza).find('error :first')[0].tagName,
		    }:{};
		    $(document).trigger('ack.jingle', [self.sid, error]);
		});
	    if (this.statsinterval !== null) {
		window.clearInterval(this.statsinterval);
		this.statsinterval = null;
	    }
	};

	JingleSession.prototype.sendMute = function (muted, content) {
	    var info = $iq({to: this.peerjid,
		type: 'set'})
		.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
		    action: 'session-info',
		    initiator: this.initiator,
		    sid: this.sid });
	    info.c(muted ? 'mute' : 'unmute', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
	    info.attrs({'creator': this.me == this.initiator ? 'creator' : 'responder'});
	    if (content) {
		info.attrs({'name': content});
	    }
	    this.connection.send(info);
	};

	JingleSession.prototype.sendRinging = function () {
	    var info = $iq({to: this.peerjid,
		type: 'set'})
		.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
		    action: 'session-info',
		    initiator: this.initiator,
		    sid: this.sid });
	    info.c('ringing', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
	    this.connection.send(info);
	};

	JingleSession.prototype.getStats = function (interval) {
	    var self = this;
	    var recv = {audio: 0, video: 0};
	    var lost = {audio: 0, video: 0};
	    var lastrecv = {audio: 0, video: 0};
	    var lastlost = {audio: 0, video: 0};
	    var loss = {audio: 0, video: 0};
	    var delta = {audio: 0, video: 0};
	    this.statsinterval = window.setInterval(function () {
		if (self && self.peerconnection && self.peerconnection.getStats) {
		    self.peerconnection.getStats(function (stats) {
			var results = stats.result();
			// TODO: there are so much statistics you can get from this..
			for (var i = 0; i < results.length; ++i) {
			    if (results[i].type == 'ssrc') {
				var packetsrecv = results[i].stat('packetsReceived');
				var packetslost = results[i].stat('packetsLost');
				if (packetsrecv && packetslost) {
				    packetsrecv = parseInt(packetsrecv, 10);
				    packetslost = parseInt(packetslost, 10);

				    if (results[i].stat('googFrameRateReceived')) {
					lastlost.video = lost.video;
					lastrecv.video = recv.video;
					recv.video = packetsrecv;
					lost.video = packetslost;
				    } else {
					lastlost.audio = lost.audio;
					lastrecv.audio = recv.audio;
					recv.audio = packetsrecv;
					lost.audio = packetslost;
				    }
				}
			    }
			}
			delta.audio = recv.audio - lastrecv.audio;
			delta.video = recv.video - lastrecv.video;
			loss.audio = (delta.audio > 0) ? Math.ceil(100 * (lost.audio - lastlost.audio) / delta.audio) : 0;
			loss.video = (delta.video > 0) ? Math.ceil(100 * (lost.video - lastlost.video) / delta.video) : 0;
			$(document).trigger('packetloss.jingle', [self.sid, loss]);
		    });
		}
	    }, interval || 3000);
	    return this.statsinterval;
	};

	// BAO

	JingleSession.prototype.setBandwidth = function (sdp) 
	{
	    // remove existing bandwidth lines
	    //sdp = sdp.replace( /b=AS([^\r\n]+\r\n)/g , '');

	    // audio bandwidth 
	    sdp = sdp.replace( /a=mid:audio\r\n/g , 'a=mid:audio\r\nb=AS:' + config.audioBandwidth + '\r\n');

	    // video bandwidth
	    sdp = sdp.replace( /a=mid:video\r\n/g , 'a=mid:video\r\nb=AS:' + config.videoBandwidth + '\r\n');

	    return sdp;
	}


	/**
	 * Strophe.jingle.sdp.util
	 */

	/**
	 * Contains utility classes used in SDP class.
	 *
	 */

	/**
	 * Class holds a=ssrc lines and media type a=mid
	 * @param ssrc synchronization source identifier number(a=ssrc lines from SDP)
	 * @param type media type eg. "audio" or "video"(a=mid frm SDP)
	 * @constructor
	 */
	function ChannelSsrc(ssrc, type) {
	    this.ssrc = ssrc;
	    this.type = type;
	    this.lines = [];
	}

	/**
	 * Class holds a=ssrc-group: lines
	 * @param semantics
	 * @param ssrcs
	 * @constructor
	 */
	function ChannelSsrcGroup(semantics, ssrcs, line) {
	    this.semantics = semantics;
	    this.ssrcs = ssrcs;
	}

	/**
	 * Helper class represents media channel. Is a container for ChannelSsrc, holds channel idx and media type.
	 * @param channelNumber channel idx in SDP media array.
	 * @param mediaType media type(a=mid)
	 * @constructor
	 */
	function MediaChannel(channelNumber, mediaType) {
	    /**
	     * SDP channel number
	     * @type {*}
	     */
	    this.chNumber = channelNumber;
	    /**
	     * Channel media type(a=mid)
	     * @type {*}
	     */
	    this.mediaType = mediaType;
	    /**
	     * The maps of ssrc numbers to ChannelSsrc objects.
	     */
	    this.ssrcs = {};

	    /**
	     * The array of ChannelSsrcGroup objects.
	     * @type {Array}
	     */
	    this.ssrcGroups = [];
	}

	SDPUtil = {
	    iceparams: function (mediadesc, sessiondesc) {
		var data = null;
		if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
		    SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
		    data = {
			ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
			pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
		    };
		}
		return data;
	    },
	    parse_iceufrag: function (line) {
		return line.substring(12);
	    },
	    build_iceufrag: function (frag) {
		return 'a=ice-ufrag:' + frag;
	    },
	    parse_icepwd: function (line) {
		return line.substring(10);
	    },
	    build_icepwd: function (pwd) {
		return 'a=ice-pwd:' + pwd;
	    },
	    parse_mid: function (line) {
		return line.substring(6);
	    },
	    parse_mline: function (line) {
		var parts = line.substring(2).split(' '),
		    data = {};
		data.media = parts.shift();
		data.port = parts.shift();
		data.proto = parts.shift();
		if (parts[parts.length - 1] === '') { // trailing whitespace
		    parts.pop();
		}
		data.fmt = parts;
		return data;
	    },
	    build_mline: function (mline) {
		return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
	    },
	    parse_rtpmap: function (line) {
		var parts = line.substring(9).split(' '),
		    data = {};
		data.id = parts.shift();
		parts = parts[0].split('/');
		data.name = parts.shift();
		data.clockrate = parts.shift();
		data.channels = parts.length ? parts.shift() : '1';
		return data;
	    },
	    /**
	     * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
	     * @param line eg. "a=sctpmap:5000 webrtc-datachannel"
	     * @returns [SCTP port number, protocol, streams]
	     */
	    parse_sctpmap: function (line)
	    {
		var parts = line.substring(10).split(' ');
		var sctpPort = parts[0];
		var protocol = parts[1];
		// Stream count is optional
		var streamCount = parts.length > 2 ? parts[2] : null;
		return [sctpPort, protocol, streamCount];// SCTP port
	    },
	    build_rtpmap: function (el) {
		var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
		if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
		    line += '/' + el.getAttribute('channels');
		}
		return line;
	    },
	    parse_crypto: function (line) {
		var parts = line.substring(9).split(' '),
		    data = {};
		data.tag = parts.shift();
		data['crypto-suite'] = parts.shift();
		data['key-params'] = parts.shift();
		if (parts.length) {
		    data['session-params'] = parts.join(' ');
		}
		return data;
	    },
	    parse_fingerprint: function (line) { // RFC 4572
		var parts = line.substring(14).split(' '),
		    data = {};
		data.hash = parts.shift();
		data.fingerprint = parts.shift();
		// TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
		return data;
	    },
	    parse_fmtp: function (line) {
		var parts = line.split(' '),
		    i, key, value,
		    data = [];
		parts.shift();
		parts = parts.join(' ').split(';');
		for (i = 0; i < parts.length; i++) {
		    key = parts[i].split('=')[0];
		    while (key.length && key[0] == ' ') {
			key = key.substring(1);
		    }
		    value = parts[i].split('=')[1];
		    if (key && value) {
			data.push({name: key, value: value});
		    } else if (key) {
			// rfc 4733 (DTMF) style stuff
			data.push({name: '', value: key});
		    }
		}
		return data;
	    },
	    parse_icecandidate: function (line) {
		var candidate = {},
		    elems = line.split(' ');
		candidate.foundation = elems[0].substring(12);
		candidate.component = elems[1];
		candidate.protocol = elems[2].toLowerCase();
		candidate.priority = elems[3];
		candidate.ip = elems[4];
		candidate.port = elems[5];
		// elems[6] => "typ"
		candidate.type = elems[7];
		candidate.generation = 0; // default value, may be overwritten below
		for (var i = 8; i < elems.length; i += 2) {
		    switch (elems[i]) {
			case 'raddr':
			    candidate['rel-addr'] = elems[i + 1];
			    break;
			case 'rport':
			    candidate['rel-port'] = elems[i + 1];
			    break;
			case 'generation':
			    candidate.generation = elems[i + 1];
			    break;
			case 'tcptype':
			    candidate.tcptype = elems[i + 1];
			    break;
			default: // TODO
			    //console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
		    }
		}
		candidate.network = '1';
		candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
		return candidate;
	    },
	    build_icecandidate: function (cand) {
		var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
		line += ' ';
		switch (cand.type) {
		    case 'srflx':
		    case 'prflx':
		    case 'relay':
			if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
			    line += 'raddr';
			    line += ' ';
			    line += cand['rel-addr'];
			    line += ' ';
			    line += 'rport';
			    line += ' ';
			    line += cand['rel-port'];
			    line += ' ';
			}
			break;
		}
		if (cand.hasOwnAttribute('tcptype')) {
		    line += 'tcptype';
		    line += ' ';
		    line += cand.tcptype;
		    line += ' ';
		}
		line += 'generation';
		line += ' ';
		line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
		return line;
	    },
	    parse_ssrc: function (desc) {
		// proprietary mapping of a=ssrc lines
		// TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
		// and parse according to that
		var lines = desc.split('\r\n'),
		    data = {};
		for (var i = 0; i < lines.length; i++) {
		    if (lines[i].substring(0, 7) == 'a=ssrc:') {
			var idx = lines[i].indexOf(' ');
			data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
		    }
		}
		return data;
	    },
	    parse_rtcpfb: function (line) {
		var parts = line.substr(10).split(' ');
		var data = {};
		data.pt = parts.shift();
		data.type = parts.shift();
		data.params = parts;
		return data;
	    },
	    parse_extmap: function (line) {
		var parts = line.substr(9).split(' ');
		var data = {};
		data.value = parts.shift();
		if (data.value.indexOf('/') != -1) {
		    data.direction = data.value.substr(data.value.indexOf('/') + 1);
		    data.value = data.value.substr(0, data.value.indexOf('/'));
		} else {
		    data.direction = 'both';
		}
		data.uri = parts.shift();
		data.params = parts;
		return data;
	    },
	    find_line: function (haystack, needle, sessionpart) {
		var lines = haystack.split('\r\n');
		for (var i = 0; i < lines.length; i++) {
		    if (lines[i].substring(0, needle.length) == needle) {
			return lines[i];
		    }
		}
		if (!sessionpart) {
		    return false;
		}
		// search session part
		lines = sessionpart.split('\r\n');
		for (var j = 0; j < lines.length; j++) {
		    if (lines[j].substring(0, needle.length) == needle) {
			return lines[j];
		    }
		}
		return false;
	    },
	    find_lines: function (haystack, needle, sessionpart) {
		var lines = haystack.split('\r\n'),
		    needles = [];
		for (var i = 0; i < lines.length; i++) {
		    if (lines[i].substring(0, needle.length) == needle)
			needles.push(lines[i]);
		}
		if (needles.length || !sessionpart) {
		    return needles;
		}
		// search session part
		lines = sessionpart.split('\r\n');
		for (var j = 0; j < lines.length; j++) {
		    if (lines[j].substring(0, needle.length) == needle) {
			needles.push(lines[j]);
		    }
		}
		return needles;
	    },
	    candidateToJingle: function (line) {
		// a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
		//      <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
		if (line.indexOf('candidate:') === 0) {
		    line = 'a=' + line;
		} else if (line.substring(0, 12) != 'a=candidate:') {
		    //console.log('parseCandidate called with a line that is not a candidate line');
		    //console.log(line);
		    return null;
		}
		if (line.substring(line.length - 2) == '\r\n') // chomp it
		    line = line.substring(0, line.length - 2);
		var candidate = {},
		    elems = line.split(' '),
		    i;
		if (elems[6] != 'typ') {
		    //console.log('did not find typ in the right place');
		    //console.log(line);
		    return null;
		}
		candidate.foundation = elems[0].substring(12);
		candidate.component = elems[1];
		candidate.protocol = elems[2].toLowerCase();
		candidate.priority = elems[3];
		candidate.ip = elems[4];
		candidate.port = elems[5];
		// elems[6] => "typ"
		candidate.type = elems[7];

		candidate.generation = '0'; // default, may be overwritten below
		for (i = 8; i < elems.length; i += 2) {
		    switch (elems[i]) {
			case 'raddr':
			    candidate['rel-addr'] = elems[i + 1];
			    break;
			case 'rport':
			    candidate['rel-port'] = elems[i + 1];
			    break;
			case 'generation':
			    candidate.generation = elems[i + 1];
			    break;
			case 'tcptype':
			    candidate.tcptype = elems[i + 1];
			    break;
			default: // TODO
			    //console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
		    }
		}
		candidate.network = '1';
		candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
		return candidate;
	    },
	    candidateFromJingle: function (cand) {
		var line = 'a=candidate:';
		line += cand.getAttribute('foundation');
		line += ' ';
		line += cand.getAttribute('component');
		line += ' ';
		line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
		line += ' ';
		line += cand.getAttribute('priority');
		line += ' ';
		line += cand.getAttribute('ip');
		line += ' ';
		line += cand.getAttribute('port');
		line += ' ';
		line += 'typ';
		line += ' ' + cand.getAttribute('type');
		line += ' ';
		switch (cand.getAttribute('type')) {
		    case 'srflx':
		    case 'prflx':
		    case 'relay':
			if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
			    line += 'raddr';
			    line += ' ';
			    line += cand.getAttribute('rel-addr');
			    line += ' ';
			    line += 'rport';
			    line += ' ';
			    line += cand.getAttribute('rel-port');
			    line += ' ';
			}
			break;
		}
		if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
		    line += 'tcptype';
		    line += ' ';
		    line += cand.getAttribute('tcptype');
		    line += ' ';
		}
		line += 'generation';
		line += ' ';
		line += cand.getAttribute('generation') || '0';
		return line + '\r\n';
	    }
	};



	/**
	 * Strophe.jingle.sdp
	 */

	function SDP(sdp) {
	    this.media = sdp.split('\r\nm=');
	    for (var i = 1; i < this.media.length; i++) {
		this.media[i] = 'm=' + this.media[i];
		if (i != this.media.length - 1) {
		    this.media[i] += '\r\n';
		}
	    }
	    this.session = this.media.shift() + '\r\n';
	    this.raw = this.session + this.media.join('');
	}
	/**
	 * Returns map of MediaChannel mapped per channel idx.
	 */
	SDP.prototype.getMediaSsrcMap = function() {
	    var self = this;
	    var media_ssrcs = {};
	    for (channelNum = 0; channelNum < self.media.length; channelNum++) {
		modified = true;
		tmp = SDPUtil.find_lines(self.media[channelNum], 'a=ssrc:');
		var type = SDPUtil.parse_mid(SDPUtil.find_line(self.media[channelNum], 'a=mid:'));
		var channel = new MediaChannel(channelNum, type);
		media_ssrcs[channelNum] = channel;
		tmp.forEach(function (line) {
		    var linessrc = line.substring(7).split(' ')[0];
		    // allocate new ChannelSsrc
		    if(!channel.ssrcs[linessrc]) {
			channel.ssrcs[linessrc] = new ChannelSsrc(linessrc, type);
		    }
		    channel.ssrcs[linessrc].lines.push(line);
		});
		tmp = SDPUtil.find_lines(self.media[channelNum], 'a=ssrc-group:');
		tmp.forEach(function(line){
		    var semantics = line.substr(0, idx).substr(13);
		    var ssrcs = line.substr(14 + semantics.length).split(' ');
		    if (ssrcs.length != 0) {
			var ssrcGroup = new ChannelSsrcGroup(semantics, ssrcs);
			channel.ssrcGroups.push(ssrcGroup);
		    }
		});
	    }
	    return media_ssrcs;
	};
	/**
	 * Returns <tt>true</tt> if this SDP contains given SSRC.
	 * @param ssrc the ssrc to check.
	 * @returns {boolean} <tt>true</tt> if this SDP contains given SSRC.
	 */
	SDP.prototype.containsSSRC = function(ssrc) {
	    var channels = this.getMediaSsrcMap();
	    var contains = false;
	    Object.keys(channels).forEach(function(chNumber){
		var channel = channels[chNumber];
		//console.log("Check", channel, ssrc);
		if(Object.keys(channel.ssrcs).indexOf(ssrc) != -1){
		    contains = true;
		}
	    });
	    return contains;
	};

	/**
	 * Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx.
	 * @param otherSdp the other SDP to check ssrc with.
	 */
	SDP.prototype.getNewMedia = function(otherSdp) {

	    // this could be useful in Array.prototype.
	    function arrayEquals(array) {
		// if the other array is a falsy value, return
		if (!array)
		    return false;

		// compare lengths - can save a lot of time
		if (this.length != array.length)
		    return false;

		for (var i = 0, l=this.length; i < l; i++) {
		    // Check if we have nested arrays
		    if (this[i] instanceof Array && array[i] instanceof Array) {
			// recurse into the nested arrays
			if (!this[i].equals(array[i]))
			    return false;
		    }
		    else if (this[i] != array[i]) {
			// Warning - two different object instances will never be equal: {x:20} != {x:20}
			return false;
		    }
		}
		return true;
	    }

	    var myMedia = this.getMediaSsrcMap();
	    var othersMedia = otherSdp.getMediaSsrcMap();
	    var newMedia = {};
	    Object.keys(othersMedia).forEach(function(channelNum) {
		var myChannel = myMedia[channelNum];
		var othersChannel = othersMedia[channelNum];
		if(!myChannel && othersChannel) {
		    // Add whole channel
		    newMedia[channelNum] = othersChannel;
		    return;
		}
		// Look for new ssrcs accross the channel
		Object.keys(othersChannel.ssrcs).forEach(function(ssrc) {
		    if(Object.keys(myChannel.ssrcs).indexOf(ssrc) === -1) {
			// Allocate channel if we've found ssrc that doesn't exist in our channel
			if(!newMedia[channelNum]){
			    newMedia[channelNum] = new MediaChannel(othersChannel.chNumber, othersChannel.mediaType);
			}
			newMedia[channelNum].ssrcs[ssrc] = othersChannel.ssrcs[ssrc];
		    }
		});

		// Look for new ssrc groups across the channels
		othersChannel.ssrcGroups.forEach(function(otherSsrcGroup){

		    // try to match the other ssrc-group with an ssrc-group of ours
		    var matched = false;
		    for (var i = 0; i < myChannel.ssrcGroups.length; i++) {
			var mySsrcGroup = myChannel.ssrcGroups[i];
			if (otherSsrcGroup.semantics == mySsrcGroup.semantics
			    && arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {

			    matched = true;
			    break;
			}
		    }

		    if (!matched) {
			// Allocate channel if we've found an ssrc-group that doesn't
			// exist in our channel

			if(!newMedia[channelNum]){
			    newMedia[channelNum] = new MediaChannel(othersChannel.chNumber, othersChannel.mediaType);
			}
			newMedia[channelNum].ssrcGroups.push(otherSsrcGroup);
		    }
		});
	    });
	    return newMedia;
	};

	// remove iSAC and CN from SDP
	SDP.prototype.mangle = function () {
	    var i, j, mline, lines, rtpmap, newdesc;
	    for (i = 0; i < this.media.length; i++) {
		lines = this.media[i].split('\r\n');
		lines.pop(); // remove empty last element
		mline = SDPUtil.parse_mline(lines.shift());
		if (mline.media != 'audio')
		    continue;
		newdesc = '';
		mline.fmt.length = 0;
		for (j = 0; j < lines.length; j++) {
		    if (lines[j].substr(0, 9) == 'a=rtpmap:') {
			rtpmap = SDPUtil.parse_rtpmap(lines[j]);
			if (rtpmap.name == 'CN' || rtpmap.name == 'ISAC')
			    continue;
			mline.fmt.push(rtpmap.id);
			newdesc += lines[j] + '\r\n';
		    } else {
			newdesc += lines[j] + '\r\n';
		    }
		}
		this.media[i] = SDPUtil.build_mline(mline) + '\r\n';
		this.media[i] += newdesc;
	    }
	    this.raw = this.session + this.media.join('');
	};

	// remove lines matching prefix from session section
	SDP.prototype.removeSessionLines = function(prefix) {
	    var self = this;
	    var lines = SDPUtil.find_lines(this.session, prefix);
	    lines.forEach(function(line) {
		self.session = self.session.replace(line + '\r\n', '');
	    });
	    this.raw = this.session + this.media.join('');
	    return lines;
	}
	// remove lines matching prefix from a media section specified by mediaindex
	// TODO: non-numeric mediaindex could match mid
	SDP.prototype.removeMediaLines = function(mediaindex, prefix) {
	    var self = this;
	    var lines = SDPUtil.find_lines(this.media[mediaindex], prefix);
	    lines.forEach(function(line) {
		self.media[mediaindex] = self.media[mediaindex].replace(line + '\r\n', '');
	    });
	    this.raw = this.session + this.media.join('');
	    return lines;
	}

	// add content's to a jingle element
	SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
	//    //console.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]);
	    var i, j, k, mline, ssrc, rtpmap, tmp, line, lines;
	    var self = this;
	    // new bundle plan
	    if (SDPUtil.find_line(this.session, 'a=group:')) {
		lines = SDPUtil.find_lines(this.session, 'a=group:');
		for (i = 0; i < lines.length; i++) {
		    tmp = lines[i].split(' ');
		    var semantics = tmp.shift().substr(8);
		    elem.c('group', {xmlns: 'urn:xmpp:jingle:apps:grouping:0', semantics:semantics});
		    for (j = 0; j < tmp.length; j++) {
			elem.c('content', {name: tmp[j]}).up();
		    }
		    elem.up();
		}
	    }
	    for (i = 0; i < this.media.length; i++) {
		mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]);
		if (!(mline.media === 'audio' ||
		      mline.media === 'video' ||
		      mline.media === 'application'))
		{
		    continue;
		}
		if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) {
		    ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first
		} else {
		    if(ssrcs && ssrcs[mline.media])
		    {
			ssrc = ssrcs[mline.media];
		    }
		    else
			ssrc = false;
		}

		elem.c('content', {creator: thecreator, name: mline.media});
		if (SDPUtil.find_line(this.media[i], 'a=mid:')) {
		    // prefer identifier from a=mid if present
		    var mid = SDPUtil.parse_mid(SDPUtil.find_line(this.media[i], 'a=mid:'));
		    elem.attrs({ name: mid });
		}

		if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length)
		{
		    elem.c('description',
			{xmlns: 'urn:xmpp:jingle:apps:rtp:1',
			    media: mline.media });
		    if (ssrc) {
			elem.attrs({ssrc: ssrc});
		    }
		    for (j = 0; j < mline.fmt.length; j++) {
			rtpmap = SDPUtil.find_line(this.media[i], 'a=rtpmap:' + mline.fmt[j]);
			elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
			// put any 'a=fmtp:' + mline.fmt[j] lines into <param name=foo value=bar/>
			if (SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])) {
			    tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j]));
			    for (k = 0; k < tmp.length; k++) {
				elem.c('parameter', tmp[k]).up();
			    }
			}
			this.RtcpFbToJingle(i, elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb

			elem.up();
		    }
		    if (SDPUtil.find_line(this.media[i], 'a=crypto:', this.session)) {
			elem.c('encryption', {required: 1});
			var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session);
			crypto.forEach(function(line) {
			    elem.c('crypto', SDPUtil.parse_crypto(line)).up();
			});
			elem.up(); // end of encryption
		    }

		    if (ssrc) {
			// new style mapping
			elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
			// FIXME: group by ssrc and support multiple different ssrcs
			var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:');
			if(ssrclines.length > 0) {
			    ssrclines.forEach(function (line) {
				idx = line.indexOf(' ');
				var linessrc = line.substr(0, idx).substr(7);
				if (linessrc != ssrc) {
				    elem.up();
				    ssrc = linessrc;
				    elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
				}
				var kv = line.substr(idx + 1);
				elem.c('parameter');
				if (kv.indexOf(':') == -1) {
				    elem.attrs({ name: kv });
				} else {
				    elem.attrs({ name: kv.split(':', 2)[0] });
				    elem.attrs({ value: kv.split(':', 2)[1] });
				}
				elem.up();
			    });
			    elem.up();
			}
			else
			{
			    elem.up();
			    elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
			    elem.c('parameter');
			    elem.attrs({name: "cname", value:Math.random().toString(36).substring(7)});
			    elem.up();
			    var msid = null;
			    if(mline.media == "audio")
			    {
				msid = connection.jingle.localAudio.getAudioTracks()[0].id;
			    }
			    else
			    {
				msid = connection.jingle.localVideo.getVideoTracks()[0].id;
			    }
			    if(msid != null)
			    {
				msid = msid.replace(/[\{,\}]/g,"");
				elem.c('parameter');
				elem.attrs({name: "msid", value:msid});
				elem.up();
				elem.c('parameter');
				elem.attrs({name: "mslabel", value:msid});
				elem.up();
				elem.c('parameter');
				elem.attrs({name: "label", value:msid});
				elem.up();
				elem.up();
			    }


			}

			// XEP-0339 handle ssrc-group attributes
			var ssrc_group_lines = SDPUtil.find_lines(this.media[i], 'a=ssrc-group:');
			ssrc_group_lines.forEach(function(line) {
			    idx = line.indexOf(' ');
			    var semantics = line.substr(0, idx).substr(13);
			    var ssrcs = line.substr(14 + semantics.length).split(' ');
			    if (ssrcs.length != 0) {
				elem.c('ssrc-group', { semantics: semantics, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
				ssrcs.forEach(function(ssrc) {
				    elem.c('source', { ssrc: ssrc })
					.up();
				});
				elem.up();
			    }
			});
		    }

		    if (SDPUtil.find_line(this.media[i], 'a=rtcp-mux')) {
			elem.c('rtcp-mux').up();
		    }

		    // XEP-0293 -- map a=rtcp-fb:*
		    this.RtcpFbToJingle(i, elem, '*');

		    // XEP-0294
		    if (SDPUtil.find_line(this.media[i], 'a=extmap:')) {
			lines = SDPUtil.find_lines(this.media[i], 'a=extmap:');
			for (j = 0; j < lines.length; j++) {
			    tmp = SDPUtil.parse_extmap(lines[j]);
			    elem.c('rtp-hdrext', { xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0',
				uri: tmp.uri,
				id: tmp.value });
			    if (tmp.hasOwnProperty('direction')) {
				switch (tmp.direction) {
				    case 'sendonly':
					elem.attrs({senders: 'responder'});
					break;
				    case 'recvonly':
					elem.attrs({senders: 'initiator'});
					break;
				    case 'sendrecv':
					elem.attrs({senders: 'both'});
					break;
				    case 'inactive':
					elem.attrs({senders: 'none'});
					break;
				}
			    }
			    // TODO: handle params
			    elem.up();
			}
		    }
		    elem.up(); // end of description
		}

		// map ice-ufrag/pwd, dtls fingerprint, candidates
		this.TransportToJingle(i, elem);

		if (SDPUtil.find_line(this.media[i], 'a=sendrecv', this.session)) {
		    elem.attrs({senders: 'both'});
		} else if (SDPUtil.find_line(this.media[i], 'a=sendonly', this.session)) {
		    elem.attrs({senders: 'initiator'});
		} else if (SDPUtil.find_line(this.media[i], 'a=recvonly', this.session)) {
		    elem.attrs({senders: 'responder'});
		} else if (SDPUtil.find_line(this.media[i], 'a=inactive', this.session)) {
		    elem.attrs({senders: 'none'});
		}
		if (mline.port == '0') {
		    // estos hack to reject an m-line
		    elem.attrs({senders: 'rejected'});
		}
		elem.up(); // end of content
	    }
	    elem.up();
	    return elem;
	};

	SDP.prototype.TransportToJingle = function (mediaindex, elem) {
	    var i = mediaindex;
	    var tmp;
	    var self = this;
	    elem.c('transport');

	    // XEP-0343 DTLS/SCTP
	    if (SDPUtil.find_line(this.media[mediaindex], 'a=sctpmap:').length)
	    {
		var sctpmap = SDPUtil.find_line(
		    this.media[i], 'a=sctpmap:', self.session);
		if (sctpmap)
		{
		    var sctpAttrs = SDPUtil.parse_sctpmap(sctpmap);
		    elem.c('sctpmap',
			{
			    xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1',
			    number: sctpAttrs[0], /* SCTP port */
			    protocol: sctpAttrs[1], /* protocol */
			});
		    // Optional stream count attribute
		    if (sctpAttrs.length > 2)
			elem.attrs({ streams: sctpAttrs[2]});
		    elem.up();
		}
	    }
	    // XEP-0320
	    var fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session);
	    fingerprints.forEach(function(line) {
		tmp = SDPUtil.parse_fingerprint(line);
		tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0';
		elem.c('fingerprint').t(tmp.fingerprint);
		delete tmp.fingerprint;
		line = SDPUtil.find_line(self.media[mediaindex], 'a=setup:', self.session);
		if (line) {
		    tmp.setup = line.substr(8);
		}
		elem.attrs(tmp);
		elem.up(); // end of fingerprint
	    });
	    tmp = SDPUtil.iceparams(this.media[mediaindex], this.session);
	    if (tmp) {
		tmp.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
		elem.attrs(tmp);
		// XEP-0176
		if (SDPUtil.find_line(this.media[mediaindex], 'a=candidate:', this.session)) { // add any a=candidate lines
		    var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=candidate:', this.session);
		    lines.forEach(function (line) {
			elem.c('candidate', SDPUtil.candidateToJingle(line)).up();
		    });
		}
	    }
	    elem.up(); // end of transport
	}

	SDP.prototype.RtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP-0293
	    var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=rtcp-fb:' + payloadtype);
	    lines.forEach(function (line) {
		var tmp = SDPUtil.parse_rtcpfb(line);
		if (tmp.type == 'trr-int') {
		    elem.c('rtcp-fb-trr-int', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', value: tmp.params[0]});
		    elem.up();
		} else {
		    elem.c('rtcp-fb', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', type: tmp.type});
		    if (tmp.params.length > 0) {
			elem.attrs({'subtype': tmp.params[0]});
		    }
		    elem.up();
		}
	    });
	};

	SDP.prototype.RtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293
	    var media = '';
	    var tmp = elem.find('>rtcp-fb-trr-int[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
	    if (tmp.length) {
		media += 'a=rtcp-fb:' + '*' + ' ' + 'trr-int' + ' ';
		if (tmp.attr('value')) {
		    media += tmp.attr('value');
		} else {
		    media += '0';
		}
		media += '\r\n';
	    }
	    tmp = elem.find('>rtcp-fb[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
	    tmp.each(function () {
		media += 'a=rtcp-fb:' + payloadtype + ' ' + $(this).attr('type');
		if ($(this).attr('subtype')) {
		    media += ' ' + $(this).attr('subtype');
		}
		media += '\r\n';
	    });
	    return media;
	};

	// construct an SDP from a jingle stanza
	SDP.prototype.fromJingle = function (jingle) {
	    var self = this;
	    this.raw = 'v=0\r\n' +
		'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
		's=-\r\n' +
		't=0 0\r\n';
	    // http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04#section-8
	    if ($(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').length) {
		$(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').each(function (idx, group) {
		    var contents = $(group).find('>content').map(function (idx, content) {
			return content.getAttribute('name');
		    }).get();
		    if (contents.length > 0) {
			self.raw += 'a=group:' + (group.getAttribute('semantics') || group.getAttribute('type')) + ' ' + contents.join(' ') + '\r\n';
		    }
		});
	    }

	    this.session = this.raw;
	    jingle.find('>content').each(function () {
		var m = self.jingle2media($(this));
		self.media.push(m);
	    });

	    // reconstruct msid-semantic -- apparently not necessary
	    /*
	     var msid = SDPUtil.parse_ssrc(this.raw);
	     if (msid.hasOwnProperty('mslabel')) {
	     this.session += "a=msid-semantic: WMS " + msid.mslabel + "\r\n";
	     }
	     */

	    this.raw = this.session + this.media.join('');
	};

	// translate a jingle content element into an an SDP media part
	SDP.prototype.jingle2media = function (content) {
	    var media = '',
		desc = content.find('description'),
		ssrc = desc.attr('ssrc'),
		self = this,
		tmp;
	    var sctp = content.find(
		'>transport>sctpmap[xmlns="urn:xmpp:jingle:transports:dtls-sctp:1"]');

	    tmp = { media: desc.attr('media') };
	    tmp.port = '1';
	    if (content.attr('senders') == 'rejected') {
		// estos hack to reject an m-line.
		tmp.port = '0';
	    }
	    if (content.find('>transport>fingerprint').length || desc.find('encryption').length) {
		if (sctp.length)
		    tmp.proto = 'DTLS/SCTP';
		else
		    tmp.proto = 'RTP/SAVPF';
	    } else {
		tmp.proto = 'RTP/AVPF';
	    }
	    if (!sctp.length)
	    {
		tmp.fmt = desc.find('payload-type').map(
		    function () { return this.getAttribute('id'); }).get();
		media += SDPUtil.build_mline(tmp) + '\r\n';
	    }
	    else
	    {
		media += 'm=application 1 DTLS/SCTP ' + sctp.attr('number') + '\r\n';
		media += 'a=sctpmap:' + sctp.attr('number') +
		    ' ' + sctp.attr('protocol');

		var streamCount = sctp.attr('streams');
		if (streamCount)
		    media += ' ' + streamCount + '\r\n';
		else
		    media += '\r\n';
	    }

	    media += 'c=IN IP4 0.0.0.0\r\n';
	    if (!sctp.length)
		media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n';
	    tmp = content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
	    if (tmp.length) {
		if (tmp.attr('ufrag')) {
		    media += SDPUtil.build_iceufrag(tmp.attr('ufrag')) + '\r\n';
		}
		if (tmp.attr('pwd')) {
		    media += SDPUtil.build_icepwd(tmp.attr('pwd')) + '\r\n';
		}
		tmp.find('>fingerprint').each(function () {
		    // FIXME: check namespace at some point
		    media += 'a=fingerprint:' + this.getAttribute('hash');
		    media += ' ' + $(this).text();
		    media += '\r\n';
		    if (this.getAttribute('setup')) {
			media += 'a=setup:' + this.getAttribute('setup') + '\r\n';
		    }
		});
	    }
	    switch (content.attr('senders')) {
		case 'initiator':
		    media += 'a=sendonly\r\n';
		    break;
		case 'responder':
		    media += 'a=recvonly\r\n';
		    break;
		case 'none':
		    media += 'a=inactive\r\n';
		    break;
		case 'both':
		    media += 'a=sendrecv\r\n';
		    break;
	    }
	    media += 'a=mid:' + content.attr('name') + '\r\n';

	    // <description><rtcp-mux/></description>
	    // see http://code.google.com/p/libjingle/issues/detail?id=309 -- no spec though
	    // and http://mail.jabber.org/pipermail/jingle/2011-December/001761.html
	    if (desc.find('rtcp-mux').length) {
		media += 'a=rtcp-mux\r\n';
	    }

	    if (desc.find('encryption').length) {
		desc.find('encryption>crypto').each(function () {
		    media += 'a=crypto:' + this.getAttribute('tag');
		    media += ' ' + this.getAttribute('crypto-suite');
		    media += ' ' + this.getAttribute('key-params');
		    if (this.getAttribute('session-params')) {
			media += ' ' + this.getAttribute('session-params');
		    }
		    media += '\r\n';
		});
	    }
	    desc.find('payload-type').each(function () {
		media += SDPUtil.build_rtpmap(this) + '\r\n';
		if ($(this).find('>parameter').length) {
		    media += 'a=fmtp:' + this.getAttribute('id') + ' ';
		    media += $(this).find('parameter').map(function () { return (this.getAttribute('name') ? (this.getAttribute('name') + '=') : '') + this.getAttribute('value'); }).get().join('; ');
		    media += '\r\n';
		}
		// xep-0293
		media += self.RtcpFbFromJingle($(this), this.getAttribute('id'));
	    });

	    // xep-0293
	    media += self.RtcpFbFromJingle(desc, '*');

	    // xep-0294
	    tmp = desc.find('>rtp-hdrext[xmlns="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"]');
	    tmp.each(function () {
		media += 'a=extmap:' + this.getAttribute('id') + ' ' + this.getAttribute('uri') + '\r\n';
	    });

	    content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]>candidate').each(function () {
		media += SDPUtil.candidateFromJingle(this);
	    });

	    // XEP-0339 handle ssrc-group attributes
	    tmp = content.find('description>ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
		var semantics = this.getAttribute('semantics');
		var ssrcs = $(this).find('>source').map(function() {
		    return this.getAttribute('ssrc');
		}).get();

		if (ssrcs.length != 0) {
		    media += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
		}
	    });

	    tmp = content.find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
	    tmp.each(function () {
		var ssrc = this.getAttribute('ssrc');
		$(this).find('>parameter').each(function () {
		    media += 'a=ssrc:' + ssrc + ' ' + this.getAttribute('name');
		    if (this.getAttribute('value') && this.getAttribute('value').length)
			media += ':' + this.getAttribute('value');
		    media += '\r\n';
		});
	    });

	    return media;
	};


	/**
	 * Strophe.jingle plugin
	 */


	Strophe.addConnectionPlugin('jingle', {
	    connection: null,
	    sessions: {},
	    jid2session: {},
	    ice_config: {iceServers: []},
	    pc_constraints: {},
	    media_constraints: {
		mandatory: {
		    'OfferToReceiveAudio': true,
		    'OfferToReceiveVideo': true
		}
		// MozDontOfferDataChannel: true when this is firefox
	    },
	    localAudio: null,
	    localVideo: null,

	    init: function (conn) {
		this.connection = conn;
		if (this.connection.disco) {
		    // http://xmpp.org/extensions/xep-0167.html#support
		    // http://xmpp.org/extensions/xep-0176.html#support
		    this.connection.disco.addFeature('urn:xmpp:jingle:1');
		    this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
		    this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
		    this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
		    this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');


		    // this is dealt with by SDP O/A so we don't need to annouce this
		    //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
		    //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
		    if (config.useRtcpMux) {
			this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
		    }
		    if (config.useBundle) {
			this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
		    }
		    //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
		}
		this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
	    },
	    onJingle: function (iq) {
		var sid = $(iq).find('jingle').attr('sid');
		var action = $(iq).find('jingle').attr('action');
		var fromJid = iq.getAttribute('from');
		// send ack first
		var ack = $iq({type: 'result',
		    to: fromJid,
		    id: iq.getAttribute('id')
		});
		//console.log('on jingle ' + action + ' from ' + fromJid, iq);
		var sess = this.sessions[sid];
		if ('session-initiate' != action) {
		    if (sess === null) {
			ack.type = 'error';
			ack.c('error', {type: 'cancel'})
			    .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
			    .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
			this.connection.send(ack);
			return true;
		    }
		    // compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
		    // local jid is not checked
		    if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
			console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
			ack.type = 'error';
			ack.c('error', {type: 'cancel'})
			    .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
			    .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
			this.connection.send(ack);
			return true;
		    }
		} else if (sess !== undefined) {
		    // existing session with same session id
		    // this might be out-of-order if the sess.peerjid is the same as from
		    ack.type = 'error';
		    ack.c('error', {type: 'cancel'})
			.c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
		    console.warn('duplicate session id', sid);
		    this.connection.send(ack);
		    return true;
		}
		// FIXME: check for a defined action
		this.connection.send(ack);
		// see http://xmpp.org/extensions/xep-0166.html#concepts-session
		switch (action) {
		    case 'session-initiate':
			sess = new JingleSession($(iq).attr('to'), $(iq).find('jingle').attr('sid'), this.connection);
			// configure session

			//in firefox we have only one stream object
			if (this.localAudio != this.localVideo) {
			    sess.localStreams.push(this.localAudio);
			}
			if (this.localVideo) {
			    sess.localStreams.push(this.localVideo);
			}
			sess.media_constraints = this.media_constraints;
			sess.pc_constraints = this.pc_constraints;
			sess.ice_config = this.ice_config;

			sess.initiate(fromJid, false);
			// FIXME: setRemoteDescription should only be done when this call is to be accepted
			sess.setRemoteDescription($(iq).find('>jingle'), 'offer');

			this.sessions[sess.sid] = sess;
			this.jid2session[sess.peerjid] = sess;

			// the callback should either
			// .sendAnswer and .accept
			// or .sendTerminate -- not necessarily synchronus
			$(document).trigger('callincoming.jingle', [sess.sid]);
			break;
		    case 'session-accept':
			sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
			sess.accept();
			$(document).trigger('callaccepted.jingle', [sess.sid]);
			break;
		    case 'session-terminate':
			// If this is not the focus sending the terminate, we have
			// nothing more to do here.
			if (Object.keys(this.sessions).length < 1
			    || !(this.sessions[Object.keys(this.sessions)[0]]
				instanceof JingleSession))
			{
			    break;
			}
			//console.log('terminating...', sess.sid);
			sess.terminate();
			this.terminate(sess.sid);
			if ($(iq).find('>jingle>reason').length) {
			    $(document).trigger('callterminated.jingle', [
				sess.sid,
				sess.peerjid,
				$(iq).find('>jingle>reason>:first')[0].tagName,
				$(iq).find('>jingle>reason>text').text()
			    ]);
			} else {
			    $(document).trigger('callterminated.jingle',
						[sess.sid, sess.peerjid]);
			}
			break;
		    case 'transport-info':
			sess.addIceCandidate($(iq).find('>jingle>content'));
			break;
		    case 'session-info':
			var affected;
			if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
			    $(document).trigger('ringing.jingle', [sess.sid]);
			} else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
			    affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
			    $(document).trigger('mute.jingle', [sess.sid, affected]);
			} else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
			    affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
			    $(document).trigger('unmute.jingle', [sess.sid, affected]);
			}
			break;
		    case 'addsource': // FIXME: proprietary, un-jingleish
		    case 'source-add': // FIXME: proprietary
			sess.addSource($(iq).find('>jingle>content'), fromJid);
			break;
		    case 'removesource': // FIXME: proprietary, un-jingleish
		    case 'source-remove': // FIXME: proprietary
			sess.removeSource($(iq).find('>jingle>content'), fromJid);
			break;
		    default:
			console.warn('jingle action not implemented', action);
			break;
		}
		return true;
	    },
	    initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
		var sess = new JingleSession(myjid || this.connection.jid,
		    Math.random().toString(36).substr(2, 12), // random string
		    this.connection);
		// configure session

		//in firefox we have only one stream
		if (this.localAudio != this.localVideo) {
		    sess.localStreams.push(this.localAudio);
		}
		if (this.localVideo) {
		    sess.localStreams.push(this.localVideo);
		}
		sess.media_constraints = this.media_constraints;
		sess.pc_constraints = this.pc_constraints;
		sess.ice_config = this.ice_config;

		sess.initiate(peerjid, true);
		this.sessions[sess.sid] = sess;
		this.jid2session[sess.peerjid] = sess;
		sess.sendOffer();
		return sess;
	    },
	    terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
		if (sid === null || sid === undefined) {
		    for (sid in this.sessions) {
			if (this.sessions[sid].state != 'ended') {
			    this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
			    this.sessions[sid].terminate();
			}
			delete this.jid2session[this.sessions[sid].peerjid];
			delete this.sessions[sid];
		    }
		} else if (this.sessions.hasOwnProperty(sid)) {
		    if (this.sessions[sid].state != 'ended') {
			this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
			this.sessions[sid].terminate();
		    }
		    delete this.jid2session[this.sessions[sid].peerjid];
		    delete this.sessions[sid];
		}
	    },
	    // Used to terminate a session when an unavailable presence is received.
	    terminateByJid: function (jid) {
		if (this.jid2session.hasOwnProperty(jid)) {
		    var sess = this.jid2session[jid];
		    if (sess) {
			sess.terminate();
			//console.log('peer went away silently', jid);
			delete this.sessions[sess.sid];
			delete this.jid2session[jid];
			$(document).trigger('callterminated.jingle',
					    [sess.sid, jid], 'gone');
		    }
		}
	    },
	    terminateRemoteByJid: function (jid, reason) {
		if (this.jid2session.hasOwnProperty(jid)) {
		    var sess = this.jid2session[jid];
		    if (sess) {
			sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
			sess.terminate();
			//console.log('terminate peer with jid', sess.sid, jid);
			delete this.sessions[sess.sid];
			delete this.jid2session[jid];
			$(document).trigger('callterminated.jingle',
					    [sess.sid, jid, 'kicked']);
		    }
		}
	    },
	    getStunAndTurnCredentials: function () {
		// get stun and turn configuration from server via xep-0215
		// uses time-limited credentials as described in
		// http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
		//
		// see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
		// for a prosody module which implements this
		//
		// currently, this doesn't work with updateIce and therefore credentials with a long
		// validity have to be fetched before creating the peerconnection
		// TODO: implement refresh via updateIce as described in
		//      https://code.google.com/p/webrtc/issues/detail?id=1650
		var self = this;
		this.connection.sendIQ(
		    $iq({type: 'get', to: this.connection.domain})
			.c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
		    function (res) {
			var iceservers = [];
			$(res).find('>services>service').each(function (idx, el) {
			    el = $(el);
			    var dict = {};
			    var type = el.attr('type');
			    switch (type) {
				case 'stun':
				    dict.url = 'stun:' + el.attr('host');
				    if (el.attr('port')) {
					dict.url += ':' + el.attr('port');
				    }
				    iceservers.push(dict);
				    break;
				case 'turn':
				case 'turns':
				    dict.url = type + ':';
				    if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
					if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
					    dict.url += el.attr('username') + '@';
					} else {
					    dict.username = el.attr('username'); // only works in M28
					}
				    }
				    dict.url += el.attr('host');
				    if (el.attr('port') && el.attr('port') != '3478') {
					dict.url += ':' + el.attr('port');
				    }
				    if (el.attr('transport') && el.attr('transport') != 'udp') {
					dict.url += '?transport=' + el.attr('transport');
				    }
				    if (el.attr('password')) {
					dict.credential = el.attr('password');
				    }
				    iceservers.push(dict);
				    break;
			    }
			});
			self.ice_config.iceServers = iceservers;
		    },
		    function (err) {
			console.warn('getting turn credentials failed', err);
			console.warn('is mod_turncredentials or similar installed?');
		    }
		);
		// implement push?
	    }
	});


	/**
	 * Strophe.openfire connector
	 */

	var Openfire = {};


	/** Class: Openfire.Connection
	 *  WebSockets Connection Manager for Openfire
	 *
	 *  Thie class manages an WebSockets connection
	 *  to an Openfire XMPP server through the WebSockets plugin and dispatches events to the user callbacks as
	 *  data arrives.  It uses the server side Openfire authentication
	 *
	 *  After creating a Openfire object, the user will typically
	 *  call connect() with a user supplied callback to handle connection level
	 *  events like authentication failure, disconnection, or connection
	 *  complete.
	 *
	 *  To send data to the connection, use send(doc) or sendRaw(text)
	 *
	 *  Use xmlInput(doc) and RawInput(text) overrideable function to receive XML data coming into the
	 *  connection.
	 *
	 *  The user will also have several event handlers defined by using
	 *  addHandler() and addTimedHandler().  These will allow the user code to
	 *  respond to interesting stanzas or do something periodically with the
	 *  connection.  These handlers will be active once authentication is
	 *  finished.
	 *
	 *  Create and initialize a Openfire object.
	 *
	 *
	 *  Returns:
	 *    A new Openfire object.
	 */

	Openfire.Connection = function(url) 
	{
	    if (!window.WebSocket) 
	    {
		window.WebSocket=window.MozWebSocket;

		if (!window.WebSocket)
		{
			var msg = "WebSocket not supported by this browser";			
			alert(msg);
			throw Error(msg);
		}
	    }

	    this.host = url.indexOf("/") < 0 ? url : url.split("/")[2];   
	    this.protocol = url.indexOf("/") < 0 ? "wss:" : (url.split("/")[0] == "http:") ? "ws:" : "wss:";   
	    this.jid = "";
	    this.resource = "ofmeet" + Math.round(Math.random() * 10000);
	    this.streamId = null;

	    // handler lists

	    this.timedHandlers = [];
	    this.handlers = [];
	    this.removeTimeds = [];
	    this.removeHandlers = [];
	    this.addTimeds = [];
	    this.addHandlers = [];
	    this._idleTimeout = null;

	    this.authenticated = false;
	    this.disconnecting = false;
	    this.connected = false;

	    this.errors = 0;

	    this._uniqueId = Math.round(Math.random() * 10000);

	    // setup onIdle callback every 1/10th of a second
	    this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);

	    // initialize plugins

	    for (var k in Strophe._connectionPlugins) 
	    {   
		if (Strophe._connectionPlugins.hasOwnProperty(k)) {
		    var ptype = Strophe._connectionPlugins[k];
		    // jslint complaints about the below line, but this is fine
		    var F = function () {};
		    F.prototype = ptype;
		    this[k] = new F();
		    this[k].init(this);	    
		}
	    }	   
	}

	Openfire.Connection.prototype = {

	    /** Function: reset
	     *  Reset the connection.
	     *
	     *  This function should be called after a connection is disconnected
	     *  before that connection is reused.
	     */

	    reset: function ()
	    {
		this.streamId = null;

		this.timedHandlers = [];
		this.handlers = [];
		this.removeTimeds = [];
		this.removeHandlers = [];
		this.addTimeds = [];
		this.addHandlers = [];

		this.authenticated = false;
		this.disconnecting = false;
		this.connected = false;

		this.errors = 0;
	    },


	    /** Function: pause
	     *  UNUSED with websockets
	     */

	    pause: function ()
	    {
		return;
	    },

	    /** Function: resume
	     *  UNUSED with websockets
	     */

	    resume: function ()
	    {
		return;
	    },

	    /** Function: getUniqueId
	     *  Generate a unique ID for use in <iq/> elements.
	     *
	     *  All <iq/> stanzas are required to have unique id attributes.  This
	     *  function makes creating these easy.  Each connection instance has
	     *  a counter which starts from zero, and the value of this counter
	     *  plus a colon followed by the suffix becomes the unique id. If no
	     *  suffix is supplied, the counter is used as the unique id.
	     *
	     *  Suffixes are used to make debugging easier when reading the stream
	     *  data, and their use is recommended.  The counter resets to 0 for
	     *  every new connection for the same reason.  For connections to the
	     *  same server that authenticate the same way, all the ids should be
	     *  the same, which makes it easy to see changes.  This is useful for
	     *  automated testing as well.
	     *
	     *  Parameters:
	     *    (String) suffix - A optional suffix to append to the id.
	     *
	     *  Returns:
	     *    A unique string to be used for the id attribute.
	     */

	    getUniqueId: function (suffix)
	    {
		if (typeof(suffix) == "string" || typeof(suffix) == "number") {
		    return ++this._uniqueId + ":" + suffix;
		} else {
		    return ++this._uniqueId + "";
		}
	    },

	    /** Function: connect
	     *  Starts the connection process.
	     *
	     *
	     *  Parameters:
	     *    (String) username - The Openfire username.  
	     *    (String) pass - The user's password.
	     *    (String) resource - The user resource for this connection.     
	     *    (Function) callback The connect callback function.
	     */

	    connect: function (jid, pass, callback, wait, hold, route)
	    { 	    
		this.jid = jid.indexOf("/") > -1 ? jid : jid + '/' + this.resource;        
		this.username = jid.indexOf("@") < 0 ? null : jid.split("@")[0];
		this.pass = pass == "" ? null : pass;        
		this.connect_callback = callback;  
		this.disconnecting = false;
		this.connected = false;
		this.authenticated = false;
		this.errors = 0;

		this._changeConnectStatus(Strophe.Status.CONNECTING, null);   
		this.url = this.protocol + "//" + this.host + "/ofmeetws/server?username=" + this.username + "&password=" + this.pass + "&resource=" + this.resource;
		this._ws = new WebSocket(this.url, "xmpp");  

		this._ws.onopen = this._onopen.bind(this);
		this._ws.onmessage = this._onmessage.bind(this);
		this._ws.onclose = this._onclose.bind(this);

		window.openfireWebSocket = this;

		this.jid = this.jid.indexOf("@") < 0 ? this.resource + "@" + this.jid  : this.jid;

		this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);

	    },


	    /** 
	     *
	     * Private Function: _onopen websocket event handler
	     *
	     */    

	    _onopen: function() 
	    {       
		this.connected = true;
		this.authenticated = true;
		this.resource = Strophe.getResourceFromJid(this.jid);
		this.domain = Strophe.getDomainFromJid(this.jid);

		try {

		   this._changeConnectStatus(Strophe.Status.CONNECTED, null);	

		} catch (e) {

		    throw Error("User connection callback caused an exception: " + e);
		} 

		this.interval = setInterval (function() {window.openfireWebSocket.sendRaw(" ")}, 10000 );	
	    },

	    /** Function: attach
	     *  UNUSED, use connect again
	     */

	    attach: function()
	    {
		return 
	    },

	    /** Function: xmlInput
	     *  User overrideable function that receives XML data coming into the
	     *  connection.
	     *
	     *  The default function does nothing.  User code can override this with
	     *  > Openfire.xmlInput = function (elem) {
	     *  >   (user code)
	     *  > };
	     *
	     *  Parameters:
	     *    (XMLElement) elem - The XML data received by the connection.
	     */

	    xmlInput: function (elem)
	    {
		return;
	    },

	    /** Function: xmlOutput
	     *  User overrideable function that receives XML data sent to the
	     *  connection.
	     *
	     *  The default function does nothing.  User code can override this with
	     *  > Openfire.xmlOutput = function (elem) {
	     *  >   (user code)
	     *  > };
	     *
	     *  Parameters:
	     *    (XMLElement) elem - The XMLdata sent by the connection.
	     */

	    xmlOutput: function (elem)
	    {
		return;
	    },

	    /** Function: rawInput
	     *  User overrideable function that receives raw data coming into the
	     *  connection.
	     *
	     *  The default function does nothing.  User code can override this with
	     *  > Openfire.rawInput = function (data) {
	     *  >   (user code)
	     *  > };
	     *
	     *  Parameters:
	     *    (String) data - The data received by the connection.
	     */

	    rawInput: function (data)
	    {
		return;
	    },

	    /** Function: rawOutput
	     *  User overrideable function that receives raw data sent to the
	     *  connection.
	     *
	     *  The default function does nothing.  User code can override this with
	     *  > Openfire.rawOutput = function (data) {
	     *  >   (user code)
	     *  > };
	     *
	     *  Parameters:
	     *    (String) data - The data sent by the connection.
	     */

	    rawOutput: function (data)
	    {
		return;
	    },




	    /** Function: sendRaw
	     *  Send a stanza in raw XML text.
	     *
	     *  This function is called to push data onto the send queue to
	     *  go out over the wire.  Whenever a request is sent to the BOSH
	     *  server, all pending data is sent and the queue is flushed.
	     *
	     *  Parameters:
	     *    xml - The stanza text XML to send.
	     */

	    sendRaw: function(xml) {

		if(!this.connected || this._ws == null) {
		    throw Error("Not connected, cannot send packets.");
		}

		if (xml != " ")
		{
			this.xmlOutput(this._textToXML(xml));
			this.rawOutput(xml);
		}

		this._ws.send(xml);
	    },


	    /** Function: send
	     *  Send a stanza.
	     *
	     *  This function is called to push data onto the send queue to
	     *  go out over the wire.  Whenever a request is sent to the BOSH
	     *  server, all pending data is sent and the queue is flushed.
	     *
	     *  Parameters:
	     *    (XMLElement |
	     *     [XMLElement] |
	     *     Strophe.Builder) elem - The stanza to send.
	     */

	    send: function(elem) 
	    {    
		if(!this.connected || this._ws == null) {
		    throw Error("Not connected, cannot send packets.");
		}

		var toSend = "";

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

		if (typeof(elem.sort) === "function") 
		{
		    for (var i = 0; i < elem.length; i++) 
		    {
			toSend += Strophe.serialize(elem[i]);
			this.xmlOutput(elem[i]);
		    }

		} else if (typeof(elem.tree) === "function") {

		    toSend = Strophe.serialize(elem.tree());
		    this.xmlOutput(elem.tree());

		} else {

		    toSend = Strophe.serialize(elem);
		    this.xmlOutput(elem);
		}

		this.rawOutput(toSend);
		this._ws.send(toSend);
	    },    

	    /** Function: flush
	     *  UNUSED
	     */

	    flush: function ()
	    {
		return
	    },


	    /** Function: sendIQ
	     *  Helper function to send IQ stanzas.
	     *
	     *  Parameters:
	     *    (XMLElement) elem - The stanza to send.
	     *    (Function) callback - The callback function for a successful request.
	     *    (Function) errback - The callback function for a failed or timed
	     *      out request.  On timeout, the stanza will be null.
	     *    (Integer) timeout - The time specified in milliseconds for a
	     *      timeout to occur.
	     *
	     *  Returns:
	     *    The id used to send the IQ.
	     */

	    sendIQ: function(elem, callback, errback, timeout) {
		var timeoutHandler = null;
		var that = this;

		if (typeof(elem.tree) === "function") {
		    elem = elem.tree();
		}
		var id = elem.getAttribute('id');

		// inject id if not found

		if (!id) {
		    id = this.getUniqueId("sendIQ");
		    elem.setAttribute("id", id);
		}

		var handler = this.addHandler(function (stanza) {
		    // remove timeout handler if there is one

		    if (timeoutHandler) {
			that.deleteTimedHandler(timeoutHandler);
		    }

		    var iqtype = stanza.getAttribute('type');

		    if (iqtype == 'result') 
		    {
			if (callback) {
			    callback(stanza);
			}

		    } else if (iqtype == 'get') {

		    } else if (iqtype == 'error') {
			if (errback) {
			    errback(stanza);
			}
		    } else {
			throw {
			    name: "StropheError",
			    message: "Got bad IQ type of " + iqtype
			};
		    }
		}, null, 'iq', null, id);

		// if timeout specified, setup timeout handler.

		if (timeout) 
		{
		    timeoutHandler = this.addTimedHandler(timeout, function () {
			// get rid of normal handler
			that.deleteHandler(handler);

			// call errback on timeout with null stanza
			if (errback) {
			    errback(null);
			}
			return false;
		    });
		}

		this.send(elem);

		return id;
	    },

	    /** Function: addTimedHandler
	     *  Add a timed handler to the connection.
	     *
	     *  This function adds a timed handler.  The provided handler will
	     *  be called every period milliseconds until it returns false,
	     *  the connection is terminated, or the handler is removed.  Handlers
	     *  that wish to continue being invoked should return true.
	     *
	     *  Because of method binding it is necessary to save the result of
	     *  this function if you wish to remove a handler with
	     *  deleteTimedHandler().
	     *
	     *  Note that user handlers are not active until authentication is
	     *  successful.
	     *
	     *  Parameters:
	     *    (Integer) period - The period of the handler.
	     *    (Function) handler - The callback function.
	     *
	     *  Returns:
	     *    A reference to the handler that can be used to remove it.
	     */

	    addTimedHandler: function (period, handler)
	    {
		var thand = new Strophe.TimedHandler(period, handler);
		this.addTimeds.push(thand);
		return thand;
	    },


	    /** Function: deleteTimedHandler
	     *  Delete a timed handler for a connection.
	     *
	     *  This function removes a timed handler from the connection.  The
	     *  handRef parameter is *not* the function passed to addTimedHandler(),
	     *  but is the reference returned from addTimedHandler().
	     *
	     *  Parameters:
	     *    (Strophe.TimedHandler) handRef - The handler reference.
	     */

	    deleteTimedHandler: function (handRef)
	    {
		// this must be done in the Idle loop so that we don't change
		// the handlers during iteration

		this.removeTimeds.push(handRef);
	    },

	    /** Function: addHandler
	     *  Add a stanza handler for the connection.
	     *
	     *  This function adds a stanza handler to the connection.  The
	     *  handler callback will be called for any stanza that matches
	     *  the parameters.  Note that if multiple parameters are supplied,
	     *  they must all match for the handler to be invoked.
	     *
	     *  The handler will receive the stanza that triggered it as its argument.
	     *  The handler should return true if it is to be invoked again;
	     *  returning false will remove the handler after it returns.
	     *
	     *  As a convenience, the ns parameters applies to the top level element
	     *  and also any of its immediate children.  This is primarily to make
	     *  matching /iq/query elements easy.
	     *
	     *  The options argument contains handler matching flags that affect how
	     *  matches are determined. Currently the only flag is matchBare (a
	     *  boolean). When matchBare is true, the from parameter and the from
	     *  attribute on the stanza will be matched as bare JIDs instead of
	     *  full JIDs. To use this, pass {matchBare: true} as the value of
	     *  options. The default value for matchBare is false. 
	     *
	     *  The return value should be saved if you wish to remove the handler
	     *  with deleteHandler().
	     *
	     *  Parameters:
	     *    (Function) handler - The user callback.
	     *    (String) ns - The namespace to match.
	     *    (String) name - The stanza name to match.
	     *    (String) type - The stanza type attribute to match.
	     *    (String) id - The stanza id attribute to match.
	     *    (String) from - The stanza from attribute to match.
	     *    (String) options - The handler options
	     *
	     *  Returns:
	     *    A reference to the handler that can be used to remove it.
	     */

	    addHandler: function (handler, ns, name, type, id, from, options)
	    {
		var hand = new Strophe.Handler(handler, ns, name, type, id, from, options);
		this.addHandlers.push(hand);
		return hand;
	    },

	    /** Function: deleteHandler
	     *  Delete a stanza handler for a connection.
	     *
	     *  This function removes a stanza handler from the connection.  The
	     *  handRef parameter is *not* the function passed to addHandler(),
	     *  but is the reference returned from addHandler().
	     *
	     *  Parameters:
	     *    (Strophe.Handler) handRef - The handler reference.
	     */

	    deleteHandler: function (handRef)
	    {
		// this must be done in the Idle loop so that we don't change
		// the handlers during iteration

		this.removeHandlers.push(handRef);
	    },

	    /** Function: disconnect
	     *  Start the graceful disconnection process.
	     *
	     *  This function starts the disconnection process.  This process starts
	     *  by sending unavailable presence and sending BOSH body of type
	     *  terminate.  A timeout handler makes sure that disconnection happens
	     *  even if the BOSH server does not respond.
	     *
	     *  The user supplied connection callback will be notified of the
	     *  progress as this process happens.
	     *
	     *  Parameters:
	     *    (String) reason - The reason the disconnect is occuring.
	     */    

	    disconnect: function(reason) {

		if(!this.connected || this._ws == null) {
		    return;
		}

		this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason);  
		Strophe.info("Disconnect was called because: " + reason);

		this._ws.close();

	    },

	    /** PrivateFunction: _onDisconnectTimeout
	     *  _Private_ timeout handler for handling non-graceful disconnection.
	     *
	     *  If the graceful disconnect process does not complete within the
	     *  time allotted, this handler finishes the disconnect anyway.
	     *
	     *  Returns:
	     *    false to remove the handler.
	     */

	    _onDisconnectTimeout: function ()
	    {
		Strophe.info("_onDisconnectTimeout was called");

		this._doDisconnect();

		return false;
	    },

	    /** PrivateFunction: _doDisconnect
	     *  _Private_ function to disconnect.
	     *
	     *  This is the last piece of the disconnection logic.  This resets the
	     *  connection and alerts the user's connection callback.
	     */

	    _doDisconnect: function ()
	    {
		Strophe.info("_doDisconnect was called");
		this._onclose();
	    },

	    /** PrivateFunction: _changeConnectStatus
	     *  _Private_ helper function that makes sure plugins and the user's
	     *  callback are notified of connection status changes.
	     *
	     *  Parameters:
	     *    (Integer) status - the new connection status, one of the values
	     *      in Strophe.Status
	     *    (String) condition - the error condition or null
	     */

	    _changeConnectStatus: function (status, condition)
	    {
		// notify all plugins listening for status changes

		for (var k in Strophe._connectionPlugins) 
		{
		    if (Strophe._connectionPlugins.hasOwnProperty(k)) 
		    {
			var plugin = this[k];

			if (plugin.statusChanged) 
			{
			    try {
				plugin.statusChanged(status, condition);
			    } catch (err) {
				Strophe.error("" + k + " plugin caused an exception changing status: " + err);
			    }
			}
		    }
		}

		// notify the user's callback

		if (typeof this.connect_callback == 'function')
		{
		    try {
			this.connect_callback(status, condition);
		    } catch (e) {
			Strophe.error("User connection callback caused an exception: " + e);
		   }
		}
	    },



	    /** 
	     *
	     * Private Function: _onclose websocket event handler
	     *
	     */    

	    _onclose: function() 
	    {
		Strophe.info("websocket closed");
		//console.log('_onclose - disconnected');
		clearInterval(this.interval);

		this.authenticated = false;
		this.disconnecting = false;
		this.streamId = null;

		// tell the parent we disconnected

		this._changeConnectStatus(Strophe.Status.DISCONNECTED, null);
		this.connected = false;

		// delete handlers

		this.handlers = [];
		this.timedHandlers = [];
		this.removeTimeds = [];
		this.removeHandlers = [];
		this.addTimeds = [];
		this.addHandlers = [];

		if(this._ws.readyState != this._ws.CLOSED)
		{
		  this._ws.close();
		}
	    },

	    /** 
	     *
	     * Private Function: _onmessage websocket event handler
	     *
	     */    

	    _onmessage: function(packet) 
	    {
		var elem;

		try {
		    elem = this._textToXML(packet.data);

		} catch (e) {

		    if (e != "parsererror") { throw e; }
		    this.disconnect("strophe-parsererror");
		}

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

		this.xmlInput(elem);
		this.rawInput(packet.data);

		// remove handlers scheduled for deletion

		var i, hand;

		while (this.removeHandlers.length > 0) 
		{
		    hand = this.removeHandlers.pop();
		    i = this.handlers.indexOf(hand);

		    if (i >= 0) {
			this.handlers.splice(i, 1);
		    }
		}

		// add handlers scheduled for addition

		while (this.addHandlers.length > 0) 
		{
		    this.handlers.push(this.addHandlers.pop());
		}  

		// send each incoming stanza through the handler chain

		var i, newList;
		newList = this.handlers;
		this.handlers = [];

		for (i = 0; i < newList.length; i++) 
		{          
		    var hand = newList[i];

		    if (hand.isMatch(elem) && (this.authenticated || !hand.user)) 
		    {
			if (hand.run(elem)) 
			{
			    this.handlers.push(hand);
			}

		    } else {
			this.handlers.push(hand);
		    }
		}        
	    },


	    /** 
	     *
	     * Private Function: _textToXML convert text to DOM Document object
	     *
	     */    

	    _textToXML: function (text) {

		var doc = null;

		if (window['DOMParser']) {
		    var parser = new DOMParser();
		    doc = parser.parseFromString(text, 'text/xml');

		} else if (window['ActiveXObject']) {
		    var doc = new ActiveXObject("MSXML2.DOMDocument");
		    doc.async = false;
		    doc.loadXML(text);

		} else {
		    throw Error('No DOMParser object found.');
		}

		return doc.firstChild;
	    },     

	    /** PrivateFunction: _onIdle
	     *  _Private_ handler to process events during idle cycle.
	     *
	     *  This handler is called every 100ms to fire timed handlers that
	     *  are ready and keep poll requests going.
	     */

	    _onIdle: function ()
	    {
		var i, thand, since, newList;

		// remove timed handlers that have been scheduled for deletion

		while (this.removeTimeds.length > 0) 
		{
		    thand = this.removeTimeds.pop();
		    i = this.timedHandlers.indexOf(thand);
		    if (i >= 0) {
			this.timedHandlers.splice(i, 1);
		    }
		}

		// add timed handlers scheduled for addition

		while (this.addTimeds.length > 0) 
		{
		    this.timedHandlers.push(this.addTimeds.pop());
		}

		// call ready timed handlers
		var now = new Date().getTime();
		newList = [];

		for (i = 0; i < this.timedHandlers.length; i++) 
		{
		    thand = this.timedHandlers[i];

		    if (this.authenticated || !thand.user) {
			since = thand.lastCalled + thand.period;

			if (since - now <= 0) {
			    if (thand.run()) {
				newList.push(thand);
			    }
			} else {
			    newList.push(thand);
			}
		    }
		}

		this.timedHandlers = newList;

		// reactivate the timer

		clearTimeout(this._idleTimeout);
		this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
	    }        
	}

	/**
	 * Strophe.connectionmanager plugin
	 */

	Strophe.addConnectionPlugin('connectionmanager', {
	    /*
	     This plugin implements a Connection Manager that monitors
	     the Strophe connection and keeps track of unsent stanzas.
	     */
	    conn: null,
	    firstTime: true,
	    conn_state: null,
	    element_queue: [],
	    enabled: true,  // enabled by default

	    _status_lookup: {},

	    _receiveTimer: null,
	    _reconnectInterval: null,

	    // default config
	    config: {
		// try to reconnet continously, even after a graceful disconnect
		// unless the disconnect reason is "logout"
		autoReconnect: true,

		// if true (default), will automatically empty queue on successfull reconnect
		// (+/- 20 stanzas per packet, 2 seconds apart)
		autoResend: true,

		receiveTimeout: 20, // in seconds
		pingTimeout: 10, // in seconds
		reconnectInterval: 10, // in seconds - interval at which to attempt reconnection
		onEnqueueElement: null,
		onDequeueElement: null,
		onReceiveTimeout: null,
		onPingTimeout: null,
		onPingOK: null
	    },

	    // API

	    configure: function(config){
		config = config || {};

		for(var c in config){
		    if(config.hasOwnProperty(c)){
			this.config[c] = config[c];
		    }
		}
	    },

	    sendQueuedElements: function(max){
		max = max || this.element_queue.length;

		this.conn.pause(); // facilitate bulk sending
		for(var i = 0; i < this.element_queue.length && i < max; i++){
		    this.conn.send(this.element_queue[i]);
		}
		this.conn.resume();
	    },

	    resendAll: function(sent_callback){
		var sendInt = setInterval(function(){
		    if(this.element_queue.length === 0){
			clearInterval(sendInt);

			if(sent_callback){ sent_callback(); }
			return;
		    }

		    this.sendQueuedElements(20);
		}.bind(this), 2000);
	    },

	    enable: function(){
		this.disable(); // prevent double-enabling

		var that = this;
		var conn = this.conn;

		// insert a tap into the builtin _queueData function
		var _queueData = conn._queueData;

		if (_queueData)
		{
			conn._queueData = function (element) {
			    if(that._enqueueElement(element)){
				_queueData.call(this, element);
			    } else {
				//console.log("NOT passing through queue call to Strophe Connection!");
			    }
			};
		}

		var _onRequestStateChange = conn._onRequestStateChange;

		if (_onRequestStateChange)
		{        
			conn._onRequestStateChange = function(func, req){
			    _onRequestStateChange.call(this, function(){
				that._requestReceived(req);
				func(req);
			    }, req);
			};
		}

		// build a reverse lookup of Strophe.Status states
		for(var s in Strophe.Status){
		    if(Strophe.Status.hasOwnProperty(s)){
			this._status_lookup[Strophe.Status[s]] = s;
		    }
		}

		this.enabled = true;
	    },

	    disable: function(){
		this.conn._queueData = Strophe.Connection.prototype._queueData;
		this.conn._onRequestStateChange = Strophe.Connection.prototype._onRequestStateChange;

		clearTimeout(this._receiveTimer);
		clearInterval(this._reconnectInterval);

		this.enabled = false;
	    },

	    reconnect: function(){
		//console.log("reconnect start");

		var xhr = new XMLHttpRequest();
		xhr.open("GET", "/ofmeet/config", true);

		xhr.onload = (function() {
			//console.log("reconnect onload");
		}).bind(this);

		xhr.send(null);      	

		this.conn.disconnect();
		this.conn._onDisconnectTimeout(); // clears requests
		this.conn.connect(this.conn.jid, this.conn.pass,
				  this.conn.connect_callback,
				  this.conn.wait, this.conn.hold);
	    },

	    //--------------------------

	    init: function(conn) {
		this.conn = conn;

		this.enable();

		//console.log("strophe plugin: connectionmanager enabled");         
	    },

	    /** Called automatically by Strophe when the connection status changes */
	    statusChanged: function(status, condition){
		if(!this.enabled){
		    return;
		}

		this.conn_state = status;
		//console.log("Strophe connection status: " + this._status_lookup[status] || status, condition);

		// start timer when connected and reset it when not connected
		clearTimeout(this._receiveTimer);

		if(status == Strophe.Status.CONNECTED){
		    this.conn.send($pres()); 

		    if (!this.firstTime) setTimeout(function() { window.location.reload(true);}, 5000);

		    this.firstTime = false; 

		    this._restartReceiveTimer();
		    clearInterval(this._reconnectInterval);
	//             clearInterval(this.__countInterval);
	//             this.__countInterval = null;

		    if(this.config.autoResend){
			this.resendAll();
		    }
			    }
		else if(status == Strophe.Status.DISCONNECTING){

		}
		else if(status == Strophe.Status.DISCONNECTED){
		    this.conn.reconnecting = true;

		    if(condition == "logout"){
			// disable auto reconnect
			this.config.autoReconnect = false;
		    }

		    clearInterval(this._reconnectInterval);

		    if(this.config.autoReconnect){           
			this._reconnectInterval = setInterval(
			    function(){
				if(!this.conn.connected){
				    this.reconnect();
				}
			    }.bind(this),
			    this.config.reconnectInterval * 1000);

	//                 // start the counting interval (for testing purposes)
	//                 if(this.__countInterval == null){
	//                     var i = 0;
	//                     this.__countInterval = setInterval(function(){
	//                         this.conn.send($msg({to: "all@conference." + this.conn.domain,
	//                                              type: "groupchat"}).c("body").t(i++));
	//                     }.bind(this), 1000);
	//                 }
		    }
		}
	    },

	    _enqueueElement: function(el){
		 //console.log("req sent: " + el);

		if(!el){
		    return;
		}


		// only enqueue elements that are not used for authentication
		// or session establishment.
		// also don't buffer presence stanzas
		if( (!el.getAttribute("id") 
		    || (Strophe.isTagEqual(el, "iq") && el.getAttribute("id").indexOf("_auth_") != -1))
		    || el.getAttribute("xmlns") == "urn:ietf:params:xml:ns:xmpp-sasl"
		    || Strophe.isTagEqual(el, "presence")
		  ){
		    // this is an auth/session stanza
		    return true;
		}

		// only push non-empty requests
		if(this.element_queue.indexOf(el) == -1){
		    this.element_queue.push(el);

		    if(this.config.onEnqueueElement){
			this.config.onEnqueueElement(el);
		    }
		}

		 //console.log("sentQueue: " + this.element_queue.length + " " + this.element_queue);
		 //console.log("_requests: " + this.conn._requests.length + " " + this.conn._requests);

		// if strophe is in the AUTHENTICATING (auth/session establishment) stage,
		// don't allow "regular requests" to be pushed through
	//         if(this.conn_state == Strophe.Status.AUTHENTICATING ||
	//            this.conn_state == Strophe.Status.DISCONNECTED){
		if(this.conn_state != Strophe.Status.CONNECTED){
		    return false;
		}

		return true;
	    },

	    _requestReceived: function(req){
		 //console.log("req received: " + req);

		// only dequeue in connected state
		if(this.conn_state == Strophe.Status.CONNECTED){
		    // clear the queue
		    var els = req.xmlData.childNodes;
		    if(els && els.length > 0){
			for(var i = 0; i < this.element_queue.length; i++){
			    for(var j = 0; j < els.length; j++){
				//if(this.element_queue[i] == els[j]){
				if(this.element_queue[i] && els[j] && (
				    this.element_queue[i].getAttribute("id") 
					== els[j].getAttribute("id"))){  // use request id comparisons instead
				    //console.log("removing from sent queue:" + this.element_queue[i]);
				    this.element_queue.splice(i--, 1);

				    if(this.config.onDequeueElement){
					this.config.onDequeueElement(els[j]);
				    }
				}
			    }
			}
		    }

		     //console.log("sentQueue:" + this.element_queue.length + " " + this.element_queue);
		     //console.log("_requests:" + this.conn._requests.length + " " + this.conn._requests);
		}

		this._restartReceiveTimer();
	    },

	    _restartReceiveTimer: function(){
		clearTimeout(this._receiveTimer);
		this._receiveTimer = setTimeout(this._onReceiveTimeout.bind(this),
						this.config.receiveTimeout * 1000);
	    },

	    _onReceiveTimeout: function(){
		// receive timeout reached, do a ping
		Strophe.info("CM: receive timeout");

		if(this.config.onReceiveTimeout){
		    this.config.onReceiveTimeout();
		}

		this.conn.send($iq(
		    {
			id: "ping",
			type: "get",
			to: this.conn.domain
		    }).c("ping", {xmlns: 'urn:xmpp:ping'}));

		var pingTimeout = setTimeout(this._onPingTimeout.bind(this),
					     this.config.pingTimeout * 1000);

		this.conn.addHandler(function(resp){
		    clearTimeout(pingTimeout);

		    if(this.config.onPingOK){
			this.config.onPingOK();
		    }
		}.bind(this), null, null, null, "ping");
	    },

	    _onPingTimeout: function(){
		Strophe.warn("CM: ping timed out, disconnecting!");

		if(this.config.onPingTimeout){
		    this.config.onPingTimeout();
		}

		// disconnect connection
		this.conn.disconnect();
	    }

	}); 

	/**
	 * Strophe.disco plugin
	 */

	Strophe.addConnectionPlugin("disco",{_connection:null,_identities:[],_features:[],_items:[],init:function(conn){this._connection=conn;this._identities=[];this._features=[];this._items=[];conn.addHandler(this._onDiscoInfo.bind(this),Strophe.NS.DISCO_INFO,"iq","get",null,null);conn.addHandler(this._onDiscoItems.bind(this),Strophe.NS.DISCO_ITEMS,"iq","get",null,null)},addIdentity:function(category,type,name,lang){for(var i=0;i<this._identities.length;i++){if(this._identities[i].category==category&&this._identities[i].type==type&&this._identities[i].name==name&&this._identities[i].lang==lang){return false}}this._identities.push({category:category,type:type,name:name,lang:lang});return true},addFeature:function(var_name){for(var i=0;i<this._features.length;i++){if(this._features[i]==var_name){return false}}this._features.push(var_name);return true},removeFeature:function(var_name){for(var i=0;i<this._features.length;i++){if(this._features[i]===var_name){this._features.splice(i,1);return true}}return false},addItem:function(jid,name,node,call_back){if(node&&!call_back){return false}this._items.push({jid:jid,name:name,node:node,call_back:call_back});return true},info:function(jid,node,success,error,timeout){var attrs={xmlns:Strophe.NS.DISCO_INFO};if(node){attrs.node=node}var info=$iq({from:this._connection.jid,to:jid,type:"get"}).c("query",attrs);this._connection.sendIQ(info,success,error,timeout)},items:function(jid,node,success,error,timeout){var attrs={xmlns:Strophe.NS.DISCO_ITEMS};if(node){attrs.node=node}var items=$iq({from:this._connection.jid,to:jid,type:"get"}).c("query",attrs);this._connection.sendIQ(items,success,error,timeout)},_buildIQResult:function(stanza,query_attrs){var id=stanza.getAttribute("id");var from=stanza.getAttribute("from");var iqresult=$iq({type:"result",id:id});if(from!==null){iqresult.attrs({to:from})}return iqresult.c("query",query_attrs)},_onDiscoInfo:function(stanza){var node=stanza.getElementsByTagName("query")[0].getAttribute("node");var attrs={xmlns:Strophe.NS.DISCO_INFO};if(node){attrs.node=node}var iqresult=this._buildIQResult(stanza,attrs);for(var i=0;i<this._identities.length;i++){var attrs={category:this._identities[i].category,type:this._identities[i].type};if(this._identities[i].name){attrs.name=this._identities[i].name}if(this._identities[i].lang){attrs["xml:lang"]=this._identities[i].lang}iqresult.c("identity",attrs).up()}for(var i=0;i<this._features.length;i++){iqresult.c("feature",{"var":this._features[i]}).up()}this._connection.send(iqresult.tree());return true},_onDiscoItems:function(stanza){var query_attrs={xmlns:Strophe.NS.DISCO_ITEMS};var node=stanza.getElementsByTagName("query")[0].getAttribute("node");if(node){query_attrs.node=node;var items=[];for(var i=0;i<this._items.length;i++){if(this._items[i].node==node){items=this._items[i].call_back(stanza);break}}}else{var items=this._items}var iqresult=this._buildIQResult(stanza,query_attrs);for(var i=0;i<items.length;i++){var attrs={jid:items[i].jid};if(items[i].name){attrs.name=items[i].name}if(items[i].node){attrs.node=items[i].node}iqresult.c("item",attrs).up()}this._connection.send(iqresult.tree());return true}});

	/**
	 * Strophe.caps plugin
	 */

	Strophe.addConnectionPlugin("caps",{HASH:"sha-1",node:"http://strophe.im/strophejs/",_ver:"",_connection:null,_knownCapabilities:{},_jidVerIndex:{},init:function(conn){this._connection=conn;Strophe.addNamespace("CAPS","http://jabber.org/protocol/caps");if(!this._connection.disco){throw"Caps plugin requires the disco plugin to be installed."}this._connection.disco.addFeature(Strophe.NS.CAPS);this._connection.addHandler(this._delegateCapabilities.bind(this),Strophe.NS.CAPS)},generateCapsAttrs:function(){return{xmlns:Strophe.NS.CAPS,hash:this.HASH,node:this.node,ver:this.generateVer()}},generateVer:function(){if(this._ver!==""){return this._ver}var ver="",identities=this._connection.disco._identities.sort(this._sortIdentities),identitiesLen=identities.length,features=this._connection.disco._features.sort(),featuresLen=features.length;for(var i=0;i<identitiesLen;i++){var curIdent=identities[i];ver+=curIdent.category+"/"+curIdent.type+"/"+curIdent.lang+"/"+curIdent.name+"<"}for(var i=0;i<featuresLen;i++){ver+=features[i]+"<"}this._ver=b64_sha1(ver);return this._ver},getCapabilitiesByJid:function(jid){if(this._jidVerIndex[jid]){return this._knownCapabilities[this._jidVerIndex[jid]]}return null},_delegateCapabilities:function(stanza){var from=stanza.getAttribute("from"),c=stanza.querySelector("c"),ver=c.getAttribute("ver"),node=c.getAttribute("node");if(!this._knownCapabilities[ver]){return this._requestCapabilities(from,node,ver)}else{this._jidVerIndex[from]=ver}if(!this._jidVerIndex[from]||!this._jidVerIndex[from]!==ver){this._jidVerIndex[from]=ver}return true},_requestCapabilities:function(to,node,ver){if(to!==this._connection.jid){var id=this._connection.disco.info(to,node+"#"+ver);this._connection.addHandler(this._handleDiscoInfoReply.bind(this),Strophe.NS.DISCO_INFO,"iq","result",id,to)}return true},_handleDiscoInfoReply:function(stanza){var query=stanza.querySelector("query"),node=query.getAttribute("node").split("#"),ver=node[1],from=stanza.getAttribute("from");if(!this._knownCapabilities[ver]){var childNodes=query.childNodes,childNodesLen=childNodes.length;this._knownCapabilities[ver]=[];for(var i=0;i<childNodesLen;i++){var node=childNodes[i];this._knownCapabilities[ver].push({name:node.nodeName,attributes:node.attributes})}this._jidVerIndex[from]=ver}else{if(!this._jidVerIndex[from]||!this._jidVerIndex[from]!==ver){this._jidVerIndex[from]=ver}}return false},_sortIdentities:function(a,b){if(a.category>b.category){return 1}if(a.category<b.category){return -1}if(a.type>b.type){return 1}if(a.type<b.type){return -1}if(a.lang>b.lang){return 1}if(a.lang<b.lang){return -1}return 0}});

	/**
	 * TraceablePeerConnection
	 */

	function TraceablePeerConnection(ice_config, constraints) {
	    var self = this;
	    var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection;
	    this.peerconnection = new RTCPeerconnection(ice_config, constraints);
	    this.updateLog = [];
	    this.stats = {};
	    this.statsinterval = null;
	    this.maxstats = 0; // limit to 300 values, i.e. 5 minutes; set to 0 to disable

	    /**
	     * Array of ssrcs that will be added on next modifySources call.
	     * @type {Array}
	     */
	    this.addssrc = [];
	    /**
	     * Array of ssrcs that will be added on next modifySources call.
	     * @type {Array}
	     */
	    this.removessrc = [];
	    /**
	     * Pending operation that will be done during modifySources call.
	     * Currently 'mute'/'unmute' operations are supported.
	     *
	     * @type {String}
	     */
	    this.pendingop = null;

	    /**
	     * Flag indicates that peer connection stream have changed and modifySources should proceed.
	     * @type {boolean}
	     */
	    this.switchstreams = false;

	    // override as desired
	    this.trace = function (what, info) {
		//console.warn('WTRACE', what, info);
		self.updateLog.push({
		    time: new Date(),
		    type: what,
		    value: info || ""
		});
	    };
	    this.onicecandidate = null;
	    this.peerconnection.onicecandidate = function (event) {
		self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
		if (self.onicecandidate !== null) {
		    self.onicecandidate(event);
		}
	    };
	    this.onaddstream = null;
	    this.peerconnection.onaddstream = function (event) {
		self.trace('onaddstream', event.stream.id);
		if (self.onaddstream !== null) {
		    self.onaddstream(event);
		}
	    };
	    this.onremovestream = null;
	    this.peerconnection.onremovestream = function (event) {
		self.trace('onremovestream', event.stream.id);
		if (self.onremovestream !== null) {
		    self.onremovestream(event);
		}
	    };
	    this.onsignalingstatechange = null;
	    this.peerconnection.onsignalingstatechange = function (event) {
		self.trace('onsignalingstatechange', self.signalingState);
		if (self.onsignalingstatechange !== null) {
		    self.onsignalingstatechange(event);
		}
	    };
	    this.oniceconnectionstatechange = null;
	    this.peerconnection.oniceconnectionstatechange = function (event) {
		self.trace('oniceconnectionstatechange', self.iceConnectionState);
		if (self.oniceconnectionstatechange !== null) {
		    self.oniceconnectionstatechange(event);
		}
	    };
	    this.onnegotiationneeded = null;
	    this.peerconnection.onnegotiationneeded = function (event) {
		self.trace('onnegotiationneeded');
		if (self.onnegotiationneeded !== null) {
		    self.onnegotiationneeded(event);
		}
	    };
	    self.ondatachannel = null;
	    this.peerconnection.ondatachannel = function (event) {
		self.trace('ondatachannel', event);
		if (self.ondatachannel !== null) {
		    self.ondatachannel(event);
		}
	    };
	    if (!navigator.mozGetUserMedia && this.maxstats) {
		this.statsinterval = window.setInterval(function() {
		    self.peerconnection.getStats(function(stats) {
			var results = stats.result();
			for (var i = 0; i < results.length; ++i) {
			    //console.log(results[i].type, results[i].id, results[i].names())
			    var now = new Date();
			    results[i].names().forEach(function (name) {
				var id = results[i].id + '-' + name;
				if (!self.stats[id]) {
				    self.stats[id] = {
					startTime: now,
					endTime: now,
					values: [],
					times: []
				    };
				}
				self.stats[id].values.push(results[i].stat(name));
				self.stats[id].times.push(now.getTime());
				if (self.stats[id].values.length > self.maxstats) {
				    self.stats[id].values.shift();
				    self.stats[id].times.shift();
				}
				self.stats[id].endTime = now;
			    });
			}
		    });

		}, 1000);
	    }
	};

	dumpSDP = function(description) {
	    return 'type: ' + description.type + '\r\n' + description.sdp;
	}

	if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
	    TraceablePeerConnection.prototype.__defineGetter__('signalingState', function() { return this.peerconnection.signalingState; });
	    TraceablePeerConnection.prototype.__defineGetter__('iceConnectionState', function() { return this.peerconnection.iceConnectionState; });
	    TraceablePeerConnection.prototype.__defineGetter__('localDescription', function() {
		var publicLocalDescription = simulcast.reverseTransformLocalDescription(this.peerconnection.localDescription);
		return publicLocalDescription;
	    });
	    TraceablePeerConnection.prototype.__defineGetter__('remoteDescription', function() {
		var publicRemoteDescription = simulcast.reverseTransformRemoteDescription(this.peerconnection.remoteDescription);
		return publicRemoteDescription;
	    });
	}

	TraceablePeerConnection.prototype.addStream = function (stream) {
	    this.trace('addStream', stream.id);
	    simulcast.resetSender();
	    try
	    {
		this.peerconnection.addStream(stream);
	    }
	    catch (e)
	    {
		console.error(e);
		return;
	    }
	};

	TraceablePeerConnection.prototype.removeStream = function (stream, stopStreams) {
	    this.trace('removeStream', stream.id);
	    simulcast.resetSender();
	    if(stopStreams) {
		stream.getAudioTracks().forEach(function (track) {
		    track.stop();
		});
		stream.getVideoTracks().forEach(function (track) {
		    track.stop();
		});
	    }
	    this.peerconnection.removeStream(stream);
	};

	TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
	    this.trace('createDataChannel', label, opts);
	    return this.peerconnection.createDataChannel(label, opts);
	};

	TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
	    var self = this;
	    description = simulcast.transformLocalDescription(description);
	    this.trace('setLocalDescription', dumpSDP(description));
	    this.peerconnection.setLocalDescription(description,
		function () {
		    self.trace('setLocalDescriptionOnSuccess');
		    successCallback();
		},
		function (err) {
		    self.trace('setLocalDescriptionOnFailure', err);
		    failureCallback(err);
		}
	    );
	    /*
	     if (this.statsinterval === null && this.maxstats > 0) {
	     // start gathering stats
	     }
	     */
	};

	TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
	    var self = this;
	    description = simulcast.transformRemoteDescription(description);
	    this.trace('setRemoteDescription', dumpSDP(description));
	    this.peerconnection.setRemoteDescription(description,
		function () {
		    self.trace('setRemoteDescriptionOnSuccess');
		    successCallback();
		},
		function (err) {
		    self.trace('setRemoteDescriptionOnFailure', err);
		    failureCallback(err);
		}
	    );
	    /*
	     if (this.statsinterval === null && this.maxstats > 0) {
	     // start gathering stats
	     }
	     */
	};

	TraceablePeerConnection.prototype.hardMuteVideo = function (muted) {
	    this.pendingop = muted ? 'mute' : 'unmute';
	};

	TraceablePeerConnection.prototype.enqueueAddSsrc = function(channel, ssrcLines) {
	    if (!this.addssrc[channel]) {
		this.addssrc[channel] = '';
	    }
	    this.addssrc[channel] += ssrcLines;
	};

	TraceablePeerConnection.prototype.addSource = function (elem) {
	    //console.log('addssrc', new Date().getTime());
	    //console.log('ice', this.iceConnectionState);
	    var sdp = new SDP(this.remoteDescription.sdp);
	    var mySdp = new SDP(this.peerconnection.localDescription.sdp);

	    var self = this;
	    $(elem).each(function (idx, content) {
		var name = $(content).attr('name');
		var lines = '';
		tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
		    var semantics = this.getAttribute('semantics');
		    var ssrcs = $(this).find('>source').map(function () {
			return this.getAttribute('ssrc');
		    }).get();

		    if (ssrcs.length != 0) {
			lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
		    }
		});
		tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
		tmp.each(function () {
		    var ssrc = $(this).attr('ssrc');
		    if(mySdp.containsSSRC(ssrc)){
			/**
			 * This happens when multiple participants change their streams at the same time and
			 * ColibriFocus.modifySources have to wait for stable state. In the meantime multiple
			 * addssrc are scheduled for update IQ. See
			 */
			console.warn("Got add stream request for my own ssrc: "+ssrc);
			return;
		    }
		    $(this).find('>parameter').each(function () {
			lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
			if ($(this).attr('value') && $(this).attr('value').length)
			    lines += ':' + $(this).attr('value');
			lines += '\r\n';
		    });
		});
		sdp.media.forEach(function(media, idx) {
		    if (!SDPUtil.find_line(media, 'a=mid:' + name))
			return;
		    sdp.media[idx] += lines;
		    self.enqueueAddSsrc(idx, lines);
		});
		sdp.raw = sdp.session + sdp.media.join('');
	    });
	};

	TraceablePeerConnection.prototype.enqueueRemoveSsrc = function(channel, ssrcLines) {
	    if (!this.removessrc[channel]){
		this.removessrc[channel] = '';
	    }
	    this.removessrc[channel] += ssrcLines;
	};

	TraceablePeerConnection.prototype.removeSource = function (elem) {
	    //console.log('removessrc', new Date().getTime());
	    //console.log('ice', this.iceConnectionState);
	    var sdp = new SDP(this.remoteDescription.sdp);
	    var mySdp = new SDP(this.peerconnection.localDescription.sdp);

	    var self = this;
	    $(elem).each(function (idx, content) {
		var name = $(content).attr('name');
		var lines = '';
		tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
		    var semantics = this.getAttribute('semantics');
		    var ssrcs = $(this).find('>source').map(function () {
			return this.getAttribute('ssrc');
		    }).get();

		    if (ssrcs.length != 0) {
			lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
		    }
		});
		tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
		tmp.each(function () {
		    var ssrc = $(this).attr('ssrc');
		    // This should never happen, but can be useful for bug detection
		    if(mySdp.containsSSRC(ssrc)){
			console.error("Got remove stream request for my own ssrc: "+ssrc);
			return;
		    }
		    $(this).find('>parameter').each(function () {
			lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
			if ($(this).attr('value') && $(this).attr('value').length)
			    lines += ':' + $(this).attr('value');
			lines += '\r\n';
		    });
		});
		sdp.media.forEach(function(media, idx) {
		    if (!SDPUtil.find_line(media, 'a=mid:' + name))
			return;
		    sdp.media[idx] += lines;
		    self.enqueueRemoveSsrc(idx, lines);
		});
		sdp.raw = sdp.session + sdp.media.join('');
	    });
	};

	TraceablePeerConnection.prototype.modifySources = function(successCallback) {
	    var self = this;
	    if (this.signalingState == 'closed') return;
	    if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
		// There is nothing to do since scheduled job might have been executed by another succeeding call
		if(successCallback){
		    successCallback();
		}
		return;
	    }

	    // FIXME: this is a big hack
	    // https://code.google.com/p/webrtc/issues/detail?id=2688
	    // ^ has been fixed.
	    if (!(this.signalingState == 'stable' && this.iceConnectionState == 'connected')) {
		console.warn('modifySources not yet', this.signalingState, this.iceConnectionState);
		this.wait = true;
		window.setTimeout(function() { self.modifySources(successCallback); }, 250);
		return;
	    }
	    if (this.wait) {
		window.setTimeout(function() { self.modifySources(successCallback); }, 2500);
		this.wait = false;
		return;
	    }

	    // Reset switch streams flag
	    this.switchstreams = false;

	    var sdp = new SDP(this.remoteDescription.sdp);

	    // add sources
	    this.addssrc.forEach(function(lines, idx) {
		sdp.media[idx] += lines;
	    });
	    this.addssrc = [];

	    // remove sources
	    this.removessrc.forEach(function(lines, idx) {
		lines = lines.split('\r\n');
		lines.pop(); // remove empty last element;
		lines.forEach(function(line) {
		    sdp.media[idx] = sdp.media[idx].replace(line + '\r\n', '');
		});
	    });
	    this.removessrc = [];

	    // FIXME:
	    // this was a hack for the situation when only one peer exists
	    // in the conference.
	    // check if still required and remove
	    if (sdp.media[0])
		sdp.media[0] = sdp.media[0].replace('a=recvonly', 'a=sendrecv');
	    if (sdp.media[1])
		sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');

	    sdp.raw = sdp.session + sdp.media.join('');
	    this.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}),
		function() {

		    if(self.signalingState == 'closed') {
			console.error("createAnswer attempt on closed state");
			return;
		    }

		    self.createAnswer(
			function(modifiedAnswer) {
			    // change video direction, see https://github.com/jitsi/jitmeet/issues/41
			    if (self.pendingop !== null) {
				var sdp = new SDP(modifiedAnswer.sdp);
				if (sdp.media.length > 1) {
				    switch(self.pendingop) {
					case 'mute':
					    sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
					    break;
					case 'unmute':
					    sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
					    break;
				    }
				    sdp.raw = sdp.session + sdp.media.join('');
				    modifiedAnswer.sdp = sdp.raw;
				}
				self.pendingop = null;
			    }

			    // FIXME: pushing down an answer while ice connection state
			    // is still checking is bad...
			    //console.log(self.peerconnection.iceConnectionState);

			    // trying to work around another chrome bug
			    //modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass');
			    self.setLocalDescription(modifiedAnswer,
				function() {
				    //console.log('modified setLocalDescription ok');
				    if(successCallback){
					successCallback();
				    }
				},
				function(error) {
				    console.error('modified setLocalDescription failed', error);
				}
			    );
			},
			function(error) {
			    console.error('modified answer failed', error);
			}
		    );
		},
		function(error) {
		    console.error('modify failed', error);
		}
	    );
	};

	TraceablePeerConnection.prototype.close = function () {
	    this.trace('stop');
	    if (this.statsinterval !== null) {
		window.clearInterval(this.statsinterval);
		this.statsinterval = null;
	    }
	    try {
	    	this.peerconnection.close();
	    	
	    } catch (e) {}
	};

	TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
	    var self = this;
	    this.trace('createOffer', JSON.stringify(constraints, null, ' '));
	    this.peerconnection.createOffer(
		function (offer) {
		    self.trace('createOfferOnSuccess', dumpSDP(offer));
		    successCallback(offer);
		},
		function(err) {
		    self.trace('createOfferOnFailure', err);
		    failureCallback(err);
		},
		constraints
	    );
	};

	TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
	    var self = this;
	    this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
	    this.peerconnection.createAnswer(
		function (answer) {
		    answer = simulcast.transformAnswer(answer);
		    self.trace('createAnswerOnSuccess', dumpSDP(answer));
		    successCallback(answer);
		},
		function(err) {
		    self.trace('createAnswerOnFailure', err);
		    failureCallback(err);
		},
		constraints
	    );
	};

	TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
	    var self = this;
	    this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
	    this.peerconnection.addIceCandidate(candidate);
	    /* maybe later
	     this.peerconnection.addIceCandidate(candidate,
	     function () {
	     self.trace('addIceCandidateOnSuccess');
	     successCallback();
	     },
	     function (err) {
	     self.trace('addIceCandidateOnFailure', err);
	     failureCallback(err);
	     }
	     );
	     */
	};

	TraceablePeerConnection.prototype.getStats = function(callback, errback) {
	    if (navigator.mozGetUserMedia) {
		// ignore for now...
		if(!errback)
		    errback = function () {

		    }
		this.peerconnection.getStats(null,callback,errback);
	    } else {
		this.peerconnection.getStats(callback);
	    }
	};

	// mozilla chrome compat layer -- very similar to adapter.js
	function setupRTC() {
	    var RTC = null;
	    if (navigator.mozGetUserMedia) {
		//console.log('This appears to be Firefox');
		var version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
		if (version >= 22) {
		    RTC = {
			peerconnection: mozRTCPeerConnection,
			browser: 'firefox',
			getUserMedia: navigator.mozGetUserMedia.bind(navigator),
			attachMediaStream: function (element, stream) {
			    element[0].mozSrcObject = stream;
			    element[0].play();
			},
			pc_constraints: {},
			getLocalSSRC: function (session, callback) {
			    // NOTE(gp) latest FF nightlies seem to provide the local
			    // SSRCs in their SDP so there's no longer necessary to
			    // take it from the peer connection stats.
			    /*session.peerconnection.getStats(function (s) {
				    var ssrcs = {};
				    s.forEach(function (item) {
					if (item.type == "outboundrtp" && !item.isRemote)
					{
					    ssrcs[item.id.split('_')[2]] = item.ssrc;
					}
				    });
				    session.localStreamsSSRC = {
					"audio": ssrcs.audio,//for stable 0
					"video": ssrcs.video// for stable 1
				    };
				    callback(session.localStreamsSSRC);
				},
				function () {
				    callback(null);
				});*/
			    callback(null);
			},
			getStreamID: function (stream) {
			    var tracks = stream.getVideoTracks();
			    if(!tracks || tracks.length == 0)
			    {
				tracks = stream.getAudioTracks();
			    }
			    return tracks[0].id.replace(/[\{,\}]/g,"");
			},
			getVideoSrc: function (element) {
			    return element.mozSrcObject;
			},
			setVideoSrc: function (element, src) {
			    element.mozSrcObject = src;
			}
		    };
		    if (!MediaStream.prototype.getVideoTracks)
			MediaStream.prototype.getVideoTracks = function () { return []; };
		    if (!MediaStream.prototype.getAudioTracks)
			MediaStream.prototype.getAudioTracks = function () { return []; };
		    RTCSessionDescription = mozRTCSessionDescription;
		    RTCIceCandidate = mozRTCIceCandidate;
		}
	    } else if (navigator.webkitGetUserMedia) {
		//console.log('This appears to be Chrome');
		RTC = {
		    peerconnection: webkitRTCPeerConnection,
		    browser: 'chrome',
		    getUserMedia: navigator.webkitGetUserMedia.bind(navigator),
		    attachMediaStream: function (element, stream) {
			element.attr('src', webkitURL.createObjectURL(stream));
		    },
		    // DTLS should now be enabled by default but..
		    pc_constraints: {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]},
		    getLocalSSRC: function (session, callback) {
			callback(null);
		    },
		    getStreamID: function (stream) {
			// streams from FF endpoints have the characters '{' and '}'
			// that make jQuery choke.
			return stream.id.replace(/[\{,\}]/g,"");
		    },
		    getVideoSrc: function (element) {
			return element.getAttribute("src");
		    },
		    setVideoSrc: function (element, src) {
			element.setAttribute("src", src);
		    }
		};
		if (navigator.userAgent.indexOf('Android') != -1) {
		    RTC.pc_constraints = {}; // disable DTLS on Android
		}
		if (!webkitMediaStream.prototype.getVideoTracks) {
		    webkitMediaStream.prototype.getVideoTracks = function () {
			return this.videoTracks;
		    };
		}
		if (!webkitMediaStream.prototype.getAudioTracks) {
		    webkitMediaStream.prototype.getAudioTracks = function () {
			return this.audioTracks;
		    };
		}
	    }
	    if (RTC === null) {
		try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { }
	    }
	    return RTC;
	}

	function getUserMediaWithConstraints(um, success_callback, failure_callback, resolution, bandwidth, fps, desktopStream) {
	    var constraints = {audio: false, video: false};

	    if (um.indexOf('video') >= 0) {
		constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
	    }
	    if (um.indexOf('audio') >= 0) {
		constraints.audio = { mandatory: {}, optional: []};// same behaviour as true
	    }
	    if (um.indexOf('screen') >= 0) {
		constraints.video = {
		    mandatory: {
			chromeMediaSource: "screen",
			googLeakyBucket: true,
			maxWidth: window.screen.width,
			maxHeight: window.screen.height,
			maxFrameRate: 3
		    },
		    optional: []
		};
	    }
	    if (um.indexOf('desktop') >= 0) {
		constraints.video = {
		    mandatory: {
			chromeMediaSource: "desktop",
			chromeMediaSourceId: desktopStream,
			googLeakyBucket: true,
			maxWidth: window.screen.width,
			maxHeight: window.screen.height,
			maxFrameRate: 3
		    },
		    optional: []
		}
	    }

	    if (constraints.audio) {
		// if it is good enough for hangouts...
		constraints.audio.optional.push(
		    {googEchoCancellation: true},
		    {googAutoGainControl: true},
		    {googNoiseSupression: true},
		    {googHighpassFilter: true},
		    {googNoisesuppression2: true},
		    {googEchoCancellation2: true},
		    {googAutoGainControl2: true}
		);
	    }
	    if (constraints.video) {
		constraints.video.optional.push(
		    {googNoiseReduction: false} // chrome 37 workaround for issue 3807, reenable in M38
		);
		if (um.indexOf('video') >= 0) {
		    constraints.video.optional.push(
			{googLeakyBucket: true}
		    );
		}
	    }

	    // Check if we are running on Android device
	    var isAndroid = navigator.userAgent.indexOf('Android') != -1;

	    if (resolution && !constraints.video || isAndroid) {
		constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
	    }
	    // see https://code.google.com/p/chromium/issues/detail?id=143631#c9 for list of supported resolutions
	    switch (resolution) {
		// 16:9 first
		case '1080':
		case 'fullhd':
		    constraints.video.mandatory.minWidth = 1920;
		    constraints.video.mandatory.minHeight = 1080;
		    break;
		case '720':
		case 'hd':
		    constraints.video.mandatory.minWidth = 1280;
		    constraints.video.mandatory.minHeight = 720;
		    break;
		case '360':
		    constraints.video.mandatory.minWidth = 640;
		    constraints.video.mandatory.minHeight = 360;
		    break;
		case '180':
		    constraints.video.mandatory.minWidth = 320;
		    constraints.video.mandatory.minHeight = 180;
		    break;
		// 4:3
		case '960':
		    constraints.video.mandatory.minWidth = 960;
		    constraints.video.mandatory.minHeight = 720;
		    break;
		case '640':
		case 'vga':
		    constraints.video.mandatory.minWidth = 640;
		    constraints.video.mandatory.minHeight = 480;
		    break;
		case '320':
		    constraints.video.mandatory.minWidth = 320;
		    constraints.video.mandatory.minHeight = 240;
		    break;
		default:
		    if (isAndroid) {
			constraints.video.mandatory.minWidth = 320;
			constraints.video.mandatory.minHeight = 240;
			constraints.video.mandatory.maxFrameRate = 15;
		    }
		    break;
	    }
	    if (constraints.video)
	    {
		    if (constraints.video.mandatory.minWidth)
			constraints.video.mandatory.maxWidth = constraints.video.mandatory.minWidth;
		    if (constraints.video.mandatory.minHeight)
			constraints.video.mandatory.maxHeight = constraints.video.mandatory.minHeight;
	    }

	    if (bandwidth) { // doesn't work currently, see webrtc issue 1846
		if (!constraints.video) constraints.video = {mandatory: {}, optional: []};//same behaviour as true
		constraints.video.optional.push({bandwidth: bandwidth});
	    }
	    if (fps) { // for some cameras it might be necessary to request 30fps
		// so they choose 30fps mjpg over 10fps yuy2
		if (!constraints.video) constraints.video = {mandatory: {}, optional: []};// same behaviour as true;
		constraints.video.mandatory.minFrameRate = fps;
	    }

	    var isFF = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;

	    try {
		if (config.enableSimulcast
		    && constraints.video
		    && constraints.video.chromeMediaSource !== 'screen'
		    && constraints.video.chromeMediaSource !== 'desktop'
		    && !isAndroid

		    // We currently do not support FF, as it doesn't have multistream support.
		    && !isFF) {
		    simulcast.getUserMedia(constraints, function (stream) {
			    //console.log('onUserMediaSuccess');
			    success_callback(stream);
			},
			function (error) {
			    console.warn('Failed to get access to local media. Error ', error);
			    if (failure_callback) {
				failure_callback(error);
			    }
			});
		} else {

		    RTC.getUserMedia(constraints,
			function (stream) {
			    //console.log('onUserMediaSuccess');
			    success_callback(stream);
			},
			function (error) {
			    console.warn('Failed to get access to local media. Error ', error);
			    if (failure_callback) {
				failure_callback(error);
			    }
			});

		}
	    } catch (e) {
		console.error('GUM failed: ', e);
		if(failure_callback) {
		    failure_callback(e);
		}
	    }
	}


	/**
	 * Strophe logger implementation. Logs from level WARN and above.
	 */

	Strophe.log = function (level, msg) {
	    switch(level) {
		case Strophe.LogLevel.WARN:
		    console.warn("Strophe: "+msg);
		    break;
		case Strophe.LogLevel.ERROR:
		case Strophe.LogLevel.FATAL:
		    console.error("Strophe: "+msg);
		    break;
	    }
	};

	Strophe.getStatusString = function(status)
	{
	    switch (status)
	    {
		case Strophe.Status.ERROR:
		    return "ERROR";
		case Strophe.Status.CONNECTING:
		    return "CONNECTING";
		case Strophe.Status.CONNFAIL:
		    return "CONNFAIL";
		case Strophe.Status.AUTHENTICATING:
		    return "AUTHENTICATING";
		case Strophe.Status.AUTHFAIL:
		    return "AUTHFAIL";
		case Strophe.Status.CONNECTED:
		    return "CONNECTED";
		case Strophe.Status.DISCONNECTED:
		    return "DISCONNECTED";
		case Strophe.Status.DISCONNECTING:
		    return "DISCONNECTING";
		case Strophe.Status.ATTACHED:
		    return "ATTACHED";
		default:
		    return "unknown";
	    }
	};

	/**
	 * The audio Levels plugin.
	 */
	var AudioLevels = (function(my) {
	    var audioLevelCanvasCache = {};

	    my.LOCAL_LEVEL = 'local';

	    /**
	     * Updates the audio level canvas for the given peerJid. If the canvas
	     * didn't exist we create it.
	     */
	    my.updateAudioLevelCanvas = function (peerJid) {
		var resourceJid = null;
		var videoSpanId = null;
		if (!peerJid)
		    videoSpanId = 'localVideoContainer';
		else {
		    resourceJid = Strophe.getResourceFromJid(peerJid);

		    videoSpanId = 'participant_' + resourceJid;
		}

		var videoSpan = document.getElementById(videoSpanId);

		if (!videoSpan) {
		    if (resourceJid)
			console.error("No video element for jid", resourceJid);
		    else
			console.error("No video element for local video.");

		    return;
		}

		var audioLevelCanvas = $('#' + videoSpanId + '>canvas');

		var videoSpaceWidth = $('#remoteVideos').width();
		var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
		var thumbnailWidth = thumbnailSize[0];
		var thumbnailHeight = thumbnailSize[1];

		if (!audioLevelCanvas || audioLevelCanvas.length === 0) {

		    audioLevelCanvas = document.createElement('canvas');
		    audioLevelCanvas.className = "audiolevel";
		    audioLevelCanvas.style.bottom = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px";
		    audioLevelCanvas.style.left = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px";
		    resizeAudioLevelCanvas( audioLevelCanvas,
			    thumbnailWidth,
			    thumbnailHeight);

		    videoSpan.appendChild(audioLevelCanvas);
		} else {
		    audioLevelCanvas = audioLevelCanvas.get(0);

		    resizeAudioLevelCanvas( audioLevelCanvas,
			    thumbnailWidth,
			    thumbnailHeight);
		}
	    };

	    /**
	     * Updates the audio level UI for the given resourceJid.
	     *
	     * @param resourceJid the resource jid indicating the video element for
	     * which we draw the audio level
	     * @param audioLevel the newAudio level to render
	     */
	    my.updateAudioLevel = function (resourceJid, audioLevel) {
		drawAudioLevelCanvas(resourceJid, audioLevel);

		var videoSpanId = getVideoSpanId(resourceJid);

		var audioLevelCanvas = $('#' + videoSpanId + '>canvas').get(0);

		if (!audioLevelCanvas)
		    return;

		var drawContext = audioLevelCanvas.getContext('2d');

		var canvasCache = audioLevelCanvasCache[resourceJid];

		drawContext.clearRect (0, 0,
			audioLevelCanvas.width, audioLevelCanvas.height);
		drawContext.drawImage(canvasCache, 0, 0);

		if(resourceJid === AudioLevels.LOCAL_LEVEL) {
		    if(!connection.emuc.myroomjid) {
			return;
		    }
		    resourceJid = Strophe.getResourceFromJid(connection.emuc.myroomjid);
		}

		if(resourceJid  === VideoLayout.getLargeVideoState().userResourceJid) {
		    AudioLevels.updateActiveSpeakerAudioLevel(audioLevel);
		}
	    };

	    my.updateActiveSpeakerAudioLevel = function(audioLevel) {
		var drawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
		var r = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2;
		var center = (interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE + r) / 2;

		// Save the previous state of the context.
		drawContext.save();

		drawContext.clearRect(0, 0, 300, 300);

		// Draw a circle.
		drawContext.arc(center, center, r, 0, 2 * Math.PI);

		// Add a shadow around the circle
		drawContext.shadowColor = interfaceConfig.SHADOW_COLOR;
		drawContext.shadowBlur = getShadowLevel(audioLevel);
		drawContext.shadowOffsetX = 0;
		drawContext.shadowOffsetY = 0;

		// Fill the shape.
		drawContext.fill();

		drawContext.save();

		drawContext.restore();


		drawContext.arc(center, center, r, 0, 2 * Math.PI);

		drawContext.clip();
		drawContext.clearRect(0, 0, 277, 200);

		// Restore the previous context state.
		drawContext.restore();
	    };

	    /**
	     * Resizes the given audio level canvas to match the given thumbnail size.
	     */
	    function resizeAudioLevelCanvas(audioLevelCanvas,
					    thumbnailWidth,
					    thumbnailHeight) {
		audioLevelCanvas.width = thumbnailWidth + interfaceConfig.CANVAS_EXTRA;
		audioLevelCanvas.height = thumbnailHeight + interfaceConfig.CANVAS_EXTRA;
	    }

	    /**
	     * Draws the audio level canvas into the cached canvas object.
	     *
	     * @param resourceJid the resource jid indicating the video element for
	     * which we draw the audio level
	     * @param audioLevel the newAudio level to render
	     */
	    function drawAudioLevelCanvas(resourceJid, audioLevel) {
		if (!audioLevelCanvasCache[resourceJid]) {

		    var videoSpanId = getVideoSpanId(resourceJid);

		    var audioLevelCanvasOrig = $('#' + videoSpanId + '>canvas').get(0);

		    /*
		     * FIXME Testing has shown that audioLevelCanvasOrig may not exist.
		     * In such a case, the method CanvasUtil.cloneCanvas may throw an
		     * error. Since audio levels are frequently updated, the errors have
		     * been observed to pile into the console, strain the CPU.
		     */
		    if (audioLevelCanvasOrig)
		    {
			audioLevelCanvasCache[resourceJid]
			    = CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
		    }
		}

		var canvas = audioLevelCanvasCache[resourceJid];

		if (!canvas || $('#videospace').css("display") == "none")
		    return;

		var drawContext = canvas.getContext('2d');

		drawContext.clearRect(0, 0, canvas.width, canvas.height);

		var shadowLevel = getShadowLevel(audioLevel);

		if (shadowLevel > 0)
		    // drawContext, x, y, w, h, r, shadowColor, shadowLevel
		    CanvasUtil.drawRoundRectGlow(   drawContext,
			interfaceConfig.CANVAS_EXTRA/2, interfaceConfig.CANVAS_EXTRA/2,
			canvas.width - interfaceConfig.CANVAS_EXTRA,
			canvas.height - interfaceConfig.CANVAS_EXTRA,
			interfaceConfig.CANVAS_RADIUS,
			interfaceConfig.SHADOW_COLOR,
			shadowLevel);
	    }

	    /**
	     * Returns the shadow/glow level for the given audio level.
	     *
	     * @param audioLevel the audio level from which we determine the shadow
	     * level
	     */
	    function getShadowLevel (audioLevel) {
		var shadowLevel = 0;

		if (audioLevel <= 0.3) {
		    shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*(audioLevel/0.3));
		}
		else if (audioLevel <= 0.6) {
		    shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3));
		}
		else {
		    shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4));
		}
		return shadowLevel;
	    }

	    /**
	     * Returns the video span id corresponding to the given resourceJid or local
	     * user.
	     */
	    function getVideoSpanId(resourceJid) {
		var videoSpanId = null;
		if (resourceJid === AudioLevels.LOCAL_LEVEL
			|| (connection.emuc.myroomjid && resourceJid
			    === Strophe.getResourceFromJid(connection.emuc.myroomjid)))
		    videoSpanId = 'localVideoContainer';
		else
		    videoSpanId = 'participant_' + resourceJid;

		return videoSpanId;
	    }

	    /**
	     * Indicates that the remote video has been resized.
	     */
	    $(document).bind('remotevideo.resized', function (event, width, height) {
		var resized = false;
		$('#remoteVideos>span>canvas').each(function() {
		    var canvas = $(this).get(0);
		    if (canvas.width !== width + interfaceConfig.CANVAS_EXTRA) {
			canvas.width = width + interfaceConfig.CANVAS_EXTRA;
			resized = true;
		    }

		    if (canvas.heigh !== height + interfaceConfig.CANVAS_EXTRA) {
			canvas.height = height + interfaceConfig.CANVAS_EXTRA;
			resized = true;
		    }
		});

		if (resized)
		    Object.keys(audioLevelCanvasCache).forEach(function (resourceJid) {
			audioLevelCanvasCache[resourceJid].width
			    = width + interfaceConfig.CANVAS_EXTRA;
			audioLevelCanvasCache[resourceJid].height
			    = height + interfaceConfig.CANVAS_EXTRA;
		    });
	    });

	    return my;

	})(AudioLevels || {});


	/**
	 * ConnectionQuality.
	 */

	var ConnectionQuality = (function () {

	    /**
	     * Constructs new ConnectionQuality object
	     * @constructor
	     */
	    function ConnectionQuality() {

	    }

	    /**
	     * local stats
	     * @type {{}}
	     */
	    var stats = {};

	    /**
	     * remote stats
	     * @type {{}}
	     */
	    var remoteStats = {};

	    /**
	     * Interval for sending statistics to other participants
	     * @type {null}
	     */
	    var sendIntervalId = null;

	    /**
	     * Updates the local statistics
	     * @param data new statistics
	     */
	    ConnectionQuality.updateLocalStats = function (data) {
		stats = data;
		VideoLayout.updateLocalConnectionStats(100 - stats.packetLoss.total,stats);
		if(sendIntervalId == null)
		{
		    startSendingStats();
		}
	    };

	    /**
	     * Start statistics sending.
	     */
	    function startSendingStats() {
		sendStats();
		sendIntervalId = setInterval(sendStats, 10000);
	    }

	    /**
	     * Sends statistics to other participants
	     */
	    function sendStats() {
		connection.emuc.addConnectionInfoToPresence(convertToMUCStats(stats));
		connection.emuc.sendPresence();
	    }

	    /**
	     * Converts statistics to format for sending through XMPP
	     * @param stats the statistics
	     * @returns {{bitrate_donwload: *, bitrate_uplpoad: *, packetLoss_total: *, packetLoss_download: *, packetLoss_upload: *}}
	     */
	    function convertToMUCStats(stats) {
		return {
		    "bitrate_download": stats.bitrate.download,
		    "bitrate_upload": stats.bitrate.upload,
		    "packetLoss_total": stats.packetLoss.total,
		    "packetLoss_download": stats.packetLoss.download,
		    "packetLoss_upload": stats.packetLoss.upload
		};
	    }

	    /**
	     * Converts statitistics to format used by VideoLayout
	     * @param stats
	     * @returns {{bitrate: {download: *, upload: *}, packetLoss: {total: *, download: *, upload: *}}}
	     */
	    function parseMUCStats(stats) {
		return {
		    bitrate: {
			download: stats.bitrate_download,
			upload: stats.bitrate_upload
		    },
		    packetLoss: {
			total: stats.packetLoss_total,
			download: stats.packetLoss_download,
			upload: stats.packetLoss_upload
		    }
		};
	    }

	    /**
	     * Updates remote statistics
	     * @param jid the jid associated with the statistics
	     * @param data the statistics
	     */
	    ConnectionQuality.updateRemoteStats = function (jid, data) {
		if(data == null || data.packetLoss_total == null)
		{
		    VideoLayout.updateConnectionStats(jid, null, null);
		    return;
		}
		remoteStats[jid] = parseMUCStats(data);

		VideoLayout.updateConnectionStats(jid, 100 - data.packetLoss_total,remoteStats[jid]);

	    };

	    /**
	     * Stops statistics sending.
	     */
	    ConnectionQuality.stopSendingStats = function () {
		clearInterval(sendIntervalId);
		sendIntervalId = null;
		//notify UI about stopping statistics gathering
		VideoLayout.onStatsStop();
	    };

	    /**
	     * Returns the local statistics.
	     */
	    ConnectionQuality.getStats = function () {
		return stats;
	    }

	    return ConnectionQuality;
	})();


	/**
	 * Avatar
	 */

	var Avatar = (function(my) {
	    var users = {};
	    var activeSpeakerJid;
	    /**
	     * Sets the user's avatar in the settings menu(if local user), contact list
	     * and thumbnail
	     * @param jid jid of the user
	     * @param id email or userID to be used as a hash
	     */
	    my.setUserAvatar = function(jid, id) {
		if(id) {
		    if(users[jid] === id) {
			return;
		    }
		    users[jid] = id;
		}
		var thumbUrl = getGravatarUrl(users[jid] || jid, 100, jid); // BAO
		var contactListUrl = getGravatarUrl(users[jid] || jid, null, jid);
		var resourceJid = Strophe.getResourceFromJid(jid);
		var thumbnail = $('#participant_' + resourceJid);
		var avatar = $('#avatar_' + resourceJid);

		// set the avatar in the thumbnail
		if(avatar && avatar.length > 0) {
		    avatar[0].src = thumbUrl;
		} else {
		    if (thumbnail && thumbnail.length > 0) {
			avatar = document.createElement('img');
			avatar.id = 'avatar_' + resourceJid;
			avatar.className = 'userAvatar';
			avatar.src = thumbUrl;
			thumbnail.append(avatar);
		    }
		}

		//if the user is the current active speaker - update the active speaker
		// avatar
		if(jid === activeSpeakerJid) {
		    Avatar.updateActiveSpeakerAvatarSrc(jid);
		}
	    };

	    /**
	     * Hides or shows the user's avatar
	     * @param jid jid of the user
	     * @param show whether we should show the avatar or not
	     * video because there is no dominant speaker and no focused speaker
	     */
	    my.showUserAvatar = function(jid, show) {
		if(users[jid]) {
		    var resourceJid = Strophe.getResourceFromJid(jid);
		    var video = $('#participant_' + resourceJid + '>video');
		    var avatar = $('#avatar_' + resourceJid);

		    if(jid === connection.emuc.myroomjid) {
			video = $('#localVideoWrapper>video');
		    }
		    if(show === undefined || show === null) {
			show = isUserMuted(jid);
		    }

		    //if the user is the currently focused, the dominant speaker or if
		    //there is no focused and no dominant speaker and the large video is
		    //currently shown
		    if (activeSpeakerJid === jid && VideoLayout.isLargeVideoOnTop()) {
			setVisibility($("#largeVideo"), !show);
			setVisibility($('#activeSpeaker'), show);
			setVisibility(avatar, false);
			setVisibility(video, false);
		    } else {
			if (video && video.length > 0) {
			    setVisibility(video, !show);
			    setVisibility(avatar, show);
			}
		    }
		}
	    };

	    /**
	     * Updates the src of the active speaker avatar
	     * @param jid of the current active speaker
	     */
	    my.updateActiveSpeakerAvatarSrc = function(jid) {
		if(!jid) {
		    jid = connection.emuc.findJidFromResource(
			    VideoLayout.getLargeVideoState().userResourceJid);
		}
		var avatar = $("#activeSpeakerAvatar")[0];
		var url = getGravatarUrl(users[jid],
		    interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE, jid); // BAO
		if(jid === activeSpeakerJid && avatar.src === url) {
		    return;
		}
		activeSpeakerJid = jid;
		var isMuted = isUserMuted(jid);
		if(jid && isMuted !== null) {
		    avatar.src = url;
		    setVisibility($("#largeVideo"), !isMuted);
		    Avatar.showUserAvatar(jid, isMuted);
		}
	    };

	    function setVisibility(selector, show) {
		if (selector && selector.length > 0) {
		    selector.css("visibility", show ? "visible" : "hidden");
		}
	    }

	    function isUserMuted(jid) {
		// XXX(gp) we may want to rename this method to something like
		// isUserStreaming, for example.
		if (jid && jid != connection.emuc.myroomjid) {
		    var resource = Strophe.getResourceFromJid(jid);
		    if (!VideoLayout.isInLastN(resource)) {
			return true;
		    }
		}

		if (!mediaStreams[jid] || !mediaStreams[jid][MediaStream.VIDEO_TYPE]) {
		    return null;
		}
		return mediaStreams[jid][MediaStream.VIDEO_TYPE].muted;
	    }

	    function getGravatarUrl(id, size, jid) {		// BAO

		if (connection.emuc.myroomjid == jid && config.userAvatar && config.userAvatar != "null")
		{
			return config.userAvatar;	// BAO openfire avatars

		} else if (connection.ofmuc.members[jid] && connection.ofmuc.members[jid].avatar) {

			return connection.ofmuc.members[jid].avatar;
		}

		if(id === connection.emuc.myroomjid || !id) {
		    id = SettingsMenu.getUID();
		}

		return 'https://www.gravatar.com/avatar/' +
		    MD5.hexdigest(id.trim().toLowerCase()) +
		    "?d=mm&size=" + (size || "30");
	    }

	    return my;
	}(Avatar || {}));

	/**
	 * Utility class for drawing canvas shapes.
	 */
	var CanvasUtil = (function(my) {

	    /**
	     * Draws a round rectangle with a glow. The glowWidth indicates the depth
	     * of the glow.
	     *
	     * @param drawContext the context of the canvas to draw to
	     * @param x the x coordinate of the round rectangle
	     * @param y the y coordinate of the round rectangle
	     * @param w the width of the round rectangle
	     * @param h the height of the round rectangle
	     * @param glowColor the color of the glow
	     * @param glowWidth the width of the glow
	     */
	    my.drawRoundRectGlow
		= function(drawContext, x, y, w, h, r, glowColor, glowWidth) {

		// Save the previous state of the context.
		drawContext.save();

		if (w < 2 * r) r = w / 2;
		if (h < 2 * r) r = h / 2;

		// Draw a round rectangle.
		drawContext.beginPath();
		drawContext.moveTo(x+r, y);
		drawContext.arcTo(x+w, y,   x+w, y+h, r);
		drawContext.arcTo(x+w, y+h, x,   y+h, r);
		drawContext.arcTo(x,   y+h, x,   y,   r);
		drawContext.arcTo(x,   y,   x+w, y,   r);
		drawContext.closePath();

		// Add a shadow around the rectangle
		drawContext.shadowColor = glowColor;
		drawContext.shadowBlur = glowWidth;
		drawContext.shadowOffsetX = 0;
		drawContext.shadowOffsetY = 0;

		// Fill the shape.
		drawContext.fill();

		drawContext.save();

		drawContext.restore();

	//      1) Uncomment this line to use Composite Operation, which is doing the
	//      same as the clip function below and is also antialiasing the round
	//      border, but is said to be less fast performance wise.

	//      drawContext.globalCompositeOperation='destination-out';

		drawContext.beginPath();
		drawContext.moveTo(x+r, y);
		drawContext.arcTo(x+w, y,   x+w, y+h, r);
		drawContext.arcTo(x+w, y+h, x,   y+h, r);
		drawContext.arcTo(x,   y+h, x,   y,   r);
		drawContext.arcTo(x,   y,   x+w, y,   r);
		drawContext.closePath();

	//      2) Uncomment this line to use Composite Operation, which is doing the
	//      same as the clip function below and is also antialiasing the round
	//      border, but is said to be less fast performance wise.

	//      drawContext.fill();

		// Comment these two lines if choosing to do the same with composite
		// operation above 1 and 2.
		drawContext.clip();
		drawContext.clearRect(0, 0, 277, 200);

		// Restore the previous context state.
		drawContext.restore();
	    };

	    /**
	     * Clones the given canvas.
	     *
	     * @return the new cloned canvas.
	     */
	    my.cloneCanvas = function (oldCanvas) {
		/*
		 * FIXME Testing has shown that oldCanvas may not exist. In such a case,
		 * the method CanvasUtil.cloneCanvas may throw an error. Since audio
		 * levels are frequently updated, the errors have been observed to pile
		 * into the console, strain the CPU.
		 */
		if (!oldCanvas)
		    return oldCanvas;

		//create a new canvas
		var newCanvas = document.createElement('canvas');
		var context = newCanvas.getContext('2d');

		//set dimensions
		newCanvas.width = oldCanvas.width;
		newCanvas.height = oldCanvas.height;

		//apply the old canvas to the new one
		context.drawImage(oldCanvas, 0, 0);

		//return the new canvas
		return newCanvas;
	    };

	    return my;
	})(CanvasUtil || {});

	/**
	 * Handles commands received via chat messages.
	 */
	var CommandsProcessor = (function()
	{
	    /**
	     * Constructs new CommandProccessor instance from a message.
	     * @param message the message
	     * @constructor
	     */
	    function CommandsPrototype(message)
	    {
		/**
		 * Extracts the command from the message.
		 * @param message the received message
		 * @returns {string} the command
		 */
		function getCommand(message)
		{
		    if(message)
		    {
			for(var command in commands)
			{
			    if(message.indexOf("/" + command) == 0)
				return command;
			}
		    }
		    return "";
		};

		var command = getCommand(message);

		/**
		 * Returns the name of the command.
		 * @returns {String} the command
		 */
		this.getCommand = function()
		{
		    return command;
		}


		var messageArgument = message.substr(command.length + 2);

		/**
		 * Returns the arguments of the command.
		 * @returns {string}
		 */
		this.getArgument = function()
		{
		    return messageArgument;
		}
	    }

	    /**
	     * Checks whether this instance is valid command or not.
	     * @returns {boolean}
	     */
	    CommandsPrototype.prototype.isCommand = function()
	    {
		if(this.getCommand())
		    return true;
		return false;
	    }

	    /**
	     * Processes the command.
	     */
	    CommandsPrototype.prototype.processCommand = function()
	    {
		if(!this.isCommand())
		    return;

		commands[this.getCommand()](this.getArgument());

	    }

	    /**
	     * Processes the data for topic command.
	     * @param commandArguments the arguments of the topic command.
	     */
	    var processTopic = function(commandArguments)
	    {
		var topic = Util.escapeHtml(commandArguments);
		connection.emuc.setSubject(topic);
	    }

	    /**
	     * List with supported commands. The keys are the names of the commands and
	     * the value is the function that processes the message.
	     * @type {{String: function}}
	     */
	    var commands = {
		"topic" : processTopic
	    };

	    return CommandsPrototype;
	})();


	/* global connection, Strophe, updateLargeVideo, focusedVideoSrc*/

	// cache datachannels to avoid garbage collection
	// https://code.google.com/p/chromium/issues/detail?id=405545
	var _dataChannels = [];

	/**
	 * Callback triggered by PeerConnection when new data channel is opened
	 * on the bridge.
	 * @param event the event info object.
	 */

	function onDataChannel(event)
	{
	    var dataChannel = event.channel;

	    dataChannel.onopen = function ()
	    {
		//console.info("Data channel opened by the Videobridge!", dataChannel);

		// Code sample for sending string and/or binary data
		// Sends String message to the bridge
		//dataChannel.send("Hello bridge!");
		// Sends 12 bytes binary message to the bridge
		//dataChannel.send(new ArrayBuffer(12));

		// when the data channel becomes available, tell the bridge about video
		// selections so that it can do adaptive simulcast,
		var userJid = VideoLayout.getLargeVideoState().userJid;
		// we want the notification to trigger even if userJid is undefined,
		// or null.
		onSelectedEndpointChanged(userJid);
	    };

	    dataChannel.onerror = function (error)
	    {
		console.error("Data Channel Error:", error, dataChannel);
	    };

	    dataChannel.onmessage = function (event)
	    {
		var data = event.data;
		// JSON
		var obj;

		try
		{
		    obj = JSON.parse(data);
		}
		catch (e)
		{
		    console.error(
			"Failed to parse data channel message as JSON: ",
			data,
			dataChannel);
		}
		if (('undefined' !== typeof(obj)) && (null !== obj))
		{
		    var colibriClass = obj.colibriClass;

		    if ("DominantSpeakerEndpointChangeEvent" === colibriClass)
		    {
			// Endpoint ID from the Videobridge.
			var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint;

			//console.info("Data channel new dominant speaker event: ",   dominantSpeakerEndpoint);
			$(document).trigger(
			    'dominantspeakerchanged',
			    [dominantSpeakerEndpoint]);
		    }
		    else if ("InLastNChangeEvent" === colibriClass)
		    {
			var oldValue = obj.oldValue;
			var newValue = obj.newValue;
			// Make sure that oldValue and newValue are of type boolean.
			var type;

			if ((type = typeof oldValue) !== 'boolean') {
			    if (type === 'string') {
				oldValue = (oldValue == "true");
			    } else {
				oldValue = new Boolean(oldValue).valueOf();
			    }
			}
			if ((type = typeof newValue) !== 'boolean') {
			    if (type === 'string') {
				newValue = (newValue == "true");
			    } else {
				newValue = new Boolean(newValue).valueOf();
			    }
			}
			$(document).trigger('inlastnchanged', [oldValue, newValue]);
		    }
		    else if ("LastNEndpointsChangeEvent" === colibriClass)
		    {
			// The new/latest list of last-n endpoint IDs.
			var lastNEndpoints = obj.lastNEndpoints;
			// The list of endpoint IDs which are entering the list of
			// last-n at this time i.e. were not in the old list of last-n
			// endpoint IDs.
			var endpointsEnteringLastN = obj.endpointsEnteringLastN;
			var stream = obj.stream;

			//console.log("Data channel new last-n event: ",  lastNEndpoints, endpointsEnteringLastN, obj);
			
			$(document).trigger(
			    'lastnchanged',
			    [lastNEndpoints, endpointsEnteringLastN, stream]);
		    }
		    else if ("SimulcastLayersChangedEvent" === colibriClass)
		    {
			$(document).trigger(
			    'simulcastlayerschanged',
			    [obj.endpointSimulcastLayers]);
		    }
		    else if ("SimulcastLayersChangingEvent" === colibriClass)
		    {
			$(document).trigger(
			    'simulcastlayerschanging',
			    [obj.endpointSimulcastLayers]);
		    }
		    else if ("StartSimulcastLayerEvent" === colibriClass)
		    {
			$(document).trigger('startsimulcastlayer', obj.simulcastLayer);
		    }
		    else if ("StopSimulcastLayerEvent" === colibriClass)
		    {
			$(document).trigger('stopsimulcastlayer', obj.simulcastLayer);
		    }
		    else
		    {
			console.debug("Data channel JSON-formatted message: ", obj);
		    }
		}
	    };

	    dataChannel.onclose = function ()
	    {
		//console.info("The Data Channel closed", dataChannel);
		var idx = _dataChannels.indexOf(dataChannel);
		if (idx > -1) 
		    _dataChannels = _dataChannels.splice(idx, 1);
	    };
	    _dataChannels.push(dataChannel);
	}

	/**
	 * Binds "ondatachannel" event listener to given PeerConnection instance.
	 * @param peerConnection WebRTC peer connection instance.
	 */
	function bindDataChannelListener(peerConnection)
	{
	    peerConnection.ondatachannel = onDataChannel;

	    // Sample code for opening new data channel from Jitsi Meet to the bridge.
	    // Although it's not a requirement to open separate channels from both bridge
	    // and peer as single channel can be used for sending and receiving data.
	    // So either channel opened by the bridge or the one opened here is enough
	    // for communication with the bridge.
	/*
	    var dataChannelOptions = { reliable: true };
	    var dataChannel
	       = peerConnection.createDataChannel("myChannel", dataChannelOptions);

	    // Can be used only when is in open state
	    dataChannel.onopen = function ()
	    {
		dataChannel.send("My channel !!!");
	    };
	    dataChannel.onmessage = function (event)
	    {
		var msgData = event.data;
		//console.info("Got My Data Channel Message:", msgData, dataChannel);
	    };
	*/
	}


	/* global Strophe */
	Strophe.addConnectionPlugin('logger', {
	    // logs raw stanzas and makes them available for download as JSON
	    connection: null,
	    log: [],
	    init: function (conn) {
		this.connection = conn;
		this.connection.rawInput = this.log_incoming.bind(this);
		this.connection.rawOutput = this.log_outgoing.bind(this);
	    },
	    log_incoming: function (stanza) {
		this.log.push([new Date().getTime(), 'incoming', stanza]);
	    },
	    log_outgoing: function (stanza) {
		this.log.push([new Date().getTime(), 'outgoing', stanza]);
	    },
	});


	/**
	 * JitsiPopover
	 */

	var JitsiPopover = (function () {
	    /**
	     * Constructs new JitsiPopover and attaches it to the element
	     * @param element jquery selector
	     * @param options the options for the popover.
	     * @constructor
	     */
	    function JitsiPopover(element, options)
	    {
		this.options = {
		    skin: "white",
		    content: ""
		};
		if(options)
		{
		    if(options.skin)
			this.options.skin = options.skin;

		    if(options.content)
			this.options.content = options.content;
		}

		this.elementIsHovered = false;
		this.popoverIsHovered = false;
		this.popoverShown = false;

		element.data("jitsi_popover", this);
		this.element = element;
		this.template = ' <div class="jitsipopover ' + this.options.skin +
		    '"><div class="arrow"></div><div class="jitsipopover-content"></div>' +
		    '<div class="jitsiPopupmenuPadding"></div></div>';
		var self = this;
		this.element.on("mouseenter", function () {
		    self.elementIsHovered = true;
		    self.show();
		}).on("mouseleave", function () {
		    self.elementIsHovered = false;
		    setTimeout(function () {
			self.hide();
		    }, 10);
		});
	    }

	    /**
	     * Shows the popover
	     */
	    JitsiPopover.prototype.show = function () {
		this.createPopover();
		this.popoverShown = true;

	    };

	    /**
	     * Hides the popover
	     */
	    JitsiPopover.prototype.hide = function () {
		if(!this.elementIsHovered && !this.popoverIsHovered && this.popoverShown)
		{
		    this.forceHide();
		}
	    };

	    /**
	     * Hides the popover
	     */
	    JitsiPopover.prototype.forceHide = function () {
		$(".jitsipopover").remove();
		this.popoverShown = false;
	    };

	    /**
	     * Creates the popover html
	     */
	    JitsiPopover.prototype.createPopover = function () {
		$("body").append(this.template);
		$(".jitsipopover > .jitsipopover-content").html(this.options.content);
		var self = this;
		$(".jitsipopover").on("mouseenter", function () {
		    self.popoverIsHovered = true;
		}).on("mouseleave", function () {
		    self.popoverIsHovered = false;
		    self.hide();
		});

		this.refreshPosition();
	    };

	    /**
	     * Refreshes the position of the popover
	     */
	    JitsiPopover.prototype.refreshPosition = function () {
		$(".jitsipopover").position({
		    my: "bottom",
		    at: "top",
		    collision: "fit",
		    of: this.element,
		    using: function (position, elements) {
			var calcLeft = elements.target.left - elements.element.left + elements.target.width/2;
			$(".jitsipopover").css({top: position.top, left: position.left, display: "table"});
			$(".jitsipopover > .arrow").css({left: calcLeft});
			$(".jitsipopover > .jitsiPopupmenuPadding").css({left: calcLeft - 50});
		    }
		});
	    };

	    /**
	     * Updates the content of popover.
	     * @param content new content
	     */
	    JitsiPopover.prototype.updateContent = function (content) {
		this.options.content = content;
		if(!this.popoverShown)
		    return;
		$(".jitsipopover").remove();
		this.createPopover();
	    };

	    return JitsiPopover;


	})();


	/**
	 * VideoLayout
	 */

	var VideoLayout = (function (my) {
	    var currentDominantSpeaker = null;
	    var lastNCount = -1;
	    var localLastNCount = -1;
	    var localLastNSet = [];
	    var lastNEndpointsCache = [];
	    var lastNPickupJid = null;
	    var largeVideoState = {
		updateInProgress: false,
		newSrc: ''
	    };

	    var defaultLocalDisplayName = "Me";

	    my.init = function()
	    {
	    	lastNCount = config.channelLastN;
	    	localLastNCount = config.chan	    
	    }
	    
	    my.connectionIndicators = {};

	    my.isInLastN = function(resource) {
		return lastNCount < 0 // lastN is disabled, return true
		    || (lastNCount > 0 && lastNEndpointsCache.length == 0) // lastNEndpoints cache not built yet, return true
		    || (lastNEndpointsCache && lastNEndpointsCache.indexOf(resource) !== -1);
	    };

	    my.changeLocalStream = function (stream) {
		connection.jingle.localAudio = stream;
		VideoLayout.changeLocalVideo(stream, true);
	    };

	    my.changeLocalAudio = function(stream) {
		connection.jingle.localAudio = stream;
		RTC.attachMediaStream($('#localAudio'), stream);
		document.getElementById('localAudio').autoplay = true;
		document.getElementById('localAudio').volume = 0;
		if (preMuted) {
		    setAudioMuted(true);
		    preMuted = false;
		}
	    };

	    my.changeLocalVideo = function(stream, flipX) {
		connection.jingle.localVideo = stream;

		var localVideo = document.createElement('video');
		localVideo.id = 'localVideo_' + RTC.getStreamID(stream);
		localVideo.autoplay = true;
		localVideo.volume = 0; // is it required if audio is separated ?
		localVideo.oncontextmenu = function () { return false; };

		var localVideoContainer = document.getElementById('localVideoWrapper');
		localVideoContainer.appendChild(localVideo);

		// Set default display name.
		setDisplayName('localVideoContainer');

		if(!VideoLayout.connectionIndicators["localVideoContainer"]) {
		    VideoLayout.connectionIndicators["localVideoContainer"]
			= new ConnectionIndicator($("#localVideoContainer")[0], null);
		}

		AudioLevels.updateAudioLevelCanvas();

		var localVideoSelector = $('#' + localVideo.id);
		// Add click handler to both video and video wrapper elements in case
		// there's no video.
		localVideoSelector.click(function (event) {
		    event.stopPropagation();
		    VideoLayout.handleVideoThumbClicked(
			RTC.getVideoSrc(localVideo),
			false,
			Strophe.getResourceFromJid(connection.emuc.myroomjid));
		});
		$('#localVideoContainer').click(function (event) {
		    event.stopPropagation();
		    VideoLayout.handleVideoThumbClicked(
			RTC.getVideoSrc(localVideo),
			false,
			Strophe.getResourceFromJid(connection.emuc.myroomjid));
		});

		// Add hover handler
		$('#localVideoContainer').hover(
		    function() {
			VideoLayout.showDisplayName('localVideoContainer', true);
		    },
		    function() {
			if (!VideoLayout.isLargeVideoVisible()
				|| RTC.getVideoSrc(localVideo) !== RTC.getVideoSrc($('#largeVideo')[0]))
			    VideoLayout.showDisplayName('localVideoContainer', false);
		    }
		);
		// Add stream ended handler
		stream.onended = function () {
		    localVideoContainer.removeChild(localVideo);
		    VideoLayout.updateRemovedVideo(RTC.getVideoSrc(localVideo));
		};
		// Flip video x axis if needed
		flipXLocalVideo = flipX;
		if (flipX) {
		    localVideoSelector.addClass("flipVideoX");
		}
		// Attach WebRTC stream
		var videoStream = simulcast.getLocalVideoStream();
		RTC.attachMediaStream(localVideoSelector, videoStream);

		localVideoSrc = RTC.getVideoSrc(localVideo);

		var myResourceJid = null;
		if(connection.emuc.myroomjid)
		{
		   myResourceJid = Strophe.getResourceFromJid(connection.emuc.myroomjid);
		}
		VideoLayout.updateLargeVideo(localVideoSrc, 0,
		    myResourceJid);

	    };

	    /**
	     * Checks if removed video is currently displayed and tries to display
	     * another one instead.
	     * @param removedVideoSrc src stream identifier of the video.
	     */
	    my.updateRemovedVideo = function(removedVideoSrc) {
		if (removedVideoSrc === RTC.getVideoSrc($('#largeVideo')[0])) {
		    // this is currently displayed as large
		    // pick the last visible video in the row
		    // if nobody else is left, this picks the local video
		    var pick
			= $('#remoteVideos>span[id!="mixedstream"]:visible:last>video')
			    .get(0);

		    if (!pick) {
			//console.info("Last visible video no longer exists");
			pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);

			if (!pick || !RTC.getVideoSrc(pick)) {
			    // Try local video
			    //console.info("Fallback to local video...");
			    pick = $('#remoteVideos>span>span>video').get(0);
			}
		    }

		    // mute if localvideo
		    if (pick) {
			var container = pick.parentNode;
			var jid = null;
			if(container)
			{
			    if(container.id == "localVideoWrapper")
			    {
				jid = Strophe.getResourceFromJid(connection.emuc.myroomjid);
			    }
			    else
			    {
				jid = VideoLayout.getPeerContainerResourceJid(container);
			    }
			}

			VideoLayout.updateLargeVideo(RTC.getVideoSrc(pick), pick.volume, jid);
		    } else {
			console.warn("Failed to elect large video");
		    }
		}
	    };

	    my.getLargeVideoState = function () {
		return largeVideoState;
	    };

	    /**
	     * Updates the large video with the given new video source.
	     */
	    my.updateLargeVideo = function(newSrc, vol, resourceJid) {
		//console.log('hover in', newSrc);

		if (RTC.getVideoSrc($('#largeVideo')[0]) !== newSrc) {

		    $('#activeSpeaker').css('visibility', 'hidden');
		    // Due to the simulcast the localVideoSrc may have changed when the
		    // fadeOut event triggers. In that case the getJidFromVideoSrc and
		    // isVideoSrcDesktop methods will not function correctly.
		    //
		    // Also, again due to the simulcast, the updateLargeVideo method can
		    // be called multiple times almost simultaneously. Therefore, we
		    // store the state here and update only once.

		    largeVideoState.newSrc = newSrc;
		    largeVideoState.isVisible = $('#largeVideo').is(':visible');
		    largeVideoState.isDesktop = isVideoSrcDesktop(resourceJid);
		    if(jid2Ssrc[largeVideoState.userResourceJid] ||
			(connection && connection.emuc.myroomjid &&
			    largeVideoState.userResourceJid ===
			    Strophe.getResourceFromJid(connection.emuc.myroomjid))) {
			largeVideoState.oldResourceJid = largeVideoState.userResourceJid;
		    } else {
			largeVideoState.oldResourceJid = null;
		    }
		    largeVideoState.userResourceJid = resourceJid;

		    // Screen stream is already rotated
		    largeVideoState.flipX = (newSrc === localVideoSrc) && flipXLocalVideo;

		    var userChanged = false;
		    if (largeVideoState.oldResourceJid !== largeVideoState.userResourceJid) {
			userChanged = true;
			// we want the notification to trigger even if userJid is undefined,
			// or null.
			$(document).trigger("selectedendpointchanged", [largeVideoState.userResourceJid]);
		    }

		    if (!largeVideoState.updateInProgress) {
			largeVideoState.updateInProgress = true;

			var doUpdate = function () {

			    Avatar.updateActiveSpeakerAvatarSrc(
				connection.emuc.findJidFromResource(
				    largeVideoState.userResourceJid));

			    if (!userChanged && largeVideoState.preload &&
				largeVideoState.preload !== null &&
				RTC.getVideoSrc($(largeVideoState.preload)[0]) === newSrc)
			    {

				//console.info('Switching to preloaded video');
				var attributes = $('#largeVideo').prop("attributes");

				// loop through largeVideo attributes and apply them on
				// preload.
				$.each(attributes, function () {
				    if (this.name !== 'id' && this.name !== 'src') {
					largeVideoState.preload.attr(this.name, this.value);
				    }
				});

				largeVideoState.preload.appendTo($('#largeVideoContainer'));
				$('#largeVideo').attr('id', 'previousLargeVideo');
				largeVideoState.preload.attr('id', 'largeVideo');
				$('#previousLargeVideo').remove();

				largeVideoState.preload.on('loadedmetadata', function (e) {
				    currentVideoWidth = this.videoWidth;
				    currentVideoHeight = this.videoHeight;
				    VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight);
				});
				largeVideoState.preload = null;
				largeVideoState.preload_ssrc = 0;
			    } else {
				RTC.setVideoSrc($('#largeVideo')[0], largeVideoState.newSrc);
			    }

			    var videoTransform = document.getElementById('largeVideo')
				.style.webkitTransform;

			    if (largeVideoState.flipX && videoTransform !== 'scaleX(-1)') {
				document.getElementById('largeVideo').style.webkitTransform
				    = "scaleX(-1)";
			    }
			    else if (!largeVideoState.flipX && videoTransform === 'scaleX(-1)') {
				document.getElementById('largeVideo').style.webkitTransform
				    = "none";
			    }

			    // Change the way we'll be measuring and positioning large video

			    getVideoSize = largeVideoState.isDesktop
				? getDesktopVideoSize
				: getCameraVideoSize;
			    getVideoPosition = largeVideoState.isDesktop
				? getDesktopVideoPosition
				: getCameraVideoPosition;


			    // Only if the large video is currently visible.
			    // Disable previous dominant speaker video.
			    if (largeVideoState.oldResourceJid) {
				VideoLayout.enableDominantSpeaker(
				    largeVideoState.oldResourceJid,
				    false);
			    }

			    // Enable new dominant speaker in the remote videos section.
			    if (largeVideoState.userResourceJid) {
				VideoLayout.enableDominantSpeaker(
				    largeVideoState.userResourceJid,
				    true);
			    }

			    if (userChanged && largeVideoState.isVisible) {
				// using "this" should be ok because we're called
				// from within the fadeOut event.
				$(this).fadeIn(300);
			    }

			    if(userChanged) {
				Avatar.showUserAvatar(
				    connection.emuc.findJidFromResource(
					largeVideoState.oldResourceJid));
			    }

			    largeVideoState.updateInProgress = false;
			};

			if (userChanged) {
			    $('#largeVideo').fadeOut(300, doUpdate);
			} else {
			    doUpdate();
			}
		    }
		} else {
		    Avatar.showUserAvatar(
			connection.emuc.findJidFromResource(
			    largeVideoState.userResourceJid));
		}

	    };

	    my.handleVideoThumbClicked = function(videoSrc,
						  noPinnedEndpointChangedEvent, 
						  resourceJid) {
		// Restore style for previously focused video
		var oldContainer = null;
		if(focusedVideoInfo) {
		    var focusResourceJid = focusedVideoInfo.resourceJid;
		    oldContainer = getParticipantContainer(focusResourceJid);
		}

		if (oldContainer) {
		    oldContainer.removeClass("videoContainerFocused");
		}

		// Unlock current focused.
		if (focusedVideoInfo && focusedVideoInfo.src === videoSrc)
		{
		    focusedVideoInfo = null;
		    var dominantSpeakerVideo = null;
		    // Enable the currently set dominant speaker.
		    if (currentDominantSpeaker) {
			dominantSpeakerVideo
			    = $('#participant_' + currentDominantSpeaker + '>video')
				.get(0);

			if (dominantSpeakerVideo) {
			    VideoLayout.updateLargeVideo(
				RTC.getVideoSrc(dominantSpeakerVideo),
				1,
				currentDominantSpeaker);
			}
		    }

		    if (!noPinnedEndpointChangedEvent) {
			$(document).trigger("pinnedendpointchanged");
		    }
		    return;
		}

		// Lock new video
		focusedVideoInfo = {
		    src: videoSrc,
		    resourceJid: resourceJid
		};

		// Update focused/pinned interface.
		if (resourceJid)
		{
		    var container = getParticipantContainer(resourceJid);
		    container.addClass("videoContainerFocused");

		    if (!noPinnedEndpointChangedEvent) {
			$(document).trigger("pinnedendpointchanged", [resourceJid]);
		    }
		}

		if ($('#largeVideo').attr('src') === videoSrc &&
		    VideoLayout.isLargeVideoOnTop()) {
		    return;
		}

		// Triggers a "video.selected" event. The "false" parameter indicates
		// this isn't a prezi.
		$(document).trigger("video.selected", [false]);

		VideoLayout.updateLargeVideo(videoSrc, 1, resourceJid);

		$('audio').each(function (idx, el) {
		    if (el.id.indexOf('mixedmslabel') !== -1) {
			el.volume = 0;
			el.volume = 1;
		    }
		});
	    };

	    /**
	     * Positions the large video.
	     *
	     * @param videoWidth the stream video width
	     * @param videoHeight the stream video height
	     */
	    my.positionLarge = function (videoWidth, videoHeight) {
		var videoSpaceWidth = $('#videospace').width();
		var videoSpaceHeight = window.innerHeight;

		var videoSize = getVideoSize(videoWidth,
					     videoHeight,
					     videoSpaceWidth,
					     videoSpaceHeight);

		var largeVideoWidth = videoSize[0];
		var largeVideoHeight = videoSize[1];

		var videoPosition = getVideoPosition(largeVideoWidth,
						     largeVideoHeight,
						     videoSpaceWidth,
						     videoSpaceHeight);

		var horizontalIndent = videoPosition[0];
		var verticalIndent = videoPosition[1];

		positionVideo($('#largeVideo'),
			      largeVideoWidth,
			      largeVideoHeight,
			      horizontalIndent, verticalIndent);
	    };

	    /**
	     * Shows/hides the large video.
	     */
	    my.setLargeVideoVisible = function(isVisible) {
		var resourceJid = largeVideoState.userResourceJid;

		if (isVisible) {
		    $('#largeVideo').css({visibility: 'visible'});
		    $('.watermark').css({visibility: 'visible'});
		    VideoLayout.enableDominantSpeaker(resourceJid, true);
		}
		else {
		    $('#largeVideo').css({visibility: 'hidden'});
		    $('#activeSpeaker').css('visibility', 'hidden');
		    $('.watermark').css({visibility: 'hidden'});
		    VideoLayout.enableDominantSpeaker(resourceJid, false);
		    if(focusedVideoInfo) {
			var focusResourceJid = focusedVideoInfo.resourceJid;
			var oldContainer = getParticipantContainer(focusResourceJid);

			if (oldContainer && oldContainer.length > 0) {
			    oldContainer.removeClass("videoContainerFocused");
			}
			focusedVideoInfo = null;
			if(focusResourceJid) {
			    Avatar.showUserAvatar(
				connection.emuc.findJidFromResource(focusResourceJid));
			}
		    }
		}
	    };

	    /**
	     * Indicates if the large video is currently visible.
	     *
	     * @return <tt>true</tt> if visible, <tt>false</tt> - otherwise
	     */
	    my.isLargeVideoVisible = function() {
		return $('#largeVideo').is(':visible');
	    };

	    my.isLargeVideoOnTop = function () {
		return true;
	    };

	    /**
	     * Checks if container for participant identified by given peerJid exists
	     * in the document and creates it eventually.
	     * 
	     * @param peerJid peer Jid to check.
	     * @param userId user email or id for setting the avatar
	     * 
	     * @return Returns <tt>true</tt> if the peer container exists,
	     * <tt>false</tt> - otherwise
	     */
	    my.ensurePeerContainerExists = function(peerJid, userId) {

		var resourceJid = Strophe.getResourceFromJid(peerJid);

		var videoSpanId = 'participant_' + resourceJid;

		if ($('#' + videoSpanId).length > 0) {
		    // If there's been a focus change, make sure we add focus related
		    // interface!!
		    if (Moderator.isModerator() && $('#remote_popupmenu_' + resourceJid).length <= 0) {
			addRemoteVideoMenu(peerJid,
			    document.getElementById(videoSpanId));
		    }
		}
		else {
		    var container =
			VideoLayout.addRemoteVideoContainer(peerJid, videoSpanId, userId);
		    Avatar.setUserAvatar(peerJid, userId);
		    // Set default display name.
		    setDisplayName(videoSpanId);

		    VideoLayout.connectionIndicators[videoSpanId] = new ConnectionIndicator(container, peerJid);

		    var nickfield = document.createElement('span');
		    nickfield.className = "nick";
		    nickfield.appendChild(document.createTextNode(resourceJid));
		    container.appendChild(nickfield);

		    // In case this is not currently in the last n we don't show it.
		    if (localLastNCount
			&& localLastNCount > 0
			&& $('#remoteVideos>span').length >= localLastNCount + 2) {
			showPeerContainer(resourceJid, 'hide');
		    }
		    else
			VideoLayout.resizeThumbnails();
		}
	    };

	    my.addRemoteVideoContainer = function(peerJid, spanId) {
		var container = document.createElement('span');
		container.id = spanId;
		container.className = 'videocontainer';
		var remotes = document.getElementById('remoteVideos');

		// If the peerJid is null then this video span couldn't be directly
		// associated with a participant (this could happen in the case of prezi).
		if (Moderator.isModerator() && peerJid !== null)
		    addRemoteVideoMenu(peerJid, container);

		remotes.appendChild(container);
		AudioLevels.updateAudioLevelCanvas(peerJid);

		return container;
	    };

	    /**
	     * Creates an audio or video stream element.
	     */
	    my.createStreamElement = function (sid, stream) {
		var isVideo = stream.getVideoTracks().length > 0;

		var element = isVideo
				? document.createElement('video')
				: document.createElement('audio');
		var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_')
			    + sid + '_' + RTC.getStreamID(stream);

		element.id = id;
		element.autoplay = true;
		element.oncontextmenu = function () { return false; };

		return element;
	    };

	    my.addRemoteStreamElement
		= function (container, sid, stream, peerJid, thessrc) {
		var newElementId = null;

		var isVideo = stream.getVideoTracks().length > 0;

		if (container) {
		    var streamElement = VideoLayout.createStreamElement(sid, stream);
		    newElementId = streamElement.id;

		    container.appendChild(streamElement);

		    var sel = $('#' + newElementId);
		    sel.hide();

		    // If the container is currently visible we attach the stream.
		    if (!isVideo
			|| (container.offsetParent !== null && isVideo)) {
			var videoStream = simulcast.getReceivingVideoStream(stream);
			RTC.attachMediaStream(sel, videoStream);

			if (isVideo)
			    waitForRemoteVideo(sel, thessrc, stream, peerJid);
		    }

		    stream.onended = function () {
			//console.log('stream ended', this);

			VideoLayout.removeRemoteStreamElement(
			    stream, isVideo, container);

			// NOTE(gp) it seems that under certain circumstances, the
			// onended event is not fired and thus the contact list is not
			// updated.
			//
			// The onended event of a stream should be fired when the SSRCs
			// corresponding to that stream are removed from the SDP; but
			// this doesn't seem to always be the case, resulting in ghost
			// contacts.
			//
			// In an attempt to fix the ghost contacts problem, I'm moving
			// the removeContact() method call in app.js, inside the
			// 'muc.left' event handler.
		    };

		    // Add click handler.
		    container.onclick = function (event) {
			/*
			 * FIXME It turns out that videoThumb may not exist (if there is
			 * no actual video).
			 */
			var videoThumb = $('#' + container.id + '>video').get(0);
			if (videoThumb) {
			    VideoLayout.handleVideoThumbClicked(
				RTC.getVideoSrc(videoThumb),
				false,
				Strophe.getResourceFromJid(peerJid));
			}

			event.stopPropagation();
			event.preventDefault();
			return false;
		    };

		    // Add hover handler
		    $(container).hover(
			function() {
			    VideoLayout.showDisplayName(container.id, true);
			},
			function() {
			    var videoSrc = null;
			    if ($('#' + container.id + '>video')
				    && $('#' + container.id + '>video').length > 0) {
				videoSrc = RTC.getVideoSrc($('#' + container.id + '>video').get(0));
			    }

			    // If the video has been "pinned" by the user we want to
			    // keep the display name on place.
			    if (!VideoLayout.isLargeVideoVisible()
				    || videoSrc !== RTC.getVideoSrc($('#largeVideo')[0]))
				VideoLayout.showDisplayName(container.id, false);
			}
		    );
		}

		return newElementId;
	    };

	    /**
	     * Removes the remote stream element corresponding to the given stream and
	     * parent container.
	     * 
	     * @param stream the stream
	     * @param isVideo <tt>true</tt> if given <tt>stream</tt> is a video one.
	     * @param container
	     */
	    my.removeRemoteStreamElement = function (stream, isVideo, container) {
		if (!container)
		    return;

		var select = null;
		var removedVideoSrc = null;
		if (isVideo) {
		    select = $('#' + container.id + '>video');
		    removedVideoSrc = RTC.getVideoSrc(select.get(0));
		}
		else
		    select = $('#' + container.id + '>audio');


		// Mark video as removed to cancel waiting loop(if video is removed
		// before has started)
		select.removed = true;
		select.remove();

		var audioCount = $('#' + container.id + '>audio').length;
		var videoCount = $('#' + container.id + '>video').length;

		if (!audioCount && !videoCount) {
		    //console.log("Remove whole user", container.id);
		    if(VideoLayout.connectionIndicators[container.id])
			VideoLayout.connectionIndicators[container.id].remove();
		    // Remove whole container
		    container.remove();

		    VideoLayout.resizeThumbnails();
		}

		if (removedVideoSrc)
		    VideoLayout.updateRemovedVideo(removedVideoSrc);
	    };

	    /**
	     * Show/hide peer container for the given resourceJid.
	     */
	    function showPeerContainer(resourceJid, state) {
		var peerContainer = $('#participant_' + resourceJid);

		if (!peerContainer)
		    return;

		var isHide = state === 'hide';
		var resizeThumbnails = false;

		if (!isHide) {
		    if (!peerContainer.is(':visible')) {
			resizeThumbnails = true;
			peerContainer.show();
		    }

		    if (state == 'show')
		    {
			// peerContainer.css('-webkit-filter', '');
			var jid = connection.emuc.findJidFromResource(resourceJid);
			Avatar.showUserAvatar(jid, false);
		    }
		    else // if (state == 'avatar')
		    {
			// peerContainer.css('-webkit-filter', 'grayscale(100%)');
			var jid = connection.emuc.findJidFromResource(resourceJid);
			Avatar.showUserAvatar(jid, true);
		    }
		}
		else if (peerContainer.is(':visible') && isHide)
		{
		    resizeThumbnails = true;
		    peerContainer.hide();
		    if(VideoLayout.connectionIndicators['participant_' + resourceJid])
			VideoLayout.connectionIndicators['participant_' + resourceJid].hide();
		}

		if (resizeThumbnails) {
		    VideoLayout.resizeThumbnails();
		}

		// We want to be able to pin a participant from the contact list, even
		// if he's not in the lastN set!

	    };

	    /**
	     * Sets the display name for the given video span id.
	     */
	    function setDisplayName(videoSpanId, displayName) {
		var nameSpan = $('#' + videoSpanId + '>span.displayname');
		var defaultLocalDisplayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME;

		// If we already have a display name for this video.
		if (nameSpan.length > 0) {
		    var nameSpanElement = nameSpan.get(0);

		    if (nameSpanElement.id === 'localDisplayName' &&
			$('#localDisplayName').text() !== displayName) {
			
			if (displayName && displayName.length > 0 && displayName.indexOf(" (me)") == -1)
			    $('#localDisplayName').text(displayName + ' (me)');
			else
			    $('#localDisplayName').text(defaultLocalDisplayName);
		    } else {
			if (displayName && displayName.length > 0)
			    $('#' + videoSpanId + '_name').text(displayName);
			else
			    $('#' + videoSpanId + '_name').text(interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME);
		    }
		} else {
		    var editButton = null;

		    nameSpan = document.createElement('span');
		    nameSpan.className = 'displayname';
		    $('#' + videoSpanId)[0].appendChild(nameSpan);

		    if (videoSpanId === 'localVideoContainer') {
			editButton = createEditDisplayNameButton();
			nameSpan.innerText = defaultLocalDisplayName;
		    }
		    else {
			nameSpan.innerText = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
		    }

		    if (displayName && displayName.length > 0) {
			nameSpan.innerText = displayName;
		    }

		    if (!editButton) {
			nameSpan.id = videoSpanId + '_name';
		    } else {
			nameSpan.id = 'localDisplayName';
			$('#' + videoSpanId)[0].appendChild(editButton);

			var editableText = document.createElement('input');
			editableText.className = 'displayname';
			editableText.type = 'text';
			editableText.id = 'editDisplayName';

			if (displayName && displayName.length) {
			    editableText.value
				= displayName.substring(0, displayName.indexOf(' (me)'));
			}

			editableText.setAttribute('style', 'display:none;');
			editableText.setAttribute('placeholder', 'ex. Jane Pink');
			$('#' + videoSpanId)[0].appendChild(editableText);

			$('#localVideoContainer .displayname')
			    .bind("click", function (e) {

			    e.preventDefault();
			    e.stopPropagation();
			    $('#localDisplayName').hide();
			    $('#editDisplayName').show();
			    $('#editDisplayName').focus();
			    $('#editDisplayName').select();

			    $('#editDisplayName').one("focusout", function (e) {
				VideoLayout.inputDisplayNameHandler(this.value);
			    });

			    $('#editDisplayName').on('keydown', function (e) {
				if (e.keyCode === 13) {
				    e.preventDefault();
				    VideoLayout.inputDisplayNameHandler(this.value);
				}
			    });
			});
		    }
		}
	    }

	    my.inputDisplayNameHandler = function (name) {
		if (nickname !== name) {
		    nickname = name;
		    window.localStorage.displayname = nickname;
		    connection.emuc.addDisplayNameToPresence(nickname);
		    connection.emuc.sendPresence();
		}

		if (!$('#localDisplayName').is(":visible")) {
		    var tempDisplayName = nickname;
		    if (tempDisplayName.indexOf(" (me)") == -1) tempDisplayName = tempDisplayName + " (me)";
		    
		    if (nickname)
			$('#localDisplayName').text(tempDisplayName);
		    else
			$('#localDisplayName').text(interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
			
		    $('#localDisplayName').show();
		}

		$('#editDisplayName').hide();
	    };

	    /**
	     * Shows/hides the display name on the remote video.
	     * @param videoSpanId the identifier of the video span element
	     * @param isShow indicates if the display name should be shown or hidden
	     */
	    my.showDisplayName = function(videoSpanId, isShow) {
		var nameSpan = $('#' + videoSpanId + '>span.displayname').get(0);
		if (isShow) {
		    if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length) 
			nameSpan.setAttribute("style", "display:inline-block;");
		}
		else {
		    if (nameSpan)
			nameSpan.setAttribute("style", "display:none;");
		}
	    };

	    /**
	     * Shows the presence status message for the given video.
	     */
	    my.setPresenceStatus = function (videoSpanId, statusMsg) {

		if (!$('#' + videoSpanId).length) {
		    // No container
		    return;
		}

		var statusSpan = $('#' + videoSpanId + '>span.status');
		if (!statusSpan.length) {
		    //Add status span
		    statusSpan = document.createElement('span');
		    statusSpan.className = 'status';
		    statusSpan.id = videoSpanId + '_status';
		    $('#' + videoSpanId)[0].appendChild(statusSpan);

		    statusSpan = $('#' + videoSpanId + '>span.status');
		}

		// Display status
		if (statusMsg && statusMsg.length) {
		    $('#' + videoSpanId + '_status').text(statusMsg);
		    statusSpan.get(0).setAttribute("style", "display:inline-block;");
		}
		else {
		    // Hide
		    statusSpan.get(0).setAttribute("style", "display:none;");
		}
	    };

	    /**
	     * Shows a visual indicator for the moderator of the conference.
	     */
	    my.showModeratorIndicator = function () {
		if (Moderator.isModerator()) {
		    var indicatorSpan = $('#localVideoContainer .focusindicator');

		    if (indicatorSpan.children().length === 0)
		    {
			createModeratorIndicatorElement(indicatorSpan[0]);
		    }
		} else {
		    Object.keys(connection.emuc.members).forEach(function (jid) {
			var member = connection.emuc.members[jid];
			if (member.role === 'moderator') {
			    var moderatorId
				= 'participant_' + Strophe.getResourceFromJid(jid);

			    var moderatorContainer
				= document.getElementById(moderatorId);

			    if (Strophe.getResourceFromJid(jid) === 'focus') {
				// Skip server side focus
				return;
			    }
			    if (!moderatorContainer) {
				console.error("No moderator container for " + jid);
				return;
			    }
			    var indicatorSpan
				= $('#' + moderatorId + ' .focusindicator');

			    if (!indicatorSpan || indicatorSpan.length === 0) {
				indicatorSpan = document.createElement('span');
				indicatorSpan.className = 'focusindicator';

				moderatorContainer.appendChild(indicatorSpan);

				createModeratorIndicatorElement(indicatorSpan);
			    }
			}
		    });
		}
	    };

	    /**
	     * Shows video muted indicator over small videos.
	     */
	    my.showVideoIndicator = function(videoSpanId, isMuted) {
		var videoMutedSpan = $('#' + videoSpanId + '>span.videoMuted');

		if (isMuted === 'false') {
		    if (videoMutedSpan.length > 0) {
			videoMutedSpan.remove();
		    }
		}
		else {
		    if(videoMutedSpan.length == 0) {
			videoMutedSpan = document.createElement('span');
			videoMutedSpan.className = 'videoMuted';

			$('#' + videoSpanId)[0].appendChild(videoMutedSpan);

			var mutedIndicator = document.createElement('i');
			mutedIndicator.className = 'icon-camera-disabled';
			Util.setTooltip(mutedIndicator,
			    "Participant has<br/>stopped the camera.",
			    "top");
			videoMutedSpan.appendChild(mutedIndicator);
		    }

		    VideoLayout.updateMutePosition(videoSpanId);

		}
	    };

	    my.updateMutePosition = function (videoSpanId) {
		var audioMutedSpan = $('#' + videoSpanId + '>span.audioMuted');
		var connectionIndicator = $('#' + videoSpanId + '>div.connectionindicator');
		var videoMutedSpan = $('#' + videoSpanId + '>span.videoMuted');
		if(connectionIndicator.length > 0
		    && connectionIndicator[0].style.display != "none") {
		    audioMutedSpan.css({right: "23px"});
		    videoMutedSpan.css({right: ((audioMutedSpan.length > 0? 23 : 0) + 30) + "px"});
		}
		else
		{
		    audioMutedSpan.css({right: "0px"});
		    videoMutedSpan.css({right: (audioMutedSpan.length > 0? 30 : 0) + "px"});
		}
	    }
	    /**
	     * Shows audio muted indicator over small videos.
	     * @param {string} isMuted
	     */
	    my.showAudioIndicator = function(videoSpanId, isMuted) {
		var audioMutedSpan = $('#' + videoSpanId + '>span.audioMuted');

		if (isMuted === 'false') {
		    if (audioMutedSpan.length > 0) {
			audioMutedSpan.popover('hide');
			audioMutedSpan.remove();
		    }
		}
		else {
		    if(audioMutedSpan.length == 0 ) {
			audioMutedSpan = document.createElement('span');
			audioMutedSpan.className = 'audioMuted';
			Util.setTooltip(audioMutedSpan,
			    "Participant is muted",
			    "top");

			$('#' + videoSpanId)[0].appendChild(audioMutedSpan);
			var mutedIndicator = document.createElement('i');
			mutedIndicator.className = 'icon-mic-disabled';
			audioMutedSpan.appendChild(mutedIndicator);

		    }
		    VideoLayout.updateMutePosition(videoSpanId);
		}
	    };

	    /*
	     * Shows or hides the audio muted indicator over the local thumbnail video.
	     * @param {boolean} isMuted
	     */
	    my.showLocalAudioIndicator = function(isMuted) {
		VideoLayout.showAudioIndicator('localVideoContainer', isMuted.toString());
	    };

	    /**
	     * Resizes the large video container.
	     */
	    my.resizeLargeVideoContainer = function () {

		var availableHeight = window.innerHeight;
		var availableWidth = Util.getAvailableVideoWidth();

		if (availableWidth < 0 || availableHeight < 0) return;

		$('#videospace').width(availableWidth);
		$('#videospace').height(availableHeight);
		$('#largeVideoContainer').width(availableWidth);
		$('#largeVideoContainer').height(availableHeight);

		var avatarSize = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE;
		var top = availableHeight / 2 - avatarSize / 4 * 3;
		$('#activeSpeaker').css('top', top);

		VideoLayout.resizeThumbnails();
	    };

	    /**
	     * Resizes thumbnails.
	     */
	    my.resizeThumbnails = function() {
		var videoSpaceWidth = $('#remoteVideos').width();

		var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
		var width = thumbnailSize[0];
		var height = thumbnailSize[1];

		// size videos so that while keeping AR and max height, we have a
		// nice fit
		$('#remoteVideos').height(height);
		$('#remoteVideos>span').width(width);
		$('#remoteVideos>span').height(height);

		$('.userAvatar').css('left', (width - height) / 2);

		$(document).trigger("remotevideo.resized", [width, height]);
	    };

	    /**
	     * Enables the dominant speaker UI.
	     *
	     * @param resourceJid the jid indicating the video element to
	     * activate/deactivate
	     * @param isEnable indicates if the dominant speaker should be enabled or
	     * disabled
	     */
	    my.enableDominantSpeaker = function(resourceJid, isEnable) {

		var videoSpanId = null;
		var videoContainerId = null;
		if (resourceJid
			=== Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
		    videoSpanId = 'localVideoWrapper';
		    videoContainerId = 'localVideoContainer';
		}
		else {
		    videoSpanId = 'participant_' + resourceJid;
		    videoContainerId = videoSpanId;
		}

		var displayName = resourceJid;
		var nameSpan = $('#' + videoContainerId + '>span.displayname');
		if (nameSpan.length > 0)
		    displayName = nameSpan.text();

		//console.log("UI enable dominant speaker",   displayName,   resourceJid,    isEnable);

		videoSpan = document.getElementById(videoContainerId);

		if (!videoSpan) {
		    return;
		}

		var video = $('#' + videoSpanId + '>video');

		if (video && video.length > 0) {
		    if (isEnable) {
			var isLargeVideoVisible = VideoLayout.isLargeVideoOnTop();
			VideoLayout.showDisplayName(videoContainerId, isLargeVideoVisible);

			if (!videoSpan.classList.contains("dominantspeaker"))
			    videoSpan.classList.add("dominantspeaker");
		    }
		    else {
			VideoLayout.showDisplayName(videoContainerId, false);

			if (videoSpan.classList.contains("dominantspeaker"))
			    videoSpan.classList.remove("dominantspeaker");
		    }

		    Avatar.showUserAvatar(
			connection.emuc.findJidFromResource(resourceJid));
		}
	    };

	    /**
	     * Gets the selector of video thumbnail container for the user identified by
	     * given <tt>userJid</tt>
	     * @param resourceJid user's Jid for whom we want to get the video container.
	     */
	    function getParticipantContainer(resourceJid)
	    {
		if (!resourceJid)
		    return null;

		if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid))
		    return $("#localVideoContainer");
		else
		    return $("#participant_" + resourceJid);
	    }

	    /**
	     * Sets the size and position of the given video element.
	     *
	     * @param video the video element to position
	     * @param width the desired video width
	     * @param height the desired video height
	     * @param horizontalIndent the left and right indent
	     * @param verticalIndent the top and bottom indent
	     */
	    function positionVideo(video,
				   width,
				   height,
				   horizontalIndent,
				   verticalIndent) {
		video.width(width);
		video.height(height);
		video.css({  top: verticalIndent + 'px',
			     bottom: verticalIndent + 'px',
			     left: horizontalIndent + 'px',
			     right: horizontalIndent + 'px'});
	    }

	    /**
	     * Calculates the thumbnail size.
	     *
	     * @param videoSpaceWidth the width of the video space
	     */
	    my.calculateThumbnailSize = function (videoSpaceWidth) {
		// Calculate the available height, which is the inner window height minus
	       // 39px for the header minus 2px for the delimiter lines on the top and
	       // bottom of the large video, minus the 36px space inside the remoteVideos
	       // container used for highlighting shadow.
	       var availableHeight = 100;

		var numvids = $('#remoteVideos>span:visible').length;
		if (localLastNCount && localLastNCount > 0) {
		    numvids = Math.min(localLastNCount + 1, numvids);
		}

	       // Remove the 3px borders arround videos and border around the remote
	       // videos area and the 4 pixels between the local video and the others
	       //TODO: Find out where the 4 pixels come from and remove them
	       var availableWinWidth = videoSpaceWidth - 2 * 3 * numvids - 70 - 4;

	       var availableWidth = availableWinWidth / numvids;
	       var aspectRatio = 16.0 / 9.0;
	       var maxHeight = Math.min(160, availableHeight);
	       availableHeight = Math.min(maxHeight, availableWidth / aspectRatio);
	       if (availableHeight < availableWidth / aspectRatio) {
		   availableWidth = Math.floor(availableHeight * aspectRatio);
	       }

	       return [availableWidth, availableHeight];
	   };

	   /**
	    * Returns an array of the video dimensions, so that it keeps it's aspect
	    * ratio and fits available area with it's larger dimension. This method
	    * ensures that whole video will be visible and can leave empty areas.
	    *
	    * @return an array with 2 elements, the video width and the video height
	    */
	   function getDesktopVideoSize(videoWidth,
					videoHeight,
					videoSpaceWidth,
					videoSpaceHeight) {
	       if (!videoWidth)
		   videoWidth = currentVideoWidth;
	       if (!videoHeight)
		   videoHeight = currentVideoHeight;

	       var aspectRatio = videoWidth / videoHeight;

	       var availableWidth = Math.max(videoWidth, videoSpaceWidth);
	       var availableHeight = Math.max(videoHeight, videoSpaceHeight);

	       videoSpaceHeight -= $('#remoteVideos').outerHeight();

	       if (availableWidth / aspectRatio >= videoSpaceHeight)
	       {
		   availableHeight = videoSpaceHeight;
		   availableWidth = availableHeight * aspectRatio;
	       }

	       if (availableHeight * aspectRatio >= videoSpaceWidth)
	       {
		   availableWidth = videoSpaceWidth;
		   availableHeight = availableWidth / aspectRatio;
	       }

	       return [availableWidth, availableHeight];
	   }

	   /**
	    * Creates the edit display name button.
	    * 
	    * @returns the edit button
	    */
	    function createEditDisplayNameButton() {
		var editButton = document.createElement('a');
		editButton.className = 'displayname';
		Util.setTooltip(editButton,
				'Click to edit your<br/>display name',
				"top");
		editButton.innerHTML = '<i class="fa fa-pencil"></i>';

		return editButton;
	    }

	    /**
	     * Creates the element indicating the moderator(owner) of the conference.
	     *
	     * @param parentElement the parent element where the owner indicator will
	     * be added
	     */
	    function createModeratorIndicatorElement(parentElement) {
		var moderatorIndicator = document.createElement('i');
		moderatorIndicator.className = 'fa fa-star';
		parentElement.appendChild(moderatorIndicator);

		Util.setTooltip(parentElement,
			"The owner of<br/>this conference",
			"top");
	    }

	    /**
	     * Updates the remote video menu.
	     *
	     * @param jid the jid indicating the video for which we're adding a menu.
	     * @param isMuted indicates the current mute state
	     */
	    my.updateRemoteVideoMenu = function(jid, isMuted) {
		var muteMenuItem
		    = $('#remote_popupmenu_'
			    + Strophe.getResourceFromJid(jid)
			    + '>li>a.mutelink');

		var mutedIndicator = "<i class='icon-mic-disabled'></i>";

		if (muteMenuItem.length) {
		    var muteLink = muteMenuItem.get(0);

		    if (isMuted === 'true') {
			muteLink.innerHTML = mutedIndicator + ' Muted';
			muteLink.className = 'mutelink disabled';
		    }
		    else {
			muteLink.innerHTML = mutedIndicator + ' Mute';
			muteLink.className = 'mutelink';
		    }
		}
	    };

	    /**
	     * Returns the current dominant speaker resource jid.
	     */
	    my.getDominantSpeakerResourceJid = function () {
		return currentDominantSpeaker;
	    };

	    /**
	     * Returns the corresponding resource jid to the given peer container
	     * DOM element.
	     *
	     * @return the corresponding resource jid to the given peer container
	     * DOM element
	     */
	    my.getPeerContainerResourceJid = function (containerElement) {
		var i = containerElement.id.indexOf('participant_');

		if (i >= 0)
		    return containerElement.id.substring(i + 12); 
	    };

	    /**
	     * Adds the remote video menu element for the given <tt>jid</tt> in the
	     * given <tt>parentElement</tt>.
	     *
	     * @param jid the jid indicating the video for which we're adding a menu.
	     * @param parentElement the parent element where this menu will be added
	     */
	    function addRemoteVideoMenu(jid, parentElement) {
		var spanElement = document.createElement('span');
		spanElement.className = 'remotevideomenu';

		parentElement.appendChild(spanElement);

		var menuElement = document.createElement('i');
		menuElement.className = 'fa fa-angle-down';
		menuElement.title = 'Remote user controls';
		spanElement.appendChild(menuElement);

	//        <ul class="popupmenu">
	//        <li><a href="#">Mute</a></li>
	//        <li><a href="#">Eject</a></li>
	//        </ul>

		var popupmenuElement = document.createElement('ul');
		popupmenuElement.className = 'popupmenu';
		popupmenuElement.id
		    = 'remote_popupmenu_' + Strophe.getResourceFromJid(jid);
		spanElement.appendChild(popupmenuElement);

		var muteMenuItem = document.createElement('li');
		var muteLinkItem = document.createElement('a');

		var mutedIndicator = "<i class='icon-mic-disabled'></i>";

		if (!mutedAudios[jid]) {
		    muteLinkItem.innerHTML = mutedIndicator + 'Mute';
		    muteLinkItem.className = 'mutelink';
		}
		else {
		    muteLinkItem.innerHTML = mutedIndicator + ' Muted';
		    muteLinkItem.className = 'mutelink disabled';
		}

		muteLinkItem.onclick = function(){
		    if ($(this).attr('disabled') != undefined) {
			event.preventDefault();
		    }
		    var isMute = mutedAudios[jid] == true;
		    connection.moderate.setMute(jid, !isMute);
		    popupmenuElement.setAttribute('style', 'display:none;');

		    if (isMute) {
			this.innerHTML = mutedIndicator + ' Muted';
			this.className = 'mutelink disabled';
		    }
		    else {
			this.innerHTML = mutedIndicator + ' Mute';
			this.className = 'mutelink';
		    }
		};

		muteMenuItem.appendChild(muteLinkItem);
		popupmenuElement.appendChild(muteMenuItem);

		var ejectIndicator = "<i class='fa fa-eject'></i>";

		var ejectMenuItem = document.createElement('li');
		var ejectLinkItem = document.createElement('a');
		ejectLinkItem.innerHTML = ejectIndicator + ' Kick out';
		ejectLinkItem.onclick = function(){
		    connection.moderate.eject(jid);
		    popupmenuElement.setAttribute('style', 'display:none;');
		};

		ejectMenuItem.appendChild(ejectLinkItem);
		popupmenuElement.appendChild(ejectMenuItem);

		var paddingSpan = document.createElement('span');
		paddingSpan.className = 'popupmenuPadding';
		popupmenuElement.appendChild(paddingSpan);
	    }

	    /**
	     * On audio muted event.
	     */
	    $(document).bind('audiomuted.muc', function (event, jid, isMuted) {
		/*
		 // FIXME: but focus can not mute in this case ? - check
		if (jid === connection.emuc.myroomjid) {

		    // The local mute indicator is controlled locally
		    return;
		}*/
		var videoSpanId = null;
		if (jid === connection.emuc.myroomjid) {
		    videoSpanId = 'localVideoContainer';
		} else {
		    VideoLayout.ensurePeerContainerExists(jid);
		    videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
		}

		mutedAudios[jid] = isMuted;

		if (Moderator.isModerator()) {
		    VideoLayout.updateRemoteVideoMenu(jid, isMuted);
		}

		if (videoSpanId)
		    VideoLayout.showAudioIndicator(videoSpanId, isMuted);
	    });

	    /**
	     * On video muted event.
	     */
	    $(document).bind('videomuted.muc', function (event, jid, isMuted) {
		var videoSpanId = null;
		if (jid === connection.emuc.myroomjid) {
		    videoSpanId = 'localVideoContainer';
		} else {
		    VideoLayout.ensurePeerContainerExists(jid);
		    videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
		}

		if (videoSpanId)
		    VideoLayout.showVideoIndicator(videoSpanId, isMuted);
	    });

	    /**
	     * Display name changed.
	     */
	    $(document).bind('displaynamechanged', function (event, jid, displayName, status) {
		var name = null;
		if (jid === 'localVideoContainer'
		    || jid === connection.emuc.myroomjid) {
		    name = nickname;
		    setDisplayName('localVideoContainer',
				   displayName);
		} else {
		    VideoLayout.ensurePeerContainerExists(jid);
		    name = $('#participant_' + Strophe.getResourceFromJid(jid) + "_name").text();
		    setDisplayName(
			'participant_' + Strophe.getResourceFromJid(jid),
			displayName,
			status);
		}
	    });

	    /**
	     * On dominant speaker changed event.
	     */
	    $(document).bind('dominantspeakerchanged', function (event, resourceJid) {
		// We ignore local user events.
		if (resourceJid
			=== Strophe.getResourceFromJid(connection.emuc.myroomjid))
		    return;

		// Update the current dominant speaker.
		if (resourceJid !== currentDominantSpeaker) {
		    var oldSpeakerVideoSpanId = "participant_" + currentDominantSpeaker,
			newSpeakerVideoSpanId = "participant_" + resourceJid;
		    if($("#" + oldSpeakerVideoSpanId + ">span.displayname").text() ===
			interfaceConfig.DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME) {
			setDisplayName(oldSpeakerVideoSpanId, null);
		    }
		    if($("#" + newSpeakerVideoSpanId + ">span.displayname").text() ===
			interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME) {
			setDisplayName(newSpeakerVideoSpanId,
			    interfaceConfig.DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME);
		    }
		    currentDominantSpeaker = resourceJid;
		} else {
		    return;
		}

		// Obtain container for new dominant speaker.
		var container  = document.getElementById(
			'participant_' + resourceJid);

		// Local video will not have container found, but that's ok
		// since we don't want to switch to local video.
		if (container && !focusedVideoInfo)
		{
		    var video = container.getElementsByTagName("video");

		    // Update the large video if the video source is already available,
		    // otherwise wait for the "videoactive.jingle" event.
		    if (video.length && video[0].currentTime > 0)
			VideoLayout.updateLargeVideo(RTC.getVideoSrc(video[0]), resourceJid);
		}
	    });

	    /**
	     * On last N change event.
	     *
	     * @param event the event that notified us
	     * @param lastNEndpoints the list of last N endpoints
	     * @param endpointsEnteringLastN the list currently entering last N
	     * endpoints
	     */
	    $(document).bind('lastnchanged', function ( event,
							lastNEndpoints,
							endpointsEnteringLastN,
							stream) {
		if (lastNCount !== lastNEndpoints.length)
		    lastNCount = lastNEndpoints.length;

		lastNEndpointsCache = lastNEndpoints;

		// Say A, B, C, D, E, and F are in a conference and LastN = 3.
		//
		// If LastN drops to, say, 2, because of adaptivity, then E should see
		// thumbnails for A, B and C. A and B are in E's server side LastN set,
		// so E sees them. C is only in E's local LastN set.
		//
		// If F starts talking and LastN = 3, then E should see thumbnails for
		// F, A, B. B gets "ejected" from E's server side LastN set, but it
		// enters E's local LastN ejecting C.

		// Increase the local LastN set size, if necessary.
		if (lastNCount > localLastNCount) {
		    localLastNCount = lastNCount;
		}

		// Update the local LastN set preserving the order in which the
		// endpoints appeared in the LastN/local LastN set.

		var nextLocalLastNSet = lastNEndpoints.slice(0);
		for (var i = 0; i < localLastNSet.length; i++) {
		    if (nextLocalLastNSet.length >= localLastNCount) {
			break;
		    }

		    var resourceJid = localLastNSet[i];
		    if (nextLocalLastNSet.indexOf(resourceJid) === -1) {
			nextLocalLastNSet.push(resourceJid);
		    }
		}

		localLastNSet = nextLocalLastNSet;

		var updateLargeVideo = false;

		// Handle LastN/local LastN changes.
		$('#remoteVideos>span').each(function( index, element ) {
		    var resourceJid = VideoLayout.getPeerContainerResourceJid(element);

		    var isReceived = true;
		    if (resourceJid
			&& lastNEndpoints.indexOf(resourceJid) < 0
			&& localLastNSet.indexOf(resourceJid) < 0) {
			//console.log("Remove from last N", resourceJid);
			showPeerContainer(resourceJid, 'hide');
			isReceived = false;
		    } else if (resourceJid
			&& $('#participant_' + resourceJid).is(':visible')
			&& lastNEndpoints.indexOf(resourceJid) < 0
			&& localLastNSet.indexOf(resourceJid) >= 0) {
			showPeerContainer(resourceJid, 'avatar');
			isReceived = false;
		    }

		    if (!isReceived) {
			// resourceJid has dropped out of the server side lastN set, so
			// it is no longer being received. If resourceJid was being
			// displayed in the large video we have to switch to another
			// user.
			var largeVideoResource = largeVideoState.userResourceJid;
			if (!updateLargeVideo && resourceJid === largeVideoResource) {
			    updateLargeVideo = true;
			}
		    }
		});

		if (!endpointsEnteringLastN || endpointsEnteringLastN.length < 0)
		    endpointsEnteringLastN = lastNEndpoints;

		if (endpointsEnteringLastN && endpointsEnteringLastN.length > 0) {
		    endpointsEnteringLastN.forEach(function (resourceJid) {

			var isVisible = $('#participant_' + resourceJid).is(':visible');
			showPeerContainer(resourceJid, 'show');
			if (!isVisible) {
			    //console.log("Add to last N", resourceJid);

			    var jid = connection.emuc.findJidFromResource(resourceJid);
			    var mediaStream = mediaStreams[jid][MediaStream.VIDEO_TYPE];
			    var sel = $('#participant_' + resourceJid + '>video');

			    var videoStream = simulcast.getReceivingVideoStream(
				mediaStream.stream);
			    RTC.attachMediaStream(sel, videoStream);
			    if (lastNPickupJid == mediaStream.peerjid) {
				// Clean up the lastN pickup jid.
				lastNPickupJid = null;

				// Don't fire the events again, they've already
				// been fired in the contact list click handler.
				VideoLayout.handleVideoThumbClicked(
				    $(sel).attr('src'),
				    false,
				    Strophe.getResourceFromJid(mediaStream.peerjid));

				updateLargeVideo = false;
			    }
			    waitForRemoteVideo(sel, mediaStream.ssrc, mediaStream.stream, resourceJid);
			}
		    })
		}

		// The endpoint that was being shown in the large video has dropped out
		// of the lastN set and there was no lastN pickup jid. We need to update
		// the large video now.

		if (updateLargeVideo) {

		    var resource, container, src;
		    var myResource
			= Strophe.getResourceFromJid(connection.emuc.myroomjid);

		    // Find out which endpoint to show in the large video.
		    for (var i = 0; i < lastNEndpoints.length; i++) {
			resource = lastNEndpoints[i];
			if (!resource || resource === myResource)
			    continue;

			container = $("#participant_" + resource);
			if (container.length == 0)
			    continue;

			src = $('video', container).attr('src');
			if (!src)
			    continue;

			// videoSrcToSsrc needs to be update for this call to succeed.
			VideoLayout.updateLargeVideo(src);
			break;

		    }
		}
	    });

	    $(document).bind('videoactive.jingle', function (event, videoelem) {
		if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
		    // ignore mixedmslabela0 and v0

		    videoelem.show();
		    VideoLayout.resizeThumbnails();

		    var videoParent = videoelem.parent();
		    var parentResourceJid = null;
		    if (videoParent)
			parentResourceJid
			    = VideoLayout.getPeerContainerResourceJid(videoParent[0]);

		    // Update the large video to the last added video only if there's no
		    // current dominant, focused speaker or prezi playing or update it to
		    // the current dominant speaker.

		    if ((!focusedVideoInfo && !VideoLayout.getDominantSpeakerResourceJid()) || (parentResourceJid &&  VideoLayout.getDominantSpeakerResourceJid() === parentResourceJid)) 
		    {
			VideoLayout.updateLargeVideo(
			    RTC.getVideoSrc(videoelem[0]),
			    1,
			    parentResourceJid);
		    }

		    VideoLayout.showModeratorIndicator();
		}
	    });

	    $(document).bind('simulcastlayerschanging', function (event, endpointSimulcastLayers) {
		endpointSimulcastLayers.forEach(function (esl) {

		    var resource = esl.endpoint;

		    // if lastN is enabled *and* the endpoint is *not* in the lastN set,
		    // then ignore the event (= do not preload anything).
		    //
		    // The bridge could probably stop sending this message if it's for
		    // an endpoint that's not in lastN.

		    if (lastNCount != -1
			&& (lastNCount < 1 || lastNEndpointsCache.indexOf(resource) === -1)) {
			return;
		    }

		    var primarySSRC = esl.simulcastLayer.primarySSRC;

		    // Get session and stream from primary ssrc.
		    var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC);
		    var session = res.session;
		    var electedStream = res.stream;

		    if (session && electedStream) {
			var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);

			//console.info([esl, primarySSRC, msid, session, electedStream]);

			var msidParts = msid.split(' ');

			var preload = (Strophe.getResourceFromJid(ssrc2jid[primarySSRC]) == largeVideoState.userResourceJid);

			if (preload) {
			    if (largeVideoState.preload)
			    {
				$(largeVideoState.preload).remove();
			    }
			    //console.info('Preloading remote video');
			    largeVideoState.preload = $('<video autoplay></video>');
			    // ssrcs are unique in an rtp session
			    largeVideoState.preload_ssrc = primarySSRC;

			    RTC.attachMediaStream(largeVideoState.preload, electedStream)
			}

		    } else {
			console.error('Could not find a stream or a session.', session, electedStream);
		    }
		});
	    });

	    /**
	     * On simulcast layers changed event.
	     */
	    $(document).bind('simulcastlayerschanged', function (event, endpointSimulcastLayers) {
		endpointSimulcastLayers.forEach(function (esl) {

		    var resource = esl.endpoint;

		    // if lastN is enabled *and* the endpoint is *not* in the lastN set,
		    // then ignore the event (= do not change large video/thumbnail
		    // SRCs).
		    //
		    // Note that even if we ignore the "changed" event in this event
		    // handler, the bridge must continue sending these events because
		    // the simulcast code in simulcast.js uses it to know what's going
		    // to be streamed by the bridge when/if the endpoint gets back into
		    // the lastN set.

		    if (lastNCount != -1
			&& (lastNCount < 1 || lastNEndpointsCache.indexOf(resource) === -1)) {
			return;
		    }

		    var primarySSRC = esl.simulcastLayer.primarySSRC;

		    // Get session and stream from primary ssrc.
		    var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC);
		    var session = res.session;
		    var electedStream = res.stream;

		    if (session && electedStream) {
			var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);

			//console.info('Switching simulcast substream.');
			//console.info([esl, primarySSRC, msid, session, electedStream]);

			var msidParts = msid.split(' ');
			var selRemoteVideo = $(['#', 'remoteVideo_', session.sid, '_', msidParts[0]].join(''));

			var updateLargeVideo = (Strophe.getResourceFromJid(ssrc2jid[primarySSRC])
			    == largeVideoState.userResourceJid);
			var updateFocusedVideoSrc = (focusedVideoInfo && focusedVideoInfo.src && focusedVideoInfo.src != '' &&
			    (RTC.getVideoSrc(selRemoteVideo[0]) == focusedVideoInfo.src));

			var electedStreamUrl;
			if (largeVideoState.preload_ssrc == primarySSRC)
			{
			    RTC.setVideoSrc(selRemoteVideo[0], RTC.getVideoSrc(largeVideoState.preload[0]));
			}
			else
			{
			    if (largeVideoState.preload
				&& largeVideoState.preload != null) {
				$(largeVideoState.preload).remove();
			    }

			    largeVideoState.preload_ssrc = 0;

			    RTC.attachMediaStream(selRemoteVideo, electedStream);
			}

			var jid = ssrc2jid[primarySSRC];
			jid2Ssrc[jid] = primarySSRC;

			if (updateLargeVideo) {
			    VideoLayout.updateLargeVideo(RTC.getVideoSrc(selRemoteVideo[0]), null,
				Strophe.getResourceFromJid(jid));
			}

			if (updateFocusedVideoSrc) {
			    focusedVideoInfo.src = RTC.getVideoSrc(selRemoteVideo[0]);
			}

			var videoId;
			if(resource == Strophe.getResourceFromJid(connection.emuc.myroomjid))
			{
			    videoId = "localVideoContainer";
			}
			else
			{
			    videoId = "participant_" + resource;
			}
			var connectionIndicator = VideoLayout.connectionIndicators[videoId];
			if(connectionIndicator)
			    connectionIndicator.updatePopoverData();

		    } else {
			console.error('Could not find a stream or a session.', session, electedStream);
		    }
		});
	    });

	    /**
	     * Constructs new connection indicator.
	     * @param videoContainer the video container associated with the indicator.
	     * @constructor
	     */
	    function ConnectionIndicator(videoContainer, jid)
	    {
		this.videoContainer = videoContainer;
		this.bandwidth = null;
		this.packetLoss = null;
		this.bitrate = null;
		this.showMoreValue = false;
		this.resolution = null;
		this.transport = [];
		this.popover = null;
		this.jid = jid;
		this.create();
	    }

	    /**
	     * Values for the connection quality
	     * @type {{98: string, 81: string, 64: string, 47: string, 30: string, 0: string}}
	     */
	    ConnectionIndicator.connectionQualityValues = {
		98: "18px", //full
		81: "15px",//4 bars
		64: "11px",//3 bars
		47: "7px",//2 bars
		30: "3px",//1 bar
		0: "0px"//empty
	    };

	    ConnectionIndicator.getIP = function(value)
	    {
		return value.substring(0, value.lastIndexOf(":"));
	    };

	    ConnectionIndicator.getPort = function(value)
	    {
		return value.substring(value.lastIndexOf(":") + 1, value.length);
	    };

	    ConnectionIndicator.getStringFromArray = function (array) {
		var res = "";
		for(var i = 0; i < array.length; i++)
		{
		    res += (i == 0? "" : ", ") + array[i];
		}
		return res;
	    }
	    /**
	     * Generates the html content.
	     * @returns {string} the html content.
	     */
	    ConnectionIndicator.prototype.generateText = function () {
		var downloadBitrate, uploadBitrate, packetLoss, resolution;

		if(this.bitrate === null)
		{
		    downloadBitrate = "N/A";
		    uploadBitrate = "N/A";
		}
		else
		{
		    downloadBitrate = this.bitrate.download? this.bitrate.download + " Kbps" : "N/A";
		    uploadBitrate = this.bitrate.upload? this.bitrate.upload + " Kbps" : "N/A";
		}

		if(this.packetLoss === null)
		{
		    packetLoss = "N/A";
		}
		else
		{

		    packetLoss = "<span class='jitsipopover_green'>&darr;</span>" +
			(this.packetLoss.download != null? this.packetLoss.download : "N/A") +
			"% <span class='jitsipopover_orange'>&uarr;</span>" +
			(this.packetLoss.upload != null? this.packetLoss.upload : "N/A") + "%";
		}

		var resolutionValue = null;
		if(this.resolution && this.jid != null)
		{
		    var keys = Object.keys(this.resolution);
		    if(keys.length == 1)
		    {
			for(var ssrc in this.resolution)
			{
			    resolutionValue = this.resolution[ssrc];
			}
		    }
		    else if(keys.length > 1)
		    {
			var displayedSsrc = simulcast.getReceivingSSRC(this.jid);
			resolutionValue = this.resolution[displayedSsrc];
		    }
		}

		if(this.jid==null)
		{
		    resolution = "";
		    if(this.resolution == null || !Object.keys(this.resolution)
			|| Object.keys(this.resolution).length == 0)
		    {
			resolution = "N/A";
		    }
		    else
			for(var i in this.resolution)
			{
			    resolutionValue = this.resolution[i];
			    if(resolutionValue)
			    {
				if(resolutionValue.height &&
				    resolutionValue.width)
				{
				    resolution += (resolution == ""? "" : ", ")
					+ resolutionValue.width + "x" + resolutionValue.height;
				}
			    }
			}
		}
		else if(!resolutionValue ||
		    !resolutionValue.height ||
		    !resolutionValue.width)
		{
		    resolution = "N/A";
		}
		else
		{
		    resolution = resolutionValue.width + "x" + resolutionValue.height;
		}

		var result = "<table style='width:100%'><tr><td><span class='jitsipopover_blue'>Bitrate:</span></td><td><span class='jitsipopover_green'>&darr;</span>" +
		    downloadBitrate + " <span class='jitsipopover_orange'>&uarr;</span>" +
		    uploadBitrate + "</td></tr><tr><td>" +
		    "<tr><td><span class='jitsipopover_blue'>Packet loss: </span></td><td>" + packetLoss  + "</td></tr><tr><td>" +
		    "<span class='jitsipopover_blue'>Resolution:</span></td><td>" + resolution + "</td></tr></table>";

		if(this.videoContainer.id == "localVideoContainer")
		    result += "<div class=\"jitsipopover_showmore\" onclick = \"ofmeet.VideoLayout.connectionIndicators['" +
			 this.videoContainer.id + "'].showMore()\">" + (this.showMoreValue? "Show less" : "Show More") + "</div><br />";

		if(this.showMoreValue)
		{
		    var downloadBandwidth, uploadBandwidth, transport;
		    if(this.bandwidth === null)
		    {
			downloadBandwidth = "N/A";
			uploadBandwidth = "N/A";
		    }
		    else
		    {
			downloadBandwidth = this.bandwidth.download? this.bandwidth.download + " Kbps" : "N/A";
			uploadBandwidth = this.bandwidth.upload? this.bandwidth.upload + " Kbps" : "N/A";
		    }

		    if(!this.transport || this.transport.length === 0)
		    {
			transport = "<tr><td><span class='jitsipopover_blue'>Address:</span></td><td> N/A</td></tr>";
		    }
		    else
		    {
			var data = {remoteIP: [], localIP:[], remotePort:[], localPort:[]};
			for(var i = 0; i < this.transport.length; i++)
			{
			    var ip =  ConnectionIndicator.getIP(this.transport[i].ip);
			    var port = ConnectionIndicator.getPort(this.transport[i].ip);
			    var localIP = ConnectionIndicator.getIP(this.transport[i].localip);
			    var localPort = ConnectionIndicator.getPort(this.transport[i].localip);
			    if(data.remoteIP.indexOf(ip) == -1)
			    {
			       data.remoteIP.push(ip);
			    }

			    if(data.remotePort.indexOf(port) == -1)
			    {
				data.remotePort.push(port);
			    }

			    if(data.localIP.indexOf(localIP) == -1)
			    {
				data.localIP.push(localIP);
			    }

			    if(data.localPort.indexOf(localPort) == -1)
			    {
				data.localPort.push(localPort);
			    }

			}
			var localTransport = "<tr><td><span class='jitsipopover_blue'>Local address" +
			    (data.localIP.length > 1? "es" : "") + ": </span></td><td> " +
			     ConnectionIndicator.getStringFromArray(data.localIP) + "</td></tr>";
			transport = "<tr><td><span class='jitsipopover_blue'>Remote address"+
			    (data.remoteIP.length > 1? "es" : "") + ":</span></td><td> " +
			    ConnectionIndicator.getStringFromArray(data.remoteIP) + "</td></tr>";
			if(this.transport.length > 1)
			{
			    transport += "<tr><td><span class='jitsipopover_blue'>Remote ports:</span></td><td>";
			    localTransport += "<tr><td><span class='jitsipopover_blue'>Local ports:</span></td><td>";
			}
			else
			{
			    transport += "<tr><td><span class='jitsipopover_blue'>Remote port:</span></td><td>";
			    localTransport += "<tr><td><span class='jitsipopover_blue'>Local port:</span></td><td>";
			}

			transport += ConnectionIndicator.getStringFromArray(data.remotePort);
			localTransport += ConnectionIndicator.getStringFromArray(data.localPort);
			transport += "</td></tr>";
			transport += localTransport + "</td></tr>";
			transport +="<tr><td><span class='jitsipopover_blue'>Transport:</span></td><td>" + this.transport[0].type + "</td></tr>";

		    }

		    result += "<table  style='width:100%'><tr><td><span class='jitsipopover_blue'>Estimated bandwidth:</span> </td><td>" +
			"<span class='jitsipopover_green'>&darr;</span>" + downloadBandwidth +
			" <span class='jitsipopover_orange'>&uarr;</span>" +
			uploadBandwidth + "</td></tr>";

		    result += transport + "</table>";

		}

		return result;
	    };

	    /**
	     * Shows or hide the additional information.
	     */
	    ConnectionIndicator.prototype.showMore = function () {
		this.showMoreValue = !this.showMoreValue;
		this.updatePopoverData();
	    };

	    /**
	     * Creates the indicator
	     */
	    ConnectionIndicator.prototype.create = function () {
		this.connectionIndicatorContainer = document.createElement("div");
		this.connectionIndicatorContainer.className = "connectionindicator";
		this.connectionIndicatorContainer.style.display = "none";
		this.videoContainer.appendChild(this.connectionIndicatorContainer);
		this.popover = new JitsiPopover($("#" + this.videoContainer.id + " > .connectionindicator"),
		    {content: "<div class=\"connection_info\">Come back here for " +
			"connection information once the conference starts</div>", skin: "black"});

		function createIcon(classes)
		{
		    var icon = document.createElement("span");
		    for(var i in classes)
		    {
			icon.classList.add(classes[i]);
		    }
		    icon.appendChild(document.createElement("i")).classList.add("icon-connection");
		    return icon;
		}
		this.emptyIcon = this.connectionIndicatorContainer.appendChild(
		    createIcon(["connection", "connection_empty"]));
		this.fullIcon = this.connectionIndicatorContainer.appendChild(
		    createIcon(["connection", "connection_full"]));

	    };

	    /**
	     * Removes the indicator
	     */
	    ConnectionIndicator.prototype.remove = function()
	    {
		this.connectionIndicatorContainer.remove();
		this.popover.forceHide();

	    };

	    /**
	     * Updates the data of the indicator
	     * @param percent the percent of connection quality
	     * @param object the statistics data.
	     */
	    ConnectionIndicator.prototype.updateConnectionQuality = function (percent, object) {

		if(percent === null)
		{
		    this.connectionIndicatorContainer.style.display = "none";
		    this.popover.forceHide();
		    return;
		}
		else
		{
		    if(this.connectionIndicatorContainer.style.display == "none") {
			this.connectionIndicatorContainer.style.display = "block";
			VideoLayout.updateMutePosition(this.videoContainer.id);
		    }
		}
		this.bandwidth = object.bandwidth;
		this.bitrate = object.bitrate;
		this.packetLoss = object.packetLoss;
		this.transport = object.transport;
		if(object.resolution)
		{
		    this.resolution = object.resolution;
		}
		for(var quality in ConnectionIndicator.connectionQualityValues)
		{
		    if(percent >= quality)
		    {
			this.fullIcon.style.width = ConnectionIndicator.connectionQualityValues[quality];
		    }
		}
		this.updatePopoverData();
	    };

	    /**
	     * Updates the resolution
	     * @param resolution the new resolution
	     */
	    ConnectionIndicator.prototype.updateResolution = function (resolution) {
		this.resolution = resolution;
		this.updatePopoverData();
	    };

	    /**
	     * Updates the content of the popover
	     */
	    ConnectionIndicator.prototype.updatePopoverData = function () {
		this.popover.updateContent("<div class=\"connection_info\">" + this.generateText() + "</div>");
	    };

	    /**
	     * Hides the popover
	     */
	    ConnectionIndicator.prototype.hide = function () {
		this.popover.forceHide();
	    };

	    /**
	     * Hides the indicator
	     */
	    ConnectionIndicator.prototype.hideIndicator = function () {
		this.connectionIndicatorContainer.style.display = "none";
		if(this.popover)
		    this.popover.forceHide();
	    };

	    /**
	     * Updates the data for the indicator
	     * @param id the id of the indicator
	     * @param percent the percent for connection quality
	     * @param object the data
	     */
	    function updateStatsIndicator(id, percent, object) {
		if(VideoLayout.connectionIndicators[id])
		    VideoLayout.connectionIndicators[id].updateConnectionQuality(percent, object);
	    }

	    /**
	     * Updates local stats
	     * @param percent
	     * @param object
	     */
	    my.updateLocalConnectionStats = function (percent, object) {
		var resolution = null;
		if(object.resolution !== null)
		{
		    resolution = object.resolution;
		    object.resolution = resolution[connection.emuc.myroomjid];
		    delete resolution[connection.emuc.myroomjid];
		}
		updateStatsIndicator("localVideoContainer", percent, object);
		for(var jid in resolution)
		{
		    if(resolution[jid] === null)
			continue;
		    var id = 'participant_' + Strophe.getResourceFromJid(jid);
		    if(VideoLayout.connectionIndicators[id])
		    {
			VideoLayout.connectionIndicators[id].updateResolution(resolution[jid]);
		    }
		}

	    };

	    /**
	     * Updates remote stats.
	     * @param jid the jid associated with the stats
	     * @param percent the connection quality percent
	     * @param object the stats data
	     */
	    my.updateConnectionStats = function (jid, percent, object) {
		var resourceJid = Strophe.getResourceFromJid(jid);

		var videoSpanId = 'participant_' + resourceJid;
		updateStatsIndicator(videoSpanId, percent, object);
	    };

	    /**
	     * Removes the connection
	     * @param jid
	     */
	    my.removeConnectionIndicator = function (jid) {
		if(VideoLayout.connectionIndicators['participant_' + Strophe.getResourceFromJid(jid)])
		    VideoLayout.connectionIndicators['participant_' + Strophe.getResourceFromJid(jid)].remove();
	    };

	    /**
	     * Hides the connection indicator
	     * @param jid
	     */
	    my.hideConnectionIndicator = function (jid) {
		if(VideoLayout.connectionIndicators['participant_' + Strophe.getResourceFromJid(jid)])
		    VideoLayout.connectionIndicators['participant_' + Strophe.getResourceFromJid(jid)].hide();
	    };

	    /**
	     * Hides all the indicators
	     */
	    my.onStatsStop = function () {
		for(var indicator in VideoLayout.connectionIndicators)
		{
		    VideoLayout.connectionIndicators[indicator].hideIndicator();
		}
	    };

	    return my;
	}(VideoLayout || {}));


	/* global $ */
	/**
	 * Utility functions.
	 */
	var Util = (function (my) {

	    /**
	     * Returns the text width for the given element.
	     *
	     * @param el the element
	     */
	    my.getTextWidth = function (el) {
		return (el.clientWidth + 1);
	    };

	    /**
	     * Returns the text height for the given element.
	     *
	     * @param el the element
	     */
	    my.getTextHeight = function (el) {
		return (el.clientHeight + 1);
	    };

	    /**
	     * Casts the given number to integer.
	     *
	     * @param number the number to cast
	     */
	    my.toInteger = function (number) {
		return Math.round(Number(number));
	    };

	    /**
	     * Plays the sound given by id.
	     *
	     * @param id the identifier of the audio element.
	     */

	    /**
	     * Escapes the given text.
	     */
	    my.escapeHtml = function (unsafeText) {
		return $('<div/>').text(unsafeText).html();
	    };

	    /**
	     * Returns the available video width.
	     */
	    my.getAvailableVideoWidth = function () {
		var rightPanelWidth = 0;

		return window.innerWidth - rightPanelWidth;
	    };

	    my.imageToGrayScale = function (canvas) {
		var context = canvas.getContext('2d');
		var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
		var pixels  = imgData.data;

		for (var i = 0, n = pixels.length; i < n; i += 4) {
		    var grayscale
			= pixels[i] * .3 + pixels[i+1] * .59 + pixels[i+2] * .11;
		    pixels[i  ] = grayscale;        // red
		    pixels[i+1] = grayscale;        // green
		    pixels[i+2] = grayscale;        // blue
		    // pixels[i+3]              is alpha
		}
		// redraw the image in black & white
		context.putImageData(imgData, 0, 0);
	    };

	    my.setTooltip = function (element, tooltipText, position) {
		element.setAttribute("data-content", tooltipText);
		element.setAttribute("data-toggle", "popover");
		element.setAttribute("data-placement", position);
		element.setAttribute("data-html", true);
		element.setAttribute("data-container", "body");
	    };

	    my.createExpBackoffTimer = function (step) {
		var count = 1;
		return function (reset) {
		    // Reset call
		    if (reset) {
			count = 1;
			return;
		    }
		    // Calculate next timeout
		    var timeout = Math.pow(2, count - 1);
		    count += 1;
		    return timeout * step;
		};
	    };

	    return my;
	}(Util || {}));



	/**
	 * trackUsage.
	 */

	(function () {

		function trackUsage(eventname, obj) {
		    //console.log('track', eventname, obj);
		    // implement your own tracking mechanism here
		}
		if (typeof exports !== 'undefined') {
		    module.exports = trackUsage;
		} else {
		    window.trackUsage = trackUsage;
		}

	})();


	/**
	 *
	 * SimulcastUtils
	 *
	 */
	function SimulcastUtils() {
	    this.logger = new SimulcastLogger("SimulcastUtils", 1);
	}

	/**
	 *
	 * @type {{}}
	 * @private
	 */
	SimulcastUtils.prototype._emptyCompoundIndex = {};

	/**
	 *
	 * @param lines
	 * @param videoSources
	 * @private
	 */
	SimulcastUtils.prototype._replaceVideoSources = function (lines, videoSources) {
	    var i, inVideo = false, index = -1, howMany = 0;

	    this.logger.info('Replacing video sources...');

	    for (i = 0; i < lines.length; i++) {
		if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
		    // Out of video.
		    break;
		}

		if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
		    // In video.
		    inVideo = true;
		}

		if (inVideo && (lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:'
		    || lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:')) {

		    if (index === -1) {
			index = i;
		    }

		    howMany++;
		}
	    }

	    //  efficiency baby ;)
	    lines.splice.apply(lines,
		[index, howMany].concat(videoSources));

	};

	SimulcastUtils.prototype.isValidDescription = function (desc)
	{
	    return desc && desc != null
		&& desc.type && desc.type != ''
		&& desc.sdp && desc.sdp != '';
	};

	SimulcastUtils.prototype._getVideoSources = function (lines) {
	    var i, inVideo = false, sb = [];

	    this.logger.info('Getting video sources...');

	    for (i = 0; i < lines.length; i++) {
		if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
		    // Out of video.
		    break;
		}

		if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
		    // In video.
		    inVideo = true;
		}

		if (inVideo && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
		    // In SSRC.
		    sb.push(lines[i]);
		}

		if (inVideo && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
		    sb.push(lines[i]);
		}
	    }

	    return sb;
	};

	SimulcastUtils.prototype.parseMedia = function (lines, mediatypes) {
	    var i, res = [], type, cur_media, idx, ssrcs, cur_ssrc, ssrc,
		ssrc_attribute, group, semantics, skip = true;

	    this.logger.info('Parsing media sources...');

	    for (i = 0; i < lines.length; i++) {
		if (lines[i].substring(0, 'm='.length) === 'm=') {

		    type = lines[i]
			.substr('m='.length, lines[i].indexOf(' ') - 'm='.length);
		    skip = mediatypes !== undefined && mediatypes.indexOf(type) === -1;

		    if (!skip) {
			cur_media = {
			    'type': type,
			    'sources': {},
			    'groups': []
			};

			res.push(cur_media);
		    }

		} else if (!skip && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {

		    idx = lines[i].indexOf(' ');
		    ssrc = lines[i].substring('a=ssrc:'.length, idx);
		    if (cur_media.sources[ssrc] === undefined) {
			cur_ssrc = {'ssrc': ssrc};
			cur_media.sources[ssrc] = cur_ssrc;
		    }

		    ssrc_attribute = lines[i].substr(idx + 1).split(':', 2)[0];
		    cur_ssrc[ssrc_attribute] = lines[i].substr(idx + 1).split(':', 2)[1];

		    if (cur_media.base === undefined) {
			cur_media.base = cur_ssrc;
		    }

		} else if (!skip && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
		    idx = lines[i].indexOf(' ');
		    semantics = lines[i].substr(0, idx).substr('a=ssrc-group:'.length);
		    ssrcs = lines[i].substr(idx).trim().split(' ');
		    group = {
			'semantics': semantics,
			'ssrcs': ssrcs
		    };
		    cur_media.groups.push(group);
		} else if (!skip && (lines[i].substring(0, 'a=sendrecv'.length) === 'a=sendrecv' ||
		    lines[i].substring(0, 'a=recvonly'.length) === 'a=recvonly' ||
		    lines[i].substring(0, 'a=sendonly'.length) === 'a=sendonly' ||
		    lines[i].substring(0, 'a=inactive'.length) === 'a=inactive')) {

		    cur_media.direction = lines[i].substring('a='.length);
		}
	    }

	    return res;
	};

	/**
	 * The _indexOfArray() method returns the first a CompoundIndex at which a
	 * given element can be found in the array, or _emptyCompoundIndex if it is
	 * not present.
	 *
	 * Example:
	 *
	 * _indexOfArray('3', [ 'this is line 1', 'this is line 2', 'this is line 3' ])
	 *
	 * returns {row: 2, column: 14}
	 *
	 * @param needle
	 * @param haystack
	 * @param start
	 * @returns {}
	 * @private
	 */
	SimulcastUtils.prototype._indexOfArray = function (needle, haystack, start) {
	    var length = haystack.length, idx, i;

	    if (!start) {
		start = 0;
	    }

	    for (i = start; i < length; i++) {
		idx = haystack[i].indexOf(needle);
		if (idx !== -1) {
		    return {row: i, column: idx};
		}
	    }
	    return this._emptyCompoundIndex;
	};

	SimulcastUtils.prototype._removeSimulcastGroup = function (lines) {
	    var i;

	    for (i = lines.length - 1; i >= 0; i--) {
		if (lines[i].indexOf('a=ssrc-group:SIM') !== -1) {
		    lines.splice(i, 1);
		}
	    }
	};

	SimulcastUtils.prototype._compileVideoSources = function (videoSources) {
	    var sb = [], ssrc, addedSSRCs = [];

	    this.logger.info('Compiling video sources...');

	    // Add the groups
	    if (videoSources.groups && videoSources.groups.length !== 0) {
		videoSources.groups.forEach(function (group) {
		    if (group.ssrcs && group.ssrcs.length !== 0) {
			sb.push([['a=ssrc-group:', group.semantics].join(''), group.ssrcs.join(' ')].join(' '));

			// if (group.semantics !== 'SIM') {
			group.ssrcs.forEach(function (ssrc) {
			    addedSSRCs.push(ssrc);
			    sb.splice.apply(sb, [sb.length, 0].concat([
				["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
				["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
			});
			//}
		    }
		});
	    }

	    // Then add any free sources.
	    if (videoSources.sources) {
		for (ssrc in videoSources.sources) {
		    if (addedSSRCs.indexOf(ssrc) === -1) {
			sb.splice.apply(sb, [sb.length, 0].concat([
			    ["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
			    ["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
		    }
		}
	    }

	    return sb;
	};

	function SimulcastReceiver() {
	    this.simulcastUtils = new SimulcastUtils();
	    this.logger = new SimulcastLogger('SimulcastReceiver', 1);
	}

	SimulcastReceiver.prototype._remoteVideoSourceCache = '';
	SimulcastReceiver.prototype._remoteMaps = {
	    msid2Quality: {},
	    ssrc2Msid: {},
	    msid2ssrc: {},
	    receivingVideoStreams: {}
	};

	SimulcastReceiver.prototype._cacheRemoteVideoSources = function (lines) {
	    this._remoteVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
	};

	SimulcastReceiver.prototype._restoreRemoteVideoSources = function (lines) {
	    this.simulcastUtils._replaceVideoSources(lines, this._remoteVideoSourceCache);
	};

	SimulcastReceiver.prototype._ensureGoogConference = function (lines) {
	    var sb;

	    this.logger.info('Ensuring x-google-conference flag...')

	    if (this.simulcastUtils._indexOfArray('a=x-google-flag:conference', lines) === this.simulcastUtils._emptyCompoundIndex) {
		// TODO(gp) do that for the audio as well as suggested by fippo.
		// Add the google conference flag
		sb = this.simulcastUtils._getVideoSources(lines);
		sb = ['a=x-google-flag:conference'].concat(sb);
		this.simulcastUtils._replaceVideoSources(lines, sb);
	    }
	};

	SimulcastReceiver.prototype._restoreSimulcastGroups = function (sb) {
	    this._restoreRemoteVideoSources(sb);
	};

	/**
	 * Restores the simulcast groups of the remote description. In
	 * transformRemoteDescription we remove those in order for the set remote
	 * description to succeed. The focus needs the signal the groups to new
	 * participants.
	 *
	 * @param desc
	 * @returns {*}
	 */
	SimulcastReceiver.prototype.reverseTransformRemoteDescription = function (desc) {
	    var sb;

	    if (!this.simulcastUtils.isValidDescription(desc)) {
		return desc;
	    }

	    if (config.enableSimulcast) {
		sb = desc.sdp.split('\r\n');

		this._restoreSimulcastGroups(sb);

		desc = new RTCSessionDescription({
		    type: desc.type,
		    sdp: sb.join('\r\n')
		});
	    }

	    return desc;
	};

	SimulcastUtils.prototype._ensureOrder = function (lines) {
	    var videoSources, sb;

	    videoSources = this.parseMedia(lines, ['video'])[0];
	    sb = this._compileVideoSources(videoSources);

	    this._replaceVideoSources(lines, sb);
	};

	SimulcastReceiver.prototype._updateRemoteMaps = function (lines) {
	    var remoteVideoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0],
		videoSource, quality;

	    // (re) initialize the remote maps.
	    this._remoteMaps.msid2Quality = {};
	    this._remoteMaps.ssrc2Msid = {};
	    this._remoteMaps.msid2ssrc = {};

	    var self = this;
	    if (remoteVideoSources && remoteVideoSources.groups && remoteVideoSources.groups.length !== 0) {
		remoteVideoSources.groups.forEach(function (group) {
		    if (group.semantics === 'SIM' && group.ssrcs && group.ssrcs.length !== 0) {
			quality = 0;
			group.ssrcs.forEach(function (ssrc) {
			    videoSource = remoteVideoSources.sources[ssrc];
			    self._remoteMaps.msid2Quality[videoSource.msid] = quality++;
			    self._remoteMaps.ssrc2Msid[videoSource.ssrc] = videoSource.msid;
			    self._remoteMaps.msid2ssrc[videoSource.msid] = videoSource.ssrc;
			});
		    }
		});
	    }
	};

	SimulcastReceiver.prototype._setReceivingVideoStream = function (resource, ssrc) {
	    this._remoteMaps.receivingVideoStreams[resource] = ssrc;
	};

	/**
	 * Returns a stream with single video track, the one currently being
	 * received by this endpoint.
	 *
	 * @param stream the remote simulcast stream.
	 * @returns {webkitMediaStream}
	 */
	SimulcastReceiver.prototype.getReceivingVideoStream = function (stream) {
	    var tracks, i, electedTrack, msid, quality = 0, receivingTrackId;

	    var self = this;
	    if (config.enableSimulcast) {

		stream.getVideoTracks().some(function (track) {
		    return Object.keys(self._remoteMaps.receivingVideoStreams).some(function (resource) {
			var ssrc = self._remoteMaps.receivingVideoStreams[resource];
			var msid = self._remoteMaps.ssrc2Msid[ssrc];
			if (msid == [stream.id, track.id].join(' ')) {
			    electedTrack = track;
			    return true;
			}
		    });
		});

		if (!electedTrack) {
		    // we don't have an elected track, choose by initial quality.
		    tracks = stream.getVideoTracks();
		    for (i = 0; i < tracks.length; i++) {
			msid = [stream.id, tracks[i].id].join(' ');
			if (this._remoteMaps.msid2Quality[msid] === quality) {
			    electedTrack = tracks[i];
			    break;
			}
		    }

		    // TODO(gp) if the initialQuality could not be satisfied, lower
		    // the requirement and try again.
		}
	    }

	    return (electedTrack)
		? new webkitMediaStream([electedTrack])
		: stream;
	};

	SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
	    var resource = Strophe.getResourceFromJid(jid);
	    var ssrc = this._remoteMaps.receivingVideoStreams[resource];

	    // If we haven't receiving a "changed" event yet, then we must be receiving
	    // low quality (that the sender always streams).
	    if (!ssrc && connection.jingle) {
		var session;
		var i, j, k;

		var keys = Object.keys(connection.jingle.sessions);
		for (i = 0; i < keys.length; i++) {
		    var sid = keys[i];

		    if (ssrc) {
			// stream found, stop.
			break;
		    }

		    session = connection.jingle.sessions[sid];
		    if (session.remoteStreams) {
			for (j = 0; j < session.remoteStreams.length; j++) {
			    var remoteStream = session.remoteStreams[j];

			    if (ssrc) {
				// stream found, stop.
				break;
			    }
			    var tracks = remoteStream.getVideoTracks();
			    if (tracks) {
				for (k = 0; k < tracks.length; k++) {
				    var track = tracks[k];
				    var msid = [remoteStream.id, track.id].join(' ');
				    var _ssrc = this._remoteMaps.msid2ssrc[msid];
				    var _jid = ssrc2jid[_ssrc];
				    var quality = this._remoteMaps.msid2Quality[msid];
				    if (jid == _jid && quality == 0) {
					ssrc = _ssrc;
					// stream found, stop.
					break;
				    }
				}
			    }
			}
		    }
		}
	    }

	    return ssrc;
	};

	SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
	{
	    var session, electedStream;
	    var i, j, k;
	    if (connection.jingle) {
		var keys = Object.keys(connection.jingle.sessions);
		for (i = 0; i < keys.length; i++) {
		    var sid = keys[i];

		    if (electedStream) {
			// stream found, stop.
			break;
		    }

		    session = connection.jingle.sessions[sid];
		    if (session.remoteStreams) {
			for (j = 0; j < session.remoteStreams.length; j++) {
			    var remoteStream = session.remoteStreams[j];

			    if (electedStream) {
				// stream found, stop.
				break;
			    }
			    var tracks = remoteStream.getVideoTracks();
			    if (tracks) {
				for (k = 0; k < tracks.length; k++) {
				    var track = tracks[k];
				    var msid = [remoteStream.id, track.id].join(' ');
				    var tmp = this._remoteMaps.msid2ssrc[msid];
				    if (tmp == ssrc) {
					electedStream = new webkitMediaStream([track]);
					// stream found, stop.
					break;
				    }
				}
			    }
			}
		    }
		}
	    }

	    return {
		session: session,
		stream: electedStream
	    };
	};

	/**
	 * Gets the fully qualified msid (stream.id + track.id) associated to the
	 * SSRC.
	 *
	 * @param ssrc
	 * @returns {*}
	 */
	SimulcastReceiver.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
	    return this._remoteMaps.ssrc2Msid[ssrc];
	};

	function SimulcastSender() {
	    this.simulcastUtils = new SimulcastUtils();
	    this.logger = new SimulcastLogger('SimulcastSender', 1);
	}

	SimulcastSender.prototype.displayedLocalVideoStream = null;

	SimulcastSender.prototype._generateGuid = (function () {
	    function s4() {
		return Math.floor((1 + Math.random()) * 0x10000)
		    .toString(16)
		    .substring(1);
	    }

	    return function () {
		return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
		    s4() + '-' + s4() + s4() + s4();
	    };
	}());

	// Returns a random integer between min (included) and max (excluded)
	// Using Math.round() gives a non-uniform distribution!
	SimulcastSender.prototype._generateRandomSSRC = function () {
	    var min = 0, max = 0xffffffff;
	    return Math.floor(Math.random() * (max - min)) + min;
	};

	SimulcastSender.prototype.getLocalVideoStream = function () {
	    return (this.displayedLocalVideoStream != null)
		? this.displayedLocalVideoStream
		// in case we have no simulcast at all, i.e. we didn't perform the GUM
		: connection.jingle.localVideo;
	};

	function NativeSimulcastSender() {
	    SimulcastSender.call(this); // call the super constructor.
	}

	NativeSimulcastSender.prototype = Object.create(SimulcastSender.prototype);

	NativeSimulcastSender.prototype._localExplosionMap = {};
	NativeSimulcastSender.prototype._isUsingScreenStream = false;
	NativeSimulcastSender.prototype._localVideoSourceCache = '';

	NativeSimulcastSender.prototype.reset = function () {
	    this._localExplosionMap = {};
	    this._isUsingScreenStream = isUsingScreenStream;
	};

	NativeSimulcastSender.prototype._cacheLocalVideoSources = function (lines) {
	    this._localVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
	};

	NativeSimulcastSender.prototype._restoreLocalVideoSources = function (lines) {
	    this.simulcastUtils._replaceVideoSources(lines, this._localVideoSourceCache);
	};

	NativeSimulcastSender.prototype._appendSimulcastGroup = function (lines) {
	    var videoSources, ssrcGroup, simSSRC, numOfSubs = 2, i, sb, msid;

	    this.logger.info('Appending simulcast group...');

	    // Get the primary SSRC information.
	    videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];

	    // Start building the SIM SSRC group.
	    ssrcGroup = ['a=ssrc-group:SIM'];

	    // The video source buffer.
	    sb = [];

	    // Create the simulcast sub-streams.
	    for (i = 0; i < numOfSubs; i++) {
		// TODO(gp) prevent SSRC collision.
		simSSRC = this._generateRandomSSRC();
		ssrcGroup.push(simSSRC);

		if (videoSources.base) 
		{
			sb.splice.apply(sb, [sb.length, 0].concat(
			    [["a=ssrc:", simSSRC, " cname:", videoSources.base.cname].join(''),
				["a=ssrc:", simSSRC, " msid:", videoSources.base.msid].join('')]
			));
		}

		this.logger.info(['Generated substream ', i, ' with SSRC ', simSSRC, '.'].join(''));

	    }

	    // Add the group sim layers.
	    sb.splice(0, 0, ssrcGroup.join(' '))

	    this.simulcastUtils._replaceVideoSources(lines, sb);
	};

	// Does the actual patching.
	NativeSimulcastSender.prototype._ensureSimulcastGroup = function (lines) {

	    this.logger.info('Ensuring simulcast group...');

	    if (this.simulcastUtils._indexOfArray('a=ssrc-group:SIM', lines) === this.simulcastUtils._emptyCompoundIndex) {
		this._appendSimulcastGroup(lines);
		this._cacheLocalVideoSources(lines);
	    } else {
		// verify that the ssrcs participating in the SIM group are present
		// in the SDP (needed for presence).
		this._restoreLocalVideoSources(lines);
	    }
	};

	/**
	 * Produces a single stream with multiple tracks for local video sources.
	 *
	 * @param lines
	 * @private
	 */
	NativeSimulcastSender.prototype._explodeSimulcastSenderSources = function (lines) {
	    var sb, msid, sid, tid, videoSources, self;

	    this.logger.info('Exploding local video sources...');

	    videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];

	    self = this;
	    if (videoSources.groups && videoSources.groups.length !== 0) {
		videoSources.groups.forEach(function (group) {
		    if (group.semantics === 'SIM') {
			group.ssrcs.forEach(function (ssrc) {

			    // Get the msid for this ssrc..
			    if (self._localExplosionMap[ssrc]) {
				// .. either from the explosion map..
				msid = self._localExplosionMap[ssrc];
			    } else {
				// .. or generate a new one (msid).
				sid = videoSources.sources[ssrc].msid
				    .substring(0, videoSources.sources[ssrc].msid.indexOf(' '));

				tid = self._generateGuid();
				msid = [sid, tid].join(' ');
				self._localExplosionMap[ssrc] = msid;
			    }

			    // Assign it to the source object.
			    videoSources.sources[ssrc].msid = msid;

			    // TODO(gp) Change the msid of associated sources.
			});
		    }
		});
	    }

	    sb = this.simulcastUtils._compileVideoSources(videoSources);

	    this.simulcastUtils._replaceVideoSources(lines, sb);
	};

	/**
	 * GUM for simulcast.
	 *
	 * @param constraints
	 * @param success
	 * @param err
	 */
	NativeSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {

	    // There's nothing special to do for native simulcast, so just do a normal GUM.
	    navigator.webkitGetUserMedia(constraints, function (hqStream) {
		success(hqStream);
	    }, err);
	};

	/**
	 * Prepares the local description for public usage (i.e. to be signaled
	 * through Jingle to the focus).
	 *
	 * @param desc
	 * @returns {RTCSessionDescription}
	 */
	NativeSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
	    var sb;

	    if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
		return desc;
	    }


	    sb = desc.sdp.split('\r\n');

	    this._explodeSimulcastSenderSources(sb);

	    desc = new RTCSessionDescription({
		type: desc.type,
		sdp: sb.join('\r\n')
	    });

	    this.logger.fine(['Exploded local video sources', desc.sdp].join(' '));

	    return desc;
	};

	/**
	 * Ensures that the simulcast group is present in the answer, _if_ native
	 * simulcast is enabled,
	 *
	 * @param desc
	 * @returns {*}
	 */
	NativeSimulcastSender.prototype.transformAnswer = function (desc) {

	    if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
		return desc;
	    }

	    var sb = desc.sdp.split('\r\n');

	    // Even if we have enabled native simulcasting previously
	    // (with a call to SLD with an appropriate SDP, for example),
	    // createAnswer seems to consistently generate incomplete SDP
	    // with missing SSRCS.
	    //
	    // So, subsequent calls to SLD will have missing SSRCS and presence
	    // won't have the complete list of SRCs.
	    this._ensureSimulcastGroup(sb);

	    desc = new RTCSessionDescription({
		type: desc.type,
		sdp: sb.join('\r\n')
	    });

	    this.logger.fine(['Transformed answer', desc.sdp].join(' '));

	    return desc;
	};


	/**
	 *
	 *
	 * @param desc
	 * @returns {*}
	 */
	NativeSimulcastSender.prototype.transformLocalDescription = function (desc) {
	    return desc;
	};

	/**
	 * Removes the ssrc-group:SIM from the remote description bacause Chrome
	 * either gets confused and thinks this is an FID group or, if an FID group
	 * is already present, it fails to set the remote description.
	 *
	 * @param desc
	 * @returns {*}
	 */
	SimulcastReceiver.prototype.transformRemoteDescription = function (desc) {

	    if (desc && desc.sdp) {
		var sb = desc.sdp.split('\r\n');

		this._updateRemoteMaps(sb);
		this._cacheRemoteVideoSources(sb);

		// NOTE(gp) this needs to be called after updateRemoteMaps because we
		// need the simulcast group in the _updateRemoteMaps() method.
		this.simulcastUtils._removeSimulcastGroup(sb);

		if (desc.sdp.indexOf('a=ssrc-group:SIM') !== -1) {
		    // We don't need the goog conference flag if we're not doing
		    // simulcast.
		    this._ensureGoogConference(sb);
		}

		desc = new RTCSessionDescription({
		    type: desc.type,
		    sdp: sb.join('\r\n')
		});

		this.logger.fine(['Transformed remote description', desc.sdp].join(' '));
	    }

	    return desc;
	};

	NativeSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
	    // Nothing to do here, native simulcast does that auto-magically.
	};

	NativeSimulcastSender.prototype.constructor = NativeSimulcastSender;

	function SimpleSimulcastSender() {
	    SimulcastSender.call(this);
	}

	SimpleSimulcastSender.prototype = Object.create(SimulcastSender.prototype);

	SimpleSimulcastSender.prototype.localStream = null;
	SimpleSimulcastSender.prototype._localMaps = {
	    msids: [],
	    msid2ssrc: {}
	};

	/**
	 * Groups local video sources together in the ssrc-group:SIM group.
	 *
	 * @param lines
	 * @private
	 */
	SimpleSimulcastSender.prototype._groupLocalVideoSources = function (lines) {
	    var sb, videoSources, ssrcs = [], ssrc;

	    this.logger.info('Grouping local video sources...');

	    videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];

	    for (ssrc in videoSources.sources) {
		// jitsi-meet destroys/creates streams at various places causing
		// the original local stream ids to change. The only thing that
		// remains unchanged is the trackid.
		this._localMaps.msid2ssrc[videoSources.sources[ssrc].msid.split(' ')[1]] = ssrc;
	    }

	    var self = this;
	    // TODO(gp) add only "free" sources.
	    this._localMaps.msids.forEach(function (msid) {
		ssrcs.push(self._localMaps.msid2ssrc[msid]);
	    });

	    if (!videoSources.groups) {
		videoSources.groups = [];
	    }

	    videoSources.groups.push({
		'semantics': 'SIM',
		'ssrcs': ssrcs
	    });

	    sb = this.simulcastUtils._compileVideoSources(videoSources);

	    this.simulcastUtils._replaceVideoSources(lines, sb);
	};

	/**
	 * GUM for simulcast.
	 *
	 * @param constraints
	 * @param success
	 * @param err
	 */
	SimpleSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {

	    // TODO(gp) what if we request a resolution not supported by the hardware?
	    // TODO(gp) make the lq stream configurable; although this wouldn't work with native simulcast
	    var lqConstraints = {
		audio: false,
		video: {
		    mandatory: {
			maxWidth: 320,
			maxHeight: 180,
			maxFrameRate: 15
		    }
		}
	    };

	    this.logger.info('HQ constraints: ', constraints);
	    this.logger.info('LQ constraints: ', lqConstraints);


	    // NOTE(gp) if we request the lq stream first webkitGetUserMedia
	    // fails randomly. Tested with Chrome 37. As fippo suggested, the
	    // reason appears to be that Chrome only acquires the cam once and
	    // then downscales the picture (https://code.google.com/p/chromium/issues/detail?id=346616#c11)

	    var self = this;
	    navigator.webkitGetUserMedia(constraints, function (hqStream) {

		self.localStream = hqStream;

		// reset local maps.
		self._localMaps.msids = [];
		self._localMaps.msid2ssrc = {};

		// add hq trackid to local map
		self._localMaps.msids.push(hqStream.getVideoTracks()[0].id);

		navigator.webkitGetUserMedia(lqConstraints, function (lqStream) {

		    self.displayedLocalVideoStream = lqStream;

		    // NOTE(gp) The specification says Array.forEach() will visit
		    // the array elements in numeric order, and that it doesn't
		    // visit elements that don't exist.

		    // add lq trackid to local map
		    self._localMaps.msids.splice(0, 0, lqStream.getVideoTracks()[0].id);

		    self.localStream.addTrack(lqStream.getVideoTracks()[0]);
		    success(self.localStream);
		}, err);
	    }, err);
	};

	/**
	 * Prepares the local description for public usage (i.e. to be signaled
	 * through Jingle to the focus).
	 *
	 * @param desc
	 * @returns {RTCSessionDescription}
	 */
	SimpleSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
	    var sb;

	    if (!this.simulcastUtils.isValidDescription(desc)) {
		return desc;
	    }

	    sb = desc.sdp.split('\r\n');

	    this._groupLocalVideoSources(sb);

	    desc = new RTCSessionDescription({
		type: desc.type,
		sdp: sb.join('\r\n')
	    });

	    this.logger.fine('Grouped local video sources');
	    this.logger.fine(desc.sdp);

	    return desc;
	};

	/**
	 * Ensures that the simulcast group is present in the answer, _if_ native
	 * simulcast is enabled,
	 *
	 * @param desc
	 * @returns {*}
	 */
	SimpleSimulcastSender.prototype.transformAnswer = function (desc) {
	    return desc;
	};


	/**
	 *
	 *
	 * @param desc
	 * @returns {*}
	 */
	SimpleSimulcastSender.prototype.transformLocalDescription = function (desc) {

	    var sb = desc.sdp.split('\r\n');

	    this.simulcastUtils._removeSimulcastGroup(sb);

	    desc = new RTCSessionDescription({
		type: desc.type,
		sdp: sb.join('\r\n')
	    });

	    this.logger.fine('Transformed local description');
	    this.logger.fine(desc.sdp);

	    return desc;
	};

	SimpleSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
	    var trackid;

	    var self = this;
	    this.logger.log(['Requested to', enabled ? 'enable' : 'disable', ssrc].join(' '));
	    if (Object.keys(this._localMaps.msid2ssrc).some(function (tid) {
		// Search for the track id that corresponds to the ssrc
		if (self._localMaps.msid2ssrc[tid] == ssrc) {
		    trackid = tid;
		    return true;
		}
	    }) && self.localStream.getVideoTracks().some(function (track) {
		// Start/stop the track that corresponds to the track id
		if (track.id === trackid) {
		    track.enabled = enabled;
		    return true;
		}
	    })) {
		this.logger.log([trackid, enabled ? 'enabled' : 'disabled'].join(' '));
		$(document).trigger(enabled
		    ? 'simulcastlayerstarted'
		    : 'simulcastlayerstopped');
	    } else {
		this.logger.error("I don't have a local stream with SSRC " + ssrc);
	    }
	};

	SimpleSimulcastSender.prototype.constructor = SimpleSimulcastSender;

	function NoSimulcastSender() {
	    SimulcastSender.call(this);
	}

	NoSimulcastSender.prototype = Object.create(SimulcastSender.prototype);

	/**
	 * GUM for simulcast.
	 *
	 * @param constraints
	 * @param success
	 * @param err
	 */
	NoSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
	    navigator.webkitGetUserMedia(constraints, function (hqStream) {
		success(hqStream);
	    }, err);
	};

	/**
	 * Prepares the local description for public usage (i.e. to be signaled
	 * through Jingle to the focus).
	 *
	 * @param desc
	 * @returns {RTCSessionDescription}
	 */
	NoSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
	    return desc;
	};

	/**
	 * Ensures that the simulcast group is present in the answer, _if_ native
	 * simulcast is enabled,
	 *
	 * @param desc
	 * @returns {*}
	 */
	NoSimulcastSender.prototype.transformAnswer = function (desc) {
	    return desc;
	};


	/**
	 *
	 *
	 * @param desc
	 * @returns {*}
	 */
	NoSimulcastSender.prototype.transformLocalDescription = function (desc) {
	    return desc;
	};

	NoSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {

	};

	NoSimulcastSender.prototype.constructor = NoSimulcastSender;

	/**
	 *
	 * @constructor
	 */
	function SimulcastManager() {

	    // Create the simulcast utilities.
	    this.simulcastUtils = new SimulcastUtils();

	    // Create remote simulcast.
	    this.simulcastReceiver = new SimulcastReceiver();

	    // Initialize local simulcast.

	    // TODO(gp) move into SimulcastManager.prototype.getUserMedia and take into
	    // account constraints.
	    if (!config.enableSimulcast) {
		this.simulcastSender = new NoSimulcastSender();
	    } else {

		var isChromium = window.chrome,
		    vendorName = window.navigator.vendor;
		if(isChromium !== null && isChromium !== undefined
		    /* skip opera */
		    && vendorName === "Google Inc."
		    /* skip Chromium as suggested by fippo */
		    && !window.navigator.appVersion.match(/Chromium\//) ) {
		    var ver = parseInt(window.navigator.appVersion.match(/Chrome\/(\d+)\./)[1], 10);
		    if (ver > 37) {
			this.simulcastSender = new NativeSimulcastSender();
		    } else {
			this.simulcastSender = new NoSimulcastSender();
		    }
		} else {
		    this.simulcastSender = new NoSimulcastSender();
		}

	    }
	}

	/**
	 * Restores the simulcast groups of the remote description. In
	 * transformRemoteDescription we remove those in order for the set remote
	 * description to succeed. The focus needs the signal the groups to new
	 * participants.
	 *
	 * @param desc
	 * @returns {*}
	 */
	SimulcastManager.prototype.reverseTransformRemoteDescription = function (desc) {
	    return this.simulcastReceiver.reverseTransformRemoteDescription(desc);
	};

	/**
	 * Removes the ssrc-group:SIM from the remote description bacause Chrome
	 * either gets confused and thinks this is an FID group or, if an FID group
	 * is already present, it fails to set the remote description.
	 *
	 * @param desc
	 * @returns {*}
	 */
	SimulcastManager.prototype.transformRemoteDescription = function (desc) {
	    return this.simulcastReceiver.transformRemoteDescription(desc);
	};

	/**
	 * Gets the fully qualified msid (stream.id + track.id) associated to the
	 * SSRC.
	 *
	 * @param ssrc
	 * @returns {*}
	 */
	SimulcastManager.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
	    return this.simulcastReceiver.getRemoteVideoStreamIdBySSRC(ssrc);
	};

	/**
	 * Returns a stream with single video track, the one currently being
	 * received by this endpoint.
	 *
	 * @param stream the remote simulcast stream.
	 * @returns {webkitMediaStream}
	 */
	SimulcastManager.prototype.getReceivingVideoStream = function (stream) {
	    return this.simulcastReceiver.getReceivingVideoStream(stream);
	};

	/**
	 *
	 *
	 * @param desc
	 * @returns {*}
	 */
	SimulcastManager.prototype.transformLocalDescription = function (desc) {
	    return this.simulcastSender.transformLocalDescription(desc);
	};

	/**
	 *
	 * @returns {*}
	 */
	SimulcastManager.prototype.getLocalVideoStream = function() {
	    return this.simulcastSender.getLocalVideoStream();
	};

	/**
	 * GUM for simulcast.
	 *
	 * @param constraints
	 * @param success
	 * @param err
	 */
	SimulcastManager.prototype.getUserMedia = function (constraints, success, err) {

	    this.simulcastSender.getUserMedia(constraints, success, err);
	};

	/**
	 * Prepares the local description for public usage (i.e. to be signaled
	 * through Jingle to the focus).
	 *
	 * @param desc
	 * @returns {RTCSessionDescription}
	 */
	SimulcastManager.prototype.reverseTransformLocalDescription = function (desc) {
	    return this.simulcastSender.reverseTransformLocalDescription(desc);
	};

	/**
	 * Ensures that the simulcast group is present in the answer, _if_ native
	 * simulcast is enabled,
	 *
	 * @param desc
	 * @returns {*}
	 */
	SimulcastManager.prototype.transformAnswer = function (desc) {
	    return this.simulcastSender.transformAnswer(desc);
	};

	SimulcastManager.prototype.getReceivingSSRC = function (jid) {
	    return this.simulcastReceiver.getReceivingSSRC(jid);
	};

	SimulcastManager.prototype.getReceivingVideoStreamBySSRC = function (msid) {
	    return this.simulcastReceiver.getReceivingVideoStreamBySSRC(msid);
	};

	/**
	 *
	 * @param lines
	 * @param mediatypes
	 * @returns {*}
	 */
	SimulcastManager.prototype.parseMedia = function(lines, mediatypes) {
	    var sb = lines.sdp.split('\r\n');
	    return this.simulcastUtils.parseMedia(sb, mediatypes);
	};

	SimulcastManager.prototype._setReceivingVideoStream = function(resource, ssrc) {
	    this.simulcastReceiver._setReceivingVideoStream(resource, ssrc);
	};

	SimulcastManager.prototype._setLocalVideoStreamEnabled = function(ssrc, enabled) {
	    this.simulcastSender._setLocalVideoStreamEnabled(ssrc, enabled);
	};

	SimulcastManager.prototype.resetSender = function() {
	    if (typeof this.simulcastSender.reset === 'function'){
		this.simulcastSender.reset();
	    }
	};

	/**
	 *
	 * @constructor
	 */
	function SimulcastLogger(name, lvl) {
	    this.name = name;
	    this.lvl = lvl;
	}

	SimulcastLogger.prototype.log = function (text) {
	    if (this.lvl) {
		//console.log(text);
	    }
	};

	SimulcastLogger.prototype.info = function (text) {
	    if (this.lvl > 1) {
		//console.info(text);
	    }
	};

	SimulcastLogger.prototype.fine = function (text) {
	    if (this.lvl > 2) {
		//console.log(text);
	    }
	};

	SimulcastLogger.prototype.error = function (text) {
	    console.error(text);
	};

	var simulcast = null;

	$(document).bind('simulcastlayerschanged', function (event, endpointSimulcastLayers) {
	    endpointSimulcastLayers.forEach(function (esl) {
		var ssrc = esl.simulcastLayer.primarySSRC;
		simulcast._setReceivingVideoStream(esl.endpoint, ssrc);
	    });
	});

	$(document).bind('startsimulcastlayer', function (event, simulcastLayer) {
	    var ssrc = simulcastLayer.primarySSRC;
	    simulcast._setLocalVideoStreamEnabled(ssrc, true);
	});

	$(document).bind('stopsimulcastlayer', function (event, simulcastLayer) {
	    var ssrc = simulcastLayer.primarySSRC;
	    simulcast._setLocalVideoStreamEnabled(ssrc, false);
	});


	/**
	 * SettingsMenu
	 */

	var SettingsMenu = (function(my) {

	    var email = '';
	    var displayName = '';
	    var userId;

	    if(supportsLocalStorage()) {
		if(!window.localStorage.jitsiMeetId) {
		    window.localStorage.jitsiMeetId = generateUniqueId();
		    //console.log("generated id", window.localStorage.jitsiMeetId);
		}
		userId = window.localStorage.jitsiMeetId || '';
		email = window.localStorage.email || '';
		displayName = window.localStorage.displayname || '';
	    } else {
		//console.log("local storage is not supported");
		userId = generateUniqueId();
	    }

	    my.update = function() {
		var newDisplayName = Util.escapeHtml($('#setDisplayName').get(0).value);
		if(newDisplayName) {
		    displayName = newDisplayName;
		    connection.emuc.addDisplayNameToPresence(displayName);
		    window.localStorage.displayname = displayName;
		}

		var newEmail = Util.escapeHtml($('#setEmail').get(0).value);
		connection.emuc.addEmailToPresence(newEmail);
		email = newEmail;
		window.localStorage.email = newEmail;

		connection.emuc.sendPresence();
		Avatar.setUserAvatar(connection.emuc.myroomjid, email);
	    };

	    my.isVisible = function() {
		return $('#settingsmenu').is(':visible');
	    };

	    my.getUID = function() {
		return userId;
	    };

	    my.getEmail = function() {
		return email;
	    };

	    my.getDisplayName = function() {
		return displayName;
	    };

	    my.setDisplayName = function(newDisplayName) {
		displayName = newDisplayName;
		window.localStorage.displayname = displayName;
	    };

	    function supportsLocalStorage() {
		try {
		    return 'localStorage' in window && window.localStorage !== null;
		} catch (e) {
		    //console.log("localstorage is not supported");
		    return false;
		}
	    }

	    function generateUniqueId() {
		function _p8() {
		    return (Math.random().toString(16)+"000000000").substr(2,8);
		}
		return _p8() + _p8() + _p8() + _p8();
	    }

	    $(document).bind('displaynamechanged', function(event, peerJid, newDisplayName) {
	       if(peerJid === 'localVideoContainer' ||
		   peerJid === connection.emuc.myroomjid) {
		   SettingsMenu.setDisplayName(newDisplayName);
	       }
	    });

	    return my;
	}(SettingsMenu || {}));



	/**
	 * Calculates packet lost percent using the number of lost packets and the
	 * number of all packet.
	 * @param lostPackets the number of lost packets
	 * @param totalPackets the number of all packets.
	 * @returns {number} packet loss percent
	 */

	function calculatePacketLoss(lostPackets, totalPackets) {
	    if(!totalPackets || totalPackets <= 0 || !lostPackets || lostPackets <= 0)
		return 0;
	    return Math.round((lostPackets/totalPackets)*100);
	}

	/**
	 * Peer statistics data holder.
	 * @constructor
	 */

	function PeerStats()
	{
	    this.ssrc2Loss = {};
	    this.ssrc2AudioLevel = {};
	    this.ssrc2bitrate = {};
	    this.ssrc2resolution = {};
	}

	/**
	 * The bandwidth
	 * @type {{}}
	 */
	PeerStats.bandwidth = {};

	/**
	 * The bit rate
	 * @type {{}}
	 */
	PeerStats.bitrate = {};



	/**
	 * The packet loss rate
	 * @type {{}}
	 */
	PeerStats.packetLoss = null;

	/**
	 * Sets packets loss rate for given <tt>ssrc</tt> that blong to the peer
	 * represented by this instance.
	 * @param ssrc audio or video RTP stream SSRC.
	 * @param lossRate new packet loss rate value to be set.
	 */
	PeerStats.prototype.setSsrcLoss = function (ssrc, lossRate)
	{
	    this.ssrc2Loss[ssrc] = lossRate;
	};

	/**
	 * Sets resolution for given <tt>ssrc</tt> that belong to the peer
	 * represented by this instance.
	 * @param ssrc audio or video RTP stream SSRC.
	 * @param resolution new resolution value to be set.
	 */
	PeerStats.prototype.setSsrcResolution = function (ssrc, resolution)
	{
	    if(resolution === null && this.ssrc2resolution[ssrc])
	    {
		delete this.ssrc2resolution[ssrc];
	    }
	    else if(resolution !== null)
		this.ssrc2resolution[ssrc] = resolution;
	};

	/**
	 * Sets the bit rate for given <tt>ssrc</tt> that blong to the peer
	 * represented by this instance.
	 * @param ssrc audio or video RTP stream SSRC.
	 * @param bitrate new bitrate value to be set.
	 */
	PeerStats.prototype.setSsrcBitrate = function (ssrc, bitrate)
	{
	    if(this.ssrc2bitrate[ssrc])
	    {
		this.ssrc2bitrate[ssrc].download += bitrate.download;
		this.ssrc2bitrate[ssrc].upload += bitrate.upload;
	    }
	    else {
		this.ssrc2bitrate[ssrc] = bitrate;
	    }
	};

	/**
	 * Sets new audio level(input or output) for given <tt>ssrc</tt> that identifies
	 * the stream which belongs to the peer represented by this instance.
	 * @param ssrc RTP stream SSRC for which current audio level value will be
	 *        updated.
	 * @param audioLevel the new audio level value to be set. Value is truncated to
	 *        fit the range from 0 to 1.
	 */
	PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel)
	{
	    // Range limit 0 - 1
	    this.ssrc2AudioLevel[ssrc] = Math.min(Math.max(audioLevel, 0), 1);
	};

	/**
	 * Array with the transport information.
	 * @type {Array}
	 */
	PeerStats.transport = [];


	/**
	 * <tt>StatsCollector</tt> registers for stats updates of given
	 * <tt>peerconnection</tt> in given <tt>interval</tt>. On each update particular
	 * stats are extracted and put in {@link PeerStats} objects. Once the processing
	 * is done <tt>audioLevelsUpdateCallback</tt> is called with <tt>this</tt>
	 * instance as an event source.
	 *
	 * @param peerconnection webRTC peer connection object.
	 * @param interval stats refresh interval given in ms.
	 * @param {function(StatsCollector)} audioLevelsUpdateCallback the callback
	 * called on stats update.
	 * @constructor
	 */
	function StatsCollector(peerconnection, audioLevelsInterval,
				audioLevelsUpdateCallback, statsInterval,
				statsUpdateCallback)
	{
	    this.peerconnection = peerconnection;
	    this.baselineAudioLevelsReport = null;
	    this.currentAudioLevelsReport = null;
	    this.currentStatsReport = null;
	    this.baselineStatsReport = null;
	    this.audioLevelsIntervalId = null;

	    /**
	     * Gather PeerConnection stats once every this many milliseconds.
	     */
	    this.GATHER_INTERVAL = 10000;

	    /**
	     * Log stats via the focus once every this many milliseconds.
	     */
	    this.LOG_INTERVAL = 60000;

	    /**
	     * Gather stats and store them in this.statsToBeLogged.
	     */
	    this.gatherStatsIntervalId = null;

	    /**
	     * Send the stats already saved in this.statsToBeLogged to be logged via
	     * the focus.
	     */
	    this.logStatsIntervalId = null;

	    /**
	     * Stores the statistics which will be send to the focus to be logged.
	     */
	    this.statsToBeLogged =
	    {
	      timestamps: [],
	      stats: {}
	    };

	    // Updates stats interval
	    this.audioLevelsIntervalMilis = audioLevelsInterval;

	    this.statsIntervalId = null;
	    this.statsIntervalMilis = statsInterval;
	    // Map of jids to PeerStats
	    this.jid2stats = {};

	    this.audioLevelsUpdateCallback = audioLevelsUpdateCallback;
	    this.statsUpdateCallback = statsUpdateCallback;
	}

	/**
	 * Stops stats updates.
	 */
	StatsCollector.prototype.stop = function ()
	{
	    if (this.audioLevelsIntervalId)
	    {
		clearInterval(this.audioLevelsIntervalId);
		this.audioLevelsIntervalId = null;
		clearInterval(this.statsIntervalId);
		this.statsIntervalId = null;
		clearInterval(this.logStatsIntervalId);
		this.logStatsIntervalId = null;
		clearInterval(this.gatherStatsIntervalId);
		this.gatherStatsIntervalId = null;
	    }
	};

	/**
	 * Callback passed to <tt>getStats</tt> method.
	 * @param error an error that occurred on <tt>getStats</tt> call.
	 */
	StatsCollector.prototype.errorCallback = function (error)
	{
	    console.error("Get stats error", error);
	    this.stop();
	};

	/**
	 * Starts stats updates.
	 */
	StatsCollector.prototype.start = function ()
	{
	    var self = this;
	    this.audioLevelsIntervalId = setInterval(
		function ()
		{
		    // Interval updates
		    self.peerconnection.getStats(
			function (report)
			{
			    var results = null;
			    if(!report || !report.result || typeof report.result != 'function')
			    {
				results = report;
			    }
			    else
			    {
				results = report.result();
			    }
			    //console.error("Got interval report", results);
			    self.currentAudioLevelsReport = results;
			    self.processAudioLevelReport();
			    self.baselineAudioLevelsReport =
				self.currentAudioLevelsReport;
			},
			self.errorCallback
		    );
		},
		self.audioLevelsIntervalMilis
	    );

	    this.statsIntervalId = setInterval(
		function () {
		    // Interval updates
		    self.peerconnection.getStats(
			function (report)
			{
			    var results = null;
			    if(!report || !report.result || typeof report.result != 'function')
			    {
				//firefox
				results = report;
			    }
			    else
			    {
				//chrome
				results = report.result();
			    }
			    //console.error("Got interval report", results);
			    self.currentStatsReport = results;
			    try
			    {
				self.processStatsReport();
			    }
			    catch(e)
			    {
				console.error("Unsupported key:" + e);
			    }

			    self.baselineStatsReport = self.currentStatsReport;
			},
			self.errorCallback
		    );
		},
		self.statsIntervalMilis
	    );

	    if (config.logStats) {
		this.gatherStatsIntervalId = setInterval(
		    function () {
			self.peerconnection.getStats(
			    function (report) {
				self.addStatsToBeLogged(report.result());
			    },
			    function () {
			    }
			);
		    },
		    this.GATHER_INTERVAL
		);

		this.logStatsIntervalId = setInterval(
		    function() { self.logStats(); },
		    this.LOG_INTERVAL);
	    }
	};

	/**
	 * Converts the stats to the format used for logging, and saves the data in
	 * this.statsToBeLogged.
	 * @param reports Reports as given by webkitRTCPerConnection.getStats.
	 */
	StatsCollector.prototype.addStatsToBeLogged = function (reports) {
	    var self = this;
	    var num_records = this.statsToBeLogged.timestamps.length;
	    this.statsToBeLogged.timestamps.push(new Date().getTime());
	    reports.map(function (report) {
		var stat = self.statsToBeLogged.stats[report.id];
		if (!stat) {
		    stat = self.statsToBeLogged.stats[report.id] = {};
		}
		stat.type = report.type;
		report.names().map(function (name) {
		    var values = stat[name];
		    if (!values) {
			values = stat[name] = [];
		    }
		    while (values.length < num_records) {
			values.push(null);
		    }
		    values.push(report.stat(name));
		});
	    });
	};

	StatsCollector.prototype.logStats = function () {
	    if (!focusJid) {
		return;
	    }

	    var deflate = true;

	    var content = JSON.stringify(this.statsToBeLogged);
	    if (deflate) {
		content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
	    }
	    content = Base64.encode(content);

	    // XEP-0337-ish
	    var message = $msg({to: focusJid, type: 'normal'});
	    message.c('log', { xmlns: 'urn:xmpp:eventlog',
			       id: 'PeerConnectionStats'});
	    message.c('message').t(content).up();
	    if (deflate) {
		message.c('tag', {name: "deflated", value: "true"}).up();
	    }
	    message.up();

	    connection.send(message);

	    // Reset the stats
	    this.statsToBeLogged.stats = {};
	    this.statsToBeLogged.timestamps = [];
	};
	var keyMap = {
	    "firefox": {
		"ssrc": "ssrc",
		"packetsReceived": "packetsReceived",
		"packetsLost": "packetsLost",
		"packetsSent": "packetsSent",
		"bytesReceived": "bytesReceived",
		"bytesSent": "bytesSent"
	    },
	    "chrome": {
		"receiveBandwidth": "googAvailableReceiveBandwidth",
		"sendBandwidth": "googAvailableSendBandwidth",
		"remoteAddress": "googRemoteAddress",
		"transportType": "googTransportType",
		"localAddress": "googLocalAddress",
		"activeConnection": "googActiveConnection",
		"ssrc": "ssrc",
		"packetsReceived": "packetsReceived",
		"packetsSent": "packetsSent",
		"packetsLost": "packetsLost",
		"bytesReceived": "bytesReceived",
		"bytesSent": "bytesSent",
		"googFrameHeightReceived": "googFrameHeightReceived",
		"googFrameWidthReceived": "googFrameWidthReceived",
		"googFrameHeightSent": "googFrameHeightSent",
		"googFrameWidthSent": "googFrameWidthSent",
		"audioInputLevel": "audioInputLevel",
		"audioOutputLevel": "audioOutputLevel"
	    }
	};

	/**
	 * Stats processing logic.
	 */
	StatsCollector.prototype.processStatsReport = function () {
	    if (!this.baselineStatsReport) {
		return;
	    }

	    for (var idx in this.currentStatsReport) {
		var now = this.currentStatsReport[idx];
		try {
		    if (getStatValue(now, 'receiveBandwidth') ||
			getStatValue(now, 'sendBandwidth')) {
			PeerStats.bandwidth = {
			    "download": Math.round(
				    (getStatValue(now, 'receiveBandwidth')) / 1000),
			    "upload": Math.round(
				    (getStatValue(now, 'sendBandwidth')) / 1000)
			};
		    }
		}
		catch(e){/*not supported*/}

		if(now.type == 'googCandidatePair')
		{
		    var ip, type, localIP, active;
		    try {
			ip = getStatValue(now, 'remoteAddress');
			type = getStatValue(now, "transportType");
			localIP = getStatValue(now, "localAddress");
			active = getStatValue(now, "activeConnection");
		    }
		    catch(e){/*not supported*/}
		    if(!ip || !type || !localIP || active != "true")
			continue;
		    var addressSaved = false;
		    for(var i = 0; i < PeerStats.transport.length; i++)
		    {
			if(PeerStats.transport[i].ip == ip &&
			    PeerStats.transport[i].type == type &&
			    PeerStats.transport[i].localip == localIP)
			{
			    addressSaved = true;
			}
		    }
		    if(addressSaved)
			continue;
		    PeerStats.transport.push({localip: localIP, ip: ip, type: type});
		    continue;
		}

		if(now.type == "candidatepair")
		{
		    if(now.state == "succeeded")
			continue;

		    var local = this.currentStatsReport[now.localCandidateId];
		    var remote = this.currentStatsReport[now.remoteCandidateId];
		    PeerStats.transport.push({localip: local.ipAddress + ":" + local.portNumber,
			ip: remote.ipAddress + ":" + remote.portNumber, type: local.transport});

		}

		if (now.type != 'ssrc' && now.type != "outboundrtp" &&
		    now.type != "inboundrtp") {
		    continue;
		}

		var before = this.baselineStatsReport[idx];
		if (!before) {
		    //console.warn(getStatValue(now, 'ssrc') + ' not enough data');
		    continue;
		}

		var ssrc = getStatValue(now, 'ssrc');
		if(!ssrc)
		    continue;
		var jid = ssrc2jid[ssrc];
		if (!jid) {
		    console.warn("No jid for ssrc: " + ssrc);
		    continue;
		}

		var jidStats = this.jid2stats[jid];
		if (!jidStats) {
		    jidStats = new PeerStats();
		    this.jid2stats[jid] = jidStats;
		}


		var isDownloadStream = true;
		var key = 'packetsReceived';
		if (!getStatValue(now, key))
		{
		    isDownloadStream = false;
		    key = 'packetsSent';
		    if (!getStatValue(now, key))
		    {
			console.warn("No packetsReceived nor packetSent stat found");
			continue;
		    }
		}
		var packetsNow = getStatValue(now, key);
		if(!packetsNow || packetsNow < 0)
		    packetsNow = 0;

		var packetsBefore = getStatValue(before, key);
		if(!packetsBefore || packetsBefore < 0)
		    packetsBefore = 0;
		var packetRate = packetsNow - packetsBefore;
		if(!packetRate || packetRate < 0)
		    packetRate = 0;
		var currentLoss = getStatValue(now, 'packetsLost');
		if(!currentLoss || currentLoss < 0)
		    currentLoss = 0;
		var previousLoss = getStatValue(before, 'packetsLost');
		if(!previousLoss || previousLoss < 0)
		    previousLoss = 0;
		var lossRate = currentLoss - previousLoss;
		if(!lossRate || lossRate < 0)
		    lossRate = 0;
		var packetsTotal = (packetRate + lossRate);

		jidStats.setSsrcLoss(ssrc,
		    {"packetsTotal": packetsTotal,
			"packetsLost": lossRate,
			"isDownloadStream": isDownloadStream});


		var bytesReceived = 0, bytesSent = 0;
		if(getStatValue(now, "bytesReceived"))
		{
		    bytesReceived = getStatValue(now, "bytesReceived") -
			getStatValue(before, "bytesReceived");
		}

		if(getStatValue(now, "bytesSent"))
		{
		    bytesSent = getStatValue(now, "bytesSent") -
			getStatValue(before, "bytesSent");
		}

		var time = Math.round((now.timestamp - before.timestamp) / 1000);
		if(bytesReceived <= 0 || time <= 0)
		{
		    bytesReceived = 0;
		}
		else
		{
		    bytesReceived = Math.round(((bytesReceived * 8) / time) / 1000);
		}

		if(bytesSent <= 0 || time <= 0)
		{
		    bytesSent = 0;
		}
		else
		{
		    bytesSent = Math.round(((bytesSent * 8) / time) / 1000);
		}

		jidStats.setSsrcBitrate(ssrc, {
		    "download": bytesReceived,
		    "upload": bytesSent});

		var resolution = {height: null, width: null};
		try {
		    if (getStatValue(now, "googFrameHeightReceived") &&
			getStatValue(now, "googFrameWidthReceived")) {
			resolution.height = getStatValue(now, "googFrameHeightReceived");
			resolution.width = getStatValue(now, "googFrameWidthReceived");
		    }
		    else if (getStatValue(now, "googFrameHeightSent") &&
			getStatValue(now, "googFrameWidthSent")) {
			resolution.height = getStatValue(now, "googFrameHeightSent");
			resolution.width = getStatValue(now, "googFrameWidthSent");
		    }
		}
		catch(e){/*not supported*/}

		if(resolution.height && resolution.width)
		{
		    jidStats.setSsrcResolution(ssrc, resolution);
		}
		else
		{
		    jidStats.setSsrcResolution(ssrc, null);
		}


	    }

	    var self = this;
	    // Jid stats
	    var totalPackets = {download: 0, upload: 0};
	    var lostPackets = {download: 0, upload: 0};
	    var bitrateDownload = 0;
	    var bitrateUpload = 0;
	    var resolutions = {};
	    Object.keys(this.jid2stats).forEach(
		function (jid)
		{
		    Object.keys(self.jid2stats[jid].ssrc2Loss).forEach(
			function (ssrc)
			{
			    var type = "upload";
			    if(self.jid2stats[jid].ssrc2Loss[ssrc].isDownloadStream)
				type = "download";
			    totalPackets[type] +=
				self.jid2stats[jid].ssrc2Loss[ssrc].packetsTotal;
			    lostPackets[type] +=
				self.jid2stats[jid].ssrc2Loss[ssrc].packetsLost;
			}
		    );
		    Object.keys(self.jid2stats[jid].ssrc2bitrate).forEach(
			function (ssrc) {
			    bitrateDownload +=
				self.jid2stats[jid].ssrc2bitrate[ssrc].download;
			    bitrateUpload +=
				self.jid2stats[jid].ssrc2bitrate[ssrc].upload;

			    delete self.jid2stats[jid].ssrc2bitrate[ssrc];
			}
		    );
		    resolutions[jid] = self.jid2stats[jid].ssrc2resolution;
		}
	    );

	    PeerStats.bitrate = {"upload": bitrateUpload, "download": bitrateDownload};

	    PeerStats.packetLoss = {
		total:
		    calculatePacketLoss(lostPackets.download + lostPackets.upload,
			totalPackets.download + totalPackets.upload),
		download:
		    calculatePacketLoss(lostPackets.download, totalPackets.download),
		upload:
		    calculatePacketLoss(lostPackets.upload, totalPackets.upload)
	    };
	    this.statsUpdateCallback(
		{
		    "bitrate": PeerStats.bitrate,
		    "packetLoss": PeerStats.packetLoss,
		    "bandwidth": PeerStats.bandwidth,
		    "resolution": resolutions,
		    "transport": PeerStats.transport
		});
	    PeerStats.transport = [];

	};

	/**
	 * Stats processing logic.
	 */
	StatsCollector.prototype.processAudioLevelReport = function ()
	{
	    if (!this.baselineAudioLevelsReport)
	    {
		return;
	    }

	    for (var idx in this.currentAudioLevelsReport)
	    {
		var now = this.currentAudioLevelsReport[idx];

		if (now.type != 'ssrc')
		{
		    continue;
		}

		var before = this.baselineAudioLevelsReport[idx];
		if (!before)
		{
		   //console.warn(getStatValue(now, 'ssrc') + ' not enough data');
		    continue;
		}

		var ssrc = getStatValue(now, 'ssrc');
		var jid = ssrc2jid[ssrc];
		if (!jid)
		{
		    console.warn("No jid for ssrc: " + ssrc);
		    continue;
		}

		var jidStats = this.jid2stats[jid];
		if (!jidStats)
		{
		    jidStats = new PeerStats();
		    this.jid2stats[jid] = jidStats;
		}

		// Audio level
		var audioLevel = null;

		try {
		    audioLevel = getStatValue(now, 'audioInputLevel');
		    if (!audioLevel)
			audioLevel = getStatValue(now, 'audioOutputLevel');
		}
		catch(e) {/*not supported*/
		    console.warn("Audio Levels are not available in the statistics.");
		    clearInterval(this.audioLevelsIntervalId);
		    return;
		}

		if (audioLevel)
		{
		    // TODO: can't find specs about what this value really is,
		    // but it seems to vary between 0 and around 32k.
		    audioLevel = audioLevel / 32767;
		    jidStats.setSsrcAudioLevel(ssrc, audioLevel);
		    if(jid != connection.emuc.myroomjid)
			this.audioLevelsUpdateCallback(jid, audioLevel);
		}

	    }


	};

	function getStatValue(item, name) {
	    if(!keyMap[RTC.browser][name])
		throw "The property isn't supported!";
	    var key = keyMap[RTC.browser][name];
	    return RTC.browser == "chrome"? item.stat(key) : item[key];
	}


	/**
	 * Strophe.ofmuc plugin
	 */


	Strophe.addConnectionPlugin('ofmuc', {
	    connection: null,
	    roomJid: null,
	    members: {},

	    init: function (conn) {
		this.connection = conn;
		this.connection.addHandler(this.onMessage.bind(this), null, 'message'); 
		this.connection.addHandler(this.onPresence.bind(this), null, 'presence');        
		this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable'); 
        	this.connection.addHandler(this.onRayo.bind(this), 'urn:xmpp:rayo:1');  		
	    },   

	    onPresence: function (pres) {
		//console.log('ofmuc onPresence', $(pres))  

		var from = pres.getAttribute('from');
		var type = pres.getAttribute('type');

		if (type != null) {
		   return true;
		}

		if (!this.roomJid || Strophe.getBareJidFromJid(from) != this.roomJid) return true;        

		var member = {};
		member.show = $(pres).find('>show').text();
		member.status = $(pres).find('>status').text();
		var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
		member.affiliation = tmp.attr('affiliation');
		member.role = tmp.attr('role');

		if (from == this.roomJid) {

		} else if (this.members[from] === undefined) {
		    this.members[from] = member;

		    if (config.userAvatar && config.userAvatar != "null")
		    {
			this.avatarShare(config.userAvatar);
		    }           
		}
		return true;
	    },

	    onPresenceUnavailable: function (pres) {
		//console.log('onPresenceUnavailable', $(pres));

		var from = pres.getAttribute('from');
		if (!this.roomJid) return true;  

		delete this.members[from];
		return true;
	    },   

	    onMessage: function (msg) {
		//console.log('onMessage', $(msg))
		var that = this;
		var from = msg.getAttribute('from');
		var farparty = SettingsMenu.getDisplayName();

		if (!farparty) farparty = Strophe.getResourceFromJid(from); 	
		if (!that.roomJid) that.roomJid = Strophe.getBareJidFromJid(from);        


		$(msg).find('avatarshare').each(function() 
		{
			that.members[from].avatar = $(this).text();	
			Avatar.setUserAvatar(from);
		});

		return true;
	    },

	    onRayo: function (packet) 
	    {
		//console.log("onRayo", packet);
		var from = $(packet).attr('from');

		var jid = null;
		var videoSpanId = null;
		var node = null;

		$(packet).find('header').each(function() 
		{		
			var name = $(this).attr('name');
			var value = $(this).attr('value');

			//console.log("onRayo header", name, value);

			if (name == "caller_id")
			{	
				if (value.indexOf("@") > -1)
				{
					var callerId = value.substring(4); // remove sip:

					node = Strophe.getNodeFromJid(callerId);
					jid = callerId + "/" + node;
				} else {
					node = value;
					jid = node + "@" + config.hosts.domain + "/" + node
				}
				videoSpanId = 'participant_' + node;
			}

		});	

		$(packet).find('answered').each(function() 
		{	
			var callId = Strophe.getNodeFromJid(from); 

			//console.log("onRayo callid", callId, jid);

			if (jid)
			{
				VideoLayout.ensurePeerContainerExists(jid);	
				var container = document.getElementById(videoSpanId);

				if (container) 
				{	
					$(container).show();			
					$(container).attr("title", Strophe.getBareJidFromJid(jid));
				}
			}
		});

		$(packet).find('hangup').each(function() 
		{	
			var callId = Strophe.getNodeFromJid(from); 

			//console.log("onRayo callid", callId, jid);	

			if (jid) 
			{
				var container = document.getElementById(videoSpanId);

				if (container) 
				{
				    VideoLayout.removeConnectionIndicator(jid);
				    // hide here, wait for video to close before removing
				    $(container).hide();
				    VideoLayout.resizeThumbnails();
				}
			}		
		});	

		return true;
	    },
	    
	    avatarShare: function(avatar) {
		//console.log("ofmuc.avatarShare", avatar)
		var msg = $msg({to: this.roomJid, type: 'groupchat'});
		msg.c('avatarshare', {xmlns: 'http://igniterealtime.org/protocol/avatarshare'}).t(avatar).up();
		this.connection.send(msg);        
	    }       
	});


	/**
	 * ofmeet-screenshare
	 */

	/**
	 * Indicates that desktop stream is currently in use(for toggle purpose).
	 * @type {boolean}
	 */
	var isUsingScreenStream = false;
	/**
	 * Indicates that switch stream operation is in progress and prevent from triggering new events.
	 * @type {boolean}
	 */
	var switchInProgress = false;

	/**
	 * Method used to get screen sharing stream.
	 *
	 * @type {function (stream_callback, failure_callback}
	 */
	var obtainDesktopStream = null;

	/**
	 * Flag used to cache desktop sharing enabled state. Do not use directly as it can be <tt>null</tt>.
	 * @type {null|boolean}
	 */
	var _desktopSharingEnabled = null;

	/**
	 * Method obtains desktop stream from WebRTC 'screen' source.
	 * Flag 'chrome://flags/#enable-usermedia-screen-capture' must be enabled.
	 */
	function obtainWebRTCScreen(streamCallback, failCallback) {
	    getUserMediaWithConstraints(
		['screen'],
		streamCallback,
		failCallback
	    );
	}

	/**
	 * Constructs inline install URL for Chrome desktop streaming extension.
	 * The 'chromeExtensionId' must be defined in config.js.
	 * @returns {string}
	 */
	function getWebStoreInstallUrl()
	{
	    return "https://chrome.google.com/webstore/detail/" + config.chromeExtensionId;
	}

	/**
	 * Checks whether extension update is required.
	 * @param minVersion minimal required version
	 * @param extVersion current extension version
	 * @returns {boolean}
	 */
	function isUpdateRequired(minVersion, extVersion)
	{
	    try
	    {
		var s1 = minVersion.split('.');
		var s2 = extVersion.split('.');

		var len = Math.max(s1.length, s2.length);
		for (var i = 0; i < len; i++)
		{
		    var n1 = 0,
			n2 = 0;

		    if (i < s1.length)
			n1 = parseInt(s1[i]);
		    if (i < s2.length)
			n2 = parseInt(s2[i]);

		    if (isNaN(n1) || isNaN(n2))
		    {
			return true;
		    }
		    else if (n1 !== n2)
		    {
			return n1 > n2;
		    }
		}

		// will happen if boths version has identical numbers in
		// their components (even if one of them is longer, has more components)
		return false;
	    }
	    catch (e)
	    {
		console.error("Failed to parse extension version", e);
		messageHandler.showError('Error',
		    'Error when trying to detect desktopsharing extension.');
		return true;
	    }
	}


	function checkExtInstalled(isInstalledCallback) 
	{
		isInstalledCallback($('#ofmeet-extension-installed').length > 0);  
	}

	function doGetStreamFromExtension(streamCallback, failCallback) 
	{
	    var pending = window.setTimeout(

		function () {
			failCallback("Extension failed to get the stream");
		}, 1000);

	    window.postMessage({ type: 'ofmeetGetScreen', id: pending }, '*');

	    window.addEventListener('message', function (event) 
	    {
		if(event.origin != window.location.origin)
			return;

		if(event.data.type == 'ofmeetGotScreen') 
		{
			if (event.data.sourceId === '') 
			{
				// user canceled
				failCallback("Extension failed to get the stream");

			} else {

				if (event.data.sourceId) 
				{
					getUserMediaWithConstraints(['desktop'],

						function (stream) {
							streamCallback(stream);
						},
						failCallback,
						null, null, null,
						event.data.sourceId);
				} else {
					failCallback("Extension failed to get the stream from ofmeet extension");
				}
			}

		} else if (event.data.type == 'ofmeetGetScreenPending') {
			window.clearTimeout(event.data.id);
		}		
	    });    
	}
	/**
	 * Asks Chrome extension to call chooseDesktopMedia and gets chrome 'desktop' stream for returned stream token.
	 */
	function obtainScreenFromExtension(streamCallback, failCallback) {
	    checkExtInstalled(
		function (isInstalled) {
		    if (isInstalled) {
			doGetStreamFromExtension(streamCallback, failCallback);
		    } else {
			window.open(getWebStoreInstallUrl(), "_blank");
			messageHandler.showError('Error', 'Install manually and reload webpage');
		    }
		}
	    );
	}

	/**
	 * @returns {boolean} <tt>true</tt> if desktop sharing feature is available and enabled.
	 */
	function isDesktopSharingEnabled() {
	    if (_desktopSharingEnabled === null) {
		if (obtainDesktopStream === obtainScreenFromExtension) {
		    // Parse chrome version
		    var userAgent = navigator.userAgent.toLowerCase();
		    // We can assume that user agent is chrome, because it's enforced when 'ext' streaming method is set
		    var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10);
		    //console.log("Chrome version" + userAgent, ver);
		    _desktopSharingEnabled = ver >= 34;
		} else {
		    _desktopSharingEnabled = obtainDesktopStream === obtainWebRTCScreen;
		}
	    }
	    return _desktopSharingEnabled;
	}

	/**
	 * Call this method to toggle desktop sharing feature.
	 * @param method pass "ext" to use chrome extension for desktop capture(chrome extension required),
	 *        pass "webrtc" to use WebRTC "screen" desktop source('chrome://flags/#enable-usermedia-screen-capture'
	 *        must be enabled), pass any other string or nothing in order to disable this feature completely.
	 */
	function setDesktopSharing(method) {
	    // Check if we are running chrome
	    if (!navigator.webkitGetUserMedia) {
		obtainDesktopStream = null;
		//console.info("Desktop sharing disabled");
	    } else if (method == "ext") {
		obtainDesktopStream = obtainScreenFromExtension;
		//console.info("Using Chrome extension for desktop sharing");
	    } else if (method == "webrtc") {
		obtainDesktopStream = obtainWebRTCScreen;
		//console.info("Using Chrome WebRTC for desktop sharing");
	    }

	    // Reset enabled cache
	    _desktopSharingEnabled = null;
	}

	/**
	 * Initializes <link rel=chrome-webstore-item /> with extension id set in config.js to support inline installs.
	 * Host site must be selected as main website of published extension.
	 */
	function initInlineInstalls()
	{
	    $("link[rel=chrome-webstore-item]").attr("href", getWebStoreInstallUrl());
	}

	function getSwitchStreamFailed(error) {
	    console.error("Failed to obtain the stream to switch to", error);
	    switchInProgress = false;
	}

	function streamSwitchDone() {
	    //window.setTimeout(
	    //    function () {
	    switchInProgress = false;
	    Toolbar.changeDesktopSharingButtonState(isUsingScreenStream);
	    //    }, 100
	    //);
	}

	function newStreamCreated(stream) {

	    var oldStream = connection.jingle.localVideo;

	    connection.jingle.localVideo = stream;

	    VideoLayout.changeLocalVideo(stream, !isUsingScreenStream);

	    var conferenceHandler = getConferenceHandler();
	    if (conferenceHandler) {
		// FIXME: will block switchInProgress on true value in case of exception
		conferenceHandler.switchStreams(stream, oldStream, streamSwitchDone);
	    } else {
		// We are done immediately
		console.error("No conference handler");
		messageHandler.showError('Error',
		    'Unable to switch video stream.');
		streamSwitchDone();
	    }
	}


	/* a simple MUC connection plugin
	 * can only handle a single MUC room
	 */

	Strophe.addConnectionPlugin('emuc', {
	    connection: null,
	    roomjid: null,
	    myroomjid: null,
	    members: {},
	    list_members: [], // so we can elect a new focus
	    presMap: {},
	    preziMap: {},
	    joined: false,
	    isOwner: false,
	    role: null,
	    init: function (conn) {
		this.connection = conn;
	    },
	    initPresenceMap: function (myroomjid) {
		this.presMap['to'] = myroomjid;
		this.presMap['xns'] = 'http://jabber.org/protocol/muc';
	    },
	    doJoin: function (jid, password) {
		this.myroomjid = jid;

		//console.info("Joined MUC as " + this.myroomjid);

		this.initPresenceMap(this.myroomjid);

		if (!this.roomjid) {
		    this.roomjid = Strophe.getBareJidFromJid(jid);
		    // add handlers (just once)
		    this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true});
		    this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true});
		    this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true});
		    this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true});
		}
		if (password !== undefined) {
		    this.presMap['password'] = password;
		}
		this.sendPresence();
	    },
	    doLeave: function() {
		//console.log("do leave", this.myroomjid);
		var pres = $pres({to: this.myroomjid, type: 'unavailable' });
		this.presMap.length = 0;
		this.connection.send(pres);
	    },
	    onPresence: function (pres) {
		var from = pres.getAttribute('from');
		var type = pres.getAttribute('type');
		if (type != null) {
		    return true;
		}

		// Parse etherpad tag.
		var etherpad = $(pres).find('>etherpad');
		if (etherpad.length) {
		    $(document).trigger('etherpadadded.muc', [from, etherpad.text()]);
		}

		// Parse prezi tag.
		var presentation = $(pres).find('>prezi');
		if (presentation.length)
		{
		    var url = presentation.attr('url');
		    var current = presentation.find('>current').text();

		    //console.log('presentation info received from', from, url);

		    if (this.preziMap[from] == null) {
			this.preziMap[from] = url;

			$(document).trigger('presentationadded.muc', [from, url, current]);
		    }
		    else {
			$(document).trigger('gotoslide.muc', [from, url, current]);
		    }
		}
		else if (this.preziMap[from] != null) {
		    var url = this.preziMap[from];
		    delete this.preziMap[from];
		    $(document).trigger('presentationremoved.muc', [from, url]);
		}

		// Parse audio info tag.
		var audioMuted = $(pres).find('>audiomuted');
		if (audioMuted.length) {
		    $(document).trigger('audiomuted.muc', [from, audioMuted.text()]);
		}

		// Parse video info tag.
		var videoMuted = $(pres).find('>videomuted');
		if (videoMuted.length) {
		    $(document).trigger('videomuted.muc', [from, videoMuted.text()]);
		}

		var stats = $(pres).find('>stats');
		if(stats.length)
		{
		    var statsObj = {};
		    Strophe.forEachChild(stats[0], "stat", function (el) {
			statsObj[el.getAttribute("name")] = el.getAttribute("value");
		    });
		    ConnectionQuality.updateRemoteStats(from, statsObj);
		}

		// Parse status.
		if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
		    // http://xmpp.org/extensions/xep-0045.html#createroom-instant
		    this.isOwner = true;
		    var create = $iq({type: 'set', to: this.roomjid})
			    .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
			    .c('x', {xmlns: 'jabber:x:data', type: 'submit'});
		    this.connection.sendIQ(create); // fire away
		}

		// Parse roles.
		var member = {};
		member.show = $(pres).find('>show').text();
		member.status = $(pres).find('>status').text();
		var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
		member.affiliation = tmp.attr('affiliation');
		member.role = tmp.attr('role');

		// Focus recognition
		member.jid = tmp.attr('jid');
		member.isFocus = false;
		if (member.jid
		    && member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) {
		    member.isFocus = true;
		}

		var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
		member.displayName = (nicktag.length > 0 ? nicktag.text() : null);

		if (from == this.myroomjid) {
		    if (member.affiliation == 'owner') this.isOwner = true;
		    if (this.role !== member.role) {
			this.role = member.role;
			$(document).trigger('local.role.changed.muc', [from, member, pres]);
		    }
		    if (!this.joined) {
			this.joined = true;
			$(document).trigger('joined.muc', [from, member]);
			this.list_members.push(from);
		    }
		} else if (this.members[from] === undefined) {
		    // new participant
		    this.members[from] = member;
		    this.list_members.push(from);
		    $(document).trigger('entered.muc', [from, member, pres]);
		} else {
		    // Presence update for existing participant
		    // Watch role change:
		    if (this.members[from].role != member.role) {
			this.members[from].role = member.role
			$(document).trigger('role.changed.muc', [from, member, pres]);
		    }
		}
		// Always trigger presence to update bindings
		//console.log('presence change from', from, pres);
		$(document).trigger('presence.muc', [from, member, pres]);

		// Trigger status message update
		if (member.status) {
		    $(document).trigger('presence.status.muc', [from, member, pres]);
		}

		return true;
	    },
	    onPresenceUnavailable: function (pres) {
		var from = pres.getAttribute('from');
		// Status code 110 indicates that this notification is "self-presence".
		if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
		    delete this.members[from];
		    this.list_members.splice(this.list_members.indexOf(from), 1);
		    $(document).trigger('left.muc', [from]);
		}
		// If the status code is 110 this means we're leaving and we would like
		// to remove everyone else from our view, so we trigger the event.
		else if (this.list_members.length > 1) {
		    for (var i = 0; i < this.list_members.length; i++) {
			var member = this.list_members[i];
			delete this.members[i];
			this.list_members.splice(i, 1);
			$(document).trigger('left.muc', member);
		    }
		}
		if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
		    $(document).trigger('kicked.muc', [from]);
		}
		return true;
	    },
	    onPresenceError: function (pres) {
		var from = pres.getAttribute('from');
		if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
		    $(document).trigger('passwordrequired.muc', [from]);
		} else if ($(pres).find(
			'>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
		    var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
		    if(toDomain === config.hosts.anonymousdomain) {
			// we are connected with anonymous domain and only non anonymous users can create rooms
			// we must authorize the user
			$(document).trigger('passwordrequired.main');
		    } else {
			console.warn('onPresError ', pres);
			messageHandler.openReportDialog(null,
			    'Oops! Something went wrong and we couldn`t connect to the conference.',
			pres);
		    }
		} else {
		    console.warn('onPresError ', pres);
		    messageHandler.openReportDialog(null,
			'Oops! Something went wrong and we couldn`t connect to the conference.',
			pres);
		}
		return true;
	    },
	    sendMessage: function (body, nickname) {
		var msg = $msg({to: this.roomjid, type: 'groupchat'});
		msg.c('body', body).up();
		
		if (nickname) {
		    msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
		}
		this.connection.send(msg);
	    },
	    setSubject: function (subject){
		var msg = $msg({to: this.roomjid, type: 'groupchat'});
		msg.c('subject', subject);
		this.connection.send(msg);
		//console.log("topic changed to " + subject);
	    },
	    onMessage: function (msg) {
		// FIXME: this is a hack. but jingle on muc makes nickchanges hard
		var from = msg.getAttribute('from');
		var nick = $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]').text() || Strophe.getResourceFromJid(from);

		var txt = $(msg).find('>body').text();
		var type = msg.getAttribute("type");

		if(type == "error")
		{
		    //Chat.chatAddError($(msg).find('>text').text(), txt);
		    return true;
		}

		var subject = $(msg).find('>subject');

		if(subject.length)
		{
		    var subjectText = subject.text();
		    
		    if(subjectText || subjectText == "") 
		    {
			$(document).trigger('message.subject.muc', [from, nick, subjectText]);
		    }
		}

		if (txt) 
		{
			$(document).trigger('message.body.muc', [from, nick, txt]);
		}
		return true;
	    },
	    lockRoom: function (key) {
		//http://xmpp.org/extensions/xep-0045.html#roomconfig
		var ob = this;
		this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
		    function (res) {
			if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
			    var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
			    formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
			    formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
			    formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
			    // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
			    formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
			    // FIXME: is muc#roomconfig_passwordprotectedroom required?
			    this.connection.sendIQ(formsubmit,
				function (res) {
				    // password is required
				    if (sharedKey)
				    {
					//console.log('set room password');
					Toolbar.lockLockButton();
				    }
				    else
				    {
					//console.log('removed room password');
					Toolbar.unlockLockButton();
				    }
				},
				function (err) {
				    console.warn('setting password failed', err);
				    messageHandler.showError('Lock failed',
					'Failed to lock conference.',
					err);
				    setSharedKey('');
				}
			    );
			} else {
			    console.warn('room passwords not supported');
			    messageHandler.showError('Warning',
				'Room passwords are currently not supported.');
			    setSharedKey('');

			}
		    },
		    function (err) {
			console.warn('setting password failed', err);
			messageHandler.showError('Lock failed',
			    'Failed to lock conference.',
			    err);
			setSharedKey('');
		    }
		);
	    },
	    kick: function (jid) {
		var kickIQ = $iq({to: this.roomjid, type: 'set'})
		    .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
		    .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
		    .c('reason').t('You have been kicked.').up().up().up();

		this.connection.sendIQ(
			kickIQ,
			function (result) {
			    //console.log('Kick participant with jid: ', jid, result);
			},
			function (error) {
			    //console.log('Kick participant error: ', error);
			});
	    },
	    sendPresence: function () {
		var pres = $pres({to: this.presMap['to'] });
		pres.c('x', {xmlns: this.presMap['xns']});

		if (this.presMap['password']) {
		    pres.c('password').t(this.presMap['password']).up();
		}

		pres.up();

		// Send XEP-0115 'c' stanza that contains our capabilities info
		if (connection.caps) {
		    connection.caps.node = config.clientNode;
		    pres.c('c', connection.caps.generateCapsAttrs()).up();
		}

		pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'})
		    .t(navigator.userAgent).up();

		if(this.presMap['bridgeIsDown']) {
		    pres.c('bridgeIsDown').up();
		}

		if(this.presMap['email']) {
		    pres.c('email').t(this.presMap['email']).up();
		}

		if(this.presMap['userId']) {
		    pres.c('userId').t(this.presMap['userId']).up();
		}

		if (this.presMap['displayName']) {
		    // XEP-0172
		    pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'})
			.t(this.presMap['displayName']).up();
		}

		if (this.presMap['audions']) {
		    pres.c('audiomuted', {xmlns: this.presMap['audions']})
			.t(this.presMap['audiomuted']).up();
		}

		if (this.presMap['videons']) {
		    pres.c('videomuted', {xmlns: this.presMap['videons']})
			.t(this.presMap['videomuted']).up();
		}

		if(this.presMap['statsns'])
		{
		    var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
		    for(var stat in this.presMap["stats"])
			if(this.presMap["stats"][stat] != null)
			    stats.c("stat",{name: stat, value: this.presMap["stats"][stat]}).up();
		    pres.up();
		}

		if (this.presMap['prezins']) {
		    pres.c('prezi',
			    {xmlns: this.presMap['prezins'],
			    'url': this.presMap['preziurl']})
			    .c('current').t(this.presMap['prezicurrent']).up().up();
		}

		if (this.presMap['etherpadns']) {
		    pres.c('etherpad', {xmlns: this.presMap['etherpadns']})
			.t(this.presMap['etherpadname']).up();
		}

		if (this.presMap['medians'])
		{
		    pres.c('media', {xmlns: this.presMap['medians']});
		    var sourceNumber = 0;
		    Object.keys(this.presMap).forEach(function (key) {
			if (key.indexOf('source') >= 0) {
			     sourceNumber++;
			}
		    });
		    if (sourceNumber > 0)
			for (var i = 1; i <= sourceNumber/3; i ++) {
			    pres.c('source',
				   {type: this.presMap['source' + i + '_type'],
				   ssrc: this.presMap['source' + i + '_ssrc'],
				   direction: this.presMap['source'+ i + '_direction']
							    || 'sendrecv' }
			    ).up();
			}
		}

		pres.up();
		connection.send(pres);
	    },
	    addDisplayNameToPresence: function (displayName) {
		this.presMap['displayName'] = displayName;
	    },
	    addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) {
		if (!this.presMap['medians'])
		    this.presMap['medians'] = 'http://estos.de/ns/mjs';

		this.presMap['source' + sourceNumber + '_type'] = mtype;
		this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
		this.presMap['source' + sourceNumber + '_direction'] = direction;
	    },
	    clearPresenceMedia: function () {
		var self = this;
		Object.keys(this.presMap).forEach( function(key) {
		    if(key.indexOf('source') != -1) {
			delete self.presMap[key];
		    }
		});
	    },
	    addCurrentSlideToPresence: function (currentSlide) {
		this.presMap['prezicurrent'] = currentSlide;
	    },
	    addAudioInfoToPresence: function(isMuted) {
		this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
		this.presMap['audiomuted'] = isMuted.toString();
	    },
	    addVideoInfoToPresence: function(isMuted) {
		this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
		this.presMap['videomuted'] = isMuted.toString();
	    },
	    addConnectionInfoToPresence: function(stats) {
		this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
		this.presMap['stats'] = stats;
	    },
	    findJidFromResource: function(resourceJid) {
		if(resourceJid && 
		    resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
		    return connection.emuc.myroomjid;
		}
		var peerJid = null;
		Object.keys(this.members).some(function (jid) {
		    peerJid = jid;
		    return Strophe.getResourceFromJid(jid) === resourceJid;
		});
		return peerJid;
	    },
	    addBridgeIsDownToPresence: function() {
		this.presMap['bridgeIsDown'] = true;
	    },
	    addEmailToPresence: function(email) {
		this.presMap['email'] = email;
	    },
	    addUserIdToPresence: function(userId) {
		this.presMap['userId'] = userId;
	    },
	    isModerator: function() {
		return this.role === 'moderator';
	    }
	});


	/**
	 * Moderator
	 *
	 * Contains logic responsible for enabling/disabling functionality available
	 * only to moderator users.
	 */
	var Moderator = (function (my) {

	    var focusUserJid;
	    var getNextTimeout = Util.createExpBackoffTimer(1000);
	    var getNextErrorTimeout = Util.createExpBackoffTimer(1000);

	    my.isModerator = function () {
		return connection.emuc.isModerator();
	    };

	    my.onModeratorStatusChanged = function (isModerator) {

		$(document).trigger('local.role.moderator', [isModerator]);
	    };

	    my.init = function () {
		$(document).bind(
		    'role.changed.muc',
		    function (event, jid, info, pres) {
			//console.info("Role changed for " + jid + ", new role: " + info.role);
			VideoLayout.showModeratorIndicator();
		    }
		);

		$(document).bind(
		    'local.role.changed.muc',
		    function (event, jid, info, pres) {
			//console.info("My role changed, new role: " + info.role);
			VideoLayout.showModeratorIndicator();
			Moderator.onModeratorStatusChanged(Moderator.isModerator());
		    }
		);

		$(document).bind(
		    'left.muc',
		    function (event, jid) {
			//console.info("Someone left is it focus ? " + jid);
			var resource = Strophe.getResourceFromJid(jid);
			if (resource === 'focus' && !sessionTerminated) {
			    //console.info("Focus has left the room - leaving conference");
			    //hangUp();
			    // We'd rather reload to have everything re-initialized
			    // FIXME: show some message before reload
			    location.reload();
			}
		    }
		);
	    };

	    my.setFocusUserJid = function (focusJid) {
		if (!focusUserJid) {
		    focusUserJid = focusJid;
		    //console.info("Focus jid set to: " + focusUserJid);
		}
	    };

	    my.getFocusUserJid = function () {
		return focusUserJid;
	    };

	    my.getFocusComponent = function () {
		// Get focus component address
		var focusComponent = config.hosts.focus;
		// If not specified use default: 'focus.domain'
		if (!focusComponent) {
		    focusComponent = 'focus.' + config.hosts.domain;
		}
		return focusComponent;
	    };

	    my.createConferenceIq = function () {
		// Generate create conference IQ
		var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'});
		elem.c('conference', {
		    xmlns: 'http://jitsi.org/protocol/focus',
		    room: roomName
		});
		if (config.hosts.bridge !== undefined)
		{
		    elem.c(
			'property',
			{ name: 'bridge', value: config.hosts.bridge})
			.up();
		}
		if (config.channelLastN !== undefined)
		{
		    elem.c(
			'property',
			{ name: 'channelLastN', value: config.channelLastN})
			.up();
		}
		if (config.adaptiveLastN !== undefined)
		{
		    elem.c(
			'property',
			{ name: 'adaptiveLastN', value: config.adaptiveLastN})
			.up();
		}
		if (config.adaptiveSimulcast !== undefined)
		{
		    elem.c(
			'property',
			{ name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
			.up();
		}
		if (config.openSctp !== undefined)
		{
		    elem.c(
			'property',
			{ name: 'openSctp', value: config.openSctp})
			.up();
		}
		if (config.enableFirefoxSupport !== undefined)
		{
		    elem.c(
			'property',
			{ name: 'enableFirefoxHacks', value: config.enableFirefoxSupport})
			.up();
		}
		elem.up();
		return elem;
	    };

	    // FIXME: we need to show the fact that we're waiting for the focus
	    // to the user(or that focus is not available)
	    my.allocateConferenceFocus = function (roomName, callback) {
		// Try to use focus user JID from the config
		Moderator.setFocusUserJid(config.focusUserJid);
		// Send create conference IQ
		var iq = Moderator.createConferenceIq();
		connection.sendIQ(
		    iq,
		    function (result) {
			if ('true' === $(result).find('conference').attr('ready')) {
			    // Reset both timers
			    getNextTimeout(true);
			    getNextErrorTimeout(true);
			    Moderator.setFocusUserJid(
				$(result).find('conference').attr('focusjid'));
			    callback();
			} else {
			    var waitMs = getNextTimeout();
			    //console.info("Waiting for the focus... " + waitMs);
			    // Reset error timeout
			    getNextErrorTimeout(true);
			    window.setTimeout(
				function () {
				    Moderator.allocateConferenceFocus(
					roomName, callback);
				}, waitMs);
			}
		    },
		    function (error) {
			var waitMs = getNextErrorTimeout();
			console.error("Focus error, retry after " + waitMs, error);
			// Show message
			messageHandler.notify(
			    'Conference focus', 'disconnected',
			    Moderator.getFocusComponent() +
			    ' not available - retry in ' + (waitMs / 1000) + ' sec');
			// Reset response timeout
			getNextTimeout(true);
			window.setTimeout(
			    function () {
				Moderator.allocateConferenceFocus(roomName, callback);
			    }, waitMs);
		    }
		);
	    };

	    return my;
	}(Moderator || {}));




	/**
	 * Strophe.moderate connection plugin.
	 */

	Strophe.addConnectionPlugin('moderate', {
	    connection: null,
	    init: function (conn) {
		this.connection = conn;

		this.connection.addHandler(this.onMute.bind(this),
					   'http://jitsi.org/jitmeet/audio',
					   'iq',
					   'set',
					   null,
					   null);
	    },
	    setMute: function (jid, mute) {
		//console.info("set mute", mute);
		var iqToFocus = $iq({to: focusJid, type: 'set'})
		    .c('mute', {
			xmlns: 'http://jitsi.org/jitmeet/audio',
			jid: jid
		    })
		    .t(mute.toString())
		    .up();

		this.connection.sendIQ(
		    iqToFocus,
		    function (result) {
			//console.log('set mute', result);
		    },
		    function (error) {
			//console.log('set mute error', error);
			// FIXME: this causes an exception
			//messageHandler.openReportDialog(null, 'Failed to mute ' +
			  //  $("#participant_" + jid).find(".displayname").text() ||
			    //"participant" + '.', error);
		    });
	    },
	    onMute: function (iq) {
		var from = iq.getAttribute('from');
		if (from !== focusJid) {
		    console.warn("Ignored mute from non focus peer");
		    return false;
		}
		var mute = $(iq).find('mute');
		if (mute.length) {
		    var doMuteAudio = mute.text() === "true";
		    setAudioMuted(doMuteAudio);
		    forceMuted = doMuteAudio;
		}
		return true;
	    },
	    eject: function (jid) {
		// We're not the focus, so can't terminate
		//connection.jingle.terminateRemoteByJid(jid, 'kick');
		connection.emuc.kick(jid);
	    }
	});

	/**
	 * messageHandler
	 */

	var messageHandler = (function(my) {

	    /**
	     * Shows a message to the user.
	     *
	     * @param titleString the title of the message
	     * @param messageString the text of the message
	     */
	    my.openMessageDialog = function(titleString, messageString) {
		$.prompt(messageString,
		    {
			title: titleString,
			persistent: false
		    }
		);
	    };

	    /**
	     * Shows a message to the user with two buttons: first is given as a parameter and the second is Cancel.
	     *
	     * @param titleString the title of the message
	     * @param msgString the text of the message
	     * @param persistent boolean value which determines whether the message is persistent or not
	     * @param leftButton the fist button's text
	     * @param submitFunction function to be called on submit
	     * @param loadedFunction function to be called after the prompt is fully loaded
	     * @param closeFunction function to be called after the prompt is closed
	     */
	    my.openTwoButtonDialog = function(titleString, msgString, persistent, leftButton,
					      submitFunction, loadedFunction, closeFunction) {
		var buttons = {};
		buttons[leftButton] = true;
		buttons.Cancel = false;
		$.prompt(msgString, {
		    title: titleString,
		    persistent: false,
		    buttons: buttons,
		    defaultButton: 1,
		    loaded: loadedFunction,
		    submit: submitFunction,
		    close: closeFunction
		});
	    };

	    /**
	     * Shows a message to the user with two buttons: first is given as a parameter and the second is Cancel.
	     *
	     * @param titleString the title of the message
	     * @param msgString the text of the message
	     * @param persistent boolean value which determines whether the message is persistent or not
	     * @param buttons object with the buttons. The keys must be the name of the button and value is the value
	     * that will be passed to submitFunction
	     * @param submitFunction function to be called on submit
	     * @param loadedFunction function to be called after the prompt is fully loaded
	     */
	    my.openDialog = function(titleString, msgString, persistent, buttons, submitFunction, loadedFunction) {
		$.prompt(msgString, {
		    title: titleString,
		    persistent: false,
		    buttons: buttons,
		    defaultButton: 1,
		    loaded: loadedFunction,
		    submit: submitFunction
		});
	    };

	    /**
	     * Shows a dialog with different states to the user.
	     *
	     * @param statesObject object containing all the states of the dialog
	     * @param loadedFunction function to be called after the prompt is fully loaded
	     * @param stateChangedFunction function to be called when the state of the dialog is changed
	     */
	    my.openDialogWithStates = function(statesObject, loadedFunction, stateChangedFunction) {


		var myPrompt = $.prompt(statesObject);

		myPrompt.on('impromptu:loaded', loadedFunction);
		myPrompt.on('impromptu:statechanged', stateChangedFunction);
	    };

	    /**
	     * Shows a dialog prompting the user to send an error report.
	     *
	     * @param titleString the title of the message
	     * @param msgString the text of the message
	     * @param error the error that is being reported
	     */
	    my.openReportDialog = function(titleString, msgString, error) {
		my.openMessageDialog(titleString, msgString);
		//console.log(error);
		//FIXME send the error to the server
	    };

	    /**
	     *  Shows an error dialog to the user.
	     * @param title the title of the message
	     * @param message the text of the messafe
	     */
	    my.showError = function(title, message) {
		if(!(title || message)) {
		    title = title || "Oops!";
		    message = message || "There was some kind of error";
		}
		messageHandler.openMessageDialog(title, message);
	    };

	    my.notify = function(displayName, cls, message) {
		toastr.info(
		    '<span class="nickname">' +
			displayName +
		    '</span><br>' +
		    '<span class=' + cls + '>' +
			message +
		    '</span>');
	    };

	    return my;
	}(messageHandler || {}));


	/**
	 * Provides a wrapper class for the MediaStream.
	 * 
	 * TODO : Add here the src from the video element and other related properties
	 * and get rid of some of the mappings that we use throughout the UI.
	 */

	var MediaStream = (function() {
	    /**
	     * Creates a MediaStream object for the given data, session id and ssrc.
	     *
	     * @param data the data object from which we obtain the stream,
	     * the peerjid, etc.
	     * @param sid the session id
	     * @param ssrc the ssrc corresponding to this MediaStream
	     *
	     * @constructor
	     */
	    function MediaStreamProto(data, sid, ssrc) {
		this.stream = data.stream;
		this.peerjid = data.peerjid;
		this.ssrc = ssrc;
		this.session = connection.jingle.sessions[sid];
		this.type = (this.stream.getVideoTracks().length > 0)
			    ? MediaStream.VIDEO_TYPE : MediaStream.AUDIO_TYPE;
		this.muted = false;
	    }

	    return MediaStreamProto;
	})();

	MediaStream.VIDEO_TYPE = 'Video';
	MediaStream.AUDIO_TYPE = 'Audio';


	/**
	 * Provides statistics for the local stream.
	 */
	var LocalStatsCollector = (function() {
	    /**
	     * Size of the webaudio analizer buffer.
	     * @type {number}
	     */
	    var WEBAUDIO_ANALIZER_FFT_SIZE = 2048;

	    /**
	     * Value of the webaudio analizer smoothing time parameter.
	     * @type {number}
	     */
	    var WEBAUDIO_ANALIZER_SMOOTING_TIME = 0.8;

	    /**
	     * <tt>LocalStatsCollector</tt> calculates statistics for the local stream.
	     *
	     * @param stream the local stream
	     * @param interval stats refresh interval given in ms.
	     * @param {function(LocalStatsCollector)} updateCallback the callback called on stats
	     *                                   update.
	     * @constructor
	     */
	    function LocalStatsCollectorProto(stream, interval, updateCallback) {
		window.AudioContext = window.AudioContext || window.webkitAudioContext;
		this.stream = stream;
		this.intervalId = null;
		this.intervalMilis = interval;
		this.audioLevelsUpdateCallback = updateCallback;
		this.audioLevel = 0;
	    }

	    /**
	     * Starts the collecting the statistics.
	     */
	    LocalStatsCollectorProto.prototype.start = function () {
		if (!window.AudioContext)
		    return;

		var context = new AudioContext();
		var analyser = context.createAnalyser();
		analyser.smoothingTimeConstant = WEBAUDIO_ANALIZER_SMOOTING_TIME;
		analyser.fftSize = WEBAUDIO_ANALIZER_FFT_SIZE;


		var source = context.createMediaStreamSource(this.stream);
		source.connect(analyser);


		var self = this;

		this.intervalId = setInterval(
		    function () {
			var array = new Uint8Array(analyser.frequencyBinCount);
			analyser.getByteTimeDomainData(array);
			var audioLevel = TimeDomainDataToAudioLevel(array);
			if(audioLevel != self.audioLevel) {
			    self.audioLevel = animateLevel(audioLevel, self.audioLevel);
			    self.audioLevelsUpdateCallback(LocalStatsCollectorProto.LOCAL_JID, self.audioLevel);
			}
		    },
		    this.intervalMilis
		);

	    };

	    /**
	     * Stops collecting the statistics.
	     */
	    LocalStatsCollectorProto.prototype.stop = function () {
		if (this.intervalId) {
		    clearInterval(this.intervalId);
		    this.intervalId = null;
		}
	    };

	    /**
	     * Converts time domain data array to audio level.
	     * @param array the time domain data array.
	     * @returns {number} the audio level
	     */
	    var TimeDomainDataToAudioLevel = function (samples) {

		var maxVolume = 0;

		var length = samples.length;

		for (var i = 0; i < length; i++) {
		    if (maxVolume < samples[i])
			maxVolume = samples[i];
		}

		return parseFloat(((maxVolume - 127) / 128).toFixed(3));
	    };

	    /**
	     * Animates audio level change
	     * @param newLevel the new audio level
	     * @param lastLevel the last audio level
	     * @returns {Number} the audio level to be set
	     */
	    function animateLevel(newLevel, lastLevel)
	    {
		var value = 0;
		var diff = lastLevel - newLevel;
		if(diff > 0.2)
		{
		    value = lastLevel - 0.2;
		}
		else if(diff < -0.4)
		{
		    value = lastLevel + 0.4;
		}
		else
		{
		    value = newLevel;
		}

		return parseFloat(value.toFixed(3));
	    }

	    /**
	     * Indicates that this audio level is for local jid.
	     * @type {string}
	     */
	    LocalStatsCollectorProto.LOCAL_JID = 'local';

	    return LocalStatsCollectorProto;
	})();


	/*
	 * Toastr
	 * Copyright 2012-2014 John Papa and Hans Fjällemark.
	 * All Rights Reserved.
	 * Use, reproduction, distribution, and modification of this code is subject to the terms and
	 * conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
	 *
	 * Author: John Papa and Hans Fjällemark
	 * ARIA Support: Greta Krafsig
	 * Project: https://github.com/CodeSeven/toastr
	 */
	; (function (define) {
	    define(['jquery'], function ($) {
		return (function () {
		    var $container;
		    var listener;
		    var toastId = 0;
		    var toastType = {
			error: 'error',
			info: 'info',
			success: 'success',
			warning: 'warning'
		    };

		    var toastr = {
			clear: clear,
			remove: remove,
			error: error,
			getContainer: getContainer,
			info: info,
			options: {},
			subscribe: subscribe,
			success: success,
			version: '2.0.3',
			warning: warning
		    };

		    return toastr;

		    //#region Accessible Methods
		    function error(message, title, optionsOverride) {
			return notify({
			    type: toastType.error,
			    iconClass: getOptions().iconClasses.error,
			    message: message,
			    optionsOverride: optionsOverride,
			    title: title
			});
		    }

		    function getContainer(options, create) {
			if (!options) { options = getOptions(); }
			$container = $('#' + options.containerId);
			if ($container.length) {
			    return $container;
			}
			if(create) {
			    $container = createContainer(options);
			}
			return $container;
		    }

		    function info(message, title, optionsOverride) {
			return notify({
			    type: toastType.info,
			    iconClass: getOptions().iconClasses.info,
			    message: message,
			    optionsOverride: optionsOverride,
			    title: title
			});
		    }

		    function subscribe(callback) {
			listener = callback;
		    }

		    function success(message, title, optionsOverride) {
			return notify({
			    type: toastType.success,
			    iconClass: getOptions().iconClasses.success,
			    message: message,
			    optionsOverride: optionsOverride,
			    title: title
			});
		    }

		    function warning(message, title, optionsOverride) {
			return notify({
			    type: toastType.warning,
			    iconClass: getOptions().iconClasses.warning,
			    message: message,
			    optionsOverride: optionsOverride,
			    title: title
			});
		    }

		    function clear($toastElement) {
			var options = getOptions();
			if (!$container) { getContainer(options); }
			if (!clearToast($toastElement, options)) {
			    clearContainer(options);
			}
		    }

		    function remove($toastElement) {
			var options = getOptions();
			if (!$container) { getContainer(options); }
			if ($toastElement && $(':focus', $toastElement).length === 0) {
			    removeToast($toastElement);
			    return;
			}
			if ($container.children().length) {
			    $container.remove();
			}
		    }
		    //#endregion

		    //#region Internal Methods

		    function clearContainer(options){
			var toastsToClear = $container.children();
			for (var i = toastsToClear.length - 1; i >= 0; i--) {
			    clearToast($(toastsToClear[i]), options);
			};
		    }

		    function clearToast($toastElement, options){
			if ($toastElement && $(':focus', $toastElement).length === 0) {
			    $toastElement[options.hideMethod]({
				duration: options.hideDuration,
				easing: options.hideEasing,
				complete: function () { removeToast($toastElement); }
			    });
			    return true;
			}
			return false;
		    }

		    function createContainer(options) {
			$container = $('<div/>')
			    .attr('id', options.containerId)
			    .addClass(options.positionClass)
			    .attr('aria-live', 'polite')
			    .attr('role', 'alert');

			$container.appendTo($(options.target));
			return $container;
		    }

		    function getDefaults() {
			return {
			    tapToDismiss: true,
			    toastClass: 'toast',
			    containerId: 'toast-container',
			    debug: false,

			    showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery
			    showDuration: 300,
			    showEasing: 'swing', //swing and linear are built into jQuery
			    onShown: undefined,
			    hideMethod: 'fadeOut',
			    hideDuration: 1000,
			    hideEasing: 'swing',
			    onHidden: undefined,

			    extendedTimeOut: 1000,
			    iconClasses: {
				error: 'toast-error',
				info: 'toast-info',
				success: 'toast-success',
				warning: 'toast-warning'
			    },
			    iconClass: 'toast-info',
			    positionClass: 'toast-top-right',
			    timeOut: 5000, // Set timeOut and extendedTimeout to 0 to make it sticky
			    titleClass: 'toast-title',
			    messageClass: 'toast-message',
			    target: 'body',
			    closeHtml: '<button>&times;</button>',
			    newestOnTop: true
			};
		    }

		    function publish(args) {
			if (!listener) { return; }
			listener(args);
		    }

		    function notify(map) {
			var options = getOptions(),
			    iconClass = map.iconClass || options.iconClass;

			if (typeof (map.optionsOverride) !== 'undefined') {
			    options = $.extend(options, map.optionsOverride);
			    iconClass = map.optionsOverride.iconClass || iconClass;
			}

			toastId++;

			$container = getContainer(options, true);
			var intervalId = null,
			    $toastElement = $('<div/>'),
			    $titleElement = $('<div/>'),
			    $messageElement = $('<div/>'),
			    $closeElement = $(options.closeHtml),
			    response = {
				toastId: toastId,
				state: 'visible',
				startTime: new Date(),
				options: options,
				map: map
			    };

			if (map.iconClass) {
			    $toastElement.addClass(options.toastClass).addClass(iconClass);
			}

			if (map.title) {
			    $titleElement.append(map.title).addClass(options.titleClass);
			    $toastElement.append($titleElement);
			}

			if (map.message) {
			    $messageElement.append(map.message).addClass(options.messageClass);
			    $toastElement.append($messageElement);
			}

			if (options.closeButton) {
			    $closeElement.addClass('toast-close-button').attr("role", "button");
			    $toastElement.prepend($closeElement);
			}

			if (options.reposition) {
			    options.reposition();
			}

			$toastElement.hide();
			if (options.newestOnTop) {
			    $container.prepend($toastElement);
			} else {
			    $container.append($toastElement);
			}


			$toastElement[options.showMethod](
			    { duration: options.showDuration, easing: options.showEasing, complete: options.onShown }
			);

			if (options.timeOut > 0) {
			    intervalId = setTimeout(hideToast, options.timeOut);
			}

			$toastElement.hover(stickAround, delayedHideToast);
			if (!options.onclick && options.tapToDismiss) {
			    $toastElement.click(hideToast);
			}

			if (options.closeButton && $closeElement) {
			    $closeElement.click(function (event) {
				if( event.stopPropagation ) {
				    event.stopPropagation();
				} else if( event.cancelBubble !== undefined && event.cancelBubble !== true ) {
				    event.cancelBubble = true;
				}
				hideToast(true);
			    });
			}

			if (options.onclick) {
			    $toastElement.click(function () {
				options.onclick();
				hideToast();
			    });
			}

			publish(response);

			if (options.debug && console) {
			    //console.log(response);
			}

			return $toastElement;

			function hideToast(override) {
			    if ($(':focus', $toastElement).length && !override) {
				return;
			    }
			    return $toastElement[options.hideMethod]({
				duration: options.hideDuration,
				easing: options.hideEasing,
				complete: function () {
				    removeToast($toastElement);
				    if (options.onHidden && response.state !== 'hidden') {
					options.onHidden();
				    }
				    response.state = 'hidden';
				    response.endTime = new Date();
				    publish(response);
				}
			    });
			}

			function delayedHideToast() {
			    if (options.timeOut > 0 || options.extendedTimeOut > 0) {
				intervalId = setTimeout(hideToast, options.extendedTimeOut);
			    }
			}

			function stickAround() {
			    clearTimeout(intervalId);
			    $toastElement.stop(true, true)[options.showMethod](
				{ duration: options.showDuration, easing: options.showEasing }
			    );
			}
		    }

		    function getOptions() {
			return $.extend({}, getDefaults(), toastr.options);
		    }

		    function removeToast($toastElement) {
			if (!$container) { $container = getContainer(); }
			if ($toastElement.is(':visible')) {
			    return;
			}
			$toastElement.remove();
			$toastElement = null;
			if ($container.children().length === 0) {
			    $container.remove();
			}
		    }
		    //#endregion

		})();
	    });
	}(typeof define === 'function' && define.amd ? define : function (deps, factory) {
	    if (typeof module !== 'undefined' && module.exports) { //Node
		module.exports = factory(require('jquery'));
	    } else {
		window['toastr'] = factory(window['jQuery']);
	    }
	}));

	/* ========================================================================
	 * Bootstrap: tooltip.js v3.1.1
	 * http://getbootstrap.com/javascript/#tooltip
	 * Inspired by the original jQuery.tipsy by Jason Frame
	 * ========================================================================
	 * Copyright 2011-2014 Twitter, Inc.
	 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
	 * ======================================================================== */


	+function ($) {


	  // TOOLTIP PUBLIC CLASS DEFINITION
	  // ===============================

	  var Tooltip = function (element, options) {
	    this.type       =
	    this.options    =
	    this.enabled    =
	    this.timeout    =
	    this.hoverState =
	    this.$element   = null

	    this.init('tooltip', element, options)
	  }

	  Tooltip.DEFAULTS = {
	    animation: true,
	    placement: 'top',
	    selector: false,
	    template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
	    trigger: 'hover focus',
	    title: '',
	    delay: 0,
	    html: false,
	    container: false
	  }

	  Tooltip.prototype.init = function (type, element, options) {
	    this.enabled  = true
	    this.type     = type
	    this.$element = $(element)
	    this.options  = this.getOptions(options)

	    var triggers = this.options.trigger.split(' ')

	    for (var i = triggers.length; i--;) {
	      var trigger = triggers[i]

	      if (trigger == 'click') {
		this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
	      } else if (trigger != 'manual') {
		var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'
		var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'

		this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
		this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
	      }
	    }

	    this.options.selector ?
	      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
	      this.fixTitle()
	  }

	  Tooltip.prototype.getDefaults = function () {
	    return Tooltip.DEFAULTS
	  }

	  Tooltip.prototype.getOptions = function (options) {
	    options = $.extend({}, this.getDefaults(), this.$element.data(), options)

	    if (options.delay && typeof options.delay == 'number') {
	      options.delay = {
		show: options.delay,
		hide: options.delay
	      }
	    }

	    return options
	  }

	  Tooltip.prototype.getDelegateOptions = function () {
	    var options  = {}
	    var defaults = this.getDefaults()

	    this._options && $.each(this._options, function (key, value) {
	      if (defaults[key] != value) options[key] = value
	    })

	    return options
	  }

	  Tooltip.prototype.enter = function (obj) {
	    var self = obj instanceof this.constructor ?
	      obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)

	    clearTimeout(self.timeout)

	    self.hoverState = 'in'

	    if (!self.options.delay || !self.options.delay.show) return self.show()

	    self.timeout = setTimeout(function () {
	      if (self.hoverState == 'in') self.show()
	    }, self.options.delay.show)
	  }

	  Tooltip.prototype.leave = function (obj) {
	    var self = obj instanceof this.constructor ?
	      obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)

	    clearTimeout(self.timeout)

	    self.hoverState = 'out'

	    if (!self.options.delay || !self.options.delay.hide) return self.hide()

	    self.timeout = setTimeout(function () {
	      if (self.hoverState == 'out') self.hide()
	    }, self.options.delay.hide)
	  }

	  Tooltip.prototype.show = function () {
	    var e = $.Event('show.bs.' + this.type)

	    if (this.hasContent() && this.enabled) {
	      this.$element.trigger(e)

	      if (e.isDefaultPrevented()) return
	      var that = this;

	      var $tip = this.tip()

	      this.setContent()

	      if (this.options.animation) $tip.addClass('fade')

	      var placement = typeof this.options.placement == 'function' ?
		this.options.placement.call(this, $tip[0], this.$element[0]) :
		this.options.placement

	      var autoToken = /\s?auto?\s?/i
	      var autoPlace = autoToken.test(placement)
	      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'

	      $tip
		.detach()
		.css({ top: 0, left: 0, display: 'block' })
		.addClass(placement)

	      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)

	      var pos          = this.getPosition()
	      var actualWidth  = $tip[0].offsetWidth
	      var actualHeight = $tip[0].offsetHeight

	      if (autoPlace) {
		var $parent = this.$element.parent()

		var orgPlacement = placement
		var docScroll    = document.documentElement.scrollTop || document.body.scrollTop
		var parentWidth  = this.options.container == 'body' ? window.innerWidth  : $parent.outerWidth()
		var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight()
		var parentLeft   = this.options.container == 'body' ? 0 : $parent.offset().left

		placement = placement == 'bottom' && pos.top   + pos.height  + actualHeight - docScroll > parentHeight  ? 'top'    :
			    placement == 'top'    && pos.top   - docScroll   - actualHeight < 0                         ? 'bottom' :
			    placement == 'right'  && pos.right + actualWidth > parentWidth                              ? 'left'   :
			    placement == 'left'   && pos.left  - actualWidth < parentLeft                               ? 'right'  :
			    placement

		$tip
		  .removeClass(orgPlacement)
		  .addClass(placement)
	      }

	      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)

	      this.applyPlacement(calculatedOffset, placement)
	      this.hoverState = null

	      var complete = function() {
		that.$element.trigger('shown.bs.' + that.type)
	      }

	      var deltas = {
		  "bottom": $tip[0].getBoundingClientRect().bottom - window.innerHeight,
		  "right": $tip[0].getBoundingClientRect().right - window.innerWidth,
		  "left": -$tip[0].getBoundingClientRect().left,
		  "top": -$tip[0].getBoundingClientRect().top
	      };
	      for(var direction in deltas) {
		  if (deltas[direction] > 0) {
		      var delta = deltas[direction];
		      if(direction === "right" || direction === "bottom") {
			  delta = -delta;
		      }
		      direction = direction === "top" || direction === "bottom" ? "top" : "left";
		      var currentPosition = parseInt($tip.css(direction), 10);
		      $tip.css(direction, currentPosition + delta);
		      if(direction === "left") {
			  $tip.children(".arrow").css(direction, parseInt($tip.children(".arrow").css(direction), 10) - delta);
		      } else {
			  $tip.children(".arrow").css(direction, 50 - $tip[0].getBoundingClientRect().height / delta + "%");
		      }
		  }
	      }

	      $.support.transition && this.$tip.hasClass('fade') ?
		$tip
		  .one($.support.transition.end, complete)
		  .emulateTransitionEnd(150) :
		complete()
	    }
	  }

	  Tooltip.prototype.applyPlacement = function (offset, placement) {
	    var replace
	    var $tip   = this.tip()
	    var width  = $tip[0].offsetWidth
	    var height = $tip[0].offsetHeight

	    // manually read margins because getBoundingClientRect includes difference
	    var marginTop = parseInt($tip.css('margin-top'), 10)
	    var marginLeft = parseInt($tip.css('margin-left'), 10)

	    // we must check for NaN for ie 8/9
	    if (isNaN(marginTop))  marginTop  = 0
	    if (isNaN(marginLeft)) marginLeft = 0

	    offset.top  = offset.top  + marginTop
	    offset.left = offset.left + marginLeft

	    // $.fn.offset doesn't round pixel values
	    // so we use setOffset directly with our own function B-0
	    $.offset.setOffset($tip[0], $.extend({
	      using: function (props) {
		$tip.css({
		  top: Math.round(props.top),
		  left: Math.round(props.left)
		})
	      }
	    }, offset), 0)

	    $tip.addClass('in')

	    // check to see if placing tip in new offset caused the tip to resize itself
	    var actualWidth  = $tip[0].offsetWidth
	    var actualHeight = $tip[0].offsetHeight

	    if (placement == 'top' && actualHeight != height) {
	      replace = true
	      offset.top = offset.top + height - actualHeight
	    }

	    if (/bottom|top/.test(placement)) {
	      var delta = 0

	      if (offset.left < 0) {
		delta       = offset.left * -2
		offset.left = 0

		$tip.offset(offset)

		actualWidth  = $tip[0].offsetWidth
		actualHeight = $tip[0].offsetHeight
	      }

	      this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
	    } else {
	      this.replaceArrow(actualHeight - height, actualHeight, 'top')
	    }

	    if (replace) $tip.offset(offset)
	  }

	  Tooltip.prototype.replaceArrow = function (delta, dimension, position) {
	    this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + '%') : '')
	  }

	  Tooltip.prototype.setContent = function () {
	    var $tip  = this.tip()
	    var title = this.getTitle()

	    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
	    $tip.removeClass('fade in top bottom left right')
	  }

	  Tooltip.prototype.hide = function () {
	    var that = this
	    var $tip = this.tip()
	    var e    = $.Event('hide.bs.' + this.type)

	    function complete() {
	      if (that.hoverState != 'in') $tip.detach()
	      that.$element.trigger('hidden.bs.' + that.type)
	    }

	    this.$element.trigger(e)

	    if (e.isDefaultPrevented()) return

	    $tip.removeClass('in')

	    $.support.transition && this.$tip.hasClass('fade') ?
	      $tip
		.one($.support.transition.end, complete)
		.emulateTransitionEnd(150) :
	      complete()

	    this.hoverState = null

	    return this
	  }

	  Tooltip.prototype.fixTitle = function () {
	    var $e = this.$element
	    if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
	      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
	    }
	  }

	  Tooltip.prototype.hasContent = function () {
	    return this.getTitle()
	  }

	  Tooltip.prototype.getPosition = function () {
	    var el = this.$element[0]
	    return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
	      width: el.offsetWidth,
	      height: el.offsetHeight
	    }, this.$element.offset())
	  }

	  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
	    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2  } :
		   placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2  } :
		   placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
		/* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width   }
	  }

	  Tooltip.prototype.getTitle = function () {
	    var title
	    var $e = this.$element
	    var o  = this.options

	    title = $e.attr('data-original-title')
	      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)

	    return title
	  }

	  Tooltip.prototype.tip = function () {
	    return this.$tip = this.$tip || $(this.options.template)
	  }

	  Tooltip.prototype.arrow = function () {
	    return this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')
	  }

	  Tooltip.prototype.validate = function () {
	    if (!this.$element[0].parentNode) {
	      this.hide()
	      this.$element = null
	      this.options  = null
	    }
	  }

	  Tooltip.prototype.enable = function () {
	    this.enabled = true
	  }

	  Tooltip.prototype.disable = function () {
	    this.enabled = false
	  }

	  Tooltip.prototype.toggleEnabled = function () {
	    this.enabled = !this.enabled
	  }

	  Tooltip.prototype.toggle = function (e) {
	    var self = e ? $(e.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) : this
	    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
	  }

	  Tooltip.prototype.destroy = function () {
	    clearTimeout(this.timeout)
	    this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)
	  }


	  // TOOLTIP PLUGIN DEFINITION
	  // =========================

	  var old = $.fn.tooltip

	  $.fn.tooltip = function (option) {
	    return this.each(function () {
	      var $this   = $(this)
	      var data    = $this.data('bs.tooltip')
	      var options = typeof option == 'object' && option

	      if (!data && option == 'destroy') return
	      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
	      if (typeof option == 'string') data[option]()
	    })
	  }

	  $.fn.tooltip.Constructor = Tooltip


	  // TOOLTIP NO CONFLICT
	  // ===================

	  $.fn.tooltip.noConflict = function () {
	    $.fn.tooltip = old
	    return this
	  }

	}(jQuery);


	/* ========================================================================
	 * Bootstrap: popover.js v3.1.1
	 * http://getbootstrap.com/javascript/#popovers
	 * ========================================================================
	 * Copyright 2011-2014 Twitter, Inc.
	 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
	 * ======================================================================== */


	+function ($) {

	  // POPOVER PUBLIC CLASS DEFINITION
	  // ===============================

	  var Popover = function (element, options) {
	    this.init('popover', element, options)
	  }

	  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')

	  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
	    placement: 'right',
	    trigger: 'click',
	    content: '',
	    template: '<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
	  })


	  // NOTE: POPOVER EXTENDS tooltip.js
	  // ================================

	  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)

	  Popover.prototype.constructor = Popover

	  Popover.prototype.getDefaults = function () {
	    return Popover.DEFAULTS
	  }

	  Popover.prototype.setContent = function () {
	    var $tip    = this.tip()
	    var title   = this.getTitle()
	    var content = this.getContent()

	    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
	    $tip.find('.popover-content')[ // we use append for html objects to maintain js events
	      this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
	    ](content)

	    $tip.removeClass('fade top bottom left right in')

	    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
	    // this manually by checking the contents.
	    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
	  }

	  Popover.prototype.hasContent = function () {
	    return this.getTitle() || this.getContent()
	  }

	  Popover.prototype.getContent = function () {
	    var $e = this.$element
	    var o  = this.options

	    return $e.attr('data-content')
	      || (typeof o.content == 'function' ?
		    o.content.call($e[0]) :
		    o.content)
	  }

	  Popover.prototype.arrow = function () {
	    return this.$arrow = this.$arrow || this.tip().find('.arrow')
	  }

	  Popover.prototype.tip = function () {
	    if (!this.$tip) this.$tip = $(this.options.template)
	    return this.$tip
	  }


	  // POPOVER PLUGIN DEFINITION
	  // =========================

	  var old = $.fn.popover

	  $.fn.popover = function (option) {
	    return this.each(function () {
	      var $this   = $(this)
	      var data    = $this.data('bs.popover')
	      var options = typeof option == 'object' && option

	      if (!data && option == 'destroy') return
	      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
	      if (typeof option == 'string') data[option]()
	    })
	  }

	  $.fn.popover.Constructor = Popover


	  // POPOVER NO CONFLICT
	  // ===================

	  $.fn.popover.noConflict = function () {
	    $.fn.popover = old
	    return this
	  }

	}(jQuery);


	var connection = null;
	var authenticatedUser = false;
	var activecall = null;
	var RTC = null;
	var nickname = null;
	var sharedKey = '';
	var focusJid = null;
	var roomUrl = null;
	var roomName = null;
	var ssrc2jid = {};
	var mediaStreams = {};
	var bridgeIsDown = false;
	//TODO: this array must be removed when firefox implement multistream support
	var notReceivedSSRCs = [];

	var jid2Ssrc = {};

	/**
	 * The stats collector that process stats data and triggers updates to app.js.
	 * @type {StatsCollector}
	 */
	var statsCollector = null;

	/**
	 * The stats collector for the local stream.
	 * @type {LocalStatsCollector}
	 */
	var localStatsCollector = null;

	/**
	 * Indicates whether ssrc is camera video or desktop stream.
	 * FIXME: remove those maps
	 */
	var ssrc2videoType = {};
	/**
	 * Currently focused video "src"(displayed in large video).
	 * @type {String}
	 */
	var focusedVideoInfo = null;
	var mutedAudios = {};
	/**
	 * Remembers if we were muted by the focus.
	 * @type {boolean}
	 */
	var forceMuted = false;
	/**
	 * Indicates if we have muted our audio before the conference has started.
	 * @type {boolean}
	 */
	var preMuted = false;

	var localVideoSrc = null;
	var flipXLocalVideo = true;
	var isFullScreen = false;
	var currentVideoWidth = null;
	var currentVideoHeight = null;
	/**
	 * Method used to calculate large video size.
	 * @type {function ()}
	 */
	var getVideoSize;
	/**
	 * Method used to get large video position.
	 * @type {function ()}
	 */
	var getVideoPosition;

	var sessionTerminated = false;

	function connectOpenfire(jid, password) 
	{
	    //console.log("connect", jid, password);
	    
	    var localAudio, localVideo;
	    
	    if (connection && connection.jingle) {
		localAudio = connection.jingle.localAudio;
		localVideo = connection.jingle.localVideo;
	    }

	    // BAO
	    connection = new Openfire.Connection(config.bosh);

	    var email = SettingsMenu.getEmail();
	    var displayName = SettingsMenu.getDisplayName();
	    
	    if(email) {
		connection.emuc.addEmailToPresence(email);
	    } else {
		connection.emuc.addUserIdToPresence(SettingsMenu.getUID());
	    }

	    if(displayName) {
		connection.emuc.addDisplayNameToPresence(displayName);
	    }

	    if (connection.disco) {
		// for chrome, add multistream cap
	    }
	    connection.jingle.pc_constraints = RTC.pc_constraints;
	    
	    if (config.useIPv6) {
		// https://code.google.com/p/webrtc/issues/detail?id=2828
		if (!connection.jingle.pc_constraints.optional) connection.jingle.pc_constraints.optional = [];
		connection.jingle.pc_constraints.optional.push({googIPv6: true});
	    }
	    if (localAudio) connection.jingle.localAudio = localAudio;
	    if (localVideo) connection.jingle.localVideo = localVideo;

	    var anonymousConnectionFailed = false;
	    
	    connection.connect(jid, password, function (status, msg) 
	    {
		//console.log('Strophe status changed to', Strophe.getStatusString(status));
		
		if (status === Strophe.Status.CONNECTED) 
		{
		    if (config.useStunTurn) {
			connection.jingle.getStunAndTurnCredentials();
		    }

		    connection.send($pres());    	// BAO     
		    
		    obtainAudioAndVideoPermissions(function (stream) 
		    {
			var audioStream, videoStream;

			if(window.webkitMediaStream)
			{
			    var audioStream = new webkitMediaStream();
			    var videoStream = new webkitMediaStream();
			    var audioTracks = stream.getAudioTracks();
			    var videoTracks = stream.getVideoTracks();

			    for (var i = 0; i < audioTracks.length; i++) {
				audioStream.addTrack(audioTracks[i]);
			    }

			    for (i = 0; i < videoTracks.length; i++) {
				videoStream.addTrack(videoTracks[i]);
			    }
			    VideoLayout.changeLocalAudio(audioStream);
			    startLocalRtpStatsCollector(audioStream);

			    VideoLayout.changeLocalVideo(videoStream, true);
			}
			else
			{
			    VideoLayout.changeLocalStream(stream);
			    startLocalRtpStatsCollector(stream);

			}
		    	
		    	$(document).trigger('ofmeet.connected', [connection]);
		    });
		    
		    
		} else if (status === Strophe.Status.CONNFAIL) {
		    if(msg === 'x-strophe-bad-non-anon-jid') {
			anonymousConnectionFailed = true;
		    }
		} else if (status === Strophe.Status.DISCONNECTED) {
		    if(anonymousConnectionFailed) {
			// prompt user for username and password
			$(document).trigger('passwordrequired.main');
		    }
		} else if (status === Strophe.Status.AUTHFAIL) {
		    // wrong password or username, prompt user
		    $(document).trigger('passwordrequired.main');

		}
	    });
	}

	/**
	 * We ask for audio and video combined stream in order to get permissions and
	 * not to ask twice.
	 */
	function obtainAudioAndVideoPermissions(callback) {
	    // Get AV
	    var cb = function (stream) {
		//console.log('got', stream, stream.getAudioTracks().length, stream.getVideoTracks().length);
		callback(stream);
		trackUsage('localMedia', {
		    audio: stream.getAudioTracks().length,
		    video: stream.getVideoTracks().length
		});
	    }
	    getUserMediaWithConstraints(
		['audio', 'video'],
		cb,
		function (error) {
		    console.error('failed to obtain audio/video stream - trying audio only', error);
		    getUserMediaWithConstraints(
			['audio'],
			cb,
			function (error) {
			    console.error('failed to obtain audio/video stream - stop', error);
			    trackUsage('localMediaError', {
				media: error.media || 'video',
				name : error.name
			    });
			    messageHandler.showError("Error",
				"Failed to obtain permissions to use the local microphone" +
				    "and/or camera.");
			}
		    );
		},
		config.resolution || '360');
	}

	function generateRoomName() {
	    var roomnode = null;
	    var path = window.location.pathname;

	    // determinde the room node from the url
	    // TODO: just the roomnode or the whole bare jid?
	    if (config.getroomnode && typeof config.getroomnode === 'function') {
		// custom function might be responsible for doing the pushstate
		roomnode = config.getroomnode(path);
	    } else {
		/* fall back to default strategy
		 * this is making assumptions about how the URL->room mapping happens.
		 * It currently assumes deployment at root, with a rewrite like the
		 * following one (for nginx):
		location ~ ^/([a-zA-Z0-9]+)$ {
		    rewrite ^/(.*)$ / break;
		}
		 */
		if (path.length > 1) {
		    roomnode = path.substr(1).toLowerCase();
		} else {
		    var word = RoomNameGenerator.generateRoomWithoutSeparator();
		    roomnode = word.toLowerCase();

		    window.history.pushState('VideoChat',
			    'Room: ' + word, window.location.pathname + word);
		}
	    }

	    roomName = roomnode + '@' + config.hosts.muc;
	}

	function doJoin(audioVideo) 
	{
	    if (!roomName) {
		generateRoomName();
	    }

	    if (audioVideo)
	    	Moderator.allocateConferenceFocus(roomName, doJoinAfterFocus);
	    else
	        doJoinAfterFocus();
	}

	function doJoinAfterFocus() {

	    var roomjid;
	    roomjid = roomName;

	    if (config.useNicks) {
		var nick = window.prompt('Your nickname (optional)');
		if (nick) {
		    roomjid += '/' + nick;
		} else {
		    roomjid += '/' + Strophe.getResourceFromJid(connection.jid); // BAO
		}
	    } else {

		var tmpJid = Strophe.getResourceFromJid(connection.jid);	// BAO

		if(!authenticatedUser)
		    tmpJid = tmpJid.substr(0, 8);

		roomjid += '/' + tmpJid;
	    }
	    connection.ofmuc.roomJid = roomName; // BAO    
	    connection.emuc.doJoin(roomjid);
	}

	function waitForRemoteVideo(selector, ssrc, stream, jid) {
	    // XXX(gp) so, every call to this function is *always* preceded by a call
	    // to the RTC.attachMediaStream() function but that call is *not* followed
	    // by an update to the videoSrcToSsrc map!
	    //
	    // The above way of doing things results in video SRCs that don't correspond
	    // to any SSRC for a short period of time (to be more precise, for as long
	    // the waitForRemoteVideo takes to complete). This causes problems (see
	    // bellow).
	    //
	    // I'm wondering why we need to do that; i.e. why call RTC.attachMediaStream()
	    // a second time in here and only then update the videoSrcToSsrc map? Why
	    // not simply update the videoSrcToSsrc map when the RTC.attachMediaStream()
	    // is called the first time? I actually do that in the lastN changed event
	    // handler because the "orphan" video SRC is causing troubles there. The
	    // purpose of this method would then be to fire the "videoactive.jingle".
	    //
	    // Food for though I guess :-)

	    if (selector.removed || !selector.parent().is(":visible")) {
		console.warn("Media removed before had started", selector);
		return;
	    }

	    if (stream.id === 'mixedmslabel') return;

	    if (selector[0].currentTime > 0) {
		var videoStream = simulcast.getReceivingVideoStream(stream);
		RTC.attachMediaStream(selector, videoStream); // FIXME: why do i have to do this for FF?

		// FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type
		//        in order to get rid of too many maps
		if (ssrc && jid) {
		    jid2Ssrc[Strophe.getResourceFromJid(jid)] = ssrc;
		} else {
		    console.warn("No ssrc given for jid", jid);
	//            messageHandler.showError('Warning', 'No ssrc was given for the video.');
		}

		$(document).trigger('videoactive.jingle', [selector]);
	    } else {
		setTimeout(function () {
		    waitForRemoteVideo(selector, ssrc, stream, jid);
		    }, 250);
	    }
	}

	$(document).bind('remotestreamadded.jingle', function (event, data, sid) {
	    waitForPresence(data, sid);
	});

	function waitForPresence(data, sid) {
	    var sess = connection.jingle.sessions[sid];

	    var thessrc;

	    // look up an associated JID for a stream id
	    if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) {
		// look only at a=ssrc: and _not_ at a=ssrc-group: lines

		var ssrclines
		    = SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc:');
		ssrclines = ssrclines.filter(function (line) {
		    // NOTE(gp) previously we filtered on the mslabel, but that property
		    // is not always present.
		    // return line.indexOf('mslabel:' + data.stream.label) !== -1;

		    return ((line.indexOf('msid:' + data.stream.id) !== -1));
		});
		if (ssrclines.length) {
		    thessrc = ssrclines[0].substring(7).split(' ')[0];

		    // We signal our streams (through Jingle to the focus) before we set
		    // our presence (through which peers associate remote streams to
		    // jids). So, it might arrive that a remote stream is added but
		    // ssrc2jid is not yet updated and thus data.peerjid cannot be
		    // successfully set. Here we wait for up to a second for the
		    // presence to arrive.

		    if (!ssrc2jid[thessrc]) {
			// TODO(gp) limit wait duration to 1 sec.
			setTimeout(function(d, s) {
			    return function() {
				    waitForPresence(d, s);
			    }
			}(data, sid), 250);
			return;
		    }

		    // ok to overwrite the one from focus? might save work in colibri.js
		    //console.log('associated jid', ssrc2jid[thessrc], data.peerjid);
		    if (ssrc2jid[thessrc]) {
			data.peerjid = ssrc2jid[thessrc];
		    }
		}
	    }

	    //TODO: this code should be removed when firefox implement multistream support
	    if(RTC.browser == "firefox")
	    {
		if((notReceivedSSRCs.length == 0) ||
		    !ssrc2jid[notReceivedSSRCs[notReceivedSSRCs.length - 1]])
		{
		    // TODO(gp) limit wait duration to 1 sec.
		    setTimeout(function(d, s) {
			return function() {
			    waitForPresence(d, s);
			}
		    }(data, sid), 250);
		    return;
		}

		thessrc = notReceivedSSRCs.pop();
		if (ssrc2jid[thessrc]) {
		    data.peerjid = ssrc2jid[thessrc];
		}
	    }

	    // NOTE(gp) now that we have simulcast, a media stream can have more than 1
	    // ssrc. We should probably take that into account in our MediaStream
	    // wrapper.
	    var mediaStream = new MediaStream(data, sid, thessrc);
	    var jid = data.peerjid || connection.emuc.myroomjid;
	    if(!mediaStreams[jid]) {
		mediaStreams[jid] = {};
	    }
	    mediaStreams[jid][mediaStream.type] = mediaStream;

	    var container;
	    var remotes = document.getElementById('remoteVideos');

	    if (data.peerjid) {
		VideoLayout.ensurePeerContainerExists(data.peerjid);

		container  = document.getElementById(
			'participant_' + Strophe.getResourceFromJid(data.peerjid));
	    } else {
		if (data.stream.id !== 'mixedmslabel'
		    // FIXME: default stream is added always with new focus
		    // (to be investigated)
		    && data.stream.id !== 'default') {
		    console.error('can not associate stream',
			data.stream.id,
			'with a participant');
		    // We don't want to add it here since it will cause troubles
		    return;
		}
		// FIXME: for the mixed ms we dont need a video -- currently
		container = document.createElement('span');
		container.id = 'mixedstream';
		container.className = 'videocontainer';
		remotes.appendChild(container);
	    }

	    var isVideo = data.stream.getVideoTracks().length > 0;

	    if (container) {
		VideoLayout.addRemoteStreamElement( container,
						    sid,
						    data.stream,
						    data.peerjid,
						    thessrc);
	    }

	    // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
	    if (isVideo &&
		data.peerjid && sess.peerjid === data.peerjid &&
		data.stream.getVideoTracks().length === 0 &&
		connection.jingle.localVideo.getVideoTracks().length > 0) {
		//
		window.setTimeout(function () {
		    sendKeyframe(sess.peerconnection);
		}, 3000);
	    }
	}

	// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
	function sendKeyframe(pc) {
	    //console.log('sendkeyframe', pc.iceConnectionState);
	    if (pc.iceConnectionState !== 'connected') return; // safe...
	    pc.setRemoteDescription(
		pc.remoteDescription,
		function () {
		    pc.createAnswer(
			function (modifiedAnswer) {
			    pc.setLocalDescription(
				modifiedAnswer,
				function () {
				    // noop
				},
				function (error) {
				    //console.log('triggerKeyframe setLocalDescription failed', error);
				    messageHandler.showError();
				}
			    );
			},
			function (error) {
			    //console.log('triggerKeyframe createAnswer failed', error);
			    messageHandler.showError();
			}
		    );
		},
		function (error) {
		    //console.log('triggerKeyframe setRemoteDescription failed', error);
		    messageHandler.showError();
		}
	    );
	}

	// Really mute video, i.e. dont even send black frames
	function muteVideo(pc, unmute) {
	    // FIXME: this probably needs another of those lovely state safeguards...
	    // which checks for iceconn == connected and sigstate == stable
	    pc.setRemoteDescription(pc.remoteDescription,
		function () {
		    pc.createAnswer(
			function (answer) {
			    var sdp = new SDP(answer.sdp);
			    if (sdp.media.length > 1) {
				if (unmute)
				    sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
				else
				    sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
				sdp.raw = sdp.session + sdp.media.join('');
				answer.sdp = sdp.raw;
			    }
			    pc.setLocalDescription(answer,
				function () {
				    //console.log('mute SLD ok');
				},
				function (error) {
				    //console.log('mute SLD error');
				    messageHandler.showError('Error',
					'Oops! Something went wrong and we failed to ' +
					    'mute! (SLD Failure)');
				}
			    );
			},
			function (error) {
			    //console.log(error);
			    messageHandler.showError();
			}
		    );
		},
		function (error) {
		    //console.log('muteVideo SRD error');
		    messageHandler.showError('Error',
			'Oops! Something went wrong and we failed to stop video!' +
			    '(SRD Failure)');

		}
	    );
	}

	/**
	 * Callback for audio levels changed.
	 * @param jid JID of the user
	 * @param audioLevel the audio level value
	 */
	function audioLevelUpdated(jid, audioLevel)
	{
	    var resourceJid;
	    if(jid === LocalStatsCollector.LOCAL_JID)
	    {
		resourceJid = AudioLevels.LOCAL_LEVEL;
		if(isAudioMuted())
		{
		    audioLevel = 0;
		}
	    }
	    else
	    {
		resourceJid = Strophe.getResourceFromJid(jid);
	    }

	    AudioLevels.updateAudioLevel(resourceJid, audioLevel);
	}

	/**
	 * Starts the {@link StatsCollector} if the feature is enabled in config.js.
	 */
	function startRtpStatsCollector()
	{
	    stopRTPStatsCollector();
	    if (config.enableRtpStats)
	    {
		statsCollector = new StatsCollector(
		    getConferenceHandler().peerconnection, 200, audioLevelUpdated, 2000,
		    ConnectionQuality.updateLocalStats);
		statsCollector.start();
	    }
	}

	/**
	 * Stops the {@link StatsCollector}.
	 */
	function stopRTPStatsCollector()
	{
	    if (statsCollector)
	    {
		statsCollector.stop();
		statsCollector = null;
		ConnectionQuality.stopSendingStats();
	    }
	}

	/**
	 * Starts the {@link LocalStatsCollector} if the feature is enabled in config.js
	 * @param stream the stream that will be used for collecting statistics.
	 */
	function startLocalRtpStatsCollector(stream)
	{
	    if(config.enableRtpStats)
	    {
		localStatsCollector = new LocalStatsCollector(stream, 100, audioLevelUpdated);
		localStatsCollector.start();
	    }
	}

	/**
	 * Stops the {@link LocalStatsCollector}.
	 */
	function stopLocalRtpStatsCollector()
	{
	    if(localStatsCollector)
	    {
		localStatsCollector.stop();
		localStatsCollector = null;
	    }
	}

	$(document).bind('callincoming.jingle', function (event, sid) {
	    var sess = connection.jingle.sessions[sid];

	    // TODO: do we check activecall == null?
	    activecall = sess;

	    startRtpStatsCollector();

	    // Bind data channel listener in case we're a regular participant
	    if (config.openSctp)
	    {
		bindDataChannelListener(sess.peerconnection);
	    }

	    // TODO: check affiliation and/or role
	    //console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
	    sess.usedrip = true; // not-so-naive trickle ice
	    sess.sendAnswer();
	    sess.accept();

	});

	$(document).bind('conferenceCreated.jingle', function (event, focus)
	{
	    startRtpStatsCollector();
	});

	$(document).bind('conferenceCreated.jingle', function (event, focus)
	{
	    // Bind data channel listener in case we're the focus
	    if (config.openSctp)
	    {
		bindDataChannelListener(focus.peerconnection);
	    }
	});

	$(document).bind('setLocalDescription.jingle', function (event, sid) {
	    // put our ssrcs into presence so other clients can identify our stream
	    var sess = connection.jingle.sessions[sid];
	    var newssrcs = [];
	    var media = simulcast.parseMedia(sess.peerconnection.localDescription);
	    media.forEach(function (media) {

		if(Object.keys(media.sources).length > 0) {
		    // TODO(gp) maybe exclude FID streams?
		    Object.keys(media.sources).forEach(function (ssrc) {
			newssrcs.push({
			    'ssrc': ssrc,
			    'type': media.type,
			    'direction': media.direction
			});
		    });
		}
		else if(sess.localStreamsSSRC && sess.localStreamsSSRC[media.type])
		{
		    newssrcs.push({
			'ssrc': sess.localStreamsSSRC[media.type],
			'type': media.type,
			'direction': media.direction
		    });
		}

	    });

	    //console.log('new ssrcs', newssrcs);

	    // Have to clear presence map to get rid of removed streams
	    connection.emuc.clearPresenceMedia();

	    if (newssrcs.length > 0) {
		for (var i = 1; i <= newssrcs.length; i ++) {
		    // Change video type to screen
		    if (newssrcs[i-1].type === 'video' && isUsingScreenStream) {
			newssrcs[i-1].type = 'screen';
		    }
		    connection.emuc.addMediaToPresence(i,
			newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction);
		}

		connection.emuc.sendPresence();
	    }
	});

	$(document).bind('iceconnectionstatechange.jingle', function (event, sid, session) {
	    switch (session.peerconnection.iceConnectionState) {
	    case 'checking': 
		session.timeChecking = (new Date()).getTime();
		session.firstconnect = true;
		break;
	    case 'completed': // on caller side
	    case 'connected':
		if (session.firstconnect) {
		    session.firstconnect = false;
		    var metadata = {};
		    metadata.setupTime = (new Date()).getTime() - session.timeChecking;
		    session.peerconnection.getStats(function (res) {
			if(res && res.result) {
			    res.result().forEach(function (report) {
				if (report.type == 'googCandidatePair' && report.stat('googActiveConnection') == 'true') {
				    metadata.localCandidateType = report.stat('googLocalCandidateType');
				    metadata.remoteCandidateType = report.stat('googRemoteCandidateType');

				    // log pair as well so we can get nice pie charts
				    metadata.candidatePair = report.stat('googLocalCandidateType') + ';' + report.stat('googRemoteCandidateType');

				    if (report.stat('googRemoteAddress').indexOf('[') === 0) {
					metadata.ipv6 = true;
				    }
				}
			    });
			    trackUsage('iceConnected', metadata);
			}
		    });
		}
		break;
	    }
	});

	$(document).bind('joined.muc', function (event, jid, info) 
	{
	    updateRoomUrl(window.location.href);
	    
	    var displayName = !config.displayJids ? info.displayName : Strophe.getResourceFromJid(jid);
	    
	    if (displayName)
	    {
	    	if (displayName.indexOf(" (me)") == -1) displayName = displayName + ' (me)';
	    	$(document).trigger('displaynamechanged', ['localVideoContainer', displayName]);
	    	document.getElementById('localNick').appendChild(document.createTextNode(displayName));
	    }
	    
	});

	$(document).bind('entered.muc', function (event, jid, info, pres) {
	    //console.log('entered', jid, info);
	    if (info.isFocus)
	    {
		focusJid = jid;
		//console.info("Ignore focus: " + jid +", real JID: " + info.jid);
		// We don't want this notification for the focus.
		// messageHandler.notify('Focus', 'connected', 'connected');
		return;
	    }

	    messageHandler.notify(info.displayName || 'Somebody',
		'connected',
		'connected');

	    // Add Peer's container
	    var id = $(pres).find('>userID').text();
	    var email = $(pres).find('>email');
	    if(email.length > 0) {
		id = email.text();
	    }
	    VideoLayout.ensurePeerContainerExists(jid,id);
	});

	$(document).bind('left.muc', function (event, jid) {
	    //console.log('left.muc', jid);
	    var displayName = $('#participant_' + Strophe.getResourceFromJid(jid) +
		'>.displayname').text();
	    messageHandler.notify(displayName || 'Somebody',
		'disconnected',
		'disconnected');
	    // Need to call this with a slight delay, otherwise the element couldn't be
	    // found for some reason.
	    // XXX(gp) it works fine without the timeout for me (with Chrome 38).
	    window.setTimeout(function () {
		var container = document.getElementById(
			'participant_' + Strophe.getResourceFromJid(jid));
		if (container) {
		    VideoLayout.removeConnectionIndicator(jid);
		    // hide here, wait for video to close before removing
		    $(container).hide();
		    VideoLayout.resizeThumbnails();
		}
	    }, 10);

	    delete jid2Ssrc[jid];

	    // Unlock large video
	    if (focusedVideoInfo && focusedVideoInfo.jid === jid)
	    {
		//console.info("Focused video owner has left the conference");
		focusedVideoInfo = null;
	    }

	    connection.jingle.terminateByJid(jid);
	});

	$(document).bind('presence.muc', function (event, jid, info, pres) {

	/* BAO

	    //check if the video bridge is available
	    if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
		bridgeIsDown = true;
		messageHandler.showError("Error",
		    "Jitsi Videobridge is currently unavailable. Please try again later!");
	    }
	*/
	    if (info.isFocus)
	    {
		return;
	    }

	    // Remove old ssrcs coming from the jid
	    Object.keys(ssrc2jid).forEach(function (ssrc) {
		if (ssrc2jid[ssrc] == jid) {
		    delete ssrc2jid[ssrc];
		    delete ssrc2videoType[ssrc];
		}
	    });

	    $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
		//console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
		var ssrcV = ssrc.getAttribute('ssrc');
		ssrc2jid[ssrcV] = jid;
		notReceivedSSRCs.push(ssrcV);

		var type = ssrc.getAttribute('type');
		ssrc2videoType[ssrcV] = type;

		// might need to update the direction if participant just went from sendrecv to recvonly
		if (type === 'video' || type === 'screen') {
		    var el = $('#participant_'  + Strophe.getResourceFromJid(jid) + '>video');
		    switch (ssrc.getAttribute('direction')) {
		    case 'sendrecv':
			el.show();
			break;
		    case 'recvonly':
			el.hide();
			// FIXME: Check if we have to change large video
			//VideoLayout.updateLargeVideo(el);
			break;
		    }
		}
	    });

	    var displayName = !config.displayJids ? info.displayName : Strophe.getResourceFromJid(jid);

	    if (displayName && displayName.length > 0) $(document).trigger('displaynamechanged',   [jid, displayName]);
		
	    var id = $(pres).find('>userID').text();
	    var email = $(pres).find('>email');
	    if(email.length > 0) {
		id = email.text();
	    }
	    Avatar.setUserAvatar(jid, id);

	});

	$(document).bind('presence.status.muc', function (event, jid, info, pres) {

	    VideoLayout.setPresenceStatus(
		'participant_' + Strophe.getResourceFromJid(jid), info.status);

	});

	$(document).bind('kicked.muc', function (event, jid) {
	    //console.info(jid + " has been kicked from MUC!");
	    if (connection.emuc.myroomjid === jid) {
		sessionTerminated = true;
		disposeConference(false);
		connection.emuc.doLeave();
		messageHandler.openMessageDialog("Session Terminated",
		    "Ouch! You have been kicked out of the meet!");
	    }
	});

	$(document).bind('passwordrequired.muc', function (event, jid) {
	    //console.log('on password required', jid);

	    messageHandler.openTwoButtonDialog(null,
		'<h2>Password required</h2>' +
		'<input id="lockKey" type="text" placeholder="password" autofocus>',
		true,
		"Ok",
		function (e, v, m, f) {},
		function (event) {
		    document.getElementById('lockKey').focus();
		},
		function (e, v, m, f) {
		    if (v) {
			var lockKey = document.getElementById('lockKey');
			if (lockKey.value !== null) {
			    setSharedKey(lockKey.value);
			    connection.emuc.doJoin(jid, lockKey.value);
			}
		    }
		}
	    );
	});

	/**
	 * Checks if video identified by given src is desktop stream.
	 * @param videoSrc eg.
	 * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
	 * @returns {boolean}
	 */
	function isVideoSrcDesktop(jid) {
	    // FIXME: fix this mapping mess...
	    // figure out if large video is desktop stream or just a camera

	    if(!jid)
		return false;
	    var isDesktop = false;
	    if (connection.emuc.myroomjid &&
		Strophe.getResourceFromJid(connection.emuc.myroomjid) === jid) {
		// local video
		isDesktop = isUsingScreenStream;
	    } else {
		// Do we have associations...
		var videoSsrc = jid2Ssrc[jid];
		if (videoSsrc) {
		    var videoType = ssrc2videoType[videoSsrc];
		    if (videoType) {
			// Finally there...
			isDesktop = videoType === 'screen';
		    } else {
			console.error("No video type for ssrc: " + videoSsrc);
		    }
		} else {
		    console.error("No ssrc for jid: " + jid);
		}
	    }
	    return isDesktop;
	}

	function getConferenceHandler() {
	    return activecall;
	}

	/**
	 * Mutes/unmutes the local video.
	 *
	 * @param mute <tt>true</tt> to mute the local video; otherwise, <tt>false</tt>
	 * @param options an object which specifies optional arguments such as the
	 * <tt>boolean</tt> key <tt>byUser</tt> with default value <tt>true</tt> which
	 * specifies whether the method was initiated in response to a user command (in
	 * contrast to an automatic decision taken by the application logic)
	 */
	function setVideoMute(mute, options) {
	    if (connection && connection.jingle.localVideo) {
		var session = getConferenceHandler();

		if (session) {
		    session.setVideoMute(
			mute,
			function (mute) {
			    var video = $('#video');
			    var communicativeClass = "icon-camera";
			    var muteClass = "icon-camera icon-camera-disabled";

			    if (mute) {
				video.removeClass(communicativeClass);
				video.addClass(muteClass);
			    } else {
				video.removeClass(muteClass);
				video.addClass(communicativeClass);
			    }
			    connection.emuc.addVideoInfoToPresence(mute);
			    connection.emuc.sendPresence();
			},
			options);
		}
	    }
	}

	$(document).on('inlastnchanged', function (event, oldValue, newValue) {
	    if (config.muteLocalVideoIfNotInLastN) {
		setVideoMute(!newValue, { 'byUser': false });
	    }
	});

	/**
	 * Sets muted audio state for the local participant.
	 */
	function setAudioMuted(mute) {
	    if (!(connection && connection.jingle.localAudio)) {
		preMuted = mute;
		// We still click the button.
		buttonClick("#mute", "icon-microphone icon-mic-disabled");
		return;
	    }

	    if (forceMuted && !mute) {
		//console.info("Asking focus for unmute");
		connection.moderate.setMute(connection.emuc.myroomjid, mute);
		// FIXME: wait for result before resetting muted status
		forceMuted = false;
	    }

	    if (mute == isAudioMuted()) {
		// Nothing to do
		return;
	    }

	    // It is not clear what is the right way to handle multiple tracks.
	    // So at least make sure that they are all muted or all unmuted and
	    // that we send presence just once.
	    var localAudioTracks = connection.jingle.localAudio.getAudioTracks();
	    if (localAudioTracks.length > 0) {
		for (var idx = 0; idx < localAudioTracks.length; idx++) {
		    localAudioTracks[idx].enabled = !mute;
		}
	    }
	    // isMuted is the opposite of audioEnabled
	    connection.emuc.addAudioInfoToPresence(mute);
	    connection.emuc.sendPresence();
	    VideoLayout.showLocalAudioIndicator(mute);

	    buttonClick("#mute", "icon-microphone icon-mic-disabled");
	}

	/**
	 * Checks whether the audio is muted or not.
	 * @returns {boolean} true if audio is muted and false if not.
	 */
	function isAudioMuted()
	{
	    var localAudio = connection.jingle.localAudio;
	    for (var idx = 0; idx < localAudio.getAudioTracks().length; idx++) {
		if(localAudio.getAudioTracks()[idx].enabled === true)
		    return false;
	    }
	    return true;
	}

	// Starts or stops the recording for the conference.
	function toggleRecording() {
	    Recording.toggleRecording();
	}

	/**
	 * Returns an array of the video horizontal and vertical indents,
	 * so that if fits its parent.
	 *
	 * @return an array with 2 elements, the horizontal indent and the vertical
	 * indent
	 */
	function getCameraVideoPosition(videoWidth,
					videoHeight,
					videoSpaceWidth,
					videoSpaceHeight) {
	    // Parent height isn't completely calculated when we position the video in
	    // full screen mode and this is why we use the screen height in this case.
	    // Need to think it further at some point and implement it properly.
	    var isFullScreen = document.fullScreen ||
		    document.mozFullScreen ||
		    document.webkitIsFullScreen;
	    if (isFullScreen)
		videoSpaceHeight = window.innerHeight;

	    var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
	    var verticalIndent = (videoSpaceHeight - videoHeight) / 2;

	    return [horizontalIndent, verticalIndent];
	}

	/**
	 * Returns an array of the video horizontal and vertical indents.
	 * Centers horizontally and top aligns vertically.
	 *
	 * @return an array with 2 elements, the horizontal indent and the vertical
	 * indent
	 */
	function getDesktopVideoPosition(videoWidth,
					 videoHeight,
					 videoSpaceWidth,
					 videoSpaceHeight) {

	    var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;

	    var verticalIndent = 0;// Top aligned

	    return [horizontalIndent, verticalIndent];
	}

	/**
	 * Returns an array of the video dimensions, so that it covers the screen.
	 * It leaves no empty areas, but some parts of the video might not be visible.
	 *
	 * @return an array with 2 elements, the video width and the video height
	 */
	function getCameraVideoSize(videoWidth,
				   videoHeight,
				   videoSpaceWidth,
				   videoSpaceHeight) {
	    if (!videoWidth)
		videoWidth = currentVideoWidth;
	    if (!videoHeight)
		videoHeight = currentVideoHeight;

	    var aspectRatio = videoWidth / videoHeight;

	    var availableWidth = Math.max(videoWidth, videoSpaceWidth);
	    var availableHeight = Math.max(videoHeight, videoSpaceHeight);

	    if (availableWidth / aspectRatio < videoSpaceHeight) {
		availableHeight = videoSpaceHeight;
		availableWidth = availableHeight * aspectRatio;
	    }

	    if (availableHeight * aspectRatio < videoSpaceWidth) {
		availableWidth = videoSpaceWidth;
		availableHeight = availableWidth / aspectRatio;
	    }

	    return [availableWidth, availableHeight];
	}


	$(window).bind('beforeunload', function () {
	    if (connection && connection.connected) {
		// ensure signout
		connection.disconnect();
	    }
	    disposeConference(true);
	});

	function disposeConference(onUnload) {
	    var handler = getConferenceHandler();
	    if (handler && handler.peerconnection) {
		// FIXME: probably removing streams is not required and close() should
		// be enough
		if (connection.jingle.localAudio) {
		    handler.peerconnection.removeStream(connection.jingle.localAudio, onUnload);
		}
		if (connection.jingle.localVideo) {
		    handler.peerconnection.removeStream(connection.jingle.localVideo, onUnload);
		}
		handler.peerconnection.close();
	    }
	    stopRTPStatsCollector();
	    if(onUnload) {
		stopLocalRtpStatsCollector();
	    }
	    activecall = null;
	}

	function dump(elem, filename) {
	    elem = elem.parentNode;
	    elem.download = filename || 'meetlog.json';
	    elem.href = 'data:application/json;charset=utf-8,\n';
	    var data = populateData();
	    elem.href += encodeURIComponent(JSON.stringify(data, null, '  '));
	    return false;
	}


	/**
	 * Populates the log data
	 */
	function populateData() {
	    var data = {};
	    if (connection.jingle) {
		Object.keys(connection.jingle.sessions).forEach(function (sid) {
		    var session = connection.jingle.sessions[sid];
		    if (session.peerconnection && session.peerconnection.updateLog) {
			// FIXME: should probably be a .dump call
			data["jingle_" + session.sid] = {
			    updateLog: session.peerconnection.updateLog,
			    stats: session.peerconnection.stats,
			    url: window.location.href
			};
		    }
		});
	    }
	    var metadata = {};
	    metadata.time = new Date();
	    metadata.url = window.location.href;
	    metadata.ua = navigator.userAgent;
	    if (connection.logger) {
		metadata.xmpp = connection.logger.log;
	    }
	    data.metadata = metadata;
	    return data;
	}

	/**
	 * Changes the style class of the element given by id.
	 */
	function buttonClick(id, classname) {
	    $(id).toggleClass(classname); // add the class to the clicked element
	}

	/**
	 * Locks / unlocks the room.
	 */
	function lockRoom(lock) {
	    if (lock)
		connection.emuc.lockRoom(sharedKey);
	    else
		connection.emuc.lockRoom('');
	}

	/**
	 * Sets the shared key.
	 */
	function setSharedKey(sKey) {
	    sharedKey = sKey;
	}

	/**
	 * Updates the room invite url.
	 */
	function updateRoomUrl(newRoomUrl) {
	    roomUrl = newRoomUrl;

	    // If the invite dialog has been already opened we update the information.
	    var inviteLink = document.getElementById('inviteLinkRef');
	    if (inviteLink) {
		inviteLink.value = roomUrl;
		inviteLink.select();
		document.getElementById('jqi_state0_buttonInvite').disabled = false;
	    }
	}

	/**
	 * Resizes and repositions videos in full screen mode.
	 */
	$(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange',
	    function () {
		VideoLayout.resizeLargeVideoContainer();
		VideoLayout.positionLarge();
		isFullScreen = document.fullScreen ||
		    document.mozFullScreen ||
		    document.webkitIsFullScreen;

		if (isFullScreen) {
		    setView("fullscreen");
		}
		else {
		    setView("default");
		}
	    }
	);

	/**
	 * Sets the current view.
	 */
	function setView(viewName) {
	//    if (viewName == "fullscreen") {
	//        document.getElementById('videolayout_fullscreen').disabled  = false;
	//        document.getElementById('videolayout_default').disabled  = true;
	//    }
	//    else {
	//        document.getElementById('videolayout_default').disabled  = false;
	//        document.getElementById('videolayout_fullscreen').disabled  = true;
	//    }
	}

	$(document).bind('error.jingle',  function (event, session, error)
	{
		console.error("Jingle error", error);
	});

	$(document).bind('fatalError.jingle', function (event, session, error)
	{
		sessionTerminated = true;
		connection.emuc.doLeave();
		messageHandler.showError(  "Sorry", "Internal application error[setRemoteDescription]");
	});

	function onSelectedEndpointChanged(userJid)
	{
	    //console.log('selected endpoint changed: ', userJid);
	    if (_dataChannels && _dataChannels.length != 0)
	    {
		_dataChannels.some(function (dataChannel) {
		    if (dataChannel.readyState == 'open')
		    {
			dataChannel.send(JSON.stringify({
			    'colibriClass': 'SelectedEndpointChangedEvent',
			    'selectedEndpoint': (!userJid || userJid == null)
				? null : userJid
			}));

			return true;
		    }
		});
	    }
	}

	$(document).bind("selectedendpointchanged", function(event, userJid) {
	    onSelectedEndpointChanged(userJid);
	});

	function onPinnedEndpointChanged(userJid)
	{
	    //console.log('pinned endpoint changed: ', userJid);
	    if (_dataChannels && _dataChannels.length != 0)
	    {
		_dataChannels.some(function (dataChannel) {
		    if (dataChannel.readyState == 'open')
		    {
			dataChannel.send(JSON.stringify({
			    'colibriClass': 'PinnedEndpointChangedEvent',
			    'pinnedEndpoint': (!userJid || userJid == null)
				? null : Strophe.getResourceFromJid(userJid)
			}));

			return true;
		    }
		});
	    }
	}

	$(document).bind("pinnedendpointchanged", function(event, userJid) 
	{
	    onPinnedEndpointChanged(userJid);
	});

	$(document).on('videomuted.muc', function(event, jid, value) 
	{
	    if(mediaStreams[jid] && mediaStreams[jid][MediaStream.VIDEO_TYPE]) {
		var stream = mediaStreams[jid][MediaStream.VIDEO_TYPE];
		var isMuted = (value === "true");
		if (isMuted != stream.muted) {
		    stream.muted = isMuted;
		    Avatar.showUserAvatar(jid, isMuted);
		}
	    }
	});
	
	$(document).ready(function ()
	{
		console.log("of.document.ready");
		$('body').append('<div id="videospace"> <div id="largeVideoContainer" class="videocontainer"> <div id="activeSpeaker"> <img id="activeSpeakerAvatar" src=""/> <canvas id="activeSpeakerAudioLevel"></canvas> </div> <video id="largeVideo" autoplay oncontextmenu="return false;"></video> </div> <div id="remoteVideos"> <span id="localVideoContainer" class="videocontainer"> <span id="localNick" class="nick"></span> <span id="localVideoWrapper" /> <audio id="localAudio" autoplay oncontextmenu="return false;" muted></audio> <span class="focusindicator"></span> </span> </div> </div>');
	});	

	of.visible = function (flag) 
	{
		if (flag)
		{
			$('#videospace').css("display", "block");
		} else {
			$('#videospace').css("display", "none");			
		}
	}

	of.ready = function (username, password) 
	{
		$.ajax({type: "GET", url: "/ofmeet/config", dataType: "script", headers: {"Authorization": "Basic " + btoa(username + ":" + password)}}).done(function()
		{
			var cssId = 'ofmeet-css';

			if (!document.getElementById(cssId))
			{
			    var head  = document.getElementsByTagName('head')[0];
			    var link  = document.createElement('link');
			    link.id   = cssId;
			    link.rel  = 'stylesheet';
			    link.type = 'text/css';
			    link.href = '/ofmeet/api/ofmeet.css';
			    link.media = 'all';
			    head.appendChild(link);
			}		
			of.initialise();
		})
	}
	
	of.initialise = function () 
	{
	    console.log("of.initialise");
	    
	    $('#videospace').css("display", "block");
	    
	    VideoLayout.init();
	    Moderator.init();
	    
	    simulcast = new SimulcastManager();

	    // Set default desktop sharing method
	    setDesktopSharing(config.desktopSharing);
	    // Initialize Chrome extension inline installs
	    if (config.chromeExtensionId) {
		initInlineInstalls();
	    }
	    
	    // By default we use camera
	    getVideoSize = getCameraVideoSize;
	    getVideoPosition = getCameraVideoPosition;

	    VideoLayout.resizeLargeVideoContainer();
	    $(window).resize(function () {
		VideoLayout.resizeLargeVideoContainer();
		VideoLayout.positionLarge();
	    });
  
	    // Listen for large video size updates
	    document.getElementById('largeVideo')
		.addEventListener('loadedmetadata', function (e) {
		    currentVideoWidth = this.videoWidth;
		    currentVideoHeight = this.videoHeight;
		    VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight);
		});

	    document.getElementById('largeVideo').volume = 0;

	    toastr.options = {
		"closeButton": true,
		"debug": false,
		"positionClass": "notification-bottom-right",
		"onclick": null,
		"showDuration": "300",
		"hideDuration": "1000",
		"timeOut": "2000",
		"extendedTimeOut": "1000",
		"showEasing": "swing",
		"hideEasing": "linear",
		"showMethod": "fadeIn",
		"hideMethod": "fadeOut",
		"reposition": function() {
		},
		"newestOnTop": false
	    };
	    
	    $(document).trigger('ofmeet.ready');	    
	};
    	

	of.addSIPPhone = function addSIPPhone(sipNumber) 
	{
		connection.rayo.dial(sipNumber, roomName, roomName);
	}
	
	of.removeSIPPhone = function removeSIPPhone() 
	{
		connection.rayo.rayo.hang_up();
	}

	of.toggleScreenSharing = function toggleScreenSharing() 
	{
	    if (switchInProgress || !obtainDesktopStream) {
		console.warn("Switch in progress or no method defined");
		return;
	    }
	    switchInProgress = true;

	    // Only the focus is able to set a shared key.
	    if (!isUsingScreenStream)
	    {
		obtainDesktopStream(
		    function (stream) {
			// We now use screen stream
			isUsingScreenStream = true;
			// Hook 'ended' event to restore camera when screen stream stops
			stream.addEventListener('ended',
			    function (e) {
				if (!switchInProgress && isUsingScreenStream) {
				    toggleScreenSharing();
				}
			    }
			);
			newStreamCreated(stream);
		    },
		    getSwitchStreamFailed);
	    } else {
		// Disable screen stream
		getUserMediaWithConstraints(
		    ['video'],
		    function (stream) {
			// We are now using camera stream
			isUsingScreenStream = false;
			newStreamCreated(stream);
		    },
		    getSwitchStreamFailed, config.resolution || '360'
		);
	    }
	}

	of.toggleFilmStrip = function toggleFilmStrip() 
	{
	    var filmstrip = $("#remoteVideos");
	    filmstrip.toggleClass("hidden");
	};
    
	of.toggleVideo = function toggleVideo() 
	{
	    buttonClick("#video", "icon-camera icon-camera-disabled");

	    if (connection && connection.jingle.localVideo) {
		var session = getConferenceHandler();

		if (session) {
		    setVideoMute(!session.isVideoMute());
		}
	    }
	}


	of.toggleAudio = function toggleAudio() 
	{
	    setAudioMuted(!isAudioMuted());
	}	

	of.connect = function connect(username, password) 
	{	    
	    RTC = setupRTC();
	    
	    if (RTC === null) {
		return;
	    } else if (RTC.browser !== 'chrome' && config.enableFirefoxSupport !== true) {
		return;
	    }

	    var jid = config.hosts.domain;
	    if (username) jid = username +  "@" + config.hosts.domain;

	    if (config.userName)
	    {
		jid = config.userName +  "@" + config.hosts.domain;
		authenticatedUser = true;
	    } 
	    
	    connectOpenfire(jid, password);	    
	}

	of.leaveRoom = function leaveRoom() 
	{
	    disposeConference();
	    sessionTerminated = true;
	    connection.emuc.doLeave();
	    roomName = null;
	}
	
	of.joinRoom = function joinRoom(room, audioVideo) 
	{
	    if (connection && connection.connected && Strophe.getResourceFromJid(connection.jid) // .connected is true while connecting?
		&& (connection.jingle.localAudio || connection.jingle.localVideo)) {
		if (room) roomName = room + '@' + config.hosts.muc;
		doJoin(audioVideo);
	    }
	}
	
    	of.VideoLayout = VideoLayout;
    	of.connection = connection;    	
    	
    	return of;
}(ofmeet || {}));