text: /*
   * Copyright (c) AppDynamics, Inc., and its affiliates
   * 2015
   * All Rights Reserved
   */
  
  var http = require('http');
  var https = require('https');
  var url = require('url');
  var util = require('util');
  var EventEmitter = require('events').EventEmitter;
  var HttpsProxyAgent = require('https-proxy-agent');
  
  var native;
  
  var MAX_HTTP_RESPONSE_SIZE = 50 * 1024 * 1024; // 50 MB.
  var LOG_FILE_NAME = 'appd_node_agent_%Y_%m_%d__%H_%M_%S.%N.log';
  var ENV_FILE_NAME = 'appd_node_agent_env_%Y_%m_%d__%H_%M_%S.%N.log';
  var MARK_NODES_HISTORICAL_URL = 'mark-nodes-historical';
  
  function init(nativeExt) {
    if (native && process.env.NODE_ENV != 'appd_test') {
      return;
    }
  
    native = nativeExt;
  }
  
  function LibAgent(agent) {
    var self = this;
    var opts = agent.opts;
    self.agent = agent;
    var logCfg = {
      'logfiles': opts.logging ? opts.logging.logfiles : undefined,
      'rootDir': self.agent.tmpDir,
      'debug': opts.debug
    };
    var isConsoleOutputEnv = process.env.APPDYNAMICS_LOGGER_OUTPUT_TYPE === "console" ? true : false;
  
    if (!logCfg.logfiles) {
      // libagent fills in the rest of the fields for the default case
      logCfg.logfiles = [{
        'filename': LOG_FILE_NAME,
        'outputType': isConsoleOutputEnv ? "console" : "file",
        'level': process.env.APPDYNAMICS_LOGGER_LEVEL || "INFO"
      }, {
        'filename': ENV_FILE_NAME,
        'channel': 'ENV',
        'outputType': isConsoleOutputEnv ? "console" : "file",
        'level': process.env.APPDYNAMICS_LOGGER_LEVEL || "INFO"
      }];
    }
  
    var len = logCfg.logfiles.length;
    for (var i = 0; i < len; ++i) {
      var cfg = logCfg.logfiles[i];
  
      if (opts.debug) {
        if (!cfg.level || cfg.level !== 'TRACE') {
          cfg.level = 'DEBUG';
        }
      }
  
      if (!cfg.filename) {
        cfg.filename = "appd_node_agent_" + i + ".log";
      }
  
      var pos = cfg.filename.search('%');
      if (pos == -1 || pos == cfg.filename.length - 1) {
        // this doesn't fix a filename without an extension
        cfg.filename = cfg.filename.replace(/\.([^.]*)$/, ".%N.$1");
      }
  
      if (!cfg.root_directory) {
        cfg.root_directory = self.agent.tmpDir;
      }
    }
  
    self.agent = new native.NodeAgent(JSON.stringify(logCfg));
  };
  
  LibAgent.prototype.init = function (agent) {
    var self = this;
  
    var opts = agent.opts;
    var controllerInfo = {
      host: opts.controllerHostName,
      port: opts.controllerPort,
      ssl: self.isControllerSSLEnabled(opts.controllerSslEnabled),
      accountName: opts.accountName || "customer1",
      accountKey: opts.accountAccessKey,
      proxy: {
        hostName: opts.proxyHost,
        port: opts.proxyPort,
        userName: opts.proxyUser,
        password: opts.proxyPasswordFile
      }
    };
    if (controllerInfo.proxy.password) {
      var fs = require('fs');
      controllerInfo.proxy.password = (fs.readFileSync(controllerInfo.proxy.password, 'utf-8')).trim();
    }
    var nativeHTTPClient = opts && opts.nativeHTTPClient;
    var certificateFile = opts && opts.certificateFile;
    if (certificateFile) {
      controllerInfo.certificateFile = require('fs').readFileSync(certificateFile);
      if (!nativeHTTPClient) {
        nativeHTTPClient = {
          certificateFile: certificateFile
        };
      } else {
        nativeHTTPClient.certificateFile = certificateFile;
      }
    }
    if (nativeHTTPClient) {
      if (!('certificateFile' in nativeHTTPClient)) {
        var path = require('path');
        // Note: this needs to be changed if this file is moved in the hierarchy
        var moduleRoot = path.dirname(__dirname);
        nativeHTTPClient.certificateFile = path.resolve(moduleRoot, 'ca-bundle.crt');
      }
      controllerInfo.nativeHTTPClient = nativeHTTPClient;
    }
  
    var analyticsConfig = opts && opts.analytics;
    var analyticsHost = (analyticsConfig && analyticsConfig.host) ? analyticsConfig.host : (process.env.APPDYNAMICS_ANALYTICS_HOST_NAME || 'localhost');
    var analyticsPort = (analyticsConfig && analyticsConfig.port) ? analyticsConfig.port : (parseInt(process.env.APPDYNAMICS_ANALYTICS_PORT, 10) || 9090);
    var analyticsSSL = (analyticsConfig && analyticsConfig.ssl) ? analyticsConfig.ssl : ((process.env.APPDYNAMICS_ANALYTICS_SSL_ENABLED === 'true') || false);
    var analyticsInfo = {
      host: analyticsHost,
      port: analyticsPort,
      ssl: analyticsSSL
    };
    self.delegate = new Delegate(controllerInfo, analyticsInfo);
  
    var agentInfo = {
      appName: opts.applicationName,
      tierName: opts.tierName,
      nodeName: opts.nodeName,
      agentType: 'NODEJS_APP_AGENT',
      reuseNode: opts.reuseNode,
      reuseNodePrefix: opts.reuseNodePrefix,
      uniqueHostId: opts.uniqueHostId,
      analyticsMaxSegmentSizeInBytes: opts.analyticsMaxSegmentSizeInBytes,
      analyticsMaxSegmentsPerRequest: opts.analyticsMaxSegmentsPerRequest,
      analyticsMaxMessageSizeInBytes: opts.analyticsMaxMessageSizeInBytes
    };
  
    self.markNodeHistorical = (opts.reuseNode == 'true' || opts.reuseNode == true) ? true : false;
    self.agent.initializeNodeAgent(self.delegate, agentInfo);
    self.delegate.agent = self;
  
    self.timeSkew = 0;
  };
  
  module.exports = {
    'init': init,
    'LibAgent': LibAgent
  };
  
  LibAgent.prototype.isControllerSSLEnabled = function (controllerSslEnabled) {
    return (controllerSslEnabled === 'true' || controllerSslEnabled === '1' || controllerSslEnabled === true) || false;
  };
  
  LibAgent.prototype.start = function (meta, filters) {
    return this.agent.start(meta, filters && JSON.stringify(filters) || "{}");
  };
  
  LibAgent.prototype.startBusinessTransaction = function (entryPointType, optionalName, correlationHeader, callback, isHttpRequest) {
    return this.agent.startBusinessTransaction(entryPointType, optionalName, correlationHeader, callback, isHttpRequest);
  };
  
  LibAgent.prototype.stopBusinessTransaction = function (btGuid) {
    this.agent.stopBusinessTransaction(btGuid);
  };
  
  LibAgent.prototype.startExitCall = function (btGuid, exitPointType,
    exitPointSubType, backendName,
    category, command, properties, useBackendConfig) {
    return this.agent.startExitCall(btGuid, exitPointType,
      exitPointSubType, backendName, category, command, properties, useBackendConfig);
  };
  
  LibAgent.prototype.stopExitCall = function (exitCall) {
    this.agent.stopExitCall(exitCall);
  };
  
  LibAgent.prototype.getBusinessTransactionId = function (txnGuid) {
    return this.agent.getBusinessTransactionId(txnGuid);
  };
  
  LibAgent.prototype.setHttpParamsInTransactionSnapshot = function (txnGuid, url, method, statusCode) {
    return this.agent.setHttpParamsInTransactionSnapshot(txnGuid, url, method, statusCode);
  };
  
  LibAgent.prototype.addHttpDataToTransactionSnapshot = function (txnGuid, request) {
    return this.agent.addHttpDataToTransactionSnapshot(txnGuid, request);
  };
  
  LibAgent.prototype.addAnalyticsHttpData = function (transaction, userData) {
    this.agent.addAnalyticsHttpData(transaction, userData);
  };
  
  LibAgent.prototype.addAnalyticsExitCallData = function (exitCallId, userData) {
    this.agent.addAnalyticsExitCallData(exitCallId, userData);
  };
  
  LibAgent.prototype.getUserProcessSnapshotRequest = function () {
    return this.agent.getUserProcessSnapshotRequest();
  };
  
  LibAgent.prototype.addIdentifyingPropertyToExitCall = function (exitCall, key, value) {
    this.agent.addIdentifyingPropertyToExitCall(exitCall, key, value);
  };
  
  LibAgent.prototype.getBusinessTransactionGuid = function (txn) {
    return this.agent.getBusinessTransactionGuid(txn);
  };
  
  // ensure this is kept in sync with LogLevel in agent.h
  var LogLevel = {
    TRACE: 0,
    DEBUG: 1,
    INFO: 2,
    WARN: 3,
    ERROR: 4,
    FATAL: 5
  };
  
  LibAgent.prototype.logTrace = function (msg) {
    this.agent.log(LogLevel.TRACE, msg);
  };
  
  LibAgent.prototype.logDebug = function (msg) {
    this.agent.log(LogLevel.DEBUG, msg);
  };
  
  LibAgent.prototype.logInfo = function (msg) {
    this.agent.log(LogLevel.INFO, msg);
  };
  
  LibAgent.prototype.logWarn = function (msg) {
    this.agent.log(LogLevel.WARN, msg);
  };
  
  LibAgent.prototype.logError = function (msg) {
    this.agent.log(LogLevel.ERROR, msg);
  };
  
  LibAgent.prototype.logFatal = function (msg) {
    this.agent.log(LogLevel.FATAL, msg);
  };
  
  LibAgent.prototype.logEnv = function (msg) {
    // Add the logger name to it to send it to a different logger.
    this.agent.log(LogLevel.INFO, msg, 'ENV');
  };
  
  LibAgent.prototype.getInitialMetricDataRequestDelay = function () {
    return this.agent.getInitialMetricDataRequestDelay();
  };
  
  LibAgent.prototype.updateConfig = function () {
    return this.agent.updateConfig();
  };
  
  LibAgent.prototype.registerMetrics = function () {
    return this.agent.registerMetrics();
  };
  
  LibAgent.prototype.rollupAndSendMetrics = function () {
    return this.agent.rollupAndSendMetrics();
  };
  
  LibAgent.prototype.registerObjects = function () {
    return this.agent.registerObjects();
  };
  
  LibAgent.prototype.processAndSendEventData = function () {
    return this.agent.processAndSendEventData();
  };
  
  LibAgent.prototype.updatePeriodicSnapshotTimers = function () {
    return this.agent.updatePeriodicSnapshotTimers();
  };
  
  LibAgent.prototype.reportOverflows = function () {
    return this.agent.reportOverflows();
  };
  
  LibAgent.prototype.processAndSendSnapshots = function () {
    return this.agent.processAndSendSnapshots();
  };
  
  LibAgent.prototype.processAndSendTopSummaryStats = function () {
    return this.agent.processAndSendTopSummaryStats();
  };
  
  LibAgent.prototype.sendAnalyticsData = function () {
    return this.agent.sendAnalyticsData();
  };
  
  LibAgent.prototype.aggregateRuntimeStatistics = function () {
    return this.agent.aggregateRuntimeStatistics();
  };
  
  LibAgent.prototype.disableResolutionForExitCall = function (exitCall) {
    return this.agent.disableResolutionForExitCall(exitCall);
  };
  
  LibAgent.prototype.getCorrelationHeader = function (exitCall) {
    return this.agent.getCorrelationHeader(exitCall);
  };
  
  LibAgent.prototype.addCustomMetric = function (name, aggregatorType, timeRollup, clusterRollup, holeHandling) {
    return this.agent.addCustomMetric(name, aggregatorType, timeRollup, clusterRollup, holeHandling);
  };
  
  LibAgent.prototype.addMetric = function (name, aggregatorType, timeRollup, clusterRollup, holeHandling) {
    return this.agent.addMetric(name, aggregatorType, timeRollup, clusterRollup, holeHandling);
  };
  
  LibAgent.prototype.reportMetric = function (metricId, value) {
    return this.agent.reportMetric(metricId, value);
  };
  
  LibAgent.prototype.addErrorToTransaction = function (btGuid, name, message, errorCode, stack) {
    if (stack) {
      return this.agent.addErrorToTransaction(btGuid, name, message, errorCode, stack);
    } else {
      return this.agent.addErrorToTransaction(btGuid, name, message, errorCode);
    }
  };
  
  LibAgent.prototype.addErrorToExitCall = function (exitCallGuid, name, message) {
    return this.agent.addErrorToExitCall(exitCallGuid, name, message);
  };
  
  LibAgent.prototype.addHttpErrorToExitCall = function (exitCallGuid, name,
    message, httpStatusCode) {
    return this.agent.addHttpErrorToExitCall(exitCallGuid, name, message, httpStatusCode);
  };
  
  LibAgent.prototype.addErrorWithStackTraceToExitCall = function (exitCallGuid, message,
    stackTraceData, httpStatusCode) {
    if (httpStatusCode) {
      return this.agent.addErrorWithStackTraceToExitCall(exitCallGuid, message, stackTraceData, httpStatusCode);
    } else {
      return this.agent.addErrorWithStackTraceToExitCall(exitCallGuid, message, stackTraceData);
    }
  };
  
  LibAgent.prototype.getInstanceTrackingConfig = function () {
    return this.agent.getInstanceTrackingConfig();
  };
  
  LibAgent.prototype.addInstanceData = function (instanceCounts) {
    return this.agent.addInstanceData(instanceCounts);
  };
  
  LibAgent.prototype.isSnapshotRequired = function (txnGuid) {
    return this.agent.isSnapshotRequired(txnGuid);
  };
  
  LibAgent.prototype.setSnapshotRequired = function (txnGuid) {
    this.agent.setSnapshotRequired(txnGuid);
  };
  
  LibAgent.prototype.startProcessSnapshot = function () {
    this.agent.startProcessSnapshot();
  };
  
  LibAgent.prototype.addProcessSnapshot = function (processSnapshot) {
    this.agent.addProcessSnapshot(processSnapshot);
  };
  
  LibAgent.prototype.addTransactionSnapshot = function (txnGuid, transactionSnapshot, snapshotData) {
    this.agent.addTransactionSnapshot(txnGuid, transactionSnapshot, snapshotData);
  };
  
  LibAgent.prototype.addProcessSnapshotGuid = function (txnGuid, psGuid) {
    this.agent.addProcessSnapshotGuid(txnGuid, psGuid);
  };
  
  LibAgent.prototype.addAnalyticsHttpData = function (transaction, callback) {
    this.agent.addAnalyticsHttpData(transaction, callback);
  };
  
  LibAgent.prototype.addAnalyticsUserData = function (transaction, userData) {
    this.agent.addAnalyticsUserData(transaction, userData);
  };
  
  LibAgent.prototype.addEvent = function (severity, type, summary, details) {
    return this.agent.addEvent(severity, type, summary, details);
  };
  
  LibAgent.prototype.sendEvents = function () {
    return this.agent.sendEvents();
  };
  
  LibAgent.prototype.isAgentInitialized = function () {
    return this.agent.isAgentInitialized();
  };
  
  LibAgent.prototype.getEumCookieFields = function (txnGuid, shortForm) {
    return this.agent.getEumCookieFields(txnGuid, shortForm);
  };
  
  LibAgent.prototype.uploadLogfiles = function () {
    return this.agent.uploadLogfiles();
  };
  
  LibAgent.prototype.btPurgeChecker = function () {
    return this.agent.btPurgeChecker();
  };
  
  LibAgent.prototype.shutDown = function (cb) {
    if (this.markNodeHistorical) {
      this.delegate.markNodeHistoricalCb = cb
    }
    this.agent.shutDown();
  };
  
  function PendingRequest(httpRequest) {
    this.httpRequest = httpRequest;
  }
  
  PendingRequest.prototype.abort = function () {
    this.httpRequest.abort();
  };
  
  function Delegate(controllerInfo, analyticsInfo) {
    this.controllerInfo = controllerInfo;
    this.analyticsInfo = analyticsInfo;
    EventEmitter.call(this);
  }
  
  util.inherits(Delegate, EventEmitter);
  
  Delegate.prototype.__onResponse = function (request, response, cb) {
    var self = this;
    var statusCode = response.statusCode | 0; // force status code to be an int.
    if ((statusCode <= 99) || (statusCode >= 1000)) {
      // status code must be a 3 digit integer.
      // If the status code is not a 3 digit integer for some reason
      // convert it to a status code of 500 ( internal server error ).
      statusCode = 500;
    }
    var responseType = ((statusCode != 200) && (statusCode != 202)) ? 'ERROR' : 'SUCCESS';
  
    if ((process.env.NODE_ENV == 'test') && (request.path).indexOf('applicationConfiguration') > -1 && statusCode == 200) {
      self.emit('configResponse');
    }
    self.agent.logTrace('Delegate Response');
    self.agent.logTrace(JSON.stringify(response.headers, null, "  "));
  
    var contentLength = response.headers['content-length'] | 0;
    if (contentLength < 0) {
      contentLength = 0;
    }
    else if (contentLength > MAX_HTTP_RESPONSE_SIZE) {
      contentLength = MAX_HTTP_RESPONSE_SIZE;
    }
  
    var transferEncoding = response.headers.hasOwnProperty('transfer-encoding');
    if (!contentLength && !transferEncoding) {
      self.agent.logWarn('Received http response with invalid headers');
      request.onComplete(responseType, statusCode, new Buffer(0));
      if (cb)
        cb(responseType, statusCode, new Buffer(0));
      return;
    }
  
  
    var buffers = [];
    var totalSize = 0;
    response.on('data', function (chunk) {
      /* RFC 2616: If a message is received with both a Transfer-Encoding header field and a
        * Content-Length header field, the latter must be ignored
        */
      if (!transferEncoding && (totalSize == contentLength))
        return;
  
      var newSize = totalSize + chunk.length;
      if (!transferEncoding && (newSize > contentLength)) {
        chunk = chunk.slice(0, chunk.length - (contentLength - totalSize));
      }
      buffers.push(chunk);
      totalSize += chunk.length;
    });
  
    response.on('end', function () {
      var buffer = Buffer.concat(buffers);
      request.onComplete(responseType, statusCode, buffer);
      if (cb)
        cb(responseType, statusCode, buffer);
      self.emit('response', statusCode, buffer);
    });
  };
  
  Delegate.prototype.__onError = function (request, cb) {
    var self = this;
  
    var buffer = new Buffer(0);
    request.onComplete('ERROR', 0, buffer);
    if (cb)
      cb('ERROR', 0, buffer);
    self.emit('response', 0, buffer);
  };
  
  Delegate.prototype.__doHttpRequest = function (request, options, client, cb) {
    var self = this;
    var postData = request.postData;
  
    self.agent.logTrace('__doHttpRequest: ' + request.path + ((options.headers['Content-Type'] != 'application/json') ?
      postData.length.toString() : JSON.stringify(postData).length.toString()));
  
    try {
      self.agent.logTrace('Delegate Request');
      var {ca, ...newOptions} = options
      self.agent.logTrace(JSON.stringify(newOptions, null, "  "));
    }
    catch (err) {
      self.agent.logTrace("__doHttpRequest error: " + err);
    }
  
    var httpRequest = client.request(options, function (response) {
      self.__onResponse(request, response, cb);
    });
  
    httpRequest.on('error', function (error) {
      self.__onError(request, cb);
    });
  
    httpRequest.end(postData);
    return new PendingRequest(httpRequest);
  };
  
  Delegate.prototype.sendRequest = function (request) {
    var self = this;
  
    var requestOptions = {
      'appdIgnore': true,
      'method': request.method,
    };
  
    if (request.type == "CONTROLLER") {
      self.agent.logTrace("Controller Request");
      var httpModule = (self.controllerInfo.ssl) ? https : http;
  
      requestOptions.headers = {
        'Content-Length': request.postData.length.toString(),
        'Accept': 'application/x-protobuf',
        'Content-Type': request.contentType
      };
  
      requestOptions['hostname'] = self.controllerInfo.host;
      requestOptions['port'] = self.controllerInfo.port;
      requestOptions['path'] = request.path || '/';
      requestOptions['auth'] = 'singularity-agent@' + self.controllerInfo.accountName +
        ':' + self.controllerInfo.accountKey;
      if (self.controllerInfo.certificateFile) {
        requestOptions['ca'] = self.controllerInfo.certificateFile;
      }
  
      if (self.controllerInfo.proxy.hostName) {
        var ro = requestOptions;
        var proxy = self.controllerInfo.proxy;
        var proxyAuth = proxy.userName && proxy.password && Buffer.from(`${proxy.userName}:${proxy.password}`).toString('base64');
  
        if (self.controllerInfo.ssl) {
          var proxyUrl = `http://${proxy.hostName}:${proxy.port}`;
          var proxyOpts = url.parse(proxyUrl);
          if (proxyAuth) {
            proxyOpts.headers = {
              'Proxy-Authentication': `Basic ${proxyAuth}`
            };
          }
          var agent = new HttpsProxyAgent(proxyOpts);
          ro['agent'] = agent;
        } else {
          httpModule = http;
          ro['path'] = `http://${ro.hostname}:${ro.port}${ro.path}`;
          ro['headers']['Host'] = `${ro.hostname}:${ro.port}`;
          ro['hostname'] = proxy.hostName;
          ro['port'] = proxy.port;
          if (proxyAuth) {
            ro['headers']['Proxy-Authorization'] = `Basic ${proxyAuth}`;
          }
        }
      }
      if (requestOptions['path'].indexOf(MARK_NODES_HISTORICAL_URL) > -1)
        return this.__doHttpRequest(request, requestOptions, httpModule, this.markNodeHistoricalCb);
      else
        return this.__doHttpRequest(request, requestOptions, httpModule);
    } else if (request.type == "ANALYTICS") {
      self.agent.logTrace("Analytics Request");
  
      requestOptions.headers = {
        'Content-Length': request.postData.length.toString(),
        'Content-Type': 'application/json'
      };
  
      requestOptions['hostname'] = self.analyticsInfo.host;
      requestOptions['port'] = self.analyticsInfo.port;
      requestOptions['path'] = request.path;
  
      return this.__doHttpRequest(request, requestOptions, (self.analyticsInfo.ssl) ? https : http);
    } else {
      self.agent.logWarn('Invalid request type ' + request.type);
    }
  };
  
  Delegate.prototype.timeSkewChanged = function (timeSkew) {
    this.agent.timeSkew = timeSkew;
    this.agent.logInfo('TimeSkew changed: ' + timeSkew);
  };
  
  Delegate.prototype.agentInitialized = function () {
    var self = this;
    self.emit('initialConfigUpdateDone');
  };
  
  Delegate.prototype.startProcessSnapshot = function () {
    var self = this;
    self.emit('autoProcessSnapshot');
  };
  
  Delegate.prototype.agentReset = function () {
    var self = this;
    self.emit('agentReset');
  };
  
  Delegate.prototype.agentDisabled = function () {
    var self = this;
    self.emit('agentDisabled');
  };
  
  Delegate.prototype.businessTransactionDropped = function (guid) {
    var self = this;
    self.emit('transactionDropped', guid);
  };
  