/**
 * @file chiefCommander.js
 * @module chiefCommander
 * @description Holds all of the functions that manage the command queue, and allows commands to be enqueued and dequeued from the command queue.
 * Also executes the command queue ultimately by making calls to the commandBroker to execute individual commands.
 * @requires module:commandBroker
 * @requires module:chiefData
 * @requires module:loggers
 * @requires module:data
 * @requires module:queue
 * @requires module:stack
 * @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/01
 * @copyright Copyright © 2022-… by Seth Hollingsead. All rights reserved
 */

// Internal imports
import commandBroker from '../brokers/commandBroker.js';
import chiefData from '../controllers/chiefData.js';
import loggers from '../executrix/loggers.js';
import D from '../structures/data.js';
import queue from '../structures/queue.js';
import stack from '../structures/stack.js';
// External imports
import hayConst from '@haystacks/constants';
import path from 'path';
import configurator from '../executrix/configurator.js'

const {bas, cfg, msg, sys, wrd} = hayConst;
const baseFileName = path.basename(import.meta.url, path.extname(import.meta.url));
// framework.controllers.chiefCommander.
const namespacePrefix = wrd.cframework + bas.cDot + wrd.ccontrollers + bas.cDot + baseFileName + bas.cDot;

/**
 * @function bootStrapCommands
 * @description Initializes all of the commands and gets them added to the D-data structure.
 * @return {void}
 * @author Seth Hollingsead
 * @date 2022/02/01
 */
async function bootStrapCommands() {
  let functionName = bootStrapCommands.name;
  await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
  await commandBroker.bootStrapCommands();
  await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
}

/**
 * @function reinitializeCommands
 * @description Clears out and then re-bootstraps the commands.
 * @return {void}
 * @author Seth Hollingsead
 * @date 2023/02/12
 */
async function reinitializeCommands() {
  let functionName = reinitializeCommands.name;
  await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
  await commandBroker.resetCommands();
  await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
}

/**
 * @function addClientCommands
 * @description This is a wrapper function for calling the commandBroker.addClientCommands.
 * @param {object} clientCommands A map of client defined command names and client defined command function calls.
 * @return {void}
 * @author Seth Hollingsead
 * @date 2022/02/17
 */
async function addClientCommands(clientCommands) {
  let functionName = addClientCommands.name;
  await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
  await commandBroker.addClientCommands(clientCommands);
  await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
}

/**
 * @function loadCommandAliasesFromPath
 * @description Loads the command aliases XML file that is specified by the input.
 * The data is automatically saved on the D-data structure.
 * @param {string} commandAliasesFilePathConfigurationName The path and file name to the XML file that contains the command aliases definitions.
 * (Could be system command aliases or client command aliases)
 * @param {string} contextName The context name defines what parent command alias namespace the loaded and merged data should be stored in.
 * Example:
 * contextName = "framework" => D['CommandsAliases']['Framework']
 * contextName = "application" => D['CommandAliases']['Application']
 * contextName = "plugin" => D['CommandAliases']['Plugins']['<pluginName>']
 * @return {boolean|object} True or False if the data to be loaded is not from a plugin, returns a JSON object is the data to be loaded is from a plugin.
 * @author Seth Hollingsead
 * @date 2022/02/02
 */
async function loadCommandAliasesFromPath(commandAliasesFilePathConfigurationName, contextName) {
  let functionName = loadCommandAliasesFromPath.name;
  await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
  // commandAliasesFilePathConfigurationName is:
  await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasesFilePathConfigurationNameIs + commandAliasesFilePathConfigurationName);
  // contextName is:
  await loggers.consoleLog(namespacePrefix + functionName, msg.ccontextNameIs + contextName);
  let returnData = false;
  let allCommandAliasesData = {};
  if (!contextName.toUpperCase().includes(wrd.cPLUGIN)) {
    allCommandAliasesData = await chiefData.setupAllXmlData(commandAliasesFilePathConfigurationName, sys.cCommandsAliases);
  } else if (contextName.toUpperCase().includes(wrd.cPLUGIN)) {
    allCommandAliasesData = await chiefData.setupAllXmlPluginData(commandAliasesFilePathConfigurationName, sys.cCommandsAliases);
  }
  
  // allCommandAliasesData is:
  await loggers.consoleLog(namespacePrefix + functionName, msg.callCommandAliasesDataIs + JSON.stringify(allCommandAliasesData));
  if (D[sys.cCommandsAliases] === undefined) { // Make sure we only do this if it's undefined, otherwise we might wipe out previously loaded data.
    D[sys.cCommandsAliases] = {};
    D[sys.cCommandsAliases][sys.cFramework] = allCommandAliasesData;
    returnData = true;
  } else if (contextName.toUpperCase() === wrd.cAPPLICATION) {
    D[sys.cCommandsAliases][wrd.cApplication] = allCommandAliasesData;
    returnData = true;
  } else if (contextName.toUpperCase().includes(wrd.cPLUGIN)) {
    let pluginName = await configurator.getConfigurationSetting(wrd.csystem, sys.cPluginName);
    // NOTE: If there is a pluginName in the configuration setting, then we have a special condition that is running here.
    // This is the case that the build-Release app is running to roll a release of a plugin, and plugin validation data is being loaded for validation.
    if (pluginName) {
      D[sys.cCommandsAliases][wrd.cPlugins] = {};
      D[sys.cCommandsAliases][wrd.cPlugins][pluginName] = {};
      D[sys.cCommandsAliases][wrd.cPlugins][pluginName] = allCommandAliasesData;
    }
    returnData = allCommandAliasesData;
  }
  // console.log('All loaded command aliases data is: ' + JSON.stringify(D[sys.cCommandsAliases]));
  await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
  await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
  return returnData;
}

/**
 * @function enqueueCommand
 * @description Determines if the command queue has been setup or not,
 * if not then it is initialized, and the command is added to the command queue.
 * @param {string} command The command that should be added  to the command queue.
 * @return {void}
 * @author Seth Hollingsead
 * @date 2022/02/02
 */
async function enqueueCommand(command) {
  let functionName = enqueueCommand.name;
  await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
  // command is:
  await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandIs + command);
  if (D[sys.cCommandQueue] === undefined) {
    await queue.initQueue(sys.cCommandQueue);
  }
  if (await configurator.getConfigurationSetting(wrd.csystem, cfg.clogAllCommands) === true) {
    await stack.push(sys.cSystemCommandLog, command);
  }
  // D-command stack is:
  // console.log(namespacePrefix + functionName + bas.cSpace + msg.cdCommandStackIs, D[wrd.cCommands]);
  await queue.enqueue(sys.cCommandQueue, command);
  await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
}

/**
 * @function isCommandQueueEmpty
 * @description Determines if the command queue is empty or not empty.
 * @return {boolean} A True or False value to indicate if the command queue is empty or not empty.
 * @author Seth Hollingsead
 * @date 2022/02/02
 */
async function isCommandQueueEmpty() {
  let functionName = isCommandQueueEmpty.name;
  await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
  let returnData = false;
  returnData = await queue.isEmpty(sys.cCommandQueue);
  await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
  await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
  return returnData;
}

/**
 * @function processCommandQueue
 * @description Pulls the command from the front of the command queue and executes it using the command broker.
 * @return {array<boolean,string|integer|boolean|object|array>} An array with a boolean True or False value to
 * indicate if the application should exit or not exit, followed by the command output.
 * @author Seth Hollingsead
 * @date 2022/02/02
 */
async function processCommandQueue() {
  let functionName = processCommandQueue.name;
  await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
  let commandToExecute;
  let returnData;
  await queue.queuePrint(sys.cCommandQueue);
  commandToExecute = await queue.dequeue(sys.cCommandQueue);
  // commandToExecute is:
  await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandToExecuteIs + commandToExecute);
  returnData = await commandBroker.executeCommand(commandToExecute);
  await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
  await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
  return returnData;
}

export default {
  bootStrapCommands,
  reinitializeCommands,
  addClientCommands,
  loadCommandAliasesFromPath,
  enqueueCommand,
  isCommandQueueEmpty,
  processCommandQueue
};