//===-- MIDriver.cpp --------------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

// Third party headers:
#include <fstream>
#include "lldb/API/SBError.h"

// In-house headers:
#include "MIDriver.h"
#include "MICmnResources.h"
#include "MICmnLog.h"
#include "MICmdMgr.h"
#include "MICmnLLDBDebugger.h"
#include "MICmnMIResultRecord.h"
#include "MICmnMIValueConst.h"
#include "MICmnThreadMgrStd.h"
#include "MIUtilDebug.h"
#include "MIUtilSingletonHelper.h"
#include "MICmnStreamStdout.h"
#include "MICmnStreamStderr.h"
#include "MICmdArgValFile.h"
#include "MICmdArgValString.h"
#include "MICmnConfig.h"
#include "MICmnLLDBDebugSessionInfo.h"

// Instantiations:
#if _DEBUG
const CMIUtilString CMIDriver::ms_constMIVersion = MIRSRC(IDS_MI_VERSION_DESCRIPTION_DEBUG);
#else
const CMIUtilString CMIDriver::ms_constMIVersion = MIRSRC(IDS_MI_VERSION_DESCRIPTION); // Matches version in resources file
#endif // _DEBUG
const CMIUtilString CMIDriver::ms_constAppNameShort(MIRSRC(IDS_MI_APPNAME_SHORT));
const CMIUtilString CMIDriver::ms_constAppNameLong(MIRSRC(IDS_MI_APPNAME_LONG));

//++ ------------------------------------------------------------------------------------
// Details: CMIDriver constructor.
// Type:    Method.
// Args:    None.
// Return:  None.
// Throws:  None.
//--
CMIDriver::CMIDriver()
    : m_bFallThruToOtherDriverEnabled(false)
    , m_bDriverIsExiting(false)
    , m_handleMainThread(0)
    , m_rStdin(CMICmnStreamStdin::Instance())
    , m_rLldbDebugger(CMICmnLLDBDebugger::Instance())
    , m_rStdOut(CMICmnStreamStdout::Instance())
    , m_eCurrentDriverState(eDriverState_NotRunning)
    , m_bHaveExecutableFileNamePathOnCmdLine(false)
    , m_bDriverDebuggingArgExecutable(false)
    , m_bHaveCommandFileNamePathOnCmdLine(false)
{
}

//++ ------------------------------------------------------------------------------------
// Details: CMIDriver destructor.
// Type:    Overridden.
// Args:    None.
// Return:  None.
// Throws:  None.
//--
CMIDriver::~CMIDriver()
{
}

//++ ------------------------------------------------------------------------------------
// Details: Set whether *this driver (the parent) is enabled to pass a command to its
//          fall through (child) driver to interpret the command and do work instead
//          (if *this driver decides it can't handle the command).
// Type:    Method.
// Args:    vbYes   - (R) True = yes fall through, false = do not pass on command.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::SetEnableFallThru(const bool vbYes)
{
    m_bFallThruToOtherDriverEnabled = vbYes;
    return MIstatus::success;
}

//++ ------------------------------------------------------------------------------------
// Details: Get whether *this driver (the parent) is enabled to pass a command to its
//          fall through (child) driver to interpret the command and do work instead
//          (if *this driver decides it can't handle the command).
// Type:    Method.
// Args:    None.
// Return:  bool - True = yes fall through, false = do not pass on command.
// Throws:  None.
//--
bool
CMIDriver::GetEnableFallThru() const
{
    return m_bFallThruToOtherDriverEnabled;
}

//++ ------------------------------------------------------------------------------------
// Details: Retrieve MI's application name of itself.
// Type:    Method.
// Args:    None.
// Return:  CMIUtilString & - Text description.
// Throws:  None.
//--
const CMIUtilString &
CMIDriver::GetAppNameShort() const
{
    return ms_constAppNameShort;
}

//++ ------------------------------------------------------------------------------------
// Details: Retrieve MI's application name of itself.
// Type:    Method.
// Args:    None.
// Return:  CMIUtilString & - Text description.
// Throws:  None.
//--
const CMIUtilString &
CMIDriver::GetAppNameLong() const
{
    return ms_constAppNameLong;
}

//++ ------------------------------------------------------------------------------------
// Details: Retrieve MI's version description of itself.
// Type:    Method.
// Args:    None.
// Return:  CMIUtilString & - Text description.
// Throws:  None.
//--
const CMIUtilString &
CMIDriver::GetVersionDescription() const
{
    return ms_constMIVersion;
}

//++ ------------------------------------------------------------------------------------
// Details: Initialize setup *this driver ready for use.
// Type:    Method.
// Args:    None.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::Initialize()
{
    m_eCurrentDriverState = eDriverState_Initialising;
    m_clientUsageRefCnt++;

    ClrErrorDescription();

    if (m_bInitialized)
        return MIstatus::success;

    bool bOk = MIstatus::success;
    CMIUtilString errMsg;

    // Initialize all of the modules we depend on
    MI::ModuleInit<CMICmnLog>(IDS_MI_INIT_ERR_LOG, bOk, errMsg);
    MI::ModuleInit<CMICmnStreamStdout>(IDS_MI_INIT_ERR_STREAMSTDOUT, bOk, errMsg);
    MI::ModuleInit<CMICmnStreamStderr>(IDS_MI_INIT_ERR_STREAMSTDERR, bOk, errMsg);
    MI::ModuleInit<CMICmnResources>(IDS_MI_INIT_ERR_RESOURCES, bOk, errMsg);
    MI::ModuleInit<CMICmnThreadMgrStd>(IDS_MI_INIT_ERR_THREADMANAGER, bOk, errMsg);
    MI::ModuleInit<CMICmnStreamStdin>(IDS_MI_INIT_ERR_STREAMSTDIN, bOk, errMsg);
    MI::ModuleInit<CMICmdMgr>(IDS_MI_INIT_ERR_CMDMGR, bOk, errMsg);
    bOk &= m_rLldbDebugger.SetDriver(*this);
    MI::ModuleInit<CMICmnLLDBDebugger>(IDS_MI_INIT_ERR_LLDBDEBUGGER, bOk, errMsg);

    m_bExitApp = false;

    m_bInitialized = bOk;

    if (!bOk)
    {
        const CMIUtilString msg = CMIUtilString::Format(MIRSRC(IDS_MI_INIT_ERR_DRIVER), errMsg.c_str());
        SetErrorDescription(msg);
        return MIstatus::failure;
    }

    m_eCurrentDriverState = eDriverState_RunningNotDebugging;

    return bOk;
}

//++ ------------------------------------------------------------------------------------
// Details: Unbind detach or release resources used by *this driver.
// Type:    Method.
// Args:    None.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::Shutdown()
{
    if (--m_clientUsageRefCnt > 0)
        return MIstatus::success;

    if (!m_bInitialized)
        return MIstatus::success;

    m_eCurrentDriverState = eDriverState_ShuttingDown;

    ClrErrorDescription();

    bool bOk = MIstatus::success;
    CMIUtilString errMsg;

    // Shutdown all of the modules we depend on
    MI::ModuleShutdown<CMICmnLLDBDebugger>(IDS_MI_INIT_ERR_LLDBDEBUGGER, bOk, errMsg);
    MI::ModuleShutdown<CMICmdMgr>(IDS_MI_INIT_ERR_CMDMGR, bOk, errMsg);
    MI::ModuleShutdown<CMICmnStreamStdin>(IDS_MI_INIT_ERR_STREAMSTDIN, bOk, errMsg);
    MI::ModuleShutdown<CMICmnThreadMgrStd>(IDS_MI_INIT_ERR_THREADMANAGER, bOk, errMsg);
    MI::ModuleShutdown<CMICmnResources>(IDS_MI_INIT_ERR_RESOURCES, bOk, errMsg);
    MI::ModuleShutdown<CMICmnStreamStderr>(IDS_MI_INIT_ERR_STREAMSTDERR, bOk, errMsg);
    MI::ModuleShutdown<CMICmnStreamStdout>(IDS_MI_INIT_ERR_STREAMSTDOUT, bOk, errMsg);
    MI::ModuleShutdown<CMICmnLog>(IDS_MI_INIT_ERR_LOG, bOk, errMsg);

    if (!bOk)
    {
        SetErrorDescriptionn(MIRSRC(IDS_MI_SHUTDOWN_ERR), errMsg.c_str());
    }

    m_eCurrentDriverState = eDriverState_NotRunning;

    return bOk;
}

//++ ------------------------------------------------------------------------------------
// Details: Work function. Client (the driver's user) is able to append their own message
//          in to the MI's Log trace file.
// Type:    Method.
// Args:    vMessage          - (R) Client's text message.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::WriteMessageToLog(const CMIUtilString &vMessage)
{
    CMIUtilString msg;
    msg = CMIUtilString::Format(MIRSRC(IDS_MI_CLIENT_MSG), vMessage.c_str());
    return m_pLog->Write(msg, CMICmnLog::eLogVerbosity_ClientMsg);
}

//++ ------------------------------------------------------------------------------------
// Details: CDriverMgr calls *this driver initialize setup ready for use.
// Type:    Overridden.
// Args:    None.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::DoInitialize()
{
    return CMIDriver::Instance().Initialize();
}

//++ ------------------------------------------------------------------------------------
// Details: CDriverMgr calls *this driver to unbind detach or release resources used by
//          *this driver.
// Type:    Overridden.
// Args:    None.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::DoShutdown()
{
    return CMIDriver::Instance().Shutdown();
}

//++ ------------------------------------------------------------------------------------
// Details: Retrieve the name for *this driver.
// Type:    Overridden.
// Args:    None.
// Return:  CMIUtilString & - Driver name.
// Throws:  None.
//--
const CMIUtilString &
CMIDriver::GetName() const
{
    const CMIUtilString &rName = GetAppNameLong();
    const CMIUtilString &rVsn = GetVersionDescription();
    static CMIUtilString strName = CMIUtilString::Format("%s %s", rName.c_str(), rVsn.c_str());

    return strName;
}

//++ ------------------------------------------------------------------------------------
// Details: Retrieve *this driver's last error condition.
// Type:    Overridden.
// Args:    None.
// Return:  CMIUtilString - Text description.
// Throws:  None.
//--
CMIUtilString
CMIDriver::GetError() const
{
    return GetErrorDescription();
}

//++ ------------------------------------------------------------------------------------
// Details: Call *this driver to return it's debugger.
// Type:    Overridden.
// Args:    None.
// Return:  lldb::SBDebugger & - LLDB debugger object reference.
// Throws:  None.
//--
lldb::SBDebugger &
CMIDriver::GetTheDebugger()
{
    return m_rLldbDebugger.GetTheDebugger();
}

//++ ------------------------------------------------------------------------------------
// Details: Specify another driver *this driver can call should this driver not be able
//          to handle the client data input. DoFallThruToAnotherDriver() makes the call.
// Type:    Overridden.
// Args:    vrOtherDriver     - (R) Reference to another driver object.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::SetDriverToFallThruTo(const CMIDriverBase &vrOtherDriver)
{
    m_pDriverFallThru = const_cast<CMIDriverBase *>(&vrOtherDriver);

    return m_pDriverFallThru->SetDriverParent(*this);
}

//++ ------------------------------------------------------------------------------------
// Details: Proxy function CMIDriverMgr IDriver interface implementation. *this driver's
//          implementation called from here to match the existing function name of the
//          original LLDB driver class (the extra indirection is not necessarily required).
//          Check the arguments that were passed to this program to make sure they are
//          valid and to get their argument values (if any).
// Type:    Overridden.
// Args:    argc        - (R)   An integer that contains the count of arguments that follow in
//                              argv. The argc parameter is always greater than or equal to 1.
//          argv        - (R)   An array of null-terminated strings representing command-line
//                              arguments entered by the user of the program. By convention,
//                              argv[0] is the command with which the program is invoked.
//          vpStdOut    - (R)   Pointer to a standard output stream.
//          vwbExiting  - (W)   True = *this want to exit, Reasons: help, invalid arg(s),
//                              version information only.
//                              False = Continue to work, start debugger i.e. Command
//                              interpreter.
// Return:  lldb::SBError - LLDB current error status.
// Throws:  None.
//--
lldb::SBError
CMIDriver::DoParseArgs(const int argc, const char *argv[], FILE *vpStdOut, bool &vwbExiting)
{
    return ParseArgs(argc, argv, vpStdOut, vwbExiting);
}

//++ ------------------------------------------------------------------------------------
// Details: Check the arguments that were passed to this program to make sure they are
//          valid and to get their argument values (if any). The following are options
//          that are only handled by *this driver:
//              --executable <file>
//              --source <file> or -s <file>
//          The application's options --interpreter and --executable in code act very similar.
//          The --executable is necessary to differentiate whether the MI Driver is being
//          used by a client (e.g. Eclipse) or from the command line. Eclipse issues the option
//          --interpreter and also passes additional arguments which can be interpreted as an
//          executable if called from the command line. Using --executable tells the MI Driver
//          it is being called from the command line and to prepare to launch the executable
//          argument for a debug session. Using --interpreter on the command line does not
//          issue additional commands to initialise a debug session.
// Type:    Overridden.
// Args:    argc        - (R)   An integer that contains the count of arguments that follow in
//                              argv. The argc parameter is always greater than or equal to 1.
//          argv        - (R)   An array of null-terminated strings representing command-line
//                              arguments entered by the user of the program. By convention,
//                              argv[0] is the command with which the program is invoked.
//          vpStdOut    - (R)   Pointer to a standard output stream.
//          vwbExiting  - (W)   True = *this want to exit, Reasons: help, invalid arg(s),
//                              version information only.
//                              False = Continue to work, start debugger i.e. Command
//                              interpreter.
// Return:  lldb::SBError - LLDB current error status.
// Throws:  None.
//--
lldb::SBError
CMIDriver::ParseArgs(const int argc, const char *argv[], FILE *vpStdOut, bool &vwbExiting)
{
    lldb::SBError errStatus;
    const bool bHaveArgs(argc >= 2);

    // *** Add any args handled here to GetHelpOnCmdLineArgOptions() ***

    // CODETAG_MIDRIVE_CMD_LINE_ARG_HANDLING
    // Look for the command line options
    bool bHaveExecutableFileNamePath = false;
    bool bHaveExecutableLongOption = false;

    if (bHaveArgs)
    {
        // Search right to left to look for filenames
        for (MIint i = argc - 1; i > 0; i--)
        {
            const CMIUtilString strArg(argv[i]);
            const CMICmdArgValFile argFile;

            // Check for a filename
            if (argFile.IsFilePath(strArg) || CMICmdArgValString(true, false, true).IsStringArg(strArg))
            {
                // Is this the command file for the '-s' or '--source' options?
                const CMIUtilString strPrevArg(argv[i - 1]);
                if (strPrevArg.compare("-s") == 0 || strPrevArg.compare("--source") == 0)
                {
                    m_strCmdLineArgCommandFileNamePath = strArg;
                    m_bHaveCommandFileNamePathOnCmdLine = true;
                    i--; // skip '-s' on the next loop
                    continue;
                }
                // Else, must be the executable
                bHaveExecutableFileNamePath = true;
                m_strCmdLineArgExecuteableFileNamePath = strArg;
                m_bHaveExecutableFileNamePathOnCmdLine = true;
            }
            // Report error if no command file was specified for the '-s' or '--source' options
            else if (strArg.compare("-s") == 0 || strArg.compare("--source") == 0)
            {
                vwbExiting = true;
                const CMIUtilString errMsg = CMIUtilString::Format(MIRSRC(IDS_CMD_ARGS_ERR_VALIDATION_MISSING_INF), strArg.c_str());
                errStatus.SetErrorString(errMsg.c_str());
                break;
            }
            // This argument is also checked for in CMIDriverMgr::ParseArgs()
            else if (strArg.compare("--executable") == 0) // Used to specify that there is executable argument also on the command line
            {                                             // See fn description.
                bHaveExecutableLongOption = true;
            }
        }
    }

    if (bHaveExecutableFileNamePath && bHaveExecutableLongOption)
    {
        SetDriverDebuggingArgExecutable();
    }

    return errStatus;
}

//++ ------------------------------------------------------------------------------------
// Details: A client can ask if *this driver is GDB/MI compatible.
// Type:    Overridden.
// Args:    None.
// Return:  True - GBD/MI compatible LLDB front end.
//          False - Not GBD/MI compatible LLDB front end.
// Throws:  None.
//--
bool
CMIDriver::GetDriverIsGDBMICompatibleDriver() const
{
    return true;
}

//++ ------------------------------------------------------------------------------------
// Details: Start worker threads for the driver.
// Type:    Method.
// Args:    None.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::StartWorkerThreads()
{
    bool bOk = MIstatus::success;

    // Grab the thread manager
    CMICmnThreadMgrStd &rThreadMgr = CMICmnThreadMgrStd::Instance();

    // Start the event polling thread
    if (bOk && !rThreadMgr.ThreadStart<CMICmnLLDBDebugger>(m_rLldbDebugger))
    {
        const CMIUtilString errMsg = CMIUtilString::Format(MIRSRC(IDS_THREADMGR_ERR_THREAD_FAIL_CREATE),
                                                           CMICmnThreadMgrStd::Instance().GetErrorDescription().c_str());
        SetErrorDescriptionn(errMsg);
        return MIstatus::failure;
    }

    return bOk;
}

//++ ------------------------------------------------------------------------------------
// Details: Stop worker threads for the driver.
// Type:    Method.
// Args:    None.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::StopWorkerThreads()
{
    CMICmnThreadMgrStd &rThreadMgr = CMICmnThreadMgrStd::Instance();
    return rThreadMgr.ThreadAllTerminate();
}

//++ ------------------------------------------------------------------------------------
// Details: Call this function puts *this driver to work.
//          This function is used by the application's main thread.
// Type:    Overridden.
// Args:    None.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::DoMainLoop()
{
    if (!InitClientIDEToMIDriver()) // Init Eclipse IDE
    {
        SetErrorDescriptionn(MIRSRC(IDS_MI_INIT_ERR_CLIENT_USING_DRIVER));
        return MIstatus::failure;
    }

    if (!StartWorkerThreads())
        return MIstatus::failure;

    bool bOk = MIstatus::success;

    if (HaveExecutableFileNamePathOnCmdLine())
    {
        if (!LocalDebugSessionStartupExecuteCommands())
        {
            SetErrorDescription(MIRSRC(IDS_MI_INIT_ERR_LOCAL_DEBUG_SESSION));
            bOk = MIstatus::failure;
        }
    }

    // App is not quitting currently
    m_bExitApp = false;

    // Handle source file
    if (m_bHaveCommandFileNamePathOnCmdLine)
    {
        const bool bAsyncMode = false;
        ExecuteCommandFile(bAsyncMode);
    }

    // While the app is active
    while (bOk && !m_bExitApp)
    {
        CMIUtilString errorText;
        const char *pCmd = m_rStdin.ReadLine (errorText);
        if (pCmd != nullptr)
        {
            CMIUtilString lineText(pCmd);
            if (!lineText.empty ())
            {
                // Check that the handler thread is alive (otherwise we stuck here)
                assert(CMICmnLLDBDebugger::Instance().ThreadIsActive());

                {
                    // Lock Mutex before processing commands so that we don't disturb an event
                    // being processed
                    CMIUtilThreadLock lock(CMICmnLLDBDebugSessionInfo::Instance().GetSessionMutex());
                    bOk = InterpretCommand(lineText);
                }

                // Draw prompt if desired
                bOk = bOk && CMICmnStreamStdout::WritePrompt();

                // Wait while the handler thread handles incoming events
                CMICmnLLDBDebugger::Instance().WaitForHandleEvent();
            }
        }
    }

    // Signal that the application is shutting down
    DoAppQuit();

    // Close and wait for the workers to stop
    StopWorkerThreads();

    // Ensure that a new line is sent as the last act of the dying driver
    m_rStdOut.WriteMIResponse("\n", false);

    return MIstatus::success;
}

//++ ------------------------------------------------------------------------------------
// Details: Set things in motion, set state etc that brings *this driver (and the
//          application) to a tidy shutdown.
//          This function is used by the application's main thread.
// Type:    Method.
// Args:    None.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::DoAppQuit()
{
    bool bYesQuit = true;

    // Shutdown stuff, ready app for exit
    {
        CMIUtilThreadLock lock(m_threadMutex);
        m_bDriverIsExiting = true;
    }

    return bYesQuit;
}

//++ ------------------------------------------------------------------------------------
// Details: *this driver passes text commands to a fall through driver is it does not
//          understand them (the LLDB driver).
//          This function is used by the application's main thread.
// Type:    Method.
// Args:    vTextLine           - (R) Text data representing a possible command.
//          vwbCmdYesValid      - (W) True = Command valid, false = command not handled.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::InterpretCommandFallThruDriver(const CMIUtilString &vTextLine, bool &vwbCmdYesValid)
{
    MIunused(vTextLine);
    MIunused(vwbCmdYesValid);

    // ToDo: Implement when less urgent work to be done or decide remove as not required
    // bool bOk = MIstatus::success;
    // bool bCmdNotUnderstood = true;
    // if( bCmdNotUnderstood && GetEnableFallThru() )
    //{
    //  CMIUtilString errMsg;
    //  bOk = DoFallThruToAnotherDriver( vStdInBuffer, errMsg );
    //  if( !bOk )
    //  {
    //      errMsg = errMsg.StripCREndOfLine();
    //      errMsg = errMsg.StripCRAll();
    //      const CMIDriverBase * pOtherDriver = GetDriverToFallThruTo();
    //      const char * pName = pOtherDriver->GetDriverName().c_str();
    //      const char * pId = pOtherDriver->GetDriverId().c_str();
    //      const CMIUtilString msg( CMIUtilString::Format( MIRSRC( IDS_DRIVER_ERR_FALLTHRU_DRIVER_ERR ), pName, pId, errMsg.c_str() )
    //);
    //      m_pLog->WriteMsg( msg );
    //  }
    //}
    //
    // vwbCmdYesValid = bOk;
    // CMIUtilString strNot;
    // if( vwbCmdYesValid)
    //  strNot = CMIUtilString::Format( "%s ", MIRSRC( IDS_WORD_NOT ) );
    // const CMIUtilString msg( CMIUtilString::Format( MIRSRC( IDS_FALLTHRU_DRIVER_CMD_RECEIVED ), vTextLine.c_str(), strNot.c_str() ) );
    // m_pLog->WriteLog( msg );

    return MIstatus::success;
}

//++ ------------------------------------------------------------------------------------
// Details: Retrieve the name for *this driver.
// Type:    Overridden.
// Args:    None.
// Return:  CMIUtilString & - Driver name.
// Throws:  None.
//--
const CMIUtilString &
CMIDriver::GetDriverName() const
{
    return GetName();
}

//++ ------------------------------------------------------------------------------------
// Details: Get the unique ID for *this driver.
// Type:    Overridden.
// Args:    None.
// Return:  CMIUtilString & - Text description.
// Throws:  None.
//--
const CMIUtilString &
CMIDriver::GetDriverId() const
{
    return GetId();
}

//++ ------------------------------------------------------------------------------------
// Details: This function allows *this driver to call on another driver to perform work
//          should this driver not be able to handle the client data input.
//          SetDriverToFallThruTo() specifies the fall through to driver.
//          Check the error message if the function returns a failure.
// Type:    Overridden.
// Args:    vCmd        - (R) Command instruction to interpret.
//          vwErrMsg    - (W) Error description on command failing.
// Return:  MIstatus::success - Command succeeded.
//          MIstatus::failure - Command failed.
// Throws:  None.
//--
bool
CMIDriver::DoFallThruToAnotherDriver(const CMIUtilString &vCmd, CMIUtilString &vwErrMsg)
{
    bool bOk = MIstatus::success;

    CMIDriverBase *pOtherDriver = GetDriverToFallThruTo();
    if (pOtherDriver == nullptr)
        return bOk;

    return pOtherDriver->DoFallThruToAnotherDriver(vCmd, vwErrMsg);
}

//++ ------------------------------------------------------------------------------------
// Details: *this driver provides a file stream to other drivers on which *this driver
//          write's out to and they read as expected input. *this driver is passing
//          through commands to the (child) pass through assigned driver.
// Type:    Overrdidden.
// Args:    None.
// Return:  FILE * - Pointer to stream.
// Throws:  None.
//--
FILE *
CMIDriver::GetStdin() const
{
    // Note this fn is called on CMIDriverMgr register driver so stream has to be
    // available before *this driver has been initialized! Flaw?

    // This very likely to change later to a stream that the pass thru driver
    // will read and we write to give it 'input'
    return stdin;
}

//++ ------------------------------------------------------------------------------------
// Details: *this driver provides a file stream to other pass through assigned drivers
//          so they know what to write to.
// Type:    Overidden.
// Args:    None.
// Return:  FILE * - Pointer to stream.
// Throws:  None.
//--
FILE *
CMIDriver::GetStdout() const
{
    // Note this fn is called on CMIDriverMgr register driver so stream has to be
    // available before *this driver has been initialized! Flaw?

    // Do not want to pass through driver to write to stdout
    return NULL;
}

//++ ------------------------------------------------------------------------------------
// Details: *this driver provides a error file stream to other pass through assigned drivers
//          so they know what to write to.
// Type:    Overidden.
// Args:    None.
// Return:  FILE * - Pointer to stream.
// Throws:  None.
//--
FILE *
CMIDriver::GetStderr() const
{
    // Note this fn is called on CMIDriverMgr register driver so stream has to be
    // available before *this driver has been initialized! Flaw?

    // This very likely to change later to a stream that the pass thru driver
    // will write to and *this driver reads from to pass on the CMICmnLog object
    return stderr;
}

//++ ------------------------------------------------------------------------------------
// Details: Set a unique ID for *this driver. It cannot be empty.
// Type:    Overridden.
// Args:    vId - (R) Text description.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::SetId(const CMIUtilString &vId)
{
    if (vId.empty())
    {
        SetErrorDescriptionn(MIRSRC(IDS_DRIVER_ERR_ID_INVALID), GetName().c_str(), vId.c_str());
        return MIstatus::failure;
    }

    m_strDriverId = vId;
    return MIstatus::success;
}

//++ ------------------------------------------------------------------------------------
// Details: Get the unique ID for *this driver.
// Type:    Overridden.
// Args:    None.
// Return:  CMIUtilString & - Text description.
// Throws:  None.
//--
const CMIUtilString &
CMIDriver::GetId() const
{
    return m_strDriverId;
}

//++ ------------------------------------------------------------------------------------
// Details: Interpret the text data and match against current commands to see if there
//          is a match. If a match then the command is issued and actioned on. The
//          text data if not understood by *this driver is past on to the Fall Thru
//          driver.
//          This function is used by the application's main thread.
// Type:    Method.
// Args:    vTextLine   - (R) Text data representing a possible command.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::InterpretCommand(const CMIUtilString &vTextLine)
{
    const bool bNeedToRebroadcastStopEvent = m_rLldbDebugger.CheckIfNeedToRebroadcastStopEvent();
    bool bCmdYesValid = false;
    bool bOk = InterpretCommandThisDriver(vTextLine, bCmdYesValid);
    if (bOk && !bCmdYesValid)
        bOk = InterpretCommandFallThruDriver(vTextLine, bCmdYesValid);

    if (bNeedToRebroadcastStopEvent)
        m_rLldbDebugger.RebroadcastStopEvent();

    return bOk;
}

//++ ------------------------------------------------------------------------------------
// Details: Helper function for CMIDriver::InterpretCommandThisDriver.
//          Convert a CLI command to MI command (just wrap any CLI command
//          into "<tokens>-interpreter-exec command \"<CLI command>\"").
// Type:    Method.
// Args:    vTextLine   - (R) Text data representing a possible command.
// Return:  CMIUtilString   - The original MI command or converted CLI command.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
CMIUtilString
CMIDriver::WrapCLICommandIntoMICommand(const CMIUtilString &vTextLine) const
{
    // Tokens contain following digits
    static const CMIUtilString digits("0123456789");

    // Consider an algorithm on the following example:
    // 001-file-exec-and-symbols "/path/to/file"
    //
    // 1. Skip a command token
    // For example:
    // 001-file-exec-and-symbols "/path/to/file"
    // 001target create "/path/to/file"
    //    ^ -- command starts here (in both cases)
    // Also possible case when command not found:
    // 001
    //    ^ -- i.e. only tokens are present (or empty string at all)
    const size_t nCommandOffset = vTextLine.find_first_not_of(digits);

    // 2. Check if command is empty
    // For example:
    // 001-file-exec-and-symbols "/path/to/file"
    // 001target create "/path/to/file"
    //    ^ -- command not empty (in both cases)
    // or:
    // 001
    //    ^ -- command wasn't found
    const bool bIsEmptyCommand = (nCommandOffset == CMIUtilString::npos);

    // 3. Check and exit if it isn't a CLI command
    // For example:
    // 001-file-exec-and-symbols "/path/to/file"
    // 001
    //    ^ -- it isn't CLI command (in both cases)
    // or:
    // 001target create "/path/to/file"
    //    ^ -- it's CLI command
    const bool bIsCliCommand = !bIsEmptyCommand && (vTextLine.at(nCommandOffset) != '-');
    if (!bIsCliCommand)
        return vTextLine;

   // 4. Wrap CLI command to make it MI-compatible
   //
   // 001target create "/path/to/file"
   // ^^^ -- token
   const std::string vToken(vTextLine.begin(), vTextLine.begin() + nCommandOffset);
   // 001target create "/path/to/file"
   //    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- CLI command
   const CMIUtilString vCliCommand(std::string(vTextLine, nCommandOffset));

   // 5. Escape special characters and embed the command in a string
   // Result: it looks like -- target create \"/path/to/file\".
   const std::string vShieldedCliCommand(vCliCommand.AddSlashes());

   // 6. Turn the CLI command into an MI command, as in:
   // 001-interpreter-exec command "target create \"/path/to/file\""
   // ^^^ -- token
   //    ^^^^^^^^^^^^^^^^^^^^^^^^^^^                               ^ -- wrapper
   //                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- shielded CLI command
   return CMIUtilString::Format("%s-interpreter-exec command \"%s\"",
                                vToken.c_str(), vShieldedCliCommand.c_str());
}

//++ ------------------------------------------------------------------------------------
// Details: Interpret the text data and match against current commands to see if there
//          is a match. If a match then the command is issued and actioned on. If a
//          command cannot be found to match then vwbCmdYesValid is set to false and
//          nothing else is done here.
//          This function is used by the application's main thread.
// Type:    Method.
// Args:    vTextLine           - (R) Text data representing a possible command.
//          vwbCmdYesValid      - (W) True = Command valid, false = command not handled.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::InterpretCommandThisDriver(const CMIUtilString &vTextLine, bool &vwbCmdYesValid)
{
    // Convert any CLI commands into MI commands
    const CMIUtilString vMITextLine(WrapCLICommandIntoMICommand(vTextLine));

    vwbCmdYesValid = false;
    bool bCmdNotInCmdFactor = false;
    SMICmdData cmdData;
    CMICmdMgr &rCmdMgr = CMICmdMgr::Instance();
    if (!rCmdMgr.CmdInterpret(vMITextLine, vwbCmdYesValid, bCmdNotInCmdFactor, cmdData))
        return MIstatus::failure;

    if (vwbCmdYesValid)
    {
        // For debugging only
        // m_pLog->WriteLog( cmdData.strMiCmdAll.c_str() );

        return ExecuteCommand(cmdData);
    }

    // Check for escape character, may be cursor control characters
    // This code is not necessary for application operation, just want to keep tabs on what
    // has been given to the driver to try and interpret.
    if (vMITextLine.at(0) == 27)
    {
        CMIUtilString logInput(MIRSRC(IDS_STDIN_INPUT_CTRL_CHARS));
        for (MIuint i = 0; i < vMITextLine.length(); i++)
        {
            logInput += CMIUtilString::Format("%d ", vMITextLine.at(i));
        }
        m_pLog->WriteLog(logInput);
        return MIstatus::success;
    }

    // Write to the Log that a 'command' was not valid.
    // Report back to the MI client via MI result record.
    CMIUtilString strNotInCmdFactory;
    if (bCmdNotInCmdFactor)
        strNotInCmdFactory = CMIUtilString::Format(MIRSRC(IDS_DRIVER_CMD_NOT_IN_FACTORY), cmdData.strMiCmd.c_str());
    const CMIUtilString strNot(CMIUtilString::Format("%s ", MIRSRC(IDS_WORD_NOT)));
    const CMIUtilString msg(
        CMIUtilString::Format(MIRSRC(IDS_DRIVER_CMD_RECEIVED), vMITextLine.c_str(), strNot.c_str(), strNotInCmdFactory.c_str()));
    const CMICmnMIValueConst vconst = CMICmnMIValueConst(msg);
    const CMICmnMIValueResult valueResult("msg", vconst);
    const CMICmnMIResultRecord miResultRecord(cmdData.strMiCmdToken, CMICmnMIResultRecord::eResultClass_Error, valueResult);
    const bool bOk = m_rStdOut.WriteMIResponse(miResultRecord.GetString());

    // Proceed to wait for or execute next command
    return bOk;
}

//++ ------------------------------------------------------------------------------------
// Details: Having previously had the potential command validated and found valid now
//          get the command executed.
//          This function is used by the application's main thread.
// Type:    Method.
// Args:    vCmdData    - (RW) Command meta data.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMIDriver::ExecuteCommand(const SMICmdData &vCmdData)
{
    CMICmdMgr &rCmdMgr = CMICmdMgr::Instance();
    return rCmdMgr.CmdExecute(vCmdData);
}

//++ ------------------------------------------------------------------------------------
// Details: Set the MI Driver's exit application flag. The application checks this flag
//          after every stdin line is read so the exit may not be instantaneous.
//          If vbForceExit is false the MI Driver queries its state and determines if is
//          should exit or continue operating depending on that running state.
//          This is related to the running state of the MI driver.
// Type:    Overridden.
// Args:    None.
// Return:  None.
// Throws:  None.
//--
void
CMIDriver::SetExitApplicationFlag(const bool vbForceExit)
{
    if (vbForceExit)
    {
        CMIUtilThreadLock lock(m_threadMutex);
        m_bExitApp = true;
        return;
    }

    // CODETAG_DEBUG_SESSION_RUNNING_PROG_RECEIVED_SIGINT_PAUSE_PROGRAM
    // Did we receive a SIGINT from the client during a running debug program, if
    // so then SIGINT is not to be taken as meaning kill the MI driver application
    // but halt the inferior program being debugged instead
    if (m_eCurrentDriverState == eDriverState_RunningDebugging)
    {
        InterpretCommand("-exec-interrupt");
        return;
    }

    m_bExitApp = true;
}

//++ ------------------------------------------------------------------------------------
// Details: Get the  MI Driver's exit exit application flag.
//          This is related to the running state of the MI driver.
// Type:    Method.
// Args:    None.
// Return:  bool    - True = MI Driver is shutting down, false = MI driver is running.
// Throws:  None.
//--
bool
CMIDriver::GetExitApplicationFlag() const
{
    return m_bExitApp;
}

//++ ------------------------------------------------------------------------------------
// Details: Get the current running state of the MI Driver.
// Type:    Method.
// Args:    None.
// Return:  DriverState_e   - The current running state of the application.
// Throws:  None.
//--
CMIDriver::DriverState_e
CMIDriver::GetCurrentDriverState() const
{
    return m_eCurrentDriverState;
}

//++ ------------------------------------------------------------------------------------
// Details: Set the current running state of the MI Driver to running and currently not in
//          a debug session.
// Type:    Method.
// Return:  MIstatus::success - Functionality succeeded.
//          MIstatus::failure - Functionality failed.
// Return:  DriverState_e   - The current running state of the application.
// Throws:  None.
//--
bool
CMIDriver::SetDriverStateRunningNotDebugging()
{
    // CODETAG_DEBUG_SESSION_RUNNING_PROG_RECEIVED_SIGINT_PAUSE_PROGRAM

    if (m_eCurrentDriverState == eDriverState_RunningNotDebugging)
        return MIstatus::success;

    // Driver cannot be in the following states to set eDriverState_RunningNotDebugging
    switch (m_eCurrentDriverState)
    {
        case eDriverState_NotRunning:
        case eDriverState_Initialising:
        case eDriverState_ShuttingDown:
        {
            SetErrorDescription(MIRSRC(IDS_DRIVER_ERR_DRIVER_STATE_ERROR));
            return MIstatus::failure;
        }
        case eDriverState_RunningDebugging:
        case eDriverState_RunningNotDebugging:
            break;
        case eDriverState_count:
            SetErrorDescription(
                CMIUtilString::Format(MIRSRC(IDS_CODE_ERR_INVALID_ENUMERATION_VALUE), "SetDriverStateRunningNotDebugging()"));
            return MIstatus::failure;
    }

    // Driver must be in this state to set eDriverState_RunningNotDebugging
    if (m_eCurrentDriverState != eDriverState_RunningDebugging)
    {
        SetErrorDescription(MIRSRC(IDS_DRIVER_ERR_DRIVER_STATE_ERROR));
        return MIstatus::failure;
    }

    m_eCurrentDriverState = eDriverState_RunningNotDebugging;

    return MIstatus::success;
}

//++ ------------------------------------------------------------------------------------
// Details: Set the current running state of the MI Driver to running and currently not in
//          a debug session. The driver's state must in the state running and in a
//          debug session to set this new state.
// Type:    Method.
// Return:  MIstatus::success - Functionality succeeded.
//          MIstatus::failure - Functionality failed.
// Return:  DriverState_e   - The current running state of the application.
// Throws:  None.
//--
bool
CMIDriver::SetDriverStateRunningDebugging()
{
    // CODETAG_DEBUG_SESSION_RUNNING_PROG_RECEIVED_SIGINT_PAUSE_PROGRAM

    if (m_eCurrentDriverState == eDriverState_RunningDebugging)
        return MIstatus::success;

    // Driver cannot be in the following states to set eDriverState_RunningDebugging
    switch (m_eCurrentDriverState)
    {
        case eDriverState_NotRunning:
        case eDriverState_Initialising:
        case eDriverState_ShuttingDown:
        {
            SetErrorDescription(MIRSRC(IDS_DRIVER_ERR_DRIVER_STATE_ERROR));
            return MIstatus::failure;
        }
        case eDriverState_RunningDebugging:
        case eDriverState_RunningNotDebugging:
            break;
        case eDriverState_count:
            SetErrorDescription(CMIUtilString::Format(MIRSRC(IDS_CODE_ERR_INVALID_ENUMERATION_VALUE), "SetDriverStateRunningDebugging()"));
            return MIstatus::failure;
    }

    // Driver must be in this state to set eDriverState_RunningDebugging
    if (m_eCurrentDriverState != eDriverState_RunningNotDebugging)
    {
        SetErrorDescription(MIRSRC(IDS_DRIVER_ERR_DRIVER_STATE_ERROR));
        return MIstatus::failure;
    }

    m_eCurrentDriverState = eDriverState_RunningDebugging;

    return MIstatus::success;
}

//++ ------------------------------------------------------------------------------------
// Details: Prepare the client IDE so it will start working/communicating with *this MI
//          driver.
// Type:    Method.
// Args:    None.
// Return:  MIstatus::success - Functionality succeeded.
//          MIstatus::failure - Functionality failed.
// Throws:  None.
//--
bool
CMIDriver::InitClientIDEToMIDriver() const
{
    // Put other IDE init functions here
    return InitClientIDEEclipse();
}

//++ ------------------------------------------------------------------------------------
// Details: The IDE Eclipse when debugging locally expects "(gdb)\n" character
//          sequence otherwise it refuses to communicate and times out. This should be
//          sent to Eclipse before anything else.
// Type:    Method.
// Args:    None.
// Return:  MIstatus::success - Functionality succeeded.
//          MIstatus::failure - Functionality failed.
// Throws:  None.
//--
bool
CMIDriver::InitClientIDEEclipse() const
{
    return CMICmnStreamStdout::WritePrompt();
}

//++ ------------------------------------------------------------------------------------
// Details: Ask *this driver whether it found an executable in the MI Driver's list of
//          arguments which to open and debug. If so instigate commands to set up a debug
//          session for that executable.
// Type:    Method.
// Args:    None.
// Return:  bool - True = True = Yes executable given as one of the parameters to the MI
//                 Driver.
//                 False = not found.
// Throws:  None.
//--
bool
CMIDriver::HaveExecutableFileNamePathOnCmdLine() const
{
    return m_bHaveExecutableFileNamePathOnCmdLine;
}

//++ ------------------------------------------------------------------------------------
// Details: Retrieve from *this driver executable file name path to start a debug session
//          with (if present see HaveExecutableFileNamePathOnCmdLine()).
// Type:    Method.
// Args:    None.
// Return:  CMIUtilString & - Executeable file name path or empty string.
// Throws:  None.
//--
const CMIUtilString &
CMIDriver::GetExecutableFileNamePathOnCmdLine() const
{
    return m_strCmdLineArgExecuteableFileNamePath;
}

//++ ------------------------------------------------------------------------------------
// Details: Execute commands (by injecting them into the stdin line queue container) and
//          other code to set up the MI Driver such that is can take the executable
//          argument passed on the command and create a debug session for it.
// Type:    Method.
// Args:    None.
// Return:  MIstatus::success - Functionality succeeded.
//          MIstatus::failure - Functionality failed.
// Throws:  None.
//--
bool
CMIDriver::LocalDebugSessionStartupExecuteCommands()
{
    const CMIUtilString strCmd(CMIUtilString::Format("-file-exec-and-symbols \"%s\"", m_strCmdLineArgExecuteableFileNamePath.AddSlashes().c_str()));
    bool bOk = CMICmnStreamStdout::TextToStdout(strCmd);
    bOk = bOk && InterpretCommand(strCmd);
    bOk = bOk && CMICmnStreamStdout::WritePrompt();
    return bOk;
}

//++ ------------------------------------------------------------------------------------
// Details: Set the MI Driver into "its debugging an executable passed as an argument"
//          mode as against running via a client like Eclipse.
// Type:    Method.
// Args:    None.
// Return:  None.
// Throws:  None.
//--
void
CMIDriver::SetDriverDebuggingArgExecutable()
{
    m_bDriverDebuggingArgExecutable = true;
}

//++ ------------------------------------------------------------------------------------
// Details: Retrieve the MI Driver state indicating if it is operating in "its debugging
//          an executable passed as an argument" mode as against running via a client
//          like Eclipse.
// Type:    Method.
// Args:    None.
// Return:  None.
// Throws:  None.
//--
bool
CMIDriver::IsDriverDebuggingArgExecutable() const
{
    return m_bDriverDebuggingArgExecutable;
}

//++ ------------------------------------------------------------------------------------
// Details: Execute commands from command source file in specified mode, and
//          set exit-flag if needed.
// Type:    Method.
// Args:    vbAsyncMode       - (R) True = execute commands in asynchronous mode, false = otherwise.
// Return:  MIstatus::success - Function succeeded.
//          MIstatus::failure - Function failed.
// Throws:  None.
//--
bool
CMIDriver::ExecuteCommandFile(const bool vbAsyncMode)
{
    std::ifstream ifsStartScript(m_strCmdLineArgCommandFileNamePath.c_str());
    if (!ifsStartScript.is_open())
    {
        const CMIUtilString errMsg(
            CMIUtilString::Format(MIRSRC(IDS_UTIL_FILE_ERR_OPENING_FILE_UNKNOWN), m_strCmdLineArgCommandFileNamePath.c_str()));
        SetErrorDescription(errMsg.c_str());
        const bool bForceExit = true;
        SetExitApplicationFlag(bForceExit);
        return MIstatus::failure;
    }

    // Switch lldb to synchronous mode
    CMICmnLLDBDebugSessionInfo &rSessionInfo(CMICmnLLDBDebugSessionInfo::Instance());
    const bool bAsyncSetting = rSessionInfo.GetDebugger().GetAsync();
    rSessionInfo.GetDebugger().SetAsync(vbAsyncMode);

    // Execute commands from file
    bool bOk = MIstatus::success;
    CMIUtilString strCommand;
    while (!m_bExitApp && std::getline(ifsStartScript, strCommand))
    {
        // Print command
        bOk = CMICmnStreamStdout::TextToStdout(strCommand);

        // Skip if it's a comment or empty line
        if (strCommand.empty() || strCommand[0] == '#')
            continue;

        // Execute if no error
        if (bOk)
        {
            CMIUtilThreadLock lock(rSessionInfo.GetSessionMutex());
            bOk = InterpretCommand(strCommand);
        }

        // Draw the prompt after command will be executed (if enabled)
        bOk = bOk && CMICmnStreamStdout::WritePrompt();

        // Exit if there is an error
        if (!bOk)
        {
            const bool bForceExit = true;
            SetExitApplicationFlag(bForceExit);
            break;
        }

        // Wait while the handler thread handles incoming events
        CMICmnLLDBDebugger::Instance().WaitForHandleEvent();
    }

    // Switch lldb back to initial mode
    rSessionInfo.GetDebugger().SetAsync(bAsyncSetting);

    return bOk;
}

//++ ------------------------------------------------------------------------------------
// Details: Gets called when lldb-mi gets a signal. Stops the process if it was SIGINT.
//
// Type:    Method.
// Args:    signal that was delivered
// Return:  None.
// Throws:  None.
//--
void
CMIDriver::DeliverSignal(int signal)
{
    if (signal == SIGINT && (m_eCurrentDriverState == eDriverState_RunningDebugging))
        InterpretCommand("-exec-interrupt");
}
