// Cloud engines global namespace
if(!CE) var CE = {};

// --- Timer helpers
if(!CE.rTimer) {
    CE.rTimer = function(func, timeout) {
        return window.setTimeout(func, timeout);
    };
    CE.uTimer = function(tid) {
        return window.clearTimeout(tid);
    };
    CE.rInterval = function(func, timeout) {
        return window.setInterval(func, timeout);
    };
    CE.cInterval = function(tid) {
        return window.clearInterval(tid);
    };
}

// --- DOM helpers
if(!CE.dce) {
    CE.$ = function(n) {
        if(typeof(n)=='string') {
          return document.getElementById(n);
        } else {
          return n;
        }
    }
    CE.dce = function(t,i,c) {
        var el = document.createElement(t);
        if(t=='a') el.href='#';
        if(i) el.id = i;
        if(c) el.className = c;
        return el;
    };
    CE.dca = function(oc,i,c,t) {
        var a = CE.dce('a',i,c);
        if(oc) CE.CEU.attachEvent(a, 'click', oc);
        if(t)  a.title = t;
        return a;
    };
    CE.dci = function(u,i,c,a) {
        var im = CE.dce('img',i,c);
        im.border = '0';
        if(a) im.alt=a;
        if(u) im.src=u;
        return im;
    };
    CE.dcib = function(i,c,a) {
        return CE.dci(CE.STRTAB.lookup('imgbase')+'blank.gif',i,c,a);
    };
    CE.dctn = function(t) {
        return document.createTextNode(t);
    };
    CE.dco = function(l,v,i,c) {
        var o = CE.dce('option', i, c);
        o.value = v;
        o.appendChild(CE.dctn(l));
        return o;
    };
    CE.dcl = function(fid,c) {
        var l = CE.dce('label', null, c);
        l.htmlFor = fid;
        return l;
    };
    CE.rac = function(e) {
      var f = CE.$(e);
      if(f) {
        while(f.hasChildNodes()) {
          var c = f.firstChild;
          //CE.CEU.releaseAllEvents(c, 'click', true);
          f.removeChild(c);
        }
      }
    };
    CE.trims = function(s, l, r) {
      if(s) {
        if(s.length > l) {
          if(r) {
            s = "..." + s.substr(s.length-l);
          } else {
            s = s.substr(0, l) + "...";
          }
        }   
      }
      return s;
    };
    CE.trims2 = function(s, l, r) {
      // Same as trims except chops to l including appended ellipsis
      if(l < 3)
        l = 3;
      if(s) {
        if(s.length > l) {
          if(r) {
            s = "..." + s.substr(s.length-l-3);
          } else {
            s = s.substr(0, l-3) + "...";
          }
        }   
      }
      return s;
    };
}

// --- class name helpers
if(!CE.aCN) {
    CE.lCN = function(el) {
        var cnl;
        if(el.className) {
            cnl = el.className.split(' ');
        } else {
            cnl = [];
        }
        return cnl;
    };
    CE.irCN = function(el, cn, cnl) {
        if(cnl === undefined || cnl === null) {
            cnl = CE.lCN(el);
        }
        for(var i=0; i<cnl.length; i++) {
            if(cnl[i] == cn) {
                cnl.splice(i, 1);
                i--;
            }
        }
        return cnl;
    };
    CE.hCN = function(e, cn) {
        var r = false;
        var el = CE.$(e);
        if(el) {
            var cnl = CE.lCN(el, cn);
            for(var i=0; i<cnl.length; i++) {
                if(cnl[i] == cn) {
                    r = true;
                    break;
                }
            }
        }
        return r;
    };
    CE.dis = function(evt) {
        if(!evt) evt = window.event;
        var targ = evt.target;
        if(!targ) targ = evt.srcElement;
        if(targ.nodeType == 3 || targ.nodeName.toLowerCase() == 'span') targ = targ.parentNode;
        if(CE.hCN(targ, 'cedisabled'))
            return true;
        targ = targ.parentNode;
        return (targ && CE.hCN(targ, 'cedisabled'));
    };
    CE.aCN = function(e, cn) {
        var el = CE.$(e);
        if(el) {
            var cnl = CE.irCN(el, cn);
            cnl[cnl.length] = cn;
            el.className = cnl.join(' ');
        }
    }
    CE.rCN = function(e, cn) {
        var el = CE.$(e);
        if(el) {
            var cnl = CE.irCN(el, cn);
            el.className = cnl.join(' ');
        }
    };
    CE.tCN = function(e, cn) {
        var el = CE.$(e);
        if(el) {
            var cnl = CE.lCN(el, cn);
            var found = false;
            for(var i=0; i<cnl.length; i++) {
                if(cnl[i] == cn) {
                    cnl.splice(i, 1);
                    i--;
                    found=true;
                }
            }
            if(!found) {
                cnl[cnl.length] = cn;
            }
            el.className = cnl.join(' ');
        }
    };
    CE.getByClass = function(parent, tag, cls) {
        var result = [];
        if(!parent) return result;
        var all = parent.getElementsByTagName(tag);
        for(var i = 0; i < all.length; ++i) {
            if(CE.hCN(all[i],cls))
                result.push(all[i]);
        }
        return result;
    };
}

//-----------------------------------------------------
// Cloud Engines String Table support
CE.STRTAB = new function () {
    var that = this;
    this.ltable = {};
    this.locale = 'default';
    this.load = function(locale, src) {
    };
    function rawLookup(key,a1,a2,a3,a4,a5) {
        if(that.locale in that.ltable) {
            var tbl = that.ltable[that.locale];
            if(key in tbl) {
                return tbl[key];
            } else {
                CE.CEDBG.println("CEU: ERROR: Locale '"+that.locale+"' does not define string '"+key+"'");
            }
        } else {
            CE.CEDBG.println("CEU: ERROR: Locale '"+that.locale+"' not defined");
        }
        var m ='---{'+key;
        if(a1!==undefined) {
            m+='(%1';
            if(a2) { m+=',%2'; }
            if(a3) { m+=',%3'; }
            if(a4) { m+=',%4'; }
            if(a5) { m+=',%5'; }
            m+=')';
        }
        return m+'}---';
    }
    this.lookup = function(key,a1,a2,a3,a4,a5) {
        var m = rawLookup(key,a1,a2,a3,a4,a5);
        if(a1!==undefined) {m=m.replace(/%1/g, a1);}
        if(a2!==undefined) {m=m.replace(/%2/g, a2);}
        if(a3!==undefined) {m=m.replace(/%3/g, a3);}
        if(a4!==undefined) {m=m.replace(/%4/g, a4);}
        if(a5!==undefined) {m=m.replace(/%5/g, a5);}
        return m;
    };
    this.lookupdb = function(key,a1,a2,a3,a4,a5) {
        if(key.substr(0,9) == '%%SQI18N:') {
            // DB specifies localized string
            return that.lookup(key.substr(9),a1,a2,a3,a4,a5);
        } else {
            // DB hardcoded string
            return key;
        }
    };
  // This function will look up the string and return it as a document node.
  // The actual node type returned will depend on the presence of positional
  // arguments in the localized string:
  //   0. No Position arguments ('%x') -- a text node is returned
  //   1. Arguments of type [object] will be assumed to be document nodes
  //      and an 'span' element will be created that includes the text nodes
  //      of the localized string and the document elements at the positions
  //      specified by the '%1', '%2', '%3', '%4', '%5' placeholders in the
  //      localized string
  //   2. Arguments of type [string] will be assumed to be turned into 
  //      document text nodes and follow the same behaviour of #1
  //   3. Arguments of type [function] will be invoked in the order the place
  //      holders are encountered and the return code is treated as #1 or #2
  //      above.
  this.lookupel = function(key,a1,a2,a3,a4,a5) {
    return that.lookupeldoc(document,key,a1,a2,a3,a4,a5);
  };
  // Implementation of the above. Call this version if you are using calling
  //   this from another document (ex child frame) so that the node returned
  //   is created in the right document.
  this.lookupeldoc = function(doc,key,a1,a2,a3,a4,a5,ishtml) {
    var m = rawLookup(key,a1,a2,a3,a4,a5);
    var s = m.split('%');
    var sl = s.length;
    if(sl<=1) {
      if(ishtml) {
        var span = doc.createElement('span');
        span.innerHTML = m;
        return span;
      } else {
        return doc.createTextNode(m);
      }
    } else {
      var a = [a1,a2,a3,a4,a5];
      var i;
      var span = doc.createElement('span');
      if(ishtml)
        span.innerHTML = s[0];
      else
        span.appendChild(doc.createTextNode(s[0]));
      for(i=1;i<sl;i++) {
        var ai = parseInt(s[i].substring(0, 1));
        if(isNaN(ai)) {
          if(ishtml)
            span.innerHTML += s[i];
          else
            span.appendChild(doc.createTextNode(s[i]));
        } else {
          ai--;
          var av = a[ai];
          if(typeof(av) == 'function') {
            av = av();
          }
          if(typeof(av) == 'object') {
            span.appendChild(a[ai]);
          } else if(av) {
            if(ishtml)
              span.innerHTML += av;
            else
              span.appendChild(doc.createTextNode(av));
          } else {
            span.appendChild(doc.createTextNode('--(A'+(ai+1)+')--'));
          }
          var r = s[i].substring(1);
          if(r) {
            if(ishtml)
              span.innerHTML += r;
            else
              span.appendChild(doc.createTextNode(r));
          }
        }
      }
      return span;
    }
  };
  // Same as lookupel but expects that the content of the localized text is html rather than plain text
  this.lookupelh = function(key,a1,a2,a3,a4,a5) {
    return that.lookupeldoc(document,key,a1,a2,a3,a4,a5,true);
  };
}();

//-----------------------------------------------------
// Cloud Engines Utility namespace
CE.CEU = new function() {
  //----------------------------------------------------------------------------------------
  // Private variables
  var that = this;
  // Compile a regular express for email validation once early
  var emailregex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".\"))@(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,})$/;
  // last event id so we can generate new onces incrementally
  var __lasteid = 0;
  // map from eid to handlers so the DOM doesn't hold references to temporal objects
  var __ehandlers = {};

  //----------------------------------------------------------------------------------------
  // Public variables

  this.user = null;
  this.svc = null;

  //----------------------------------------------------------------------------------------
  // Private Functions
  function emsg(e,m,a1,a2,a3,a4,a5) {
    var el = CE.CEU.$(e);
    if(el) {
      CE.rCN(el, 'hidden');
      CE.rac(el);
      el.appendChild(CE.STRTAB.lookupel(m,a1,a2,a3,a4,a5));
    }
    el = null;
  }
  function getval(x) {
    var e;
    if(typeof(x)==='string') {
      e = x;
    } else {
      e = x.value;
    }
    return e.replace(/^\s*(.*?)\s*$/,"$1");
  }

  function onGetUserSuccess(r, data) {
    if(r && r.user) {
      var loc = r.user.locale;
      that.user = r.user;
      that.checkUserLocale(loc);
      if(data) {
        if(data[0]) {
          var usernameel = CE.$(data[0]);
          if(usernameel) {
            CE.rac(usernameel);
            if(navigator.platform == 'iPad')
                usernameel.appendChild(document.createTextNode(CE.trims2(r.user.screenname,11)));
            else
                usernameel.appendChild(document.createTextNode(r.user.screenname));
          }
          usernameel = null;
        }
        if(data[1]) {
          var useropsel = CE.$(data[1]);
          if(useropsel) {
            CE.rac(useropsel);
          }
          useropsel = null;
        }
        if(typeof(data[2])=='function') {
          data[2]('success', that.user);
        }
      }
    } else {
      onGetUserFailure(r, data);
    }
  }
  function onGetUserFailure(r, data) {
    // No user object returned?
    CE.CEDBG.println('CEU: Failed to get user object: '+r);
    if(data) {
      if(typeof(data[2])=='function') {
        return data[2]('failure');
      }
    }
    that.logoutUser(data);
  }


    function onForgotPasswordSuccess(r, data) {
      if(r) {
        if(data) {
          if(data[0]) {
            var el = CE.$(data[0]);
            if(el) {
              CE.rac(el);
            }
            el = null;
          }
          if(typeof(data[1])=='function') {
            return data[1]('success', that.user);
          }
        }
      }
    }


    function onForgotPasswordFailure(r, data) {
      var reason = null;
      if(r && r['HB-EXCEPTION']) {
        reason = r['HB-EXCEPTION'];
      }
      CE.CEDBG.println('CEU: Failed to login user: '+r);
      if(data) {
        if(data[0]) {
          emsg(data[0], 'ceu.pwresetfail');
        }
        if(typeof(data[1])=='function') {
          return data[1]('failure', reason);
        }
      }
    }

  function onLoginUserSuccess(r, data) {
    if(r && r.user) {
      that.user = r.user
      if(r.valtoken) {
        var ma = '';
          if(data && data[2]) {
              if(data[2].checked) {
                  var date = new Date();
                  date.setTime(date.getTime()+(14*24*60*60*1000));
                  ma = ";expires="+date.toGMTString();
              }
          }
          var domain = "";
          var dparts = window.location.host.split(".");
          if(dparts.length == 3) {
              domain = ";domain=" + "." + dparts[1] + "." + dparts[2];
          }
          var ck = 'valtoken='+r.valtoken+ma+';path=/'+domain;
          CE.CEDBG.println("CEu: Login success: -- Cookie set for: "+ck);
          document.cookie = ck;
      }
      if(r.user.locale && CE.LOCALES)
        that.checkUserLocale(r.user.locale);
      if(data) {
        if(data[0]) {
          var el = CE.$(data[0]);
          if(el) {
            CE.rac(el);
          }
          el = null;
        }
        if(typeof(data[1])=='function') {
            return data[1]('success', that.user, r.albumid);
        }
      }
    }
  }
  function onLoginUserFailure(r, data) {
    var reason = null;
    if(r && r['HB-EXCEPTION']) {
      reason = r['HB-EXCEPTION'];
    }
      if(reason.ecode==811) {
          window.location.replace("almost.html");
          return;
      }
      
    CE.CEDBG.println('CEU: Failed to login user: '+r);
    if(data) {
      if(data[0]) {
        emsg(data[0], 'ceu.loginfail');
      }
      if(typeof(data[1])=='function') {
        return data[1]('failure', reason);
      }
    }
  }

  function onNewUserDeviceSuccess(r, data) {
      // Seems to have worked. Lets just direct the user to 
      // check his email.
      window.location.replace("almost.html");
      return;

      if(r && r.user) {
      that.user = r.user
      if(r.valtoken) {
        var ma = '';
        // TODO: remember me code:
//         if(cb.checked) {
//           // Calcuate an expiration date 14 days in the future...
//           var date = new Date();
//           date.setTime(date.getTime()+(14*24*60*60*1000));
//           ma = ";expires="+date.toGMTString();
//         }
        var ck = 'valtoken='+r.valtoken+ma+';path=/';
        CE.CEDBG.println("CEU: New User success: -- Cookie set for: "+ck);
        document.cookie = ck;
      }
      if(data) {
        if(data[0]) {
          var el = CE.$(data[0]);
          if(el) {
            CE.rac(el);
          }
          el = null;
        }
        if(typeof(data[1])=='function') {
          return data[1]('success', that.user);
        }
      }
    }
  }
  function onNewUserDeviceFailure(r, data) {
    var reason = null;
    if(r && r['HB-EXCEPTION']) {
      reason = r['HB-EXCEPTION'];
    }
    CE.CEDBG.println('CEU: Failed to create user (with device): '+r);
    if(data) {
      if(data[0]) {
        if(reason) {
          if(reason.ecode == 801) {
              var a = document.createElement("a");
              a.href=CE.STRTAB.lookup('helpurl');
                a.target="_blank";
              a.appendChild(CE.STRTAB.lookupel('view.clickspace'));
              emsg(data[0], "ceu.nodevicehelp", a);
          } else {

          // TODO: Map out exception code messages...
            var string = that.errorstrings["" + reason.ecode];
            if(string) {
                var a = document.createElement("a");
                a.appendChild(CE.STRTAB.lookupel("ceu.clickhere"));
                a.href = '#';
                emsg(data[0], string, a);
            } else {
                emsg(data[0], 'ceu.rpcexcept', reason.ecode, reason.message);
            }
          }
        } else {
          emsg(data[0], 'ceu.newuserdevfail', CE.CEDBG.serialize(r));
        }
        if(typeof(data[1])=='function') {
          return data[1]('failure', reason);
        }
      }
    }
  }

  // Dispatch function for events to avoid DOM having references to temporal objects
  function dispatchEvent(event) {
      var e = event || window.event;
      var tel;
      if(e.target) { 
          tel = that.$(e.target); 
      } else { 
          tel = that.$(e.srcElement);  
     }
      if(tel.nodeType==3) { 
          if(tel.parentNode) {
              tel = that.$(tel.parentNode); 
          }
      }
      while(tel && typeof(tel.__EID)==='undefined' && tel.parentNode) {
          tel = that.$(tel.parentNode);
      }
      if(tel) {
          var h = __ehandlers[tel.__EID+'_'+e.type];
          if(!h) {
              h = __ehandlers[tel.__EID];
          }
          if(typeof(h)!=='undefined') {
              this.__EH = h.onEvent;
              var ret = this.__EH(h, e, tel);
              this.__EH = null;
              return ret;
          }
      } else {
          CE.CEDBG.println('CEU: Failed to get target of event!');
      }
  }

  // Helper event function for keybaord events
  function kpKeyHelper(p,e,t) {
    var k;
    k=e.keyCode;
    if(k==p.ecode) {
      if(typeof(p.eobj) === 'function') {
        p.eobj();
      } else {
        p.eobj.onEvent(p.eobj);
      }
    }
  }
  
  this.stopevt = function(evt) {
    evt.cancelBubble = true;
    if(evt.stopPropagation) evt.stopPropagation();
  };

  function iteratetabs(tabobjs, tabpfx, panepfx, match, callback)
  {
    var i;
    var foundone = false;
    for(i=tabobjs.length-1; i>=0; i--) {
      var tabobj = tabobjs[i];
      var tabname = tabobjs[i];
      if(tabobj && typeof(tabobj) == "object") {
        tabname = tabobj.name;
      } else {
        tabobj = null;
      }
      var tab = document.getElementById(tabpfx+tabname);
      var pane = null;
      if(panepfx!=null) {
        pane = document.getElementById(panepfx+tabname);
      }
      if(tab!=null) {
        if(typeof(callback) == "function") {
          callback(tabobj, tabname, tab, pane, (match==tabname));
        }
        if(match == tabname) {
          foundone = true;
        }
      }
    }
    return foundone;
  }

  //----------------------------------------------------------------------------------------
  // Public Functions
  this.$ = function(n) {
    return CE.$(n);
  }
  this.getCookie = function(n) {
    var ca = document.cookie.split(';');
    var i;
    for(i=0;i<ca.length;i++) {
      var c=ca[i];
      c=c.replace(" ", "");
      if(c.indexOf(n+'=')===0) {
        return c.substring((n + '=').length, c.length);
      }
    }
    return null;
  }
  this.setCookieTemp = function(n, v) {
    document.cookie = n+'='+v + ';path=/';
  };
  this.getValtoken = function() {
    return this.getCookie("valtoken");
  }
  this.isLoggedIn = function() {
    if(this.getValtoken()) {
      return true;
    }
    return false;
  }
  this.validateEmail = function(e) {
    //CE.CEDBG.println('CEU: Validating Email "'+e+'"');
    if(!e) {
      return false;
    }
    if(emailregex.test(e)) {
      //CE.CEDBG.println('CEU:   +++ Valid Email "'+e+'": '+emailregex);
      return true;
    } else {
      CE.CEDBG.println('CEU:   +++ Invalid Email "'+e+'" '+emailregex);
      return false;
    }
  }
                       
  this.errorstrings = {
      "606": "ceu.usererror",
      "801": "ceu.nodevice",
      "806": "ceu.alreadyreg",
      "810": "ceu.userexists"
  };

  this.notifyError = function(data, ctx, a1, a2, a3, a4, a5) {
      var pane = document.createElement('div');
      switch(ctx) {
      case null:
      case undefined:
      case 'MSG':
          CE.rac(pane);
          if(typeof(data) == 'object') {
              pane.appendChild(data);
          } else {
              pane.appendChild(document.createTextNode(data));
          }
          break;
      case 'RPC':
          var reason = null;
          if(data && data['HB-EXCEPTION']) {
              reason = data['HB-EXCEPTION'];
          }
          if(reason) {
              // TODO: Map out exception code messages...
              var errstr = that.errorstrings["" + reason.ecode];

              if(errstr) {
                  emsg(pane, errstr, a1);
              } else {
                  emsg(pane, "ceu.rpcexcept", reason.ecode, reason.message, a1);
              }
          } else {
              emsg(pane, 'ceu.rpcerror', CE.CEDBG.serialize(data), a1);
          }
          break;
      default:
          emsg(pane, 'ceu.unknownerror', CE.CEDBG.serialize(ctx), CE.CEDBG.serialize(data));
          break;
      }
      var dlg = new CE.CEU.Dialog(CE.STRTAB.lookup('ceu.error.title'), pane, 
          [{name:'ok',label:CE.STRTAB.lookup('button.ok')}]);
      dlg.show();
      dlg = null;
  }
  this.getCalendarDate = function(aDate) {
      var m  = aDate.getMonth();
      var mn = CE.STRTAB.lookup('ceu.month'+m);
      var d  = aDate.getDate();
      var y  = aDate.getYear();
      if(y < 1000) { y = y + 1900; }
      return CE.STRTAB.lookup('ceu.datefmt', mn, d, y);
  }
  this.getDateFromString = function(aString) {
  }
  this.trimstring = function(string, maxchars) {
    return CE.trims(string, maxchars-3);
  }

  this.shallowCopy = function(obj) {
      var copy = {};
      for(var i in obj) {
          copy[i] = obj[i];
      }
      return copy;
  }
  
  this.shallowCopyInto = function(dest, obj) {
      for(var i in obj) {
          dest[i] = obj[i];
      }
  };

  this.attachEvent = function(element, type, eobj) {
    // Set a unique id for the handler if not yet set...
    if(typeof(eobj.__EID)==='undefined') {
        if(typeof(element.__EID)==='undefined') {
            eobj.__EID = ''+(++__lasteid);
        } else {
            eobj.__EID = element.__EID;
        }
    }
    if(typeof(__ehandlers[eobj.__EID+'_'+type])==='undefined') 
        __ehandlers[eobj.__EID+'_'+type]=eobj;
    // Store the EID
    element.__EID = eobj.__EID;
    // Set up the actual click handler
    element['on'+type] = dispatchEvent;
  }
  
  this.getEventObj = function(element, type) {
    if(typeof(element.__EID)==='undefined')
        return null;
    return __ehandlers[element.__EID+'_'+type]
  };

  this.releaseEvent = function(element, type, eobj) {
    if(eobj && typeof(eobj.__EID)!=='undefined') {
      try { delete __ehandlers[eobj.__EID+'_'+type]; } catch(e) {};
      __ehandlers[eobj.__EID+'_'+type] = null;
      try {delete eobj.__EID; } catch (e) {};
    }
    if(element && typeof(element.__EID)!=='undefined') {
      try { delete __ehandlers[element.__EID+'_'+type]; } catch(e) {};
      __ehandlers[element.__EID+'_'+type] = null;
      try { delete element.__EID; } catch (e) {};
      element['on'+type] = null;
    }
  };
  this.releaseAllEvents = function(element, type, removeel, cb) {
      var doit = false;
      element = CE.$(element);
      if(element) {
          doit = true;
          if(cb) doit = cb(element);
          if(doit) this.releaseEvent(element, type, null);
          if(element.hasChildNodes()) {
              var children = element.childNodes;
              for(var i=children.length-1; i>=0; i--) {
                  var e = children[i];
                  var r = true;
                  if(e.nodeType == 1) {
                      r = this.releaseAllEvents(e, type, removeel, cb);
                      if(removeel && r) {
                          element.removeChild(e);
                      }
                  } else {
                      if(removeel && doit) {
                          element.removeChild(e);
                      }
                  }
              }
          }
      }
      return doit;
  };
  this.attachEnterPressedEvent = function(element, eobj) {
    this.attachEvent(element, 'keydown', {onEvent:kpKeyHelper, eobj:eobj, ecode:13});
  };
  this.attachEscapePressedEvent = function(element, eobj) {
    this.attachEvent(element, 'keydown', {onEvent:kpKeyHelper, eobj:eobj, ecode:27});
  };
  this.attachKeyPressedEvent = function(element, eobj, keyCode) {
    this.attachEvent(element, 'keydown', {onEvent:kpKeyHelper, eobj:eobj, ecode:keyCode});
  };
  this.showpane = function(tabobjs, tabpfx, panepfx, match, args) {
    var except = null;
    // Iterate first to find the one to hide...
    var ihidit = null;
    var numhid = 0;
    var numsel = 0;
    iteratetabs(tabobjs, tabpfx, panepfx, match, 
                  function(to, tn, t, p, dm) {
                    if(!dm) {
                      if(p===null || p.className!='hidden') {
                        numsel++;
                        var hideit = true;
                        if(typeof(to.can_hide)=='function') {
                          hideit=to.can_hide(t, p, match, args);
                        }
                        if(hideit) {
                          t.className='';
                          if(p!=null) { p.className='hidden'; }
                          if(typeof(to.notify_hide)=='function') {
                            to.notify_hide(t, p, args);
                          }
                          ihidit = tn;
                          numhid++;
                        } else {
                          CE.CEDBG.println('CEU: CanHide returned false "'+t.id+'"');
                        }
                      }
                    }
                  });
    // Make sure we didn't short circuit because of can_hide
    if(numsel<=0 || numhid>0) {
      // Iterate again to find the one to show...
      var numshown = 0;
      var numfound = 0;
      iteratetabs(tabobjs, tabpfx, panepfx, match,
                    function(to, tn, t, p, dm) {
                      if(dm) {
                        if(p==null || p.className=='hidden') {
                          numfound++;
                          var showit = true;
                          if(typeof(to.can_show)=='function') {
                            //try {
                            showit=to.can_show(t, p, args);
                            //} catch(e) {
                            //    except = e;
                            //    showit = false;
                            //}
                          }
                          if(showit) {
                            t.className='selected';
                            if(p!=null) { 
                              p.className='visible'; 
                              // This is just a dirty hack for IE layout bugs...
                              window.setTimeout(function() {p.className='visible';}, 0);
                            }
                            if(typeof(to.notify_show)=='function') {
                              //try {
                              to.notify_show(t, p, args);
                              //} catch(e) {
                              //    except = e;
                              //}
                            }
                            // Log the page transition to analytics
                            try {
                              urchinTracker(match);
                            } catch(e) {}
                            numshown++;
                          }
                        }
                      }
                      return 0;
                    });
      if(numshown>0) {
      } else {
        if(numfound>0) {
        } else {
          CE.CEDBG.println('CEU: No Nav Item "'+tabpfx+match+'" not showable!');
        }
        // we need to bring the "hidden" frame back to the surface!
        if(ihidit) {
          CE.CEDBG.println('CEU: Returning to old tab "'+ihidit+'" because show failed!');
          sqh_show(tabobjs, tabpfx, panepfx, ihidit, null);
        }
      }
    }
    if(except) {
      throw except;
    }
    return false;
  };
  
  this.getAbsPos = function(el) {
    if(el.getBoundingClientRect) {
      var rect = el.getBoundingClientRect();
      //CE.CEDBG.println('CEU.getAbsPos(getBoundingClientRect) y='+rect.top+' scrollY='+window.pageYOffset);
      return {x: rect.left, y: rect.top};
    } else {
      var bo = document.getBoxObjectFor(el);
      //CE.CEDBG.println('CEU.getAbsPos(getBoxObjectFor) y='+bo.y+' scrollY='+window.pageYOffset);
      return {x: bo.x, y: bo.y};
    }
  };

  this.init = function() {
    CE.CEU.user = null;

    if(!CE.CEU.svc) {
      CE.CEU.svc = new CE.CERPC();
    }
      var l = that.getSearchParam("locale");
      if(l) {
          l = that.getLocale(l);
          if(l) {
              that.switchToLocale(l);
          }
      }
  };

  this.getSearchParam = function(name, decode, hash) {
    var ret = null;
    if((hash && window.location.hash) || window.location.search) {
        var ts = hash?window.location.hash:window.location.search;
        var idx = ts.indexOf((hash?"#":"?") + name + "=");
        if(idx == -1) {
            idx = ts.indexOf("&" + name + "=");
        }
        if(idx != -1) {
            ret = ts.slice(idx + name.length + 2);
            idx = ret.indexOf("&");
            if(idx != -1) {
                ret = ret.slice(0, idx);
            }
        }
    }
    if(ret && decode)
        return decodeURIComponent(ret);
    else
        return ret;
  };

  this.logoutUser = function(data) {
      CE.CEDBG.println("CEU: Logging out user");
      CE.CEU.user = null;
      // Calcuate an expiration date -1 days in the future...
      var date = new Date();

      var domain = "";
      var dparts = window.location.host.split(".");
      if(dparts.length == 3) {
          domain = ";domain=" + "." + dparts[1] + "." + dparts[2];
      }
      date.setTime(date.getTime()+((-1)*24*60*60*1000));
      var ck = 'valtoken=;expires='+date.toGMTString() + ';path=/';
      CE.CEDBG.println("CEU: Logout: -- Cookie set for: "+ck);
      document.cookie = ck;
      if(domain) {
          ck = 'valtoken=;expires='+date.toGMTString()+domain+';path=/';
          document.cookie = ck;
      }
          
    if(data) {
      if(data[0]) {
        var usernameel = CE.$(data[0]);
        if(usernameel) {
          CE.rac(usernameel);
        }
        usernameel = null;
      }
      if(data[1]) {
        var useropsel = CE.$(data[1]);
        if(useropsel) {
          CE.rac(useropsel);
        }
        useropsel = null;
      }
      if(typeof(data[2])=='function') {
        data[2]('logout');
      } else if(data[2]) {
        window.location.replace(data[2]);
      }
    }
  };
  
  this.isExperimental = function() {
    return (CE.CEU.user && CE.CEU.user.flags && CE.CEU.user.flags.indexOf("experimental") != -1);
  };

  this.getUser = function(usernamedomel, useropsdomel, require) {
    CE.CEU.svc.getUser(onGetUserSuccess, onGetUserFailure, [usernamedomel, useropsdomel, require]);
  };

  this.loginUser = function(email, pw, errordomel, cb, remember) {
    var e;
    var p;
    var el = CE.$(errordomel);
    if(el) {
      CE.rac(el);
    }
    el = null;
    e = getval(email);
    p = getval(pw);
    if(!e || e=="" || !p || p=="") {
      emsg(errordomel, 'ceu.reqarg');
    } else if(!that.validateEmail(e)) {
      emsg(errordomel, 'ceu.invalidemail');
    } else {
      CE.CEU.svc.loginUser(e, p, onLoginUserSuccess, onLoginUserFailure, [errordomel, cb, remember]);
    }
  };

  this.forgotPassword = function(email, errordomel, cb) {
    var e;
    var el = CE.$(errordomel);
    if(el) {
      CE.rac(el);
    }
    el = null;
    e = getval(email);
    if(!e || e=="") {
      emsg(errordomel, 'ceu.reqarg');
    } else if(!that.validateEmail(e)) {
      emsg(errordomel, 'ceu.invalidemail');
    } else {
      CE.CEU.svc.asyncRPC("POST", "initiatePasswordReset", [ "email", e ], onForgotPasswordSuccess, 
                          onForgotPasswordFailure, [errordomel, cb]);
    }                      
  };

  function onUpdatePasswordFailure(r, data) {
      var reason = null;
      if(r && r['HB-EXCEPTION']) {
          reason = r['HB-EXCEPTION'];
      }
      if(reason && reason.ecode && reason.ecode == "606") {
          if(data[0]) {
              var a = document.createElement("a");
              a.innerHTML = CE.STRTAB.lookup("ceu.clickhere");
              a.href = 'forgot.html';
              emsg(data[0], "ceu.reseterror", a);
          } 
      } else {
          onLoginUserFailure(r, data);
      }
      if(data.cbf)
          data.cbf();
  }

  this.updatePassword = function(pwtoken, shtoken, pw1, pw2, errordomel, cb, cbf) {
    var e;
    var p;
    var p2;
    var el = CE.$(errordomel);
    if(el) {
      CE.rac(el);
    }
    el = null;
    p = getval(pw1);
    p2 = getval(pw2);
    if(((!pwtoken || pwtoken=="") && (!shtoken || shtoken=="")) || !p || p=="") {
      emsg(errordomel, 'ceu.reqarg');
      if(cbf) cbf();
    } else if(p!=p2) {
      emsg(errordomel, 'ceu.pwmatch');
      if(cbf) cbf();
    } else {
      // hijack the loginUser callbacks because we want to do exactly the same thing. 
        var args = [];
        
        if(pwtoken) {
            args.push("passtoken");
            args.push(pwtoken);
        } else if(shtoken) {
            args.push("sharetoken");
            args.push(shtoken);
        }
        args.push("password");
        args.push(p);

        CE.CEU.svc.asyncRPC("POST", "updatePassword", args,
                            onLoginUserSuccess, onUpdatePasswordFailure, [errordomel, cb, cbf]);
    }
  };

  this.getLocale = function(localeid) {
      if(!localeid || !CE.LOCALES)
          return null;
      for(var i = 0; i < CE.LOCALES.length; i++) {
          if(CE.LOCALES[i].id == localeid) {
              return CE.LOCALES[i];
          }
      }
      return null;
  }

  function switchToLocale(locale) {

      if(locale == null) return;

      var a = CE.CEU.$("celocaledisplay");
      if(a) {
          CE.rac(a);
          a.appendChild(CE.dctn(locale.name));
          if(locale.beta && eval(locale.beta)) {
              var beta = CE.dce('span', null, 'cebetatag');
              beta.appendChild(CE.dctn(' beta'));
              a.appendChild(beta)
          }
      }
      locale = locale.path;

      var pa = window.location.pathname.split("/");
      var pn = "";

      if(pa.length > 2) {
          if(pa[1] == locale) {
              return;
          }
          for(var i = 2; i < pa.length; i++) {
              pn += "/" + pa[i];
          }
      } else if(locale == "") {
          return;
      } else if(pa.length > 1) {
          for(var i = 1; i < pa.length; i++) {
              pn += "/" + pa[i];
          }
      }
      if(locale != "") {
          pn = "/" + locale + pn;
      }
      window.location.replace(pn+window.location.search);      
  }

  this.switchToLocale = function(l) { return switchToLocale(l); }
                      
  function onUpdateUserLocaleSuccess(r, l) {
      updateLocaleCookie(l.id);
      switchToLocale(l);
  }

  function updateLocaleCookie(locale) {
      var date = new Date();
      if(locale) {
          date.setTime(date.getTime()+(14*24*60*60*1000));
          ma = ";expires="+date.toGMTString();
          var ck = "locale="+locale+ma+";path=/";
          document.cookie = ck;
      } else {
          date.setTime(date.getTime()+((-1)*24*60*60*1000));
          var ck = 'locale=;expires='+date.toGMTString() + ';path=/';
          document.cookie = ck;
      }
  }

  function updateUserLocale(ev) {
      if(that.getCookie("locale")==ev.locale.id) {
          if(ev.dialog) {
              ev.dialog.hide();
              ev.dialog = null;
          }
          return false;
      }

      if(that.getValtoken()) {
          if(ev && ev.locale) {
              CE.CEU.svc.asyncRPC("POST", "updateUser", [ "locale", ev.locale.id ], onUpdateUserLocaleSuccess, null, ev.locale);
          }
      } else {
          updateLocaleCookie(ev.locale.id);
          switchToLocale(ev.locale);          
      }
      return false;
  }

  this.populateLocaleList = function(elid, clsname, showcur, dlg, numPerLine) {
      var curLocale = that.getCookie("locale");
      if(!curLocale)
            curLocale = '';
      var el = CE.CEU.$(elid);
      if(el) {
          var num = 0;
          for(var i = 0; i < CE.LOCALES.length; i++) {
              if(CE.LOCALES[i].name && (CE.LOCALES[i].experimental != '1' || CE.CEU.isExperimental())) {
                  var useClass = clsname;
                  if(showcur && ((curLocale == CE.LOCALES[i].id) || (curLocale == '' && i == 0)) )
                        useClass += ' ' + 'bold';
                  var spn = CE.dce('span', null, useClass);
                  var a = CE.dca({onEvent: updateUserLocale, locale: CE.LOCALES[i], dialog: dlg } );
                  a.appendChild(CE.dctn(CE.LOCALES[i].name));
                  spn.appendChild(a)
                  if(CE.LOCALES[i].beta && eval(CE.LOCALES[i].beta)) {
                      var beta = CE.dce('span', null, 'cebetatag');
                      beta.appendChild(CE.dctn(' beta'));
                      spn.appendChild(beta)
                  }
                  if(numPerLine && num && (num%numPerLine == 0)) {
                      el.appendChild(CE.dce('br'));
                  }
                  ++num;

                  el.appendChild(spn);
              }
          }
      }
  }

  this.checkUserLocale = function(localeid) {
      var l = that.getLocale(localeid);
      if(l) {
          updateLocaleCookie(localeid);
          switchToLocale(l);
      }
  }      

  this.ensureCurrentLocale = function() {
      var localeid = that.getCookie("locale");
      if(localeid) {
          var l = that.getLocale(localeid);
          if(l) {
              switchToLocale(l);
          }
      }
  }
                       
  this.newUserDevice = function (email, pw, pw2, devid, errordomel, cb) {
    var e;
    var p;
    var p2;
    var d;
    var el = CE.$(errordomel);
    if(el) {
      CE.rac(el);
    }
    el = null;
    e = getval(email);
    p = getval(pw);
    p2 = getval(pw2);
      d = getval(devid);
      d = d.toUpperCase();
    if(!e || e=="" || !p || p=="" || !p2 || p2=="" || !d || d=="") {
      emsg(errordomel, 'ceu.reqarg');
    } else if(!that.validateEmail(e)) {
      emsg(errordomel, 'ceu.invalidemail');
    } else if(p!=p2) {
      emsg(errordomel, 'ceu.pwmatch');
    } else {
        // TODO: Screen name?
        CE.CEU.svc.asyncRPC('POST', 'createUserDevice', 
                            ['email', e, 'password', p, 'devtoken', d, "domain", window.location.hostname ],
                            onNewUserDeviceSuccess, onNewUserDeviceFailure, [errordomel, cb]);
    }
  }
  
  this.showMessage = function(title, message, cb) {
      var dlg = new CE.CEU.Dialog(title, CE.dctn(message), 
                                  [{name:'ok', label:CE.STRTAB.lookup('button.ok'), callback:cb}],
                                  null, null, null, null, null, cb);
      dlg.show();
  };
  
  this.promptYesNo = function(title, message, cbYes, cbNo) {
      var dlg = new CE.CEU.Dialog(title, (typeof(message)=='string')?CE.dctn(message):message, 
                                  [{name:'yes', label:CE.STRTAB.lookup('button.yes'), callback:cbYes},
                                   {name:'no',  label:CE.STRTAB.lookup('button.no'),  callback:cbNo}],
                                  null, null, null, null, null, cbNo);
      dlg.show();
  };

  this.showInputDialog = function(title, message, inputcls, inputid, inputtype, btntext, cb, dflt) {
      var root = CE.dce("div");
      var cnt = CE.dce("div");
      var sd = CE.dce("div", "", "label");
  
      root.appendChild(cnt);
      sd.appendChild(CE.dctn(CE.STRTAB.lookup(message)));
      cnt.appendChild(sd);
  
      sd = CE.dce("div", "ceinputdlg_error", "dlgerr2 hidden");
      cnt.appendChild(sd);
  
      sd = CE.dce("div");
      var input = CE.dce("input", inputid, inputcls + " extramarginbottom");
      input.type = inputtype;
      if(dflt) input.value = dflt;
      sd.appendChild(input);
      cnt.appendChild(sd);
  
      var dlg = new CE.CEU.Dialog(CE.STRTAB.lookup(title), root,
          [{name:'reg', label:CE.STRTAB.lookup(btntext), callback: cb}, 
          {name:'cancel', label:CE.STRTAB.lookup('ceui.cancel')}],
          null, false, "widish");
  
      dlg.show();
      CE.$(inputid).focus();
      CE.CEU.attachEnterPressedEvent(CE.$(inputid), function(){if(cb()) dlg.hide();});
  }

  this.showMultiInputDialog = function(title, inputs, btntext, cbOk, cbCancel, noCancel) {
      var root = CE.dce("div");
      var cnt = CE.dce("div");

      var sd = CE.dce("div", "ceinputdlg_error", "dlgerr2 hidden");
      cnt.appendChild(sd);

      for(var i=0; i<inputs.length; ++i) {
          if(inputs[i].inputtype == 'div') {
              cnt.appendChild(inputs[i].div);
          } else {
              sd = CE.dce("div", "", "dlglabel");
              root.appendChild(cnt);
              sd.appendChild(CE.dctn(CE.STRTAB.lookup(inputs[i].message)));
              
              if(inputs[i].twoline) {
                  cnt.appendChild(sd);
                  sd = CE.dce("div");
              }
    
              var input;
              if(inputs[i].inputtype == 'select') {
                  input = CE.dce("select", inputs[i].inputid, inputs[i].inputcls + " extramarginbottom");
              } else {
                  input = CE.dce("input", inputs[i].inputid, inputs[i].inputcls + " extramarginbottom");
                  input.type = inputs[i].inputtype;
              }
              if(inputs[i].dflt) {
                  if(inputs[i].inputtype != 'checkbox') {
                      input.value = inputs[i].dflt;
                  }
              }
              if(inputs[i].disabled) {
                input.disabled = true;
              }
              sd.appendChild(input);
              if(inputs[i].dflt && inputs[i].inputtype == 'checkbox') {
                  if(inputs[i].lnk) {
                      var lnk = CE.dca( {onEvent: function(eh){window.open(eh.lnk); return false;}, lnk:inputs[i].lnk}, "", "extramarginbottom" );
                      lnk.appendChild(CE.dctn(inputs[i].dflt));
                      sd.appendChild(CE.dctn(' '));
                      sd.appendChild(lnk);
                  } else {
                      var spn = CE.dce("span", "", "extramarginbottom");
                      spn.appendChild(CE.dctn(' '+inputs[i].dflt));
                      sd.appendChild(spn);
                  }
              }
              cnt.appendChild(sd);
          }
      }
      
      var buttons = [{name:'reg', label:CE.STRTAB.lookup(btntext), callback: cbOk}];
      if(!noCancel)
        buttons.push({name:'cancel', label:CE.STRTAB.lookup('ceui.cancel'), callback: cbCancel});
  
      var dlg = new CE.CEU.Dialog(CE.STRTAB.lookup(title), root, buttons,
                                  null, noCancel, "widish", null, null, cbCancel);
  
      dlg.show();

      var focused = false;
      for(var i=0; i<inputs.length; ++i) {
          if(inputs[i].inputtype != 'div') {
            CE.CEU.attachEnterPressedEvent(CE.$(inputs[i].inputid), function(){if(cbOk()) dlg.hide();});
            if(!focused || inputs[i].focus) {
                CE.$(inputs[i].inputid).focus();
                focused = true;
            }
          }
      }
      
      return dlg;
  }

    function onChooseFolderSelect(e) {
        if(e.sel.div) {
            var oldSelName = CE.getByClass(e.sel.div, 'div', 'cechoose_foldername')[0];
            CE.rCN(oldSelName, 'selected');
        }
        
        e.sel.svc  = e.svc;
        e.sel.file = e.file;
        e.sel.div  = e.div;
        
        var selName = CE.getByClass(e.sel.div, 'div', 'cechoose_foldername')[0];
        CE.aCN(selName, 'selected');
    }
    
    function appendChooseFolderItem(parentDiv, svc, file, sel, isRoot, select) {
        var folder     = parentDiv.appendChild(CE.dce('div',null,'cechoose_folder'));
        var folderNib  = folder.appendChild(CE.dce('div',null,'cechoose_foldernib'+(isRoot?' hidden':'')));
        var folderName = folder.appendChild(CE.dce('div',null,'cechoose_foldername'+(isRoot?' ceroot':'')));
        folderName.appendChild(CE.dctn(file.name));
        folder.appendChild(CE.dce('div',null,isRoot?'':'cechoose_folderchildren'));
        
        CE.CEU.attachEvent(folderName, 'click', {'svc':svc, 'file':file, div:folder, 'sel':sel, onEvent:onChooseFolderSelect});
        CE.CEU.attachEvent(folderNib,  'click', {'svc':svc, 'file':file, div:folder, 'sel':sel, onEvent:onChooseFolderExpand});
        if(select)
            onChooseFolderSelect({'svc':svc, 'file':file, div:folder, 'sel':sel});
        
        return folder.lastChild;
    }
    
    function appendChooseFolderItems(parentDiv, svc, parentId, sel, cb, selectName) {
        if(parentId == svc.fileid) {
            // Special case for root: first append Root before enumerating
            parentDiv = appendChooseFolderItem(parentDiv, svc, {fileid:parentId, name:CE.STRTAB.lookup('view.sync.root')}, sel, true);
        }
        
        CE.CEU.svc.asyncRPC('POST', 'listFiles', ['deviceid',svc.deviceid, 'serviceid',svc.serviceid,
                            'maxcount',500, 'parentid',parentId, 'sortcrit','+name'],
        function(r) {
            var numChildren = 0;
            if(r.files) {
                for(var i = 0; i < r.files.length; ++i) {
                    (function(i) {
                        if(r.files[i].type == '1'/*dir*/) {
                            ++numChildren;
                            appendChooseFolderItem(parentDiv, svc, r.files[i], sel, false, (selectName==r.files[i].name));
                        }
                    })(i);
                }
            }
            
            if(cb) {
                cb(numChildren);
            }
        },
        function() {
            CE.CEU.showLoadingAni(false);
            CE.CEU.showMessage(CE.STRTAB.lookup('choosefolder.title'), CE.STRTAB.lookup('choosefolder.error'));
        });
    }
    
    function onChooseFolderExpand(e, selectName) {
        CE.CEU.showLoadingAni(true);
        var folderList = e.div.lastChild;
        CE.rac(folderList);

        if(CE.hCN(e.div.firstChild,'expanded')) {
            // Collapse
            CE.rCN(e.div.firstChild, 'expanded');
            CE.CEU.showLoadingAni(false);
        } else {
            // Expand
            appendChooseFolderItems(folderList, e.svc, e.file.fileid, e.sel,
            function(numChildren) {
                if(numChildren == 0) {
                    CE.aCN(e.div.firstChild, 'hidden'); // no children, so hide nib
                } else {
                    CE.aCN(e.div.firstChild, 'expanded');
                }
                CE.CEU.showLoadingAni(false);
            },
            selectName);
        }
    }
    
    function onChooseFolderCreate(e) {
        CE.CEU.showMultiInputDialog("choosefolder.newfolder.title",
                                    [{message:"choosefolder.newfolder.msg", inputcls:"std-field-wide", inputid:"foldername", inputtype:"text", twoline:true}],
                                    "button.ok", function() {
            // Make sure the name they entered is valid
            var name = CE.CEU.stripBadFnChars(CE.CEU.$('foldername').value);
            if(name.length < 1) {
                CE.rCN('ceinputdlg_error', 'hidden');
                CE.rac(CE.CEU.$('ceinputdlg_error'));
                CE.CEU.$('ceinputdlg_error').appendChild(CE.STRTAB.lookupel('view.invaliddirname'));
                return false;
            }
            
            // Create the new folder
            CE.CEU.showLoadingAni(true);
            var args = ['deviceid',  e.sel.svc.deviceid,
                        'serviceid', e.sel.svc.serviceid,
                        'parentid',  e.sel.file ? e.sel.file.fileid : e.sel.svc.fileid,
                        'type',      '1',
                        'filename',  name];
            CE.CEU.svc.asyncRPC('POST', 'createFile', args,
            function(r) {
                // Success; refresh the listing
                if(e.sel.file && e.sel.svc && e.sel.file.fileid != e.sel.svc.fileid) {
                    CE.rCN(e.sel.div.firstChild, 'hidden');
                    onChooseFolderExpand({'svc':e.sel.svc, 'file':e.sel.file, div:e.sel.div, 'sel':e.sel}, name);
                } else {
                    CE.rac(e.sel.rootdiv);
                    appendChooseFolderItems(e.sel.rootdiv, e.sel.svc, e.sel.svc.fileid, e.sel,
                    function() {
                        CE.CEU.showLoadingAni(false);
                    },
                    name);
                }
            },
            function() {
                // Error creating folder
                CE.CEU.showLoadingAni(false);
                CE.CEU.showMessage(CE.STRTAB.lookup('view.failcreate'), CE.STRTAB.lookup('view.errorcreatingdir'));
            });
            
            return true;
        });
    }
    
    this.showChooseFolderDialog = function(svcmap, sharemap, okCb, cancelCb, disallowedParent) {
        var selItem = {'svc':null, 'file':null, 'div':null, 'rootdiv':null};

        var onOk = function() {
            if(selItem.file) {
                if(!disallowedParent) {
                    return okCb(selItem);
                } else {
                    // Ensure the selected folder isn't a descendant (or the same as) disallowedParent
                    CE.CEU.isDescendant(disallowedParent, selItem,
                    function() { //yes
                        CE.CEU.showMessage(CE.STRTAB.lookup('choosefolder.descendanterr.title'), CE.STRTAB.lookup('choosefolder.descendanterr.msg'));
                    },
                    function() { //no
                        dlg.hide();
                        okCb(selItem);
                    });
                    return false;
                }
            } else if(cancelCb) {
                return cancelCb();
            } else {
                return true;
            }
        };

        var pane = CE.dce('div');
        var dlg  = new CE.CEU.Dialog(CE.STRTAB.lookup('choosefolder.title'), pane, 
                                    [{name:'ok',label:CE.STRTAB.lookup('button.ok'),callback:onOk},
                                     {name:'cancel',label:CE.STRTAB.lookup('button.cancel'),callback:cancelCb}],
                                    null, null, 'CEUDialog_narrow1', null, null, cancelCb);
        var newFolderBut = pane.appendChild(CE.dce('div',null,'cechoose_newfolder'));
        newFolderBut.title = CE.STRTAB.lookup('choosefolder.newfolder.hint');
        CE.CEU.attachEvent(newFolderBut, 'click', {onEvent:onChooseFolderCreate, sel:selItem});
        var svcList = pane.appendChild(CE.dce('select',null,'cechoose_svclist'));
        var svcs    = {};
        for(var s in svcmap) {
            if(svcmap[s].online == '1' && svcmap[s].type.indexOf(':printer') == -1) {
                svcs[s] = CE.CEU.shallowCopy(svcmap[s]);
                svcs[s].fileid = '0';
                var svcOpt = svcList.appendChild(CE.dce('option'));
                svcOpt.value = s;
                svcOpt.appendChild(CE.dctn(svcs[s].name));
            }
        }
        if(sharemap) {
            for(var s in sharemap) {
                if(sharemap[s].perms != 0 && sharemap[s].root && (!sharemap[s].albumtype || sharemap[s].albumtype == '0' || sharemap[s].albumtype == '1')) {
                    svcs[s] = CE.CEU.shallowCopy(sharemap[s].root);
                    var svcOpt = svcList.appendChild(CE.dce('option'));
                    svcOpt.value = s;
                    svcOpt.appendChild(CE.dctn(CE.STRTAB.lookup('choosefolder.sharedprefix')+svcs[s].name));
                }
            }
        }
        var folderList = pane.appendChild(CE.dce('div',null,'cechoose_folderlist'));
        selItem.rootdiv = folderList;
        
        var onSelectService = function() {
            selItem.file = null;
            selItem.div  = null;
            CE.rac(folderList);
            var svc = svcs[ svcList.options[svcList.selectedIndex].value ];
            selItem.svc  = svc;
            CE.CEU.showLoadingAni(true);
            appendChooseFolderItems(folderList, svc, svc.fileid, selItem,
            function() {
                CE.CEU.showLoadingAni(false);
            });
        };
        CE.CEU.attachEvent(svcList, 'change', {onEvent:onSelectService});
        
        if(svcList.options.length > 0) {
            svcList.selectedIndex = 0;
            onSelectService();
        }
        
        dlg.show();
    }

    this.showChooseFolder2Dialog = function(svcmap1, svcmap2, sharemap1, sharemap2, dlgTitle, labels, allowChildDest, okCb, cancelCb) {
        var svcmaps   = [svcmap1,   svcmap2];
        var sharemaps = [sharemap1, sharemap2];
        var selItems  = [];
        selItems[0] = {'svc':null, 'file':null, 'div':null, 'rootdiv':null};
        selItems[1] = {'svc':null, 'file':null, 'div':null, 'rootdiv':null};
        
        var onOk = function() {
            if(selItems[0].file && selItems[1].file) {
                if(allowChildDest) {
                    return okCb(selItems[0], selItems[1]);
                } else {
                    // Ensure folder1 isn't a descendant (or the same as) folder0
                    CE.CEU.isDescendant(selItems[0], selItems[1],
                    function() { //yes
                        CE.CEU.showMessage(CE.STRTAB.lookup('choosefolder.descendanterr.title'), CE.STRTAB.lookup('choosefolder.descendanterr.msg'));
                    },
                    function() { //no
                        dlg.hide();
                        okCb(selItems[0], selItems[1]);
                    });
                    return false;
                }
            } else if(cancelCb) {
                return cancelCb();
            } else {
                return true;
            }
        };
        
        var dlgPane = CE.dce('div');
        var dlg  = new CE.CEU.Dialog(dlgTitle, dlgPane, 
                                    [{name:'ok',label:CE.STRTAB.lookup('button.ok'),callback:onOk},
                                     {name:'cancel',label:CE.STRTAB.lookup('button.cancel'),callback:cancelCb}],
                                    null, null, 'CEUDialog_narrow1double', null, null, cancelCb);
        
        for(var i = 0; i < 2; ++i) {
            (function(i) {
                if(i != 0)
                    dlgPane.appendChild(CE.dce('div',null,'CEUDialog_page CEUDialog_page_arrow'));

                var selItem = selItems[i];
                var pane = dlgPane.appendChild(CE.dce('div',null,'CEUDialog_page'));
                
                (pane.appendChild(CE.dce('div',null,'bold extramarginbottom'))).appendChild(CE.dctn(labels[i]));
                
                var newFolderBut = pane.appendChild(CE.dce('div',null,'cechoose_newfolder'));
                newFolderBut.title = CE.STRTAB.lookup('choosefolder.newfolder.hint');
                CE.CEU.attachEvent(newFolderBut, 'click', {onEvent:onChooseFolderCreate, sel:selItem});

                var svcList = pane.appendChild(CE.dce('select',null,'cechoose_svclist'));
                var svcs    = {};
                for(var s in svcmaps[i]) {
                    if(svcmaps[i][s].online == '1' && svcmaps[i][s].type.indexOf(':printer') == -1) {
                        svcs[s] = CE.CEU.shallowCopy(svcmaps[i][s]);
                        svcs[s].fileid = '0';
                        var svcOpt = svcList.appendChild(CE.dce('option'));
                        svcOpt.value = s;
                        svcOpt.appendChild(CE.dctn(svcs[s].name));
                    }
                }
                if(sharemaps[i]) {
                    for(var s in sharemaps[i]) {
                        if(sharemaps[i][s].perms != 0 && sharemaps[i][s].root && (!sharemaps[i][s].albumtype || sharemaps[i][s].albumtype == '0' || sharemaps[i][s].albumtype == '1')) {
                            svcs[s] = CE.CEU.shallowCopy(sharemaps[i][s].root);
                            var svcOpt = svcList.appendChild(CE.dce('option'));
                            svcOpt.value = s;
                            svcOpt.appendChild(CE.dctn(CE.STRTAB.lookup('choosefolder.sharedprefix')+svcs[s].name));
                        }
                    }
                }
                var folderList = pane.appendChild(CE.dce('div',null,'cechoose_folderlist'));
                selItem.rootdiv = folderList;
                
                var onSelectService = function() {
                    selItem.file = null;
                    selItem.div  = null;
                    CE.rac(folderList);
                    var svc = svcs[ svcList.options[svcList.selectedIndex].value ];
                    selItem.svc  = svc;
                    CE.CEU.showLoadingAni(true);
                    appendChooseFolderItems(folderList, svc, svc.fileid, selItem,
                    function() {
                        CE.CEU.showLoadingAni(false);
                    });
                };
                CE.CEU.attachEvent(svcList, 'change', {onEvent:onSelectService});
                
                if(svcList.options.length > 0) {
                    svcList.selectedIndex = 0;
                    onSelectService();
                }
            })(i);
        }
        
        dlg.show();
    }

  this.isDescendant = function(parent, child, cbYes, cbNo) {
    if(parent.svc.serviceid != child.svc.serviceid || parent.svc.deviceid != child.svc.deviceid)
        return cbNo();
    if(parent.file.fileid == child.file.fileid || parent.file.fileid == '0')
        return cbYes();
    if(child.file.fileid == '0')
        return cbNo();
    
    var getParent = function(parentId) {
        var args = ['deviceid',child.svc.deviceid, 'serviceid',child.svc.serviceid, 'fileid',parentId];
        
        CE.CEU.svc.asyncRPC('POST', 'getFile', args,
        function(r) {
            if(r.file.fileid == parent.file.fileid)
                cbYes();
            else if(r.file.fileid == '0')
                cbNo();
            else
                getParent(r.file.parentid);
        },
        function() {
            cbNo();
        });
    };
    
    getParent(child.file.parentid);
  };
  
  this.showLoadingAni = function(show) {
      if(show) {
          CE.rCN('celoading',         'hidden');
          CE.rCN('celoadingfg',       'hidden');
          CE.rCN('cetoolbar_loading', 'hidden');
          CE.rCN('cehead_loading',    'hidden');
      } else {
          CE.aCN('celoading',         'hidden');
          CE.aCN('celoadingfg',       'hidden');
          CE.aCN('cetoolbar_loading', 'hidden');
          CE.aCN('cehead_loading',    'hidden');
      }
  }

  this.xmlEncode = function(text)
  {
      var result = text;
        
      var amp = /&/gi;
      var gt = />/gi;
      var lt = /</gi;
      var quot = /"/gi;
      var apos = /'/gi;

      var helpemacs = /"/gi;

      var xml_gt = "&#62;";
      var xml_lt = "&#38;#60;";
      var xml_amp = "&#38;#38;";
      var xml_quot = "&#34;";
      var xml_apos = "&#39;";
        
      result = result.replace(amp, xml_amp);
      result = result.replace(quot, xml_quot);
      result = result.replace(lt, xml_lt);
      result = result.replace(gt, xml_gt);
      result = result.replace(apos, xml_apos);
        
      return result;
  }

  this.getFlashMovieElement = function(movieName) {
      if (navigator.appName.indexOf("Microsoft") != -1) {
          return window[movieName];
      } else {
          return document[movieName];
      }
  };

  this.getFlashVersion = function() {
      var version = [ 0, 0, 0 ];
      
      if(navigator.plugins && navigator.mimeTypes.length){
          var x = navigator.plugins["Shockwave Flash"];
          if(x && x.description) {
              version = x.description.replace(/([a-zA-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".").split(".")
          }
          // 	    } else if (navigator.userAgent && navigator.userAgent.indexOf("Windows CE") >= 0){ // if Windows CE
          // 		var axo = 1;
          // 		var counter = 3;
          // 		while(axo) {
          // 		    try {
          // 			counter++;
          // 			axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+ counter);
          //                         version = [ counter, 0, 0 ];
          // 		    } catch (e) {
          // 			axo = null;
          // 		    }
          // 		}
          //                 axo = null;
      } else {
          var axo = null;
          try {
              axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
          } catch(e) {
              try {
                  axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");
                  version = [6, 0, 21];
                  axo.AllowScriptAccess = "always";
              } catch(e) {
                  if (version[0] == 6) {
                      axo = null;
                      return version;
                  }
              }
              // 		    try {
              // 			axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
              // 		    } catch(e) {}
          }
          if (axo != null) {
              version = axo.GetVariable("$version").split(" ")[1].split(",");
          }
          axo = null;
      }
      return version;
  }

  this.getFlashUpgradeHTML = function() {
      var div = CE.dce('div');
      div.className = 'flashupgrade';
      
      var a = CE.dce('a');
      a.className = 'sq';
      a.href = CE.STRTAB.lookup('flashurl'); 
      a.appendChild(CE.STRTAB.lookupel('getithere'));
      div.appendChild(CE.STRTAB.lookupel('flashupgrade', a));
      return div;
  }

  this.padInt = function(n, len) {
      s = n.toString();
      if(s.length < len) {
          s = ('0000000000' + n.toString()).slice(-len);
      }
      return s;
  }

  this.stripBadFnChars = function(badName) {
    var str = badName;
    if( !str )
        str = '';
    
    str = str.replace(/^\s*(.*?)\s*$/,"$1");
    str = str.replace(/\"/g, "");
    str = str.replace(/\\/g, "");
    str = str.replace(/\//g, "");
    str = str.replace(/\:/g, "");
    
    // Filenames of the form COMn or LPTn are not allowed
    if(str.length == 4) {
        var pref = str.substr(0, 3);
        if(pref == 'COM' || pref == 'LPT') {
            var num = str.substr(3, 1);
            for(var i = 0; i <= 9; ++i) {
                if(num == i) {
                    str = str + '-';
                    break;
                }
            }
        }
    }
    
    return str;
  };

  this.onShowInlineHint = function(evt, strid, str1) {
    if(!evt)        evt        = window.event;
    if(!evt.target) evt.target = evt.srcElement;
    CE.rac(evt.target);
    evt.target.style.zIndex = 500;
    
    var div = CE.dce('div');
    div.innerHTML = CE.STRTAB.lookup(strid, str1);
    var hintDiv = evt.target.appendChild(CE.dce('div',null,'cehintbubble'));
    hintDiv.appendChild(div);
    
    // Make sure the tip does not go outside the viewport
    var pos = CE.CEU.getAbsPos(hintDiv);
    var viewHeight = window.innerHeight || document.documentElement.clientHeight;
    if((pos.y + hintDiv.offsetHeight) > viewHeight) {
        var top = hintDiv.offsetTop;
        top -= ((pos.y + hintDiv.offsetHeight) - viewHeight);
        hintDiv.style.top = top + 'px';
    }
    var viewWidth = window.innerWidth || document.documentElement.clientWidth;
    if((pos.x + hintDiv.offsetWidth) > viewWidth) {
        hintDiv.style.left = '-' + (hintDiv.offsetWidth+8) + 'px';
    }
  };
  this.onHideInlineHint = function(evt) {
    if(!evt)        evt        = window.event;
    if(!evt.target) evt.target = evt.srcElement;
    CE.rac(evt.target);
    evt.target.style.zIndex = '';
  };

  this.showDlHint = function() {
    var locale = CE.CEU.getCookie('locale');
    var url    = 'http://download.pogoplug.com/downloads/index.html?bare=1&ext=1';
    if(locale && locale != '') {
        url += '&lang=' + locale;
    }
    
    var content = CE.dce('div', null, 'cefulliframe');
    var iframe = content.appendChild(CE.dce('iframe',null,'cefulliframe'));
    iframe.setAttribute('frameBorder', '0');
    iframe.allowTransparency = 'true';
    iframe.src = url;
    var dlg = new CE.CEU.Dialog(CE.STRTAB.lookup('view.downloads.title'), content, 
                                [{name:'close', label:CE.STRTAB.lookup('view.close'), callback:null}],
                                null, null, 'CEUDialog_hint2', null, null, null);
    dlg.show();
  };
  
  this.structlen = function(obj) {
    var len = 0;
    if(obj) {
        for(var k in obj) {
            ++len;
        }
    }
    return len;
  };
  this.structhead = function(obj) {
    if(obj) {
        for(var k in obj) {
            return obj[k];
        }
    }
    return null;
  };

  this.CLOUD_TYPES = [{name:'facebook', type:'xce:plugfs:cloud:facebook'},
                      {name:'google',   type:'xce:plugfs:cloud:gdocs'}];



  this.RegisterDeviceDialog = function(cb) {
      var that = this;
      var m_pane = CE.dce('div');

      var m_pendingsearch = false;

      var m_device = null;
      var m_dlg = new CE.CEU.Dialog(CE.STRTAB.lookup("ces.register.title"), m_pane, 
          [{name:'cancel', label:CE.STRTAB.lookup('ceui.cancel')}], null, true);

      function onDeviceRegisterComplete() {
          CE.rac(m_pane);

          m_pane.appendChild(CE.STRTAB.lookupel("settings.register.success"));

          m_dlg.removeButton('reg');
          m_dlg.removeButton('cancel');

          m_dlg.addButton('ok', CE.STRTAB.lookup('ceui.ok'), function() {
            if(cb) cb();
            return true;
          });
      }

      function onDeviceRegisterSuccess(r, d) {
          // Check if a firmware upgrade is available for this pogoplug
          CE.CEU.svc.asyncRPC('POST', 'checkUpgrade', ['deviceid',d.deviceid], function(r,d){
              if(r.available == '1') {
                  // New firmware available
                  CE.rac(m_pane);
                  m_pane.appendChild(CE.STRTAB.lookupel('activate.upgrading'));
                  m_pane.appendChild(CE.dce('br'));
                  m_pane.appendChild(CE.STRTAB.lookupel('activate.takeaminute'));
                  var aniPane = CE.dce('div', null, 'ceaniwait');
                  m_pane.appendChild(aniPane);
                  aniPane.appendChild(CE.dci(CE.STRTAB.lookup('imgbase')+'loading.gif'));
                  m_dlg.removeButton('cancel');
                  // Install the upgrade firmware now
                  CE.CEU.svc.asyncRPC('POST', 'checkUpgrade', ['deviceid',d.deviceid,'perform','1'],
                  function(r, d){
                      var devId = d.deviceid;
                      // Wait until the upgraded device is available again
                      var waitTillDeviceVisible = function() {
                          setTimeout(function() {
                              CE.CEU.svc.asyncRPC("POST", "getDevice", ['deviceid',devId], function(r){
                                  if(r.device) {
                                      onDeviceRegisterComplete();
                                  } else {
                                      waitTillDeviceVisible();
                                  }
                              }, waitTillDeviceVisible);
                          }, 5000);
                      };
                      waitTillDeviceVisible();
                  }, onDeviceRegisterComplete, d);          
              } else {
                  onDeviceRegisterComplete();
              }
          }, onDeviceRegisterComplete, d);          
      }

      function assembleDevId() {
          var devid = "", l = null;
          
          var oneinput = CE.CEU.$("devid");
          if(oneinput) {
              return oneinput.value.toUpperCase();
          }
          for(var i = 1; i < 6; i++) {
              l = document.getElementById("devid"+i);
              if(l.value.length < l.maxLength) {
                  l.style.backgroundColor = 'red';
              } else {
                  l.style.backgroundColor = '';
              }
              devid += l.value.toUpperCase();
          }
          return devid;
      }

      function onDeviceRegisterFailure(r, data, method) {
          var reason = null;
          if(r && r['HB-EXCEPTION']) {
              reason = r['HB-EXCEPTION'];
          }
          if(reason) {
              if(reason.ecode == 801) {
                  var pane = m_pane;
                  
                  CE.rac(pane);
                  
                  //var title = CE.dce("div", null, "dlgtitle");
                  //title.appendChild(CE.STRTAB.lookupel('settings.notfound.title'));
                  //pane.appendChild(title);
                  
                  var bdy = CE.dce("div", null, "dlgmsg");
                  pane.appendChild(bdy);
                  
                  var msg = CE.dce("div", null, "sec");
                  msg.appendChild(CE.STRTAB.lookupel('settings.notfound.devid'));
                  bdy.appendChild(msg);
                  
                  msg = CE.dce("span", null, "cedevconfirm sec");
                  var t = data.deviceid.substr(0, 6) + '-' +
                      data.deviceid.substr(6, 6) + '-' +
                      data.deviceid.substr(12, 2) + '-' +
                      data.deviceid.substr(14, 6) + '-' +
                      data.deviceid.substr(20, 6);
                  msg.appendChild(document.createTextNode(t));
                  bdy.appendChild(msg);
                  
                  var blts = CE.dce("div", null, "dlgmsg");
                  var ul = CE.dce("ul", null, "ceul");
                  
                  var li = CE.dce("li", null, "bullet");
                  li.appendChild(CE.STRTAB.lookupel('settings.notfound.power'));
                  ul.appendChild(li);
                  
                  li = CE.dce("li", null, "bullet");
                  li.appendChild(CE.STRTAB.lookupel('settings.notfound.connected'));
                  ul.appendChild(li);
                  
                  li = CE.dce("li", null, "bullet");
                  li.appendChild(CE.STRTAB.lookupel('settings.notfound.dhcp'));
                  ul.appendChild(li);

                  li = CE.dce("li", null, "bullet");
                  li.appendChild(CE.STRTAB.lookupel('settings.notfound.udp'));
                  ul.appendChild(li);

                  blts.appendChild(ul);
                  bdy.appendChild(blts);

                  var sec = CE.dce("div", null, "sec");
                  var p = CE.dce("p");
                  p.appendChild(CE.STRTAB.lookupel('settings.notfound.unplug'));

                  sec.appendChild(p);
                  blts.appendChild(sec)

                  var p = CE.dce("p");
                  p.appendChild(CE.STRTAB.lookupel('settings.notfound.probcont'));
                  
                  var a = CE.dce('a');
                  a.href = CE.STRTAB.lookup("supporturl");
                  a.target = "_blank";
                  a.appendChild(CE.STRTAB.lookupel('view.clickspace'));
                  p.appendChild(a);
                  p.appendChild(CE.STRTAB.lookupel('view.problems.contact'));
                  
                  sec.appendChild(p);
                  
              } else if(reason.ecode == 806) {

                  var pane = m_pane;

                  CE.rac(pane);

                  var title = CE.dce("div", null, "dlgtitle");
                  
                  title.appendChild(CE.STRTAB.lookupel('settings.error.already'));

                  pane.appendChild(title);
                  var msg = CE.dce("div", null, "dlgmsg");
                  var p = CE.dce("p");

                  p.appendChild(CE.STRTAB.lookupel('settings.error.correct'));
                  msg.appendChild(p);

                  p = CE.dce("p");
                  p.appendChild(CE.STRTAB.lookupel("settings.error.addlhelp"));

                  var a = CE.dce("a");
                  a.href=CE.STRTAB.lookup('supporturl');
                  a.target="_blank";
                  a.appendChild(CE.STRTAB.lookupel('view.clickspace')); 
                  p.appendChild(a);
                  p.appendChild(CE.STRTAB.lookupel('view.problems.contact'));
                  msg.appendChild(p);
                  pane.appendChild(msg);
              }
          }
      }

      function onAddDeviceRegClick() {
          var devid = null;
          m_dlg.removeButton('reg');

          if(m_device) {
              devid = m_device.deviceid;
          } else {
              devid = assembleDevId();
          }
          CE.CEU.svc.asyncRPC("POST", "registerDevice", [ "devtoken", devid ],
                              onDeviceRegisterSuccess, onDeviceRegisterFailure, { deviceid: devid });
          //CE.CEU.releaseAllEvents("iddiv", 'click', true);
      }

      function checkDevId(eh) {
          var l = document.getElementById(eh.devid);
          if(l && l.value.length != l.maxLength) {
              l.style.backgroundColor = 'red';
          } else {
              l.style.backgroundColor = "";
          }
      }

      function skipTo(eh) {
          var l = document.getElementById(eh.devid1);
          if(l && l.value) {
              if(l.value.length == l.maxLength) {
                  l = document.getElementById(eh.devid2);
                  if(l) {
                      l.focus();
                  }
              }
          }
      }
  
      function addDeviceIDInputs(iddivname) {
          var iddiv = CE.CEU.$(iddivname);
          var input = CE.dce("input", "devid1", "dev6");
          input.type = "text";
          input.maxLength = "6";
          CE.CEU.attachEvent(input, "blur", { onEvent: checkDevId, devid: 'devid1'});
          CE.CEU.attachEvent(input, "keyup",{ onEvent: skipTo, devid1: 'devid1', devid2: 'devid2'});
          
          iddiv.appendChild(input);
          iddiv.appendChild(CE.dctn(" - "));

          input = CE.dce("input", "devid2", "dev6");
          input.type = "text";
          input.maxLength = "6";
          CE.CEU.attachEvent(input, "blur", { onEvent: checkDevId, devid: 'devid2'});
          CE.CEU.attachEvent(input, "keyup",{ onEvent: skipTo, devid1: 'devid2', devid2: 'devid3'});
          
          iddiv.appendChild(input);
          iddiv.appendChild(CE.dctn(" - "));
          
          input = CE.dce("input", "devid3", "dev2");
          input.type = "text";
          input.maxLength = "2";
          CE.CEU.attachEvent(input, "blur",  { onEvent: checkDevId, devid: 'devid3'});
          CE.CEU.attachEvent(input, "keyup", { onEvent: skipTo, devid1: 'devid3', devid2: 'devid4'});
          
          iddiv.appendChild(input);
          iddiv.appendChild(CE.dctn(" - "));
          
          input = CE.dce("input", "devid4", "dev6");
          input.type = "text";
          input.maxLength = "6";
          CE.CEU.attachEvent(input, "blur",  { onEvent: checkDevId, devid: 'devid4'});
          CE.CEU.attachEvent(input, "keyup", { onEvent: skipTo, devid1: 'devid4', devid2: 'devid5'});

          iddiv.appendChild(input);
          iddiv.appendChild(CE.dctn(" - "));

          input = CE.dce("input", "devid5", "dev6");
          input.type = "text";
          input.maxLength = "6";
          CE.CEU.attachEvent(input, "blur",  { onEvent: checkDevId, devid: 'devid5'});

          iddiv.appendChild(input);
      }

      function toggleDeviceIDEnterFormat() {
          var ip = CE.CEU.$("devid");
          
          CE.CEU.releaseAllEvents("iddiv", 'click', true);
          if(ip) {
              addDeviceIDInputs("iddiv");
          } else {
              ip = CE.dce("input", "devid", "field idfield");
              ip.maxLength = 26;
              var d = CE.CEU.$("iddiv");
              d.appendChild(ip);
          }
      }

      function tryManualEntry() {
          if(m_timer) {
              CE.uTimer(m_timer);
              m_timer = null;

              m_pendingsearch = false;
          }
          m_device = null;

          CE.rac(m_pane);

          var lbl = CE.dce("div", null, "extramarginbottom");
          lbl.appendChild(CE.STRTAB.lookupel('settings.notfound'));
          m_pane.appendChild(lbl);
          
          lbl = CE.dce("div", null, "label");
          lbl.appendChild(CE.STRTAB.lookupel("ces.register.msg"));
          m_pane.appendChild(lbl);
          
          CE.CEU.attachEvent(lbl, "click", { onEvent: toggleDeviceIDEnterFormat });
          m_pane.appendChild(lbl);

          var iddiv = CE.dce("div", "iddiv");
          addDeviceIDInputs(iddiv);

          m_pane.appendChild(iddiv);

          m_dlg.removeButton('reg');
          m_dlg.addButton('reg', CE.STRTAB.lookup('ceui.register'), onAddDeviceRegClick);            
      }

      function foundIt(d) {
          CE.rac(m_pane);
          m_pane.appendChild(CE.STRTAB.lookupel("settings.foundlocal"));
          m_device = d;

          m_dlg.removeButton('reg');
          m_dlg.addButton('reg', CE.STRTAB.lookup('ceui.register'), onAddDeviceRegClick);            
      }

      function onGetLocalDeviceSuccess(r) {
          if(m_pendingsearch) {
              m_pendingsearch = false;
              if(r.device && r.device.deviceid) {
                  foundIt(r.device);
              } else {
                  tryManualEntry();
              }
          }
      }

      function onGetLocalDeviceFailure(r) {
          tryManualEntry();
      }
      
      function startLocalDeviceSearch() {
          m_timer = null;
          m_pendingsearch = true
          CE.CEU.svc.asyncRPC("POST", "getLocalDevice", [], onGetLocalDeviceSuccess, onGetLocalDeviceFailure);
      }

      this.show = function() {
          m_device = null;

          var pane = m_pane;
          CE.rac(pane);

          var div = CE.dce("div");
          div.appendChild(CE.STRTAB.lookupel("settings.waitlocate"));
          pane.appendChild(div);

          div = CE.dce("div", null, "buttons");
          div.appendChild(CE.dci(CE.STRTAB.lookup("imgbase")+"loading.gif"));
          var a = CE.dca({onEvent:tryManualEntry}, null, "rename");
          a.appendChild(CE.STRTAB.lookupel("settings.cancel"));
          div.appendChild(a);
          
          pane.appendChild(div);

          m_timer = CE.rTimer(startLocalDeviceSearch, 3000);

          m_dlg.show();
      }; 
  };
  
  this.ActiveCopyConfig = function(m_svcmap, m_sharemap, m_cbFail, m_contentDiv) {
      var that      = this;
      var m_pollIds = {};
      
      this.appendSyncTableHead = function(el) {
          var table = el.appendChild(CE.dce('table',null,'cetable'));
          var tbody = table.appendChild(CE.dce('tbody'));
          var head  = tbody.appendChild(CE.dce('tr',null,'head'));
          (head.appendChild(CE.dce('td',null,'head'))).appendChild(CE.STRTAB.lookupel('view.from'));
          (head.appendChild(CE.dce('td',null,'head'))).appendChild(CE.STRTAB.lookupel('view.to'));
          return tbody;
      };
      
      function updateSyncStatus(el, sync) {
          CE.rac(el);
          
          if(sync.progress && parseInt(sync.progress) < 100) {
              el.appendChild(CE.STRTAB.lookupel('view.sync.status.inprog1'));
              el.appendChild(CE.dctn(sync.progress+'%'));
              el.appendChild(CE.STRTAB.lookupel('view.sync.status.inprog2'));
          } else if(sync.isenabled && sync.state && parseInt(sync.state) > 0) {
              el.appendChild(CE.STRTAB.lookupel('view.sync.state.'+sync.state));
          }
      }
      
      function pollSyncStatus(svc, sourceFolderId) {
          var key = svc.deviceid + ':' + svc.serviceid + ':' + (sourceFolderId ? sourceFolderId : '');

          var poll = function() {
              var args = ['deviceid',svc.deviceid, 'serviceid',svc.serviceid, 'feature','foldersync', 'command','listFolderSyncs'];
              if(sourceFolderId)
                  args.push('srcfileid', sourceFolderId);
  
              CE.CEU.svc.asyncRPC('POST', 'featureCommand', args,
              function(r) {
                  var found = false;
                  if(r.foldersyncs) {
                      var syncs = r.foldersyncs.length ? r.foldersyncs : [r.foldersyncs.foldersync];
                      for(var i = 0; i < syncs.length; ++i) {
                          var status = CE.CEU.$('cesync_status_' + svc.serviceid + '_' + syncs[i].syncid);
                          if(status) {
                              found = true;
                              updateSyncStatus(status, syncs[i]);
                          }
                      }
                  }
                  
                  if(found) {
                      m_pollIds[key] = setTimeout(poll, 4000);
                  } else {
                      m_pollIds[key] = null;
                  }
              });
          };
          
          if(!m_pollIds[key]) {
              m_pollIds[key] = setTimeout(poll, 4000);
          }
      }
      
      this.appendSyncTableRows = function(table, refreshCb, svc, sourceFolderId, noSyncsCb, yesSyncsCb) {
          var args = ['deviceid',svc.deviceid, 'serviceid',svc.serviceid, 'feature','foldersync', 'command','listFolderSyncs'];
          if(sourceFolderId)
              args.push('srcfileid', sourceFolderId);
          
          CE.CEU.svc.asyncRPC('POST', 'featureCommand', args,
          function(r) {
              if(r.foldersyncs) {
                  var syncs   = r.foldersyncs.length ? r.foldersyncs : [r.foldersyncs.foldersync];
                  var pending = syncs.length * 2;
                  for(var i = 0; i < syncs.length; ++i) {
                      (function(sync) {
                          var tr = table.appendChild(CE.dce('tr'));
                          // From
                          var fromLabel = ((tr.appendChild(CE.dce('td',null,'cesync_from_cell cesync_bordertop'))).appendChild(CE.dce('div',null,'cesync_folderlabel')));
                          CE.CEU.svc.asyncRPC('POST', 'getFile', ['deviceid',svc.deviceid, 'serviceid',svc.serviceid,
                              'fileid',sync.srcfileid],
                              function(rf) {
                                  --pending;
                                  var name = (rf.file.name != '.') ? rf.file.name : CE.STRTAB.lookup('view.sync.root');
                                  (fromLabel.appendChild(CE.dce('span',null,'cebold'))).appendChild(CE.dctn(name));
                                  var svcInfo = m_svcmap[svc.deviceid + ':_:' + svc.serviceid]
                                  if(svcInfo) {
                                      fromLabel.appendChild(CE.STRTAB.lookupel('view.sync.on'));
                                      fromLabel.appendChild(CE.dctn(svcInfo.name));
                                  }
                              },
                              function() {
                                  --pending;
                                  // getFile failiure
                                  (fromLabel.appendChild(CE.dce('span',null,'cebold'))).appendChild(CE.STRTAB.lookupel('view.sync.destunavail'));
                              });
                          // To
                          var toLabel = ((tr.appendChild(CE.dce('td',null,'cesync_bordertop'))).appendChild(CE.dce('div',null,'cesync_folderlabel')));
                          CE.CEU.svc.asyncRPC('POST', 'getFile', ['deviceid',sync.dstdeviceid, 'serviceid',sync.dstserviceid,
                              'fileid',sync.dstfileid],
                              function(rf) {
                                  --pending;
                                  var name = (rf.file.name != '.') ? rf.file.name : CE.STRTAB.lookup('view.sync.root');
                                  (toLabel.appendChild(CE.dce('span',null,'cebold'))).appendChild(CE.dctn(name));
                                  var svcInfo = m_svcmap[sync.dstdeviceid + ':_:' + sync.dstserviceid]
                                  if(svcInfo) {
                                      toLabel.appendChild(CE.STRTAB.lookupel('view.sync.on'));
                                      toLabel.appendChild(CE.dctn(svcInfo.name));
                                      toLabel.appendChild(CE.dctn(' '));
                                      (toLabel.appendChild(CE.dca({onEvent:that.chooseSyncDest, 'svc':svc, 'srcfileid':sourceFolderId, 'isNewSync':0, 'syncID':sync.syncid, 'refreshcb':refreshCb}))).appendChild(CE.STRTAB.lookupel('view.sync.changedest'));
                                  }
                              },
                              function() {
                                  --pending;
                                  // getFile failiure
                                  (toLabel.appendChild(CE.dce('span',null,'cebold'))).appendChild(CE.STRTAB.lookupel('view.sync.destunavail'));
                              });
                          tr = table.appendChild(CE.dce('tr'));
                          var td = tr.appendChild(CE.dce('td'));
                          td.colSpan = '2';
                          // Enabled/disabled
                          (td.appendChild(CE.dce('span'))).innerHTML = '&nbsp;&nbsp;&nbsp;';
                          var checkId = 'cesync_enabled_' + sync.syncid;
                          var check = CE.dce('input',checkId);
                          check.type    = 'checkbox';
                          td.appendChild(check);
                          check.checked = (sync.isenabled == '1');
                          td.appendChild(CE.dctn(' '));
                          var label = td.appendChild(CE.dce('label'));
                          label.htmlFor = checkId;
                          label.appendChild(CE.STRTAB.lookupel('view.enabled'));
                          CE.CEU.attachEvent(check, 'click', {onEvent:toggleSyncDest, 'svc':svc, 'sync':sync, 'checkid':checkId});
                          // Status
                          var statusId = 'cesync_status_' + svc.serviceid + '_' + sync.syncid;
                          var status = td.appendChild(CE.dce('span',statusId,'cegraytext indent-16'));
                          updateSyncStatus(status, sync);
                          // Remove
                          ((td.appendChild(CE.dce('span',null,'right-float')))
                              .appendChild(CE.dca({onEvent:removeSyncDest, 'svc':svc, syncid:sync.syncid, 'refreshcb':refreshCb})))
                                  .appendChild(CE.STRTAB.lookupel('view.hint.remove'));
                      })(syncs[i]);
                  }
                  var interval = setInterval(
                  function() {
                      if(pending < 1) {
                          clearInterval(interval);
                          if(yesSyncsCb) yesSyncsCb();
                          pollSyncStatus(svc, sourceFolderId);
                      }
                  },
                  500);
              } else {
                  // No syncs set up; show tip instead of table
                  if(noSyncsCb) noSyncsCb();
              }
          },
          function() {
              // Sync not working on this device or device offline...
              if(noSyncsCb) noSyncsCb();
          });
      }
      
      function toggleSyncDest(evt) {
          var svc     = evt.svc;
          var checked = CE.CEU.$(evt.checkid).checked;
          
          CE.CEU.svc.asyncRPC('POST', 'featureCommand',
                              ['deviceid',svc.deviceid, 'serviceid',svc.serviceid, 'feature','foldersync',
                               'command','updateFolderSync', 'syncid',evt.sync.syncid, 'dstdeviceid',evt.sync.dstdeviceid,
                               'dstserviceid', evt.sync.dstserviceid, 'dstfileid',evt.sync.dstfileid,
                               'isenabled',checked?'1':'0'],
                              null, m_cbFail);
      }
      
      function removeSyncDest(evt) {
          CE.CEU.promptYesNo(CE.STRTAB.lookup('view.sync.remove.title'), CE.STRTAB.lookup('view.sync.remove.msg'),
              function() {
                  CE.CEU.showLoadingAni(true);
                  var svc       = evt.svc;
                  var refreshCb = evt.refreshcb;
          
                  CE.CEU.svc.asyncRPC('POST', 'featureCommand', ['deviceid',svc.deviceid, 'serviceid',svc.serviceid,
                                      'feature','foldersync', 'command','deleteFolderSync', 'syncid',evt.syncid],
                                      refreshCb, m_cbFail);
                  return true;
              });
      }
      
      function hasSync(fromSvc, fromFileId, toSvc, toFileId, yesCb, noCb) {
          var args = ['feature','foldersync', 'command','listFolderSyncs',
                      'deviceid',fromSvc.deviceid, 'serviceid',fromSvc.serviceid, 'srcfileid',fromFileId];

          CE.CEU.svc.asyncRPC('POST', 'featureCommand', args,
          function(r) {
            var syncs = (r.foldersyncs && r.foldersyncs.length) ? r.foldersyncs
                      : ((r.foldersyncs && r.foldersyncs.foldersync) ? [r.foldersyncs.foldersync] : []);
            for(var i = 0; i < syncs.length; ++i) {
                if(toSvc.deviceid == syncs[i].dstdeviceid && toSvc.serviceid == syncs[i].dstserviceid && toFileId == syncs[i].dstfileid) {
                    yesCb();
                    return;
                }
            }
            noCb();
          },
          noCb);
      }
      
      this.chooseSyncDest = function(evt, a, b, defFileId, defSvc, defRefreshCb) {
          var folderId  = evt.srcfileid ? evt.srcfileid : defFileId;
          var svc       = evt.svc ? evt.svc : defSvc;
          var refreshCb = evt.refreshcb ? evt.refreshcb : defRefreshCb;
          
          if(!folderId) folderId = '0';
          
          CE.CEU.svc.asyncRPC('POST', 'getFile', ['deviceid',svc.deviceid, 'serviceid',svc.serviceid, 'fileid',folderId],
          function(rg) {
              CE.CEU.showChooseFolderDialog(m_svcmap, m_sharemap,
              function(r) {
                  // OK
                  hasSync(r.svc, r.file.fileid, svc, folderId,
                  function() {
                      // Sync already exists in other direction; prevent the loop
                      CE.CEU.showMessage(CE.STRTAB.lookup('view.sync.loop.title'), CE.STRTAB.lookup('view.sync.loop.msg'));
                  },
                  function() {
                      // Ok ok
                      CE.CEU.showLoadingAni(true);
                      if(evt.isNewSync) {
                          CE.CEU.svc.asyncRPC('POST', 'featureCommand',
                                              ['deviceid',svc.deviceid, 'serviceid',svc.serviceid, 'feature','foldersync',
                                               'command','createFolderSync', 'srcfileid',folderId,
                                               'dstdeviceid',r.svc.deviceid, 'dstserviceid',r.svc.serviceid, 'dstfileid',r.file.fileid,
                                               'isenabled','1'],
                                              refreshCb, m_cbFail);
                      } else {
                          CE.CEU.svc.asyncRPC('POST', 'featureCommand',
                                              ['deviceid',svc.deviceid, 'serviceid',svc.serviceid, 'feature','foldersync',
                                              'command','updateFolderSync', 'syncid',evt.syncID, 'dstdeviceid',r.svc.deviceid,
                                              'dstserviceid',r.svc.serviceid, 'dstfileid',r.file.fileid,  'isenabled','1'],
                                              refreshCb, m_cbFail);
                      }
                  });
                  return true;
              },
              function() {
                  // Cancel
                  return true;
              },
              {'svc':svc, 'file':rg.file});
          },
          m_cbFail);
      };
  
      function addSyncSourceDest() {
          var sourceSvcMap = {};
          for(var s in m_svcmap) {
              if(m_svcmap[s].isValidSyncSource)
                  sourceSvcMap[s] = m_svcmap[s];
          }
        
          CE.CEU.showChooseFolder2Dialog(sourceSvcMap, m_svcmap, null, m_sharemap, CE.STRTAB.lookup('view.sync.choosetitle'),
                                         [CE.STRTAB.lookup('view.from'), CE.STRTAB.lookup('view.to')], false,
          function(folder1, folder2) {
              // OK
              hasSync(folder2.svc, folder2.file.fileid, folder1.svc, folder1.file.fileid,
              function() {
                  // Sync already exists in other direction; prevent the loop
                  CE.CEU.showMessage(CE.STRTAB.lookup('view.sync.loop.title'), CE.STRTAB.lookup('view.sync.loop.msg'));
              },
              function() {
                  // Ok ok
                  CE.CEU.showLoadingAni(true);
                  CE.CEU.svc.asyncRPC('POST', 'featureCommand', ['feature','foldersync', 'command','createFolderSync',
                                      'deviceid',folder1.svc.deviceid, 'serviceid',folder1.svc.serviceid,
                                      'srcfileid',folder1.file.fileid,
                                      'dstdeviceid',folder2.svc.deviceid, 'dstserviceid',folder2.svc.serviceid,
                                      'dstfileid',folder2.file.fileid, 'isenabled','1'],
                                      that.showConfig, m_cbFail);
              });
              return true;
          });
      }
      
      function enableDisableAllSyncs() {
          var checks = m_contentDiv.getElementsByTagName('input');
          if(checks.length < 1)
              return;
          var mixed  = false;
          for(var i = 1; i < checks.length; ++i) {
            if(checks[i].checked != checks[0].checked) {
                mixed = true;
                break;
            }
          }
          
          var enable = mixed ? true : !checks[0].checked;
          
          for(var i = 0; i < checks.length; ++i) {
              if(checks[i].checked != enable) {
                  checks[i].checked = enable;
                  var evt = CE.CEU.getEventObj(checks[i], 'click');
                  evt.onEvent(evt);
              }
          }
      }
      
      this.showConfig = function() {
          CE.rac(m_contentDiv);
          
          CE.CEU.showLoadingAni(true);
          var tableDiv = m_contentDiv.appendChild(CE.dce('div'));
          var table = that.appendSyncTableHead(tableDiv);
          CE.aCN(tableDiv, 'hidden');
          
          var numSvc   = 0;
          var numEmpty = 0;
          var numFull  = 0;
          for(var s in m_svcmap) {
              ++numSvc;
              that.appendSyncTableRows(table, that.showConfig, m_svcmap[s], null,
                                       function(){++numEmpty;}, function(){++numFull; CE.rCN(tableDiv,'hidden');});
          }
          
          var interval = setInterval(
          function() {
              if(numSvc == (numEmpty+numFull)) {
                  clearInterval(interval);
                  CE.CEU.showLoadingAni(false);
                  if(numFull < 1) {
                      (m_contentDiv.appendChild(CE.dce('div',null,'cehintbubble')))
                          .innerHTML = CE.STRTAB.lookup('view.sync.config.none.html');
                  }
                  
                  var buttons = m_contentDiv.appendChild(CE.dce('p',null,'cedir cetablewidth'));
                  
                  (buttons.appendChild(CE.dca({onEvent:addSyncSourceDest},null,'cebutton')))
                      .appendChild(CE.STRTAB.lookupel('view.sync.addsourcedest'));
                  
                  if(numFull > 0) {
                      buttons.appendChild(CE.dctn(' '));
                      (buttons.appendChild(CE.dca({onEvent:enableDisableAllSyncs},null,'cebutton')))
                          .appendChild(CE.STRTAB.lookupel('view.sync.enabledisableall'));
                  }
              }
          },
          500);
      };
  };
  
  this.checkOpt = function(opt) {
    var opt = CE.CEU.$(opt);
    if(opt) {
        var parent = opt.parentNode;
        while(!CE.hCN(parent,'ceopts') && !CE.hCN(parent,'ceopt-group')) {
            parent = parent.parentNode;
        }
        var tags = ['div','a'];
        for(var t in tags) {
            var opts = CE.getByClass(parent, tags[t], 'ceopt');
            for(var i = 0; i < opts.length; ++i) {
                CE.rCN(opts[i], 'selected');
            }
        }
        CE.aCN(opt, 'selected');
    }
  };
  
  this.hasAncestor = function(childEl, parentId) {
    while(childEl) {
        if(childEl.id == parentId)
            return true;
        childEl = childEl.parentNode;
    }
    return false;
  };
  
  this.poll = function(intervalMs, isDoneCb, doneCb, noQuickCheck) {
    var checkNow = function() {
        if(isDoneCb())
            doneCb();
        else
            setTimeout(checkNow, intervalMs);
    };
    
    if(noQuickCheck)
        setTimeout(checkNow, intervalMs);
    else
        checkNow();
  };
}();

//-----------------------------------------------------
// CE.CEU.Dialog - class
//   implements a modal dialog box
CE.CEU.Dialog = function(header, message, buttonlist, doSetFocus, noclose, extraclass, ech,
                         quickClose, cancelCb, nonModal, smallClose, keepTopBar) {
  var that = this;
  var hidediv, dlghldr, dlgdiv, hdrdiv, hdrmsg, msgdiv, btndiv, btnafter, shown = false;
  var buttons = [];
  var focushelper = doSetFocus;
  var cancelcb = cancelCb;
  var hdrclear = null;
  var drag_dlgstart, drag_mousestart;

  function onDragStart(d, evt)
  {
      if((!evt.which || evt.which == 1) && (!evt.button || evt.button == 1)) {
          drag_dlgstart   = {x:dlgdiv.offsetLeft, y:dlgdiv.offsetTop};
          drag_mousestart = {x:evt.clientX,       y:evt.clientY};
          if(CE.CEUI && CE.CEUI.addMouseHandler)
              CE.CEUI.addMouseHandler(onDragMove);
          else
              document.onmousemove = onDragMove;
          hdrdiv.ondragstart = function() { return false; };
          document.onselectstart = function () { return false; };
          hdrdiv.focus();
      }
  }
  function onDragStop()
  {
      if(CE.CEUI && CE.CEUI.removeMouseHandler)
          CE.CEUI.removeMouseHandler(onDragMove);
      else
          document.onmousemove = null;
      document.onselectstart = null;
      hdrdiv.focus();
  }
  function onDragMove(evt)
  {
      if(!evt) evt = window.event;

      dlgdiv.style.left = drag_dlgstart.x + (evt.clientX - drag_mousestart.x) + 'px';
      dlgdiv.style.top  = drag_dlgstart.y + (evt.clientY - drag_mousestart.y) + 'px';
      hdrdiv.focus();
  }

  function onButtonClick(btn) {
      //CE.CEDBG.println("CEU: Dialog Button '"+btn.name+"' clicked!");
      if(btn.callback==undefined) {
          that.hide();
      } else {
          //try {
          if(btn.callback(btn.name)) {
              that.hide();
          }
          //} catch(e) {
          //    CE.CEDBG.println("CEU: Exception during callback: "+e);
          //    that.hide();
          //}
      }
  }

  //CE.CEDBG.println("CEU: Creating dialog box...");
  hidediv = document.createElement('div');
  hidediv.className = "CEUDialog_hide" + (keepTopBar ? " CEUDialog_hide_keeptopbar" : "");
  
  var padNum = CE.CEU.$('CEUDialog_hide_div') ? 2 : 0;
  hidediv.id = "CEUDialog_hide_div" + (padNum ? padNum : '');
  if(padNum)
    hidediv.style.zIndex = 100 + padNum;
  
  if(quickClose) {
      hidediv.onclick = function () {
          if(cancelcb) cancelcb();
          that.hide();
          return false;
      };
  }
  
  dlghldr = document.createElement('div');
  if(ech) {
    dlghldr.className = "CEUDialog_holder "+ech;
  } else {
    dlghldr.className = "CEUDialog_holder";
  }
  dlgdiv = document.createElement('div');
  if(extraclass) {
    dlgdiv.className = "CEUDialog "+extraclass;
  } else {
    dlgdiv.className = "CEUDialog";
  }
  if(padNum)
    dlgdiv.style.zIndex = 101 + padNum;
  dlghldr.appendChild(dlgdiv);

  var dlginner = CE.dce('div', null, 'CEUDialog_inner');
  dlgdiv.appendChild(dlginner);
  hdrdiv = document.createElement('div');
  hdrdiv.className = 'CEUDialog_hdr';
  if(!noclose) {
      hdrclear = document.createElement('a');
      hdrclear.className = smallClose ? 'CEUDialog_hdrbutton_small' : 'CEUDialog_hdrbutton';
      hdrclear.href = '#';
      (function(h,t) { h.onclick = function () {if(cancelcb) cancelcb(); t.hide(); return false;} })(hdrclear, that);
      var im = document.createElement('img');
      im.src = CE.STRTAB.lookup('imgbase') + (smallClose ? 'close-12px.png' : 'dialog-close-red.png');
      im.alt = CE.STRTAB.lookup('ceu.close');
      hdrclear.appendChild(im); im = null;
      hdrdiv.appendChild(hdrclear);
  }
  hdrmsg = document.createElement('span');
  hdrdiv.appendChild(hdrmsg);
  dlginner.appendChild(hdrdiv);
  //CE.CEU.attachEvent(hdrdiv,  'mousedown',   {onEvent:onDragStart});
  //CE.CEU.attachEvent(hdrdiv,  'mouseup',     {onEvent:onDragStop});

  el = document.createElement('div');
  el.className = 'CEUDialog_msg';
  msgdiv = document.createElement('div');
  el.appendChild(msgdiv);
  dlginner.appendChild(el);
  el = null;
  btndiv = document.createElement('div');
  btndiv.className  = 'CEUDialog_btn';
    btnafter = document.createElement('div');
    btnafter.className = 'afterclear';
    btndiv.appendChild(btnafter);
  dlginner.appendChild(btndiv);

  this.onhide = null;

  // Public methods...
  this.addButton = function(bname, blabel, bcallback) {
    if(blabel==undefined) blabel = bname;
    var bel = document.createElement('a');
    bel.className = 'ceredbut';
    bel.href = '#';
    var bspan = document.createElement('span');
    bel.appendChild(bspan);
    var btxt = document.createTextNode(blabel);
    bspan.appendChild(btxt);
    var b = {name:bname, label: blabel, callback:bcallback, element: bel};
    buttons.push(b);
    bel.onclick = function() {onButtonClick(b);return false;};
    //CE.CEDBG.println("CEU: Adding button '"+bname+"' '"+blabel+"' '"+bcallback+"'");
      if(btnafter) {
          btndiv.insertBefore(bel, btnafter);
      } else {
          btndiv.appendChild(bel);
      }
  }
  this.removeButton = function(bname) {
    for(i=0; i < buttons.length; i++) {
      if(buttons[i]['name'] == bname) {
        if(btndiv) btndiv.removeChild(buttons[i].element);
        buttons.splice(i, 1);
        break;
      }
    }        
  }
  if(typeof(buttonlist) == 'string') {
    this.addButton(buttonlist);
  } else if(buttonlist) {
    for(i=0; i<buttonlist.length; i++) {
      if(typeof(buttonlist[i]) == 'object') {
        this.addButton(buttonlist[i].name, buttonlist[i].label, buttonlist[i].callback);
      } else {
        this.addButton(buttonlist[i]);
      }
    }
  }

  this.setHeader = function(header) {
    hdrmsg.innerHTML = header;
  }
  this.setHeader(header);

  this.setMessage = function(message) {
    CE.rac(msgdiv);
    if(typeof(message)=='string') {
      msgdiv.appendChild(document.createTextNode(message));
    } else if(message) {
      msgdiv.appendChild(message);
    }
  }
  this.setMessage(message);

  this.isShowing = function() {
    return shown;
  }
  this.show = function() {
    if(!shown) {
      //CE.CEDBG.println("CEU: Showing dialog...");
      shown = true;
      hidediv.style.visibility = 'hidden';
      dlghldr.style.visibility = 'hidden';
      document.body.appendChild(hidediv);
      document.body.appendChild(dlghldr);
      dlgdiv.style.left = Math.floor(hidediv.offsetWidth/2 - dlgdiv.offsetWidth/2) + 'px';
      dlgdiv.style.top  = Math.floor(hidediv.offsetHeight/2 - dlgdiv.offsetHeight/2) + 'px';
      hidediv.style.visibility = 'visible';
      dlghldr.style.visibility = 'visible';
      if(nonModal)
        CE.aCN(hidediv, 'hidden');
      if(typeof(focushelper)=='function') {
        focushelper();
        //             } else if(buttons.length>0) {
        //                 buttons[0].element.focus();
      } else {
        if(hdrclear) {
            hdrclear.focus();
            hdrclear.blur();
        }
    }
    }
    return dlgdiv;
  }
  this.hide = function() {
    if(shown) {
      if(that.onhide) {
        if(typeof that.onhide == 'function') {
          that.onhide(that);
        }
      }
      //CE.CEDBG.println("CEU: Hiding dialog...");
      document.body.removeChild(dlghldr);
      document.body.removeChild(hidediv);
      shown = false;
    }
  }
  this.destroy = function() {
    this.hide();
    //CE.CEDBG.println("CEU: Destroying dialog box...");
    for(i=0; i<buttons.length; i++) {
      buttons[i].element.onclick = null;
      buttons[i].element = null;
    }        
    CE.CEU.releaseAllEvents(hdrdiv,  'mousedown', false);
    CE.CEU.releaseAllEvents(hdrdiv,  'mouseup',   false);
    focushelper = null;
    buttons = null;
    btndiv = null;
    msgdiv = null;
    hdrmsg = null;
    hdrdiv = null;
    dlgdiv = null;
    dlghldr = null;
    hidediv = null;
  }

};

/*
 * Date Format 1.2.3
 * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
 * MIT license
 *
 * Includes enhancements by Scott Trenda <scott.trenda.net>
 * and Kris Kowal <cixar.com/~kris.kowal/>
 *
 * Accepts a date, a mask, or a date and a mask.
 * Returns a formatted version of the given date.
 * The date defaults to the current date/time.
 * The mask defaults to dateFormat.masks.default.
 */

var dateFormat = function () {
	var	token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
		timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
		timezoneClip = /[^-+\dA-Z]/g,
		pad = function (val, len) {
			val = String(val);
			len = len || 2;
			while (val.length < len) val = "0" + val;
			return val;
		};

	// Regexes and supporting functions are cached through closure
	return function (date, mask, utc) {
		var dF = dateFormat;

		// You can't provide utc if you skip other args (use the "UTC:" mask prefix)
		if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
			mask = date;
			date = undefined;
		}

		// Passing date through Date applies Date.parse, if necessary
		date = date ? new Date(date) : new Date;
		if (isNaN(date)) throw SyntaxError("invalid date");

		mask = String(dF.masks[mask] || mask || dF.masks["default"]);

		// Allow setting the utc argument via the mask
		if (mask.slice(0, 4) == "UTC:") {
			mask = mask.slice(4);
			utc = true;
		}

		var	_ = utc ? "getUTC" : "get",
			d = date[_ + "Date"](),
			D = date[_ + "Day"](),
			m = date[_ + "Month"](),
			y = date[_ + "FullYear"](),
			H = date[_ + "Hours"](),
			M = date[_ + "Minutes"](),
			s = date[_ + "Seconds"](),
			L = date[_ + "Milliseconds"](),
			o = utc ? 0 : date.getTimezoneOffset(),
			flags = {
				d:    d,
				dd:   pad(d),
				ddd:  CE.STRTAB.lookup('ceu.day'+D),
				dddd: CE.STRTAB.lookup('ceu.dayfull'+D),
				m:    m + 1,
				mm:   pad(m + 1),
				mmm:  CE.STRTAB.lookup('ceu.month'+m),
				mmmm: CE.STRTAB.lookup('ceu.monthfull'+m),
				yy:   String(y).slice(2),
				yyyy: y,
				h:    H % 12 || 12,
				hh:   pad(H % 12 || 12),
				H:    H,
				HH:   pad(H),
				M:    M,
				MM:   pad(M),
				s:    s,
				ss:   pad(s),
				l:    pad(L, 3),
				L:    pad(L > 99 ? Math.round(L / 10) : L),
				t:    H < 12 ? "a"  : "p",
				tt:   H < 12 ? "am" : "pm",
				T:    H < 12 ? "A"  : "P",
				TT:   H < 12 ? "AM" : "PM",
				Z:    utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
				o:    (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
				S:    ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
			};

		return mask.replace(token, function ($0) {
			return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
		});
	};
}();

// Some common format strings
dateFormat.masks = {
	"default":      "ddd mmm dd yyyy HH:MM:ss",
	shortDate:      "m/d/yy",
	mediumDate:     "mmm d, yyyy",
	longDate:       "mmmm d, yyyy",
	fullDate:       "dddd, mmmm d, yyyy",
	shortTime:      "h:MM TT",
	mediumTime:     "h:MM:ss TT",
	longTime:       "h:MM:ss TT Z",
	isoDate:        "yyyy-mm-dd",
	isoTime:        "HH:MM:ss",
	isoDateTime:    "yyyy-mm-dd'T'HH:MM:ss",
	isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
};

// For convenience...
Date.prototype.format = function (mask, utc) {
	return dateFormat(this, mask, utc);
};

if(!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(elt) {
        var len = this.length >>> 0;
        var from = Number(arguments[1]) || 0;
        from = (from < 0) ? Math.ceil(from) : Math.floor(from);
        if (from < 0)
            from += len;
        for(; from < len; from++) {
            if (from in this && this[from] === elt)
                return from;
        }
        return -1;
    };
}

/*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */

/*
 * Configurable variables. You may need to tweak these to be compatible with
 * the server-side, but the defaults work in most cases.
 */
var hexcase = 0;   /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad  = "";  /* base-64 pad character. "=" for strict RFC compliance   */

/*
 * These are the functions you'll usually want to call
 * They take string arguments and return either hex or base-64 encoded strings
 */
function hex_md5(s)    { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
function b64_md5(s)    { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
function hex_hmac_md5(k, d)
  { return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function b64_hmac_md5(k, d)
  { return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function any_hmac_md5(k, d, e)
  { return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }

/*
 * Perform a simple self-test to see if the VM is working
 */
function md5_vm_test()
{
  return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72";
}

/*
 * Calculate the MD5 of a raw string
 */
function rstr_md5(s)
{
  return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
}

/*
 * Calculate the HMAC-MD5, of a key and some data (raw strings)
 */
function rstr_hmac_md5(key, data)
{
  var bkey = rstr2binl(key);
  if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);

  var ipad = Array(16), opad = Array(16);
  for(var i = 0; i < 16; i++)
  {
    ipad[i] = bkey[i] ^ 0x36363636;
    opad[i] = bkey[i] ^ 0x5C5C5C5C;
  }

  var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
  return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
}

/*
 * Convert a raw string to a hex string
 */
function rstr2hex(input)
{
  try { hexcase } catch(e) { hexcase=0; }
  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  var output = "";
  var x;
  for(var i = 0; i < input.length; i++)
  {
    x = input.charCodeAt(i);
    output += hex_tab.charAt((x >>> 4) & 0x0F)
           +  hex_tab.charAt( x        & 0x0F);
  }
  return output;
}

/*
 * Convert a raw string to a base-64 string
 */
function rstr2b64(input)
{
  try { b64pad } catch(e) { b64pad=''; }
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var output = "";
  var len = input.length;
  for(var i = 0; i < len; i += 3)
  {
    var triplet = (input.charCodeAt(i) << 16)
                | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
                | (i + 2 < len ? input.charCodeAt(i+2)      : 0);
    for(var j = 0; j < 4; j++)
    {
      if(i * 8 + j * 6 > input.length * 8) output += b64pad;
      else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
    }
  }
  return output;
}

/*
 * Convert a raw string to an arbitrary string encoding
 */
function rstr2any(input, encoding)
{
  var divisor = encoding.length;
  var i, j, q, x, quotient;

  /* Convert to an array of 16-bit big-endian values, forming the dividend */
  var dividend = Array(Math.ceil(input.length / 2));
  for(i = 0; i < dividend.length; i++)
  {
    dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
  }

  /*
   * Repeatedly perform a long division. The binary array forms the dividend,
   * the length of the encoding is the divisor. Once computed, the quotient
   * forms the dividend for the next step. All remainders are stored for later
   * use.
   */
  var full_length = Math.ceil(input.length * 8 /
                                    (Math.log(encoding.length) / Math.log(2)));
  var remainders = Array(full_length);
  for(j = 0; j < full_length; j++)
  {
    quotient = Array();
    x = 0;
    for(i = 0; i < dividend.length; i++)
    {
      x = (x << 16) + dividend[i];
      q = Math.floor(x / divisor);
      x -= q * divisor;
      if(quotient.length > 0 || q > 0)
        quotient[quotient.length] = q;
    }
    remainders[j] = x;
    dividend = quotient;
  }

  /* Convert the remainders to the output string */
  var output = "";
  for(i = remainders.length - 1; i >= 0; i--)
    output += encoding.charAt(remainders[i]);

  return output;
}

/*
 * Encode a string as utf-8.
 * For efficiency, this assumes the input is valid utf-16.
 */
function str2rstr_utf8(input)
{
  var output = "";
  var i = -1;
  var x, y;

  while(++i < input.length)
  {
    /* Decode utf-16 surrogate pairs */
    x = input.charCodeAt(i);
    y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
    if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
    {
      x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
      i++;
    }

    /* Encode output as utf-8 */
    if(x <= 0x7F)
      output += String.fromCharCode(x);
    else if(x <= 0x7FF)
      output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
                                    0x80 | ( x         & 0x3F));
    else if(x <= 0xFFFF)
      output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
                                    0x80 | ((x >>> 6 ) & 0x3F),
                                    0x80 | ( x         & 0x3F));
    else if(x <= 0x1FFFFF)
      output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
                                    0x80 | ((x >>> 12) & 0x3F),
                                    0x80 | ((x >>> 6 ) & 0x3F),
                                    0x80 | ( x         & 0x3F));
  }
  return output;
}

/*
 * Encode a string as utf-16
 */
function str2rstr_utf16le(input)
{
  var output = "";
  for(var i = 0; i < input.length; i++)
    output += String.fromCharCode( input.charCodeAt(i)        & 0xFF,
                                  (input.charCodeAt(i) >>> 8) & 0xFF);
  return output;
}

function str2rstr_utf16be(input)
{
  var output = "";
  for(var i = 0; i < input.length; i++)
    output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
                                   input.charCodeAt(i)        & 0xFF);
  return output;
}

/*
 * Convert a raw string to an array of little-endian words
 * Characters >255 have their high-byte silently ignored.
 */
function rstr2binl(input)
{
  var output = Array(input.length >> 2);
  for(var i = 0; i < output.length; i++)
    output[i] = 0;
  for(var i = 0; i < input.length * 8; i += 8)
    output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
  return output;
}

/*
 * Convert an array of little-endian words to a string
 */
function binl2rstr(input)
{
  var output = "";
  for(var i = 0; i < input.length * 32; i += 8)
    output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
  return output;
}

/*
 * Calculate the MD5 of an array of little-endian words, and a bit length.
 */
function binl_md5(x, len)
{
  /* append padding */
  x[len >> 5] |= 0x80 << ((len) % 32);
  x[(((len + 64) >>> 9) << 4) + 14] = len;

  var a =  1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d =  271733878;

  for(var i = 0; i < x.length; i += 16)
  {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;

    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);

    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
  }
  return Array(a, b, c, d);
}

/*
 * These functions implement the four basic operations the algorithm uses.
 */
function md5_cmn(q, a, b, x, s, t)
{
  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */
function safe_add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left.
 */
function bit_rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}


/*
natcompare.js -- Perform 'natural order' comparisons of strings in JavaScript.
Copyright (C) 2005 by SCK-CEN (Belgian Nucleair Research Centre)
Written by Kristof Coomans <kristof[dot]coomans[at]sckcen[dot]be>

Based on the Java version by Pierre-Luc Paour, of which this is more or less a straight conversion.
Copyright (C) 2003 by Pierre-Luc Paour <natorder@paour.com>

The Java version was based on the C version by Martin Pool.
Copyright (C) 2000 by Martin Pool <mbp@humbug.org.au>

This software is provided 'as-is', without any express or implied
warranty.  In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/


function isWhitespaceChar(a)
{
    var charCode;
    charCode = a.charCodeAt(0);

    if ( charCode <= 32 )
    {
        return true;
    }
    else
    {
        return false;
    }
}

function isDigitChar(a)
{
    var charCode;
    charCode = a.charCodeAt(0);

    if ( charCode >= 48  && charCode <= 57 )
    {
        return true;
    }
    else
    {
        return false;
    }
}

function compareRight(a,b)
{
    var bias = 0;
    var ia = 0;
    var ib = 0;

    var ca;
    var cb;

    // The longest run of digits wins.  That aside, the greatest
    // value wins, but we can't know that it will until we've scanned
    // both numbers to know that they have the same magnitude, so we
    // remember it in BIAS.
    for (;; ia++, ib++) {
        ca = a.charAt(ia);
        cb = b.charAt(ib);

        if (!isDigitChar(ca)
                && !isDigitChar(cb)) {
            return bias;
        } else if (!isDigitChar(ca)) {
            return -1;
        } else if (!isDigitChar(cb)) {
            return +1;
        } else if (ca < cb) {
            if (bias == 0) {
                bias = -1;
            }
        } else if (ca > cb) {
            if (bias == 0)
                bias = +1;
        } else if (ca == 0 && cb == 0) {
            return bias;
        }
    }
}

function natcompare(a,b) {

    var ia = 0, ib = 0;
	var nza = 0, nzb = 0;
	var ca, cb;
	var result;

    while (true)
    {
        // only count the number of zeroes leading the last number compared
        nza = nzb = 0;

        ca = a.charAt(ia);
        cb = b.charAt(ib);

        // skip over leading spaces or zeros
        while ( isWhitespaceChar( ca ) || ca =='0' ) {
            if (ca == '0') {
                nza++;
            } else {
                // only count consecutive zeroes
                nza = 0;
            }

            ca = a.charAt(++ia);
        }

        while ( isWhitespaceChar( cb ) || cb == '0') {
            if (cb == '0') {
                nzb++;
            } else {
                // only count consecutive zeroes
                nzb = 0;
            }

            cb = b.charAt(++ib);
        }

        // process run of digits
        if (isDigitChar(ca) && isDigitChar(cb)) {
            if ((result = compareRight(a.substring(ia), b.substring(ib))) != 0) {
                return result;
            }
        }

        if (ca == 0 && cb == 0) {
            // The strings compare the same.  Perhaps the caller
            // will want to call strcmp to break the tie.
            return nza - nzb;
        }

        if (ca < cb) {
            return -1;
        } else if (ca > cb) {
            return +1;
        }

        ++ia; ++ib;
    }
}
