/*
 * jQuery 1.2.6 - New Wave Javascript
 *
 * Copyright (c) 2008 John Resig (jquery.com)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $
 * $Rev: 5685 $
 */
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(H(){J w=1b.4M,3m$=1b.$;J D=1b.4M=1b.$=H(a,b){I 2B D.17.5j(a,b)};J u=/^[^<]*(<(.|\\s)+>)[^>]*$|^#(\\w+)$/,62=/^.[^:#\\[\\.]*$/,12;D.17=D.44={5j:H(d,b){d=d||S;G(d.16){7[0]=d;7.K=1;I 7}G(1j d=="23"){J c=u.2D(d);G(c&&(c[1]||!b)){G(c[1])d=D.4h([c[1]],b);N{J a=S.61(c[3]);G(a){G(a.2v!=c[3])I D().2q(d);I D(a)}d=[]}}N I D(b).2q(d)}N G(D.1D(d))I D(S)[D.17.27?"27":"43"](d);I 7.6Y(D.2d(d))},5w:"1.2.6",8G:H(){I 7.K},K:0,3p:H(a){I a==12?D.2d(7):7[a]},2I:H(b){J a=D(b);a.5n=7;I a},6Y:H(a){7.K=0;2p.44.1p.1w(7,a);I 7},P:H(a,b){I D.P(7,a,b)},5i:H(b){J a=-1;I D.2L(b&&b.5w?b[0]:b,7)},1K:H(c,a,b){J d=c;G(c.1q==56)G(a===12)I 7[0]&&D[b||"1K"](7[0],c);N{d={};d[c]=a}I 7.P(H(i){R(c 1n d)D.1K(b?7.V:7,c,D.1i(7,d[c],b,i,c))})},1g:H(b,a){G((b==\'2h\'||b==\'1Z\')&&3d(a)<0)a=12;I 7.1K(b,a,"2a")},1r:H(b){G(1j b!="49"&&b!=U)I 7.4E().3v((7[0]&&7[0].2z||S).5F(b));J a="";D.P(b||7,H(){D.P(7.3t,H(){G(7.16!=8)a+=7.16!=1?7.76:D.17.1r([7])})});I a},5z:H(b){G(7[0])D(b,7[0].2z).5y().39(7[0]).2l(H(){J a=7;1B(a.1x)a=a.1x;I a}).3v(7);I 7},8Y:H(a){I 7.P(H(){D(7).6Q().5z(a)})},8R:H(a){I 7.P(H(){D(7).5z(a)})},3v:H(){I 7.3W(19,M,Q,H(a){G(7.16==1)7.3U(a)})},6F:H(){I 7.3W(19,M,M,H(a){G(7.16==1)7.39(a,7.1x)})},6E:H(){I 7.3W(19,Q,Q,H(a){7.1d.39(a,7)})},5q:H(){I 7.3W(19,Q,M,H(a){7.1d.39(a,7.2H)})},3l:H(){I 7.5n||D([])},2q:H(b){J c=D.2l(7,H(a){I D.2q(b,a)});I 7.2I(/[^+>] [^+>]/.11(b)||b.1h("..")>-1?D.4r(c):c)},5y:H(e){J f=7.2l(H(){G(D.14.1f&&!D.4n(7)){J a=7.6o(M),5h=S.3h("1v");5h.3U(a);I D.4h([5h.4H])[0]}N I 7.6o(M)});J d=f.2q("*").5c().P(H(){G(7[E]!=12)7[E]=U});G(e===M)7.2q("*").5c().P(H(i){G(7.16==3)I;J c=D.L(7,"3w");R(J a 1n c)R(J b 1n c[a])D.W.1e(d[i],a,c[a][b],c[a][b].L)});I f},1E:H(b){I 7.2I(D.1D(b)&&D.3C(7,H(a,i){I b.1k(a,i)})||D.3g(b,7))},4Y:H(b){G(b.1q==56)G(62.11(b))I 7.2I(D.3g(b,7,M));N b=D.3g(b,7);J a=b.K&&b[b.K-1]!==12&&!b.16;I 7.1E(H(){I a?D.2L(7,b)<0:7!=b})},1e:H(a){I 7.2I(D.4r(D.2R(7.3p(),1j a==\'23\'?D(a):D.2d(a))))},3F:H(a){I!!a&&D.3g(a,7).K>0},7T:H(a){I 7.3F("."+a)},6e:H(b){G(b==12){G(7.K){J c=7[0];G(D.Y(c,"2A")){J e=c.64,63=[],15=c.15,2V=c.O=="2A-2V";G(e<0)I U;R(J i=2V?e:0,2f=2V?e+1:15.K;i<2f;i++){J d=15[i];G(d.2W){b=D.14.1f&&!d.at.2x.an?d.1r:d.2x;G(2V)I b;63.1p(b)}}I 63}N I(7[0].2x||"").1o(/\\r/g,"")}I 12}G(b.1q==4L)b+=\'\';I 7.P(H(){G(7.16!=1)I;G(b.1q==2p&&/5O|5L/.11(7.O))7.4J=(D.2L(7.2x,b)>=0||D.2L(7.34,b)>=0);N G(D.Y(7,"2A")){J a=D.2d(b);D("9R",7).P(H(){7.2W=(D.2L(7.2x,a)>=0||D.2L(7.1r,a)>=0)});G(!a.K)7.64=-1}N 7.2x=b})},2K:H(a){I a==12?(7[0]?7[0].4H:U):7.4E().3v(a)},7b:H(a){I 7.5q(a).21()},79:H(i){I 7.3s(i,i+1)},3s:H(){I 7.2I(2p.44.3s.1w(7,19))},2l:H(b){I 7.2I(D.2l(7,H(a,i){I b.1k(a,i,a)}))},5c:H(){I 7.1e(7.5n)},L:H(d,b){J a=d.1R(".");a[1]=a[1]?"."+a[1]:"";G(b===12){J c=7.5C("9z"+a[1]+"!",[a[0]]);G(c===12&&7.K)c=D.L(7[0],d);I c===12&&a[1]?7.L(a[0]):c}N I 7.1P("9u"+a[1]+"!",[a[0],b]).P(H(){D.L(7,d,b)})},3b:H(a){I 7.P(H(){D.3b(7,a)})},3W:H(g,f,h,d){J e=7.K>1,3x;I 7.P(H(){G(!3x){3x=D.4h(g,7.2z);G(h)3x.9o()}J b=7;G(f&&D.Y(7,"1T")&&D.Y(3x[0],"4F"))b=7.3H("22")[0]||7.3U(7.2z.3h("22"));J c=D([]);D.P(3x,H(){J a=e?D(7).5y(M)[0]:7;G(D.Y(a,"1m"))c=c.1e(a);N{G(a.16==1)c=c.1e(D("1m",a).21());d.1k(b,a)}});c.P(6T)})}};D.17.5j.44=D.17;H 6T(i,a){G(a.4d)D.3Y({1a:a.4d,31:Q,1O:"1m"});N D.5u(a.1r||a.6O||a.4H||"");G(a.1d)a.1d.37(a)}H 1z(){I+2B 8J}D.1l=D.17.1l=H(){J b=19[0]||{},i=1,K=19.K,4x=Q,15;G(b.1q==8I){4x=b;b=19[1]||{};i=2}G(1j b!="49"&&1j b!="H")b={};G(K==i){b=7;--i}R(;i<K;i++)G((15=19[i])!=U)R(J c 1n 15){J a=b[c],2w=15[c];G(b===2w)6M;G(4x&&2w&&1j 2w=="49"&&!2w.16)b[c]=D.1l(4x,a||(2w.K!=U?[]:{}),2w);N G(2w!==12)b[c]=2w}I b};J E="4M"+1z(),6K=0,5r={},6G=/z-?5i|8B-?8A|1y|6B|8v-?1Z/i,3P=S.3P||{};D.1l({8u:H(a){1b.$=3m$;G(a)1b.4M=w;I D},1D:H(a){I!!a&&1j a!="23"&&!a.Y&&a.1q!=2p&&/^[\\s[]?H/.11(a+"")},4n:H(a){I a.1C&&!a.1c||a.2j&&a.2z&&!a.2z.1c},5u:H(a){a=D.3k(a);G(a){J b=S.3H("6w")[0]||S.1C,1m=S.3h("1m");1m.O="1r/4t";G(D.14.1f)1m.1r=a;N 1m.3U(S.5F(a));b.39(1m,b.1x);b.37(1m)}},Y:H(b,a){I b.Y&&b.Y.2r()==a.2r()},1Y:{},L:H(c,d,b){c=c==1b?5r:c;J a=c[E];G(!a)a=c[E]=++6K;G(d&&!D.1Y[a])D.1Y[a]={};G(b!==12)D.1Y[a][d]=b;I d?D.1Y[a][d]:a},3b:H(c,b){c=c==1b?5r:c;J a=c[E];G(b){G(D.1Y[a]){2U D.1Y[a][b];b="";R(b 1n D.1Y[a])1X;G(!b)D.3b(c)}}N{1U{2U c[E]}1V(e){G(c.5l)c.5l(E)}2U D.1Y[a]}},P:H(d,a,c){J e,i=0,K=d.K;G(c){G(K==12){R(e 1n d)G(a.1w(d[e],c)===Q)1X}N R(;i<K;)G(a.1w(d[i++],c)===Q)1X}N{G(K==12){R(e 1n d)G(a.1k(d[e],e,d[e])===Q)1X}N R(J b=d[0];i<K&&a.1k(b,i,b)!==Q;b=d[++i]){}}I d},1i:H(b,a,c,i,d){G(D.1D(a))a=a.1k(b,i);I a&&a.1q==4L&&c=="2a"&&!6G.11(d)?a+"2X":a},1F:{1e:H(c,b){D.P((b||"").1R(/\\s+/),H(i,a){G(c.16==1&&!D.1F.3T(c.1F,a))c.1F+=(c.1F?" ":"")+a})},21:H(c,b){G(c.16==1)c.1F=b!=12?D.3C(c.1F.1R(/\\s+/),H(a){I!D.1F.3T(b,a)}).6s(" "):""},3T:H(b,a){I D.2L(a,(b.1F||b).6r().1R(/\\s+/))>-1}},6q:H(b,c,a){J e={};R(J d 1n c){e[d]=b.V[d];b.V[d]=c[d]}a.1k(b);R(J d 1n c)b.V[d]=e[d]},1g:H(d,e,c){G(e=="2h"||e=="1Z"){J b,3X={30:"5x",5g:"1G",18:"3I"},35=e=="2h"?["5e","6k"]:["5G","6i"];H 5b(){b=e=="2h"?d.8f:d.8c;J a=0,2C=0;D.P(35,H(){a+=3d(D.2a(d,"57"+7,M))||0;2C+=3d(D.2a(d,"2C"+7+"4b",M))||0});b-=29.83(a+2C)}G(D(d).3F(":4j"))5b();N D.6q(d,3X,5b);I 29.2f(0,b)}I D.2a(d,e,c)},2a:H(f,l,k){J e,V=f.V;H 3E(b){G(!D.14.2k)I Q;J a=3P.54(b,U);I!a||a.52("3E")==""}G(l=="1y"&&D.14.1f){e=D.1K(V,"1y");I e==""?"1":e}G(D.14.2G&&l=="18"){J d=V.50;V.50="0 7Y 7W";V.50=d}G(l.1I(/4i/i))l=y;G(!k&&V&&V[l])e=V[l];N G(3P.54){G(l.1I(/4i/i))l="4i";l=l.1o(/([A-Z])/g,"-$1").3y();J c=3P.54(f,U);G(c&&!3E(f))e=c.52(l);N{J g=[],2E=[],a=f,i=0;R(;a&&3E(a);a=a.1d)2E.6h(a);R(;i<2E.K;i++)G(3E(2E[i])){g[i]=2E[i].V.18;2E[i].V.18="3I"}e=l=="18"&&g[2E.K-1]!=U?"2F":(c&&c.52(l))||"";R(i=0;i<g.K;i++)G(g[i]!=U)2E[i].V.18=g[i]}G(l=="1y"&&e=="")e="1"}N G(f.4g){J h=l.1o(/\\-(\\w)/g,H(a,b){I b.2r()});e=f.4g[l]||f.4g[h];G(!/^\\d+(2X)?$/i.11(e)&&/^\\d/.11(e)){J j=V.1A,66=f.65.1A;f.65.1A=f.4g.1A;V.1A=e||0;e=V.aM+"2X";V.1A=j;f.65.1A=66}}I e},4h:H(l,h){J k=[];h=h||S;G(1j h.3h==\'12\')h=h.2z||h[0]&&h[0].2z||S;D.P(l,H(i,d){G(!d)I;G(d.1q==4L)d+=\'\';G(1j d=="23"){d=d.1o(/(<(\\w+)[^>]*?)\\/>/g,H(b,a,c){I c.1I(/^(aK|4f|7E|aG|4T|7A|aB|3n|az|ay|av)$/i)?b:a+"></"+c+">"});J f=D.3k(d).3y(),1v=h.3h("1v");J e=!f.1h("<au")&&[1,"<2A 7w=\'7w\'>","</2A>"]||!f.1h("<ar")&&[1,"<7v>","</7v>"]||f.1I(/^<(aq|22|am|ak|ai)/)&&[1,"<1T>","</1T>"]||!f.1h("<4F")&&[2,"<1T><22>","</22></1T>"]||(!f.1h("<af")||!f.1h("<ad"))&&[3,"<1T><22><4F>","</4F></22></1T>"]||!f.1h("<7E")&&[2,"<1T><22></22><7q>","</7q></1T>"]||D.14.1f&&[1,"1v<1v>","</1v>"]||[0,"",""];1v.4H=e[1]+d+e[2];1B(e[0]--)1v=1v.5T;G(D.14.1f){J g=!f.1h("<1T")&&f.1h("<22")<0?1v.1x&&1v.1x.3t:e[1]=="<1T>"&&f.1h("<22")<0?1v.3t:[];R(J j=g.K-1;j>=0;--j)G(D.Y(g[j],"22")&&!g[j].3t.K)g[j].1d.37(g[j]);G(/^\\s/.11(d))1v.39(h.5F(d.1I(/^\\s*/)[0]),1v.1x)}d=D.2d(1v.3t)}G(d.K===0&&(!D.Y(d,"3V")&&!D.Y(d,"2A")))I;G(d[0]==12||D.Y(d,"3V")||d.15)k.1p(d);N k=D.2R(k,d)});I k},1K:H(d,f,c){G(!d||d.16==3||d.16==8)I 12;J e=!D.4n(d),40=c!==12,1f=D.14.1f;f=e&&D.3X[f]||f;G(d.2j){J g=/5Q|4d|V/.11(f);G(f=="2W"&&D.14.2k)d.1d.64;G(f 1n d&&e&&!g){G(40){G(f=="O"&&D.Y(d,"4T")&&d.1d)7p"O a3 a1\'t 9V 9U";d[f]=c}G(D.Y(d,"3V")&&d.7i(f))I d.7i(f).76;I d[f]}G(1f&&e&&f=="V")I D.1K(d.V,"9T",c);G(40)d.9Q(f,""+c);J h=1f&&e&&g?d.4G(f,2):d.4G(f);I h===U?12:h}G(1f&&f=="1y"){G(40){d.6B=1;d.1E=(d.1E||"").1o(/7f\\([^)]*\\)/,"")+(3r(c)+\'\'=="9L"?"":"7f(1y="+c*7a+")")}I d.1E&&d.1E.1h("1y=")>=0?(3d(d.1E.1I(/1y=([^)]*)/)[1])/7a)+\'\':""}f=f.1o(/-([a-z])/9H,H(a,b){I b.2r()});G(40)d[f]=c;I d[f]},3k:H(a){I(a||"").1o(/^\\s+|\\s+$/g,"")},2d:H(b){J a=[];G(b!=U){J i=b.K;G(i==U||b.1R||b.4I||b.1k)a[0]=b;N 1B(i)a[--i]=b[i]}I a},2L:H(b,a){R(J i=0,K=a.K;i<K;i++)G(a[i]===b)I i;I-1},2R:H(a,b){J i=0,T,2S=a.K;G(D.14.1f){1B(T=b[i++])G(T.16!=8)a[2S++]=T}N 1B(T=b[i++])a[2S++]=T;I a},4r:H(a){J c=[],2o={};1U{R(J i=0,K=a.K;i<K;i++){J b=D.L(a[i]);G(!2o[b]){2o[b]=M;c.1p(a[i])}}}1V(e){c=a}I c},3C:H(c,a,d){J b=[];R(J i=0,K=c.K;i<K;i++)G(!d!=!a(c[i],i))b.1p(c[i]);I b},2l:H(d,a){J c=[];R(J i=0,K=d.K;i<K;i++){J b=a(d[i],i);G(b!=U)c[c.K]=b}I c.7d.1w([],c)}});J v=9B.9A.3y();D.14={5B:(v.1I(/.+(?:9y|9x|9w|9v)[\\/: ]([\\d.]+)/)||[])[1],2k:/75/.11(v),2G:/2G/.11(v),1f:/1f/.11(v)&&!/2G/.11(v),42:/42/.11(v)&&!/(9s|75)/.11(v)};J y=D.14.1f?"7o":"72";D.1l({71:!D.14.1f||S.70=="6Z",3X:{"R":"9n","9k":"1F","4i":y,72:y,7o:y,9h:"9f",9e:"9d",9b:"99"}});D.P({6W:H(a){I a.1d},97:H(a){I D.4S(a,"1d")},95:H(a){I D.3a(a,2,"2H")},91:H(a){I D.3a(a,2,"4l")},8Z:H(a){I D.4S(a,"2H")},8X:H(a){I D.4S(a,"4l")},8W:H(a){I D.5v(a.1d.1x,a)},8V:H(a){I D.5v(a.1x)},6Q:H(a){I D.Y(a,"8U")?a.8T||a.8S.S:D.2d(a.3t)}},H(c,d){D.17[c]=H(b){J a=D.2l(7,d);G(b&&1j b=="23")a=D.3g(b,a);I 7.2I(D.4r(a))}});D.P({6P:"3v",8Q:"6F",39:"6E",8P:"5q",8O:"7b"},H(c,b){D.17[c]=H(){J a=19;I 7.P(H(){R(J i=0,K=a.K;i<K;i++)D(a[i])[b](7)})}});D.P({8N:H(a){D.1K(7,a,"");G(7.16==1)7.5l(a)},8M:H(a){D.1F.1e(7,a)},8L:H(a){D.1F.21(7,a)},8K:H(a){D.1F[D.1F.3T(7,a)?"21":"1e"](7,a)},21:H(a){G(!a||D.1E(a,[7]).r.K){D("*",7).1e(7).P(H(){D.W.21(7);D.3b(7)});G(7.1d)7.1d.37(7)}},4E:H(){D(">*",7).21();1B(7.1x)7.37(7.1x)}},H(a,b){D.17[a]=H(){I 7.P(b,19)}});D.P(["6N","4b"],H(i,c){J b=c.3y();D.17[b]=H(a){I 7[0]==1b?D.14.2G&&S.1c["5t"+c]||D.14.2k&&1b["5s"+c]||S.70=="6Z"&&S.1C["5t"+c]||S.1c["5t"+c]:7[0]==S?29.2f(29.2f(S.1c["4y"+c],S.1C["4y"+c]),29.2f(S.1c["2i"+c],S.1C["2i"+c])):a==12?(7.K?D.1g(7[0],b):U):7.1g(b,a.1q==56?a:a+"2X")}});H 25(a,b){I a[0]&&3r(D.2a(a[0],b,M),10)||0}J C=D.14.2k&&3r(D.14.5B)<8H?"(?:[\\\\w*3m-]|\\\\\\\\.)":"(?:[\\\\w\\8F-\\8E*3m-]|\\\\\\\\.)",6L=2B 4v("^>\\\\s*("+C+"+)"),6J=2B 4v("^("+C+"+)(#)("+C+"+)"),6I=2B 4v("^([#.]?)("+C+"*)");D.1l({6H:{"":H(a,i,m){I m[2]=="*"||D.Y(a,m[2])},"#":H(a,i,m){I a.4G("2v")==m[2]},":":{8D:H(a,i,m){I i<m[3]-0},8C:H(a,i,m){I i>m[3]-0},3a:H(a,i,m){I m[3]-0==i},79:H(a,i,m){I m[3]-0==i},3o:H(a,i){I i==0},3S:H(a,i,m,r){I i==r.K-1},6D:H(a,i){I i%2==0},6C:H(a,i){I i%2},"3o-4u":H(a){I a.1d.3H("*")[0]==a},"3S-4u":H(a){I D.3a(a.1d.5T,1,"4l")==a},"8z-4u":H(a){I!D.3a(a.1d.5T,2,"4l")},6W:H(a){I a.1x},4E:H(a){I!a.1x},8y:H(a,i,m){I(a.6O||a.8x||D(a).1r()||"").1h(m[3])>=0},4j:H(a){I"1G"!=a.O&&D.1g(a,"18")!="2F"&&D.1g(a,"5g")!="1G"},1G:H(a){I"1G"==a.O||D.1g(a,"18")=="2F"||D.1g(a,"5g")=="1G"},8w:H(a){I!a.3R},3R:H(a){I a.3R},4J:H(a){I a.4J},2W:H(a){I a.2W||D.1K(a,"2W")},1r:H(a){I"1r"==a.O},5O:H(a){I"5O"==a.O},5L:H(a){I"5L"==a.O},5p:H(a){I"5p"==a.O},3Q:H(a){I"3Q"==a.O},5o:H(a){I"5o"==a.O},6A:H(a){I"6A"==a.O},6z:H(a){I"6z"==a.O},2s:H(a){I"2s"==a.O||D.Y(a,"2s")},4T:H(a){I/4T|2A|6y|2s/i.11(a.Y)},3T:H(a,i,m){I D.2q(m[3],a).K},8t:H(a){I/h\\d/i.11(a.Y)},8s:H(a){I D.3C(D.3O,H(b){I a==b.T}).K}}},6x:[/^(\\[) *@?([\\w-]+) *([!*$^~=]*) *(\'?"?)(.*?)\\4 *\\]/,/^(:)([\\w-]+)\\("?\'?(.*?(\\(.*?\\))?[^(]*?)"?\'?\\)/,2B 4v("^([:.#]*)("+C+"+)")],3g:H(a,c,b){J d,1t=[];1B(a&&a!=d){d=a;J f=D.1E(a,c,b);a=f.t.1o(/^\\s*,\\s*/,"");1t=b?c=f.r:D.2R(1t,f.r)}I 1t},2q:H(t,o){G(1j t!="23")I[t];G(o&&o.16!=1&&o.16!=9)I[];o=o||S;J d=[o],2o=[],3S,Y;1B(t&&3S!=t){J r=[];3S=t;t=D.3k(t);J l=Q,3j=6L,m=3j.2D(t);G(m){Y=m[1].2r();R(J i=0;d[i];i++)R(J c=d[i].1x;c;c=c.2H)G(c.16==1&&(Y=="*"||c.Y.2r()==Y))r.1p(c);d=r;t=t.1o(3j,"");G(t.1h(" ")==0)6M;l=M}N{3j=/^([>+~])\\s*(\\w*)/i;G((m=3j.2D(t))!=U){r=[];J k={};Y=m[2].2r();m=m[1];R(J j=0,3i=d.K;j<3i;j++){J n=m=="~"||m=="+"?d[j].2H:d[j].1x;R(;n;n=n.2H)G(n.16==1){J g=D.L(n);G(m=="~"&&k[g])1X;G(!Y||n.Y.2r()==Y){G(m=="~")k[g]=M;r.1p(n)}G(m=="+")1X}}d=r;t=D.3k(t.1o(3j,""));l=M}}G(t&&!l){G(!t.1h(",")){G(o==d[0])d.4s();2o=D.2R(2o,d);r=d=[o];t=" "+t.6v(1,t.K)}N{J h=6J;J m=h.2D(t);G(m){m=[0,m[2],m[3],m[1]]}N{h=6I;m=h.2D(t)}m[2]=m[2].1o(/\\\\/g,"");J f=d[d.K-1];G(m[1]=="#"&&f&&f.61&&!D.4n(f)){J p=f.61(m[2]);G((D.14.1f||D.14.2G)&&p&&1j p.2v=="23"&&p.2v!=m[2])p=D(\'[@2v="\'+m[2]+\'"]\',f)[0];d=r=p&&(!m[3]||D.Y(p,m[3]))?[p]:[]}N{R(J i=0;d[i];i++){J a=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]==""?"*":m[2];G(a=="*"&&d[i].Y.3y()=="49")a="3n";r=D.2R(r,d[i].3H(a))}G(m[1]==".")r=D.5m(r,m[2]);G(m[1]=="#"){J e=[];R(J i=0;r[i];i++)G(r[i].4G("2v")==m[2]){e=[r[i]];1X}r=e}d=r}t=t.1o(h,"")}}G(t){J b=D.1E(t,r);d=r=b.r;t=D.3k(b.t)}}G(t)d=[];G(d&&o==d[0])d.4s();2o=D.2R(2o,d);I 2o},5m:H(r,m,a){m=" "+m+" ";J c=[];R(J i=0;r[i];i++){J b=(" "+r[i].1F+" ").1h(m)>=0;G(!a&&b||a&&!b)c.1p(r[i])}I c},1E:H(t,r,h){J d;1B(t&&t!=d){d=t;J p=D.6x,m;R(J i=0;p[i];i++){m=p[i].2D(t);G(m){t=t.8r(m[0].K);m[2]=m[2].1o(/\\\\/g,"");1X}}G(!m)1X;G(m[1]==":"&&m[2]=="4Y")r=62.11(m[3])?D.1E(m[3],r,M).r:D(r).4Y(m[3]);N G(m[1]==".")r=D.5m(r,m[2],h);N G(m[1]=="["){J g=[],O=m[3];R(J i=0,3i=r.K;i<3i;i++){J a=r[i],z=a[D.3X[m[2]]||m[2]];G(z==U||/5Q|4d|2W/.11(m[2]))z=D.1K(a,m[2])||\'\';G((O==""&&!!z||O=="="&&z==m[5]||O=="!="&&z!=m[5]||O=="^="&&z&&!z.1h(m[5])||O=="$="&&z.6v(z.K-m[5].K)==m[5]||(O=="*="||O=="~=")&&z.1h(m[5])>=0)^h)g.1p(a)}r=g}N G(m[1]==":"&&m[2]=="3a-4u"){J e={},g=[],11=/(-?)(\\d*)n((?:\\+|-)?\\d*)/.2D(m[3]=="6D"&&"2n"||m[3]=="6C"&&"2n+1"||!/\\D/.11(m[3])&&"8q+"+m[3]||m[3]),3o=(11[1]+(11[2]||1))-0,d=11[3]-0;R(J i=0,3i=r.K;i<3i;i++){J j=r[i],1d=j.1d,2v=D.L(1d);G(!e[2v]){J c=1;R(J n=1d.1x;n;n=n.2H)G(n.16==1)n.4q=c++;e[2v]=M}J b=Q;G(3o==0){G(j.4q==d)b=M}N G((j.4q-d)%3o==0&&(j.4q-d)/3o>=0)b=M;G(b^h)g.1p(j)}r=g}N{J f=D.6H[m[1]];G(1j f=="49")f=f[m[2]];G(1j f=="23")f=6u("Q||H(a,i){I "+f+";}");r=D.3C(r,H(a,i){I f(a,i,m,r)},h)}}I{r:r,t:t}},4S:H(b,c){J a=[],1t=b[c];1B(1t&&1t!=S){G(1t.16==1)a.1p(1t);1t=1t[c]}I a},3a:H(a,e,c,b){e=e||1;J d=0;R(;a;a=a[c])G(a.16==1&&++d==e)1X;I a},5v:H(n,a){J r=[];R(;n;n=n.2H){G(n.16==1&&n!=a)r.1p(n)}I r}});D.W={1e:H(f,i,g,e){G(f.16==3||f.16==8)I;G(D.14.1f&&f.4I)f=1b;G(!g.24)g.24=7.24++;G(e!=12){J h=g;g=7.3M(h,H(){I h.1w(7,19)});g.L=e}J j=D.L(f,"3w")||D.L(f,"3w",{}),1H=D.L(f,"1H")||D.L(f,"1H",H(){G(1j D!="12"&&!D.W.5k)I D.W.1H.1w(19.3L.T,19)});1H.T=f;D.P(i.1R(/\\s+/),H(c,b){J a=b.1R(".");b=a[0];g.O=a[1];J d=j[b];G(!d){d=j[b]={};G(!D.W.2t[b]||D.W.2t[b].4p.1k(f)===Q){G(f.3K)f.3K(b,1H,Q);N G(f.6t)f.6t("4o"+b,1H)}}d[g.24]=g;D.W.26[b]=M});f=U},24:1,26:{},21:H(e,h,f){G(e.16==3||e.16==8)I;J i=D.L(e,"3w"),1L,5i;G(i){G(h==12||(1j h=="23"&&h.8p(0)=="."))R(J g 1n i)7.21(e,g+(h||""));N{G(h.O){f=h.2y;h=h.O}D.P(h.1R(/\\s+/),H(b,a){J c=a.1R(".");a=c[0];G(i[a]){G(f)2U i[a][f.24];N R(f 1n i[a])G(!c[1]||i[a][f].O==c[1])2U i[a][f];R(1L 1n i[a])1X;G(!1L){G(!D.W.2t[a]||D.W.2t[a].4A.1k(e)===Q){G(e.6p)e.6p(a,D.L(e,"1H"),Q);N G(e.6n)e.6n("4o"+a,D.L(e,"1H"))}1L=U;2U i[a]}}})}R(1L 1n i)1X;G(!1L){J d=D.L(e,"1H");G(d)d.T=U;D.3b(e,"3w");D.3b(e,"1H")}}},1P:H(h,c,f,g,i){c=D.2d(c);G(h.1h("!")>=0){h=h.3s(0,-1);J a=M}G(!f){G(7.26[h])D("*").1e([1b,S]).1P(h,c)}N{G(f.16==3||f.16==8)I 12;J b,1L,17=D.1D(f[h]||U),W=!c[0]||!c[0].32;G(W){c.6h({O:h,2J:f,32:H(){},3J:H(){},4C:1z()});c[0][E]=M}c[0].O=h;G(a)c[0].6m=M;J d=D.L(f,"1H");G(d)b=d.1w(f,c);G((!17||(D.Y(f,\'a\')&&h=="4V"))&&f["4o"+h]&&f["4o"+h].1w(f,c)===Q)b=Q;G(W)c.4s();G(i&&D.1D(i)){1L=i.1w(f,b==U?c:c.7d(b));G(1L!==12)b=1L}G(17&&g!==Q&&b!==Q&&!(D.Y(f,\'a\')&&h=="4V")){7.5k=M;1U{f[h]()}1V(e){}}7.5k=Q}I b},1H:H(b){J a,1L,38,5f,4m;b=19[0]=D.W.6l(b||1b.W);38=b.O.1R(".");b.O=38[0];38=38[1];5f=!38&&!b.6m;4m=(D.L(7,"3w")||{})[b.O];R(J j 1n 4m){J c=4m[j];G(5f||c.O==38){b.2y=c;b.L=c.L;1L=c.1w(7,19);G(a!==Q)a=1L;G(1L===Q){b.32();b.3J()}}}I a},6l:H(b){G(b[E]==M)I b;J d=b;b={8o:d};J c="8n 8m 8l 8k 2s 8j 47 5d 6j 5E 8i L 8h 8g 4K 2y 5a 59 8e 8b 58 6f 8a 88 4k 87 86 84 6d 2J 4C 6c O 82 81 35".1R(" ");R(J i=c.K;i;i--)b[c[i]]=d[c[i]];b[E]=M;b.32=H(){G(d.32)d.32();d.80=Q};b.3J=H(){G(d.3J)d.3J();d.7Z=M};b.4C=b.4C||1z();G(!b.2J)b.2J=b.6d||S;G(b.2J.16==3)b.2J=b.2J.1d;G(!b.4k&&b.4K)b.4k=b.4K==b.2J?b.6c:b.4K;G(b.58==U&&b.5d!=U){J a=S.1C,1c=S.1c;b.58=b.5d+(a&&a.2e||1c&&1c.2e||0)-(a.6b||0);b.6f=b.6j+(a&&a.2c||1c&&1c.2c||0)-(a.6a||0)}G(!b.35&&((b.47||b.47===0)?b.47:b.5a))b.35=b.47||b.5a;G(!b.59&&b.5E)b.59=b.5E;G(!b.35&&b.2s)b.35=(b.2s&1?1:(b.2s&2?3:(b.2s&4?2:0)));I b},3M:H(a,b){b.24=a.24=a.24||b.24||7.24++;I b},2t:{27:{4p:H(){55();I},4A:H(){I}},3D:{4p:H(){G(D.14.1f)I Q;D(7).2O("53",D.W.2t.3D.2y);I M},4A:H(){G(D.14.1f)I Q;D(7).4e("53",D.W.2t.3D.2y);I M},2y:H(a){G(F(a,7))I M;a.O="3D";I D.W.1H.1w(7,19)}},3N:{4p:H(){G(D.14.1f)I Q;D(7).2O("51",D.W.2t.3N.2y);I M},4A:H(){G(D.14.1f)I Q;D(7).4e("51",D.W.2t.3N.2y);I M},2y:H(a){G(F(a,7))I M;a.O="3N";I D.W.1H.1w(7,19)}}}};D.17.1l({2O:H(c,a,b){I c=="4X"?7.2V(c,a,b):7.P(H(){D.W.1e(7,c,b||a,b&&a)})},2V:H(d,b,c){J e=D.W.3M(c||b,H(a){D(7).4e(a,e);I(c||b).1w(7,19)});I 7.P(H(){D.W.1e(7,d,e,c&&b)})},4e:H(a,b){I 7.P(H(){D.W.21(7,a,b)})},1P:H(c,a,b){I 7.P(H(){D.W.1P(c,a,7,M,b)})},5C:H(c,a,b){I 7[0]&&D.W.1P(c,a,7[0],Q,b)},2m:H(b){J c=19,i=1;1B(i<c.K)D.W.3M(b,c[i++]);I 7.4V(D.W.3M(b,H(a){7.4Z=(7.4Z||0)%i;a.32();I c[7.4Z++].1w(7,19)||Q}))},7X:H(a,b){I 7.2O(\'3D\',a).2O(\'3N\',b)},27:H(a){55();G(D.2Q)a.1k(S,D);N D.3A.1p(H(){I a.1k(7,D)});I 7}});D.1l({2Q:Q,3A:[],27:H(){G(!D.2Q){D.2Q=M;G(D.3A){D.P(D.3A,H(){7.1k(S)});D.3A=U}D(S).5C("27")}}});J x=Q;H 55(){G(x)I;x=M;G(S.3K&&!D.14.2G)S.3K("69",D.27,Q);G(D.14.1f&&1b==1S)(H(){G(D.2Q)I;1U{S.1C.7V("1A")}1V(3e){3B(19.3L,0);I}D.27()})();G(D.14.2G)S.3K("69",H(){G(D.2Q)I;R(J i=0;i<S.4W.K;i++)G(S.4W[i].3R){3B(19.3L,0);I}D.27()},Q);G(D.14.2k){J a;(H(){G(D.2Q)I;G(S.3f!="68"&&S.3f!="1J"){3B(19.3L,0);I}G(a===12)a=D("V, 7A[7U=7S]").K;G(S.4W.K!=a){3B(19.3L,0);I}D.27()})()}D.W.1e(1b,"43",D.27)}D.P(("7R,7Q,43,85,4y,4X,4V,7P,"+"7O,7N,89,53,51,7M,2A,"+"5o,7L,7K,8d,3e").1R(","),H(i,b){D.17[b]=H(a){I a?7.2O(b,a):7.1P(b)}});J F=H(a,c){J b=a.4k;1B(b&&b!=c)1U{b=b.1d}1V(3e){b=c}I b==c};D(1b).2O("4X",H(){D("*").1e(S).4e()});D.17.1l({67:D.17.43,43:H(g,d,c){G(1j g!=\'23\')I 7.67(g);J e=g.1h(" ");G(e>=0){J i=g.3s(e,g.K);g=g.3s(0,e)}c=c||H(){};J f="2P";G(d)G(D.1D(d)){c=d;d=U}N{d=D.3n(d);f="6g"}J h=7;D.3Y({1a:g,O:f,1O:"2K",L:d,1J:H(a,b){G(b=="1W"||b=="7J")h.2K(i?D("<1v/>").3v(a.4U.1o(/<1m(.|\\s)*?\\/1m>/g,"")).2q(i):a.4U);h.P(c,[a.4U,b,a])}});I 7},aL:H(){I D.3n(7.7I())},7I:H(){I 7.2l(H(){I D.Y(7,"3V")?D.2d(7.aH):7}).1E(H(){I 7.34&&!7.3R&&(7.4J||/2A|6y/i.11(7.Y)||/1r|1G|3Q/i.11(7.O))}).2l(H(i,c){J b=D(7).6e();I b==U?U:b.1q==2p?D.2l(b,H(a,i){I{34:c.34,2x:a}}):{34:c.34,2x:b}}).3p()}});D.P("7H,7G,7F,7D,7C,7B".1R(","),H(i,o){D.17[o]=H(f){I 7.2O(o,f)}});J B=1z();D.1l({3p:H(d,b,a,c){G(D.1D(b)){a=b;b=U}I D.3Y({O:"2P",1a:d,L:b,1W:a,1O:c})},aE:H(b,a){I D.3p(b,U,a,"1m")},aD:H(c,b,a){I D.3p(c,b,a,"3z")},aC:H(d,b,a,c){G(D.1D(b)){a=b;b={}}I D.3Y({O:"6g",1a:d,L:b,1W:a,1O:c})},aA:H(a){D.1l(D.60,a)},60:{1a:5Z.5Q,26:M,O:"2P",2T:0,7z:"4R/x-ax-3V-aw",7x:M,31:M,L:U,5Y:U,3Q:U,4Q:{2N:"4R/2N, 1r/2N",2K:"1r/2K",1m:"1r/4t, 4R/4t",3z:"4R/3z, 1r/4t",1r:"1r/as",4w:"*/*"}},4z:{},3Y:H(s){s=D.1l(M,s,D.1l(M,{},D.60,s));J g,2Z=/=\\?(&|$)/g,1u,L,O=s.O.2r();G(s.L&&s.7x&&1j s.L!="23")s.L=D.3n(s.L);G(s.1O=="4P"){G(O=="2P"){G(!s.1a.1I(2Z))s.1a+=(s.1a.1I(/\\?/)?"&":"?")+(s.4P||"7u")+"=?"}N G(!s.L||!s.L.1I(2Z))s.L=(s.L?s.L+"&":"")+(s.4P||"7u")+"=?";s.1O="3z"}G(s.1O=="3z"&&(s.L&&s.L.1I(2Z)||s.1a.1I(2Z))){g="4P"+B++;G(s.L)s.L=(s.L+"").1o(2Z,"="+g+"$1");s.1a=s.1a.1o(2Z,"="+g+"$1");s.1O="1m";1b[g]=H(a){L=a;1W();1J();1b[g]=12;1U{2U 1b[g]}1V(e){}G(i)i.37(h)}}G(s.1O=="1m"&&s.1Y==U)s.1Y=Q;G(s.1Y===Q&&O=="2P"){J j=1z();J k=s.1a.1o(/(\\?|&)3m=.*?(&|$)/,"$ap="+j+"$2");s.1a=k+((k==s.1a)?(s.1a.1I(/\\?/)?"&":"?")+"3m="+j:"")}G(s.L&&O=="2P"){s.1a+=(s.1a.1I(/\\?/)?"&":"?")+s.L;s.L=U}G(s.26&&!D.4O++)D.W.1P("7H");J n=/^(?:\\w+:)?\\/\\/([^\\/?#]+)/;G(s.1O=="1m"&&O=="2P"&&n.11(s.1a)&&n.2D(s.1a)[1]!=5Z.al){J i=S.3H("6w")[0];J h=S.3h("1m");h.4d=s.1a;G(s.7t)h.aj=s.7t;G(!g){J l=Q;h.ah=h.ag=H(){G(!l&&(!7.3f||7.3f=="68"||7.3f=="1J")){l=M;1W();1J();i.37(h)}}}i.3U(h);I 12}J m=Q;J c=1b.7s?2B 7s("ae.ac"):2B 7r();G(s.5Y)c.6R(O,s.1a,s.31,s.5Y,s.3Q);N c.6R(O,s.1a,s.31);1U{G(s.L)c.4B("ab-aa",s.7z);G(s.5S)c.4B("a9-5R-a8",D.4z[s.1a]||"a7, a6 a5 a4 5N:5N:5N a2");c.4B("X-9Z-9Y","7r");c.4B("9W",s.1O&&s.4Q[s.1O]?s.4Q[s.1O]+", */*":s.4Q.4w)}1V(e){}G(s.7m&&s.7m(c,s)===Q){s.26&&D.4O--;c.7l();I Q}G(s.26)D.W.1P("7B",[c,s]);J d=H(a){G(!m&&c&&(c.3f==4||a=="2T")){m=M;G(f){7k(f);f=U}1u=a=="2T"&&"2T"||!D.7j(c)&&"3e"||s.5S&&D.7h(c,s.1a)&&"7J"||"1W";G(1u=="1W"){1U{L=D.6X(c,s.1O,s.9S)}1V(e){1u="5J"}}G(1u=="1W"){J b;1U{b=c.5I("7g-5R")}1V(e){}G(s.5S&&b)D.4z[s.1a]=b;G(!g)1W()}N D.5H(s,c,1u);1J();G(s.31)c=U}};G(s.31){J f=4I(d,13);G(s.2T>0)3B(H(){G(c){c.7l();G(!m)d("2T")}},s.2T)}1U{c.9P(s.L)}1V(e){D.5H(s,c,U,e)}G(!s.31)d();H 1W(){G(s.1W)s.1W(L,1u);G(s.26)D.W.1P("7C",[c,s])}H 1J(){G(s.1J)s.1J(c,1u);G(s.26)D.W.1P("7F",[c,s]);G(s.26&&!--D.4O)D.W.1P("7G")}I c},5H:H(s,a,b,e){G(s.3e)s.3e(a,b,e);G(s.26)D.W.1P("7D",[a,s,e])},4O:0,7j:H(a){1U{I!a.1u&&5Z.9O=="5p:"||(a.1u>=7e&&a.1u<9N)||a.1u==7c||a.1u==9K||D.14.2k&&a.1u==12}1V(e){}I Q},7h:H(a,c){1U{J b=a.5I("7g-5R");I a.1u==7c||b==D.4z[c]||D.14.2k&&a.1u==12}1V(e){}I Q},6X:H(a,c,b){J d=a.5I("9J-O"),2N=c=="2N"||!c&&d&&d.1h("2N")>=0,L=2N?a.9I:a.4U;G(2N&&L.1C.2j=="5J")7p"5J";G(b)L=b(L,c);G(c=="1m")D.5u(L);G(c=="3z")L=6u("("+L+")");I L},3n:H(a){J s=[];G(a.1q==2p||a.5w)D.P(a,H(){s.1p(3u(7.34)+"="+3u(7.2x))});N R(J j 1n a)G(a[j]&&a[j].1q==2p)D.P(a[j],H(){s.1p(3u(j)+"="+3u(7))});N s.1p(3u(j)+"="+3u(D.1D(a[j])?a[j]():a[j]));I s.6s("&").1o(/%20/g,"+")}});D.17.1l({1N:H(c,b){I c?7.2g({1Z:"1N",2h:"1N",1y:"1N"},c,b):7.1E(":1G").P(H(){7.V.18=7.5D||"";G(D.1g(7,"18")=="2F"){J a=D("<"+7.2j+" />").6P("1c");7.V.18=a.1g("18");G(7.V.18=="2F")7.V.18="3I";a.21()}}).3l()},1M:H(b,a){I b?7.2g({1Z:"1M",2h:"1M",1y:"1M"},b,a):7.1E(":4j").P(H(){7.5D=7.5D||D.1g(7,"18");7.V.18="2F"}).3l()},78:D.17.2m,2m:H(a,b){I D.1D(a)&&D.1D(b)?7.78.1w(7,19):a?7.2g({1Z:"2m",2h:"2m",1y:"2m"},a,b):7.P(H(){D(7)[D(7).3F(":1G")?"1N":"1M"]()})},9G:H(b,a){I 7.2g({1Z:"1N"},b,a)},9F:H(b,a){I 7.2g({1Z:"1M"},b,a)},9E:H(b,a){I 7.2g({1Z:"2m"},b,a)},9D:H(b,a){I 7.2g({1y:"1N"},b,a)},9M:H(b,a){I 7.2g({1y:"1M"},b,a)},9C:H(c,a,b){I 7.2g({1y:a},c,b)},2g:H(k,j,i,g){J h=D.77(j,i,g);I 7[h.36===Q?"P":"36"](H(){G(7.16!=1)I Q;J f=D.1l({},h),p,1G=D(7).3F(":1G"),46=7;R(p 1n k){G(k[p]=="1M"&&1G||k[p]=="1N"&&!1G)I f.1J.1k(7);G(p=="1Z"||p=="2h"){f.18=D.1g(7,"18");f.33=7.V.33}}G(f.33!=U)7.V.33="1G";f.45=D.1l({},k);D.P(k,H(c,a){J e=2B D.28(46,f,c);G(/2m|1N|1M/.11(a))e[a=="2m"?1G?"1N":"1M":a](k);N{J b=a.6r().1I(/^([+-]=)?([\\d+-.]+)(.*)$/),2b=e.1t(M)||0;G(b){J d=3d(b[2]),2M=b[3]||"2X";G(2M!="2X"){46.V[c]=(d||1)+2M;2b=((d||1)/e.1t(M))*2b;46.V[c]=2b+2M}G(b[1])d=((b[1]=="-="?-1:1)*d)+2b;e.3G(2b,d,2M)}N e.3G(2b,a,"")}});I M})},36:H(a,b){G(D.1D(a)||(a&&a.1q==2p)){b=a;a="28"}G(!a||(1j a=="23"&&!b))I A(7[0],a);I 7.P(H(){G(b.1q==2p)A(7,a,b);N{A(7,a).1p(b);G(A(7,a).K==1)b.1k(7)}})},9X:H(b,c){J a=D.3O;G(b)7.36([]);7.P(H(){R(J i=a.K-1;i>=0;i--)G(a[i].T==7){G(c)a[i](M);a.7n(i,1)}});G(!c)7.5A();I 7}});J A=H(b,c,a){G(b){c=c||"28";J q=D.L(b,c+"36");G(!q||a)q=D.L(b,c+"36",D.2d(a))}I q};D.17.5A=H(a){a=a||"28";I 7.P(H(){J q=A(7,a);q.4s();G(q.K)q[0].1k(7)})};D.1l({77:H(b,a,c){J d=b&&b.1q==a0?b:{1J:c||!c&&a||D.1D(b)&&b,2u:b,41:c&&a||a&&a.1q!=9t&&a};d.2u=(d.2u&&d.2u.1q==4L?d.2u:D.28.5K[d.2u])||D.28.5K.74;d.5M=d.1J;d.1J=H(){G(d.36!==Q)D(7).5A();G(D.1D(d.5M))d.5M.1k(7)};I d},41:{73:H(p,n,b,a){I b+a*p},5P:H(p,n,b,a){I((-29.9r(p*29.9q)/2)+0.5)*a+b}},3O:[],48:U,28:H(b,c,a){7.15=c;7.T=b;7.1i=a;G(!c.3Z)c.3Z={}}});D.28.44={4D:H(){G(7.15.2Y)7.15.2Y.1k(7.T,7.1z,7);(D.28.2Y[7.1i]||D.28.2Y.4w)(7);G(7.1i=="1Z"||7.1i=="2h")7.T.V.18="3I"},1t:H(a){G(7.T[7.1i]!=U&&7.T.V[7.1i]==U)I 7.T[7.1i];J r=3d(D.1g(7.T,7.1i,a));I r&&r>-9p?r:3d(D.2a(7.T,7.1i))||0},3G:H(c,b,d){7.5V=1z();7.2b=c;7.3l=b;7.2M=d||7.2M||"2X";7.1z=7.2b;7.2S=7.4N=0;7.4D();J e=7;H t(a){I e.2Y(a)}t.T=7.T;D.3O.1p(t);G(D.48==U){D.48=4I(H(){J a=D.3O;R(J i=0;i<a.K;i++)G(!a[i]())a.7n(i--,1);G(!a.K){7k(D.48);D.48=U}},13)}},1N:H(){7.15.3Z[7.1i]=D.1K(7.T.V,7.1i);7.15.1N=M;7.3G(0,7.1t());G(7.1i=="2h"||7.1i=="1Z")7.T.V[7.1i]="9m";D(7.T).1N()},1M:H(){7.15.3Z[7.1i]=D.1K(7.T.V,7.1i);7.15.1M=M;7.3G(7.1t(),0)},2Y:H(a){J t=1z();G(a||t>7.15.2u+7.5V){7.1z=7.3l;7.2S=7.4N=1;7.4D();7.15.45[7.1i]=M;J b=M;R(J i 1n 7.15.45)G(7.15.45[i]!==M)b=Q;G(b){G(7.15.18!=U){7.T.V.33=7.15.33;7.T.V.18=7.15.18;G(D.1g(7.T,"18")=="2F")7.T.V.18="3I"}G(7.15.1M)7.T.V.18="2F";G(7.15.1M||7.15.1N)R(J p 1n 7.15.45)D.1K(7.T.V,p,7.15.3Z[p])}G(b)7.15.1J.1k(7.T);I Q}N{J n=t-7.5V;7.4N=n/7.15.2u;7.2S=D.41[7.15.41||(D.41.5P?"5P":"73")](7.4N,n,0,1,7.15.2u);7.1z=7.2b+((7.3l-7.2b)*7.2S);7.4D()}I M}};D.1l(D.28,{5K:{9l:9j,9i:7e,74:9g},2Y:{2e:H(a){a.T.2e=a.1z},2c:H(a){a.T.2c=a.1z},1y:H(a){D.1K(a.T.V,"1y",a.1z)},4w:H(a){a.T.V[a.1i]=a.1z+a.2M}}});D.17.2i=H(){J b=0,1S=0,T=7[0],3q;G(T)ao(D.14){J d=T.1d,4a=T,1s=T.1s,1Q=T.2z,5U=2k&&3r(5B)<9c&&!/9a/i.11(v),1g=D.2a,3c=1g(T,"30")=="3c";G(T.7y){J c=T.7y();1e(c.1A+29.2f(1Q.1C.2e,1Q.1c.2e),c.1S+29.2f(1Q.1C.2c,1Q.1c.2c));1e(-1Q.1C.6b,-1Q.1C.6a)}N{1e(T.5X,T.5W);1B(1s){1e(1s.5X,1s.5W);G(42&&!/^t(98|d|h)$/i.11(1s.2j)||2k&&!5U)2C(1s);G(!3c&&1g(1s,"30")=="3c")3c=M;4a=/^1c$/i.11(1s.2j)?4a:1s;1s=1s.1s}1B(d&&d.2j&&!/^1c|2K$/i.11(d.2j)){G(!/^96|1T.*$/i.11(1g(d,"18")))1e(-d.2e,-d.2c);G(42&&1g(d,"33")!="4j")2C(d);d=d.1d}G((5U&&(3c||1g(4a,"30")=="5x"))||(42&&1g(4a,"30")!="5x"))1e(-1Q.1c.5X,-1Q.1c.5W);G(3c)1e(29.2f(1Q.1C.2e,1Q.1c.2e),29.2f(1Q.1C.2c,1Q.1c.2c))}3q={1S:1S,1A:b}}H 2C(a){1e(D.2a(a,"6V",M),D.2a(a,"6U",M))}H 1e(l,t){b+=3r(l,10)||0;1S+=3r(t,10)||0}I 3q};D.17.1l({30:H(){J a=0,1S=0,3q;G(7[0]){J b=7.1s(),2i=7.2i(),4c=/^1c|2K$/i.11(b[0].2j)?{1S:0,1A:0}:b.2i();2i.1S-=25(7,\'94\');2i.1A-=25(7,\'aF\');4c.1S+=25(b,\'6U\');4c.1A+=25(b,\'6V\');3q={1S:2i.1S-4c.1S,1A:2i.1A-4c.1A}}I 3q},1s:H(){J a=7[0].1s;1B(a&&(!/^1c|2K$/i.11(a.2j)&&D.1g(a,\'30\')==\'93\'))a=a.1s;I D(a)}});D.P([\'5e\',\'5G\'],H(i,b){J c=\'4y\'+b;D.17[c]=H(a){G(!7[0])I;I a!=12?7.P(H(){7==1b||7==S?1b.92(!i?a:D(1b).2e(),i?a:D(1b).2c()):7[c]=a}):7[0]==1b||7[0]==S?46[i?\'aI\':\'aJ\']||D.71&&S.1C[c]||S.1c[c]:7[0][c]}});D.P(["6N","4b"],H(i,b){J c=i?"5e":"5G",4f=i?"6k":"6i";D.17["5s"+b]=H(){I 7[b.3y()]()+25(7,"57"+c)+25(7,"57"+4f)};D.17["90"+b]=H(a){I 7["5s"+b]()+25(7,"2C"+c+"4b")+25(7,"2C"+4f+"4b")+(a?25(7,"6S"+c)+25(7,"6S"+4f):0)}})})();',62,669,'|||||||this|||||||||||||||||||||||||||||||||||if|function|return|var|length|data|true|else|type|each|false|for|document|elem|null|style|event||nodeName|||test|undefined||browser|options|nodeType|fn|display|arguments|url|window|body|parentNode|add|msie|css|indexOf|prop|typeof|call|extend|script|in|replace|push|constructor|text|offsetParent|cur|status|div|apply|firstChild|opacity|now|left|while|documentElement|isFunction|filter|className|hidden|handle|match|complete|attr|ret|hide|show|dataType|trigger|doc|split|top|table|try|catch|success|break|cache|height||remove|tbody|string|guid|num|global|ready|fx|Math|curCSS|start|scrollTop|makeArray|scrollLeft|max|animate|width|offset|tagName|safari|map|toggle||done|Array|find|toUpperCase|button|special|duration|id|copy|value|handler|ownerDocument|select|new|border|exec|stack|none|opera|nextSibling|pushStack|target|html|inArray|unit|xml|bind|GET|isReady|merge|pos|timeout|delete|one|selected|px|step|jsre|position|async|preventDefault|overflow|name|which|queue|removeChild|namespace|insertBefore|nth|removeData|fixed|parseFloat|error|readyState|multiFilter|createElement|rl|re|trim|end|_|param|first|get|results|parseInt|slice|childNodes|encodeURIComponent|append|events|elems|toLowerCase|json|readyList|setTimeout|grep|mouseenter|color|is|custom|getElementsByTagName|block|stopPropagation|addEventListener|callee|proxy|mouseleave|timers|defaultView|password|disabled|last|has|appendChild|form|domManip|props|ajax|orig|set|easing|mozilla|load|prototype|curAnim|self|charCode|timerId|object|offsetChild|Width|parentOffset|src|unbind|br|currentStyle|clean|float|visible|relatedTarget|previousSibling|handlers|isXMLDoc|on|setup|nodeIndex|unique|shift|javascript|child|RegExp|_default|deep|scroll|lastModified|teardown|setRequestHeader|timeStamp|update|empty|tr|getAttribute|innerHTML|setInterval|checked|fromElement|Number|jQuery|state|active|jsonp|accepts|application|dir|input|responseText|click|styleSheets|unload|not|lastToggle|outline|mouseout|getPropertyValue|mouseover|getComputedStyle|bindReady|String|padding|pageX|metaKey|keyCode|getWH|andSelf|clientX|Left|all|visibility|container|index|init|triggered|removeAttribute|classFilter|prevObject|submit|file|after|windowData|inner|client|globalEval|sibling|jquery|absolute|clone|wrapAll|dequeue|version|triggerHandler|oldblock|ctrlKey|createTextNode|Top|handleError|getResponseHeader|parsererror|speeds|checkbox|old|00|radio|swing|href|Modified|ifModified|lastChild|safari2|startTime|offsetTop|offsetLeft|username|location|ajaxSettings|getElementById|isSimple|values|selectedIndex|runtimeStyle|rsLeft|_load|loaded|DOMContentLoaded|clientTop|clientLeft|toElement|srcElement|val|pageY|POST|unshift|Bottom|clientY|Right|fix|exclusive|detachEvent|cloneNode|removeEventListener|swap|toString|join|attachEvent|eval|substr|head|parse|textarea|reset|image|zoom|odd|even|before|prepend|exclude|expr|quickClass|quickID|uuid|quickChild|continue|Height|textContent|appendTo|contents|open|margin|evalScript|borderTopWidth|borderLeftWidth|parent|httpData|setArray|CSS1Compat|compatMode|boxModel|cssFloat|linear|def|webkit|nodeValue|speed|_toggle|eq|100|replaceWith|304|concat|200|alpha|Last|httpNotModified|getAttributeNode|httpSuccess|clearInterval|abort|beforeSend|splice|styleFloat|throw|colgroup|XMLHttpRequest|ActiveXObject|scriptCharset|callback|fieldset|multiple|processData|getBoundingClientRect|contentType|link|ajaxSend|ajaxSuccess|ajaxError|col|ajaxComplete|ajaxStop|ajaxStart|serializeArray|notmodified|keypress|keydown|change|mouseup|mousedown|dblclick|focus|blur|stylesheet|hasClass|rel|doScroll|black|hover|solid|cancelBubble|returnValue|wheelDelta|view|round|shiftKey|resize|screenY|screenX|relatedNode|mousemove|prevValue|originalTarget|offsetHeight|keyup|newValue|offsetWidth|eventPhase|detail|currentTarget|cancelable|bubbles|attrName|attrChange|altKey|originalEvent|charAt|0n|substring|animated|header|noConflict|line|enabled|innerText|contains|only|weight|font|gt|lt|uFFFF|u0128|size|417|Boolean|Date|toggleClass|removeClass|addClass|removeAttr|replaceAll|insertAfter|prependTo|wrap|contentWindow|contentDocument|iframe|children|siblings|prevAll|wrapInner|nextAll|outer|prev|scrollTo|static|marginTop|next|inline|parents|able|cellSpacing|adobeair|cellspacing|522|maxLength|maxlength|readOnly|400|readonly|fast|600|class|slow|1px|htmlFor|reverse|10000|PI|cos|compatible|Function|setData|ie|ra|it|rv|getData|userAgent|navigator|fadeTo|fadeIn|slideToggle|slideUp|slideDown|ig|responseXML|content|1223|NaN|fadeOut|300|protocol|send|setAttribute|option|dataFilter|cssText|changed|be|Accept|stop|With|Requested|Object|can|GMT|property|1970|Jan|01|Thu|Since|If|Type|Content|XMLHTTP|th|Microsoft|td|onreadystatechange|onload|cap|charset|colg|host|tfoot|specified|with|1_|thead|leg|plain|attributes|opt|embed|urlencoded|www|area|hr|ajaxSetup|meta|post|getJSON|getScript|marginLeft|img|elements|pageYOffset|pageXOffset|abbr|serialize|pixelLeft'.split('|'),0,{}))

Base64String = function(str, encode) {
	if(encode)
		str = b64.encode(str);
	this.value = str;
};
Base64String.prototype = {
	toJSON: function() {
		return "b64(\"" + this.value + "\")";
	}
};
	
(function ($) {
	var m = {
		'\b': '\\b',
		'\t': '\\t',
		'\n': '\\n',
		'\f': '\\f',
		'\r': '\\r',
		'"' : '\\"',
		'\\': '\\\\'
	};
	var s = {
		'array': function (x, binSafe) {
			var a = ['['], b, f, i, l = x.length, v;
			for (i = 0; i < l; i ++) {
				v = x[i];
				f = s[typeof v];
				if (!f) continue;
				
				v = f(v, binSafe);
				if (typeof v == 'string') {
					if (b) {
						a.push(',');
					}
					a.push(v);
					b = true;
				}
			}
			a.push(']');
			return a.join('');
		},
		'boolean': function (x) {
			return String(x);
		},
		'null': function (x) {
			return "null";
		},
		'number': function (x) {
			return isFinite(x) ? String(x) : 'null';
		},
		'object': function (x, binSafe) {
			if (!x) {
				return 'null';
			}
			if (x instanceof Array || (typeof x.sort == 'function' && typeof x.join == 'function')) {
				return s.array(x, binSafe);
			} else if(x instanceof String) {
				return s.string(x, binSafe);
			} else if(x instanceof Number) {
				return s.number(x);
			} else if(x instanceof Boolean) {
				return s['boolean'](x);
			}
			if (x.toJSON && x.toJSON instanceof Function) {
				return x.toJSON();
			}
			var a = ['{'], b, f, i, v;
			for (i in x) {
				v = x[i];
				f = s[typeof v];
				if (!f) continue;
				v = f(v, binSafe);
				if (typeof v == 'string') {
					if (b) {
						a.push(',');
					}
					a.push(s.string(i), ':', v);
					b = true;
				}
			}
			a.push('}');
			return a.join('');
		},
		'string': function (x, binSafe) {
			if (!/["\\\x00-\x1f]/.test(x))
				return '"' + x + '"';
            if (binSafe && /[\x00-\x1f\x7f-\xff]/.test(x)) {
                return new Base64String(x, true).toJSON();
            }
			x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
				var c = m[b];
				if (c) {
					return c;
				}
				c = b.charCodeAt();
				return '\\u00' +
				Math.floor(c / 16).toString(16) +
				(c % 16).toString(16);
			});
			return '"' + x + '"';
		}
	};

	$.toJSON = function(v, binSafe) {
		var f = s[typeof v];
		if (f) return f(v, binSafe);
	};

	$.parseJSON = function(v, safe) {
		if (safe === undefined) safe = $.parseJSON.safe;
		if (safe && !/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(v))
			return undefined;
		return eval('('+v+')');
	};

	$.parseJSON.safe = false;

})(jQuery);

/*	This work is licensed under Creative Commons GNU LGPL License.

	License: http://creativecommons.org/licenses/LGPL/2.1/
   Version: 0.9
	Author:  Stefan Goessner/2006
	Web:     http://goessner.net/ 
*/

function jsonT(self, rules) {
	var T = {
		output: false,
		init: function() {
			for (var rule in rules)
				if (rule.substr(0,4) != "self")
					rules["self."+rule] = rules[rule];
			return this;
		},
		apply: function(expr) {
			var trf = function(s){ return s.replace(/{([A-Za-z0-9_\$\.\[\]\'@\(\)]+)}/g, 
				function($0,$1){return T.processArg($1, expr);})},
				x = expr.replace(/\[[0-9]+\]/g, "[*]"), res;
			if (x in rules) {
				if (typeof(rules[x]) == "string")
					res = trf(rules[x]);
				else if (typeof(rules[x]) == "function")
					res = trf(rules[x](eval(expr)).toString());
			}
			else
				res = T.eval(expr);
			return res;
		},
		processArg: function(arg, parentExpr) {
			var expand = function(a,e){return (e=a.replace(/^\$/,e)).substr(0,4)!="self" ? ("self."+e) : e; },
				res = "";
			T.output = true;
			if (arg.charAt(0) == "@")
				res = eval(arg.replace(/@([A-za-z0-9_]+)\(([A-Za-z0-9_\$\.\[\]\']+)\)/, 
					function($0,$1,$2){return "rules['self."+$1+"']("+expand($2,parentExpr)+")";}));
			else if (arg != "$")
				res = T.apply(expand(arg, parentExpr));
			else
				res = T.eval(parentExpr);
			T.output = false;
			return res;
		},
		eval: function(expr) {
			var v = eval(expr), res = [];
			if (typeof(v) != "undefined") {
				if (typeof(v.sort) == 'function' /*v instanceof Array*/) {
					for (var i=0; i<v.length; i++) {
						if (typeof(v[i]) != "undefined") {
							rules.index = i;
							res.push(T.apply(expr+"["+i+"]"));
						}
					}
				}
				else if (typeof(v) == "object" && typeof(v.getTimezoneOffset) != 'function') {
					for (var m in v)
						if (typeof(v[m]) != "undefined")
							res.push(T.apply(expr+"."+m));
				}
				else if (T.output)
					res.push(v);
			}
			return res.join('');
		}
	};
	return T.init().apply("self");
}

/*
 * jQuery blockUI plugin
 * Version 2.10 
 * @requires jQuery v1.2.3 or later
 *
 * Examples at: http://malsup.com/jquery/block/
 * Copyright (c) 2007-2008 M. Alsup
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 * Thanks to Amir-Hossein Sobhi for some excellent contributions!
 */

;(function($) {

if (/1\.(0|1|2)\.(0|1|2)/.test($.fn.jquery) || /^1.1/.test($.fn.jquery)) {
    alert('blockUI requires jQuery v1.2.3 or later!  You are using v' + $.fn.jquery);
    return;
}

$.fn._fadeIn = $.fn.fadeIn;

// don't call setExpression in IE8
var mode = document.documentMode || 0;
var setExpr = $.browser.msie && (($.browser.version < 8 && !mode) || mode < 8);
var ie6 = $.browser.msie && /MSIE 6.0/.test(navigator.userAgent) && !mode;

// global $ methods for blocking/unblocking the entire page
$.blockUI   = function(opts) { install(window, opts); };
$.unblockUI = function(opts) { remove(window, opts); };

// plugin method for blocking element content
$.fn.block = function(opts) {
    return this.unblock({ fadeOut: 0 }).each(function() {
        if ($.css(this,'position') == 'static')
            this.style.position = 'relative';
        if ($.browser.msie)
            this.style.zoom = 1; // force 'hasLayout'
        install(this, opts);
    });
};

// plugin method for unblocking element content
$.fn.unblock = function(opts) {
    return this.each(function() {
        remove(this, opts);
    });
};

$.blockUI.version = 2.09; // 2nd generation blocking at no extra cost!

// override these in your code to change the default behavior and style
$.blockUI.defaults = {
    // message displayed when blocking (use null for no message)
    message:  '<h1>Please wait...</h1>',

    // styles for the message when blocking; if you wish to disable
    // these and use an external stylesheet then do this in your code:
    // $.blockUI.defaults.css = {};
    css: {
        padding:        0,
        margin:         0,
        width:          '30%',
        top:            '40%',
        left:           '35%',
        textAlign:      'center',
        color:          '#000',
        border:         '3px solid #aaa',
        backgroundColor:'#fff',
        cursor:         'default'
    },

    // styles for the overlay
    overlayCSS:  {
        backgroundColor: '#000',
        opacity:          0.6,
        cursor:          'default'
    },

	// IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
	iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',

	// force usage of iframe in non-IE browsers (handy for blocking applets)
	forceIframe: false,

    // z-index for the blocking overlay
    baseZ: 1000,

    // set these to true to have the message automatically centered
    centerX: true, // <-- only effects element blocking (page block controlled via css above)
    centerY: true,

    // allow body element to be stetched in ie6; this makes blocking look better
    // on "short" pages.  disable if you wish to prevent changes to the body height
    allowBodyStretch: true,

	// enable if you want key and mouse events to be disabled for content that is blocked
	bindEvents: true,

    // be default blockUI will supress tab navigation from leaving blocking content
    // (if bindEvents is true)
    constrainTabKey: true,

    // fadeIn time in millis; set to 0 to disable fadeIn on block
    fadeIn:  200,

    // fadeOut time in millis; set to 0 to disable fadeOut on unblock
    fadeOut:  400,

	// time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
	timeout: 0,

	// disable if you don't want to show the overlay
	showOverlay: true,

    // if true, focus will be placed in the first available input field when
    // page blocking
    focusInput: true,

    // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
    applyPlatformOpacityRules: true,

    // callback method invoked when unblocking has completed; the callback is
    // passed the element that has been unblocked (which is the window object for page
    // blocks) and the options that were passed to the unblock call:
    //     onUnblock(element, options)
    onUnblock: null,

    // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
    quirksmodeOffsetHack: 4
};

// private data and functions follow...

var pageBlock = null;
var pageBlockEls = [];

function install(el, opts) {
    var full = (el == window);
    var msg = opts && opts.message !== undefined ? opts.message : undefined;
    opts = $.extend({}, $.blockUI.defaults, opts || {});
    opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
    var css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
    msg = msg === undefined ? opts.message : msg;

    // remove the current block (if there is one)
    if (full && pageBlock)
        remove(window, {fadeOut:0});

    // if an existing element is being used as the blocking content then we capture
    // its current place in the DOM (and current display style) so we can restore
    // it when we unblock
    if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
        var node = msg.jquery ? msg[0] : msg;
        var data = {};
        $(el).data('blockUI.history', data);
        data.el = node;
        data.parent = node.parentNode;
        data.display = node.style.display;
        data.position = node.style.position;
		if (data.parent)
			data.parent.removeChild(node);
    }

    var z = opts.baseZ;

    // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
    // layer1 is the iframe layer which is used to supress bleed through of underlying content
    // layer2 is the overlay layer which has opacity and a wait cursor (by default)
    // layer3 is the message content that is displayed while blocking

    var lyr1 = ($.browser.msie || opts.forceIframe) 
    	? $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>')
        : $('<div class="blockUI" style="display:none"></div>');
    var lyr2 = $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
    var lyr3 = full ? $('<div class="blockUI blockMsg blockPage" style="z-index:'+z+';display:none;position:fixed"></div>')
                    : $('<div class="blockUI blockMsg blockElement" style="z-index:'+z+';display:none;position:absolute"></div>');

    // if we have a message, style it
    if (msg)
        lyr3.css(css);

    // style the overlay
    if (!opts.applyPlatformOpacityRules || !($.browser.mozilla && /Linux/.test(navigator.platform)))
        lyr2.css(opts.overlayCSS);
    lyr2.css('position', full ? 'fixed' : 'absolute');

    // make iframe layer transparent in IE
    if ($.browser.msie || opts.forceIframe)
        lyr1.css('opacity',0.0);

    $([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);

    // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
    var expr = setExpr && (!$.boxModel || $('object,embed', full ? null : el).length > 0);
    if (ie6 || expr) {
        // give body 100% height
        if (full && opts.allowBodyStretch && $.boxModel)
            $('html,body').css('height','100%');

        // fix ie6 issue when blocked element has a border width
        if ((ie6 || !$.boxModel) && !full) {
            var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
            var fixT = t ? '(0 - '+t+')' : 0;
            var fixL = l ? '(0 - '+l+')' : 0;
        }

        // simulate fixed position
        $.each([lyr1,lyr2,lyr3], function(i,o) {
            var s = o[0].style;
            s.position = 'absolute';
            if (i < 2) {
                full ? s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"')
                     : s.setExpression('height','this.parentNode.offsetHeight + "px"');
                full ? s.setExpression('width','jQuery.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"')
                     : s.setExpression('width','this.parentNode.offsetWidth + "px"');
                if (fixL) s.setExpression('left', fixL);
                if (fixT) s.setExpression('top', fixT);
            }
            else if (opts.centerY) {
                if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
                s.marginTop = 0;
            }
			else if (!opts.centerY && full) {
				var top = (opts.css && opts.css.top) ? parseInt(opts.css.top) : 0;
				var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
                s.setExpression('top',expression);
			}
        });
    }

    // show the message
	if (msg) {
		lyr3.append(msg);
		if (msg.jquery || msg.nodeType)
			$(msg).show();
	}

	if (($.browser.msie || opts.forceIframe) && opts.showOverlay)
		lyr1.show(); // opacity is zero
	if (opts.fadeIn) {
		if (opts.showOverlay)
			lyr2._fadeIn(opts.fadeIn);
		if (msg)
			lyr3.fadeIn(opts.fadeIn);
	}
	else {
		if (opts.showOverlay)
			lyr2.show();
		if (msg)
			lyr3.show();
	}

    // bind key and mouse events
    bind(1, el, opts);

    if (full) {
        pageBlock = lyr3[0];
        pageBlockEls = $(':input:enabled:visible',pageBlock);
        if (opts.focusInput)
            setTimeout(focus, 20);
    }
    else
        center(lyr3[0], opts.centerX, opts.centerY);

	if (opts.timeout) {
		// auto-unblock
		var to = setTimeout(function() {
			full ? $.unblockUI(opts) : $(el).unblock(opts);
		}, opts.timeout);
		$(el).data('blockUI.timeout', to);
	}
};

// remove the block
function remove(el, opts) {
    var full = el == window;
	var $el = $(el);
    var data = $el.data('blockUI.history');
	var to = $el.data('blockUI.timeout');
	if (to) {
		clearTimeout(to);
		$el.removeData('blockUI.timeout');
	}
    opts = $.extend({}, $.blockUI.defaults, opts || {});
    bind(0, el, opts); // unbind events
    var els = full ? $('body').children().filter('.blockUI') : $('.blockUI', el);

    if (full)
        pageBlock = pageBlockEls = null;

    if (opts.fadeOut) {
        els.fadeOut(opts.fadeOut);
        setTimeout(function() { reset(els,data,opts,el); }, opts.fadeOut);
    }
    else
        reset(els, data, opts, el);
};

// move blocking element back into the DOM where it started
function reset(els,data,opts,el) {
    els.each(function(i,o) {
        // remove via DOM calls so we don't lose event handlers
        if (this.parentNode)
            this.parentNode.removeChild(this);
    });

    if (data && data.el) {
        data.el.style.display = data.display;
        data.el.style.position = data.position;
		if (data.parent)
			data.parent.appendChild(data.el);
        $(data.el).removeData('blockUI.history');
    }

    if (typeof opts.onUnblock == 'function')
        opts.onUnblock(el,opts);
};

// bind/unbind the handler
function bind(b, el, opts) {
    var full = el == window, $el = $(el);

    // don't bother unbinding if there is nothing to unbind
    if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
        return;
    if (!full)
        $el.data('blockUI.isBlocked', b);

	// don't bind events when overlay is not in use or if bindEvents is false
    if (!opts.bindEvents || (b && !opts.showOverlay)) 
		return;

    // bind anchors and inputs for mouse and key events
    var events = 'mousedown mouseup keydown keypress';
    b ? $(document).bind(events, opts, handler) : $(document).unbind(events, handler);

// former impl...
//    var $e = $('a,:input');
//    b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
};

// event handler to suppress keyboard/mouse events when blocking
function handler(e) {
    // allow tab navigation (conditionally)
    if (e.keyCode && e.keyCode == 9) {
        if (pageBlock && e.data.constrainTabKey) {
            var els = pageBlockEls;
            var fwd = !e.shiftKey && e.target == els[els.length-1];
            var back = e.shiftKey && e.target == els[0];
            if (fwd || back) {
                setTimeout(function(){focus(back)},10);
                return false;
            }
        }
    }
    // allow events within the message content
    if ($(e.target).parents('div.blockMsg').length > 0)
        return true;

    // allow events for content that is not being blocked
    return $(e.target).parents().children().filter('div.blockUI').length == 0;
};

function focus(back) {
    if (!pageBlockEls)
        return;
    var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
    if (e)
        e.focus();
};

function center(el, x, y) {
    var p = el.parentNode, s = el.style;
    var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
    var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
    if (x) s.left = l > 0 ? (l+'px') : '0';
    if (y) s.top  = t > 0 ? (t+'px') : '0';
};

function sz(el, p) {
    return parseInt($.css(el,p))||0;
};

})(jQuery);

/*
 * jQuery Form Plugin
 * @requires jQuery v1.1 or later
 *
 * Examples at: http://malsup.com/jquery/form/
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Revision: $Id$
 */
 (function($) {
/**
 * ajaxSubmit() provides a mechanism for submitting an HTML form using AJAX.
 *
 * ajaxSubmit accepts a single argument which can be either a success callback function
 * or an options Object.  If a function is provided it will be invoked upon successful
 * completion of the submit and will be passed the response from the server.
 * If an options Object is provided, the following attributes are supported:
 *
 *  target:   Identifies the element(s) in the page to be updated with the server response.
 *            This value may be specified as a jQuery selection string, a jQuery object,
 *            or a DOM element.
 *            default value: null
 *
 *  url:      URL to which the form data will be submitted.
 *            default value: value of form's 'action' attribute
 *
 *  type:     The method in which the form data should be submitted, 'GET' or 'POST'.
 *            default value: value of form's 'method' attribute (or 'GET' if none found)
 *
 *  data:     Additional data to add to the request, specified as key/value pairs (see $.ajax).
 *
 *  beforeSubmit:  Callback method to be invoked before the form is submitted.
 *            default value: null
 *
 *  success:  Callback method to be invoked after the form has been successfully submitted
 *            and the response has been returned from the server
 *            default value: null
 *
 *  dataType: Expected dataType of the response.  One of: null, 'xml', 'script', or 'json'
 *            default value: null
 *
 *  semantic: Boolean flag indicating whether data must be submitted in semantic order (slower).
 *            default value: false
 *
 *  resetForm: Boolean flag indicating whether the form should be reset if the submit is successful
 *
 *  clearForm: Boolean flag indicating whether the form should be cleared if the submit is successful
 *
 *
 * The 'beforeSubmit' callback can be provided as a hook for running pre-submit logic or for
 * validating the form data.  If the 'beforeSubmit' callback returns false then the form will
 * not be submitted. The 'beforeSubmit' callback is invoked with three arguments: the form data
 * in array format, the jQuery object, and the options object passed into ajaxSubmit.
 * The form data array takes the following form:
 *
 *     [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
 *
 * If a 'success' callback method is provided it is invoked after the response has been returned
 * from the server.  It is passed the responseText or responseXML value (depending on dataType).
 * See jQuery.ajax for further details.
 *
 *
 * The dataType option provides a means for specifying how the server response should be handled.
 * This maps directly to the jQuery.httpData method.  The following values are supported:
 *
 *      'xml':    if dataType == 'xml' the server response is treated as XML and the 'success'
 *                   callback method, if specified, will be passed the responseXML value
 *      'json':   if dataType == 'json' the server response will be evaluted and passed to
 *                   the 'success' callback, if specified
 *      'script': if dataType == 'script' the server response is evaluated in the global context
 *
 *
 * Note that it does not make sense to use both the 'target' and 'dataType' options.  If both
 * are provided the target will be ignored.
 *
 * The semantic argument can be used to force form serialization in semantic order.
 * This is normally true anyway, unless the form contains input elements of type='image'.
 * If your form must be submitted with name/value pairs in semantic order and your form
 * contains an input of type='image" then pass true for this arg, otherwise pass false
 * (or nothing) to avoid the overhead for this logic.
 *
 *
 * When used on its own, ajaxSubmit() is typically bound to a form's submit event like this:
 *
 * $("#form-id").submit(function() {
 *     $(this).ajaxSubmit(options);
 *     return false; // cancel conventional submit
 * });
 *
 * When using ajaxForm(), however, this is done for you.
 *
 * @example
 * $('#myForm').ajaxSubmit(function(data) {
 *     alert('Form submit succeeded! Server returned: ' + data);
 * });
 * @desc Submit form and alert server response
 *
 *
 * @example
 * var options = {
 *     target: '#myTargetDiv'
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc Submit form and update page element with server response
 *
 *
 * @example
 * var options = {
 *     success: function(responseText) {
 *         alert(responseText);
 *     }
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc Submit form and alert the server response
 *
 *
 * @example
 * var options = {
 *     beforeSubmit: function(formArray, jqForm) {
 *         if (formArray.length == 0) {
 *             alert('Please enter data.');
 *             return false;
 *         }
 *     }
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc Pre-submit validation which aborts the submit operation if form data is empty
 *
 *
 * @example
 * var options = {
 *     url: myJsonUrl.php,
 *     dataType: 'json',
 *     success: function(data) {
 *        // 'data' is an object representing the the evaluated json data
 *     }
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc json data returned and evaluated
 *
 *
 * @example
 * var options = {
 *     url: myXmlUrl.php,
 *     dataType: 'xml',
 *     success: function(responseXML) {
 *        // responseXML is XML document object
 *        var data = $('myElement', responseXML).text();
 *     }
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc XML data returned from server
 *
 *
 * @example
 * var options = {
 *     resetForm: true
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc submit form and reset it if successful
 *
 * @example
 * $('#myForm).submit(function() {
 *    $(this).ajaxSubmit();
 *    return false;
 * });
 * @desc Bind form's submit event to use ajaxSubmit
 *
 *
 * @name ajaxSubmit
 * @type jQuery
 * @param options  object literal containing options which control the form submission process
 * @cat Plugins/Form
 * @return jQuery
 */
$.fn.ajaxSubmit = function(options) {
    if (typeof options == 'function')
        options = { success: options };

    options = $.extend({
        url:  this.attr('action') || window.location + "",
        type: this.attr('method') || 'GET'
    }, options || {});

    // hook for manipulating the form data before it is extracted;
    // convenient for use with rich editors like tinyMCE or FCKEditor
    var veto = {};
    $.event.trigger('form.pre.serialize', [this, options, veto]);
    if (veto.veto) return this;

    var a = this.formToArray(options.semantic);
	if (options.data) {
	    for (var n in options.data)
	        a.push( { name: n, value: options.data[n] } );
	}

    // give pre-submit callback an opportunity to abort the submit
    if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) return this;

    // fire vetoable 'validate' event
    $.event.trigger('form.submit.validate', [a, this, options, veto]);
    if (veto.veto) return this;

    var q = $.param(a);//.replace(/%20/g,'+');

    if (options.type.toUpperCase() == 'GET') {
        options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
        options.data = null;  // data is null for 'get'
    }
    else
        options.data = q; // data is the query string for 'post'

    var $form = this, callbacks = [];
    if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
    if (options.clearForm) callbacks.push(function() { $form.clearForm(); });

    // perform a load on the target only if dataType is not provided
    if (!options.dataType && options.target) {
        var oldSuccess = options.success || function(){};
        callbacks.push(function(data) {
            if (this.evalScripts)
                $(options.target).attr("innerHTML", data).evalScripts().each(oldSuccess, arguments);
            else // jQuery v1.1.4
                $(options.target).html(data).each(oldSuccess, arguments);
        });
    }
    else if (options.success)
        callbacks.push(options.success);

    options.success = function(data, status) {
        for (var i=0, max=callbacks.length; i < max; i++)
            callbacks[i](data, status, $form);
    };

    // are there files to upload?
    var files = $('input:file', this).fieldValue();
    var found = false;
    for (var j=0; j < files.length; j++)
        if (files[j])
            found = true;

    if (options.iframe || found) // options.iframe allows user to force iframe mode
        fileUpload();
    else
        $.ajax(options);

    // fire 'notify' event
    $.event.trigger('form.submit.notify', [this, options]);
    return this;


    // private function for handling file uploads (hat tip to YAHOO!)
    function fileUpload() {
        var form = $form[0];
        var opts = $.extend({}, $.ajaxSettings, options);

        var id = 'jqFormIO' + $.fn.ajaxSubmit.counter++;
        var $io = $('<iframe id="' + id + '" name="' + id + '" />');
        var io = $io[0];
        var op8 = $.browser.opera && window.opera.version() < 9;
        if ($.browser.msie || op8) io.src = 'javascript:false;document.write("");';
        $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });

        var xhr = { // mock object
            responseText: null,
            responseXML: null,
            status: 0,
            statusText: 'n/a',
            getAllResponseHeaders: function() {},
            getResponseHeader: function() {},
            setRequestHeader: function() {}
        };

        var g = opts.global;
        // trigger ajax global events so that activity/block indicators work like normal
        if (g && ! $.active++) $.event.trigger("ajaxStart");
        if (g) $.event.trigger("ajaxSend", [xhr, opts]);

        var cbInvoked = 0;
        var timedOut = 0;

        // take a breath so that pending repaints get some cpu time before the upload starts
        setTimeout(function() {
            $io.appendTo('body');
            // jQuery's event binding doesn't work for iframe events in IE
            io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);

            // make sure form attrs are set
            var encAttr = form.encoding ? 'encoding' : 'enctype';
            var t = $form.attr('target');
            $form.attr({
                target:   id,
                method:  'POST',
                action:   opts.url
            });
            form[encAttr] = 'multipart/form-data';

            // support timout
            if (opts.timeout)
                setTimeout(function() { timedOut = true; cb(); }, opts.timeout);

            form.submit();
            $form.attr('target', t); // reset target
        }, 10);

        function cb() {
            if (cbInvoked++) return;

            io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);

            var ok = true;
            try {
                if (timedOut) throw 'timeout';
                // extract the server response from the iframe
                var data, doc;
                doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
                xhr.responseText = doc.body ? doc.body.innerHTML : null;
                xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;

                if (opts.dataType == 'json' || opts.dataType == 'script') {
                    var ta = doc.getElementsByTagName('textarea')[0];
                    data = ta ? ta.value : xhr.responseText;
                    if (opts.dataType == 'json')
                        eval("data = " + data);
                    else
                        $.globalEval(data);
                }
                else if (opts.dataType == 'xml') {
                    data = xhr.responseXML;
                    if (!data && xhr.responseText != null)
                        data = toXml(xhr.responseText);
                }
                else {
                    data = xhr.responseText;
                }
            }
            catch(e){
                ok = false;
                $.handleError(opts, xhr, 'error', e);
            }

            // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
            if (ok) {
                opts.success(data, 'success');
                if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
            }
            if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
            if (g && ! --$.active) $.event.trigger("ajaxStop");
            if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');

            // clean up
            setTimeout(function() {
                $io.remove();
                xhr.responseXML = null;
            }, 100);
        };

        function toXml(s, doc) {
            if (window.ActiveXObject) {
                doc = new ActiveXObject('Microsoft.XMLDOM');
                doc.async = 'false';
                doc.loadXML(s);
            }
            else
                doc = (new DOMParser()).parseFromString(s, 'text/xml');
            return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
        };
    };
};
$.fn.ajaxSubmit.counter = 0; // used to create unique iframe ids

/**
 * ajaxForm() provides a mechanism for fully automating form submission.
 *
 * The advantages of using this method instead of ajaxSubmit() are:
 *
 * 1: This method will include coordinates for <input type="image" /> elements (if the element
 *    is used to submit the form).
 * 2. This method will include the submit element's name/value data (for the element that was
 *    used to submit the form).
 * 3. This method binds the submit() method to the form for you.
 *
 * Note that for accurate x/y coordinates of image submit elements in all browsers
 * you need to also use the "dimensions" plugin (this method will auto-detect its presence).
 *
 * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
 * passes the options argument along after properly binding events for submit elements and
 * the form itself.  See ajaxSubmit for a full description of the options argument.
 *
 *
 * @example
 * var options = {
 *     target: '#myTargetDiv'
 * };
 * $('#myForm').ajaxSForm(options);
 * @desc Bind form's submit event so that 'myTargetDiv' is updated with the server response
 *       when the form is submitted.
 *
 *
 * @example
 * var options = {
 *     success: function(responseText) {
 *         alert(responseText);
 *     }
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc Bind form's submit event so that server response is alerted after the form is submitted.
 *
 *
 * @example
 * var options = {
 *     beforeSubmit: function(formArray, jqForm) {
 *         if (formArray.length == 0) {
 *             alert('Please enter data.');
 *             return false;
 *         }
 *     }
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc Bind form's submit event so that pre-submit callback is invoked before the form
 *       is submitted.
 *
 *
 * @name   ajaxForm
 * @param  options  object literal containing options which control the form submission process
 * @return jQuery
 * @cat    Plugins/Form
 * @type   jQuery
 */
$.fn.ajaxForm = function(options) {
    return this.ajaxFormUnbind().submit(submitHandler).each(function() {
        // store options in hash
        this.formPluginId = $.fn.ajaxForm.counter++;
        $.fn.ajaxForm.optionHash[this.formPluginId] = options;
        $(":submit,input:image", this).click(clickHandler);
    });
};

$.fn.ajaxForm.counter = 1;
$.fn.ajaxForm.optionHash = {};

function clickHandler(e) {
    var $form = this.form;
    $form.clk = this;
    if (this.type == 'image') {
        if (e.offsetX != undefined) {
            $form.clk_x = e.offsetX;
            $form.clk_y = e.offsetY;
        } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
            var offset = $(this).offset();
            $form.clk_x = e.pageX - offset.left;
            $form.clk_y = e.pageY - offset.top;
        } else {
            $form.clk_x = e.pageX - this.offsetLeft;
            $form.clk_y = e.pageY - this.offsetTop;
        }
    }
    // clear form vars
    setTimeout(function() { $form.clk = $form.clk_x = $form.clk_y = null; }, 10);
};

function submitHandler() {
    // retrieve options from hash
    var id = this.formPluginId;
    var options = $.fn.ajaxForm.optionHash[id];
    $(this).ajaxSubmit(options);
    return false;
};

/**
 * ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
 *
 * @name   ajaxFormUnbind
 * @return jQuery
 * @cat    Plugins/Form
 * @type   jQuery
 */
$.fn.ajaxFormUnbind = function() {
    this.unbind('submit', submitHandler);
    return this.each(function() {
        $(":submit,input:image", this).unbind('click', clickHandler);
    });

};

/**
 * formToArray() gathers form element data into an array of objects that can
 * be passed to any of the following ajax functions: $.get, $.post, or load.
 * Each object in the array has both a 'name' and 'value' property.  An example of
 * an array for a simple login form might be:
 *
 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
 *
 * It is this array that is passed to pre-submit callback functions provided to the
 * ajaxSubmit() and ajaxForm() methods.
 *
 * The semantic argument can be used to force form serialization in semantic order.
 * This is normally true anyway, unless the form contains input elements of type='image'.
 * If your form must be submitted with name/value pairs in semantic order and your form
 * contains an input of type='image" then pass true for this arg, otherwise pass false
 * (or nothing) to avoid the overhead for this logic.
 *
 * @example var data = $("#myForm").formToArray();
 * $.post( "myscript.cgi", data );
 * @desc Collect all the data from a form and submit it to the server.
 *
 * @name formToArray
 * @param semantic true if serialization must maintain strict semantic ordering of elements (slower)
 * @type Array<Object>
 * @cat Plugins/Form
 */
$.fn.formToArray = function(semantic) {
    var a = [];
    if (this.length == 0) return a;

    var form = this[0];
    var els = semantic ? form.getElementsByTagName('*') : form.elements;
    if (!els) return a;
    for(var i=0, max=els.length; i < max; i++) {
        var el = els[i];
        var n = el.name;
        if (!n) continue;

        if (semantic && form.clk && el.type == "image") {
            // handle image inputs on the fly when semantic == true
            if(!el.disabled && form.clk == el)
                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
            continue;
        }

        var v = $.fieldValue(el, true);
        if (v && v.constructor == Array) {
            for(var j=0, jmax=v.length; j < jmax; j++)
                a.push({name: n, value: v[j]});
        }
        else if (v !== null && typeof v != 'undefined')
            a.push({name: n, value: v});
    }

    if (!semantic && form.clk) {
        // input type=='image' are not found in elements array! handle them here
        var inputs = form.getElementsByTagName("input");
        for(var i=0, max=inputs.length; i < max; i++) {
            var input = inputs[i];
            var n = input.name;
            if(n && !input.disabled && input.type == "image" && form.clk == input)
                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
        }
    }
    return a;
};


/**
 * Serializes form data into a 'submittable' string. This method will return a string
 * in the format: name1=value1&amp;name2=value2
 *
 * The semantic argument can be used to force form serialization in semantic order.
 * If your form must be submitted with name/value pairs in semantic order then pass
 * true for this arg, otherwise pass false (or nothing) to avoid the overhead for
 * this logic (which can be significant for very large forms).
 *
 * @example var data = $("#myForm").formSerialize();
 * $.ajax('POST', "myscript.cgi", data);
 * @desc Collect all the data from a form into a single string
 *
 * @name formSerialize
 * @param semantic true if serialization must maintain strict semantic ordering of elements (slower)
 * @type String
 * @cat Plugins/Form
 */
$.fn.formSerialize = function(semantic) {
    //hand off to jQuery.param for proper encoding
    return $.param(this.formToArray(semantic));
};


/**
 * Serializes all field elements in the jQuery object into a query string.
 * This method will return a string in the format: name1=value1&amp;name2=value2
 *
 * The successful argument controls whether or not serialization is limited to
 * 'successful' controls (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
 * The default value of the successful argument is true.
 *
 * @example var data = $("input").formSerialize();
 * @desc Collect the data from all successful input elements into a query string
 *
 * @example var data = $(":radio").formSerialize();
 * @desc Collect the data from all successful radio input elements into a query string
 *
 * @example var data = $("#myForm :checkbox").formSerialize();
 * @desc Collect the data from all successful checkbox input elements in myForm into a query string
 *
 * @example var data = $("#myForm :checkbox").formSerialize(false);
 * @desc Collect the data from all checkbox elements in myForm (even the unchecked ones) into a query string
 *
 * @example var data = $(":input").formSerialize();
 * @desc Collect the data from all successful input, select, textarea and button elements into a query string
 *
 * @name fieldSerialize
 * @param successful true if only successful controls should be serialized (default is true)
 * @type String
 * @cat Plugins/Form
 */
$.fn.fieldSerialize = function(successful) {
    var a = [];
    this.each(function() {
        var n = this.name;
        if (!n) return;
        var v = $.fieldValue(this, successful);
        if (v && v.constructor == Array) {
            for (var i=0,max=v.length; i < max; i++)
                a.push({name: n, value: v[i]});
        }
        else if (v !== null && typeof v != 'undefined')
            a.push({name: this.name, value: v});
    });
    //hand off to jQuery.param for proper encoding
    return $.param(a);
};


/**
 * Returns the value(s) of the element in the matched set.  For example, consider the following form:
 *
 *  <form><fieldset>
 *      <input name="A" type="text" />
 *      <input name="A" type="text" />
 *      <input name="B" type="checkbox" value="B1" />
 *      <input name="B" type="checkbox" value="B2"/>
 *      <input name="C" type="radio" value="C1" />
 *      <input name="C" type="radio" value="C2" />
 *  </fieldset></form>
 *
 *  var v = $(':text').fieldValue();
 *  // if no values are entered into the text inputs
 *  v == ['','']
 *  // if values entered into the text inputs are 'foo' and 'bar'
 *  v == ['foo','bar']
 *
 *  var v = $(':checkbox').fieldValue();
 *  // if neither checkbox is checked
 *  v === undefined
 *  // if both checkboxes are checked
 *  v == ['B1', 'B2']
 *
 *  var v = $(':radio').fieldValue();
 *  // if neither radio is checked
 *  v === undefined
 *  // if first radio is checked
 *  v == ['C1']
 *
 * The successful argument controls whether or not the field element must be 'successful'
 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
 * The default value of the successful argument is true.  If this value is false the value(s)
 * for each element is returned.
 *
 * Note: This method *always* returns an array.  If no valid value can be determined the
 *       array will be empty, otherwise it will contain one or more values.
 *
 * @example var data = $("#myPasswordElement").fieldValue();
 * alert(data[0]);
 * @desc Alerts the current value of the myPasswordElement element
 *
 * @example var data = $("#myForm :input").fieldValue();
 * @desc Get the value(s) of the form elements in myForm
 *
 * @example var data = $("#myForm :checkbox").fieldValue();
 * @desc Get the value(s) for the successful checkbox element(s) in the jQuery object.
 *
 * @example var data = $("#mySingleSelect").fieldValue();
 * @desc Get the value(s) of the select control
 *
 * @example var data = $(':text').fieldValue();
 * @desc Get the value(s) of the text input or textarea elements
 *
 * @example var data = $("#myMultiSelect").fieldValue();
 * @desc Get the values for the select-multiple control
 *
 * @name fieldValue
 * @param Boolean successful true if only the values for successful controls should be returned (default is true)
 * @type Array<String>
 * @cat Plugins/Form
 */
$.fn.fieldValue = function(successful) {
    for (var val=[], i=0, max=this.length; i < max; i++) {
        var el = this[i];
        var v = $.fieldValue(el, successful);
        if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
            continue;
        v.constructor == Array ? $.merge(val, v) : val.push(v);
    }
    return val;
};

/**
 * Returns the value of the field element.
 *
 * The successful argument controls whether or not the field element must be 'successful'
 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
 * The default value of the successful argument is true.  If the given element is not
 * successful and the successful arg is not false then the returned value will be null.
 *
 * Note: If the successful flag is true (default) but the element is not successful, the return will be null
 * Note: The value returned for a successful select-multiple element will always be an array.
 * Note: If the element has no value the return value will be undefined.
 *
 * @example var data = jQuery.fieldValue($("#myPasswordElement")[0]);
 * @desc Gets the current value of the myPasswordElement element
 *
 * @name fieldValue
 * @param Element el The DOM element for which the value will be returned
 * @param Boolean successful true if value returned must be for a successful controls (default is true)
 * @type String or Array<String> or null or undefined
 * @cat Plugins/Form
 */
$.fieldValue = function(el, successful) {
    var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
    if (typeof successful == 'undefined') successful = true;

    if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
        (t == 'checkbox' || t == 'radio') && !el.checked ||
        (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
        tag == 'select' && el.selectedIndex == -1))
            return null;

    if (tag == 'select') {
        var index = el.selectedIndex;
        if (index < 0) return null;
        var a = [], ops = el.options;
        var one = (t == 'select-one');
        var max = (one ? index+1 : ops.length);
        for(var i=(one ? index : 0); i < max; i++) {
            var op = ops[i];
            if (op.selected) {
                // extra pain for IE...
                var v = $.browser.msie && !(op.attributes['value'].specified) ? op.text : op.value;
                if (one) return v;
                a.push(v);
            }
        }
        return a;
    }
    return el.value;
};


/**
 * Clears the form data.  Takes the following actions on the form's input fields:
 *  - input text fields will have their 'value' property set to the empty string
 *  - select elements will have their 'selectedIndex' property set to -1
 *  - checkbox and radio inputs will have their 'checked' property set to false
 *  - inputs of type submit, button, reset, and hidden will *not* be effected
 *  - button elements will *not* be effected
 *
 * @example $('form').clearForm();
 * @desc Clears all forms on the page.
 *
 * @name clearForm
 * @type jQuery
 * @cat Plugins/Form
 */
$.fn.clearForm = function() {
    return this.each(function() {
        $('input,select,textarea', this).clearFields();
    });
};

/**
 * Clears the selected form elements.  Takes the following actions on the matched elements:
 *  - input text fields will have their 'value' property set to the empty string
 *  - select elements will have their 'selectedIndex' property set to -1
 *  - checkbox and radio inputs will have their 'checked' property set to false
 *  - inputs of type submit, button, reset, and hidden will *not* be effected
 *  - button elements will *not* be effected
 *
 * @example $('.myInputs').clearFields();
 * @desc Clears all inputs with class myInputs
 *
 * @name clearFields
 * @type jQuery
 * @cat Plugins/Form
 */
$.fn.clearFields = $.fn.clearInputs = function() {
    return this.each(function() {
        var t = this.type, tag = this.tagName.toLowerCase();
        if (t == 'text' || t == 'password' || tag == 'textarea')
            this.value = '';
        else if (t == 'checkbox' || t == 'radio')
            this.checked = false;
        else if (tag == 'select')
            this.selectedIndex = -1;
    });
};


/**
 * Resets the form data.  Causes all form elements to be reset to their original value.
 *
 * @example $('form').resetForm();
 * @desc Resets all forms on the page.
 *
 * @name resetForm
 * @type jQuery
 * @cat Plugins/Form
 */
$.fn.resetForm = function() {
    return this.each(function() {
        // guard against an input with the name of 'reset'
        // note that IE reports the reset function as an 'object'
        if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
            this.reset();
    });
};

})(jQuery);

/* BORROWED JQUERY DOM CREATOR */
$.create = function() {
	var ret = [], a = arguments, i, e, length;
	a = a[0].constructor == Array ? a[0] : a;
	for(i=0,length=a.length; i<length; i++) {
		if(a[i+1] && (a[i+1].constructor == Object || a[i+1].constructor == Array)) { // item is element if attributes follow
			if(a[i].jquery) {
				e = a[i];
			} else {
				e = document.createElement(a[i]);
			}
			if(a[i+1].constructor == Object && !a[i+1].jquery) {
				var attrs = a[++i];
				$(e).attr(attrs); // apply attributes
				if(attrs.style) { $(e).css(attrs.style); }
				/* Check for events */
				if(a[i+1] && a[i+1].constructor == Object && !a[i+1].jquery) {
					/* If you want to add 'data' to a callback, use [data, function] as the value */
					$.each(a[++i], function(item, value) {
						if(!value) return; /* If not defined or such, skip */
						value = value.constructor == Array ? value : [value];
						$(e).bind(item, value[0], value[1]);
					});
				}
			}
			if(a[i+1] && a[i+1].constructor == Array) {
				$(e).append($.create(a[++i])); // optional children
			} else if(a[i+1] && a[i+1].jquery) { // Handle JQuery objects
				$(e).append(a[++i]);
			}
			ret.push(e);
		} else { // item is just a text node
			if(a[i] && a[i].jquery) {
				a[i].each(function() { ret.push(this); });
			} else {
				ret.push(document.createTextNode(a[i]));
			}
		}
	}
	return $(ret);
};

$.tpl = function(json, tpl) {
	var ret = [];
	$.each((json.constructor == Array || json.constructor == Object) ? json : [json], function(k) {
		var o = $.create(tpl.apply(this, [k]));
		for(var i=0, length=o.length;i<length;i++) { ret.push(o[i]); }
	});
	return $(ret);
};

/**
 * Encoding/decode utilities used by tblive-core
 */

b64 = {};

/**
 * @tparam String input A base64-encoded string
 * @treturn String Returns the decoded binary string
 */
b64.decode = function(input) {
    var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0, len;

    /* firefox has atob, a fast b64 decoder. sometimes, though, this doesn't work. */
    /*
    try {
        output = atob(input);
        if(output) return output;
    } catch(e) { }
    */
    // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
    len = input.length;

    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 += String.fromCharCode(chr1);

        if (enc3 != 64) {
            output += String.fromCharCode(chr2);
        }
        if (enc4 != 64) {
            output += String.fromCharCode(chr3);
        }
    } while (i < len);

    return output;
};

/**
 * @tparam String input A binary string
 * @treturn String Returns a base64-encoded string
 */
b64.encode = function(input) {
    var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0, len = input.length;

    /* firefox has btoa, a fast b64 encoder. sometimes, though, this doesn't work. */
    /*
    try {
        output = btoa(input);
        if(output) return output;
    } catch(e) { }
    */
    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 += keyStr.charAt(enc1) + keyStr.charAt(enc2) +
            keyStr.charAt(enc3) + keyStr.charAt(enc4);
    } while (i < len);

    return output;
};

(function() {
    var decode = b64.decode;
    var encode = b64.encode;
    b64 = decode;
    b64.decode = decode;
    b64.encode = encode;
})();

/**
 * Converts a string into its hexadecimal equivalent
 * @tparam String str The string to be converted
 * @tparam String space Optional. A string to insert between each hex-byte. Defaults to the empty string.
 * @treturn String A hexadecimal string.
 */
BinToHex = function(str, space) {
    if (!str || str.length === 0) {
        return str;
    }

    var hexchars = "0123456789ABCDEF";
    var h = "";

    if (!space) { space = ""; }

    for (var i = 0, len = str.length; i < len; i++) {
        val = str.charCodeAt(i);
        if(val == -1) continue;
        h += hexchars.charAt( (val >> 4) & 0x0f) + hexchars.charAt(val & 0x0F) + space;
    }

    return h;
};

/**
 * Converts a hexadecimal string into its binary equivalent
 * @tparam String str The string to be converted
 * @treturn String A binary string.
 */
HexToBin = function(str) {
    if(!str || str.length === 0) {
        return str;
    }

    str = str.toLowerCase();

    var hexchars = "0123456789abcdef";
    var b = "";
    var ch = -1;

    for (var i = 0, len = str.length; i < len; i++) {
        var val = hexchars.indexOf(str.charAt(i));
        if(val == -1) continue;
        if(ch == -1) {
            ch = val << 4;
        } else {
            b += String.fromCharCode(ch + val);
            ch = -1;
        }
    }

    if(ch != -1)
        b += String.fromCharCode(ch);

    return b;
};


/* Package: TBLive
** License: Copyright (C) 2006-2007 Trustbearer Labs
**          All rights reserved.
** REDISTRIBUTION PROHIBITED WITHOUT PERMISSION
*/

/**
 * \file tblive.js
 * \brief The core TBLive client API
 * \addtogroup JavaScriptAPI
 * @{
 */

 /**
  * certProgressText: Optional. This is the text that will be displayed when the certificate is being fetched. Default = "Retrieving certificate..."
  */
var errorPage = "support.html"; //#ERROR_PAGE#

var genericDeviceName = "credential";   //#DEVICE_NAME#
var vTokenName = "software token";      //#VTOKEN_NAME#
var SimpleVirtualToken = "null";         //#SIMPLE_VTOKEN#

var images = {
	reader: "token-card.gif",       //#READER_DIALOG_IMAGE#
	usb: "token-hardware.gif",      //#USER_DIALOG_IMAGE#
	vToken: "token-software.gif",   //#VTOKEN_DIALOG_IMAGE#
	tpm: "token-tpm.gif"            //#TMP_DIALOG_IMAGE#
};

var noDeviceMsg = "No devices found";   //#NO_DEVICE_MESSAGE#
var deviceMsg = "Accessing " + genericDeviceName + " &hellip; this may take a moment";//#DEVICE_MESSAGE#

var generalProgressMsg = "Loading &hellip; this may take a moment"; //#GENERIC_LOADING_MESSAGE#
var certProgressMsg = "Retrieving certificate &hellip; this may take a few moments";//#CERTIFICATE_PROGRESS#

var enabledImage = webAssetRoot + "/images/trustbearer-signin.gif";
var disabledImage = webAssetRoot + "/images/trustbearer-signin_disabled.gif";
var dialogHeader = "images/branding/dialog-header.jpg";//#HEADER_IMAGE#
if (dialogHeader == "null")
    dialogHeader = webAssetRoot + "/images/dialog_header.jpg";

/* NOTE: Refs jsonRpcCall in tbUtil.js */

function getTBLive() {
    if (null != window._cached_tblive)
        return _cached_tblive;
    _cached_tblive = $("#TBLive")[0];
    return _cached_tblive;
}

/* Use function to help protect environment */
(function() {

var scriptExtension = window.scripts && scripts.defaultExtension || "php";
var tbScripts = window.scripts && scripts.tblive || {};

function getScript(name) {
	return webCoreRoot + "/" + (tbScripts[name] || (name + "." + scriptExtension));
}

var tbRemoteErrorPage = "support.html";//#ERROR_PAGE#

xmlrpc = {
    DSMLoaded: false,
    install_page: getScript("install"),
    redirect_page: getScript("redir"),
    remoteErrorPage: tbRemoteErrorPage,
    enableAutoLoad: window.tbEnableAutoLoad || false,
    /* NOTE: Deprecated - this is not the right place for such state */
    indicator_image: webAssetRoot + "/images/indicator_medium.gif"
};

if (errorPage == "null") {
    errorPage = window.tbRemoteErrorPage || webLibRoot + "/support_details.html";
}

// Make sure DSMs are reloaded if the page is unloaded (thus plugin state cleared)
$(window).bind("unload", function() {
	xmlrpc.DSMLoaded = false;
});
$(function() {
	var portDef = "";
	if(window.location.port && window.location.port !== "")
		portDef = ":" + window.location.port.toString();
	xmlrpc.tblive_logo_abs = window.location.protocol + "//" + window.location.hostname + portDef + dialogHeader;
});

/**
 * @tparam int faultCode a fault code
 * @tparam String faultString A string describing the error.
 * @tparam String faultTrace Describes the code location that caused the error.
 */
xmlrpc.Exception = function(faultCode, faultString, faultTrace) {
	this.xmlrpc = true;
	this.faultCode = faultCode;
	this.faultString = faultString;
	this.faultTrace = faultTrace;
	this.component = "none";
	this.shortform = "TBErrorGeneralError";
	this.longform = faultString;
	this.codelocation = "UNKNOWN:0";

    /* JSON ENCODED ERROR */
    /* STRIP ANY EXTRA JUNK */
    var actual_error;
    try {
        actual_error = faultString.match(/{(.*)}/g)[0];
        eval("actual_error = " + actual_error);
    } catch(e) {
    }
    if (actual_error)
        faultString = actual_error;

    if(typeof(faultString) == "object") {
        for (var name in faultString) {
            this[name] = faultString[name];
        }
    } else {
        /* TODO: Remove extra encoding in Lua-side */
        var data = faultString.match(/<Component>(.*)<\/Component>/);
        if (data && data.length > 1)
            this.component = data[1];
        data = faultString.match(/<ShortForm>(.*)<\/ShortForm>/);
        if (data && data.length > 1)
            this.shortform = data[1];
        data = faultString.match(/<LongForm>(.*)<\/LongForm>/);
        if (data && data.length > 1)
            this.longform = data[1];
        data = faultString.match(/<code>(.*)<\/code>/);
        if (data && data.length > 1)
            this.codelocation = data[1];
    }
};

xmlrpc.Init = function() {
    var tb = getTBLive();
    if (!tb || !tb.version) {
        throw new xmlrpc.Exception(1, "<Component>browser</Component><ShortForm>TBErrorNoPlugin</ShortForm><LongForm>Failed to find TrustBearer Live browser plugin</LongForm>");
    }

    if (!xmlrpc.DSMLoaded) {
        /* Loads up the core */
        if (!tb.loadCore(tbCoreVersionSelector)) {
            throw new xmlrpc.Exception(1, "<Component>browser</Component><ShortForm>TBErrorNoPlugin</ShortForm><LongForm>Failed to find TrustBearer Live browser plugin</LongForm>");
        }
        tbRetrieveDSM("TrustedSources.dsm", "", true);
        tbRetrieveDSM("DSMCORE.dsm", "", true);
        xmlrpc.DSMLoaded = true;
        tbCall("LoadPreReqDSMs");
        /* Setup the options... right now only enable context caching */
        tbCall("SetOption", "CacheContext", true);
        tbRetrieveDSM("luaasn1.dsm");
        tbRetrieveDSM("policy.dsm");
    }
};

// Caching mechanism to prevent duplicate error reports...
var maxStored = 10;
var alreadyLogged = [];
var decodedErrors = [];
var errorMessages = [];

/**
 * Utility function to try to pre-process any JavaScript exception into one
 * understandable by TBLive's error system
 * @tparam Object e The exception to convert
 * @treturn Object of the xmlrpc.Exception flavor
 */
var simpleErrorToException = function(e) {
    return {
        component: e.component || "none",
        shortform: e.shortform || e.source || "TBErrorUnknown",
        longform: e.longform || e.faultString || e.message || (e && e + "") || "-Unknown-",
        codelocation: e.codelocation || (e.fileName && e.lineNumber && ("JS:" + e.fileName + ":" + e.lineNumber)),
        xmlrpc: true
    };
}
/**
 * Requests that the TBLive Daemon log that an exception has occurred.
 * @tparam Error err A JavaScript error object or a TBError object
 * @tparam String tokenId The token (if any) that was being used when the error occurred
 * @tparam Function callback A function to be called when the error has been logged
 * @tparam boolean noProcessing Forces the exception to get passed-through without any pre-processing
 */
xmlrpc.logError = function(err, tokenId, callback, noProcessing) {
    if (!noProcessing && !err.xmlrpc) {
        err = simpleErrorToException(err);
    }

    var uniqueId;
    if (tokenId) {
        try {
            uniqueId = tbCall("TokenGetInfo", tokenId, "serial")[0];
        } catch(e) {
        }
    }

    var decoded, retrieved, storeAlreadyLogged = true;
    var idx = alreadyLogged.indexOf(err);
    if(idx >= 0) {
        storeAlreadyLogged = false;
        decoded = decodedErrors[idx];
        retrieved = errorMessages[idx];
    } else {
        var loc = err.lineNumber && err.fileName && ("JS:" + err.fileName + ":" + err.lineNumber);
        decoded = xmlrpc.tbDecodeMessage(err.faultString, tokenId, uniqueId, err);
        if(decoded.codeLocation == "UNKNOWN:0" && loc) {
            decoded.codeLocation = loc;
        }
    }

    function handleLookup(ret) {
        if(storeAlreadyLogged) {
            alreadyLogged.unshift(err);
            decodedErrors.unshift(decoded);
            errorMessages.unshift(ret);
            alreadyLogged[maxStored] = null;
            decodedErrors[maxStored] = null;
            errorMessages[maxStored] = null;
        }
        if(noProcessing)
            return;
        /* Need to use responseText because a real DOM needs to be setup... */
        if(!ret || ret.error) {
            err.guiMsg = "Unable to retrieve error from database\r\nOriginal Error:\r\n" + err.longform;
            if(ret.error) {
                err.guiMsg += "\r\nServer Error:\r\n" + (ret.error.message || ret.error);
            }
            if(callback)
                return callback(err);
        }
        if(ret.result instanceof Array)
            ret.result = ret.result[0];
        var msg = ret.result.message;
        var id = ret.result.id;
        err.guiMsg = msg;
        err.supportMessageID = id;
        err.longform = decoded.longform;
        err.shortform = decoded.shortform;
        err.component = decoded.component;
        err.token = tokenId;

        if(err.guiMsg) {
            err.htmlMessage = true;
            err.guiMsg = err.guiMsg
                .replace(/\$longform/ig, decoded.longform || "<NULL>")
                .replace(/\$shortform/ig, decoded.shortform || "<NULL>")
                .replace(/\$component/ig, decoded.component || "<NULL>")
                .replace(/\$token/ig, tokenId || "<UNKNOWN>");
            return callback(err);
        }
        err.guiMsg = decoded.longform;
        if(callback)
            return callback(err);
    }
    if(retrieved) {
        handleLookup(retrieved);
    } else {
        /* DEPRECATED: Setup shortForm and longForm as error logging expects */
        decoded.longForm = decoded.longform;
        decoded.shortForm = decoded.shortform;
        tbUtil.jsonRpcCall("ErrorLog.addErrorEntry", decoded, handleLookup, null, null, {remoteHost: true});
    }
};

(function() {

/**
 * Utility function to see if the available TBLive version matches the requested version
 * @internal
 */
function rawCheckVersion() {
    try {
        var tb = getTBLive();
        if (!tb || !tb.version) {
            /* TBLive not available = not match version */
            return false;
        }
        /* No longer to plugin version mgmt in JS... except to dictate to plugin what to use/upgrade */
       return true;
    } catch(e) {
    }
    return false;
}

/**
 * Utility function to attach tbPluginWatch to all .download links
 * @internal
 */
function prepareAutoLoad(div, data) {
    var button = $(".download", div);
    tbPluginWatch(button, function(installed, mode) {
        if(mode == "newInstall")
            window.location.reload();
    });
}

var lockOverlayActive = false;
/**
 * Ensures that the browser plugin is the same version as the javascript.
 * NOTE: May be Asynchronous due to the necessary delay in Safari...
 * @tparam boolean allowClose If this is not true, the user cannot close the 'please update' window.
 * @tparam Function callback A function to call when the process has completed.
 * @tparam Internal tries Internal. Ignore this parameter.
 */
tbCheckVersion = function(allowClose, callback, tries, skipInstallDialog) {
	tries = (tries && tries + 1) || 1;
	var rv = rawCheckVersion();
	if(!lockOverlayActive && !rv && tries == 1 && !skipInstallDialog) {
		lockOverlayActive = true;
		$.blockUI({message:null,overlayCSS:{opacity:0}});
	}
	if(!rv && tries < 8) {
		setTimeout(function() {
			tbCheckVersion(allowClose, callback, tries, skipInstallDialog);
		}, 500);
		return;
	}
	if (!rv) {
		if(!skipInstallDialog)
			tbVersionError(allowClose, xmlrpc.enableAutoLoad ? prepareAutoLoad : null);
	} else {
		if(lockOverlayActive) {
			$.unblockUI();
			lockOverlayActive = false;
		}
		try {
			xmlrpc.Init();
		} catch (e) {
			if (e.faultCode == 401) {
				tbServerAuthFailed();
			} else {
				tbError(e);
			}
			rv = false;
		}
	}
	if(callback)
		callback(rv);
	return;
};

/* BEGIN NEW VERSION CHECK API */
var MAX_VERSION_TRIES = 8;
var VERSION_TRY_INTERVAL = 500;
var PLUGIN_CHECK_INTERVAL = 250;
var LOCK_OVERLAY_ENABLED = true;


function showPollDialog()
{
	var info = xmlrpc.getPlatformInfo();
	var file = webPluginRoot + "/tblive-" + info.os.toLowerCase() + "-" + info.arch.toLowerCase();

	var exts = {
		MAC: ".dmg",
		WIN: ".exe"
	};

	if(exts[info.os]) file += exts[info.os];

	if(xmlrpc.getPlatformInfo().os == "WIN") {
		window.open(file);
	}
	tbDialog("Waiting for plugin to become available &hellip; if you are not prompted, <a href='" + file + "'>click here</a> to download the plugin.</a>");
}

function performVersionCheck(callback, tries, deferPolling) {
    var rv = rawCheckVersion();
    if(LOCK_OVERLAY_ENABLED && !rv) {
        lockOverlayActive = true;
        $.blockUI({message:null,overlayCSS:{opacity:0}});
    }
    if (!rv && tries) {
        setTimeout(function() {
            performVersionCheck(callback, --tries, deferPolling);
        }, VERSION_TRY_INTERVAL);
        return;
    }
    if(lockOverlayActive)
        $.unblockUI();
    var tb = getTBLive();
    /* If the plugin is present (too old) user must manually perform check
     * TODO: See how this can be fixed */
    var pollFunc = function() {
        if (tb && tb.version)
            return;
        var interval = setInterval(function() {
            if(!$.browser.msie) {
		var _ = window.navigator && navigator.plugins && navigator.plugins.refresh && navigator.plugins.refresh(true);
            }
            if (hasTBLivePlugin()) {
                document.location.reload(true);
            }
        }, PLUGIN_CHECK_INTERVAL);
    }
    if(!rv && !deferPolling) {
        showPollDialog();
        return pollFunc();
    }

    callback(rv, deferPolling || pollFunc);
}

/**
 * Checks for a usable version of the plugin according to version.
 * If no plugin found, a plugin-polling function is offerred to perform
 * a search for the plugin and page-reload.
 *
 * Inputs:
 *   callback: function(present, startPolling) {}
 *             The callback to call when plugin status has been discovered.
 *
 * NOTE: Does not perform xmlrpc.init like the old one did.  This is the
 *       responsibility of the caller or subsequent tbCall.
 */
tbPluginCheck = function(callback, deferPolling) {
    performVersionCheck(callback || function() {}, MAX_VERSION_TRIES, deferPolling);
}
/* END NEW VERSION CHECK API */


tbVersionError = function(allowClose, callback) {
	/* FROM: core/dialog/installDialog.js */
	tbSetupInstallDialog(null,allowClose, callback);
}
})();

(function() {
})();

/**
 * Utility function to call out and retrieve a DSM / DSM hash
 * @tparam String name DSM name (including .dsm)
 * @tparam String data Information for the server --unused--
 * @tparam Boolean gethash If set, the value returned is the hash of the DSM to load
 * @treturn String the DSM data or the DSM hash retrieved from the server
 */
function getDsmItem(name, data, gethash) {
    var ret = tbUtil.jsonRpcCall("DSM.getDSM", [name, !!gethash, data], null, true);
    if(!ret.result)
        throw ret.error || ret;
    return ret.result;
};

/**
 * Retrieves and executes a DSM.
 * @tparam String name The filename of the DSM to retrieve
 * @tparam var data FIXME -- unknown
 * @tparam boolean noxml FIXME -- unknown
 * @tparam String extra FIXME -- unknown
 */
tbRetrieveDSM = function(name, data, noxml, extra) {
    var rv;
    if (name.constructor == Object) {
        rv = [];
        if (name.length) {
            for (var i = 0, len = name.length; i < len; i++) {
                rv[i] = tbRetrieveDSM(name[i], data, noxml, extra);
            }
            return rv;
        } else {
            throw new xmlrpc.Exception(0, "Invalid parameter to tbRetrieveDSM (module name invalid)");
        }
    }
    var response = getDsmItem(name, data);
    if (!noxml) {
        rv = tbCall("LoadDSM", response, extra);
        if (rv[0] && rv[0] !== 0) {
            throw new xmlrpc.Exception(0, "XML LoadDSM failed: " + name);
        }
    } else {
        tbLoadDirectDSM(response);
        rv = [];
    }

    return rv;
};

/**
 * Retrieves a list of DSMs and executes each with the matching additional argument
 * @tparam Array list DSM names to load
 * @tparam Array args list of arguments to pass to the associated DSM loading
 * @treturn Array a list of return values from each DSM loading process
 */
tbRetrieveDSMs = function(list, args) {
    var ret = tbUtil.jsonRpcCall("DSM.getDSMs", [list], null, true);
    if(!ret.result)
        throw ret.error || ret;
    var dsms = ret.result;
    var ret = [];
    for(var i = 0; i < dsms.length; i++) {
        var rv = tbCall("LoadDSM", dsms[i], args[i]);
        if(rv[0] && rv[0] !== 0) {
            /* XXX: How should this really be handled... */
            throw "Error loading DSM #" + i;
        }
        ret[i] = rv;
    }
    return ret;
}

/**
 * Function to raise an error on server authentication failure
 */
tbServerAuthFailed = function() {
	tbError({
		xmlrpc:true,
		component: 'server',
		shortform: 'TBErrorUnauthorized',
		longform: 'Server authentication failed: TrustBearer Live will fail to operate'
	})
};

function asyncCall()
{
	var args = toArray(arguments);
	args.unshift("AsyncCall");
	var callback = args.pop();
	if(!Object(callback) instanceof Function) {
		args.push(callback);
		callback = false;
	}
	var id = tbCall.apply(this, args)[0];
	if(callback) {
		setTimeout(function(){
			try {
				var ret = tbCall("CheckAsyncCall", id);
				if(!ret) return setTimeout(arguments.callee, 500);
				callback(true, ret);
			} catch(e) {
				callback(false, e.shortform || "Unknown", e);
			}
		});
	}
}

function tbIsInitialized(tokenId)
{
	if(Object(tokenId) instanceof String) {
		return !tbCall("TokenGetInfo", tokenId, "requiresInitialization")[0];
	} else {
		return !tokenId.getInfo("requiresInitialization");
	}
}

window.tbIsInitialized = tbIsInitialized;

function tbInitializeDevice(tokenId, callback)
{
	if(!tokenId) throw new Error("Must specify tokenId");
	if(!callback) {
		callback = function(success, ret, err) {
			if(!success && ret != "Cancelled") {
				return tbError(err);
			}
		}
	}
	return tbFormCall("TokenInitialize", tokenId, [], callback);
}

window.tbInitializeDevice = tbInitializeDevice;

var tbPluginWatchOptions = {
	frequency: 500,
	delay: 1000,
	timeout: 0,
	upgrade: false,
	getInstaller: true
};

function quickPluginCheck()
{
	var ok = false;
	if(getTBLive()) {
		ok = true;
		try {
			tbCall("GetSafecalls")
		} catch(e) { ok = false; }
	}
	return ok;
}

function tbPluginWatch(link, callback, options)
{
    options = options || {};
    for(var k in tbPluginWatchOptions) if(!options[k]) options[k] = tbPluginWatchOptions[k];

    if(!options.upgrade && quickPluginCheck())  {
        return callback(true, "alreadyInstalled");
    }

    var start = new Date();
    var checkFunc;

    return setTimeout(function() {
        var time = new Date() - start;
        if(options.timeout && time > options.timeout) return callback(false, "timeout");
        var me = arguments.callee;
        if(!$.browser.msie) {
		var _ = window.navigator && navigator.plugins && navigator.plugins.refresh && navigator.plugins.refresh(false);
	}
        if(!hasTBLivePlugin()) return setTimeout(me, options.frequency);
        return callback(true, "newInstall");
    }, options.delay || options.frequency);
}

window.tbPluginWatch = tbPluginWatch;

})();

$(function() {
    function __initTB__() {
        if(null != window.tb && null != tb.init && getTBLive()) {
            try {
                tb.init();
            } catch (e) {
                /* FAIL - NO RETRY */
                tbError(e);
                return;
            }
            /* Nullify function's impact */
            tb.init = function() {}
        } else {
            /* Call every 500 ms... should be adequate.
             * If not, better mechanism needs to be put in place */
            setTimeout(__initTB__, 500);
        }
    }
    __initTB__();
});


/**
 * tbCall processing code specific to the post-3.0 series of plugins
 * NOTE: current check for 'server_port' is improper
 */
(function() {

function _parseTBCallResultJSON(rv) {
    /* TODO: Optimize for case where eval actually returns results */
    eval("rv = " + rv);
    return rv;
}

function handleTBCallResult(rv) {
	//console.debug("RESULTS:",rv);
    var status = rv.shift();
    if (!status) {
        /* PARSE ERROR ACCORDING TO OLD XMLRPC - however ret is just the faultString */
        var faultCode = -1;
        var faultString = "Unknown XML-RPC fault";
        var faultTrace = null;

        if (typeof(rv[0]) == "string") {
            faultString = rv[0];
        } else {
            faultCode = rv[0].faultCode || faultCode;
            faultString = rv[0].faultString || faultString;
            faultTrace = rv[0].faultTrace || faultTrace;
        }
        throw new xmlrpc.Exception(faultCode, faultString, faultTrace);
    }
    if (rv[0] == "RetrieveDSM" && rv[1]) {
        return tbRetrieveDSM(rv[1], rv[2], false, rv[3]);
    } else if(rv[0] == "RetrieveDSMs") {
        return tbRetrieveDSMs(rv[1], rv[2]);
    } else if (rv[0] == "eval" && rv[1]) {
        window.eval(rv[1]);
        return [];
    } else {
        return rv;
    }
}
// tbCall([callback], CMD, arguments...)
tbCall = function() {
    if (arguments.length < 1) {
        return "";
    }

    xmlrpc.Init();
    var tb = getTBLive();
    if (null ==  tb) {
        throw new xmlrpc.Exception(0, "No XMLHTTP control is available");
    }
	var params = [];
	for (var i = 1, len = arguments.length; i <len; i++)
		params[i - 1] = arguments[i];
	var cmd = arguments[0];
    var params = $.toJSON(params, true);
    var rv = tb.call("JSTBCall", cmd, params);
    /* Parse out the return */
	rv = _parseTBCallResultJSON(rv);
    return handleTBCallResult(rv);
};

/**
   @lua function scard.GetStatusChange(hContext, dwTimeout, [in/out] tblReaderStates)
 */
tbLoadDirectDSM = function(data) {
    var tb = getTBLive();
    if (null == tb) {
        throw new xmlrpc.Exception(0, "No XMLHTTP control is available");
    }
    /* Perform raw push */
    var rv = tb.call("LoadDSM", data);
    if (null == rv)
        return;
    rv = _parseTBCallResultJSON(rv);
    return handleTBCallResult(rv);
};

tbLuaLog = function(args) {
    var tb = getTBLive();
    if (null == tb) return;
    try {
        /* NOTE: Needs impl + needs additional param handling */
        tb.call("Log", args);
    } catch(e) {
    }
}
})();

(function() {
var progressDialog;
/**
 * Displays a progress dialog. Dismiss it with tbHideDiv().
 */
tbShowProgressDialog = function() {
    if(!progressDialog) {
        var div = $(
            "<div class='center'>" +
                "<p>" + deviceMsg + "</p>" + 
                "<img src='" + xmlrpc.indicator_image + "'/>" +
             "</div>"
        );
        progressDialog = tbApplyTemplate(div);
    }
    return tbShowDiv(421, 189, progressDialog, 'Progress');
};

/**
 * Authenticates to the specified token as an administrator.
 * @tparam String tokenId A token identifier.
 * @tparam Function callback A function to be called when the process is complete.
 */
tbVerifyAdmin = function(tokenId, callback) {
    var setup;
    var result = tbCall("TokenAdminPage", tokenId)[0];
    var div = $("<div class='tbPinDlg'><form class='noop'>" + result.HTML + "</form></div>");
    $('form.noop', div).submit(function() { return false; });
    if (result.script) setup = eval(result.script);
    var divID = tbShowDiv(result.width, result.height, tbApplyTemplate(div), 'VerifyAdmin');
    if(setup)
        setup(div, divID, callback);
    return divID;
};

var TB_UNEXPECTED_ERROR = "<b>An unexpected error has occurred.</b>";

/**
 * Posts an error to the TBLive daemon retrieves a localized error message
 * from its database.
 * @tparam Object err The error object that needs to be looked up
 * @tparam String tokenId Optional. The token (if any) that is related to the error.
 * @todo: DOCUMENT callback
 */
tbError = function(err, tokenId, callback)
{
    if(Object(tokenId) instanceof Function) {
        callback = tokenId;
        tokenId = err.token || "";
    }
    xmlrpc.logError(err, tokenId, function(ret) {
        var box = $(
            "<div>" +
                "<div class='tbErrMsg'>" +
                    "<br/><br/>" +
                    "<span><a class='moreInfo' target='_blank'>Get more information &hellip;</a></span>" +
                "</div>" +
                "<br/>" +
            "</div>");
        var errorMessage = ret.guiMsg || ret.htmlMessage || TB_UNEXPECTED_ERROR;
        $(".tbErrMsg", box).prepend(errorMessage);
        if(errorMessage != TB_UNEXPECTED_ERROR) {
            $(".tbErrMsg", box).prepend("<b>An Error has occurred:</b><br/>");
        }
        var errorData = {
            id: err.supportMessageID,
            shortform: err.shortform,
            longform: err.longform,
            component: err.component,
            token: err.token
        }
        $.each(errorData, function(k,v) {
            if(!v)
                delete errorData[k];
        });
        errorData = $.param(errorData);
        $("a.moreInfo", box).attr("href", errorPage + "?" + errorData);
        tbRawAlert(box, callback);
    });
}

})();

/**
 * \file tblive-ui.js
 * \brief Utility functions for UI magic
 * \addtogroup JavaScriptAPI
 * @{
 */

/* Returns the new dom element to use */
function tbApplyTemplate(dom) {
	var container = $.create('div', { style: {width: '100%', 'background-color': '#EFEFEF'} }, [
		'center', ['img', {src: dialogHeader} ],
		'div', {'class': 'templateContainer', style: {padding: '5px'} }, [dom]
	]);
	return container;
}

/* Plus 6 to handle border */
function tbShowFrame(width, height, url, forced, scrolling) {
	return tbShowDiv(width+6, height+6, $('<iframe scrolling="' + (scrolling || 'no') + '" src="' + url + '"></iframe>').css({height: height, width: width}), forced, null, true);
}

/* 1 and greater for functions that check return value */
var divId = 1;
var displayQueue = [];
var currentItem = null;

if($.blockUI.version >= 2) {
	$.blockUI.defaults.applyPlatformOpacityRules = false;
} else {
	$.blockUI.impl.ffLinux = false; /* For ffLinux, they disable grey-out.. we want it no matter what.. */
	$.blockUI.defaults.overlayCSS.backgroundColor = '#000000';
}
function tbDisplayNew(item) {
	var width = item[0];
	var height = item[1];
	var contents = item[2];
	var centerTop = item[5];
	var css = {
		width: width || 'auto',
		height: 'auto', /* Disable marked height from affecting 'real' height */
		'text-align': 'left',
		cursor: 'default',
		'background-color' : '#EFEFEF',
		border: 'none'
	};
	if(width) {
		css.width = width;
		css.left = (($(window).width() - width) / 2) + "px";
	}
	if(height) {
		if(height > 400)
			css.top = (($(window).height() - height) / 2) + "px";
		else if(height > 600)
			css.top = '10%';
		else
			css.top = '30%';
	}
	if(item[4]) css.height = height; /* If frame, use height */
	var data = {
		message:contents,
		css: css
	};
	$.blockUI(data);
}

function _tbShowNextDialog() {
	currentItem = displayQueue.shift();
	if(currentItem)
		tbDisplayNew(currentItem);
}

function tbShowDiv(width, height, contents, force, displayTag, iframe, level, top) {
	return tbShowDiv(width, height, contents, force, displayTag, iframe, level, top);
}

/**
 * Shows a styled blockUI DIV.
 * @tparam int width The width of the div
 * @tparam int height The height of the div.
 * @tparam String contents The HTML or DOM contents of the DIV
 * @tparam Unknown force FIXME -- document this
 * @tparam Unknown displayTag FIXME -- document this
 * @tparam Unknown iframe FIXME -- document this
 * @tparam Unknown level FIXME -- document this
 * @treturn String The ID of the window (FIXME -- is this true?)
 */
function tbShowDiv(width, height, contents, force, displayTag, iframe, level, top) {
	/* TODO: Manage queue... */
	var newItem = [width, height, contents, displayTag, iframe, top];
	newItem.id = divId++;
	newItem.level = level || 0;
	if(currentItem) {
		if(force || newItem.level > currentItem.level) {
			displayQueue.unshift(currentItem);
		} else {
			displayQueue.push(newItem);
			return newItem.id;
		}
		// Hide this top div, will cause the new dialog to show when complete
		displayQueue.unshift(newItem);
		tbHideDiv();
	} else {
		displayQueue.unshift(newItem);
		_tbShowNextDialog();
	}
	return newItem.id;
}

function tbHideDivById(id) {
	displayQueue = $.grep(displayQueue, function(item) {
		return item.id !== id;
	});
	if(currentItem && currentItem.id == id) {
		tbHideDiv();
	}
}

/**
 * Hide a DIV created by tbShowDiv
 * @tparam String id optional The identifier returned by tbShowDiv. If not present, all divs are hidden.
 */
function tbHideDiv(id) {
	if(id !== undefined && id !== null) {
		return tbHideDivById(id);
	}
	currentItem = null;
	if(displayQueue.length > 0) {
		/* NEW DIALOG WILL BE SHOWN DUE TO UNHOOK CODE */
	} else {
	}
	$.unblockUI({
		fadeOut: 0,
		onUnblock: function(element, options) {
			/* Show the next dialog from the stack */
			_tbShowNextDialog();
		}
	});
}
window.tbHideFrame = tbHideDiv;

var itemMatches = function(item, re) {
	return item && item[2] && 
		((item[2].constructor == String && item[2].match(re))
		|| (item[3] && item[3].constructor == String && item[3].match(re)));
};

function tbClearUIMatch(re, invert) {
	/* Empty the queue of matching items... */
	displayQueue = $.grep(displayQueue, function(item) {
		return !itemMatches(item, re);
	}, invert);
	var currentMatch = itemMatches(currentItem, re);
	if(currentItem && (invert && !currentMatch || !invert && currentMatch)) {
		tbHideDiv();
	}
}

/* Create a 'custom' listbox. */

function addStyle(style)
{
	var tag = document.createElement("style");
	tag.type = "text/css";
	if($.browser.msie) {
		tag.styleSheet.cssText = style;
	} else {
		tag.appendChild(document.createTextNode(style));
	}
	$("head").append(tag);
}

function convertListbox(box, selectedStyle)
{
	var uid = Math.random().toString().match(/^0\.(.*)$/)[1];
	box = $(box);
	var options = $("option", box);
	box.wrap("<div id='listbox" + uid + "'></div>");
	var ret = box.parent();
	ret.append("<ul style='list-style-type:none; padding-left: 0px; padding-top: 0.25em; border: 1px solid #e0eeee; font-family: verdana; font-size: 8pt; background: white'></ul>");
	box.hide();
	$(options).each(function(i,option) {
		var li = $("<li></li>");
		li.text($(option).text());
		li.click(function(){
			$("li", ret).removeClass("selected");
			$(this).addClass("selected");
			box.val(option.value);
			box.change();
			return false;
		});
		$("ul", ret).append(li);
	});
	$("li", ret).css("padding-left", "0.25em");
	addStyle("#listbox" + uid + " li.selected { " + (selectedStyle || "border: 1px dotted #808e8e; background: #f0fefe;") + "}");
}

/*
 *  Copyright (C) 2007, TrustBearer Labs <http://trustbearer.com>
 *  All Rights Reserved
 *
 *    Title: tblive-rpc.js
 *  Authors: Thomas Harning <thomas.harning@trustbearer.com>
 *  Purpose: RPC-related functions for TBLive/TBLive.
 */
 
/**
 * \file tblive-rpc.js
 * \brief RPC-related functions for TBLive/TBLive.
 * \addtogroup JavaScriptAPI
 * @{
 */
 
(function() {
window.tbUtil = window.tbUtil || {};

var idCount = 0;

/* getJsonRpcQuery : Prepares an url to perform a jsonRpcCall */
tbUtil.getJsonRpcQuery = function(method, args) {
	var data = {params: args, version: "1.1"};
	data.id = idCount++;
	var urlData = {
		method: method,
		jsonData: $.toJSON(data)
	};
	var url = window.webRpcTarget;
	if(window.GWT_DEBUG)
		url += (encodeURIComponent('?' + $.param(urlData)));
	else
		url += '?' + $.param(urlData);
	return url;
}

/**
 * Calls a method on the daemon
 * callback: function(result) {
 * 	if(result.error) { handle error; return; }
 * }
 *
 * Callback is expected to handle result.error cases and stop further execution.
 * This function may be called twice in the event of an exception being thrown in the handler,
 * however it will be 'caught' in the if(result.error) block. If another error happens,
 * then that last error is eaten up since it happened w/in the error handler block.
 *
 * @tparam String method The fully-qualified name of the method to be called (ie tblive.plugins.PIVPerso.getCertificate)
 * @tparam Array args The arguments to be passed to the method
 * @tparam Function callback This is called with the result when the function completes.
 * @tparam bool synchronous If this is true, the function will block until it completes (and return the result?)
 * @tparam bool hideMethod When using HTTP POST method, do not pass the method name as a URL parameter when set to true.
 * @tparam Unknown extraData FIXME -- document this
 * @tparam String mode Whether to use HTTP GET ("GET") or POST (any other value) to do the request.
 */

tbUtil.jsonRpcCall = function(method, args, callback, synchronous, hideMethod, extraData, mode) {
	var data = {params: args, version: "1.1"};
	data.id = idCount++;
	if(hideMethod && mode != 'GET')
		data.method = method;
	var returnData;
	var currentError;
	if(callback && typeof(callback) == 'function' || synchronous)
	{
		var decodeCallback = function(ret) {
			try {
				var result = ret.result;
				if(result && result.binary) {
					for(var i = result.binary.length - 1, name; i >= 0; i--) {
						name = result.binary[i];
						result[name] = b64.decode(result[name]);
					}
				}
				if(callback)
					callback(ret);
				if(synchronous)
					returnData = ret;
			} catch(e) {
				currentError = e;
				throw e;
			}
		};
	}
	var requestData = $.toJSON(data);
	var url = window.webRpcTarget;
	if(mode == 'GET' || !hideMethod) {
		if(!extraData) {
			extraData = {};
		}
		extraData.method = method;
		if(mode == 'GET')
			extraData.jsonData = requestData;
		if(window.GWT_DEBUG)
			url += (encodeURIComponent('?' + $.param(extraData)));
		else
			url += '?' + $.param(extraData);
	}
	var request = {
		url: url,
		type: 'POST',
		dataType: 'json',
		contentType: 'application/json',
		success: decodeCallback,
		error: function(xmlhttp,err, ex) {
			if(!currentError && xmlhttp.status == 200)
				err = "Invalid data returned, server may be down";
			decodeCallback({error: currentError || err, id: data.id});
		},
		async: !synchronous
	};
	if(mode == 'GET') {
		request.type = 'GET';
	} else {
		request.data = requestData;
	}
	$.ajax(request);
	return returnData;
};

})();

/**
 * Takes a string containing binary TLV data and returns an array of 
 * the where the Nth index is an object of the form 
 * { tag: tagN, value: valueN }.
 * @tparam String data The TLV data to parse
 * @tparam Boolean dontRecurse If this isn't set, tags with the 
 * CONSTRUCTED bit set will be recursively parsed.
 */
function parseTLV(data, dontRecurse) {
    var originalData = data;
    var ret = [];
    while(data.length > 1) {
        var tag = data.charCodeAt(0);
        var length = data.charCodeAt(1);
        var offset = 2;
        if(length > 0x80) {
            var n = length - 0x80;
            length = 0;
            for(var i = 0; i < n; i++) {
                length = (length << 8) + data.charCodeAt(2 + i);
            }
            offset += n;
        }
        var value = data.substring(offset, offset + length); 
        if(!dontRecurse && tag & 0x20) {
            value = parseTLV(value, false);
        }
        ret.push({tag: tag, value: value});
        data = data.substring(offset + length);
    }
    if(data.length) {
        log(originalData);
        throw new Error("Invalid TLV data");
    }
    return ret;
}

/**
 * Takes a string containing binary TLV data and returns an object of 
 * the form { tagN:valueN }. Note that if there are duplicate tags at
 * the same depth, only the last one will be represented in the return
 * value. To handle these cases, use parseTLV().
 * @tparam String data The TLV data to parse
 * @tparam Boolean dontRecurse If this isn't set, tags with the 
 * CONSTRUCTED bit set will be recursively table-ized.
 */
function getTLVTable(data, dontRecurse) {
    var originalData = data;
    var ret = {};
    while(data.length > 1) {
        var tag = data.charCodeAt(0);
        var length = data.charCodeAt(1);
        var offset = 2;
        if(length > 0x80) {
            var n = length - 0x80;
            length = 0;
            for(var i = 0; i < n; i++) {
                length = (length << 8) + data.charCodeAt(2 + i);
            }
            offset += n;
        }
        ret[tag] = data.substring(offset, offset + length);
        if(!dontRecurse && tag & 0x20) {
            ret[tag] = getTLVTable(ret[tag], false);
        }
        data = data.substring(offset + length);
    }
    if(data.length) {
        log(originalData);
        throw new Error("Invalid TLV data");
    }

    return ret;
}

function Certificate(data) {
    if(!data) throw new Error("usage: new Certificate(derData)");
    if(!tb.dsm) tb.dsm = loadDSM("DSM");
    if(!tb.asn1) tb.asn1 = loadDSM("luaasn1");
    if(data.charCodeAt(0) == 0x70) {
        var tbl = getTLVTable(data, true);
        var certInfo = tbl[0x71].charCodeAt(0);
        var certData = tbl[0x70];
        if(certInfo && (certInfo & 0x80 || certInfo & 0x01)) {
            try {
                certData = tb.dsm.Unzip(certData);
            } catch(e) {
                certData = tb.dsm.Unzip(certData, true);
            }
        }
        this.data = certData;
    } else {
        this.data = data;
    }
    this.parsed = tb.asn1.decode("Certificate", this.data);
}

Certificate.prototype = {
    getExtension: function(oid) {
        var exts = this.parsed.tbsCertificate.extensions;
        if(!exts) return;
        for(var i = 0; i < exts.length; i++) {
            if(exts[i].extnID == oid) return exts[i].extnValue;
        }
    },
    getUPN: function() {
        var san = this.getExtension("2.5.29.17");
        if(!san) return;
        var upnOID = tb.asn1.exec("asn1p.encodeOID", "1.3.6.1.4.1.311.20.2.3");
        var idx = san.indexOf(upnOID);
        if(idx == -1) return null;
        var start = idx + 14;
        var len = san.charCodeAt(idx+13);
        return san.substring(start, start + len);
    },
    getFASCN: function() {
        var san = this.getExtension("2.5.29.17");
        if(!san) return;
        var fascnOID = tb.asn1.exec("asn1p.encodeOID", "2.16.840.1.101.3.6.6");
        var idx = san.indexOf(fascnOID);
        if(idx == -1) return null;
        var start = idx + 11;
        var len = san.charCodeAt(start);
        var binFascn = san.substring(start+1, start + len);
        return tb.asn1.exec("FASCN.decode", binFascn);
    },
    getSerial: function() {
        return "0x" + BinToHex(this.parsed.tbsCertificate.serialNumber);
    },
    getKey: function() {
        if(this.key) return this.key;
        return this.key = tb.dsm.parseCert(this.data).key;
    },
    encrypt: function(data) {
        return tb.dsm.RSACrypt({mode:"encrypt",key:this.getKey(), data:data, alg:"pkcs1"});
    },
    verify: function(sig, data, alg) {
        sig = sig || this.parsed.signature;
        if(alg && alg != "sha1") {
            throw new Error("sha1 is the only supported algorithm right now...");
        }
        var hash = tb.dsm.SHA1Hash(data || tb.asn1.encode("TBSCertificate", this.parsed.tbsCertificate));
        alg = alg || "sha1";
        return tb.dsm.RSACrypt({mode:"verify",key:this.getKey(), sig: sig, hash: hash, alg: alg});
    }
}

var FASCN = {};
(function() {

function binToBString(bin)
{
    var map = [
        "0000",
        "0001",
        "0010",
        "0011",
        "0100",
        "0101",
        "0110",
        "0111",
        "1000",
        "1001",
        "1010",
        "1011",
        "1100",
        "1101",
        "1110",
        "1111"
    ]
    var ret = [];
    for(var i = 0; i < bin.length; i++) {
        var ch = bin.charCodeAt(i);
        var high = Math.floor(ch / 16);
        var low = Math.floor(ch % 16);
        ret.push(map[high] + map[low]);
    }
    return ret.join(" ");
}

function bStringToBin(bStr)
{
    var ret = [];
    bStr = bStr.replace(/ /g, "");
    for(var i = 0; i < bStr.length; i+=8) {
        var ch = 0;
        for(var j = 0; j < 8; j++) {
            var bit = parseInt(bStr.charAt(i+j));
            ch = (ch << 1) + bit;
        }
        ret.push(String.fromCharCode(ch));
    }
    return ret.join("");
}

var map = {
    "00001":0,
    "10000":1,
    "01000":2,
    "11001":3,
    "00100":4,
    "10101":5,
    "01101":6,
    "11100":7,
    "00010":8,
    "10011":9,
    "11010":"[",
    "10110":"-",
    "11111":"]"
};

var reverse = {};
for(var k in map) {
    reverse[map[k]] = k;
}

function decode(fascn)
{
    var table = map;
    var ret = [];
    var bStr = binToBString(fascn);
    bStr = bStr.replace(/ /g, "");
    for(var i = 0; i < bStr.length; i+= 5) {
        var bits = bStr.substr(i, 5);
        ret.push(table[bits]);
    }
    ret[0] = "";
    ret[38] = "";
    ret[39] = "";
    return ret.join("");
}

function encode(fascn)
{
    var table = reverse;
    var ret = []
    if(!fascn.match(/^\[.*\]$/)) fascn = "[" + fascn + "]";
    for(var i = 0; i < 39; i++) {
        ret.push(table[fascn.charAt(i)]);
    }
    var str = ret.join("");
    var lrc = [0,0,0,0,1];
    for(var i = 0; i < str.length; i+=5) {
        for(var j = 0; j < 4; j++) {
            var ch = str.charAt(i+j);
            if(ch=="1") lrc[j] = (lrc[j] + 1) % 2;
        }
    }
    for(var i = 0; i < 4; i++) {
        if(lrc[i]) {
            lrc[4] = (lrc[4] + 1) % 2
        }
    }
    str += lrc.join("");
    return bStringToBin(str);
}
FASCN.encode = encode;
FASCN.decode = decode;
})();



/** JavaScript support patches */

if (!String.prototype.trim) {
    /**
     * Trim whitespace from beginning and end of a string
     * @deprecated Use (jQuery|$).trim instead
     */
    String.prototype.trim = function() {
        return $.trim(this);
    }
}

/* Array extras from JavaScript 1.6 */
if (!Array.prototype.indexOf) {
    /**
     * Returns the first index at which a given element can be found in the array, or -1 if it is not present.
     * compares elt to elements of the Array using strict equality (the same method used by the ===, or triple-equals, operator).
     * @tparam elt Any Element to locate in the array.
     * @tparam from The index at which to begin the search. Defaults to 0, i.e. the whole array will be searched. If the index is greater than or equal to the length of the array, -1 is returned, i.e. the array will not be searched. If negative, it is taken as the offset from the end of the array. Note that even when the index is negative, the array is still searched from front to back. If the calculated index is less than 0, the whole array will be searched.
     * @treturn Number The index of the given item, or -1.
     */
    Array.prototype.indexOf = function(elt, from)
    {
        var len = this.length;

        var from = Number(from) || 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;
    };
}
if (!Array.prototype.lastIndexOf) {
    /**
     * Returns the last index at which a given element can be found in the array, or -1 if it is not present. The array is searched backwards, starting at from.
     * compares elt to elements of the Array using strict equality (the same method used by the ===, or triple-equals, operator).
     * @tparam elt Any Element to locate in the array.
     * @tparam from The index at which to start searching backwards. Defaults to the array's length, i.e. the whole array will be searched. If the index is greater than or equal to the length of the array, the whole array will be searched. If negative, it is taken as the offset from the end of the array. Note that even when the index is negative, the array is still searched from back to front. If the calculated index is less than 0, -1 is returned, i.e. the array will not be searched.
     * @treturn Number The index of the given item, or -1.
     */
    Array.prototype.lastIndexOf = function(elt , from)
    {
        var len = this.length;

        var from = Number(from);
        if(isNaN(from)) {
            from = len - 1;
        } else {
            from = (from < 0) ? Math.ceil(from): Math.floor(from);
            if (from < 0) {
                from += len;
            } else if(from >= len) {
                from = len - 1;
            }
        }

        for (; from > -1; from--) {
            if (from in this && this[from] === elt) return from;
        }
        return -1;
    };
}

if (!Array.prototype.every) {
    /**
     * Tests whether all elements in the array pass the test implemented by the provided function.
     * runs a function on items in the array while that function is returning true. It returns true if the function returns true for every item it could visit.
     * @tparam fun Function Function to test for each element.
     * @tparam thisp Any Object to use as this when executing fun.
     * @treturn Boolean true if the function returns true for every item it could visit, false otherwise.
     */
    Array.prototype.every = function(fun, thisp)
    {
        var len = this.length;
        if(typeof fun != "function") throw new TypeError();

        for(var i = 0; i < len; i++) {
            if (i in this && !fun.call(thisp, this[i], i, this)) return false;
        }

        return true;
    };
}

if (!Array.prototype.filter) {
    /**
     * Creates a new array with all elements that pass the test implemented by the provided function.
     * runs a function on every item in the array and returns an array of all items for which the function returns true.
     * @tparam fun Function Function to test each element of the array.
     * @tparam thisp Any Object to use as this when executing fun.
     * @treturn Array an array of all items for which the function returns true.
     */
    Array.prototype.filter = function(fun, thisp)
    {
        var len = this.length;
        if (typeof fun != "function") throw new TypeError();

        var res = new Array();
        for (var i = 0; i < len; i++) {
            if (i in this) {
                var val = this[i]; // in case fun mutates this
                if (fun.call(thisp, val, i, this)) res.push(val);
            }
        }

        return res;
    };
}

if (!Array.prototype.forEach) {
    /**
     * runs a function on every item in the array.
     * @tparam fun Function Function to execute for each element.
     * @tparam thisp Any Object to use as this when executing fun.
     * @treturn undefined Nothing.
     */
    Array.prototype.forEach = function(fun, thisp)
    {
        var len = this.length;
        if(typeof fun != "function") throw new TypeError();

        var thisp = arguments[1];
        for(var i = 0; i < len; i++) {
            if (i in this) {
                fun.call(thisp, this[i], i, this);
            }
        }
    };
}

if (!Array.prototype.map) {
    /**
     * Creates a new array with the results of calling a provided function on every element in this array.
     * runs a function on every item in the array and returns the results in an array.
     * @tparam fun Function Function that produces an element of the new Array from an element of the current one.
     * @tparam thisp Any Object to use as this when executing fun.
     * @treturn Array array with the results of calling a provided function on every element in this array.
     */
    Array.prototype.map = function(fun, thisp)
    {
        var len = this.length;
        if (typeof fun != "function") throw new TypeError();

        var res = new Array(len);
        for(var i = 0; i < len; i++) {
            if (i in this) {
                res[i] = fun.call(thisp, this[i], i, this);
            }
        }

        return res;
    };
}

if (!Array.prototype.some) {
    /**
     * Tests whether some element in the array passes the test implemented by the provided function.
     * runs a function on items in the array while that function returns false. It returns true if the function returns true for any item it could visit.
     * @tparam fun Function Function to test for each element.
     * @tparam thisp Any Object to use as this when executing fun.
     * @treturn Boolean It returns true if the function returns true for any item it could visit, false otherwise.
     */
    Array.prototype.some = function(fun, thisp)
    {
        var len = this.length;
        if(typeof fun != "function") throw new TypeError();

        for(var i = 0; i < len; i++) {
            if (i in this && fun.call(thisp, this[i], i, this)) {
                return true;
            }
        }

        return false;
    };
}

/* Array extras from JavaScript 1.8 */
if(!Array.prototype.reduce) {
    /**
     * Apply a function simultaneously against two values of the array (from left-to-right) as to reduce it to a single value.
     * runs a function on every item in the array and collects the results from previous calls.
     * @tparam fun Function Function to execute on each value in the array.
     * @tparam initial Any Optional object to use as the first argument to the first call of the callback.
     * @treturn Any the return value of fun(this[this.length-1])
     */
    Array.prototype.reduce = function(fun, initial)
    {
        var rv;
        var len = this.length;
        if(typeof fun != "function")
            throw new TypeError();

        // no value to return if no initial value and an empty array
        if(len == 0 && undefined === initial)
            throw new TypeError();

        var i = 0;
        if(undefined !== initial) {
            rv = initial;
        } else {
            /* TODO: Evaluate necessity of this check */
            do {
                if(i in this)    {
                    rv = this[i++];
                    break;
                }

                // if array contains no values, no initial value to return
                if(++i >= len) throw new TypeError();
            } while(true);
        }

        for(; i < len; i++) {
            if (i in this)    rv = fun.call(null, rv, this[i], i, this);
        }

        return rv;
    };
}

if (!Array.prototype.reduceRight) {
    /**
     * Apply a function simultaneously against two values of the array (from right-to-left) as to reduce it to a single value.
     * runs a function on every item in the array and collects the results from previous calls, but in reverse.
     * @tparam fun Function Function to execute on each value in the array.
     * @tparam initial Any Optional object to use as the first argument to the first call of the callback.
     * @treturn Any the return value of fun(this[0])
     */
    Array.prototype.reduceRight = function(fun, initial)
    {
        var rv;
        var len = this.length;
        if(typeof fun != "function")
            throw new TypeError();

        // no value to return if no initial value, empty array
        if (len == 0 && undefined === initial)
            throw new TypeError();

        var i = len - 1;
        if(undefined !== initial) {
            rv = initial;
        } else {
            /* TODO: Evaluate necessity of this check */
            do {
                if(i in this) {
                    rv = this[i--];
                    break;
                }

                // if array contains no values, no initial value to return
                if(--i < 0) throw new TypeError();
            } while(true);
        }

        for(; i >= 0; i--) {
            if(i in this) rv = fun.call(null, rv, this[i], i, this);
        }

        return rv;
    };
}


(function() {
function getVal(obj) {
    if(Object(obj) instanceof String) return $("<span style='color:red;margin:0.25em;'></span>").text("\"" + obj + "\"");
    if(Object(obj) instanceof Number) return $("<span style='color:blue;margin:0.25em;'>" + obj + "</span>");
    if(Object(obj) instanceof Array) {
        var parent = $("<span color='black'>[</span>");
        for(var i = 0; i < obj.length; i++) {
            parent.append(getVal(obj[i]));
            if(i != obj.length-1) parent.append(",");
        }
        parent.append("]");
        return parent;
    }
    if(Object(obj) instanceof Boolean) {
        return $("<span style='color:#000080;margin:0.25em;'>" + obj + "</span>");
    }
    if(obj === null) {
        return $("<span style='background:gray;color:white;border:1px solid black;font-size:0.8em;margin:0.25em;'>null</span>");
    }
    if(obj === undefined) {
        return $("<span style='background:gray;color:white;border:1px solid black;font-size:0.8em;margin:0.25em;'>undefined</span>");
    }
    if(Object(obj) instanceof Function) {
        var ret = $("<span style='color:green;font-weight: bold;margin:0.25em;cursor:pointer'>Function</span>");
        var click = function() {
            var child = $("<pre style='border: 1px dashed black; font-face: fixedsys; font-size: 0.8em'>" + obj.toString() + "</pre>");
            ret.after(child);
            ret.unbind("click").click(function(){
                child.remove();
                ret.unbind("click").click(click);
            });
        }
        ret.click(click);
        return ret;
    }

    var ret = $("<span style='color:green;font-weight: bold;margin:0.25em;cursor:pointer'>" + obj + "</span>");
    var click = function() {
        var children = $("<table style='border:1px solid black'></table>");
        for(var k in obj) {
            var row = $("<tr class='tb_debug_row'></tr>");
            row.append("<td style='padding:0.25em;border-bottom: 1px solid gray'>" + k + "</td>");
            var valCell = $("<td style='padding:0.25em;border-bottom: 1px solid gray'></td>");
            valCell.append(getVal(obj[k]));
            row.append(valCell);
            children.append(row);
        }
        if(!$(".tb_debug_row", children).length) {
            children.append("<tr><td style='color:#b0b0b0'>There are no properties to show for this object.</td></td>");
        }
        $(this).after(children);
        $(this).unbind("click").click(function() {
            children.remove();
            $(this).unbind("click").click(click);
        });
    }
    ret.click(click);
    return ret;
}

function onTBInit(func) {
    setTimeout(function() {
        if(window.tb && window.tb.getPrefs) {
            func();
        } else {
            setTimeout(arguments.callee, 500);
        }
    }, 500);
}

var _useDebugConsole = null;
var _isLuaLogging = null;

function useDebugConsole() {
    try {
        if(_useDebugConsole === null) {
            _useDebugConsole = parseInt(tbCall("GetPrefs", "DebugConsole")[0]);
        }
    } catch(e) {
        try {
            _useDebugConsole = window.location.hash.toLowerCase().match("debug");
        } catch(e2) {
        }
    }
    return _useDebugConsole;
}

function isLuaLogging() {
    try {
        if(_isLuaLogging === null) {
            _isLuaLogging = parseInt(tbCall("GetPrefs", "isLuaLogging")[0]);
        }
    } catch(e) {
        try {
            _isLuaLogging = window.location.hash.toLowerCase().match("debug");
        } catch(e2) {
        }
    }
    return _isLuaLogging;
}

onTBInit(function(){
    if(useDebugConsole()) {
        if(!(window.console && window.console.debug) && !window.onerror) {
            window.onerror = function() {
                try {
                    var args = $.makeArray(arguments);
                    args.unshift("An uncaught exception occurred:");
                    _log.apply(window, args);
                } catch(e) { }
            }
        }
    }
});

function _log() {
    var args = [];
    for(var i = 0; i < arguments.length; i++) {
        args.push(arguments[i]);
    }

    if(window.console && window.console.debug) {
        console.debug.apply(console, args);
    } else if(useDebugConsole()) {
        var box = $("div#tblive_debug");
        if(!box.length) {
            var css = {
                background:"white",
                height:"20%",
                position:"fixed",
                "text-align":"left",
                top:"80%",
                width:"100%",
                left:"0px"
            };
            box = $("<div id='tblive_debug'><div class='debug_ctr'><ul style='list-style-type:none;list-style:none'></ul></div></div>");
            $("body").append(box);
            for(var k in css) {
                box.css(k, css[k]);
            }

            var input = $("<input type='text' style='width:100%'>");
            input.keypress(function(e){
                var key = e.which || e.keyCode || e.keycode;
                if(key == 13) {
                    var val = $(this).val();
                    $(this).val("");
                    var ret = eval(val);
                    _log(ret);
                }
            });
            $(box).append(input);
            $(".debug_ctr", box).css("overflow", "auto").css("height", "90%");
        }


        var row = $("<li style='border-top:1px solid gray;padding:0.25em;list-style:none;list-style-type:none'></li>");
        for(var i = 0; i < args.length; i++) {
            row.append(getVal(args[i]));
        }
        $("ul", box).append(row);
        $(".debug_ctr", box).attr("scrollTop", box.attr("scrollHeight") + 10000);
    }
}

var inLog = false;
log = function() {
    if (inLog) return;
    inLog = true;
    try {
        _log.apply(this, arguments);
    } catch(e) { }

    if (isLuaLogging()) {
        var args = $.makeArray(arguments);
        tbLuaLog(args);
    }
};
/* Perform logging, just not Lua-based */
nonLuaLog = function() {
    if (inLog) return;
    inLog = true;
    try {
        _log.apply(this, arguments);
    } catch(e) {}
    inLog = false;
}
/* WARNING: Renaming functions based on case is hazardous business */
Log = log;

isLogging = function() {
    return window.console.debug || useDebugConsole() || isLuaLogging();
};
})();

/*
 *  Copyright (C) 2007, TrustBearer Labs <http://trustbearer.com>
 *  All Rights Reserved
 *
 *    Title: tbUtil.js
 *  Authors: Justin Bastress <justin.bastress@trustbearer.com>, 
 *  Purpose: Crypto Utility functions for TBLive/TBLive.
 *
 */

/**
 * \file tblive-crypto.js
 * \brief Crypto Utility functions for TBLive/TBLive.
 * \addtogroup JavaScriptCrypto
 * @{
 */

(function() {
window.tbUtil = window.tbUtil || {};

//Loads the crypto DSM if it hasn't already been loaded.
tbUtil.loadedCrypto = false;
tbUtil.loadCrypto = function() 
{
	if(tbUtil.loadedCrypto)
		return;

	tbRetrieveDSM("Crypto.dsm");
	tbUtil.loadedCrypto = true;
};

/**
 * Allows public-key operations to be performed in javascript with an arbitrary DER certificate.
 *
 * @tparam String cert The DER-encoded certificate containing the public key
 * @tparam String data The data to perform the operation on
 * @tparam Object opts A hash table of additional parameters to pass dsm.RSACrypt.
 * @treturn String The result of the cryptographic operation.
 */
 
tbUtil.publicDecrypt = function(cert, data, opts) 
{
	tbUtil.loadCrypto();
	var key = tbCall("ParseCert", cert)[0].key;

//	key.d = key.e;
	var options = {mode: "decrypt", alg: "pkcs1", key: key, data: data};

	for(var k in opts)
		options[k] = opts[k];
	
	return tbCall("RSACrypt", options)[0];
};

/**
 * Allows public-key operations to be performed in javascript with an arbitrary DER certificate.
 *
 * @tparam String cert The DER-encoded certificate containing the public key
 * @tparam String data The data to perform the operation on
 * @tparam Object opts A hash table of additional parameters to pass dsm.RSACrypt.
 * @treturn String The result of the cryptographic operation.
 */
tbUtil.publicEncrypt = function(cert, data, opts) 
{
	tbUtil.loadCrypto();
	var key = tbCall("ParseCert", cert)[0].key;
	
//	key.d = key.e;
	var options = {mode: "encrypt", alg: "pkcs1", key: key, data: data};

	for(var k in opts)
		options[k] = opts[key];

	return tbCall("RSACrypt", options)[0];
};

/**
 * A front-end to the lua_dsm verify function.
 *
 * @tparam String cert The DER-encoded certificate to be verified
 * @tparam Array<String> cas An array of DER-encoded certificates that the certificate will be verified against
 * @treturn bool Returns true if the certificate can be verified, false otherwise.
 */
tbUtil.verifyCert = function(cert, cas) 
{
	try 
	{
		tbUtil.loadCrypto();
		return tbCall("VerifyCert", cert, cas)[0];
	}
	catch(e) 
	{
		// Failed verification in a 'bad way'... which is nearly always
		return false;
	}
};

/**
 * Calculates the SHA1 hash of data.
 *
 * @tparam String data The data to be digested
 * @treturn String The output of the digest operation (20 bytes)
 */
tbUtil.SHA1Hash = function(data)
{
	tbUtil.loadCrypto();
	return tbCall("SHA1Hash", data)[0];
}

/**
 * Parses a certificate
 *
 * @tparam String cert The DER-encoded X509 certificate to be parsed
 * @treturn Object A hash table containing fields for the various parsed certificate attributes
 */
tbUtil.parseCert = function(cert)
{
	tbUtil.loadCrypto();
	return tbCall("ParseCert", cert)[0];
}

/**
 * Returns the size of the public key in the provided cert
 *
 * @tparam String cert The certificate to be examined
 * @treturn int The size of the key (in bits)
 */

tbUtil.getKeySize = function(cert)
{
	return tbUtil.parseCert(cert).key.n.length * 8;
}

})();

/*
 *  Copyright (C) 2007, TrustBearer Labs <http://trustbearer.com>
 *  All Rights Reserved
 *
 *    Title: tbUtil.js
 *  Authors: Justin Bastress <justin.bastress@trustbearer.com>,
 *           Thomas Harning <thomas.harning@trustbearer.com>
 *  Purpose: Utility, wrapper & compatibility functions for TBLive/TBLive.
 *
 */

/**
 * \file tbUtil.js
 * \brief Utility, wrapper & compatibility functions for TBLive/TBLi
 * \addtogroup JavaScriptAPI
 * @{
 */

window.tbUtil = window.tbUtil || {};

// Internal -- JSONString -> Object
tbUtil.decodeResponse = function(resp)
{
    try {
        return eval('(' + resp + ')');
    } catch(e) {
        return { error: resp };
    }
};

// Internal -- JS Error -> TBError
tbUtil.simpleErrorToException = function(e) {
    return {
        component: e.component || "tbUtil.js",
        shortform: e.shortform || e.source || "TBErrorUnknown",
        longform: e.longform || e.faultString || e.message || (e && e + "") || "-Unknown-",
        codelocation: e.codelocation || (e.fileName && e.lineNumber && ('JS:' + e.fileName + ":" + e.lineNumber)),
        xmlrpc: true
    };
}

// Internal -- default exception handler.
tbUtil.defaultExceptionHandler = function(e, forcedComponent, tokenId)
{
    var tbErr = (e.xmlrpc && e) || tbUtil.simpleErrorToException(e);
    if(forcedComponent) tbErr.component = forcedComponent;
    tbError(tbErr, tokenId);
};

tbUtil.exceptionHandler = tbUtil.defaultExceptionHandler;

/**
 * Call chaining utility, useful for callback systems and async Pass it an
 * array of functions and an initial data item.  It will call each function
 * with the parameters (next, data) where next is a function to call the next
 * item, or an arbitrary item in the list.  'next' also offers a wrapJump
 * property/function to create a new 'next' function that will act just like
 * next, just jumping to a next location.  Negative jumps jump from the last
 * element in the list.  Ex: -1 is the last function State will be provided to
 * every function as the last argument (3rd argument if no 'extraArgs' given)
 * This is to help w/ stateful chains while avoiding too many closures being
 * generated
 *
 * \tparam Array calls List of functions to call in order.
 * \param data Arbitrary data that will be passed as an argument to first function.
 * \param state Arbitrary data that will be passed as an argument to all functions.
 */

tbUtil.ChainCall = function(calls, data, state) {
    var callIndex = 0;
    function next(data, jump, absolute, extraArgs) {
        if(jump !== null && jump !== undefined) {
            if(absolute) {
                callIndex = jump;
            } else {
                if(callIndex < 0) {
                    callIndex += calls.length;
                } else {
                    callIndex+= jump;
                }
            }
        } else {
            callIndex++;
        }
        var nextCall = calls[callIndex];
        if(nextCall) {
            if(extraArgs) {
                if(extraArgs.constructor == Array) {
                    extraArgs.unshift(data);
                    extraArgs.unshift(next);
                } else {
                    extraArgs = [next, data, extraArgs];
                }
                if(state)
                    extraArgs.push(state);
                return nextCall.apply(this, extraArgs);
            }
            return nextCall(next, data, state);
        }
    }
    next.wrapJump = function(jump, absolute, extraArgs) {
        return function(data) {
            return next(data, jump, absolute, extraArgs);
        };
    };
    next(data, 0);
};

(function() {
/**
 * Retrieves publicly available TBAccess information linked to certificate.
 * @tparam String cert The DER-encoded certificate to check
 * @tparam String app The name of the application to pull data from
 * @tparam Array<String> items Optional. A list of key-names to pull. If not present, all items are returned.
 * @tparam Function callback Optional. If set, this is called with the results when the process is complete. Otherwise, this is a synchronous action that returns the results.
 * @treturn Object A key-value collection of values linked to the certificate (only if callback is not set)
 */
tbUtil.getCertInfo = function(cert, app, items, callback) {
    if(!callback && typeof(items) != "function") {
        return tbUtil.jsonRpcCall("Access.getPublicUserData", [
            app,
            new Base64String(cert, true),
            typeof(items) == "string" ? items : null
        ], false, true).result;
    } else {
        tbUtil.jsonRpcCall("Access.getPublicUserData", [
            app,
            new Base64String(cert, true),
            typeof(items) == "string" ? items : null
        ], function(ret) {
            if(!callback && typeof(items) == "function") {
                items(ret.result);
            } else if(callback && typeof(callback) == "function") {
                callback(ret.result || ret);
            }
        });
    }
}

})();

//Contains a current list of all readers with cards in them.
tbUtil.cardTokens = undefined;

//Contains the serial numbers of connected cards, indexed by the reader's name.
//Contains the list of tokens from the previous "Tick" of cardWatcher.
//If this has changed, then a reader was added/removed.
tbUtil.oldTokens = undefined;

//Id returned by setInterval.
tbUtil.cardWatcherInterval = undefined;

/**
 * Purpose:        Notify the application of all card insertion/removal
 *              events, and keep a list of connected tokens.
 *
 * Input:       Only called internally.
 *
 * Output:      Only called internally.
 *
 * Usage:       Only called internally.
 *
 * Exceptions:  Only called Internally.
 */
tbUtil.cardWatcherTick = function()
{
    var tokens;
    try
    {
        tokens = tbCall("TokenList");
        if(!tokens || tokens.length == 0) {
            tokens = [];
        } else {
            tokens = tokens[0] || [];
        }
    } catch(e) {
        if(e.shortform == "0x8010002E") {
            /* No readers connected */
            tokens = [];
        } else if(tbUtil.cardWatcherError && tbUtil.cardWatcherError(e, t) === false) {
            /* Card watcher client gets first taste of the error w/ opt to bail */
            return;
        } else if(e.shortform == 'TBErrorNoPlugin') { /* No plugin installed... should stop */
            tbUtil.stopCardWatcher();
            tbUtil.noPlugin = true;
            return tbError(e);
        } else {
            throw(e);
        }
    }

    if(tbUtil.oldTokens.length != tokens.length) {
        //Run through with the old list to weed out disconnected readers.
        var temp = tokens;
        tokens = tbUtil.oldTokens;
        tbUtil.oldTokens = temp;
    }
    function testToken(t)
    {
        try {
            tbCall("TokenStatus", t);
        } catch(e) {
            try {
                tbCall("TokenDisconnect", t);

                for(var i = 0; i < tbUtil.cardTokens.length; i++) {
                    if(tbUtil.cardTokens[i] == t) {
                        /* Card was connected */
                        tbUtil.cardTokens.splice(i, 1);
                        tbUtil.cardDisconnected(t);
                    }

                }
            } catch(e) {
                /* Card wasn't connected */
            }
            try {
                tbCall("TokenConnect", t);
                /* New connection */
                tbUtil.cardTokens[tbUtil.cardTokens.length] = t;
                tbUtil.cardConnected(t);
            } catch(e) {
                if(e.shortform == "0x8010000C") {
                    /* No card  -- do nothing */
                } else {
                    /* An error occurred -- report it */
                    if(tbUtil.cardWatcherError)
                        tbUtil.cardWatcherError(e, t);
                }
            }
        }
    }
    for(var i = 0; i < tokens.length; i++) {
        testToken(tokens[i]);
    }
};

// Wraps read/list object functions and caches the results by card serial number.
var cardWrapper;

tbUtil.cardWrapper = {
    certs: {},

    connect: function(token)
    {
        cardWrapper.lastError = null;
        var connected = true;
        try {
            tbCall("TokenStatus", token);
        } catch(e) {
            connected = false;
        }
        if(!connected) {
            try {
                tbCall("TokenDisconnect", token);
            } catch(e) {
            }
            try {
                tbCall("TokenConnect", token);
                cardWrapper.getSerial(token);
            } catch(e) {
                cardWrapper.lastError = e;
                return false;
            }
        }
        return true;
    },

    connected: function(token)
    {
        cardWrapper.lastError = null;
        try {
            tbCall("TokenStatus", token);
            return true;
        } catch(e) {
            cardWrapper.lastError = e;
            return false;
        }
    },

    getSerial: function(token)
    {
        try {
            return tbCall("TokenGetInfo", token, "serial")[0];
        } catch(e) {
            cardWrapper.lastError = e;
        }
    },

    //Clears the cache of 'id' if provided, or the entire cache if none is provided.
    invalidate: function(token, id)
    {
        if(!cardWrapper.connect(token))
            return false;
        var serial = cardWrapper.getSerial(token);
        if(!serial) return false;

        if(!id)
            delete cardWrapper.certs[serial];
        else if(cardWrapper.certs[serial])
            delete cardWrapper.certs[serial][id];

        delete cardWrapper.objects[serial];
    },

    listObjects: function(token)
    {
        if(!cardWrapper.connect(token))
            return false;

        var serial = cardWrapper.getSerial(token);
        var canCache = cardWrapper.canCache(token, serial);

        if(canCache && cardWrapper.objects[serial])
            return cardWrapper.objects[serial];
        var objects;
        try {
            objects = tbCall("TokenListObjects", token, "certificate")[0];
        } catch(e) {
            cardWrapper.lastError = e;
            return false;
        }

        if(!canCache)
            return objects;
        for(var i = 0; i < objects.length; i++) {
            var obj = objects[i];
            if(obj.value) {
                if(!cardWrapper.certs[serial])
                    cardWrapper.certs[serial] = {};

                cardWrapper.certs[serial][obj.id] = obj.value;
            }
        }
        cardWrapper.objects[serial] = objects;
        return objects;
    },

    objects: {},

    readObject: function(token, id, cacheOnly)
    {
        if(!cardWrapper.connect(token))
            return false;

        var serial = cardWrapper.getSerial(token);
        var canCache = cardWrapper.canCache(token, serial);

        if(canCache && cardWrapper.certs[serial] && cardWrapper.certs[serial][id])
            return cardWrapper.certs[serial][id];

        if(cacheOnly)
            return false;

        if(canCache && !cardWrapper.certs[serial]) cardWrapper.certs[serial] = {};

        var data;

        try {
            data = tbCall("TokenReadData", token, id)[0];
        } catch(e) {
            cardWrapper.lastError = e;
            return false;
        }

        if(data && data.value) data = data.value;
        if(canCache) cardWrapper.certs[serial][id] = data;
        return data;
    },

    canCache: function(token, providedSerial)
    {
        if(!token) return false;
        var serial = providedSerial || getSerial(token);
        if(!serial) return false;
        /* Handle plusID's non-serial mode
         * CAC's lack of serial
         * Muscle's unknown type and CPLC missing
         */
        if(serial.match(/^(Unknown card type)$/i))
            return false;
        return true;
    },

    tokenListBox: function(callback, id, cardWatcherFriendly)
    {
        cardWrapper.tokenChanged = function(test, box)
        {
            cardWrapper.lastError = null;
            box = box || $(".tbTokenListDiv");

            var msg, tokens;
            try {
                if(cardWatcherFriendly)
                    tokens = tbUtil.cardTokens || [];
                else
                    tokens = tbCall("TokenList")[0];
            } catch(e) {
                cardWrapper.lastError = e;
                if(e.shortform == "TBErrorNoPlugin")
                    msg = "<a href = '" + webCoreRoot + "/install.html'>Install TrustBearer Live</a>";
                tokens = [];
            }

            if(!tokens || tokens.length == 0) {
                if(!msg)
                    msg = "No device found</font>";
                $(box).find(".tbTokenListBox").hide();
                $(box).find(".tbTokenMsgBox").html(msg).show();
                return false;
            }

            $(box).find(".tbTokenListBox").show();
            $(box).find(".tbTokenMsgBox").hide();

            var selectBox = $(box).find(".tbTokenListBox")[0];
            var selectedIndex = selectBox.selectedIndex;

            var selectedId;

            if(tokens.length != selectBox.length && cardWatcherFriendly) {
                selectedId = tokens[tokens.length - 1];
            } else {
                selectedId = selectBox.length > 0 && selectedIndex > -1 ? selectBox.options[selectedIndex].value : tokens[0];
            }

            var newSelected = -1;
            $(selectBox).empty();

            for(var i = 0 ; i < tokens.length; i++) {
                if(tokens[i] == selectedId)
                    newSelected = i;
                $(selectBox).append("<option value = '" + tokens[i] + "'>" + tokens[i] + "</option>");
            }

            if(newSelected == -1)
                newSelected = 0;
            selectBox.selectedIndex = newSelected;

            if(arguments.length <= 1 && callback)
                callback();
            return false;
        };

        var box = $.create('div', {'class': 'tbTokenListDiv'}, [
            'br', [],
            'select', {'class': 'tbTokenListBox', id: id}, {change: cardWrapper.tokenChanged},
            'br', [],
            'button', {}, {click: cardWrapper.tokenChanged}, [
                'img', {border: 0, src:webAssetRoot + '/images/refresh.gif'},
                'Refresh'
            ],
            'div', {'class':'tbTokenMsgBox'}
        ]);

        // Need to delay this update since the items wont be on the dom yet...
        setTimeout(function() {
            cardWrapper.tokenChanged(callback, box);
        }, 10);
        return box;
    }
};

cardWrapper = tbUtil.cardWrapper;

var cardWatcherInterval = 1000;

/**
 * Calls cardWatcherTick on an interval, and sets the parameters as the default
 * callbacks for card insertion/removal events.
 *
 * \tparam Function connected A function with one parameter.  On card
 * insertion, it is invoked with the name of the affected reader.
 *
 * \tparam Function disconnected A function with one parameter.  On remove, it
 * is invoked with the name of the affected reader.
 */
tbUtil.startCardWatcher = function(connected, disconnected, error)
{
    tbUtil.cardConnected = connected;
    tbUtil.cardDisconnected = disconnected;
    tbUtil.cardWatcherError = error;

    if(tbUtil.cardWatcherInterval) {
        for(var i = 0; i < tbUtil.cardTokens.length; i++) {
            connected(tbUtil.cardTokens[i]);
        }
        return;
    }
    tbUtil.oldTokens = [];
    tbUtil.cardTokens = [];

    tbUtil.cardWatcherInterval = setInterval(tbUtil.cardWatcherTick, cardWatcherInterval);
    /* Trigger first hit immediately */
    tbUtil.cardWatcherTick();
};

//Turns off the card watcher.
tbUtil.stopCardWatcher = function()
{
    clearInterval(tbUtil.cardWatcherInterval);
    tbUtil.oldTokens = [];
    if(tbUtil.cardTokens) {
        $.each(tbUtil.cardTokens, function() {
            try {
                /* If one fails, keep going... */
                tbCall("TokenDisconnect", this);
            } catch(e) {
            }
        });
    }
    delete tbUtil.cardWatcherInterval;
};


/**
 * When passed a Lua-generated tberror, this returns an object containing its file, line, and text.
 * @tparam Object error A Lua-generated error
 * @treturn Object An object containing "text", and possibly "file" & "line" fields.
 */
tbUtil.parseDSMError = function(error)
{
    var longform;
    if(typeof(error) == "string") {
        longform = error;
    } else if(error.longform) {
        longform = error.longform;
    } else if(error.error && error.error.longform) {
        longform = error.error.longform;
    } else if(error.message) { // Java daemon error message
        longform = error.message;
    } else {
        return { text: "Unknown error format", error: error };
    }

    var errorText = longform.match(/([a-zA-Z0-9_\\-]+)[.][dl][su][ma]:([0-9]+): (.*)/);

    if(!errorText) {
        return { text: longform };
    } else {
        return { file: errorText[1], line: errorText[2], text: errorText[3] };
    }
};

/**
 * Synchronously fetches url and returns its text.
 * @tparam String url The URL to fetch
 * @treturn String The content at the URL.
 */
tbUtil.get = function(url)
{
    $.ajaxSetup({async: false});
    var xml = $.get(url);
    $.ajaxSetup({async: true});
    return xml.responseText;
};

/**
 * Sets a cookie for the current site that expires in one year.
 * @tparam String name The name of the cookie
 * @tparam String value The value of the cookie
 */
tbUtil.setCookie = function(name, value, expire, path)
{
    path = path || "/";
    if(!expire) {
        expire = new Date();
        /* 1 yr ahead */
        expire.setTime(expire.getTime() + 365 * 24 * 60 * 60 * 1000);
    }
    name = encodeURIComponent(name);
    expire = expire.toGMTString();
    value = encodeURIComponent(value);
    path = encodeURI(path);
    var toSet = name + "=" + value + "; expires=" + expire + "; path=" + path;
    document.cookie = toSet;
};

/**
 * Deletes a cookie from the current site
 * @tparam String name The name of the cookie
 */
tbUtil.eraseCookie = function(name, path)
{
    var expire = new Date();
    /* 1 yr behind */
    expire.setTime(expire.getTime() - 365 * 24 * 60 * 60 * 1000);
    tbUtil.setCookie(name, "", expire, path);
};

/**
 * Gets a cookie from the current site
 * @tparam String name The name of the cookie
 * @treturn String The value of the cookie
 */
tbUtil.getCookie = function(name)
{
    name = $.trim(name);
    var cookies = document.cookie.split(";");
    for(var i = 0; i < cookies.length; i++) {
        var parts = cookies[i].split("=");
        if($.trim(parts[0]) == name) {
            return decodeURIComponent(parts[1]);
        }
    }
};

/* Fix to handle data that is meant to be loadable via IFrame post and normal AJAX posts.. */
(function($) {
    var oldHttpData = $.httpData;
    $.httpData = function(r, type) {
        if(type != 'formjson') {
            return oldHttpData(r, type);
        }
        var data;
        if(r.responseXML) {
            data = r.responseXML.getElementsByTagName('textarea')[0];
        }
        if(!data) {
            data = $("<div>").html(r.responseText).find('textarea')[0];
        }
        data = data && data.value || r.responseText;
        return eval('(' + data + ')');
    }
})(jQuery);


/**
 * Turns an array-like object (for instance, the arguments object) into an actual array.
 * @tparam Object arrayLike The array-like object to convert into an array.
 * @treturn Array The resultant array.
 */
function toArray(arrayLike)
{
    return $.makeArray(arrayLike);
    //return Array.prototype.slice.call(arrayLike);
}

var applyCSSCache = {};

function cacheCSS(cssFile, callback)
{
    $.get(cssFile, function(ret) {
        /* kill comments */
        applyCSSCache[cssFile] = ret = ret.replace(/\/\*[^*]+\*\//g, "");
        if(callback) callback(ret);
    });
}

/**
 * Downloads the specified CSS file and applies it to the provided DOM using jQuery.
 * @tparam String cssFile The CSS file to download / apply.
 * @tparam DOM dom The DOM to apply the style to; defaults to $("body").
 * @tparam Function callback Optional. The function to call when done.
 */
function applyCSS(cssFile, dom, callback, data)
{
    callback = callback || function() { }
    dom = dom || $("body");

    if(!data && !(data = applyCSSCache[cssFile])) {
        return cacheCSS(cssFile, function(ret) {
            try {
                applyCSS(cssFile, dom, callback, ret);
            } catch(e) {
                callback(false, "Unknown", e);
            }
        });
    }

    var selector, body;

    /* [\s\S] is used because . does not match linebreaks. */

    function fetch()
    {
        var matcher = /^([^{]+)\{([^}]+)\}([\s\S]*)$/;
        var match = matcher.exec(data);
        if(!match) {
            selector = null; body = null; data = null; return;
        }
        selector = $.trim(match[1]);
        body = $.trim(match[2]);
        data = $.trim(match[3]);
    }

    var name, value;

    function getProps() {
        var matcher = /^([^:]+):([^;]+);([\s\S]*)$/;
        var match = matcher.exec(body);
        if(!match) {
            name = null; value = null; body = null; return;
        }
        name = $.trim(match[1]);
        value = $.trim(match[2]);
        body = $.trim(match[3]);
    }

    while(true) {
        fetch();
        if(!selector) {
            break;
        }
        while(true) {
            getProps();
            if(!name) {
                break;
            }
            try {
                $(selector, dom).css(name, value);
            } catch(e) {
                callback(false, "Unknown", e);
            }
        };
    };
    callback(true);
}



/* Make the $.ajax call handle some MS specific errors */
(function($) {
	var oldAjax = $.ajax;
	$.ajax = function(s) {
		var oldErrorHandle = s.error;
		s.error = function(xmlhttp) {
			switch(xmlhttp.status) {
			case 12002: // IE Timeout
			case 12029: // Dropped connections
			case 12030: // ""
			case 12031: // ""
			case 12152: // Connection closed by server
			case 13030: // Status+Text not available???? Try again
				return oldAjax(s);
			default:
				if(oldErrorHandle)
					return oldErrorHandle.apply(this, arguments);
			}
		};
		return oldAjax(s);
	};
	$.tbGet = function(url, data) {
		$.ajaxSetup({async: false});
		var xmlhttp = $.get(url, data);
		$.ajaxSetup({async: true});
		return xmlhttp;
	};
	$.tbPost = function(url, data) {
		$.ajaxSetup({async: false});
		var xmlhttp = $.post(url, data);
		$.ajaxSetup({async: true});
		return xmlhttp;
	};
	//$.tbPost = function(url, data) { return $.syncPost(url, data, 'xmlhttp'); }
	/* Utility to asynchronously post... */
	$.syncPost = function(url, data, type) {
		var response;
		var xmlhttp = $.ajax({
			type: "POST",
			url: url,
			data: data,
			error: function() {
				xmlhttp = this;
			},
			success: function(data) {
				response = data;
				xmlhttp = this; /* Since the new $.ajax w/ error fixing, the return value isn't valid after a retry */
			},
			dataType: type,
			async: false
		});
		if(type == "xmlhttp") /* Return the raw response object */
			return xmlhttp;
		return response;
	};
})(jQuery);


(function() {
    var plat, arch, agent;
    /**
     * Returns the current client operating system.
     *
     * \return One of WIN, LINUX, MAC, SOLARIS, UNIX, or OTHER.
     */
    function getPlatform() {
        if(plat) return plat;
        var platform = navigator.platform;
        if(platform.match(/win/i))
            plat = 'WIN';
        else if(platform.match(/linux/i))
            plat = 'LINUX';
        else if(platform.match(/mac/i))
            plat = 'MAC';
        else if(platform.match(/solaris/i))
            plat = 'SOLARIS';
        else if(platform.match(/unix/i))
            plat = 'UNIX';
        else
            plat = 'OTHER';
        return plat;
    }

    function decodeArch(platform) {
        if(platform.match(/ppc/i))
            arch = 'PPC';
        else if(platform.match(/intel|i.86|WOW64/i))
            arch = 'X86';
        else if(platform.match(/x86_64|x64/i))
            arch = 'X86_64';
        return arch;
    }

    /**
     * Returns client machine architecture.
     *
     * \return One of PPC, X86, X86_64, or OTHER.
     */
    function getArch() {
        if(arch) return arch;
        if (null != navigator.cpuClass) {
            if ("x64" == navigator.cpuClass)
                arch = 'X86_64';
            else if("x86" == navigator.cpuClass)
                arch = 'X86';
        }
        if (!arch && null != navigator.platform) {
            if (navigator.platform.match(/win32/i))
                arch = 'X86';
            else if(navigator.platform.match(/win64/i))
                arch = 'X86_64';
        }
        if (!arch)
            arch = decodeArch(navigator.platform) || decodeArch(navigator.userAgent);
        if (!arch && getPlatform() == 'WIN')
            arch = 'X86';
        if (!arch)
            arch = 'OTHER';
        return arch;
    }

    /**
     * Returns the type of client browser.
     *
     * \return One of OPERA, IE, SAFARI, MOZILLA, or OTHER.
     */
    function getBrowserAgent() {
        if(agent) return agent;
        var useragent = navigator.userAgent;
        if(useragent.match(/Chrome|Chromium/))
            agent = 'CHROME'
        else if(useragent.match(/Opera/))
            agent = 'OPERA';
        else if(useragent.match(/MSIE/i))
            agent = 'IE';
        else if(useragent.match(/Webkit/i))
            agent = 'SAFARI';
        else if(useragent.match(/Mozilla/i))
            agent = 'MOZILLA';
        else
            agent = 'OTHER';
        return agent;
    }

    /**
     * Returns an object containing information about the client's configuration.
     *
     * \return Object with the following fields:
     *  * browser: browser vendor
     *  * arch: machine architecture
     *  * os: operating system
     */
	xmlrpc.getPlatformInfo = function() {
		return {
			browser: getBrowserAgent(),
			arch: getArch(),
			os: getPlatform()
		}
	}
	var winRegex = /(.*)\s+([0-9]+)$/;
	var nixRegex = /(.*)\s+([0-9]+\s+[0-9]+)$/;
	function trimParen(val) {
		if(!val.match(/\(.+\)/))
			return val;
		var ret = /(.*?)\s*\([^)]*\)\s*(.*)/.exec(val);
		return trimParen(ret[1]) + trimParen(ret[2]);
	}
	function decodeTokenValue(token) {
		if(getPlatform() == 'WIN')
			ret = winRegex.exec(token) || [null,null,null];
		else {
			/* REMOVE potential serial included in name from nix value */
			ret = nixRegex.exec(token) || [null,null,null];
			ret[1] = trimParen(ret[1]);
		}
		ret.shift();
		return ret;
	}

    /**
     * Creates an error object from an error message which occurs deep in DSM land.
     * Adds client information and packages it into an object which can be sent as JSON to the server.
     *
     * \param msg The actual text of the message.
     * \param token Trusted device being used at the time of the error.
     * \param uniqueValue Some unique identifier for that message.
     * \param errorObject Value generated by the DPM, usually includes fields for short form, long form, component, line number, etc.
     *
     * \return A JavaScript object that can be sent as JSON to the daemon to look up an appropriate message to give to the user.
     */
	xmlrpc.tbDecodeMessage = function(msg, token, uniqueValue, errorObject) {
		var match, obj = {};
		msg = msg && decodeEntities(msg) || "";
		if(errorObject && errorObject.shortform) {
			obj.shortform = errorObject.shortform;
		} else {
			match = msg.match(/<ShortForm>(.*?)<\/ShortForm>/);
			obj.shortform = match && match[1];
		}
		if(errorObject && errorObject.longform) {
			obj.longform = errorObject.longform;
		} else {
			match = msg.match(/<LongForm>(.*?)<\/LongForm>/);
			obj.longform = match && match[1];
		}
		if(errorObject && errorObject.component) {
			obj.component = errorObject.component;
		} else {
			match = msg.match(/<Component>(.*?)<\/Component>/);
			obj.component = match && match[1];
		}
		obj.shortform = obj.shortform || 'TBErrorUnknown';
		if(errorObject && errorObject.codelocation) {
			obj.codeLocation = errorObject.codelocation;
		} else {
			match = msg.match(/<code>(.*?)<\/code>/);
			obj.codeLocation = match && match[1];
		}
		if(!obj.longform && !obj.component) {
			obj.longform = msg;
			obj.component = "<NULL>";
			obj.componentVersion = '(OLD_MUSTFIX)';
		}
		if(token) {
			var ret = decodeTokenValue(token);
			obj.token = ret[0];
			obj.tokenNumber = ret[1];
		}
		obj.fullAgent = navigator.userAgent;
		obj.agent = getBrowserAgent();
		obj.architecture = getArch();
		obj.platform = getPlatform();
		var plugin = getTBLive();
		obj.pluginVersion = (plugin && plugin.version || "missing");
		obj.language = navigator.language || navigator.userLanguage || "en-us";
		if(uniqueValue)
			obj.uniqueValue = new Base64String(uniqueValue, true);
		return obj;
	}
	var entityItems = /[<>&]/g;
	var encodedEntityItems = /&lt;|&gt;|&amp;/g;
	var decodeMap = {
		'&lt;': '<',
		'&gt;':'>',
		'&amp;':'&'
	};
	function decodeEntities(str) {
		return str.replace(encodedEntityItems, function(x) { return decodeMap(x); });
	}
})();

/* STYLESHEETS NEEDED GO INTO the makefile...
they are concatenated just like the JS
*/
document.write("<link rel='stylesheet' type='text/css' href='" + webAssetRoot + "/css/tblive-core.css' />");

/**
 * \file Shorthand.js
 * \brief A library that shortens calls to DPM and DSM functions.
 * \addtogroup JavaScriptAPI
 * @{
 */

(function() {

var numericTypes = [
    "byte",
    "short",
    "int",
    "long",
    "float",
    "double",
    "char"
];

var numericTypeMap = {};

var n = numericTypes.length;

for(var i = 0; i < n; i++) {
    var t = numericTypes[i];
    var _t = "java.lang." + t.charAt(0).toUpperCase() + t.substr(1);
    numericTypeMap[t] = true;
    numericTypeMap[_t] = true;
}

function isNumericType(typeName)
{
    return numericTypeMap[typeName];
}

/**
 * Returns a JavaScript object that can be used to call remote functions from the requested DPM.
 * For example:
 *   var dpm = loadDPM("myDPM");
 *   var result = dpm.someDPMFunction(arg1, arg2);
 * would be equivalent to
 *   var result = tbUtil.jsonRpcCall("myDPM.someDPMFunction", [arg1, arg2]).result;
 * @tparam String dpmName The name of the DPM (its class name would be tblive.plugins.dpmName.dpmName);
 * @treturn Object An object that can be used to call remote functions.
 */
function loadDPM(dpm)
{
    var dpmName = dpm.match("[^.]+$")[0];
    var DPM = {};
    var hasTBLive = dpm.match(/^tblive\..*/);
    var hasCOM    = dpm.match(/^com\.trustbearer\..*/);
    //If we don't have either com or tblive then we fallback to the old way fo rnow
    if (!hasTBLive && !hasCOM) {
        dpm = "tblive.plugins." + dpm;
    }
    var ret = tbUtil.jsonRpcCall("DPMLoader.loadDPM", [dpm], null, true);
    window.items = ret.result;
    $(ret.result).each(function(){
        var item = this;
        var name = item.name;
        DPM[name] = function() {
            var fullName = dpm + "." + name;
            log("Calling DPM function ", fullName, "...");
            var args = toArray(arguments);
            for(var i = 0; i < item.params.length; i++) {
                var param = item.params[i];
                var arg = args[i];
                if(isNumericType(param)) {
                    if(isNaN(arg)) {
                        log("Parameter #", i, " of ", fullName, " must be a number;");
                        log(arg);
                        throw new Error("Parameter #", i, " of ", fullName, " must be a number.");
                    }
                } else if(param.name == "[B" && !(args[i].value && args[i].toJSON)) {
                    log("Found byte[] arg for arg #", i, "; converting.");
                    args[i] = new Base64String(args[i], true);
                } else if(item.params[i].annotations.BinaryField) {
                    var field = item.params[i].annotations.BinaryField;
                    log("Found BinaryField annotation for arg #", i, ", searching for field '", field, "'...");
                    for(var k in args[i]) {
                        if(k == field) {
                            log("Found '", field, "', base64-encoding.");
                            args[i][k] = new Base64String(args[i][k], true);
                            break;
                        }
                    }
                }
            }
            var callback = args.pop();
            if(!(Object(callback) instanceof Function)) { args.push(callback); callback = false; }
            if(callback) {
                var cb = callback;
                callback = function(ret) {
                    if(ret && ret.result) {
                        return cb(true, ret.result);
                    } else {
                        return cb(false, ret && ret.error, ret);
                    }
                }
            }
            var _args = [dpmName + "." + name, args, callback, callback == false];
            log("Invoking tbUtil.jsonRpcCall(", _args, ")");
            var ret = tbUtil.jsonRpcCall.apply(window, _args);
            log("Returned ", (ret || "nothing"));
            if(callback) return;
            if(ret && ret.result || ret.result === false)
                return ret.result;
            if(ret && ret.error) {
                throw ret && ret.error || ret;
            }
        }
    });
    return DPM;
}

var defaults = {
    onerror: function(e) {
        throw e;
    }
    //onerror: tbError
}

function camelCase(name)
{
    return name.substring(0,1).toLowerCase() + name.substring(1);
}

function noToken(name)
{
    return name.replace(/^Token/, "");
}

function loadDSM(name, id)
{
    if(!id) {
        id = name;
        name = id + ".dsm";
    }
    tbRetrieveDSM(name, null, "", id);
    return loadPlugin(id);
}

function loadPlugin(token, options)
{
    if(typeof(token) != "string") {
        options = token;
        token = false;
    }

    var ret = {};
    options = options || defaults;
    var tb = getTBLive();
    if(!tb || !tb.version) return ret;

    for(var k in options) {
        ret[k] = options[k];
    }

    safecalls = tbCall("GetSafecalls", token)[0];

    $(safecalls).each(function(){
        var item = this.toString();
        ret[item] = function() {
            var args = Array.prototype.slice.apply(arguments);
            if(token) args.unshift(token); /* if present, tokenId will be arg #2 */
            args.unshift(item);            /* function name will always be arg #1 */

            log("Invoking tbCall(", args, ")");

            try {
                var rv = tbCall.apply(window, args)[0];
                log("Returned ", (rv || "nothing"));
                return rv;
            } catch(e) {
                log("An error occurred while calling ", item, ":");
                log(e);
                if(ret.onerror) {
                    ret.onerror(e);
                } else {
                    throw e;
                }
            }
        }
        ret[camelCase(item)] = ret[item];
        ret[noToken(item)] = ret[item];
        ret[camelCase(noToken(item))] = ret[item];
    });
    if(!token) {
        ret.connect = function(tokenId) {
            try {
                tbCall("TokenDisconnect", tokenId);
            } catch(e) { }
            ret.TokenConnect(tokenId);
            return loadPlugin(tokenId, options);
        }
    } else {
        ret.disconnect = ret.Disconnect = ret.TokenDisconnect = function() {
            tbCall("TokenDisconnect", ret.tokenId);
        }
        ret.tokenId = token;
    }
    return ret;
}

window.loadPlugin = loadPlugin;
window.loadDPM = loadDPM;
window.loadDSM = loadDSM;
window.__log = log;

function getToken(callback)
{
    tbUtil.getToken(function(ret) {
        if(ret instanceof Array) {
            callback(false, ret[1]);
        } else {
            callback(ret);
        }
    });
}

})();

if(window.tb) {
    setTimeout(function() {
        throw new Error("Warning: name conflict [windows.tb])");
    }, 1000);
}

/* Common first-usage commands trigger initialization. */
var tb = {
    init: function() {
        if(!getTBLive()) throw new Error("TBLive plugin not loaded.");
        tb = loadPlugin();
    }
};

$(function(){
    if(window.TBLIVE_AutoInitShorthand) {
        var func = arguments.callee;
        if(!getTBLive()) {
            return setTimeout(function() {
                func();
            }, 100);
        }
        tb.init();
    }
});


function guardCallback(callback) {
    if(!callback) {
        callback = function(success, ret, err) {
            if(success) return;
            if(ret && ret.toLowerCase & ret.toLowerCase() == "cancelled") {
                return;
            }
            return tbError(err);
        }
    }
    return function() {
        try {
            callback.apply(this, arguments);
        } catch(e) {
        }
    }
}

(function() {
/**
 * Given a table (substTable) of the form {strKey: strValue}, replaces every instance of $(strKey) in srcString with strValue.
 * @tparam String srcString The string to be formatted
 * @tparam Object substTable A table containing the key/value pairs to be substituted.
 * @treturn String The substituted string.
 */
function stringTableReplacements(srcString, substTable)
{
    for (var elt in substTable) {
        var last = srcString;

        while ((srcString = srcString.replace("$("+elt+")", substTable[elt])) != last) {
            last = srcString;
        }
    }
    return srcString;
}

function evaluate(tbl, token, root)
{
    root = root || tbl;
    for (var k in tbl) {
        var v = tbl[k];
        if (typeof(v) == "object") {
            if (v.javaScriptFunction) {
                try {
                    var func = new Function("token", v.javaScriptFunction);
                    tbl[k] = func.apply(root, [token]);
                } catch(e) {
                    log("An error occurred while evaluating form item '" + k + "':");
                    log(e);
                    throw e;
                }
            } else {
                evaluate(tbl[k], token, root);
            }
        }
    }
}

/**
 * Provides a generic interface for using form input as arguments to a DSM function.
 * @tparam String func The name of the DSM function
 * @tparam Object funcArgs An array of the arguments to be passed to the DSM function.
 * @tparam Function callback The function to be called when the process completes. It should be of the form:
 *         function(boolSuccess, varRet, tbErr)
 *         where ...
 *            boolSuccess indicates whether the call succeeded or failed
 *            varRet is the the return value of the DSM function if it succeeded, or the shortform of the error if it did not.
 *            tbErr will be an exception if one occurred.
 * @treturn divID The numeric ID returned by tbShowDiv().
 */
function tbFormCall(func, token, funcArgs, callback) {
    var __call = "tbFormCall(" + toArray(arguments).map($.toJSON).join(",") + ")";
    var id;

    if (!token || !token.tokenId) {
        token = loadPlugin(token);
    }
    var defaultType = "input";

    var fieldDefaults = {
        inputType: "text",
        "default": ""
    };
    var start = new Date();
    var types = {
        header: function(item) {
            alert(__call+": using header="+toArray(arguments).map($.toJSON).join(","));
            throw new Error(__call+": using header="+toArray(arguments).map($.toJSON).join(","));
        },
        input: "<tr><td>$(label)</td><td><input type='$(inputType)' name='$(name)' value='$(default)'></td></tr>",
        button: "<tr><td colspan='2'><input type='button' name='$(name)' value='$(label)'></td></tr>",
        select: function(item) {
            var options = [];
            for (var i = 0; i < item.options.length; i++) {
                var opt = item.options[i];

                if (Object(opt) instanceof String) {
                    opt = { label: opt, value: opt };
                }

                var str = "<option value='$(value)'>$(label)</option>";
                if (item["default"] == opt.value) {
                    str = "<option value='$(value)' selected>$(label)</option>"
                }

                options.push(stringTableReplacements(str, opt));
            }
            return "<tr><td>$(label)</td><td><select name='$(name)'>" + options.join("") + "</select></td></tr>";
        },
        image: "<tr><td colspan='2'><img src='$(src)'/></td></tr>",
        checkbox: "<tr><td colspan='2'><input type='checkbox' name='$(name)' /><span>$(label)</span></td></tr>"
    };

    var form = $(
        "<div class='tbDlgWrap'>" +
            "<img src='" + dialogHeader + "'/>" +
            "<div class='tbDlgStatus'>&nbsp;</div>" +
            "<div class='tbDlg'></div>" +
            "<div class='tbDlgButtons'></div>" +
            "<div class='tbDlgChangeToken'></div>" +
        "</div>"
    );

    var container = $(".tbDlg", form);

    var ui;

    if (Object(func) instanceof String) {
        funcArgs.unshift(token.tokenId);           /* token will be the second parameter */
        funcArgs.unshift(func + "_tbUIForm");      /* the function name will be the first */
        ui = tbCall.apply(this, funcArgs)[0];
    } else if (Object(func) instanceof Function) {
        ui = func.apply(this, funcArgs);
    } else {
        throw new Exception("Error; argument 'func' not correct type.");
    }

    var okButton, cancelButton;

    if (Object(ui.elements) instanceof String) {
        container.html(ui.elements);
        okButton = $(".okButton", container);
        cancelButton = $(".cancelButton", container);
    } else {
        var tbl = $("<table></table>");
        container.prepend(tbl);
        var tbody = $("<tbody></tbody>");
        tbl.append(tbody);

        evaluate(ui.elements);

        for (var i = 0; i < ui.elements.length; i++) {
            var field = ui.elements[i];
            if (!field) {
                log("Field is null?");
                continue; /* error? */
            }

            if (Object(field) instanceof String) {
                var func = new Function("token", field);
                field = func.apply(ui, [token]);
            }

            var uiItem = types[field.type || defaultType];

            if (Object(uiItem) instanceof Function) {
                uiItem = uiItem.apply(ui, [field]);
            }

            for (var k in fieldDefaults) {
                if (!field[k]) field[k] = fieldDefaults[k];
            }

            uiItem = stringTableReplacements(uiItem, field);

            if (field["default"]) {
                $(uiItem).val(field["default"]);
            }

            tbody.append(uiItem);
        }
        var buttonField = $(".tbDlgButtons", form);
        container.append(buttonField);
        okButton = $("<input type='button' value='OK' class='okButton' />");
        cancelButton = $("<input type='button' value = 'Cancel' class='cancelButton' />");
        buttonField.append(okButton);
        buttonField.append(cancelButton);
    }

    function collectFields()
    {
        var rv = {};
        $(":input", form).each(function(){
            var me = $(this);
            var name = me.attr("name");
            if (name) {
                if (this.type && this.type.toLowerCase() == "checkbox") {
                    rv[name] = !!me.attr("checked");
                } else {
                    rv[name] = me.val();
                }
            }
        });
        return rv;
    }

    function setStatus(msg, type)
    {
        if (!msg.match(/[^\s]/)) {
            $(".tbDlgStatus", form).css("opacity", 0.01).html("<p class='notice " + type + "'>" + msg + "</p>");
        }
        else if (type == "error"){
            $(".tbDlgStatus", form).css("opacity", 1).html("<p style='background: #fff7f7; color: #8a1f11; border: solid 1px #FBC2C4; border-top:none; padding: 4px; margin: 0; margin-left:auto; margin-right:auto; width: 90%; text-align:center; font-size:12px;'>" + msg + "</p>");
        }
        else if (type == "success"){
            $(".tbDlgStatus", form).css("opacity", 1).html("<p style='padding: 4px; margin: 0; margin-left:auto; margin-right:auto; width: 90%; text-align:center; font-size:12px; background: #f5fbdc; color: #264409; border: solid 1px #C6D880; border-top:none;'>" + msg + "</p>");
        }
        else {
            $(".tbDlgStatus", form).css("opacity", 1).html("<p style='padding: 4px; margin: 0; margin-left:auto; margin-right:auto; width: 90%; text-align:center; font-size:12px; background: #fffff1; color: #514721; border: solid 1px #FFD324; border-top:none;'>" + msg + "</p>");
        }
    }


    function getStatus()
    {
        return $(".tbDlgStatus", form).text();
    }

    var finalValidator = new Function("formFields", "token", "setStatus", "getStatus", "form", ui.finalValidation || "return true");

    var validator = new Function("formFields", "token", "setStatus", "getStatus", "form", ui.validation || "return true");

    okButton.click(function(){
        var items = collectFields();
        try {
            if (!finalValidator.apply(form, [items, token, setStatus, getStatus, form])) {
                return;
            }
        } catch(e) {
            tbHideDiv(id);
            return callback(false, e.shortform, e);
        }
        /* DONE WITH DIALOG: HIDE */
        tbHideDiv(id);
        funcArgs.shift(); /* remove first arg (func_form) */
        funcArgs.shift(); /* remove second (not first) arg (token) */
        funcArgs.unshift(items); /* Push form fields to the front */

        if (Object(func) instanceof String) {
            funcArgs.unshift(token.tokenId); /* Push the tokenId to the front */
            funcArgs.unshift(func + "_tbUIFinish") /* Push the function name to the front */
            var rv;
            try {
                rv = tbCall.apply(this, funcArgs)[0];
            } catch(e) {
                return callback(false, e.shortform, e);
            }
            /* Callback outside of try-catch to prevent double-callback on an exception. */
            return callback(true, rv);
        } else {
            return callback(true, items);
        }
    });

    cancelButton.click(function(){
        tbHideDiv(id);
        callback(false, "Cancelled");
    });

    var onKeyUp = {
        password: true,
        text: true
    }

    function _validator() {
        var ok;
        var items = collectFields();
        try {
            ok = validator.apply(form, [items, token, setStatus, getStatus, form]);
            /* Perform form-field updates */
        } catch(e) {
            /* do something special? */
        }

        if (!ok) {
            okButton.attr("disabled", true);
        } else {
            okButton.removeAttr("disabled");
        }
        return ok;
    }

    $(":input", form).each(function(i) {
        $(this).attr("tabindex", i + 1);

        if (onKeyUp[this.type]) {
            $(this).keyup(_validator);
        } else {
            $(this).change(_validator);
        }

        $(this).keypress(function(e) {
            var code = e.which || e.keyCode;
            // RETURN: 13 == normal Linux/Windows/Mac, 3 == alternate
            if ((code == 13 || code == 3)  && _validator()) okButton.click();
            else if (code == 27) cancelButton.click(); // ESCAPE
        });
    });

    if (ui && ui.status) {
        log("Default status:", ui.status);
        setStatus(ui.status);
    } else {
        $(".tbDlgStatus", form).css("opacity", 0.01);
    }

    id = tbShowDiv(420, 200, form, true);
    applyCSS(webAssetRoot + "/css/dialog.css", form, function() {
        setTimeout(_validator, 100);
    });

    return id;
}


function tbUI_processText(msg)
{
    return msg.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/&/g, "&amp;").replace(/\r\n/g, "<br/>").replace(/\n\r/g, "<br/>").replace(/[\r\n]/g, "<br/>")
}

/**
 * A styled asynchronous version of the built-in alert() function.
 * @tparam String/DOM html The text to display inside the box.
 * @tparam Object func An optional function to call when the user presses "OK".
 * @treturn divID The numeric ID returned by tbShowDiv().
 */
function tbRawAlert(html, func)
{
    var id;
    var contents = $("<span>" +
        "<div class='tbAlertText'></div>" +
        "<div class='tbDlgButtons'>" +
            "<input type='button' class='confirm' tabindex='1' value='OK'/>" +
        "</div>" +
    "</span>");

    $(".tbAlertText", contents).append(html);
    $(".confirm", contents).click(function() { tbHideDiv(id); if (func) func(); });
    return id = tbDialog(contents);
}

/**
 * A styled asynchronous version of the built-in alert() function.
 * @tparam String msg The text to display to the user.
 * @tparam Object func An optional function to call when the user presses "OK".
 * @treturn divID The numeric ID returned by tbShowDiv().
 */
function tbAlert(msg, func)
{
    return tbRawAlert("<div class='tbErrMsg'>" + tbUI_processText(msg) + "</div>", func);
}


/**
 *
 */
function tbProgress(msg)
{
    msg = msg || "This will take a few seconds &hellip;"
    var html = $("<div class='tbErrMsg'>" + msg + "</div>" +
        "<img alt='Installing' src='" + webAssetRoot + "/images/installing.gif' style='margin-top:15px;margin-bottom:15px'/>" +
        "<br/>");
    return tbDialog(html);
}

/**
 * A styled asynchronous version of the built-in confirm() function.
 * @tparam String msg The text to display to the user.
 * @tparam Object func An optional function to call when the user presses "OK" or "Cancel". The first argument to this function will be a boolean that is true if the user pressed OK, false otherwise.
 * @tparam String trueLabel An optional label for the button that causes 'true' to be passed to the callback
 * @tparam String falseLabel An optional lable for the button that causes 'false' to be passed to the callback
 * @treturn divID The numeric ID returned by tbShowDiv().
 */
function tbConfirm(msg, func)
{
    var id;
    var contents = $("<span>" +
        "<div class='tbAlertText'></div>" +
        "<div class='tbDlgButtons'>" +
            "<input type='button' class='confirm' tabindex='1' value='OK'/>" +
            "<input type='button' class='cancel' tabindex='2' value='Cancel'/>" +
        "</div>" +
    "</span>");
    if (Object(msg) instanceof String) {
        $(".tbAlertText", contents).html(tbUI_processText(msg));
    } else {
        $(".tbAlertText", contents).html(msg);
    }
    $("input.confirm", contents).click(function() { tbHideDiv(id); if (func) func(true); });
    $("input.cancel", contents).click(function() { tbHideDiv(id); if (func) func(false); });
    return id = tbDialog(contents);
}

/**
 * Display up a modal dialog with the provided contents.
 * @tparam DOMObject dom The contents of the window.
 * @treturn divID The numeric ID returned by tbShowDiv().
 */
function tbDialog(dom, noCSS)
{
    var id;
    var box = $(
        "<div class='tbDlgWrap'>" +
            "<img src='" + dialogHeader + "'/>" +
            "<div class='tbDlg'></div>" +
        //    "<div class='tbDlgButtons'></div>" +
        //    "<div class='tbDlgChangeToken'></div>" +
        "</div>"
    );
    if (Object(dom) instanceof String) {
        dom = "<div class='tbErrMsg'>" + dom + "</div>";
    }
    $(".tbDlg", box).html(dom);

    id = tbShowDiv(420, 200, box, true);
    if (!noCSS) {
        applyCSS(webAssetRoot + "/css/dialog.css", box);
    }
    return id;
}

/**
 * Throw up a modal dialog with the provided contents (defaults to "please wait" message)
 * @tparam DOMObject dom The contents of the window.
 * @treturn divID The numeric ID returned by tbShowDiv().
 */
function tbProgressDialog(dom)
{
    return tbDialog(dom || $(generalProgressMsg));
}

function doFormCall(func, token, callback)
{
    try {
        return tbFormCall(func, token, [], callback || function() { log(arguments) });
    } catch(e) {
        log("tbFormCall error:", e);
    }
}

function tbVerifyPin(token, callback, dontRaiseTbError) {
    callback = callback || function(success, ret, err) {
        if (!success) {
            if (err) {
                tbError(err);
            }
        }
    }
    doFormCall("VerifyPin", token, function(success, ret, err) {
        if (success) {
            return callback("Verified");
        }

        if (ret == "Cancelled") {
            return callback(ret, ret);
        }
        if (!dontRaiseTbError)
            tbError(err, token);

        return callback("FailedUnknown", err);
    });
};

function verifyPin(token, callback) {
    callback = callback || function(success, ret, err) {
        if (!success) {
            if (err) {
                tbError(err);
            }
        }
    }
    doFormCall("VerifyPin", token, callback);
}

function tbChangePin(token, callback, options) {
    function reConnect(options) {
        if (!options) options = {};
        options.showList = true;
        options.noPIN = true;
        TokenConnect(options, function(success, ret, err) {
            if (ret == "cancelled") {
                return callback("Cancelled", "Cancelled")
            }
            if (success) {
                return tbChangePin(ret.tokenId, callback, options);
            } else {
                return callback("FailedUnknown", err);
            }
        });
    }

    if (!token || Object(token) instanceof Function) {
        callback = token;
        return reConnect(options);
    }
    callback = callback || function(){}

    doFormCall("ChangePin", token, function(success, ret, err) {
        if (success) {
            return callback("Changed");
        }
        if (ret == "Cancelled") {
            return callback(ret, ret);
        }
        tbError(err, token);

        return callback("FailedUnknown", err);
    });

    setTimeout(function(){
        token = loadPlugin(token);

        var details = tb.getReaderDetails(token.tokenId);
        var type = tbUtil.getTokenType(details)
        var name = token.tokenType();

        var tbl = $("<table class='currentToken'></table>");

        tbl.append("<tr><td class='icon'><img height='30' width='30' src='"+ webAssetRoot +"/images/dialog/" + images[type] + "'/></td><td> using: " + name + "<input type='button' class='useDifferent' value='Change'></td></tr>");
		
        $(".useDifferent", tbl).click(function(){
            token.disconnect();
            tbHideDiv();
            reConnect(options);
        });
        $(".tbDlgButtons").before(tbl);
        //HACK!
        tbl.css("width", "95%").css("text-align", "left");
    }, 100);
};

function tbUnblockPin(token, callback) {
    callback = callback || function(){}

    doFormCall("UnblockPin", token, function(success, ret, err) {
        if (success) {
            return callback("Unblocked");
        }
        if (ret == "Cancelled") {
            return callback(ret, ret);
        }
        tbError(err, token);

        return callback("FailedUnknown", err);
    });
};

/**
 * Displays a dialog that allows the user to select a token from those
 * currently connected to the computer.
 * @tparam String varname Optional. This is the name of global variable that will be set to the string identifier of the selected token.
 * @tparam Function callback Optional. This is called with the name of the reader if one is selected, false if the user cancels the selection, or null if no readers are connected.
 * @needs_update
 */
function tbTokenSelect(options, callback) {
    options = options || {};
    if (options.alwaysPrompt !== false)
        options.alwaysPrompt = true;
    getToken(options, function(success, ret, err) {
        if (!success) {
            log("tbTokenSelect wrapper error", arguments);
            return callback(ret == "Cancelled" ? false : null);
        }
        return callback(ret);
    });
};

/*
    currently-implemented formCalls:
        doFormCall("TokenList", null);
        doFormCall("VerifyPin", token);
        doFormCall("ChangePin", token);
        doFormCall("UnblockPin", token);
    so, you can do something tricky like the following:
        doFormCall("TokenList", null, function(success, ret, err) {
            if (success) {
                tb.connect(ret);
                doFormCall("VerifyPin", ret);
            } else if (err) {
                tbError(err);
            }
        });
*/

var __gotGetTypeDSM = false;

/* TODO: don't be such a hack */
function getFriendlyName(tokenId)
{
    if (tokenId.match("Virtual")) {
        try {
            var t = tb.connect(tokenId);
            return t.getName();
        } catch(e) {}
    }
    return tokenId.match(/^(.*?)[ 0-9]*$/)[1];
}

function getFriendlyNames()
{
    if (!__gotGetTypeDSM) {
        tbRetrieveDSM("GetType.dsm");
        __gotGetTypeDSM = true;
    }
    var list = tb.list({detailed:true});
    var ret = [];
    for (var i = 0; i < list.length; i++) {
        var t = list[i];
        var name = t.name;
        if (!t.state.EMPTY || t.state.PRESENT) {
            try {
                var dsm = tbCall("GetCardType", name)[0];
                var plug = loadDSM(dsm, name);
                name = plug.getFriendlyName();
            } catch(e) { console.debug("Err", e); }
        }
        ret.push(name);
    }
    return ret;
}

/**
 * Use a blockUI dialog to prompt a user to enter a string.
 * If the first argument is a string, that is the prompt that will be displayed to the user.
 * If there is only one argument -- the callback -- then the prompt will be "Value:".
 * Otherwise, the options object supports these fields:
 *   prompt: String. The prompt that will be displayed to the user.
 *   validation: Function(String). This function is passed the currently-entered string any time the user pressed a key. If it returns true, the OK button is enabled; otherwise, it is disabled. If no validation function is provided, the default behavior is to prohibit the empty string.
 *   noCancel: Boolean. Default false. If this is true, the user will not have the option to cancel the operation.
 *   defaultValue: String. This is the default value in the text box.
 *   password: Boolean. Default false. If this is true, the input type will be 'password', not 'text'.
 * @tparam Object options Optional. See above for full description.
 * @tparam Function callback A TBLive API callback. A cancellation is the only failure case.
 */
function tbPrompt(options, callback) {
    var defaults = {
        validation: function(txt) { return txt.length > 0; },
        prompt: "Value:",
        defaultValue: "",
        password: false,
        noCancel: false
    };

    if (Object(options) instanceof(Function)) {
        callback = options;
        options = "Value:";
    }

    if (Object(options) instanceof(String)) {
        options = {
            prompt: options
        };
    }

    for (var k in defaults) {
        if (!options[k]) options[k] = defaults[k];
    }

    var inputType = options.password ? "password" : "text";
    var html = $("<div><table><tr><td>" + options.prompt + "</td><td><input type='"+inputType+"'></td></tr><td colspan='2'><input type='button' value='Cancel'/><input type='button' value='OK'/></td></table>");
    var div;
    var okButton = $("input[@value=OK]", html), cancelButton = $("input[@value=Cancel]", html);
    var textBox = $("input[@type="+inputType+"]", html);
    textBox.val(options.defaultValue);
    if (options.noCancel) cancelButton.hide();
    function change() {
        var val = textBox.val();
        if (!options.validation(val)) {
            okButton.attr("disabled", true);
        } else {
            okButton.removeAttr("disabled");
        }
    }


    function submit() {
        var val = textBox.val();
        if (!options.validation(val)) return false;
        tbHideDiv(div);
        callback(true, val);
    }

    textBox.change(change).keyup(change).keypress(function(e){
        var key = (window.event && window.event.keyCode) || e.which || e.keyCode;
        if (key == 13) submit();
    });

    okButton.click(submit);

    cancelButton.click(function(){
        tbHideDiv(div);
        callback(false, "cancelled");
    });

    tbDialog(html);
    textBox.focus().select();
    options.validation(textBox.val());
}


window.tbFormCall = tbFormCall;

window.tbRawAlert = tbRawAlert;
window.tbAlert = tbAlert;
window.tbDialog = tbDialog;
window.tbProgress = tbProgress;
window.tbProgressDialog = tbProgressDialog;
window.tbPrompt = tbPrompt;
window.tbConfirm = tbConfirm;

window.tbVerifyPin = tbVerifyPin;
window.tbChangePin = tbChangePin;
window.tbUnblockPin = tbUnblockPin;

window.tbTokenSelect = tbTokenSelect;
})();

/*********************************************************************
 * tblive/core/dialog/install.js                                     *
 *********************************************************************/
(function() { // protect environment
var isShowing = false;
function prepareDisplay(div, data) {
    if(data.os == 'LINUX') {
        if(data.arch == 'X86') {
            $(".download", div).attr('href', webPluginRoot + "/tblive-install-linux-x86.tgz");
            $(".linux", div).show();
            return;
        } else if(data.arch == 'X86_64') {
            $(".download", div).attr('href', webPluginRoot + "/tblive-install-linux-x86_64.tgz");
            $(".linux", div).show();
            return;
        }
    } else if(data.os == 'MAC') {
        if(data.arch == 'PPC') {
            $(".download", div).attr('href', webPluginRoot + "/tblive-install-osx-ppc.dmg");
            $(".osx", div).show();
            return;
        } else if(data.arch == 'X86') {
            $(".download", div).attr('href', webPluginRoot + "/tblive-install-osx-x86.dmg");
            $(".osx", div).show();
            return;
        }
    } else if(data.os == 'WIN') {
        if(data.arch == 'X86') {
            $(".download", div).attr('href', webPluginRoot + "/tblive-win-x86.exe");
            $(".windows", div).show();
            if(data.browser == 'IE') {
                $('#banner', div).css({ zindex: '1001' });
            }
            return;
        }
    }
    $(".noSupport", div).show();
}
function tbSetupInstallDialog(overrides, allowClose, callback) {
    if(isShowing) return; // PREVENT DUPLICATES
    isShowing = true;
    $.get(webCoreRoot + "/dialog/installDialog.html", function(data) {
        var item = $(data);

        $("img",item).each(function() {
            var src = $(this).attr('alt');
            $(this).attr('src', webAssetRoot + "/images/" + src);
        });

        var divID = tbShowDiv('454','295', item, true, 'installer', null, null, false); /* final arg makes blockUI not vertically center */
        var div = item;
        var data = xmlrpc.getPlatformInfo();

        if($.browser.msie) {
            $('body').addClass('overflow');
            $('.blockUI').css('top', '0px');
        }

        setTimeout(function() {
            $('body, .page_landing').prepend('<div id="banner" title="Click this Banner to Learn More"><a class="aboutSecurity"><img class="lock" /><div class="tooltip"><strong>Why does the TrustBearer Add-On make me more safe?</strong><br/><a href="javascript:void(0)">Learn about TrustBearer Security</a></div></a></div>');
            $('.installer').click(function(){
                $('#banner').hide();
            })
            $('#banner .lock').attr('src', webAssetRoot + "/images/tb-security.jpg");
            $("#banner").click(function(){
                $('#aboutSecurity').slideToggle();
            });
            $(".continue").click(function(){
                /* If NS based/not IE, force reload */
                if(!$.browser.msie) {
                    var res = window.navigator && navigator.plugins && navigator.plugins.refresh && navigator.plugins.refresh(true);
                }
                document.location.reload(true);
                return false;
            });
            $(".afterdownload").hide();
            $(".download").click(function(){
                $("#banner").remove();
                $(".beforedownload").hide();
                $(".afterdownload").show();
            });
            if(overrides && overrides.exec) {
                if(typeof overrides.exec == "string")
                    return eval(overrides.exec);
                else
                    return overrides.exec();
            }
            prepareDisplay(div, data);

            if(allowClose) {
                $(".close", div).click(function() {
                    isShowing = false;
                    tbHideDiv(divID);
                    $('#banner').remove();
                });
            } else {
                $(".close", div).hide();
            }
            /* Callback for post-preparation */
            if(callback) callback(div, data);
        }, 0);
    });
}
window.tbSetupInstallDialog = tbSetupInstallDialog;
})();

/* TokenConnect - select and connect to a device. 
 * Usage: TokenConnect(options, callback) 
 * Arguments:
 * V
 * |
 * |->options: object / optional. A set of key-value pairs describing
 * |  V        the behavior of the function.
 * |  |
 * |  |->callback: function / optional.
 * |  |    Replaces the second argument. See below for description.
 * |  |
 * |  |->dontConnect: boolean / default false.
 * |  |    If true, the token will not be connected to and therefore the
 * |  |    tokenId string will be returned to the caller.
 * |  |
 * |  |->noPIN: boolean / default false.
 * |  |    If true, the user will not be prompted for the device PIN.
 * |  |
 * |  |->showList: boolean / default false.
 * |  |    If true, the list will always be shown, regardless of whether
 * |  |    there is a default device or a suitable device is found.
 * |  |->tokenFilter: function / optional
 * |  |    If set, the list of allowed tokens will be additionally
 * |  |    filtered by this function.  It will be passed the `item'
 * |  |    representing the token and if it returns true, it will
 * |  |    be permitted in the list.
 * |  O
 * |
 * |->callback: function / required. The function that will be called
 * |  V         when the operation is finished.
 * O  |
 *    |->success: boolean. 
 *    |    If true, ret will be the chosen token. If false, ret will be
 *    |    a shortform description of the error and err will be the full
 *    |    error object.
 *    |
 *    |->ret: variant.
 *    |    The function's return value...
 *    |       On success and dontConnect == false: the token object.
 *    |       On success and dontConnect == true: the token identifier
 *    |       On cancellation: "Cancelled"
 *    |       On error: the error's shortform.
 *    |
 *    |->err: TBError.
 *    |    If the function failed, this is the error that was thrown.
 *    |    This is undefined on success.
 *    O
 */
function TokenConnect(options, callback)
{
	if(!callback && Object(options) instanceof Function) {
		callback = options;
	}
	if(options && options.callback) callback = options.callback;
	
	if(!callback) {
        callback = function(success, ret, err) {
		    if(success) return;
    		if(ret != "Cancelled") {
                return tbError(err);
            }
    	};
    }
	
	var pinBlockedMessage = "This device is blocked; choose another device or contact your administrator for more information.";
	var selected;
	
	var defaults = {
	};
	
	var list;
	
	function getList(refresh) {
		try {
			if(!tb.list) tb.init();
		} catch(e) {
            log(e);
			callback(false, "Init", e);
            return null;
		}
		
		if(!refresh && list !== undefined) return list;
		
		try {
			list = tb.list({detailed:true});
		} catch(e) {
            log(e);
			callback(false, "List", e);
            return null;
		}
        
        // Filter options based on optional user-provided function.
        // Originally intended to keep WaveTPM from PIN change list,
        // but general enough for other uses.
        if (options.tokenFilter) {
            list = list.filter(options.tokenFilter);
        }
		
		for(var i = 0; i < list.length; i++) {
			try {
				list[i].friendlyName = getFriendlyName(list[i].name);
			} catch(e) {
				list[i].friendlyName = list[i].name;
			}
		}
		return list;
	}

	function tryConnect(reader) {
		try {
			return tb.connect(reader);
		} catch(e) {
			try {
				tbCall("TokenDisconnect", reader);
			} catch(e2) {
			}
			return tb.connect(reader);
		}
	}
	
	function pcsc() {
		return true; /* FIXME */
	}

	function init() {
		if(!pcsc()) {
			/* FIXME */
			return callback(false, "PCSC", {});
		}
		return defaultReaderCheck();
	}
	
	function defaultReaderCheck()
	{	
		var reader = tbUtil.getCookie("defaultReader");
		var list = getList(true);
        if (null === list) return;
		if(options.showList) return selection(list);
		
		if(reader && !options.noPIN){
			for(var i = 0; i < list.length; i++) {
				if(list[i].name == reader && list[i].state.PRESENT && list[i].isAllowedByPolicy) {
					return connect(list[i]);
				}
			}
		} 

		var uList = getUsableList(list);
		
		if(uList.length == 1 || (uList.length > 1 && !options.noPIN)) {
			return connect(uList[0]);
		}
		
		return selection(list);
	}
	
	function connect(listItem) {
		log("TokenConnect: connect called", listItem);
		tbUtil.setCookie("defaultReader", listItem.name);
		var plugin;
		try {
			plugin = tryConnect(listItem.name);
			plugin.type = listItem;
			
			if(options.noPIN) {
				return callback(true, plugin);
			}
			
			if(plugin.getInfo("requiresInitialization")) {
				return pinInitialization(plugin);
			} else {
				return pinEntry(plugin);
			}
		} catch(e) {
			log("TokenConnect: Connect error", e);
			return callback(false, "Connect", e);
		}
	}
	
	function pinInitialization(plugin) {
		tbInitializeDevice(plugin, function(success, ret, err) {
			if(success) {
				return callback(true, plugin);
			} else if(ret == "Cancelled") {
				return callback(false, "Cancelled");
			} else if(ret == "TBErrorPinBlocked") {
				return tbAlert(pinBlockedMessage, function() {
					return selection();
				});
			} else {
				return tbError(err, plugin.tokenId, function() {
					pinInitialization(plugin);
				});
			}
		});
		var tbl = $("<table class='currentToken'></table>");
		tbl.append("<tr><td class='icon'><img height='30' width='30' src='"+webAssetRoot+"/images/dialog/" + images[getType(plugin.type)] + "'/></td><td title='" + getFriendlyType(plugin.type) + ": "+ plugin.type.name + "'> Using: " + getFriendlyType(plugin.type) + "<input type='button' class='useDifferent' value='Change'></td></tr>");
		$(".useDifferent", tbl).click(function(){
			plugin.disconnect();
			tbHideDiv();
			selection();
		});
		$(".tbDlgButtons").before(tbl);
		//HACK!
		setTimeout(function(){
			tbl.css("width", "95%").css("text-align", "left");
		}, 500);
	}

	function getCurrentToken(plugin) {
		var tbl = $("<table class='currentToken'></table>");
		tbl.append("<tr><td class='icon'><img height='30' width='30' src='"+webAssetRoot+"/images/dialog/" + images[getType(plugin.type)] + "'/></td><td title='" + getFriendlyType(plugin.type) + ": "+ plugin.type.name + "'> Using: " + getFriendlyType(plugin.type) + "<input type='button' class='useDifferent' value='Change'></td></tr>");
		$(".useDifferent", tbl).click(function(){
			plugin.disconnect();
			tbHideDiv();
			selection();
		});
		return tbl;
	}
	
	function pinEntry(plugin) {
		tbFormCall("VerifyPin", plugin.tokenId, [], function(success, ret, err) {
			if(success) {
				return callback(true, plugin);
			} else if(ret == "Cancelled") {
				plugin.disconnect();
				return callback(false, "Cancelled");
			} else {
				return tbError(err, plugin.tokenId, function() {
					return pinEntry(plugin);
				});
			}
		});
		$(".tbDlgButtons").before(getCurrentToken(plugin));
	}
	
	function selection(list) 
	{
		refreshList(list);
	}
	
	function getType(item) {
		if(item.isUSBHardware) return "usb";
		if(item.isVirtual) return "vToken";
		if(item.isTPM) return "tpm";
		return "reader";
	}
	
	window.tbUtil.getTokenType = getType;
	
	function getFriendlyType(item) {
		if(item.isUSBHardware) return "Hardware Token";
		if(item.isVirtual) return vTokenName;
		return item.tokenName;
	}
	
	window.tbUtil.getFriendlyType = getFriendlyType;
	
	function getUsableList() {
		var list = getList();
		var ret = [];
        if (!list) return ret;
		for(var i = 0; i < list.length; i++) {
			var item = list[i];
			if(!item.isAllowedByPolicy) continue;
			if(!item.state.PRESENT) continue;
            /* Apply the token filter on each item */
            if(options.tokenFilter && !options.tokenFilter(item)) continue;
			ret.push(item);
		}
		return ret;
	}
	
	function getListItem(item) {
		/* FIXME: this needs some major cleanup */
		
		var radio = $("<input class='radio' type='radio' name='tokenId'/>");

		radio.data("token", item);
		if(item.isAllowedByPolicy) {
			radio.click(function(){
				select(this);
			});
		}
		var img = $("<img height='30' width='30' />");
		img.attr("src", webAssetRoot + "/images/dialog/" + images[getType(item)]);
		var state;
		if(getType(item) == "reader" && item.tokenName != "error") {
		    if(!item.state.PRESENT) {
				state = "<em><img src='" + webAssetRoot + "/images/dialog/warning.gif'> No Card Connected</em>";
			} else {
				state = item.tokenName + "<em> - connected</em>";
			}
    		var li = $("<li class='reader'>" + "<div class='tokenInfo'><span class='state'>" + state + "</span><br/><span class='name'>" + item.friendlyName + "</span></div></li>");
		} else if(getType(item) == "tpm") {
    		if (item.isEnabled) {
    		    state = item.tokenName + "<em> - is enabled</em>";
    		    if (!item.isSupportedInBrowser)
    		        state = "<img src='" + webAssetRoot + "/images/dialog/warning.gif'>" + item.tokenName + "is not supported in your browser.";
    		} else {
                if (item.isSupportedInBrowser)
    		        state = "<img src='" + webAssetRoot + "/images/dialog/warning.gif'>" + item.tokenName + "<em> - is not enabled</em>";
    		    else
    		        state = "<img src='" + webAssetRoot + "/images/dialog/warning.gif'>" + item.tokenName + "<em> - is not supported in your browser.</em>";
    		}
    		var li = $("<li class='reader'>" + "<div class='tokenInfo'><span class='state'>" + state + "</span><br/><span class='name'>" + item.friendlyName + "</span></div></li>");
    	} else if(getType(item) == "vToken") {
		    if (SimpleVirtualToken == "true") {
            	var li = $("<li class='reader'>" + "<div class='tokenInfo'><span class='state'>Register your computer for secure access</span><br/></div></li>");
        	} else  {
            	var li = $("<li class='reader'>" + "<div class='tokenInfo'><span class='state'>" + item.friendlyName + "</span><br/><span class='name'></span></div></li>");
        	}
        } else {
    		if(item.tokenName == "error") {
    			state = "<img src='" + webAssetRoot + "/images/dialog/warning.gif'><em>error while detecting device type.</em>";
    		} else {
    			state = "";
    		}
            var li = $("<li>" + "<div class='tokenInfo'><span class='name'>" + (item.friendlyName || "") + "</span><br/><span class='state'>" + state + "</span></div></li>");
		}
		
		li.click(function(){radio.click()});

		li.prepend(img);
		li.prepend(radio);


		if(!item.isAllowedByPolicy && item.tokenName != "error" && item.tokenName != "Unsupported token") {
			radio.attr("disabled", true);
			li.addClass("disabled");
			if(item.state.PRESENT) {
				li.addClass("disallowed");
			    li.find('.tokenInfo .name').append(" <em> -  but not allowed</em>");
			}
		}
		
		if(item.tokenName == "error") {
			li.addClass("disabled");
			radio.attr("disabled", true);
		}
		
		if(item.tokenName == "Unsupported token") {
			radio.attr("disabled", true);
			li.addClass("disabled");
		}
		
		if(!item.isAllowedByPolicy && item.isVirtual) {
			li.hide();
		}
		
		return li;
	}
	
	function select(item) {
		item = $(item).parent();
		var list = item.parent();
		$(".tokenSelect li").each(function(){
		    $(this).css({"background" : "white", "border" : "solid 1px #DDD", "border-bottom" : "solid 1px #CCC"});
		});
        item.css({"background" : "#ecf8fe", "border-top" : "solid 1px #FFF", "border-left" : "solid 1px #FFF", "border-bottom" : "solid 1px #0565a8" })
		$("li", list).removeClass("selected")
		item.addClass("selected");
		tbUtil.setCookie("defaultReader", $("input", item).data("token").name);
	}
	
	function buildList(list) {
		var ul = $("<ul class='tokenSelect'/>");
		for(var i = 0; i < list.length; i++) {
			ul.append(getListItem(list[i]));
		}

		var defaultReader = tbUtil.getCookie("defaultReader");
		$("li", ul).each(function(i) {
			var tok = $("input", this).data("token");
			if(tok && tok.name == defaultReader) {
				selected = tok;
				$(this).addClass("selected").find("input").attr("checked", true);
				return false;
			}
		});
		
		if(!selected || !$(".selected", ul).length || !$("[@checked]", ul).length) {
			$("li", ul).removeClass('selected');
			$("li", ul).not(".disallowed").not(".tokenRefresh").not(".disabled").eq(0).addClass("selected").find("input").attr("checked", true);
		}
		
		var li = $("<li class='tokenRefresh'>To use a different token, connect it to<br/>your computer and click Refresh.</li>");
		var button = $("<input type='button' class='refresh' value='Refresh'/>");
		button.click(function(){
			refreshList();
		});
		li.prepend(button);
		ul.append(li);
		return ul;
	}
	
	function hasHardware(list) {
		var hw = 0;
		var sw = 0;
		var hwPresent = 0;
		for(var i = 0; i < list.length; i++) {
			var item = list[i];
			if(item.isVirtual) { 
				sw++;
			} else {
				if(item.state.PRESENT) {
					hwPresent++;
				}
				hw++;
			}
		}
		if(!hwPresent) return false;
		return true;
	}
	
	function waitForRefresh() {
		var div;
		var contents = $(
			"<div>" + 
				"<p>Connect the " + genericDeviceName + " you would like to use, and click <b>Refresh</b>.</p>" + 
				"<div>" + 
					"<ul class='tokenSelect'>" + 
						"<li>" + 
							"<img class='notFound' src='" + webAssetRoot + "/images/dialog/not-found.gif' />" + 
							"<em>No smart cards or tokens found</em>" + 
						"</li>" + 
						"<li class='tokenRefresh'>" + 
							"<input class='refresh' type='button' value='Refresh'/>" + 
							"To use a different " + genericDeviceName + ", connect it to<br/>your computer and click Refresh." + 
						"</li>" + 
					"</ul>" + 
				"</div>" + 
			"</div>"
		);
		$("input.refresh", contents).click(function(){
			refreshList();
			tbHideDiv(div);
		});
		div = tbShowDialog({title:"Connect a reader.", contents: contents, buttons: ["Cancel"], callback:function(ret) {
			callback(false, "Cancelled");
		}});
	}
	
	function refreshList(list) {
		var tList = list || getList(true);
        if (null === tList) return;
		/*
		if(tList.length == 1 && tList[0].state.PRESENT) {
			return connect(tList[0]);
		}
		*/
		var list = buildList(tList);
		var html = $("div.tokenSelectDialog");
		var showMe = false;
		var div;
		if(!html.length) {
			html = $(
				"<div class='tokenSelectDialog'>" + 
					"<div class='tbDlgStatus'>" + 
						"<p class='notice'>Select the " + genericDeviceName+ " you would like to use.</p>" + 
					"</div>" + 
					"<div class='tbDlgBody'></div>" + 
					"<div class='tbDlgButtons'></div>" + 
				"</div>"
			);
			showMe = true;
			var done = $("<input type='button' value='Done' class='doneButton'/>");
			done.click(function(){
				tbHideDiv(div);
				if(options.dontConnect) {
					return callback(true, $(".selected input", html).data("token"));
				}
				connect($(".selected input", html).data("token"));
			});
			$(".tbDlgButtons", html).append(done);
		}

		if(getUsableList().length == 0) {
			for(var i = 0; i < tList.length; i++) {
				if(tList[i].isVirtual && !tList[i].isAllowedByPolicy) {
					return waitForRefresh();
				}
			}
			
			$("input.doneButton", html).val("Cancel").unbind("click").click(function(){
				tbHideDiv(div);
				callback(false, "Cancelled");
			});
		} else {
			$("input.doneButton", html).val("Done").unbind("click").click(function(){
				var listItem =  $(".selected input", html).data("token");
				log("TokenConnect: New token selected:", listItem);

				tbHideDiv(div);
				if(options.dontConnect) {
					log("TokenConnect: Not connecting; returning just token name");
					return callback(true, $(".selected input", html).data("token").tokenName);
				}
				log("TokenConnect: Connecting...");
				connect(listItem);
			});
		}
		
		$(".tbDlgBody", html).html(list);
		$("ul.tokenSelect").css({ border : 'solid 1px #A2CC11'});
		setTimeout(function(){
			$("ul.tokenSelect").css({ border : 'solid 1px #DDDDDD'});
		}, 1000);
		if(showMe) div = tbDialog(html, true);
	}
	init();
}

function getToken(options, callback)
{
	if(options.dontConnect) {
		return TokenConnect(function(success, ret, err) {
			if(!success) {
				return callback(false, ret, err);
			} else {
				return callback(true, ret.tokenId);
			}
		});
	}
	
	return TokenConnect(options, callback);
}

/**
 * Get a token to use. Pops a select box if there is more than device connected.
 * @tparam Function callback A function with one argument to be invoked when a reader is selected.
 * @tparam bool dontConnect If this is true, the reader is simply returned but not connected to.
 * @tparam Function errorHandler A function to receive errors
 * @treturn void Returns immediately. callback is passed the tokenId on success. If the user cancels, it is passed an array of the form [null, 'cancelled']
 */
tbUtil.getToken = function(callback, dontConnect, errorHandler) {
	errorHandler = errorHandler || tbUtil.exceptionHandler || function(ret) { throw ret };
	getToken({dontConnect: dontConnect}, function(success, ret, error) {
		if(!success) {
			if(ret == "Cancelled") {
				return callback([null, "cancelled"]);
			} else {
				return errorHandler(error);
				//throw error;
			}
		}
		callback(ret.tokenId || ret);
	});
}
	
function tbShowDialog(options, callback) {
	var divID;
	if(options.callback) callback = options.callback;
	var defaults = {
		buttons: [
			'OK',
			'Cancel'
		],
		title: "",
		contents: ""
		
	};
	for(var k in defaults) if(options[k] === undefined) options[k] = defaults[k];
	var html = $(
		'<div>' + 
			'<div class="tbDlgStatus">' + 
				'<p class="notice">' + options.title + '</p>' + 
			'</div>' + 
			'<div class="tbDlgBody"></div>' + 
			'<div class="tbDlgButtons"></div>' + 
		'</div>');
	$(".tbDlgBody", html).append(options.contents);
	if(options.buttons !== null) {
		for(var i = 0; i < options.buttons.length; i++) {
			var button = $("<input type='button' value='" + options.buttons[i] + "'/>")
			button.click(function(){
				tbHideDiv(divID);
				if(this.value=='Cancel') return callback(false, "Cancelled");
				return callback(true, this.value);
			});
			$(".tbDlgButtons", html).append(button);
		}
	} else {
		$(".tbDlgButtons", html).remove();
	}
	return divID = tbDialog(html, true);
}


(function() {
    function getCaller(lvl) {
        lvl = (lvl === 0) ? 0 : (lvl || 1);
        if (lvl < 0)
            throw new Error("lvl must be positive.");
        var func = arguments.callee.caller;
        while(lvl--) { func = func.caller; }
        return func;
    }

    function getArgs(lvl) {
        var ret = getCaller(lvl).caller;
        log("Returning args for ", ret, "Arguments:", toArray(ret.arguments));
        return toArray(ret.arguments);
    }

    function saveState()
    {
        return {
            "this": this,
            arguments: getArgs(1)
        };
    }

    function reCallWithState(func, state)
    {
        log("reCalling with state:", func, state);
        return func.apply(state["this"], state.arguments);
    }

    var vk2State;
    var argsIndex = 2;
    function getVKArgs() {
        return vk2State.arguments[argsIndex];
    }

    function logFuncCall(name)
    {
        var args = getArgs(2);
        var name = name || toSource(getCaller());
        var fargs = [name+"("];
        fargs.push.apply(fargs, args);
        fargs.push(")");
        log.apply(this, fargs);
    }

    function pinOperation(tokenId, command, args, callback, pin, pinNum) {
        try {
            if (pin) {
                tbCall("TokenVerifyPin", tokenId, pinNum || 1, pin);
            }
            var ret = command.apply(this, args);
            callback(ret)
        } catch(e) {
            /* JavaScript error... */
            if (!e.faultString)
                throw e;
            /* Special case for CAPI-based modules that may error out with the Windows
             * ERROR_CANCELLED code if PIN prompt is cancelled.
             * A more robust solution will need to be cooked up for a fully integrated
             * solution */
            if (e.shortform == "TBErrorGeneralError"
                    && e.longform.match(/Encountered error.* 0x4c7 /i)) {
                return callback([false, "Cancelled"]);
            } else if (e.shortform != "TBErrorAccessDenied") {
                if (callback.handleRetry && e.longform && e.longform.match(/smart.*card.*reset/i)) {
                    try {
                        tbCall("TokenDisconnect", tokenId);
                    } catch(e) {}
                    return callback("retry");
                }
                return callback([false, e]);
            }

            tbVerifyPin(tokenId, function(ret, err) {
                if ("Verified" == ret) {
                    return pinOperation(tokenId, command, args, callback, pin, pinNum);
                }
                return callback([false, err]);
            }, true); /* Parent handles bad errors */
        }
    }
    var p7operation = function(tokenId, params) {
        var ret = tbCall("TokenPKCS7", tokenId, params)[0];
        if (params.encodeBinary)
            ret = new Base64String(ret, true);
        return ret;
    }
    function pinPKCS7(tokenId, params, callback, pin, pinNum) {
        pinOperation(tokenId, p7operation, [tokenId, params], callback, pin, pinNum);
    }
    var rsaCryptOperation = function(tokenId, params) {
        var ret = tbCall("TokenRSACrypt", tokenId, params)[0];
        if (params.encodeBinary)
            ret = new Base64String(ret, true);
        return ret;
    }
    function pinCrypt(tokenId, params, callback, pin, pinNum) {
        pinOperation(tokenId, rsaCryptOperation, [tokenId, params], callback, pin, pinNum);
    }
    function getCertificate(next, data, state) {
        if (state.options.userCert) {
            /* Ensure that the certificate is valid DER-encoded X509 certificate data */
            tbUtil.parseCert(state.options.userCert);
            state.userCert = state.options.userCert;
            return next();
        }
        /* Public key */
        if (state.options.publicKey) {
            var publicKey = state.options.publicKey;
            if (!publicKey.type == 'rsa' || !publicKey.e || !publicKey.n) {
                var err = {
                    component: "TBVerifyKey",
                    shortform: "TBErrorGeneralError",
                    longform: "Invalid public key specified",
                    xmlrpc: true
                };
                return tbUtil.exceptionHandler(err, "TBVerifyKey", state.token);
            }
            state.publicKey = publicKey;
            return next();
        }
        if (state.options.key) { // Old session key w/ cert already
            state.challengeOptions.getChallengeBySession = true;
            return next();
        }
        var err;
        try {
            log("Reading certificate...");
            var cert = tbUtil.cardWrapper.readObject(state.token, state.certID);
            if (!cert) {
                log("Couldn't find certificate. Checking autoEnroll.");
                var args = getVKArgs();
                log("vk2State", vk2State);
                log("args", args);


                if (args.autoEnroll !== false) {
                    log("Doing autoenroll");
                    args.autoEnroll = false;
                    var autoEnrollOptions = {};
                    for(var k in args) {
                        autoEnrollOptions[k] = args[k];
                    }
                    for(var k in args.autoEnrollOptions) {
                        if (!autoEnrollOptions[k]) autoEnrollOptions[k] = args.autoEnrollOptions[k];
                    }
                    return tbAutoEnroll(state.token, autoEnrollOptions, function(success, shortform, err) {
                        if (!success) {
                            var callback = vk2State.arguments[3];
                            if (shortform == "Cancelled") {
                                return callback(false);
                            } else {
                                return callback({error:err, step:"AutoEnroll"});
                            }
                        }
                        reCallWithState(tbVerifyKey2, vk2State);
                    });
                } else {
                    log("Skipping autoenroll");
                }
                // Fake error that shouldn't be used but is 'ok' according to tbError specs
                /* Note: This may not even be necessary since lastError should be set... */
                err = {
                    component: "TBVerifyKey",
                    shortform: "TBErrorObjectNotFound",
                    longform: "Could not read certificate",
                    xmlrpc: true
                };
                if (tbUtil.cardWrapper.lastError) {
                    err = tbUtil.cardWrapper.lastError;
                }
            } else {
                /* Ensure that the certificate is valid DER-encoded X509 certificate data */
                tbUtil.parseCert(cert);
                state.userCert = cert;
            }
        } catch(e) {err = e;}
        if (err) {
            if (state.commandOptions.rawReturn)
                return state.callback({error: err, step:"getCert"});
            /* Adjust the source to be TBVerifyKey... not sure if this is sane...*/
            return tbUtil.exceptionHandler(err, "TBVerifyKey", state.token);
        }
        next();
    }
    function getChallenges(next, data, state) {
        /* Prepare the challenge request */
        var ops;
        ops = state.options;
        if (state.commandOptions.getChallengeBySession)
            return tbUtil.jsonRpcCall("VerifyKey2.getChallengeBySession", [ops.key, ops.application, ops.mode, ops.challengeOptions], next);
        else if (state.userCert)
            return tbUtil.jsonRpcCall("VerifyKey2.getChallenge", [new Base64String(state.userCert, true), ops.application, ops.mode, ops.challengeOptions], next);
        else if (state.publicKey)
            return tbUtil.jsonRpcCall("VerifyKey2.getPublicKeyChallenge", [state.publicKey, ops.application, ops.mode, ops.challengeOptions], next);
    }
    var decryptDefaults = {
        mode: 'unwrap',
        alg:  'raw',
        encodeBinary: true
    };
    var signDefaults = {
        mode: 'sign',
        alg:  'sha1',
        pad:  'pkcs1',
        encodeBinary: true
    };
    /* NOTE: encodeBinary is not an option to RSACrypt/PKCS7 but instead to pinCrypt to b64 encode it */
    var actionTypes = {
        "decrypt": function(actionItem, state, jsOptions, challengeOptions, callback) {
            var params = actionItem[1];
            if (params instanceof String || typeof params == 'string') {
                params = {data: params};
            }
            params = $.extend({}, decryptDefaults, params, {keytag: state.keyID});
            pinCrypt(state.token, params, callback, jsOptions.pinNum, jsOptions.pin);
        },
        "sign": function(actionItem, state, jsOptions, challengeOptions, callback) {
            var params = actionItem[1];
            if (params instanceof String || typeof params == 'string') {
                params = {data: params};
            }
            params = $.extend({}, signDefaults, params, {keytag: state.keyID});
            pinCrypt(state.token, params, callback, jsOptions.pinNum, jsOptions.pin);
        },
        "PKCS7": function(actionItem, state, jsOptions, challengeOptions, callback) {
            var params = actionItem[1];
            params = $.extend({encodeBinary: true}, params, {keytag: state.keyID});
            pinPKCS7(state.token, params, callback, jsOptions.pinNum, jsOptions.pin);
        },
        "exec": function(actionItem, state, jsOptions, challengeOptions, callback) {
            var code = actionItem[1];
            var params = actionItem[2];
            code = "code = function(callback, token, keyID, cert, options, challengeOptions, params, serverKey) {" + code + "}";
            eval(code);
            code(params, callback, state.token, state.keyID, state.userCert, jsOptions, challengeOptions, state.serverKey);
        }
    };
    function handleAction(actionItem, state, jsOptions, challengeOptions, callback) {
        var actionType = actionItem[0];
        var fn = actionTypes[actionType];
        if (fn == null) {
            alert("UNKNOWN ACTION");
            return callback(null);
        }
        fn(actionItem, state, jsOptions, challengeOptions, callback);
    }
    function handleModule(module, state, jsOptions, challengeOptions, callback) {
        var actionResults = [];
        var i = 0, len = module.length;
        function actionIterator(result, initialLoop) {
            if (!initialLoop) {
                actionResults.push(result);
                i++;
                if (!result || result[0] == false) {
                    if (result && result[1] && result[1] != "Cancelled") {
                        if (state.commandOptions.rawReturn)
                            return state.callback({error: result[1], step:"server"});
                        else
                            return tbUtil.exceptionHandler(result[1], "TBVerifyKey", state.token);
                    }
                    /* User cancelled */
                    return state.callback(false);
                }
            }
            if (i >= len) return callback(actionResults);
            handleAction(module[i], state, jsOptions, challengeOptions, actionIterator);
        }
        actionIterator(null, true);
    }
    function handleChallenges(next, ret, state) {
        if (!ret.result && !ret.error)
            ret.error = "Missing results from response";
        if (ret.error) {
            /* XXX: OLDSKOOL ERROR HANDLING, MAY NEED OVERHAUL */
            var parsedError = tbUtil.parseDSMError(ret.error).text;
            if (state.options && state.options.key && parsedError.match(/certificate|Session/) && !parsedError.match(/not enrolled/)) {
                delete state.options.key;
                return next(null, -2);
            }
            if (state.commandOptions.rawReturn) {
                ret.step = "handleChallenges";
                return state.callback && state.callback(ret);
            }
            return tbUtil.exceptionHandler(ret.error, "TBVerifyKey", state.token);
        }
        var challenge = ret.result;
        var challengeResults = [];
        state.serverKey = challenge.serverKey;
        state.serverCert = challenge.serverCert;
        /* Right now just pull the key from the cert */
        if (!state.serverKey && state.serverCert) {
            state.serverKey = tbUtil.parseCert(state.serverCert).key;
        }
        if (!state.serverCert && state.serverKey) {
            tbUtil.loadCrypto();
            try {
                state.serverCert = tbCall("ConstructCertificate", state.serverKey);
            } catch(e) {
                return tbError(e);
            }
        }

        /* TODO: alert response to contain the necessary options, protected values, and post-processing values */
        var response = {
            key: challenge.key
        };
        var jsOptions = state.options;
        var challengeOptions = challenge.options;
        var moduleResults = [];
        var modules = challenge.actions;
        var i = 0, len = modules.length;
        function moduleIterator(result, initialLoop) {
            if (!initialLoop) {
                moduleResults.push(result);
                i++;
            }
            if (i >= len) {
                response.options = jsOptions;
                response.responses = moduleResults;
                return next(response);
            }
            handleModule(modules[i], state, jsOptions, challengeOptions, moduleIterator);
        }
        moduleIterator(null, true);
    }
    function postResponses(next, responses, state) {
        if (responses.error) {
            throw responses.error;
        }
        /* Kill application, mode, and challengeOptions   as they were handled by the other item */
        delete responses.options.application;
        delete responses.options.mode;
        delete responses.options.challengeOptions;
        /* Wrap protected item */
        if (responses.options.protectedResponseOptions) {
            var data;
            data = responses.options.protectedResponseOptions;
            data = $.toJSON(data);
            delete responses.options.protectedResponseOptions;
            tbUtil.loadCrypto();
            var protectedData = tbCall("PKCS7", {mode: "encrypt", alg: "raw", cipher: "aes128", cert: state.serverCert, flags: "nocerts nochain nointern binary nosmimecap", data:data})[0];
            responses.options["protected"] = new Base64String(protectedData, true);
        }
        if (responses.options.calculatedResponseOptions) {
            var data;
            data = responses.options.calculatedResponseOptions;
            data = data[0](data[1]);
            delete responses.options.calculatedResponseOptions;
            for(var k in data)
                responses.options[k] = data[k];
        }
        tbUtil.jsonRpcCall("VerifyKey2.challengeResponse", [responses], next);
    }
    function handleResults(next, results, state) {
        if (state.commandOptions.rawReturn) {
            return state.callback && state.callback(results);
        }
        if (results.error) {
            /* Try to decode the error... */
            var e = results.error;
            if (e.message && e.message.match(/keypair.*mismatch/i)) {
                e.shortform = "TBErrorGeneralError"; /* ? What should we use? */
            }
            if (state.commandOptions.rawReturn) {
                results.step = "handleResults";
                return state.callback(results);
            } else {
                return tbUtil.exceptionHandler(e, "TBVerifyKey", state.token);
            }
        }
        if (results.constructor == Object) results.token = state.token;
        state.callback(results.result || results);
    }
    var verifyKeyCommandSequence = [
        getCertificate,
        getChallenges,
        handleChallenges,
        postResponses,
        handleResults
    ];

    /** DISCREPENCY: If card reset, then this will bail unlike the other version where

     * it reconnected and retried from the start */
    window.tbVerifyKey2 = function(token, keytag, options, callback, commandOptions) {
        log("tbvk2Args:", arguments);
        /* save state in case we need to re-call */
        vk2State = saveState();
        log("vk2State", vk2State);

        if (typeof keytag != 'string' && !(keytag instanceof String)) {
            /* Old version (token, options, callback)  */
            return tbVerifyKey(token, keytag, options);
        }
        var state = {
            token: token,
            keyID: keytag + "_KEY", /* May need update in future */
            certID: keytag + "_CERT",
            options: options,
            callback: callback,
            commandOptions: commandOptions || {}
        };
        tbUtil.ChainCall(verifyKeyCommandSequence, null, state);
    }
    tbVerifyKey = function(token, options, callback) {
        log("call tbVerifyKey(", arguments, ")");
        var c = arguments.caller;
        //logFuncCall();
        var keytag, commandOptions;
        keytag = options.keytag;
        commandOptions = {
            rawReturn: options.rawReturn
        };
        /* Sanity check: Enroll and Unenroll must be exclusive */
        if (options.enroll && options.unenroll) {
            alert("SANITY CHECK FAILED: Cannot enroll and unenroll at the same time ");
            return;
        }
        var oldOptions = options;
        var enroll = options.enroll;
        /* Convert safe => protected for verifykey2 */
        if (enroll && enroll.safe) {
            enroll.protected = $.extend({}, enroll.protected || {}, enroll.safe);
            delete enroll.safe;
        }
        if (options.remove) {
            var remove = options.remove;
            enroll = enroll || {};
            /* Convert safe => protected for verifykey2 */
            if (remove.safe) {
                remove.protected = $.extend({}, remove.protected || {}, remove.safe);
                delete remove.safe;
            }
            for(var k in remove) {
                var val = remove[k];
                if (k == 'protected') continue;
                enroll[k] = null;
            }
            if (remove["protected"]) {
                enroll["protected"] = enroll["protected"] || {};
                for(var k in remove["protected"]) {
                    enroll["protected"][k] = null;
                }
            }
        }
        options = {
            application: options.application,
            mode: options.enroll && "enroll" || options.unenroll && "unenroll" || "verify",
            /* Check user existence in both steps for consistency... */
            challengeOptions: {
                userMustExist: options.userMustExist,
                checkedEnroll: options.checkedEnroll
            },
            userMustExist: options.userMustExist,
            checkedEnroll: options.checkedEnroll,
            protectedResponseOptions: {
                enroll: enroll,
                unenroll: options.unenroll
            },
            autoEnroll: options.autoEnroll,
            autoEnrollOptions: options.autoEnrollOptions
        }

        return tbVerifyKey2(token, keytag, options, callback, commandOptions);
    }
})();


(function(){

var progressDialogID;

/* this is the ID used by the server to keep state for for this
 * specific certificate request */
var id;

function defaultCallback(success, ret, err) {
    log("tbAutoEnroll default callback:", arguments);
    if (ret == "cancelled") {
        /* do nothing */
    } else if (!success && err) {
        tbError(err);
        throw err;
    } else if (!success) {
        throw new Error("Failure: " + ret)
    }
}

var defaults = {
    keysize: 1024,
    keytag: "TB_AUTH",
    request: { renew: false }
};

/* Utility function -- given a shortform, pass "Cancelled" through,
 * otherwise return the default. */
function shortform(ret, defaultShortform) {
    if (ret == "cancelled" || ret == "Cancelled") return "cancelled";
    return defaultShortform;
}

/* utility functions for converting TBLive RSA key objects into DER
 * public key strings */

 /* number -> bytes */
function getBERSize(size) {
    if (size < 0x80) return String.fromCharCode(size);
    var nBytes = Math.floor(Math.log(size) / Math.log(0x100)) + 1;
    var ret = "";
    for(var i = 0; i < nBytes; i++) {
        ret = String.fromCharCode(size % 0x100) + ret;
        size >>= 8;
    }
    return String.fromCharCode(0x80 + nBytes) + ret;

}

/* number, string -> (tag)(length)(value) */
function tagWrap(tag, val) {
    return String.fromCharCode(tag) + getBERSize(val.length) + val;
}

/* key object -> DER key string */
function getDERKey(key) {
    return tagWrap(0x30, tagWrap(0x02, String.fromCharCode(0) + key.n) + tagWrap(0x02, key.e))
}

function cleanCallback(func) {
    return function() {
        if (id) {
            try {
                log("Cleaning up session #" + id);
                getGenericCA().cleanup({id:id});
            } catch(e) {
                log("Cleanup failed:", e);
            }
            id = false;
        }
        log("Cleaned up; returning...arguments=", arguments);
        return func.apply(this, arguments);
    }
}


/**
 * Enroll for a certificate, performing any required initialization or
 * authorization.
 * Supported options:
 *   keysize: Size of key to generate, in bits. Default = 1024.
 *   keytag: The key container to use. Default = TB_AUTH.
 *   request: Options to pass to the DPM. For MPKI, this is stuff like mail_firstName, etc.
 *   certProgressText: Optional. This is the text that will be displayed when the certificate is being fetched. Default = "Retrieving certificate..."
 *   adminAuthOptions: Options to pass to tbAdminAuth (if called). See tbAdminAuth for details.
 * @tparam TokenID token The token to enroll with; can be a token object or a PCSC token identifier.
 * @tparam Object options Optional. See above for supported options.
 * @tparam Function callback Optional. Called when the function is complete. See https://fwa2.trustbearer.com/trac/wiki/Callback for format. See above for shortforms.
 */
function tbAutoEnroll(token, options, callback)
{
    /* Tag all log entries with AutoEnroll */
    function log() {
        var args = toArray(arguments);
        args.unshift("AutoEnroll");
        window.log.apply(window.log, args);
    }

    /* options is optional; handle case where it is omitted but callback is not */
    if (Object(options) instanceof Function) {
        callback = options;
        options = {};
    }

    /* handle case where both options & callback are omitted */
    options = options || {}

    /* add default options */
    for(var k in defaults) {
        if (!options.hasOwnProperty(k)) {
            options[k] = defaults[k];
        }
    }

    /* if token ID is used, get token object. */
    if (!token.TokenType) {
        try {
            token = loadPlugin(token);
        } catch(e) {
            callback(false, "init", e);
        }
    }

    /* finish setting defaults */
    options.token = token.tokenId;
    callback = guardCallback(callback || defaultCallback);

    function initialized()
    {
        log("loading DPM");
        var dpm = getGenericCA();

        function init() {
            log("setting up...");
            var _options = dpm.setup(options.request);
            for(var k in _options) {
                if (!options.hasOwnProperty(k)) options[k] = _options[k];
            }
            id = _options.id;
            log("Request id:", id);

            if (options.clientScript) {
                runAlternateScript(options.clientScript);
            } else {
                genKeys();
            }
        }

        function runAlternateScript(rv) {
            var environment = {
                options: options,
                dpm: dpm,
                callback: callback,
                token: token
            };

            var script;

            if (rv.url) {
                script = tbUtil.get(webTbRoot + rv.url);
            } else if (rv.script) {
                script = rv.script;
            }

            var func = new Function("token", "dpm", "options", "callback", script);

            with(environment) {
                func.call(environment, token, dpm, options, callback);
            }
        }

        function genKeys() {
            try {
                log("try to generate keys...");
                var pubKey = token.RSACrypt({
                    mode: "genkeys",
                    keysize: options.keysize,
                    keytag: options.keytag + "_KEY"
                });
                log("Generated key:", pubKey);
                getTBSRequest(pubKey);
            } catch(e) {
                log("GenKeys failed:", e);
                if (e.shortform == "TBErrorAccessDenied") {
                    if (typeof(tbAdminAuth) != "function") {
                        return callback(false, shortform(ret, "genkeys"), e);
                    }
                    tbAdminAuth(token, options.adminAuthOptions, function(success, ret, err) {
                        log("GenKeys/tbAdminAuth ret:", arguments);
                        if (!success) return callback(false, shortform(ret, "genkeys"), err);
                        return genKeys();
                    });
                } else {
                    return callback(false, "genkeys", e);
                }
            }
        }

        function getTBSRequest(pubKey) {
            try {
                var args = {
                    publicKey: b64.encode(getDERKey(pubKey)),
                    id: id
                }
                log("Getting TBS Request...");
                var ret = dpm.getTBSRequest(args);
                log("Got TBS Request:", ret);
                return sign(ret.request);
            } catch(e) {
                log("getTBSRequest failed:", e);
                return callback(false, "getTBSRequest", e);
            }
        }

        function sign(request) {
            try {
                log("Trying to sign...");
                var signature = token.RSACrypt({
                    mode: "sign",
                    keytag: options.keytag + "_KEY",
                    data: request,
                    pad: "pkcs1",
                    /* NOTE: Another option with different (dis)advantages would
                     * be to calculate the key size from the returned key; for
                     * now we will ignore that because of the problems inherent
                     * in converting a modulus length (in bytes) to a key size
                     * (in bits). */
                    keysize: options.keysize,
                    alg: "sha1"
                });
                log("Signed. Signature:", signature);
                return getCertificate(signature);
            } catch(e) {
                log("sign error:", e);
                if (e.shortform == "TBErrorAccessDenied") {
                    return verifyPin(token.tokenId, function(success, ret, err) {
                        log("sign/verifyPin ret:", arguments);
                        if (!success) return callback(false, shortform(ret, "sign"), err);
                        return sign(request);
                    });
                } else {
                    return callback(false, "sign", e);
                }
            }
        }

        function getCertificate(signature, dialog) {
            /* ensure that dialog is displayed by showing it
             * then re-calling with setTimeout */
            if (!dialog) {
                progressDialogID = tbProgress(certProgressMsg);
                return setTimeout(function() {
                    getCertificate(signature, true);
                }, 10);
            }

            try {
                log("Trying to fetch certificate...");
                var args = {
                    id: id,
                    signature: b64.encode(signature)
                };

                var ret = dpm.getCertificate(args, function(success, ret, err) {
                    tbHideDiv(progressDialogID);
                    log("getCertificate returned:", toArray(arguments));
                    if (!success) {
                        log("getCert error", err);
                        return callback(false, "getCert", err);
                    }
                    log("Got certificate:", ret);
                    return writeCertificate(ret);
                });
            } catch(e) {
                tbHideDiv(progressDialogID);
                log("getCert error", e);
                return callback(false, "getCert", e);
            }
        }

        function writeCertificate(cert) {
            try {
                log("Trying to write certificate...");
                token.writeData(options.keytag + "_CERT", cert.certificate);
                log("Wrote certificate, done!");
                return callback(true, cert);
            } catch(e) {
                log("writeCert error", e);
                if (e.shortform == "TBErrorAccessDenied") {
                    if (typeof(tbAdminAuth) != "function") {
                        return callback(false, shortform(ret, "writeCert"), e);
                    }
                    tbAdminAuth(token, options.adminAuthOptions, function(success, ret, err) {
                        log("writeCert/adminAuth ret:", arguments);
                        if (!success) return callback(false, shortform(ret, "writeCert"), err);
                        return writeCertificate(cert);
                    });
                } else {
                    return callback(false, "writeCert", e);
                }
            }
        }

        init();
    }

    try {
        log("Trying to check initialization status...");
        if (token.getInfo("requiresInitialization")) {
            log("Succeeded, not initialized...initializing.");
            return tbInitializeDevice(token, function(success, ret, err) {
                log("init ret", arguments);
                if (!success) return callback(false, shortform(ret, "init"), err);
                initialized();
            });
        } else {
            log("Succeeded, device already initialized; enrolling.");
            initialized();
        }
    } catch(e) {
        log("Failed while checking initialization status", e);
        return callback(false, "init", e);
    }
}

function tbAutoRenew(token, options, callback) {
    var renewScript, renewFunc;

    callback = cleanCallback(callback || defaultCallback);

    try {
        log("tbAutoRenew: loading DPM");
        dpm = getGenericCA();
    } catch(e) {
        log("tbAutoRenew loadDPM error", e);
        return callback(false, "loadDPM", e);
    }

    var query = window.location.search;

    var parts = query.split("&");
    parts[0] = parts[0].replace(/^\?/, "");

    for(var i = 0; i < parts.length; i++) {
        var part = parts[i].split("=");
        options[part[0]] = part[1];
    }

    //options.form_file = "UserEnrollEncNS.fdf";

    try {
        var ret = dpm.setup(options);
        id = ret.id;
    } catch(e) {
    }

    try {
        log("tbAutoRenew: Fetching script");
        renewScript = dpm.getRenewalScript();
    } catch(e) {
        log("tbAutoRenew getRenewalScript error", e);
        return callback(false, "getScript", e);
    }

    if (renewScript) {
        log("tbAutoRenew: renewScript:" + script);

        var script = renewScript.script || tbUtil.get(webLibRoot + "/" + renewScript.file);
        if (script) {
            log("tbAutoRenew: Got script:", script);
            try {
                renewFunc = (new Function("return " + script))();
            } catch(e) {
                log("tbAutoRenew eval script error:", e);
                return callback(false, "loadScript", e);
            }
        }
    }

    if (!renewFunc) {
        log("tbAutoRenew: No custom renewal function provided; using tbAutoEnroll");
        renewFunc = tbAutoEnroll;
    }

    try {
        log("tbAutoRenew: Calling renewal function", renewFunc);
        return renewFunc(token, options, callback);
    } catch(e) {
        log("tbAutoRenew renewFunc error:", e);
        return callback(false, "unknown", e);
    }
}

var dpm;

function getGenericCA() {
    if (dpm)
        return dpm;
    dpm = loadDPM("com.trustbearer.dpm.genericca.GenericCA");
    var ret = dpm.getEscapeFunctions({});
    $(ret).each(function(i){
        var func = this;
        dpm[func] = function() {
            var args = {
                id: id,
                func: func,
                args: toArray(arguments)
            };
            var ret = dpm.escape.call(dpm, args);
            if (ret.id) id = ret.id;
            return ret.ret;
        }
    });
    return dpm;
}

/* Register the functions to the global namespace */
window.tbAutoEnroll = tbAutoEnroll;
window.tbAutoRenew = tbAutoRenew;

})();


