/**
 * @file lexicalAnalyzer.js
 * @module lexicalAnalyzer
 * @description Contains all functions used for parsing command arguments and/or business rule argumetns.
 * @requires module:ruleParsing
 * @requires module:configurator
 * @requires module:loggers
 * @requires {@link https://www.npmjs.com/package/@haystacks/constants|@haystacks/constants}
 * @requires {@link https://www.npmjs.com/package/path|path}
 * @author Seth Hollingsead
 * @date 2022/02/03
 * @copyright Copyright © 2022-… by Seth Hollingsead. All rights reserved
 */

// Internal imports
import ruleParsing from './ruleParsing.js';
import configurator from '../../executrix/configurator.js';
import loggers from '../../executrix/loggers.js';
// External imports
import hayConst from '@haystacks/constants';
import path from 'path';

const {bas, biz, cfg, msg, sys, wrd} = hayConst;
const baseFileName = path.basename(import.meta.url, path.extname(import.meta.url));
// businessRules.rules.lexicalAnalyzer.
const namespacePrefix = sys.cbusinessRules + bas.cDot + wrd.crules + bas.cDot + baseFileName + bas.cDot;

/**
 * @function parseBusinessRuleArgument
 * @description Parses a single business rule argument and returns it after cleaning it up or
 * doing required operations on it to convert it to valid input for a command as necessary.
 * @param {string|array<string|integer|boolean|object>} inputData The value of the argument, could be an array or a string.
 * @param {integer} inputMetaData The index of the argument (1, 2, 3, 4).
 * @return {string|array<string|integer|boolean|object>} The value of the argument as it should be passed into the business rule call.
 * @author Seth Hollingsead
 * @date 2022/05/03
 */
async function parseBusinessRuleArgument(inputData, inputMetaData) {
  let functionName = parseBusinessRuleArgument.name;
  await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
  // console.log(`BEGIN ${namespacePrefix}${functionName} function`);
  await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
  // console.log(msg.cinputDataIs + JSON.stringify(inputData));
  await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
  // console.log(msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
  let returnData;

  if (inputMetaData === 1) {
    // Pushing the inputData to the returnData as an array element
    await loggers.consoleLog(namespacePrefix + functionName, msg.cPushingArgumentValueToReturnDataAsArrayElement);
    // console.log(msg.cPushingArgumentValueToReturnDataAsArrayElement);
    returnData = [];
    returnData.push(inputData);
  } else {
    // Calling analyzeArgument for index = 2, consolidatedArgumentMode = false
    await loggers.consoleLog(namespacePrefix + functionName, msg.cCallingAnalyzeArgumentIndexIs + inputMetaData);
    // console.log(msg.cCallingAnalyzeArgumentIndexIs + inputMetaData);
    returnData = await analyzeArgument(inputData, '');
    // } else if (inputMetaData === 2 && consolidatedArgumentMode === true) {
    //   // Calling analyzeArgument for inputMetaData = 2, consolidatedArgumentMode = true
    //   loggers.consoleLog(namespacePrefix + functionName, msg.cCallingAnalyzeArgumentIndex2ConsolidatedArgumentModeTrue);
    //   if (inputData.length > 0) {
    //     returnData = [];
    //     // inputData.length > 0
    //     loggers.consoleLog(namespacePrefix + functionName, msg.cargumentValueLengthGreaterThanZero);
    //     for (let i = 1; i < inputData.length; i++) {
    //       // Combine all arguments into a single array on the returnData
    //       returnData.push(inputData[i]);
    //     } // End-for (let i = 1; i < inputData.length; i++)
    //   } else {
    //     // Return the inputData the same as it was passed in.
    //     loggers.consoleLog(namespacePrefix + functionName, msg.cReturnArgumentValueSameAsItWasPassedIn);
    //     returnData = inputData;
    //   }
    // } else if (inputMetaData === 3 && consolidatedArgumentMode === false) {
    //   // Calling analyzeArgument for inputMetaData = 3, consolidatedArgumentMode = false
    //   loggers.consoleLog(namespacePrefix + functionName, msg.cCallingAnalyzeArgumentIndex3ConsolidatedArgumentModeFalse);
    //   returnData = analyzeArgument(inputData);
    // } else {
    //   // WARNING: lexical.parseBusinessRuleArgument: invalid combination of inputs to the lexical.parseBusinessRuleArgument function! Pleae adjust inputs and try again.
    //   console.log(msg.cparseBusinessRuleArgumentMessage1 + msg.cparseBusinessRuleArgumentMessage2);
  }
  await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
  // console.log(msg.creturnDataIs + JSON.stringify(returnData));
  await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
  // console.log(`END ${namespacePrefix}${functionName} function`);
  return returnData;
}

/**
 * @function analyzeArgument
 * @description Does additional parsing of an individual argument.
 * Identifies the case that the argument needs to be treated as an array,
 * then the function will clean the array string tags and split the string into an array and return it.
 * This function can also identify the case that an argument contains a regular expression that must be formally created,
 * using the RegExp constructor, then the RegExp object will be returned as part of the return object.
 * @param {string} inputData The argument string that needs additional parsing.
 * @param {string} inputMetaData Not used for this business rule.
 * @return {string|array<string|integer|boolean|object>} The argument that should be returned and used by the system after all necessary parsing.
 * @author Seth Hollingsead
 * @date 2022/05/03
 */
async function analyzeArgument(inputData, inputMetaData) {
  let functionName = analyzeArgument.name;
  await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
  // console.log(`BEGIN ${namespacePrefix}${functionName} function`);
  await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
  // console.log(msg.cinputDataIs + JSON.stringify(inputData));
  await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
  // console.log(msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
  let returnData;
  let argsArrayContainsCharacterRule = [];
  argsArrayContainsCharacterRule[0] = biz.cdoesArrayContainCharacter;

  let secondaryCommandArgsDelimiter = await configurator.getConfigurationSetting(wrd.csystem, cfg.csecondaryCommandDelimiter);
  let tertiaryCommandArgsDelimiter = await configurator.getConfigurationSetting(wrd.csystem, cfg.ctertiaryCommandDelimiter);
  let argsArrayContainsOpenBracket = await ruleParsing.processRulesInternal([bas.cOpenBracket, inputData], argsArrayContainsCharacterRule);
  let argsArrayContainsCloseBracket = await ruleParsing.processRulesInternal([bas.cCloseBracket, inputData], argsArrayContainsCharacterRule);
  if (inputData.includes(secondaryCommandArgsDelimiter) === true ||
  inputData.includes(tertiaryCommandArgsDelimiter) === true) {
    // Check if there are brackets or no brackets.
    await loggers.consoleLog(namespacePrefix + functionName, msg.cCheckIfThereAreBracketsOrNoBrackets);
    // console.log(msg.cCheckIfThereAreBracketsOrNoBrackets);
    if (argsArrayContainsOpenBracket === false || argsArrayContainsCloseBracket === false) {
      // Brackets were not found
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBracketsWereNotFound);
      // console.log(msg.cBracketsWereNotFound);
      // Check if there is a regular expression or not.
      await loggers.consoleLog(namespacePrefix + functionName, msg.cCheckIfThereIsRegularExpressionOrNot);
      // console.log(msg.cCheckIfThereIsRegularExpressionOrNot);
      if (analyzeForRegularExpression(inputData, '') === true) {
        // A regular expression was found!
        await loggers.consoleLog(namespacePrefix + functionName, msg.cRegularExpressionWasFound);
        // console.log(msg.cRegularExpressionWasFound);
        returnData = await parseArgumentAsRegularExpression(inputData, '');
      } else { // No regular expression, just return the argument as it was passed in, no additional processing required.
        // No RegExp found!
        await loggers.consoleLog(namespacePrefix + functionName, msg.cNoRegExpFound);
        // console.log(msg.cNoRegExpFound);
        returnData = inputData;
      }
    } else {
      // There are Brackets, so treat the argument as an array.
      // Brackets ARE found!
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBracketsAreFound);
      // console.log(msg.cBracketsAreFound);
      returnData = await parseArgumentAsArray(inputData, '');
    }
  } else { // The inputData does not contain a secondaryCommandArgsDelimiter
    // NO secondary command argument delimiters.
    await loggers.consoleLog(namespacePrefix + functionName, msg.cNoSecondaryCommandArgumentDelimiters);
    // console.log(msg.cNoSecondaryCommandArgumentDelimiters);
    if (argsArrayContainsOpenBracket === false || argsArrayContainsCloseBracket === false) {
      // Brackets were not found
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBracketsWereNotFound);
      // console.log(msg.cBracketsWereNotFound);
      // Check if there is a Regular Expression or not.
      await loggers.consoleLog(namespacePrefix + functionName, msg.cCheckIfThereIsRegularExpressionOrNot);
      // console.log(msg.cCheckIfThereIsRegularExpressionOrNot);
      if (analyzeForRegularExpression(inputData, '') === true) {
        // A regular expression was found!
        await loggers.consoleLog(namespacePrefix + functionName, msg.cRegularExpressionWasFound);
        // console.log(msg.cRegularExpressionWasFound);
        returnData = await parseArgumentAsRegularExpression(inputData, '');
      } else { // No regular expression, just return the argument as it was passed in, no additional processing required.
        // NO RegExp found!
        await loggers.consoleLog(namespacePrefix + functionName, msg.cNoRegExpFound);
        // console.log(msg.cNoRegExpFound);
        returnData = inputData;
      }
    } else {
      // There are Brackets, so treat the argument as an array.
      // Brackets ARE found!
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBracketsAreFound);
      // console.log(msg.cBracketsAreFound);
      returnData = await parseArgumentAsArray(inputData, '');
    }
  }
  await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
  // console.log(msg.creturnDataIs + JSON.stringify(returnData));
  await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
  // console.log(`BEGIN ${namespacePrefix}{functionName} function`);
  return returnData;
}

/**
 * @function analyzeForRegularExpression
 * @description Analyzes the argument value to determine if it incudes a regular expression or no regular expression.
 * @param {string} inputData The business rule argument that should be analyzed to determine if it includes a regular expression or not regular expression.
 * @param {string} inputMetaData Not used for this business rule.
 * @return {boolean} True or False to indicate if the argument contains a regular expression or no regular expression.
 * @author Seth Hollingsead
 * @date 2022/05/03
 */
async function analyzeForRegularExpression(inputData, inputMetaData) {
  let functionName = analyzeForRegularExpression.name;
  await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
  await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
  await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
  let returnData = false;
  let argsArrayContainsCharacterRule = [];
  argsArrayContainsCharacterRule[0] = biz.cdoesArrayContainCharacter;
  let argsArrayContainsRegEx1 = await ruleParsing.processRulesInternal([wrd.cregEx, [inputData]], argsArrayContainsCharacterRule);
  await loggers.consoleLog(namespacePrefix + functionName, msg.cargsArrayContainsRegEx1Is + argsArrayContainsRegEx1);
  let argsArrayContainsRegEx2 = await ruleParsing.processRulesInternal([wrd.cRegEx, [inputData]], argsArrayContainsCharacterRule);
  await loggers.consoleLog(namespacePrefix + functionName, msg.cargsArrayContainsRegEx2Is + argsArrayContainsRegEx2);
  let argsArrayContainsColon = await ruleParsing.processRulesInternal([bas.cColon, [inputData]], argsArrayContainsCharacterRule);
  await loggers.consoleLog(namespacePrefix + functionName, msg.cargsArrayContainsColonIs + argsArrayContainsColon);
  if ((argsArrayContainsRegEx1 === true || argsArrayContainsRegEx2 === true) && argsArrayContainsColon === true) {
    returnData = true;
  }
  await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
  await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
  return returnData;
}

/**
 * @function parseArgumentAsRegularExpression
 * @description Parses the argument as a regular expression and returns a new RegExp object.
 * @param {string} inputData The argument string that should be parsed as a RegExp.
 * @param {string} inputMetaData Not used for this business rule.
 * @return {object} A RegExp object.
 * @author Seth Hollingsead
 * @date 2022/05/03
 */
async function parseArgumentAsRegularExpression(inputData, inputMetaData) {
  let functionName = parseArgumentAsRegularExpression.name;
  await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
  await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
  await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
  let returnData = [];
  let regExValue, regExFlags;
  let regExArray = inputData.split(bas.cColon);
  for (let k = 0; k < regExArray.length; k++) {
    if (regExArray[k] === wrd.cregEx || regExArray[k] === wrd.cRegEx) {
      k++;
      // regular expression is:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cregularExpressionIs + regExArray[k]);
      regExValue = regExArray[k];
      // regExValue is:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cregExValueIs + regExValue);
    } else if (regExArray[k] === wrd.cflags || regExArray[k] === wrd.cFlags) {
      k++;
      // regular expression flags are:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cregularExpressionFlagsAre + regExArray[k]);
      regExFlags = regExArray[k];
      // regExFlags is:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cregExFlagsIs + regExFlags);
    }
  } // End-for (let k = 0; k < regExArray.length; k++)
  let regularExpression;
  if (regExValue !== undefined && regExFlags === undefined) {
    regularExpression = new RegExp(regExValue);
  } else if (regExValue !== undefined && regExFlags !== undefined) {
    regularExpression = new RegExp(regExValue, regExFlags);
  } else {
    regularExpression = new RegExp(regExValue);
  }
  returnData = regularExpression;
  await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
  await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
  return returnData;
}

/**
 * @function parseArgumentAsArray
 * @description Parses teh argument as an array and return the array.
 * @param {string} inputData A string that contains an array, we will use the
 * secondary command delimiter to split the string into an array.
 * @param {string} inputMetaData Not used for this business rule.
 * @return {array<string>} An array of strings.
 * @author Seth Hollingsead
 * @date 2022/05/03
 */
async function parseArgumentAsArray(inputData, inputMetaData) {
  let functionName = parseArgumentAsArray.name;
  await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
  // console.log(`BEGIN ${namespacePrefix}${functionName} function`);
  await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
  // console.log(msg.cinputDataIs + JSON.stringify(inputData));
  await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
  // console.log(msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
  let returnData;
  let argumentValue = inputData;
  let isArray = false;
  let secondaryCommandArgsDelimiter = await configurator.getConfigurationSetting(wrd.csystem, cfg.csecondaryCommandDelimiter);
  let argsArrayContainsCharacterRule = [];
  let removeBracketsFromArgsArrayRule = [];
  let utilitiesReplaceCharacterWithCharacterRule = [];
  argsArrayContainsCharacterRule[0] = biz.cdoesArrayContainCharacter;
  removeBracketsFromArgsArrayRule[0] = biz.cremoveCharacterFromArray;
  utilitiesReplaceCharacterWithCharacterRule[0] = biz.cutilitiesReplaceCharacterWithCharacter;
  let argsArrayContainsOpenBracket = false;
  let argsArrayContainsCloseBracket = false;
  if (Array.isArray(argumentValue) === true) {
    argsArrayContainsOpenBracket = await ruleParsing.processRulesInternal([bas.cOpenBracket, argumentValue], argsArrayContainsCharacterRule);
    argsArrayContainsCloseBracket = await ruleParsing.processRulesInternal([bas.cCloseBracket, argumentValue], argsArrayContainsCharacterRule);
    isArray = true;
  } else {
    argsArrayContainsOpenBracket = argumentValue.includes(bas.cOpenBracket);
    argsArrayContainsCloseBracket = argumentValue.includes(bas.cCloseBracket);
    isArray = false;
  }

  if (isArray === false) {
    if (argumentValue.includes(secondaryCommandArgsDelimiter) === true) {
      // argumentValue contains the delimiter, lets split it!
      await loggers.consoleLog(namespacePrefix + functionName, msg.cargumentValueContainsTheDelimiterLetsSplitIt);
      // console.log(msg.cargumentValueContainsTheDelimiterLetsSplitIt);
      argumentValue = argumentValue.split(secondaryCommandArgsDelimiter);
      // Re-evaluate to determine if additional actions are necessary or not.
      argsArrayContainsOpenBracket = await ruleParsing.processRulesInternal([bas.cOpenBracket, argumentValue], argsArrayContainsCharacterRule);
      argsArrayContainsCloseBracket = await ruleParsing.processRulesInternal([bas.cCloseBracket, argumentValue], argsArrayContainsCharacterRule);
      isArray = true;
    } // End-if (argumentValue.includes(secondaryCommandArgsDelimiter) === true)
  } // End-if (isArray === false)
  if (argsArrayContainsOpenBracket === true) {
    if (isArray === true) {
      argumentValue = await ruleParsing.processRulesInternal([bas.cOpenBracket, argumentValue], removeBracketsFromArgsArrayRule);
    } else {
      argumentValue = await ruleParsing.processRulesInternal([argumentValue, [bas.cOpenBracket, '']], utilitiesReplaceCharacterWithCharacterRule);
    }
    // argumentValue after attempting to remove a open bracket from all array elements is:
    await loggers.consoleLog(namespacePrefix + functionName, msg.cargumentValueAfterAttemptingToRemoveOpenBracketFromAllArrayElementsIs + JSON.stringify(argumentValue));
    // console.log(msg.cargumentValueAfterAttemptingToRemoveOpenBracketFromAllArrayElementsIs + JSON.stringify(argumentValue));
  } // End-if (argsArrayContainsOpenBracket === true)
  if (argsArrayContainsCloseBracket === true) {
    if (isArray === true) {
      argumentValue = await ruleParsing.processRulesInternal([bas.cCloseBracket, argumentValue], removeBracketsFromArgsArrayRule);
    } else {
      argumentValue = await ruleParsing.processRulesInternal([argumentValue, [bas.cCloseBracket, '']], utilitiesReplaceCharacterWithCharacterRule);
    }
    // argumentValue after attempting to remove a close bracket from all array elements is:
    await loggers.consoleLog(namespacePrefix + functionName, msg.cargumentValueAfterAttemptingToRemoveCloseBracketFromAllArrayElementsIs + JSON.stringify(argumentValue));
    // console.log(msg.cargumentValueAfterAttemptingToRemoveCloseBracketFromAllArrayElementsIs + JSON.stringify(argumentValue));
  } // End-if (argsArrayContainsCloseBracket === true)
  // secondaryCommandArgsDelimiter is:
  await loggers.consoleLog(namespacePrefix + functionName, msg.csecondaryCommandArgsDelimiterIs + secondaryCommandArgsDelimiter);
  // console.log(msg.csecondaryCommandArgsDelimiterIs + secondaryCommandArgsDelimiter);
  if (isArray === true) {
    if (argumentValue.includes(secondaryCommandArgsDelimiter) === true) {
      // argumentValue contains the delimiter, lets split it!
      await loggers.consoleLog(namespacePrefix + functionName, msg.cargumentValueContainsTheDelimiterLetsSplitIt);
      // console.log(msg.cargumentValueContainsTheDelimiterLetsSplitIt);
      argumentValue = argumentValue.split(secondaryCommandArgsDelimiter);
    } // End-if (argumentValue.includes(secondaryCommandArgsDelimiter) === true)
    returnData = argumentValue;
  } else {
    returnData = [argumentValue];
  }
  await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
  // console.log(msg.creturnDataIs + JSON.stringify(returnData));
  await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
  // console.log(`END ${namespacePrefix}${functionName} function`);
  return returnData;
}

/**
 * @function removeStringLiteralTagsFromArray
 * @description Removes all string literal tags from all the argument array elements passed as input to the function.
 * @param {array<string>} inputData The argument array that should have the string literal tags removed.
 * The string literal tag is the tilde character: "~"
 * @param {string} inputMetaData Not used for this business rule.
 * @return {array<string>} Teh same as the input, but just with the string literal tags removed from all array elements.
 * @author Seth Hollingsead
 * @date 2022/05/03
 */
async function removeStringLiteralTagsFromArray(inputData, inputMetaData) {
  let functionName = removeStringLiteralTagsFromArray.name;
  await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
  await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
  await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
  let returnData;
  returnData = await ruleParsing.processRulesInternal([bas.cTilde, inputData], [biz.cremoveCharacterFromArray]);
  await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
  await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
  return returnData;
}

export default {
  parseBusinessRuleArgument,
  analyzeArgument,
  analyzeForRegularExpression,
  parseArgumentAsRegularExpression,
  parseArgumentAsArray,
  removeStringLiteralTagsFromArray
};
