//(function(){
/*
 * OpenHandle, 0.1.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>
 *
 */

  
// ParseHandle class
var ParseHandle = function(h) {

  // h = 'hdl:10.1000/1?index=1&type=url,hs_admin&index=100&timestamp=789';
  var a = [ 'index', 'type', 'data', 'permission', 'ttl', 'timestamp', 'reference' ];
  var fields = {};
  for (var i = 0; i < a.length; i++) {
    fields[a[i]] = [];
  }
  var uri = h.match(/(hdl:|doi:)*([^\?]+)(\?(.*))*/);
  // print(uri.join("\n"));
  
  var scheme = uri[1];
  var handle = uri[2];
  var qs = uri[3];
  var query = false;
  if (qs) {
    var params = 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; };

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

// OpenHandle class
var OpenHandle = function(args) {

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

  var testHandle =
  {
    "comment": "OpenHandle (JSON) - see http://code.google.com/p/openhandle/",
    "handle": "hdl:10.1000/1",
    "handleStatus": {
      "code": "1",
      "message": "SUCCESS"
    },
    "handleValues": [
      {
        "index": "100",
        "type": "HS_ADMIN",
        "data": {
          "adminRef": "hdl:0.NA/10.1000?index=200",
          "adminPermission": "111111110111"
        },
        "permission": "1110",
        "ttl": "+86400",
        "timestamp": "Thu Apr 13 16:08:57 BST 2000",
        "reference": []
      },
      {
        "index": "1",
        "type": "URL",
        "data": "http://www.doi.org/index.html",
        "permission": "1110",
        "ttl": "+86400",
        "timestamp": "Fri Sep 10 20:49:59 BST 2004",
        "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];
      }
    }
  }

  // constrain handle values to those matched in query
  // query is value field object from ParseHandle().getFields()
  var getHandleValuesByQuery = function(query) {
    var hv = fields.handleValues;   
    var hv_ = [];
    var matched = false;
    for (var i = 0; i < hv.length; i++) {
      var value = new 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 (query[field][j].toLowerCase() == f.toLowerCase()) {
            matched = true; 
            break;
          }
        }
        if (matched) {
          break;
        }
      }
      if (matched) {
        hv_.push(value.getFields()); 
      }
    }
    return hv_;
  };

  // getters
  var getFields = function() { return fields; };
  //
  var getComment = function() { return fields.comment; };
  var getHandle = function() { return fields.handle; };
  var getHandleString = function() { return fields.handle.substr(4); };
  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 getTestHandle = function() { return testHandle; };
  var isStatusOK = function() { return (fields.handleStatus.message == 'SUCCESS'); };
  var isValid = function() {
    var valid = true;
    return valid;
  };

  // OpenHandle methods
  return {
    //
    // getters
    getFields: getFields,
    //
    getComment: getComment,
    getHandle: getHandle,
    getHandleString: getHandleString,
    getHandleStatus: getHandleStatus,
    getHandleValues: getHandleValues,
    getHandleValuesByQuery: getHandleValuesByQuery,
    //
    // setters
    setComment: setComment,
    setHandle: setHandle,
    setHandleStatus: setHandleStatus,
    setHandleValues: setHandleValues,
    //
    // utils
    listMethods: listMethods,
    getTestHandle: getTestHandle,
    isStatusOK: isStatusOK,
    isValid: isValid
  };
};

// AdminRecord class
var 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 = [];
    for (var i = 0; i < AdminPerms.length; i++) {
      a.push(this.getAdminPerm(i) ? 'r' : '-');
    }
    return a.join("");
  };

  var getAdminPermissionStringLong = function() {
    var a = [];
    for (var i = 0; i < AdminPerms.length; i++) {
      if (this.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,
    getAdminPerm: getAdminPerm,
    getAdminPermission: getAdminPermission,
    getAdminPermissionString: getAdminPermissionString,
    getAdminPermissionStringLong: getAdminPermissionStringLong,
    //
    // utils
    toString: toString,
    listMethods: listMethods
  }

};

// HandleValue class
var HandleValue = function(args) {

  var TTL_TYPE_RELATIVE = 0;
  var TTL_TYPE_ABSOLUTE = 1;
  var MAX_RECOGNIZED_TTL = 86400*2;

  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 SUBTYPE_SEPARATOR = '.';
    return (this.getType() == type) ||
           ((type.length < this.getType().length) &&
            (this.getType()[type.length] == SUBTYPE_SEPARATOR) &&
            (this.getType().substr(0, type.length) == type));
  };
  var hasTypeSystem = function() { return (fields.type.substr(0,3) == 'HS_'); };

  // data
  var getData = function() { return fields.data; };
  var getDataAsString = function() { return fields.data; };

  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) { fields.permission[0] = hasPermission ? 1 : 0; };
  var setAdminCanWrite = function(hasPermission) { fields.permission[1] = hasPermission ? 1 : 0; };
  var setAnyoneCanRead = function(hasPermission) { fields.permission[2] = hasPermission ? 1 : 0; };
  var setAnyoneCanWrite = function(hasPermission) { 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; };

  // 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; };

  // 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 'timestamp':
        return this.getTimestamp();
      case 'ttl':
        return this.getTTL();
      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,
    getTimestampAsRFC822: getTimestampAsRFC822,
    getTimestampAsISO8601: getTimestampAsISO8601,
    // 
    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
    hasTypeSystem: hasTypeSystem,
    listMethods: listMethods,
    toStringArray: toStringArray
  };
};

// })();
