//===-- MICmdInterpreter.cpp ------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// In-house headers:
#include "MICmdInterpreter.h"
#include "MICmdFactory.h"

//++
// Details: CMICmdInterpreter constructor.
// Type:    Method.
// Args:    None.
// Return:  None.
// Throws:  None.
//--
CMICmdInterpreter::CMICmdInterpreter()
    : m_rCmdFactory(CMICmdFactory::Instance()) {}

//++
// Details: CMICmdInterpreter destructor.
// Type:    Overridable.
// Args:    None.
// Return:  None.
// Throws:  None.
//--
CMICmdInterpreter::~CMICmdInterpreter() { Shutdown(); }

//++
// Details: Initialize resources for *this Command Interpreter.
// Type:    Method.
// Args:    None.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool CMICmdInterpreter::Initialize() {
  m_clientUsageRefCnt++;

  if (m_bInitialized)
    return MIstatus::success;

  m_bInitialized = true;

  return MIstatus::success;
}

//++
// Details: Release resources for *this Command Interpreter.
// Type:    Method.
// Args:    None.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool CMICmdInterpreter::Shutdown() {
  if (--m_clientUsageRefCnt > 0)
    return MIstatus::success;

  if (!m_bInitialized)
    return MIstatus::success;

  m_bInitialized = false;

  return MIstatus::success;
}

//++
// Details: Establish whether the text data is an MI format type command.
// Type:    Method.
// Args:    vTextLine               - (R) Text data to interpret.
//          vwbYesValid             - (W) True = MI type command, false = not
//          recognised.
//          vwbCmdNotInCmdFactor    - (W) True = MI command not found in the
//          command factory, false = recognised.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool CMICmdInterpreter::ValidateIsMi(const CMIUtilString &vTextLine,
                                     bool &vwbYesValid,
                                     bool &vwbCmdNotInCmdFactor,
                                     SMICmdData &rwCmdData) {
  vwbYesValid = false;
  vwbCmdNotInCmdFactor = false;
  rwCmdData.Clear();

  if (vTextLine.empty())
    return MIstatus::success;

  // MI format is [cmd #]-[command name]<space>[command arg(s)]
  // i.e. 1-file-exec-and-symbols --thread-group i1 DEVICE_EXECUTABLE
  //      5-data-evaluate-expression --thread 1 --frame 0 *(argv)

  m_miCmdData.Clear();
  m_miCmdData.strMiCmd = vTextLine;

  // The following change m_miCmdData as valid parts are identified
  vwbYesValid = (MiHasCmdTokenEndingHyphen(vTextLine) ||
                 MiHasCmdTokenEndingAlpha(vTextLine));
  vwbYesValid = vwbYesValid && MiHasCmd(vTextLine);
  if (vwbYesValid) {
    vwbCmdNotInCmdFactor = !HasCmdFactoryGotMiCmd(MiGetCmdData());
    vwbYesValid = !vwbCmdNotInCmdFactor;
  }

  // Update command's meta data valid state
  m_miCmdData.bCmdValid = vwbYesValid;

  // Ok to return new updated command information
  rwCmdData = MiGetCmdData();

  return MIstatus::success;
}

//++
// Details: Establish whether the command name entered on the stdin stream is
// recognised by
//          the MI driver.
// Type:    Method.
// Args:    vCmd    - (R) Command information structure.
// Return:  bool  - True = yes command is recognised, false = command not
// recognised.
// Throws:  None.
//--
bool CMICmdInterpreter::HasCmdFactoryGotMiCmd(const SMICmdData &vCmd) const {
  return m_rCmdFactory.CmdExist(vCmd.strMiCmd);
}

//++
// Details: Does the command entered match the criteria for a MI command format.
//          The format to validate against is 'nn-' where there can be 1 to n
//          digits.
//          I.e. '2-gdb-exit'.
//          Is the execution token present? The command token is entered into
//          the
//          command meta data structure whether correct or not for reporting or
//          later
//          command execution purposes.
// Type:    Method.
// Args:    vTextLine   - (R) Text data to interpret.
// Return:  bool  - True = yes command token present, false = command not
// recognised.
// Throws:  None.
//--
bool CMICmdInterpreter::MiHasCmdTokenEndingHyphen(
    const CMIUtilString &vTextLine) {
  // The hyphen is mandatory
  const size_t nPos = vTextLine.find('-', 0);
  if ((nPos == std::string::npos))
    return false;

  if (MiHasCmdTokenPresent(vTextLine)) {
    const std::string strNum = vTextLine.substr(0, nPos);
    if (!CMIUtilString(strNum).IsNumber())
      return false;

    m_miCmdData.strMiCmdToken = strNum;
  }

  m_miCmdData.bMIOldStyle = false;

  return true;
}

//++
// Details: Does the command entered match the criteria for a MI command format.
//          The format to validate against is 'nnA' where there can be 1 to n
//          digits.
//          'A' represents any non numeric token. I.e. '1source .gdbinit'.
//          Is the execution token present? The command token is entered into
//          the
//          command meta data structure whether correct or not for reporting or
//          later
//          command execution purposes.
// Type:    Method.
// Args:    vTextLine   - (R) Text data to interpret.
// Return:  bool  - True = yes command token present, false = command not
// recognised.
// Throws:  None.
//--
bool CMICmdInterpreter::MiHasCmdTokenEndingAlpha(
    const CMIUtilString &vTextLine) {
  char cChar = vTextLine[0];
  MIuint i = 0;
  while (::isdigit(cChar) != 0) {
    cChar = vTextLine[++i];
  }
  if (::isalpha(cChar) == 0)
    return false;
  if (i == 0)
    return false;

  const std::string strNum = vTextLine.substr(0, i);
  m_miCmdData.strMiCmdToken = strNum.c_str();
  m_miCmdData.bMIOldStyle = true;

  return true;
}

//++
// Details: Does the command entered match the criteria for a MI command format.
//          Is the command token present before the hyphen?
// Type:    Method.
// Args:    vTextLine - (R) Text data to interpret.
// Return:  bool  - True = yes command token present, false = token not present.
// Throws:  None.
//--
bool CMICmdInterpreter::MiHasCmdTokenPresent(const CMIUtilString &vTextLine) {
  const size_t nPos = vTextLine.find('-', 0);
  return (nPos > 0);
}

//++
// Details: Does the command name entered match the criteria for a MI command
// format.
//          Is a recognised command present? The command name is entered into
//          the
//          command meta data structure whether correct or not for reporting or
//          later
//          command execution purposes. Command options is present are also put
//          into the
//          command meta data structure.
// Type:    Method.
// Args:    vTextLine   - (R) Command information structure.
// Return:  bool  - True = yes command name present, false = command not
// recognised.
// Throws:  None.
//--
bool CMICmdInterpreter::MiHasCmd(const CMIUtilString &vTextLine) {
  size_t nPos = 0;
  if (m_miCmdData.bMIOldStyle) {
    char cChar = vTextLine[0];
    size_t i = 0;
    while (::isdigit(cChar) != 0) {
      cChar = vTextLine[++i];
    }
    nPos = --i;
  } else {
    nPos = vTextLine.find('-', 0);
  }

  bool bFoundCmd = false;
  const size_t nLen = vTextLine.length();
  const size_t nPos2 = vTextLine.find(' ', nPos);
  if (nPos2 != std::string::npos) {
    if (nPos2 == nLen)
      return false;
    const CMIUtilString cmd =
        CMIUtilString(vTextLine.substr(nPos + 1, nPos2 - nPos - 1));
    if (cmd.empty())
      return false;

    m_miCmdData.strMiCmd = cmd;

    if (nPos2 < nLen)
      m_miCmdData.strMiCmdOption =
          CMIUtilString(vTextLine.substr(nPos2 + 1, nLen - nPos2 - 1));

    bFoundCmd = true;
  } else {
    const CMIUtilString cmd =
        CMIUtilString(vTextLine.substr(nPos + 1, nLen - nPos - 1));
    if (cmd.empty())
      return false;
    m_miCmdData.strMiCmd = cmd;
    bFoundCmd = true;
  }

  if (bFoundCmd)
    m_miCmdData.strMiCmdAll = vTextLine;

  return bFoundCmd;
}

//++
// Details: Retrieve the just entered new command from stdin. It contains the
// command
//          name, number and any options.
// Type:    Method.
// Args:    vTextLine   - (R) Command information structure.
// Return:  SMICmdData & - Command meta data information/result/status.
// Throws:  None.
//--
const SMICmdData &CMICmdInterpreter::MiGetCmdData() const {
  return m_miCmdData;
}
