text: 'use strict';
  
  var constants = require('./constants');
  var request = require('request');
  var LRU = require('lru-cache');
  var validUrl = require('valid-url');
  
  // use environment variable DEBUG with value 'xssec:*' for trace/error messages
  var debug = require('debug');
  var debugTrace = debug('xssec:keycache');
  var debugError = debug('xssec:keycache');
  
  debugError.log = console.error.bind(console);
  debugTrace.log = console.log.bind(console);
  
  exports.KeyCache = KeyCache;
  
  function KeyCache(cacheSize, cacheEntryExpirationTimeInMinutes,
          callUaaToReadTokenKeys, tokenKeyPath) {
      debugTrace('Initializing KeyCache with parameters cacheSize (' + cacheSize
              + '), cacheEntryExpirationTimeInMinutes ('
              + cacheEntryExpirationTimeInMinutes + '), callUaaToReadTokenKeys ('
              + callUaaToReadTokenKeys + '), tokenKeyPath (' + tokenKeyPath
              + ').');
  
      // validate input
      if (!cacheSize || cacheSize < 5) {
          var error = new Error(
                  'Parameter cacheSize invalid. To initialize the KeyCache, specify the mandatory numeric parameter cacheSize (>= 5).');
          throw error;
      }
  
      if (cacheEntryExpirationTimeInMinutes === undefined) {
          debugTrace('Parameter cacheEntryExpirationTimeInMinutes undefined. Setting default ('+constants.KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES+') for this optional parameter from configuration.');
          cacheEntryExpirationTimeInMinutes = constants.KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES;
      } else if (cacheEntryExpirationTimeInMinutes < 0) {
          var error = new Error(
                  'Parameter cacheEntryExpirationTimeInMinutes invalid. To initialize the KeyCache, you can specify the optional numeric parameter cacheEntryExpirationTimeInMinutes (>= 0).');
          throw error;
      }
  
      if (callUaaToReadTokenKeys === undefined) {
          debugTrace('Parameter callUaaToReadTokenKeys undefined. Setting default (true) for this optional boolean parameter from configuration.');
          this.callUaaToReadTokenKeys = true;
      } else if (!((callUaaToReadTokenKeys === true) || (callUaaToReadTokenKeys === false))) {
          var error = new Error(
                  'Parameter callUaaToReadTokenKeys invalid. To initialize the KeyCache, you can specify the optional boolean parameter callUaaToReadTokenKeys (true, false).');
          throw error;
      } else {
          this.callUaaToReadTokenKeys = callUaaToReadTokenKeys;
      }
  
      if (tokenKeyPath === undefined) {
          debugTrace('Parameter tokenKeyPath undefined. Setting default ("'+constants.KEYCACHE_DEFAULT_TOKENKEY_PATH+'") for this optional string parameter from configuration.');
          this.tokenKeyPath = constants.KEYCACHE_DEFAULT_TOKENKEY_PATH;
      } else {
          this.tokenKeyPath = tokenKeyPath;
      }
  
      this.lruCache = new LRU({
          max : cacheSize,
          maxAge : cacheEntryExpirationTimeInMinutes * 60000
      });
  }
  
  KeyCache.prototype.addKey = function addKey(keyId, key) {
      if ((keyId === null) || (keyId === undefined)) {
          var error = new Error(
                  'Parameter keyId null or undefined. To add a key to the KeyCache with function KeyCache.addKey, you need to specify parameter keyId.');
          throw error;
      }
      if ((key === null) || (key === undefined)) {
          var error = new Error(
                  'Parameter key null or undefined. To add a key to the KeyCache with function KeyCache.addKey, you need to specify parameter key.');
          throw error;
      }
      debugTrace('Adding key to cache. keyID: "' + keyId + '", key: "' + key
              + '".');
      this.lruCache.set(keyId, key);
  };
  
  KeyCache.prototype.getKey = function getKey(keyId, uaaUrl, cb) {
      var self = this;
  
      if ((keyId === null) || (keyId === undefined)) {
          var error = new Error(
                  'Parameter keyId null or undefined. To read a key from the KeyCache with function KeyCache.getKey, you need to specify parameter keyId.');
          return process.nextTick(function() {
              cb(error, null);
          });
      }
      if ((uaaUrl === null) || (uaaUrl === undefined)) {
          var error = new Error(
                  'Parameter uaaUrl null or undefined. To enable the KeyCache reading keys from the UAA which are yet unavailable in the cache, you need to specify parameter uaaUrl as a valid https URL.');
          return process.nextTick(function() {
              cb(error, null);
          });
      }
      if (validUrl.isHttpsUri(uaaUrl) === undefined) {
          var error = new Error(
                  'Parameter uaaUrl is not a valid https URL. To enable the KeyCache reading keys from the UAA which are yet unavailable in the cache, you need to specify parameter uaaUrl as a valid https URL.');
          return process.nextTick(function() {
              cb(error, null);
          });
      }
  
      debugTrace('Looking for key with keyID: "' + keyId + '" in cache.');
      // Check whether keyid is in cache
      var tmpResult = this.lruCache.get(keyId);
      if (tmpResult !== undefined) {
          debugTrace('Key with keyID: "' + keyId
                  + '" found in cache. Returning key "' + tmpResult + '".');
          return process.nextTick(function() {
              cb(null, tmpResult);
          });
      } else if (this.callUaaToReadTokenKeys === false) {
          // no cache hit, but configuration says not to try to obtain key from
          // UAA
          var error = new Error('Key with keyID: "' + keyId
                  + '" not found in cache. Configuration says not to query UAA.');
          return process.nextTick(function() {
              cb(error, null);
          });
      } else {
          // try to obtain key from UAA
          var options = {
              url : uaaUrl + this.tokenKeyPath
          };
          debugTrace('Key with keyID: "' + keyId
                  + '" not found in cache. Querying keys from UAA via URL "'
                  + options.url + '".');
          request
                  .get(
                          options,
                          function(err, response, body) {
                              if (err) {
                                  debugError('An error occurred when reading the token keys from '
                                          + options.url
                                          + ': '
                                          + err.message
                                          + err.stack);
                                  var error = new Error(
                                          'An error occurred when reading the token keys from '
                                                  + options.url + ': '
                                                  + err.message + err.stack);
                                  return process.nextTick(function() {
                                      cb(error, null);
                                  });
                              }
                              if (response.statusCode !== 200) {
                                  debugTrace('Call was not successful. Error Code: '
                                          + response.statusCode);
                                  var error = new Error(
                                          'Call was not successful. Error Code: '
                                                  + response.statusCode);
                                  return process.nextTick(function() {
                                      cb(error, null);
                                  });
                              }
                              var json = null;
                              try {
                                  json = JSON.parse(body);
                                  for (var i = 0; i < json.keys.length; i++) {
                                      // Note: The following code removes line
                                      // breaks before adding the key to the cache
                                      self.addKey(json.keys[i].kid,
                                              json.keys[i].value.replace(
                                                      /(\r\n|\n|\r)/gm, ''));
                                  }
                              } catch (e) {
                                  debugError('Error parsing response from UAA: '
                                          + e)
                                  var error = new Error(
                                          'Error parsing response from UAA: ' + e)
                                  return process.nextTick(function() {
                                      cb(error, null);
                                  });
                              }
                              var tmpResult = self.lruCache.get(keyId);
                              if (tmpResult !== undefined) {
                                  debugTrace('Key with keyID: "' + keyId
                                          + '" found in cache. Returning key "'
                                          + tmpResult + '".');
                                  return process.nextTick(function() {
                                      cb(null, tmpResult);
                                  });
                              } else {
                                  var error = new Error(
                                          'Obtained token keys from UAA, but key with requested keyID "'
                                                  + keyId
                                                  + '" still not found in cache.');
                                  return process.nextTick(function() {
                                      cb(error, null);
                                  });
                              }
                          });
      }
  };
  