/*
 * OpenHandle, 0.2.3
 *
 * <p>Copyright (C) 2008 Tony Hammond</p>
 *
 * <p><a rel="license" href="http://creativecommons.org/licenses/GPL/2.0/">
 * <img alt="Creative Commons License" style="border-width:0"
 * src="http://i.creativecommons.org/l/GPL/2.0/88x62.png" /></a><br />
 * This work is licensed under a <a rel="license"
 * href="http://creativecommons.org/licenses/GPL/2.0/">Creative Commons GNU
 * General Public License License</a>.</p>
 *
 * @author <a href="mailto:tony.hammond@gmail.com">Tony Hammond</a>
 *
 */

var OpenHandle = OpenHandle || {
  //
  // Common classes
  Library: null,
  Common: null,
  //
  // Core classes
  Handle: null,
  HandleValue: null,
  //
  // Support classes
  AdminRecord: null,
  ParseHandle: null,
  ServerInfo: null,
  SiteInfo: null,
  Util: null,
  ValueReference: null
};

/*
 * OpenHandle.Library class
 */
OpenHandle.Library = function(args) {

  var fields = {
    "name" : "OpenHandle JavaScript Client Library",
    "version" : "0.2.3",
    "contact" : "mailto:tony.hammond@gmail.com",
  };

  if (args != null && args != undefined
      && args != 'undefined'){
    for (var f in fields) {
      if (args[f] != null
          && args[f] != undefined
          && args[f] != 'undefined'){
        fields[f] = args[f];
      }
    }
  }

  var getContact = function() {
    return fields.contact;
  };

  var getName = function() {
    return fields.name;
  };

  var getVersion = function() {
    return fields.version;
  };


  var toString = function() {
    return this.getName() + ", v." + this.getVersion();
  };

  var listClasses = function() { var a = []; for (name in OpenHandle) { a.push(name); } return a; };
  var listMethods = function() { var a = []; for (method in this) { a.push(method); } return a; };

  var showClassMethods = function(args) {
    if (args) {
      switch (typeof args) {
        case "string":
          var args = {"name": args};
          break;
        case "object":
          var args = args;
          break;
        default:
          var args = {};
          break;
      }
    }
    else {
      var args = {};
    }
    var s = "";
    var a = [];
    var name = args.name;
    var delim1 = args.delim1 ? args.delim1 : ':\n';
    var delim2 = args.delim2 ? args.delim2 : ', ';
    var delim3 = args.delim3 ? args.delim3 : '\n\n* OpenHandle.';
    if (name) {
      s = delim3.replace(/^\s*/, "") + 
         name + delim1 + eval("(new OpenHandle." + name + "()).listMethods().join('" + delim2+ "')");
    }
    else {
      for (name in OpenHandle) {
        a.push(name + delim1 + eval("(new OpenHandle." + name + "()).listMethods().join('" + delim2+ "')"));
      }
      s = delim3.replace(/^\s*/, "") + a.join(delim3);
    }
    return s;
  };

  // Library methods
  return {
    //
    // getters
    getContact: getContact,
    getName: getName,
    getVersion: getVersion,
    //
    // utils
    listClasses: listClasses,
    listMethods: listMethods,
    showClassMethods: showClassMethods,
    toString: toString
  }

};

// OpenHandle.Common().getConstant("SUBTYPE_SEPARATOR");

// Common class
OpenHandle.Common = function() {

  var fields = {
    "MAX_RECOGNIZED_TTL" : 86400*2,
    "SUBTYPE_SEPARATOR" : ".",
    "TTL_TYPE_RELATIVE" : 0,
    "TTL_TYPE_ABSOLUTE" : 1,
    //
    // Provisional value
    "OPENHANDLE_SERVER" : "http://nascent.nature.com/openhandle/handle"
  };

  var getConstant = function(constant) {
    // return fields.constant;
    return fields[constant];
  };

  var listConstants = function() { var a = []; for (constant in fields) { a.push(constant); } return a; };
  var listMethods = function() { var a = []; for (method in this) { a.push(method); } return a; };

  // Common methods
  return {
    //
    // getters
    getConstant: getConstant,
    //
    // utils
    listConstants: listConstants,
    listMethods: listMethods
  }

};

/*
 * OpenHandle.Handle class
 */
OpenHandle.Handle = function(args) {

  var fields = {
    comment: null,
    handle: null,
    handleStatus: null,
    handleValues: null
  };

  if (args != null && args != undefined
      && args != 'undefined'){
    for (var f in fields) {
      if (args[f] != null
          && args[f] != undefined
          && args[f] != 'undefined'){
        fields[f] = args[f];
      }
    }
  }

  // constrain handle values to those matched in query
  // query is value field object from ParseHandle().getFields()
  var getHandleValuesByQuery = function(query) {
    var hv = fields.handleValues;   
    if (!query) {
      return hv;
    }
    var hv_ = [];
    var matched = false;
    for (var i = 0; i < hv.length; i++) {
      var value = new OpenHandle.HandleValue(hv[i]);
      matched = false;
      for (var field in query) {
        var f = value.getFieldByName(field);
        for (var j = 0; j < query[field].length; j++) {
          // should regex this
          if (field == 'type') {
            var type = query[field][j];
            var delim = OpenHandle.Common().getConstant("SUBTYPE_SEPARATOR");
            if (f.match(new RegExp("^" + type + "(\\" + delim + "\\w+)*$", "i")) != null) {
              matched = true; 
              break;
            }
          }
          else if (query[field][j].toLowerCase() == f.toLowerCase()) {
            matched = true; 
            break;
          }
        }
        if (matched) {
          break;
        }
      }
      if (matched) {
        hv_.push(value.getFields()); 
      }
    }
    return hv_;
  };

  // convenience getter
  var getHandleValuesByIndex = function(index) {
    return getHandleValuesByQuery({'index': [ index ]});
  };

  // convenience getter
  var getHandleValuesByType = function(type) {
    return getHandleValuesByQuery({'type': [ type ]});
  };

  var getFields = function() { return fields; };
  //
  var getComment = function() { return fields.comment; };
  var getHandle = function() { return fields.handle; };
  var getHandleStatus = function() { return fields.handleStatus; };
  var getHandleValues = function() { return fields.handleValues; };

  // setters
  var setComment = function(comment) { return (fields.comment = comment); };
  var setHandle = function(handle) { return (fields.handle = handle); };
  var setHandleStatus = function(handleStatus) { return (fields.handleStatus = handleStatus); };
  var setHandleValues = function(handleValues) { return (fields.handleValues = handleValues); };

  // utils
  var listMethods = function() { var a = []; for (method in this) { a.push(method); } return a; };
  var isStatusOK = function() { return (fields.handleStatus.message == 'SUCCESS'); };
  var isValid = function() {
    var valid = true;
    return valid;
  };
  var toString = function() { return fields.handle + " (" + fields.handleStatus.message + ")"; };

  // Handle methods
  return {
    //
    // getters
    getFields: getFields,
    //
    getComment: getComment,
    getHandle: getHandle,
    getHandleStatus: getHandleStatus,
    getHandleValues: getHandleValues,
    getStatus: getHandleStatus,
    getValues: getHandleValues,
    //
    getHandleValuesByQuery: getHandleValuesByQuery,
    getHandleValuesByIndex: getHandleValuesByIndex,
    getHandleValuesByType: getHandleValuesByType,
    getValuesByQuery: getHandleValuesByQuery,
    getValuesByIndex: getHandleValuesByIndex,
    getValuesByType: getHandleValuesByType,
    //
    // setters
    setComment: setComment,
    setHandle: setHandle,
    setHandleStatus: setHandleStatus,
    setHandleValues: setHandleValues,
    setStatus: setHandleStatus,
    setValues: setHandleValues,
    //
    // utils
    listMethods: listMethods,
    isStatusOK: isStatusOK,
    isValid: isValid,
    toString: toString
  };
};

/*
 * OpenHandle.HandleValue class
 */
OpenHandle.HandleValue = function(args) {

  var fields = {
    index: '0',
    type: '',
    data: '',
    permission: '1110',
    ttl: '+86400',
    timestamp: '0',
    reference: []
  };

  if (args != null && args != undefined
      && args != 'undefined'){
    for (var f in fields) {
      if (args[f] != null
          && args[f] != undefined
          && args[f] != 'undefined'){
        fields[f] = args[f];
      }
    }
  }

  // index
  var getIndex = function() { return fields.index; };
  var setIndex = function(index) { return (fields.index = index); };

  // type
  var getType = function() { return fields.type; };
  var getTypeAsString = function() { return fields.type; };

  var setType = function(type) { return (fields.type = type); };

  var hasType = function(type) {
    var delim = OpenHandle.Common().getConstant("SUBTYPE_SEPARATOR");
    var s = this.getType();
    return (s.match(new RegExp("^" + type +"(\\" + delim + "\\w+)*$", "i")) != null);
  };
  var hasSystemType = function() { return (fields.type.substr(0,3) == 'HS_'); };

  // data
  var getData = function() { return fields.data; };
  var getDataAsString = function() {
    switch (typeof fields.data) {
      case 'object':
        return '{...}';
        break;
      case 'string':
        return fields.data.length > 0 ? fields.data : "??";
        break;
      default:
        return '??';
        break;
    }
  };

  var setData = function(data) { return (fields.data = data); };

  // permission
  var getPermission = function() {
    var o = fields.permission;
    switch (typeof o) {
      case 'object':
        var a = [];
        a.push((o.adminRead == "true") ? "1" : "0");
        a.push((o.adminWrite == "true") ? "1" : "0");
        a.push((o.publicRead == "true") ? "1" : "0");
        a.push((o.publicWrite == "true") ? "1" : "0");
        return a.join("");
      case 'string':
        // any checking?
        return o;
      default:
        return null;
    }
  };
  var setPermission = function(permission) {
    var o = permission;
    switch (typeof o) {
      case 'object':
        var a = [];
        a.push((o.adminRead == "true") ? "1" : "0");
        a.push((o.adminWrite == "true") ? "1" : "0");
        a.push((o.publicRead == "true") ? "1" : "0");
        a.push((o.publicWrite == "true") ? "1" : "0");
        fields.permission = a.join("");
        return;
      case 'string':
        // any checking?
        fields.permission = o;
        return;
      default:
        return null;
    }
  };

  var getAdminCanRead = function() { return this.getPermission()[0] == 1 ? true : false; };
  var getAdminCanWrite = function() { return this.getPermission()[1] == 1 ? true : false; };
  var getAnyoneCanRead = function() { return this.getPermission()[2] == 1 ? true : false; };
  var getAnyoneCanWrite = function() { return this.getPermission()[3] == 1 ? true : false; };

  var setAdminCanRead = function(hasPermission) { return (fields.permission[0] = hasPermission ? 1 : 0); };
  var setAdminCanWrite = function(hasPermission) { return (fields.permission[1] = hasPermission ? 1 : 0); };
  var setAnyoneCanRead = function(hasPermission) { return (fields.permission[2] = hasPermission ? 1 : 0); };
  var setAnyoneCanWrite = function(hasPermission) { return (fields.permission[3] = hasPermission ? 1 : 0); };

  var getPermissionString = function() {
    var a = [];
    a.push(this.getAdminCanRead() ? 'r' : '-');
    a.push(this.getAdminCanWrite() ? 'w' : '-');
    a.push(this.getAnyoneCanRead() ? 'r' : '-');
    a.push(this.getAnyoneCanWrite() ? 'w' : '-');
    return a.join("");
  };
  var getPermissionStringLong = function() {
    var a = [];
    if (this.getAdminCanRead()) { a.push('adminRead'); }
    if (this.getAdminCanWrite()) { a.push('adminWrite'); }
    if (this.getAnyoneCanRead()) { a.push('publicRead'); }
    if (this.getAnyoneCanWrite()) { a.push('publicWrite'); }
    return a.join(", ");
  };

  // ttl
  var getTTL = function() {
    var o = fields.ttl;
    switch (typeof o) {
      case 'object':
        return o.ttlValue;
      case 'string':
        return (o[0] == '+') ? o.substr(1) : o;
      default:
        return null;
    }
  };
  var getTTLType = function() {
    var o = fields.ttl;
    switch (typeof o) {
      case 'object':
        return (o.ttlType == '0') ? 'relative' : 'absolute' ;
      case 'string':
        return (o[0] == '+') ? 'relative' : 'absolute' ;
      default:
        return null;
    }
  };

  var setTTL = function(ttl) { return (fields.ttl = ttl); };
  var setTTLType = function(ttlType) { return (fields.ttlType = ttlType); };

  var getTTLAsString = function() {
    // what about TTLType?
    return Math.round(100*this.getTTL()/3600)/100 + 'h'; 
  }

  // timestamp
  // var getTimestamp = function() { return this.getTimestampAsRFC822(); };
  var getTimestamp = function() { return fields.timestamp; };
  var getTimestampAsDate = function() { return this.getTimestampAsRFC822(); };
  var getTimestampAsString = function() { return this.getTimestampAsRFC822(); };
  var getNicerTimestampAsString = function() { return this.getTimestampAsRFC822(); };

  // -> Sun Nov 06 08:49:37 GMT 1994
  // <- Sun, 06 Nov 1994 08:49:37 GMT
  var getTimestampAsRFC822 = function() {
      var a = fields.timestamp.split(' ');
      // problem with a[4] == 'BST'
      if (a[4] == 'BST') {
        var at = a[3].split(':');
        // var t = parseInt(at[0]);
        var t = at[0];
        if (t > 1) {
          t = t - 1;
        }
        else {
          // error
        }
        at[0] = (t < 10) ? '0' + t.toString() : t.toString() ; 
        a[3] = at.join(':');
        a[4] = 'GMT';
        var s = a[0] + ', ' + a[2] + ' ' + a[1] + ' ' + a[5] + ' ' + a[3] + ' ' + a[4];
      }
      else {
        var s = a[0] + ', ' + a[2] + ' ' + a[1] + ' ' + a[5] + ' ' + a[3] + ' ' + a[4];
      }
      return s; };

  // -> Sun Nov 06 08:49:37 GMT 1994
  // <- 1994-11-06T08:49:37Z
  var getTimestampAsISO8601 = function() {
      var m = { 'Jan' : '01', 'Feb' : '02', 'Mar' : '03', 'Apr' : '04',
                'May' : '05', 'Jun' : '06', 'Jul' : '07', 'Aug' : '08',
                'Sep' : '09', 'Oct' : '10', 'Nov' : '11', 'Dec' : '12' };
      var a = fields.timestamp.split(' ');
      var s = a[5] + '-' + m[a[1]] + '-' + a[2] + 'T' + a[3];
      // switch on timezone = a[4]
      switch (a[4]) {
        case 'GMT':
          s += 'Z';
          break;
        case 'BST':
          s += '+01:00';
          break;
        case 'EDT':
          s += '-05:00';
          break;
        case 'EST':
          s += '-06:00';
          break;
        case 'CDT':
          s += '-05:00';
          break;
        case 'CST':
          s += '-06:00';
          break;
        case 'MDT':
          s += '-05:00';
          break;
        case 'MST':
          s += '-06:00';
          break;
        case 'PDT':
          s += '-08:00';
          break;
        case 'PST':
          s += '-09:00';
          break;
        default:
      }
      return s; };

  var setTimestamp = function(timestamp) { return (fields.timestamp = timestamp); };

  var isExpired = function(now, timeRetrieved) {};

  // reference
  var getReferences = function() {
    var o = fields.reference;
    switch (typeof o) {
      case 'object':
        var a = [];
        // o.referenceCount
        // o.referenceList
        return o.referenceList;
      case 'array':
        // any checking?
        return o;
      default:
        return null;
    }
  };
  var setReferences = function(reference) { return (fields.reference = reference); };

  var getReferenceCount = function() {
    var o = fields.reference;
    switch (typeof o) {
      case 'object':
        var a = [];
        // o.referenceCount
        // o.referenceList
        return o.referenceList;
      case 'array':
        // any checking?
        return o;
      default:
        return "0";
    }
  };

  // utils
  var toString = function() {
    var s = '';
    s += 'index=' + fields.index;
    s += ' type=' + fields.type;
    s += ' data=' + fields.data;
    return s;
  };

  var toStringArray = function() {
    var a = [];
    a.push('index = ' + fields.index);
    a.push('type = ' + fields.type);
    if (typeof(fields.data) == 'object') {
      a.push('data = {...}');
    }
    else {
      a.push('data = ' + fields.data);
    }
    // a.push('permission = ' + fields.permission);
    a.push('permission = ' + this.getPermissionStringLong());
    a.push('ttl = ' + this.getTTLType() + ', ' + Math.round(100*this.getTTL()/3600)/100 + 'h'); 
    a.push('timestamp = ' + this.getTimestampAsRFC822());
    // a.push('reference = ' + fields.reference);
    a.push('reference = ' + '[]');
    return a;
  };

  var duplicate = function(value) {
    var o = {};
    for (field in value) {
      o.field = value.field;
    }
    return o;
  };

  var listMethods = function() { var a = []; for (method in this) { a.push(method) } return a; };

  var getFields = function() { return fields; };

  var getFieldByName = function(name) {
    switch (name) {
      case 'index':
        return this.getIndex();
      case 'type':
        return this.getType();
      case 'data':
        return this.getData();
      case 'permission':
        return this.getPermission();
      case 'ttl':
        return this.getTTL();
      case 'timestamp':
        return this.getTimestamp();
      case 'reference':
        return this.getReference();
      default:
       return null;
    }
  };

  // HandleValue methods
  return {
    // 
    // HCL getters
    getAdminCanRead: getAdminCanRead,
    getAdminCanWrite: getAdminCanWrite,
    getAnyoneCanRead: getAnyoneCanRead,
    getAnyoneCanWrite: getAnyoneCanWrite,
    getData: getData,
    getDataAsString: getDataAsString,
    getIndex: getIndex,
    getNicerTimestampAsString: getNicerTimestampAsString,
    getPermissionString: getPermissionString,
    getReferences: getReferences,
    getTimestamp: getTimestamp,
    getTimestampAsDate: getTimestampAsDate,
    getTimestampAsString: getTimestampAsString,
    getTTL: getTTL,
    getTTLType: getTTLType,
    getType: getType,
    getTypeAsString: getTypeAsString,
    //
    // auxiliary getters
    getPermission: getPermission,
    getPermissionStringLong: getPermissionStringLong,
    getReference: getReferences,
    getReferenceCount: getReferenceCount,
    getTimestampAsRFC822: getTimestampAsRFC822,
    getTimestampAsISO8601: getTimestampAsISO8601,
    getTTLAsString: getTTLAsString,
    // 
    getFields: getFields,
    getFieldByName: getFieldByName,
    // 
    // HCL setters
    setAdminCanRead: setAdminCanRead,
    setAdminCanWrite: setAdminCanWrite,
    setAnyoneCanRead: setAnyoneCanRead,
    setAnyoneCanWrite: setAnyoneCanWrite,
    setData: setData,
    setIndex: setIndex,
    setReferences: setReferences,
    setTimestamp: setTimestamp,
    setTTL: setTTL,
    setTTLType: setTTLType,
    setType: setType,
    //
    // auxiliary setters
    setPermission: setPermission,
    //
    // HCL utils
    duplicate: duplicate,
    hasType: hasType,
    isExpired: isExpired,
    toString: toString,
    //
    // OpenHandle utils
    hasSystemType: hasSystemType,
    listMethods: listMethods,
    toStringArray: toStringArray
  };
};

/*
 * OpenHandle.AdminRecord class
 */
OpenHandle.AdminRecord = function(args) {

  var fields = {
    "adminRef" : "",
    "adminPermission" : "111111111111"
  };

  if (args != null && args != undefined
      && args != 'undefined'){
    for (var f in fields) {
      if (args[f] != null
          && args[f] != undefined
          && args[f] != 'undefined'){
        fields[f] = args[f];
      }
    }
  }

  var getAdminRef = function() {
    var o = fields.adminRef;
    switch (typeof o) {
      case 'object':
        return o.handle + '?index=' + o.handleValueIndex; 
      case 'string':
        return o;
      default:
        return null;
    }
  };

  var getAdminPermission = function() {
    var o = fields.adminPermission;
    switch (typeof o) {
      case 'object':
        var a = [];
        a.push((o.readValue == "true") ? "1" : "0");
        a.push((o.addNa == "true") ? "1" : "0");
        a.push((o.deleteHandle == "true") ? "1" : "0");
        a.push((o.addAdmin == "true") ? "1" : "0");
        a.push((o.removeValue == "true") ? "1" : "0");
        a.push((o.deleteNa == "true") ? "1" : "0");
        a.push((o.addValue == "true") ? "1" : "0");
        a.push((o.addHandle == "true") ? "1" : "0");
        a.push((o.listHandles == "true") ? "1" : "0");
        a.push((o.removeAdmin == "true") ? "1" : "0");
        a.push((o.modifyValue == "true") ? "1" : "0");
        a.push((o.modifyAdmin == "true") ? "1" : "0");
        return a.join("");
      case 'string':
        // any checking?
        return o;
      default:
        return null;
    }
  };

  var AdminPerms = [
    'readValue' , 'addNa' , 'deleteHandle' , 'addAdmin' ,
    'removeValue' , 'deleteNa' , 'addValue' , 'addHandle' ,
    'listHandles' , 'removeAdmin' , 'modifyValue' , 'modifyAdmin'
  ];

  var getAdminPerm = function(perm) { return this.getAdminPermission()[perm] == 1 ? true : false; };
  var setAdminPerm = function(perm) { return (fields.permission[perm] = 1); };

  var getAdminPermissionString = function() {
    var a = [];
    var getAdminPerm = function(perm) { return getAdminPermission()[perm] == 1 ? true : false; };
    for (var i = 0; i < AdminPerms.length; i++) {
      // a.push(this.getAdminPerm(i) ? 'r' : '-');
      a.push(getAdminPerm(i) ? 'r' : '-');
    }
    return a.join("");
  };

  var getAdminPermissionStringLong = function() {
    var a = [];
    var getAdminPerm = function(perm) { return getAdminPermission()[perm] == 1 ? true : false; };
    for (var i = 0; i < AdminPerms.length; i++) {
      // if (this.getAdminPerm(i)) { a.push(AdminPerms[i]); }
      if (getAdminPerm(i)) { a.push(AdminPerms[i]); }
    }
    return a.join(", ");
  };

  var toString = function() {
    return this.getAdminRef() + "\n[ " + this.getAdminPermissionStringLong() + " ]\n";
  };

  var listMethods = function() { var a = []; for (method in this) { a.push(method); } return a; };

  // AdminRecord methods
  return {
    //
    // getters
    getAdminRef: getAdminRef,
    getAdminPermission: getAdminPermission,
    getAdminPermissionString: getAdminPermissionString,
    getAdminPermissionStringLong: getAdminPermissionStringLong,
    //
    // utils
    toString: toString,
    listMethods: listMethods
  }

};

/*
 * OpenHandle.ParseHandle class
 */
OpenHandle.ParseHandle = function(args) {

  var fields = {};
  for (name in (OpenHandle.HandleValue().getFields())) {
    fields[name] = [];
  }
  var uri = [];
 
  // support optional scheme component
  // a) toplevel URI scheme - either "doi:" or "hdl:"
  // b) subnamepsace of "info:" - either "info:doi/" or "info:hdl/"
  // c) link resolver - either "http://dx.doi.org/" or "http://hdl.handle.net/"
  // support both new-style parameters (URI querystrings), and old-style (prefix notation)
  // new-style: ...?index=1&type=email
  // old-style: (index=1&type=email)@...
  // support repeated keywords by saving to value lists, i.e. index=1&index=2&index=3&...
  // support multiple values per keyword (comma separated), i.e. index=1,2&index=3&...
  if (typeof args == "string") {
    var a = args.match(/(doi:|hdl:|info:doi\/|info:hdl\/|http:\/\/dx.doi.org\/|http:\/\/hdl.handle.net\/)*(\(([^\@]+)\)@)*(([^\?\/]+)\/([^\?]+))(\?(.*))*/);
  }
  if (a) {
    uri = a;
  }
  var scheme = uri[1];
  var handle = uri[4];
  var prefix = uri[5];
  var suffix = uri[6];
  var qs = uri[7];
  var legacy_qs = uri[2];

  var query = false;
  if (qs || legacy_qs) {
    if (qs) {
      var params = qs.split('&');
    }
    else if (legacy_qs) {
      var params = legacy_qs.split(';');
    }
    for (var i = 0; i < params.length; i++) {
      var param = params[i].match(/([a-z]+)=([,\w]+)/);
      switch (param[1]) {
        case 'index':
        case 'type':
        case 'data':
        case 'permission':
        case 'ttl':
        case 'timestamp':
        case 'reference':
          var p = param[2].split(',')
          for (var j = 0; j < p.length; j++) {
            fields[param[1]].push(p[j]);
            query = true;
          }
          break;
        default:
      }
    }
  }

  // getters
  var getScheme = function() { return scheme; };
  var getHandle = function() { return handle; };
  var getFields = function() { return fields; };
  var getQuery = function() { return query; };
  var getQueryString = function() { return qs || legacy_qs; };

  // utils
  var listMethods = function() { var a = []; for (method in this) { a.push(method); } return a; };

  // ParseHandle methods
  return {
    //
    // getters
    getScheme: getScheme,
    getHandle: getHandle,
    getFields: getFields,
    getQuery: getQuery,
    getQueryString: getQueryString,
    //
    // utils
    listMethods: listMethods
  }
};

/*
 * OpenHandle.ServerInfo class
 */
OpenHandle.ServerInfo = function(args) {

  var fields = {
  };

  if (args != null && args != undefined
      && args != 'undefined'){
    for (var f in fields) {
      if (args[f] != null
          && args[f] != undefined
          && args[f] != 'undefined'){
        fields[f] = args[f];
      }
    }
  }

  var cloneServerInfo = function() {
  }

  var equals = function() {
  }

  var getAddressString = function() {
  }

  var getInetAddress = function() {
  }

  var getPublicKey = function() {
  }

  var interfaceWithProtocol = function() {
  };

  var toString = function() {
    return ""
  };

  var listMethods = function() { var a = []; for (method in this) { a.push(method); } return a; };

  // ServerInfo methods
  return {
    //
    // getters
    getAddressString: getAddressString,
    getInetAddress: getInetAddress,
    getPublicKey: getPublicKey,
    //
    // utils
    cloneServerInfo: cloneServerInfo,
    equals: equals,
    interfaceWithProtocol: interfaceWithProtocol,
    toString: toString,
    listMethods: listMethods
  }

};

/*
 * OpenHandle.SiteInfo class
 */
OpenHandle.SiteInfo = function(args) {

  var fields = {
    "version" : "" ,
    "protocolVersion" : {
       "majorProtocolVersion" : "" ,
       "minorProtocolVersion" : ""
    } ,
    "serialNumber" : "" ,
    "primaryMask" : {
      "primary" : "" ,
      "multiPrimary" : ""
    } ,
    "hashOption" : "" ,
    "hashFilter" : "" ,
    "attributes" : [
      {
        "name" : "" ,
        "value" : ""
      }
    ] ,
    "numOfServer" : "" ,
    "serverRecords" : [
      {
        "serverID" : "" ,
        "address" : "" ,
        "publicKeyRecord" : "" ,
        "serviceInterface" : {
           "interfaceCounter" : "" ,
           "interface" : {
             "serviceType" : "" ,
             "transmissionProtocol" : "" ,
             "portNumber" : ""
           }
         }
       }
    ]
  };

  if (args != null && args != undefined
      && args != 'undefined'){
    for (var f in fields) {
      if (args[f] != null
          && args[f] != undefined
          && args[f] != 'undefined'){
        fields[f] = args[f];
      }
    }
  }

  var getAttribute = function() {
  };

  var getHandleHash = function() {
  }

  var determineServerNum = function() {
  }

  var determineServer = function() {
  }

  var toString = function() {
    // Change fields.serverRecords[] into comma-and-space-separated string
    var servList = "";
    if (fields.serverRecords != null) {
      servList = servList + fields.serverRecords[0];
      for (var i = 1; i < fields.serverRecords.length; i++) {
        servList += ", " + fields.serverRecords[i];
      }
    }

    return "version: "
      + fields.protocolVersion.majorProtocolVersion
      + '.'
      + fields.protocolVersion.minorProtocolVersion
      + "; serial:"
      + fields.serialNumber
      + "; primary:"
      + (fields.primaryMask.primary ? "y; " : "n; ")
      + "servers=["
      + servList
      + "]";
  };

  var listMethods = function() { var a = []; for (method in this) { a.push(method); } return a; };

  // SiteInfo methods
  return {
    //
    // getters
    getAttribute: getAttribute,
    getHandleHash: getHandleHash,
    //
    // utils
    determineServerNum: determineServerNum,
    determineServer: determineServer,
    toString: toString,
    listMethods: listMethods
  }

};

/*
 * OpenHandle.Util class
 */
OpenHandle.Util = function() {

  // encode string for XML
  var entifyString = function(s) {
    return s.replace(/\&/g, "&amp;").replace(/\</g, "&lt;").replace(/\>/g, "&gt;");
  };

  // encode string array for XML
  var entifyStringArray = function(sa) {
    var a = [];
    for (var i = 0; i < sa.length; i++) {
      a.push(entifyString(sa[i]));
    }
    return a;
  };

  var getHandleParam = function(uri) {
    // var re = new RegExp("[\\?&]" + name + "=([^&#]*)");
    var re = new RegExp("[\\?&]handle=([^&#]*)");
    var results = re.exec(uri);
    return (results == null) ? "" : results[1];
  };
  
  // var getHandleData = function(jQuery, handle, callback, server) {
  var getHandleData = function(handle, callback, server) {
console.log("server: ", server);
    var server = server || new OpenHandle.Common().getConstant("OPENHANDLE_SERVER");
console.log("server: ", server);
    var server = new OpenHandle.Common().getConstant("OPENHANDLE_SERVER");
console.log("server: ", server);

    jQuery.getJSON(server + "?callback=?",
      { "id": handle, "format": "json" }, callback);
  };

  var helloWorld = function(args) {
    var json = args || OpenHandle.Toolbox().getTestHandleData();
    var h = new OpenHandle.Handle(json);
    var hv = h.getValues();
    var s = "The handle <" + h.getHandle() + "> has " + hv.length + " values:\n\n";;
    for (var i = 0; i < hv.length; i++) {
      s += "value #" + (i + 1) + ":\n  ";
      s += new OpenHandle.HandleValue(hv[i]).toStringArray().join("\n  ");
      s += "\n\n";
    }
    return s;
  }

  var listMethods = function() { var a = []; for (method in this) { a.push(method); } return a; };

  return {
    entifyString: entifyString,
    entifyStringArray: entifyStringArray,
    getHandleData: getHandleData,
    getHandleParam: getHandleParam,
    helloWorld: helloWorld,
    listMethods: listMethods
  }

};

/*
 * OpenHandle.ValueReference class
 */
OpenHandle.ValueReference = function(args) {

  var fields = {
    "handle" : "",
    "index" : ""
  };

  if (args != null && args != undefined
      && args != 'undefined'){
    for (var f in fields) {
      if (args[f] != null
          && args[f] != undefined
          && args[f] != 'undefined'){
        fields[f] = args[f];
      }
    }
  }

  var getHandle = function() {
    return fields.handle;
  };

  var getIndex = function() {
    return fields.index;
  }

  var equals = function(o) {
    return (this.getHandle() == o.getHandle()) 
           && (this.getIndex() == o.getIndex());
  };

  var toString = function() {
    return this.getIndex() + ":" + this.getHandle();
  };

  var listMethods = function() { var a = []; for (method in this) { a.push(method); } return a; };

  // ValueReference methods
  return {
    //
    // getters
    getHandle: getHandle,
    getIndex: getIndex,
    //
    // utils
    equals: equals,
    toString: toString,
    listMethods: listMethods
  }

};

/*
 * EOF
 */
