/*
	Licensed Material - Property of IBM

	(C) Copyright 2015, 2016 IBM Corp.

	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
*/


'use strict';

var moduleName = 'adapter';

//private modules
var errors = require('mdo-errors');
var strings = require('ibm-strings');
var mdoServerEndpoints = require('./constants/mdo-server-endpoints');
var defaultOptions = require('./constants/defaultOptions');

//3rd party modules
var log = require('npmlog');
var Q = require('q');
var FormData = require('form-data');
var http = require('http');
var Verror = require('verror');


/**
 * Obtains the specified runtime's adapters details from the MFP server and returns a JSON
 * object containing those details.
 *
 *
 * Example JSON ouput
 *
 * {
 * "productVersion": "8.0.0.00-20151202-1806",
 * "totalListSize": 1,
 * "items": [
 *   {
 *     "resourceType": "APP_DESCRIPTOR",
 *     "resourceHash": "9987b56b6ecea73cb6e937f0df4ec68cbbabf21c17906d578ef21d82b6f56327",
 *     "resourceName": "io.cordova.hellocordova$android$0.0.1",
 *     "displayName": "io.cordova.hellocordova",
 *     "deployTime": "2015-12-07T22:00:11.322Z",
 *     "project": {
 *       "name": "mfp"
 *     },
 *     "link": "http://localhost:9080/mfpadmin/management-apis/2.0/runtimes/mfp/applications/io.cordova.hellocordova",
 *     "name": "io.cordova.hellocordova",
 *     "envs": [
 *       {
 *         "resourceType": "APP_DESCRIPTOR",
 *         "resourceHash": "9987b56b6ecea73cb6e937f0df4ec68cbbabf21c17906d578ef21d82b6f56327",
 *         "resourceName": "io.cordova.hellocordova$android$0.0.1",
 *         "displayName": "io.cordova.hellocordova",
 *         "deployTime": "2015-12-07T22:00:11.322Z",
 *         "project": {
 *           "name": "mfp"
 *         },
 *         "link": "http://localhost:9080/mfpadmin/management-apis/2.0/runtimes/mfp/applications/io.cordova.hellocordova/android",
 *         "environment": "android",
 *         "versions": [
 *           {
 *             "resourceType": "APP_DESCRIPTOR",
 *             "resourceHash": "9987b56b6ecea73cb6e937f0df4ec68cbbabf21c17906d578ef21d82b6f56327",
 *             "resourceName": "io.cordova.hellocordova$android$0.0.1",
 *             "displayName": "io.cordova.hellocordova",
 *             "deployTime": "2015-12-07T22:00:11.322Z",
 *             "project": {
 *               "name": "mfp"
 *             },
 *             "link": "http://localhost:9080/mfpadmin/management-apis/2.0/runtimes/mfp/applications/io.cordova.hellocordova/android/0.0.1",
 *             "version": "0.0.1"
 *           }
 *         ]
 *       }
 *     ]
 *   }
 * ],
 * "nextPageBookmark": "",
 * "prevPageBookmark": "",
 * "startIndex": 0,
 * "pageNumber": 0,
 * "pageSize": 100
 * }
 *
 * Parameters:
 * serverDetails = {
 *    protocol: <http-protocol,     // Optional String. http || https. defaults to http
 *    host: <host-dns-name>,        // Optional String. defaults to localhost
 *    port: <host-port>,            // Optional Numeric. defaults to 9080
 *    contextRoot: <host-context>,  // Optional String. Server root context.  defaults to 'mfpadmin'
 *    userName: <user-id>,          // Optional String. Server login id. defaults to 'admin'
 *    userPassword: <user-pwd>,     // Optional String. Server login id password.  defaults to 'admin'
 *    runtime: <project-runtime>,   // Mandatory String. Project name (aka runtime, project context root)
 *    timeout: <http-request-timeout>,// Optional Numeric (milliseconds).  http request timeout; defaults to 30000 (30 seconds)
 *                                  // Overrideable by MFP_HTTP_DEFAULT_TIMEOUT env variable
 *                                  // When timeout occurs, the request is aborted and an error is returned.
 * }
 *
 * Returns:
 * Q.deferred.promise.
 *   On success, a JSON object (defined by MFP server) representing the specified runtime details is returned
 *   On failure, an Error object is returned.  Includes statusCode (HTTP code), errors (array from server)
 *     and warnings (array from server).
 */

var getAdapters = function (serverDetails) {
  var d = Q.defer();

  log.silly('getAdapters', 'Arguments: '+ (serverDetails ? JSON.stringify(serverDetails,null,' ') : serverDetails));

  var protocol     = (serverDetails && serverDetails.protocol ? serverDetails.protocol : defaultOptions.PROTOCOL) + ':';  //jshint ignore:line
  var host         = (serverDetails && serverDetails.host ? serverDetails.host : defaultOptions.HOST);
  var port         = (serverDetails && serverDetails.port ? serverDetails.port : defaultOptions.PORT);
  var instanceID   = (serverDetails && (typeof serverDetails.path !== 'undefined') && serverDetails.path.length > 1 ? serverDetails.path : defaultOptions.PATH);
  var contextRoot  = (serverDetails && serverDetails.contextRoot ? serverDetails.contextRoot : defaultOptions.CONTEXT_ROOT);
  var userName     = (serverDetails && serverDetails.userName ? serverDetails.userName : defaultOptions.CREDENTIALS);
  var userPassword = (serverDetails && serverDetails.userPassword ? serverDetails.userPassword : defaultOptions.CREDENTIALS);
  var runtime      = (serverDetails && serverDetails.runtime ? serverDetails.runtime : null);
  var timeout      = (serverDetails && serverDetails.timeout ? parseInt(serverDetails.timeout)*1000 : defaultOptions.TIMEOUT_MILLISECONDS);

  // Set trace level
  log.level = global.MDO_CLI_LOG_LEVEL ? global.MDO_CLI_LOG_LEVEL : 'info';

  // Log the input parameters
  log.silly('getAdapters', 'Arguments (with defaults): '+ JSON.stringify(serverDetails,null,' '));
  log.silly('getAdapters', 'HTTP timeout (milliseconds): '+ timeout);

  // runtime argument is mandatory.  If not specified, fail the request
  if (!runtime) {
    var err = new Error('Missing argument \'runtime\'');
    err.name = 'InvalidArgumentError';  // Alas, an equivalent error is not defined by JavaScript
    log.verbose('getAdapters', err);
    d.reject(err);
    return d.promise;
  }

  var path = strings.format(instanceID+mdoServerEndpoints.GET_ADAPTERS, contextRoot, runtime);
  var get = require('../rest-client/rest-client.js').get;
  var requestPromise = get(host, port, path, protocol, userName, userPassword, timeout);
  requestPromise.then (function (rspObject) {
    if (rspObject === undefined || rspObject.resp === undefined || rspObject.body === undefined || rspObject.httperr === undefined) {
      var formerr = new Verror(new Error('server response is undefined'), 'server response error');
      formerr.name = 'GetAdaptersError';
      d.reject(formerr);
    } else {
      var pPromise = _processResponse (rspObject.resp, rspObject.body, rspObject.httperr);
      pPromise.then (function (resp) {
        log.silly(moduleName, 'Response: ' + resp.statusCode);
        d.resolve(resp);
      }, function (err) {
        d.reject(err);
      });
    }
  }, function (err) {
    log.verbose(moduleName, 'form error: ', err);
    var formerr = new Verror(err, 'failed to get adapters');
    formerr.name = 'GetAdaptersError';
    d.reject(formerr);
  });

  /*
   * Function that processes the completed (i.e. entire body has been read)
   * HTTP response received from the server.
   *
   * Parameters:
   *   resp    - Response object
   *   body    - Response body string
   *   httperr - Error object representing HTTP status
   *   d       - deferred promise to be resolved/rejected
   */
  var _processResponse = function(resp, body, httperr, d) {
    log.silly('getAdapters', 'processResp: resp data completely read.  status: '+resp.statusCode);

    // HTTP Resp Status    Resp Body
    // ================    ===============================================
    // 200                 Expect JSON parsable body
    //                     If body parse failure -> reject with parse error + HTTP status
    //                     If body parse OK -> return SUCCESS with resp body as JSON
    // !200                If body does not exist -> reject with HTTP err
    //                     If body parse failure -> reject with parse error + HTTP status
    //                     reject with err + body.message (if exists)
    //                                     + body.additionalMessage (if exists)
    //                                     + body.errors (if exists)
    //                                     + body.warnings (if exists)

    var pD = Q.defer();

    // If the body exists, parse it
    var jsonBody = null;
    if (body) {
      log.silly('getAdapters', 'processResp: resp data completely read.  body: '+body);
      try {  // Need try/catch around JSON.parse as can throw an exception
        jsonBody = JSON.parse(body); // Throws exception on failure
        log.silly('getAdapters', 'processResp: JSON body: '+ JSON.stringify(jsonBody, null,' '));
      }
      catch(e) {  // Response body parsing syntax error
        log.verbose('getAdapters', 'processResp: parse exception: '+e.stack);
        var parseerr = new Verror(httperr, e.message);
        parseerr.name = e.name;
        var err = new Verror(parseerr, 'failed to retrieve runtime information');
        err.name = 'GetAdaptersError';
        pD.reject(err);
      }
    }

    // Did we receive an 'OK' response?
    // If so, the body must contain a JSON object with a status of 'SUCCESS'
    if (resp.statusCode === 200) {
      log.verbose('getAdapters', 'processResp: success');
      pD.resolve(jsonBody);  // Success.  Nothing to return
    }
    else { // Bad HTTP response
      if (!jsonBody) {  // No response body; just reject with HTTP resp code
        var err = new Verror(httperr, 'failed to retrieve runtime information');
        err.name = 'GetAdaptersError';
        log.verbose('getAdapters', 'processResp: failure: '+err);
        pD.reject(err);
      }
      else {  // Got body, reject with HTTP + body parts that exist
        var errors = (jsonBody.transaction && jsonBody.transaction.errors) ? jsonBody.transaction.errors : null;
        var errMsgs = _concatErrDetails(errors);
        var warnings = (jsonBody.transaction && jsonBody.transaction.warnings) ? jsonBody.transaction.warnings : null;
        var warnMsgs = _concatWarnDetails(warnings);  // jshint ignore:line

        // Concatentate pertinent response message strings
        var serverErrMsg = jsonBody.message || '';
        serverErrMsg += ((serverErrMsg && jsonBody.additionalMessage) ? ' : ' : '') + (jsonBody.additionalMessage || '');
        serverErrMsg += ((serverErrMsg && errMsgs) ? ' : ' : '') + (errMsgs || '');
        // Since this is a failure; focus just on error messsages
        //serverErrMsg += ((serverErrMsg && warnMsgs) ? ' : ' : '') + (warnMsgs || '');

        var resperr = new Verror(httperr, serverErrMsg);
        resperr.name = 'GetAdaptersError';
        var newEerr = new Verror(resperr, 'failed to retrieve runtime information');
        newEerr.name = 'GetAdaptersError';
        newEerr.statusCode = resp.statusCode;
        newEerr.errors = errors;
        newEerr.warnings = warnings;
        log.verbose('getAdapters', 'processResp: failure: '+JSON.stringify(newEerr, null, 2));
        pD.reject(newEerr);
      }
    }
    return pD.promise;
  };

  return d.promise;
};

var deleteAdapter = function (serverDetails) {
  var d = Q.defer();

  log.silly('deleteAdapter', 'Arguments: '+ (serverDetails ? JSON.stringify(serverDetails,null,' ') : serverDetails));

  var protocol     = (serverDetails && serverDetails.protocol ? serverDetails.protocol : defaultOptions.PROTOCOL) + ':';  //jshint ignore:line
  var host         = (serverDetails && serverDetails.host ? serverDetails.host : defaultOptions.HOST);
  var port         = (serverDetails && serverDetails.port ? serverDetails.port : defaultOptions.PORT);
  var instanceID   = (serverDetails && (typeof serverDetails.path !== 'undefined') && serverDetails.path !=="/" ? serverDetails.path : defaultOptions.PATH);
  var contextRoot  = (serverDetails && serverDetails.contextRoot ? serverDetails.contextRoot : defaultOptions.CONTEXT_ROOT);
  var userName     = (serverDetails && serverDetails.userName ? serverDetails.userName : defaultOptions.CREDENTIALS);
  var userPassword = (serverDetails && serverDetails.userPassword ? serverDetails.userPassword : defaultOptions.CREDENTIALS);
  var runtime      = (serverDetails && serverDetails.runtime ? serverDetails.runtime : null);
  var adapter      = (serverDetails && serverDetails.adapter ? serverDetails.adapter : null);
  var timeout      = (serverDetails && serverDetails.timeout ? parseInt(serverDetails.timeout)*1000 : defaultOptions.TIMEOUT_MILLISECONDS);

  // Set trace level
  log.level = global.MDO_CLI_LOG_LEVEL ? global.MDO_CLI_LOG_LEVEL : 'info';

  // Log the input parameters
  log.silly('deleteAdapter', 'Arguments (with defaults): '+ JSON.stringify(serverDetails,null,' '));
  log.silly('deleteAdapter', 'HTTP timeout (milliseconds): '+ timeout);

  // runtime argument is mandatory.  If not specified, fail the request
  if (!runtime) {
    var err = new Error('Missing argument \'runtime\'');
    err.name = 'InvalidArgumentError';  // Alas, an equivalent error is not defined by JavaScript
    log.verbose('deleteAdapter', err);
    d.reject(err);
    return d.promise;
  }

  var path = strings.format(instanceID+mdoServerEndpoints.DELETE_ADAPTER, contextRoot, runtime, adapter);
  var deleteReq = require('../rest-client/rest-client.js').delete;
  var requestPromise = deleteReq(host, port, path, protocol, userName, userPassword, timeout);
  requestPromise.then (function (rspObject) {
    if (rspObject === undefined || rspObject.resp === undefined || rspObject.body === undefined || rspObject.httperr === undefined) {
      var formerr = new Verror(new Error('server response is undefined'), 'server response error');
      formerr.name = 'deleteAdapterError';
      d.reject(formerr);
    } else {
      var pPromise = _processResponse (rspObject.resp, rspObject.body, rspObject.httperr);
      pPromise.then (function (resp) {
        log.silly(moduleName, 'Response: ' + resp.statusCode);
        d.resolve(resp);
      }, function (err) {
        d.reject(err);
      });
    }
  }, function (err) {
    log.verbose(moduleName, 'form error: ', err);
    var formerr = new Verror(err, 'failed to delete adapters');
    formerr.name = 'deleteAdapterError';
    d.reject(formerr);
  });

  /*
   * Function that processes the completed (i.e. entire body has been read)
   * HTTP response received from the server.
   *
   * Parameters:
   *   resp    - Response object
   *   body    - Response body string
   *   httperr - Error object representing HTTP status
   *   d       - deferred promise to be resolved/rejected
   */
  var _processResponse = function(resp, body, httperr, d) {
    log.silly('deleteAdapter', 'processResp: resp data completely read.  status: '+resp.statusCode);

    // HTTP Resp Status    Resp Body
    // ================    ===============================================
    // 200                 Expect JSON parsable body
    //                     If body parse failure -> reject with parse error + HTTP status
    //                     If body parse OK -> return SUCCESS with resp body as JSON
    // !200                If body does not exist -> reject with HTTP err
    //                     If body parse failure -> reject with parse error + HTTP status
    //                     reject with err + body.message (if exists)
    //                                     + body.additionalMessage (if exists)
    //                                     + body.errors (if exists)
    //                                     + body.warnings (if exists)

    var pD = Q.defer();

    // If the body exists, parse it
    var jsonBody = null;
    if (body) {
      log.silly('deleteAdapter', 'processResp: resp data completely read.  body: '+body);
      try {  // Need try/catch around JSON.parse as can throw an exception
        jsonBody = JSON.parse(body); // Throws exception on failure
        log.silly('deleteAdapter', 'processResp: JSON body: '+ JSON.stringify(jsonBody, null,' '));
      }
      catch(e) {  // Response body parsing syntax error
        log.verbose('deleteAdapter', 'processResp: parse exception: '+e.stack);
        var parseerr = new Verror(httperr, e.message);
        parseerr.name = e.name;
        var err = new Verror(parseerr, 'failed to retrieve runtime information');
        err.name = 'deleteAdapterError';
        pD.reject(err);
      }
    }

    // Did we receive an 'OK' response?
    // If so, the body must contain a JSON object with a status of 'SUCCESS'
    if (resp.statusCode === 200) {
      log.verbose('deleteAdapter', 'processResp: success');
      pD.resolve(jsonBody);  // Success.  Nothing to return
    }
    else { // Bad HTTP response
      if (!jsonBody) {  // No response body; just reject with HTTP resp code
        var err = new Verror(httperr, 'failed to retrieve runtime information');
        err.name = 'deleteAdapterError';
        log.verbose('deleteAdapter', 'processResp: failure: '+err);
        pD.reject(err);
      }
      else {  // Got body, reject with HTTP + body parts that exist
        var errors = (jsonBody.transaction && jsonBody.transaction.errors) ? jsonBody.transaction.errors : null;
        var errMsgs = _concatErrDetails(errors);
        var warnings = (jsonBody.transaction && jsonBody.transaction.warnings) ? jsonBody.transaction.warnings : null;
        var warnMsgs = _concatWarnDetails(warnings);  // jshint ignore:line

        // Concatentate pertinent response message strings
        var serverErrMsg = jsonBody.message || '';
        serverErrMsg += ((serverErrMsg && jsonBody.additionalMessage) ? ' : ' : '') + (jsonBody.additionalMessage || '');
        serverErrMsg += ((serverErrMsg && errMsgs) ? ' : ' : '') + (errMsgs || '');
        // Since this is a failure; focus just on error messsages
        //serverErrMsg += ((serverErrMsg && warnMsgs) ? ' : ' : '') + (warnMsgs || '');

        var resperr = new Verror(httperr, serverErrMsg);
        resperr.name = 'deleteAdapterError';
        var newEerr = new Verror(resperr, 'failed to retrieve runtime information');
        newEerr.name = 'deleteAdapterError';
        newEerr.statusCode = resp.statusCode;
        newEerr.errors = errors;
        newEerr.warnings = warnings;
        log.verbose('deleteAdapter', 'processResp: failure: '+JSON.stringify(newEerr, null, 2));
        pD.reject(newEerr);
      }
    }
    return pD.promise;
  };

  return d.promise;
};

/**
 * Obtains the specified adapter's details from the MFP server and returns a JSON
 * object containing those details.
 *
 *
 *
 * Parameters:
 * serverDetails = {
 *    protocol: <http-protocol,     // Optional String. http || https. defaults to http
 *    host: <host-dns-name>,        // Optional String. defaults to localhost
 *    port: <host-port>,            // Optional Numeric. defaults to 9080
 *    contextRoot: <host-context>,  // Optional String. Server root context.  defaults to 'mfpadmin'
 *    userName: <user-id>,          // Optional String. Server login id. defaults to 'admin'
 *    userPassword: <user-pwd>,     // Optional String. Server login id password.  defaults to 'admin'
 *    runtime: <project-runtime>,   // Mandatory String. Project name (aka runtime, project context root)
 *    adapter: <adapter-id>,	    // Mandatory String. Adapter name
 *    timeout: <http-request-timeout>,// Optional Numeric (milliseconds).  http request timeout; defaults to 30000 (30 seconds)
 *                                  // Overrideable by MFP_HTTP_DEFAULT_TIMEOUT env variable
 *                                  // When timeout occurs, the request is aborted and an error is returned.
 * }
 *
 * Returns:
 * Q.deferred.promise.
 *   On success, a JSON object (defined by MFP server) representing the specified adapter details is returned
 *   On failure, an Error object is returned.  Includes statusCode (HTTP code), errors (array from server)
 *     and warnings (array from server).
 */
var getAdapter = function (serverDetails) {
  var d = Q.defer();

  log.silly('getAdapter', 'Arguments: '+ (serverDetails ? JSON.stringify(serverDetails,null,' ') : serverDetails));
  var protocol     = (serverDetails && serverDetails.protocol ? serverDetails.protocol : defaultOptions.PROTOCOL) + ':';  //jshint ignore:line
  var host         = (serverDetails && serverDetails.host ? serverDetails.host : defaultOptions.HOST);
  var port         = (serverDetails && serverDetails.port ? serverDetails.port : defaultOptions.PORT);
  var instanceID   = (serverDetails && (typeof serverDetails.path !== 'undefined') && serverDetails.path !=="/" ? serverDetails.path : defaultOptions.PATH);
  var contextRoot  = (serverDetails && serverDetails.contextRoot ? serverDetails.contextRoot : defaultOptions.CONTEXT_ROOT);
  var userName     = (serverDetails && serverDetails.userName ? serverDetails.userName : defaultOptions.CREDENTIALS);
  var userPassword = (serverDetails && serverDetails.userPassword ? serverDetails.userPassword : defaultOptions.CREDENTIALS);
  var runtime      = (serverDetails && serverDetails.runtime ? serverDetails.runtime : null);
  var adapter      = (serverDetails && serverDetails.adapter ? serverDetails.adapter : null);
  var timeout      = (serverDetails && serverDetails.timeout ? parseInt(serverDetails.timeout)*1000 : defaultOptions.TIMEOUT_MILLISECONDS);


  // Set trace level
  log.level = global.MDO_CLI_LOG_LEVEL ? global.MDO_CLI_LOG_LEVEL : 'info';

  // Log the input parameters
  log.silly('getAdapter', 'Arguments (with defaults): '+ JSON.stringify(serverDetails,null,' '));
  log.silly('getAdapter', 'HTTP timeout (milliseconds): '+ timeout);

  // runtime argument is mandatory.  If not specified, fail the request
  if (!runtime) {
    var err = new Error('Missing argument \'runtime\'');
    err.name = 'InvalidArgumentError';  // Alas, an equivalent error is not defined by JavaScript
    log.verbose('getAdapter', err);
    d.reject(err);
    return d.promise;
  }

  var path = strings.format(instanceID+mdoServerEndpoints.GET_ADAPTER, contextRoot, runtime, adapter);
  var get = require('../rest-client/rest-client.js').get;
  var requestPromise = get(host, port, path, protocol, userName, userPassword, timeout);
  requestPromise.then (function (rspObject) {
    if (rspObject === undefined || rspObject.resp === undefined || rspObject.body === undefined || rspObject.httperr === undefined) {
      var formerr = new Verror(new Error('server response is undefined'), 'server response error');
      formerr.name = 'getAdapterError';
      d.reject(formerr);
    } else {
      var pPromise = _processResponse (rspObject.resp, rspObject.body, rspObject.httperr);
      pPromise.then (function (resp) {
        log.silly(moduleName, 'Response: ' + resp.statusCode);
        d.resolve(resp);
      }, function (err) {
        d.reject(err);
      });
    }
  }, function (err) {
    log.verbose(moduleName, 'form error: ', err);
    var formerr = new Verror(err, 'failed to get adapter');
    formerr.name = 'getAdapterError';
    d.reject(formerr);
  });

  /*
   * Function that processes the completed (i.e. entire body has been read)
   * HTTP response received from the server.
   *
   * Parameters:
   *   resp    - Response object
   *   body    - Response body string
   *   httperr - Error object representing HTTP status
   *   d       - deferred promise to be resolved/rejected
   */
  var _processResponse = function(resp, body, httperr, d) {
    log.silly('getAdapter', 'processResp: resp data completely read.  status: '+resp.statusCode);

    // HTTP Resp Status    Resp Body
    // ================    ===============================================
    // 200                 Expect JSON parsable body
    //                     If body parse failure -> reject with parse error + HTTP status
    //                     If body parse OK -> return SUCCESS with resp body as JSON
    // !200                If body does not exist -> reject with HTTP err
    //                     If body parse failure -> reject with parse error + HTTP status
    //                     reject with err + body.message (if exists)
    //                                     + body.additionalMessage (if exists)
    //                                     + body.errors (if exists)
    //                                     + body.warnings (if exists)

    var pD = Q.defer();

    // If the body exists, parse it
    var jsonBody = null;
    if (body) {
      log.silly('getAdapter', 'processResp: resp data completely read.  body: '+body);
      try {  // Need try/catch around JSON.parse as can throw an exception
        jsonBody = JSON.parse(body); // Throws exception on failure
        log.silly('getAdapter', 'processResp: JSON body: '+ JSON.stringify(jsonBody, null,' '));
      }
      catch(e) {  // Response body parsing syntax error
        log.verbose('getAdapter', 'processResp: parse exception: '+e.stack);
        var parseerr = new Verror(httperr, e.message);
        parseerr.name = e.name;
        var err = new Verror(parseerr, 'failed to retrieve runtime information');
        err.name = 'getAdapterError';
        pD.reject(err);
      }
    }

    // Did we receive an 'OK' response?
    // If so, the body must contain a JSON object with a status of 'SUCCESS'
    if (resp.statusCode === 200) {
      log.verbose('getAdapter', 'processResp: success');
      pD.resolve(jsonBody);  // Success.  Nothing to return
    }
    else { // Bad HTTP response
      if (!jsonBody) {  // No response body; just reject with HTTP resp code
        var err = new Verror(httperr, 'failed to retrieve runtime information');
        err.name = 'getAdapterError';
        log.verbose('getAdapter', 'processResp: failure: '+err);
        pD.reject(err);
      }
      else {  // Got body, reject with HTTP + body parts that exist
        var errors = (jsonBody.transaction && jsonBody.transaction.errors) ? jsonBody.transaction.errors : null;
        var errMsgs = _concatErrDetails(errors);
        var warnings = (jsonBody.transaction && jsonBody.transaction.warnings) ? jsonBody.transaction.warnings : null;
        var warnMsgs = _concatWarnDetails(warnings);  // jshint ignore:line

        // Concatentate pertinent response message strings
        var serverErrMsg = jsonBody.message || '';
        serverErrMsg += ((serverErrMsg && jsonBody.additionalMessage) ? ' : ' : '') + (jsonBody.additionalMessage || '');
        serverErrMsg += ((serverErrMsg && errMsgs) ? ' : ' : '') + (errMsgs || '');
        // Since this is a failure; focus just on error messsages
        //serverErrMsg += ((serverErrMsg && warnMsgs) ? ' : ' : '') + (warnMsgs || '');

        var resperr = new Verror(httperr, serverErrMsg);
        resperr.name = 'getAdapterError';
        var newEerr = new Verror(resperr, 'failed to retrieve runtime information');
        newEerr.name = 'getAdapterError';
        newEerr.statusCode = resp.statusCode;
        newEerr.errors = errors;
        newEerr.warnings = warnings;
        log.verbose('getAdapter', 'processResp: failure: '+JSON.stringify(newEerr, null, 2));
        pD.reject(newEerr);
      }
    }
    return pD.promise;
  };

  return d.promise;
};

function _concatErrDetails(serverErrArr) {
  var retStr = '';
  if (serverErrArr) {
    for (var i = 0; i < serverErrArr.length; i++ ) {
      retStr += (i!==0 ? ' ' : '') + (serverErrArr[i].details || '');
    }
  }
  return retStr;
}

function _concatWarnDetails(serverWarnArr) {
  var retStr = '';
  if (serverWarnArr) {
    for (var i = 0; i < serverWarnArr.length; i++ ) {
      retStr += (i!==0 ? ' ' : '') + (serverWarnArr[i].details || '');
    }
  }
  return retStr;
}

module.exports = {

  getAdapters: getAdapters,
  getAdapter: getAdapter,
  deleteAdapter: deleteAdapter

}
