/* @copyright Itential, LLC 2018-9 */

// Set globals
/* global log */

/* NodeJS internal utilities */
const fs = require('fs');
const path = require('path');

// The crypto libraries
const cryptoJS = require('crypto-js');

class AdapterPropertyUtil {
  /**
   * Adapter Translator Utility
   * @constructor
   */
  constructor(prongId, directory) {
    this.myid = prongId;
    this.baseDir = directory;
  }

  // GENERIC UTILITY CALLS FOR PROPERTIES
  /**
   * @summary Get the entity schema and information for the action
   *
   * @function getEntitySchemaFromFS
   * @param {String} entityName - the name of the entity (required)
   * @param {String} actionName - the name of the action to take (required)
   *
   * @return {Object} entitySchema - the entity schema object
   */
  getEntitySchemaFromFS(entityName, actionName) {
    const origin = `${this.myid}-propertyUtil-getEntitySchemaFromFS`;
    log.trace(origin);

    // create the generic part of an error object
    const errorObj = {
      origin
    };

    try {
      // verify required data
      if (!entityName || typeof entityName !== 'string') {
        // add the specific pieces of the error object
        errorObj.type = 'Missing Data';
        errorObj.vars = ['Entity'];
        log.error(`${origin}: Entity is required to get entity schema for action`);
        throw new Error(JSON.stringify(errorObj));
      }
      if (!actionName || typeof actionName !== 'string') {
        // add the specific pieces of the error object
        errorObj.type = 'Missing Data';
        errorObj.vars = ['Action'];

        // log (if not system entity) and throw the error
        if (entityName !== '.system') {
          log.error(`${origin}: Action is required to get entity schema for action`);
        }
        throw new Error(JSON.stringify(errorObj));
      }

      // get the path for the specific action file
      const actionFile = path.join(this.baseDir, `/entities/${entityName}/action.json`);

      // if the file does not exist - error
      if (!fs.existsSync(actionFile)) {
        // add the specific pieces of the error object
        errorObj.type = 'Missing File';
        errorObj.vars = [actionFile];

        // log (if not system entity) and throw the error
        if (entityName !== '.system') {
          log.error(`${origin}: Could not find file - ${actionFile}`);
        }
        throw new Error(JSON.stringify(errorObj));
      }

      // Read the action from the file system
      const entityActions = JSON.parse(fs.readFileSync(actionFile, 'utf-8'));

      // handle possible errors in the action file
      if (!entityActions || typeof entityActions !== 'object') {
        // add the specific pieces of the error object
        errorObj.type = 'Invalid Action File';
        errorObj.vars = ['invalid format', actionFile];

        // log (if not system entity) and throw the error
        if (entityName !== '.system') {
          log.error(`${origin}: Invalid entity action file, please verify file: ${actionFile}`);
        }
        throw new Error(JSON.stringify(errorObj));
      }
      if (!entityActions.actions || !Array.isArray(entityActions.actions)) {
        // add the specific pieces of the error object
        errorObj.type = 'Invalid Action File';
        errorObj.vars = ['missing array of actions', actionFile];

        // log (if not system entity) and throw the error
        if (entityName !== '.system') {
          log.error(`${origin}: Invalid action file syntax ${actionFile} - must contain an array of actions`);
        }
        throw new Error(JSON.stringify(errorObj));
      }

      let actionInfo = null;

      // get the specific action information
      for (let i = 0; i < entityActions.actions.length; i += 1) {
        if (entityActions.actions[i].name === actionName) {
          actionInfo = entityActions.actions[i];
        }
      }

      // if there are no actions - invalid
      if (actionInfo === null) {
        // add the specific pieces of the error object
        errorObj.type = 'Invalid Action File';
        errorObj.vars = ['missing action', actionFile];

        // log (if not system entity) and throw the error
        if (entityName !== '.system') {
          log.error(`${origin}: Entity ${entityName} - action file missing action: ${actionName}`);
        }
        throw new Error(JSON.stringify(errorObj));
      }

      // verify required action info - protocol, method, entitypath and schema
      if (!actionInfo.protocol) {
        // add the specific pieces of the error object
        errorObj.type = 'Invalid Action File';
        errorObj.vars = ['missing protocol', actionFile];

        // log (if not system entity) and throw the error
        if (entityName !== '.system') {
          log.error(`${origin}: Entity ${entityName} action ${actionName} - missing protocol`);
        }
        throw new Error(JSON.stringify(errorObj));
      }
      if (!actionInfo.method) {
        // add the specific pieces of the error object
        errorObj.type = 'Invalid Action File';
        errorObj.vars = ['missing method', actionFile];

        // log (if not system entity) and throw the error
        if (entityName !== '.system') {
          log.error(`${origin}: Entity ${entityName} action ${actionName} - missing method`);
        }
        throw new Error(JSON.stringify(errorObj));
      }
      if (!actionInfo.entitypath) {
        // add the specific pieces of the error object
        errorObj.type = 'Invalid Action File';
        errorObj.vars = ['missing entity path', actionFile];

        // log (if not system entity) and throw the error
        if (entityName !== '.system') {
          log.error(`${origin}: Entity ${entityName} action ${actionName} - missing entity path`);
        }
        throw new Error(JSON.stringify(errorObj));
      }
      if (!actionInfo.schema && (!actionInfo.requestSchema || !actionInfo.responseSchema)) {
        // add the specific pieces of the error object
        errorObj.type = 'Invalid Action File';
        errorObj.vars = ['missing schema', actionFile];

        // log (if not system entity) and throw the error
        if (entityName !== '.system') {
          log.error(`${origin}: Entity ${entityName} action ${actionName} - missing schema`);
        }
        throw new Error(JSON.stringify(errorObj));
      }

      // get the schema file name
      let reqSchemaName = actionInfo.schema;
      let respSchemaName = actionInfo.schema;

      // if there is a separate request schema file it overrides the default schema
      if (actionInfo.requestSchema) {
        reqSchemaName = actionInfo.requestSchema;
      }
      // if there is a separate response schema file it overrides the default schema
      if (actionInfo.responseSchema) {
        respSchemaName = actionInfo.responseSchema;
      }

      // get the path for the specific schema file
      const reqSchemaFile = path.join(this.baseDir, `/entities/${entityName}/${reqSchemaName}`);
      const respSchemaFile = path.join(this.baseDir, `/entities/${entityName}/${respSchemaName}`);
      const errorSchemaFile = path.join(this.baseDir, '/entities/.system/errorSchema');

      // if the file does not exist - error
      if (!fs.existsSync(reqSchemaFile)) {
        // add the specific pieces of the error object
        errorObj.type = 'Missing File';
        errorObj.vars = [reqSchemaFile];

        // log (if not system entity) and throw the error
        if (entityName !== '.system') {
          log.error(`${origin}: Could not find file - ${reqSchemaFile}`);
        }
        throw new Error(JSON.stringify(errorObj));
      }

      // if the file does not exist - error
      if (!fs.existsSync(respSchemaFile)) {
        // add the specific pieces of the error object
        errorObj.type = 'Missing File';
        errorObj.vars = [respSchemaFile];

        // log (if not system entity) and throw the error
        if (entityName !== '.system') {
          log.error(`${origin}: Could not find file - ${respSchemaFile}`);
        }
        throw new Error(JSON.stringify(errorObj));
      }

      // Read the entity schema from the file system
      const entitySchema = {
        requestSchema: JSON.parse(fs.readFileSync(reqSchemaFile, 'utf-8')),
        responseSchema: JSON.parse(fs.readFileSync(respSchemaFile, 'utf-8'))
      };

      // handle possible errors on the schema
      if (!entitySchema.requestSchema || typeof entitySchema.requestSchema !== 'object') {
        // add the specific pieces of the error object
        errorObj.type = 'Invalid Schema File';
        errorObj.vars = ['invalid format', reqSchemaFile];

        // log (if not system entity) and throw the error
        if (entityName !== '.system') {
          log.error(`${origin}: Invalid entity request schema, please verify file: ${reqSchemaFile}`);
        }
        throw new Error(JSON.stringify(errorObj));
      }
      if (!entitySchema.responseSchema || typeof entitySchema.responseSchema !== 'object') {
        // add the specific pieces of the error object
        errorObj.type = 'Invalid Schema File';
        errorObj.vars = ['invalid format', respSchemaFile];

        // log (if not system entity) and throw the error
        if (entityName !== '.system') {
          log.error(`${origin}: Invalid entity response schema, please verify file: ${respSchemaFile}`);
        }
        throw new Error(JSON.stringify(errorObj));
      }

      // if the error schema file exist - read it in
      if (fs.existsSync(errorSchemaFile)) {
        entitySchema.errorSchema = JSON.parse(fs.readFileSync(errorSchemaFile, 'utf-8'));

        // if the error schema file is bad, warn about it but continue and use the global on!
        if (entitySchema.errorSchema && typeof entitySchema.errorSchema !== 'object') {
          log.warn(`${origin}: Invalid error schema, please verify file: ${errorSchemaFile}`);
        }
      }

      // Merge the information into the entity schema
      entitySchema.protocol = actionInfo.protocol;
      entitySchema.method = actionInfo.method;
      entitySchema.timeout = actionInfo.timeout;
      entitySchema.entitypath = actionInfo.entitypath;
      entitySchema.querykey = '?';
      entitySchema.responseObjects = [];
      entitySchema.mockresponses = [];

      // if info provided, replace the defaults
      if (actionInfo.querykey) {
        entitySchema.querykey = actionInfo.querykey;
      }
      if (actionInfo.responseObjects) {
        entitySchema.responseObjects = actionInfo.responseObjects;
      }
      if (actionInfo.headers) {
        entitySchema.headers = actionInfo.headers;
      }
      if (actionInfo.requestDatatype) {
        entitySchema.requestDatatype = actionInfo.requestDatatype;
      } else if (actionInfo.datatype) {
        entitySchema.requestDatatype = actionInfo.datatype;
      } else {
        entitySchema.requestDatatype = 'JSON';
      }
      if (actionInfo.responseDatatype) {
        entitySchema.responseDatatype = actionInfo.responseDatatype;
      } else if (actionInfo.datatype) {
        entitySchema.responseDatatype = actionInfo.datatype;
      } else {
        entitySchema.responseDatatype = 'JSON';
      }
      if (Object.hasOwnProperty.call(actionInfo, 'sendEmpty')) {
        entitySchema.sendEmpty = actionInfo.sendEmpty;
      }
      if (Object.hasOwnProperty.call(actionInfo, 'sendGetBody')) {
        entitySchema.sendGetBody = actionInfo.sendGetBody;
      }
      if (actionInfo.sso) {
        entitySchema.sso = actionInfo.sso;
      }

      // need to make sure we have supported datatypes - PLAIN is best if not supported - no translation or encoding
      if (entitySchema.requestDatatype.toUpperCase() !== 'JSON' && entitySchema.requestDatatype.toUpperCase() !== 'XML'
          && entitySchema.requestDatatype.toUpperCase() !== 'URLENCODE' && entitySchema.requestDatatype.toUpperCase() !== 'FORM'
          && entitySchema.requestDatatype.toUpperCase() !== 'JSON2XML') {
        entitySchema.requestDatatype = 'PLAIN';
      }
      if (entitySchema.responseDatatype.toUpperCase() !== 'JSON' && entitySchema.responseDatatype.toUpperCase() !== 'XML'
          && entitySchema.responseDatatype.toUpperCase() !== 'URLENCODE' && entitySchema.responseDatatype.toUpperCase() !== 'XML2JSON') {
        entitySchema.responseDatatype = 'PLAIN';
      }

      // go through each response object to see if there is mock data
      for (let i = 0; i < entitySchema.responseObjects.length; i += 1) {
        entitySchema.responseObjects[i].name = entitySchema.entitypath;
        entitySchema.responseObjects[i].method = entitySchema.method;

        // if there is mock data, read the mock data from the file system
        if (entitySchema.responseObjects[i].mockFile) {
          const mockResponse = {
            name: entitySchema.responseObjects[i].name,
            method: entitySchema.responseObjects[i].method,
            type: entitySchema.responseObjects[i].type,
            file: entitySchema.responseObjects[i].mockFile
          };

          // get the mock file name
          const tempName = entitySchema.responseObjects[i].mockFile;

          // if the file name was provided
          if (tempName) {
            const mockFileName = path.join(this.baseDir, `/entities/${entityName}/${tempName}`);

            // if the file does not exist - throw warning
            if (fs.existsSync(mockFileName)) {
              // set the normal headers based on the type of data for the call
              if (entitySchema.responseDatatype && entitySchema.responseDatatype.toUpperCase() === 'PLAIN') {
                // read the mock date from the file system
                mockResponse.response = fs.readFileSync(mockFileName, 'utf-8');
              } else if (entitySchema.responseDatatype && (entitySchema.responseDatatype.toUpperCase() === 'XML'
                  || entitySchema.responseDatatype.toUpperCase() === 'XML2JSON')) {
                // read the mock date from the file system
                mockResponse.response = fs.readFileSync(mockFileName, 'utf-8');
              } else {
                // read the mock date from the file system
                try {
                  // parse the mockdata file to store it as an object
                  mockResponse.response = JSON.parse(fs.readFileSync(mockFileName, 'utf-8'));
                } catch (excep) {
                  log.warn(`${origin}: Could not parse file - ${mockFileName}`);
                  mockResponse.response = '';
                }
              }
            } else {
              log.warn(`${origin}: Could not find file - ${mockFileName}`);
              mockResponse.response = null;
            }
          } else {
            mockResponse.response = null;
          }

          // add the response to the array of mock responses
          entitySchema.mockresponses.push(mockResponse);
        }
      }

      // return the entity schema
      return entitySchema;
    } catch (e) {
      let internal = null;
      errorObj.type = 'Caught Exception';
      errorObj.vars = [];
      errorObj.exception = e;

      // determine if we already had an internal message
      try {
        internal = JSON.parse(e.message);
      } catch (ex) {
        // message was not internal
        log.error(`${origin}: Issue parsing entity schema: ${e}`);
        internal = null;
      }

      // return the appropriate error message
      if (internal && internal.origin && internal.type) {
        throw e;
      } else {
        throw new Error(JSON.stringify(errorObj));
      }
    }
  }

  /**
   * @summary Build the entitySchema structure from DB config
   *
   * @function getEntitySchemaFromDB
   * @param {Object} dbObj - the database connect information (optional)
   * @param {String} entityName - the name of the entity (required)
   * @param {String} actionName - the name of the action to take (required)
   *
   * @return {Object} entitySchema - the entity schema object
   */
  getEntitySchemaFromDB(dbObj, entityName, actionName, dbUtils, callback) {
    const origin = `${this.myid}-propertyUtil-getEntitySchemaFromDB`;
    log.trace(origin);

    // create the generic part of an error object
    const errorObj = {
      origin,
      isError: true
    };

    try {
      // verify required data
      if (!entityName || typeof entityName !== 'string') {
        // add the specific pieces of the error object
        errorObj.type = 'Missing Data';
        errorObj.vars = ['Entity'];
        log.error(`${origin}: Entity is required to get entity schema for action`);
        errorObj.error = [`${origin}: Entity is required to get entity schema for action`];
        return callback(null, errorObj);
      }
      if (!actionName || typeof actionName !== 'string') {
        // add the specific pieces of the error object
        errorObj.type = 'Missing Data';
        errorObj.vars = ['Action'];

        // log (if not system entity) and return the error
        if (entityName !== '.system') {
          log.error(`${origin}: Action is required to get entity schema for action`);
          errorObj.error = [`${origin}: Action is required to get entity schema for action`];
        }
        return callback(null, errorObj);
      }

      let entitySchema = null;

      // set up the options-filter
      const dbOpts = {
        filter: {
          id: this.myid,
          entity: entityName
        },
        entity: entityName
      };

      // call to get the adapter schema from the database
      return dbUtils.find('adapter_configs', dbOpts, dbObj, null, (dbError, dbResult) => {
        if (dbError) {
          // add the specific pieces of the error object
          errorObj.type = 'Database Error';
          errorObj.vars = ['dbError'];
          log.error(`${origin}: Database Error: ${dbError}`);
          errorObj.error = [`${origin}: Database Error: ${dbError}`];
          return callback(null, errorObj);
        }
        if (!dbResult) {
          // add the specific pieces of the error object
          errorObj.type = 'Missing Data';
          errorObj.vars = ['Entity'];
          log.error(`${origin}: Entity was not found in the database`);
          errorObj.error = [`${origin}: Entity was not found in the database`];
          return callback(null, errorObj);
        }

        // Read the action from the entity object
        const entityActions = dbResult[0];

        // handle possible errors in the action file
        if (!entityActions || typeof entityActions !== 'object') {
          // add the specific pieces of the error object
          errorObj.type = 'Invalid Action File';
          errorObj.vars = ['invalid format', entityName];

          // log (if not system entity) and return the error
          if (entityName !== '.system') {
            log.error(`${origin}: Invalid entity action file, please verify file: ${entityName}`);
            errorObj.error = [`${origin}: Invalid entity action file, please verify file: ${entityName}`];
          }
          return callback(null, errorObj);
        }
        if (!entityActions.actions || !Array.isArray(entityActions.actions)) {
          // add the specific pieces of the error object
          errorObj.type = 'Invalid Action File';
          errorObj.vars = ['missing array of actions', entityName];

          // log (if not system entity) and return the error
          if (entityName !== '.system') {
            log.error(`${origin}: Invalid action file syntax ${entityName} - must contain an array of actions`);
            errorObj.error = [`${origin}: Invalid action file syntax ${entityName} - must contain an array of actions`];
          }
          return callback(null, errorObj);
        }

        let actionInfo = null;

        // get the specific action information
        for (let i = 0; i < entityActions.actions.length; i += 1) {
          if (entityActions.actions[i].name === actionName) {
            actionInfo = entityActions.actions[i];
          }
        }

        // if there are no actions - invalid
        if (actionInfo === null) {
          // add the specific pieces of the error object
          errorObj.type = 'Invalid Action File';
          errorObj.vars = ['missing action', entityName];

          // log (if not system entity) and return the error
          if (entityName !== '.system') {
            log.error(`${origin}: Entity ${entityName} - action file missing action: ${actionName}`);
            errorObj.error = [`${origin}: Entity ${entityName} - action file missing action: ${actionName}`];
          }
          return callback(null, errorObj);
        }

        // verify required action info - protocol, method, entitypath and schema
        if (!actionInfo.protocol) {
          // add the specific pieces of the error object
          errorObj.type = 'Invalid Action File';
          errorObj.vars = ['missing protocol', entityName];

          // log (if not system entity) and return the error
          if (entityName !== '.system') {
            log.error(`${origin}: Entity ${entityName} action ${actionName} - missing protocol`);
            errorObj.error = [`${origin}: Entity ${entityName} action ${actionName} - missing protocol`];
          }
          return callback(null, errorObj);
        }
        if (!actionInfo.method) {
          // add the specific pieces of the error object
          errorObj.type = 'Invalid Action File';
          errorObj.vars = ['missing method', entityName];

          // log (if not system entity) and return the error
          if (entityName !== '.system') {
            log.error(`${origin}: Entity ${entityName} action ${actionName} - missing method`);
            errorObj.error = [`${origin}: Entity ${entityName} action ${actionName} - missing method`];
          }
          return callback(null, errorObj);
        }
        if (!actionInfo.entitypath) {
          // add the specific pieces of the error object
          errorObj.type = 'Invalid Action File';
          errorObj.vars = ['missing entity path', entityName];

          // log (if not system entity) and return the error
          if (entityName !== '.system') {
            log.error(`${origin}: Entity ${entityName} action ${actionName} - missing entity path`);
            errorObj.error = [`${origin}: Entity ${entityName} action ${actionName} - missing entity path`];
          }
          return callback(null, errorObj);
        }
        if (!actionInfo.schema && (!actionInfo.requestSchema || !actionInfo.responseSchema)) {
          // add the specific pieces of the error object
          errorObj.type = 'Invalid Action File';
          errorObj.vars = ['missing schema', entityName];

          // log (if not system entity) and return the error
          if (entityName !== '.system') {
            log.error(`${origin}: Entity ${entityName} action ${actionName} - missing schema`);
            errorObj.error = [`${origin}: Entity ${entityName} action ${actionName} - missing schema`];
          }
          return callback(null, errorObj);
        }

        // get the schema file name
        let reqSchemaName = actionInfo.schema;
        let respSchemaName = actionInfo.schema;

        // if there is a separate request schema file it overrides the default schema
        if (actionInfo.requestSchema) {
          reqSchemaName = actionInfo.requestSchema;
        }
        // if there is a separate response schema file it overrides the default schema
        if (actionInfo.responseSchema) {
          respSchemaName = actionInfo.responseSchema;
        }

        if (!entityActions.schema || !Array.isArray(entityActions.schema)) {
          // add the specific pieces of the error object
          errorObj.type = 'Invalid Schema File';
          errorObj.vars = ['missing array of schemas', reqSchemaName];

          // log (if not system entity) and return the error
          if (entityName !== '.system') {
            log.error(`${origin}: Invalid schema file syntax ${reqSchemaName} - must contain an array of schemas`);
            errorObj.error = [`${origin}: Invalid schema file syntax ${reqSchemaName} - must contain an array of schemas`];
          }
          return callback(null, errorObj);
        }

        let reqSchemaInfo = null;
        let respSchemaInfo = null;

        // get the specific action information
        for (let i = 0; i < entityActions.schema.length; i += 1) {
          if (entityActions.schema[i].name === reqSchemaName) {
            reqSchemaInfo = entityActions.schema[i].schema;
          }
          if (entityActions.schema[i].name === respSchemaName) {
            respSchemaInfo = entityActions.schema[i].schema;
          }
        }

        // if there are no actions - invalid
        if (reqSchemaInfo === null) {
          // add the specific pieces of the error object
          errorObj.type = 'Missing File';
          errorObj.vars = [reqSchemaName];

          // log (if not system entity) and return the error
          if (entityName !== '.system') {
            log.error(`${origin}: Could not find file - ${reqSchemaName}`);
            errorObj.error = [`${origin}: Could not find file - ${reqSchemaName}`];
          }
          return callback(null, errorObj);
        }

        // if there are no actions - invalid
        if (respSchemaInfo === null) {
          // add the specific pieces of the error object
          errorObj.type = 'Missing File';
          errorObj.vars = [respSchemaName];

          // log (if not system entity) and return the error
          if (entityName !== '.system') {
            log.error(`${origin}: Could not find file - ${respSchemaName}`);
            errorObj.error = [`${origin}: Could not find file - ${respSchemaName}`];
          }
          return callback(null, errorObj);
        }

        // handle possible errors on the schema
        if (typeof reqSchemaInfo !== 'object') {
          // add the specific pieces of the error object
          errorObj.type = 'Invalid Schema File';
          errorObj.vars = ['invalid format', reqSchemaInfo];

          // log (if not system entity) and return the error
          if (entityName !== '.system') {
            log.error(`${origin}: Invalid entity request schema, please verify file: ${reqSchemaInfo}`);
            errorObj.error = [`${origin}: Invalid entity request schema, please verify file: ${reqSchemaInfo}`];
          }
          return callback(null, errorObj);
        }
        if (typeof respSchemaInfo !== 'object') {
          // add the specific pieces of the error object
          errorObj.type = 'Invalid Schema File';
          errorObj.vars = ['invalid format', respSchemaInfo];

          // log (if not system entity) and return the error
          if (entityName !== '.system') {
            log.error(`${origin}: Invalid entity response schema, please verify file: ${respSchemaInfo}`);
            errorObj.error = [`${origin}: Invalid entity response schema, please verify file: ${respSchemaInfo}`];
          }
          return callback(null, errorObj);
        }

        // Read the entity schema from the file system
        entitySchema = {
          requestSchema: reqSchemaInfo,
          responseSchema: respSchemaInfo
        };

        // Merge the information into the entity schema
        entitySchema.protocol = actionInfo.protocol;
        entitySchema.method = actionInfo.method;
        entitySchema.timeout = actionInfo.timeout;
        entitySchema.entitypath = actionInfo.entitypath;
        entitySchema.querykey = '?';
        entitySchema.responseObjects = [];
        entitySchema.mockresponses = [];

        // if info provided, replace the defaults
        if (actionInfo.querykey) {
          entitySchema.querykey = actionInfo.querykey;
        }
        if (actionInfo.responseObjects) {
          entitySchema.responseObjects = actionInfo.responseObjects;
        }
        if (actionInfo.headers) {
          entitySchema.headers = actionInfo.headers;
        }
        if (actionInfo.requestDatatype) {
          entitySchema.requestDatatype = actionInfo.requestDatatype;
        } else if (actionInfo.datatype) {
          entitySchema.requestDatatype = actionInfo.datatype;
        } else {
          entitySchema.requestDatatype = 'JSON';
        }
        if (actionInfo.responseDatatype) {
          entitySchema.responseDatatype = actionInfo.responseDatatype;
        } else if (actionInfo.datatype) {
          entitySchema.responseDatatype = actionInfo.datatype;
        } else {
          entitySchema.responseDatatype = 'JSON';
        }
        if (Object.hasOwnProperty.call(actionInfo, 'sendEmpty')) {
          entitySchema.sendEmpty = actionInfo.sendEmpty;
        }
        if (Object.hasOwnProperty.call(actionInfo, 'sendGetBody')) {
          entitySchema.sendGetBody = actionInfo.sendGetBody;
        }
        if (actionInfo.sso) {
          entitySchema.sso = actionInfo.sso;
        }

        // need to make sure we have supported datatypes - PLAIN is best if not supported - no translation or encoding
        if (entitySchema.requestDatatype.toUpperCase() !== 'JSON' && entitySchema.requestDatatype.toUpperCase() !== 'XML'
            && entitySchema.requestDatatype.toUpperCase() !== 'URLENCODE' && entitySchema.requestDatatype.toUpperCase() !== 'FORM'
            && entitySchema.requestDatatype.toUpperCase() !== 'JSON2XML') {
          entitySchema.requestDatatype = 'PLAIN';
        }
        if (entitySchema.responseDatatype.toUpperCase() !== 'JSON' && entitySchema.responseDatatype.toUpperCase() !== 'XML'
            && entitySchema.responseDatatype.toUpperCase() !== 'URLENCODE' && entitySchema.responseDatatype.toUpperCase() !== 'XML2JSON') {
          entitySchema.responseDatatype = 'PLAIN';
        }

        // go through each response object to see if there is mock data
        for (let i = 0; i < entitySchema.responseObjects.length; i += 1) {
          entitySchema.responseObjects[i].name = entitySchema.entitypath;
          entitySchema.responseObjects[i].method = entitySchema.method;

          // if there is mock data, read the mock data from the file system
          if (entitySchema.responseObjects[i].mockFile) {
            const mockResponse = {
              name: entitySchema.responseObjects[i].name,
              method: entitySchema.responseObjects[i].method,
              type: entitySchema.responseObjects[i].type,
              file: entitySchema.responseObjects[i].mockFile
            };

            // get the mock file name
            const tempName = entitySchema.responseObjects[i].mockFile;

            // if the file name was provided
            if (tempName) {
              // need to get just the file name
              const mockFileArray = tempName.split('/');
              const mockFileName = mockFileArray[mockFileArray.length - 1];
              const mockResp = entityActions.mockdatafiles[mockFileName];

              if (mockResp) {
                // set the normal headers based on the type of data for the call
                if (entitySchema.responseDatatype && entitySchema.responseDatatype.toUpperCase() === 'PLAIN') {
                  // read the mock date from the file system
                  mockResponse.response = mockResp;
                } else if (entitySchema.responseDatatype && (entitySchema.responseDatatype.toUpperCase() === 'XML'
                    || entitySchema.responseDatatype.toUpperCase() === 'XML2JSON')) {
                  // read the mock date from the file system
                  mockResponse.response = mockResp;
                } else {
                  // read the mock date from the file system
                  try {
                    // parse the mockdata file to store it as an object
                    mockResponse.response = mockResp;
                  } catch (excep) {
                    log.warn(`${origin}: Could not parse file - ${mockFileName}`);
                    mockResponse.response = '';
                  }
                }
              } else {
                log.warn(`${origin}: Could not find file - ${mockFileName}`);
                mockResponse.response = null;
              }
            } else {
              mockResponse.response = null;
            }

            // add the response to the array of mock responses
            entitySchema.mockresponses.push(mockResponse);
          }
        }

        // return the entity schema
        return callback(entitySchema, null);
      });
    } catch (e) {
      errorObj.type = 'Caught Exception';
      errorObj.vars = [];
      errorObj.exception = e;

      // return the error message
      log.info(`unable to get adapter config from database: ${e}`);
      return callback(null, errorObj);
    }
  }

  /**
   * @summary Get the entity schema and information for the action
   *
   * @function getEntitySchema
   * @param {String} entityName - the name of the entity (required)
   * @param {String} actionName - the name of the action to take (required)
   *
   * @return {Object} entitySchema - the entity schema object
   */
  getEntitySchema(entityName, actionName, dbUtils, callback) {
    const origin = `${this.myid}-propertyUtil-getEntitySchema`;
    log.trace(origin);

    // need to try to get the entity schema from the adapter database
    try {
      // call to get the adapter schema from the database
      return this.getEntitySchemaFromDB(null, entityName, actionName, dbUtils, (dbresp, dberror) => {
        // if we got an error back - just means db config not in place
        if (dberror || !dbresp || (dbresp && Object.keys(dbresp).length === 0)) {
          log.debug('unable to get adapter config from adapter database');

          // Temporarily hardcode the adapter properties
          const iapDB = {
            dburl: 'mongodb://127.0.0.1:27017',
            dboptions: {},
            database: 'pronghorn'
          };

          // need to try to get the entity schema from the iap database
          return this.getEntitySchemaFromDB(iapDB, entityName, actionName, dbUtils, (iapdbresp, iapdberror) => {
            // if we got an error back - just means db config not in place
            if (iapdberror || !iapdbresp || (iapdbresp && Object.keys(iapdbresp).length === 0)) {
              log.debug('unable to get adapter config from iap database');

              // need to try to get the entity schema from the filesystem
              log.debug('returning adapter config from file system');
              try {
                return callback(this.getEntitySchemaFromFS(entityName, actionName), null);
              } catch (exc) {
                log.error('Exception caught on File System');
                return callback(null, exc);
              }
            }

            // return the iap db config
            log.debug(`returning adapter config from iap database ${iapdbresp}`);
            return callback(iapdbresp, null);
          });
        }

        // return the adapter db config
        log.debug(`returning adapter config from adapter database ${dbresp}`);
        return callback(dbresp, null);
      });
    } catch (exaddb) {
      log.debug(`unable to get adapter config: ${exaddb}`);
      return callback(null, exaddb);
    }
  }

  /**
   * @summary Takes in propertiesSchema and creates an object with all of the
   * defaults set.
   *
   * @function setDefaults
   * @param {Object} propSchema - the proeprty schema
   *
   * @return {Object} the object with default values from the property schema
   */
  setDefaults(propSchema) {
    const origin = `${this.myid}-propertyUtil-setDefaults`;
    log.trace(origin);

    const defaults = {};

    // verify the input for the method
    if (!propSchema || !propSchema.properties) {
      return defaults;
    }

    const propKeys = Object.keys(propSchema.properties);

    // loop through all of the properties in the schema
    for (let k = 0; k < propKeys.length; k += 1) {
      const thisProp = propSchema.properties[propKeys[k]];

      // if this key is to a reference
      if (thisProp.$ref) {
        const refs = thisProp.$ref.split('/');

        // references should be to a sub of another section like definitions
        // like - #/definitions/credentials
        if (refs.length >= 3) {
          // recursive call with reference object
          defaults[propKeys[k]] = this.setDefaults(propSchema[refs[1]][refs[2]]);
        }
      } else if (Object.hasOwnProperty.call(thisProp, 'default')) {
        // if there is a default put into the object
        defaults[propKeys[k]] = thisProp.default;
      }
    }

    // return the defaults
    return defaults;
  }

  /**
   * @summary Takes in properties and the secondary properties and merges them so the returned
   * object has secondary properties where no primary property values were provided.
   *
   * @function mergeProperties
   * @param {Object} properties - the primary propererties (required)
   * @param {Object} secondary - the secondary propererties (required)
   *
   * @return {Object} the properties with the merged in secondaries
   */
  mergeProperties(properties, secondary) {
    const origin = `${this.myid}-propertyUtil-mergeProperties`;
    log.trace(origin);

    // verify the input for the method
    if (!properties || typeof properties !== 'object') {
      return secondary;
    }
    if (!secondary || typeof secondary !== 'object') {
      return properties;
    }

    const combinedProps = secondary;
    const propKeys = Object.keys(properties);

    // loop through all of the primary properties to insert them into the conbined data
    for (let k = 0; k < propKeys.length; k += 1) {
      const thisProp = properties[propKeys[k]];

      // if this key is to an object
      if (thisProp && typeof thisProp === 'object' && combinedProps[propKeys[k]]) {
        // recursive call with primary and secondary object
        combinedProps[propKeys[k]] = this.mergeProperties(thisProp, combinedProps[propKeys[k]]);
      } else if ((thisProp !== undefined && thisProp !== null && thisProp !== '')
          || (combinedProps[propKeys[k]] === undefined || combinedProps[propKeys[k]] === null || combinedProps[propKeys[k]] === '')) {
        // if no secondary or primary has value merge it - overriding the secondary
        combinedProps[propKeys[k]] = thisProp;
      }
    }

    // return the merged properties
    return combinedProps;
  }

  /**
   * @summary Takes in property text and an encoding/encryption and returns the resulting
   * encoded/encrypted string
   *
   * @function encryptProperty
   * @param {String} property - the property to encrypt (required)
   * @param {String} technique - the technique to use to encrypt (required)
   *
   * @return {String} the encrypted/encoded string
   */
  encryptProperty(property, technique) {
    const origin = `${this.myid}-propertyUtil-encryptProperty`;
    log.trace(origin);

    try {
      // verify the input for the method
      if (!property) {
        return null;
      }
      if (!technique) {
        return property;
      }

      // if encoding, return the encoded string
      if (technique.toUpperCase() === 'BASE64') {
        return `{code}${Buffer.from(property).toString('base64')}`;
      }
      // if encrypting, return the encrypted string
      if (technique.toUpperCase() === 'ENCRYPT') {
        return `{crypt}${cryptoJS.AES.encrypt(property, this.myid)}`;
      }

      log.warn(`${origin}: Encyrpt technique ${technique} not supported!`);
      return property;
    } catch (e) {
      log.error(`${origin}: Encyrpt technique ${technique} took exception: ${e}`);
      return null;
    }
  }

  /**
   * @summary Takes in encrypted or encoded property and decodes/decrypts it to return
   * the actual property
   *
   * @function decryptProperty
   * @param {String} property - the property to decrypt
   *
   * @return {String} the string
   */
  decryptProperty(property) {
    const origin = `${this.myid}-propertyUtil-decryptProperty`;
    log.trace(origin);

    try {
      // verify the input for the method
      if (!property) {
        return null;
      }

      if (property.indexOf('{code}') === 0) {
        // remove the start
        const b64prop = property.substring(6);

        // decode the string
        return Buffer.from(b64prop, 'base64').toString();
      }

      if (property.indexOf('{crypt}') === 0) {
        // remove the start
        const cryptprop = property.substring(7);

        // decrypt the string
        return cryptoJS.AES.decrypt(cryptprop, this.myid).toString(cryptoJS.enc.Utf8);
      }

      log.warn(`${origin}: Invalid property ${property} - should start with {code} or {crypt}!`);
      return property;
    } catch (e) {
      log.error(`${origin}: Decrypt property took exception: ${e}`);
      return null;
    }
  }
}

module.exports = AdapterPropertyUtil;
