//---------------------------------------------------------------------------------------------------------------
// This function checks if argument is constant. If it is it returns true, overwise false
//---------------------------------------------------------------------------------------------------------------
static bool GetConstantIDFromArgument(gtASCIIString sArgument, gtASCIIString cKey, unsigned int& nSlotID)
{
    unsigned int nKeyPosition = (unsigned int)sArgument.find(cKey);

    // Check if cKey is not end of other argument
    if (nKeyPosition == std::string::npos || (nKeyPosition != 0
                                              && sArgument[nKeyPosition - 1] != '>' // code can be in HTML
                                              && sArgument[nKeyPosition - 1] != ' '))

    {
        return false;
    } // End of if

    // Looking for not digital symbols in argument
    unsigned int nNextNotDigit = (unsigned int)sArgument.find_first_not_of("0123456789", nKeyPosition + cKey.length());

    gtASCIIString sSlotID; // this variable contains string with the slot number

    if (nNextNotDigit == std::string::npos)   // All character after cKey are digits
    {
        sSlotID = sArgument.substr(nKeyPosition + cKey.length());
    } // End of if

    // Check if the variable is finished with digit
    else if ((sArgument[nNextNotDigit] != '<' && sArgument[nNextNotDigit] != ' ' && sArgument[nNextNotDigit] != ',' &&
              sArgument[nNextNotDigit] != '.' && sArgument[nNextNotDigit] != '['  && sArgument[nNextNotDigit] != '\n')
             || nNextNotDigit <= nKeyPosition + 1)
    {
        return false;
    } // End of else if
    else
    {
        sSlotID = sArgument.substr(nKeyPosition + cKey.length(), nNextNotDigit - nKeyPosition - cKey.length());
    } // End of else

    int nScanRes = sscanf_s(sSlotID.asCharArray(), "%d", &nSlotID);

    if (nScanRes < 1)
    {
        Log(logERROR, "%s: Failed to read integer from Str = %s\n", __FUNCTION__, sSlotID.asCharArray());
        return false;
    }

    return true;
}// End of GetConstantIDFromArgument
bool GraphicsServerCommunication::GetNumCapturedFrames(gtASCIIString& executable, int& numFrames)
{
    bool retVal = true;
    OS_DEBUG_LOG_TRACER_WITH_RETVAL(retVal);

    numFrames = 3;

    if (executable.length() > 6)
    {
        numFrames = 4;
    }

    return retVal;
}
// ---------------------------------------------------------------------------
// Name:        GraphicsServerCommunication::ConnectProcess
// Description: Connect to Graphics server's specific process
// Arguments:   strPid - Process ID to connect to and update current PID, (optional) can be "", function will then connect to known process
// Return Val:  bool  - Success / failure
// ---------------------------------------------------------------------------
bool GraphicsServerCommunication::ConnectProcess(const gtASCIIString strPid, const gtASCIIString& apiType)
{
    gtASCIIString strWebResponse;

    bool retVal = false;

    if ((0 < strPid.length()) && (true == strPid.isIntegerNumber()))
    {
        m_strPid = strPid;
    }

    if (apiType.isEmpty() == false)
    {
        if (apiType == GP_GRAPHICS_SERVER_DX12_API_TYPE || apiType == GP_GRAPHICS_SERVER_VULKAN_API_TYPE)
        {
            m_strApiHttpCommand = "/";
            m_strApiHttpCommand.append(apiType);
        }
        else
        {
            gtString msg = L"Wrong API given : ";
            msg.append(gtString().fromASCIIString(apiType.asCharArray()));
            GT_ASSERT_EX(false, msg.asCharArray());
            m_strApiHttpCommand = "";
        }
    }
    if (m_strPid.isEmpty() == false && m_strApiHttpCommand.isEmpty() == false)
    {
        // Connect
        gtASCIIString showStack = m_strApiHttpCommand;
        retVal = SendCommandPid(showStack.append("/ShowStack"), strWebResponse, "");

        if (retVal)
        {
            gtASCIIString timeControl = m_strApiHttpCommand;
            retVal = SendCommandPid(timeControl.append("/PushLayer=TimeControl") , strWebResponse, "");
        }

        if (retVal)
        {
            gtASCIIString tcSettings = m_strApiHttpCommand;
            retVal = SendCommandPid(tcSettings.append("/TC/Settings.xml"), strWebResponse, "");
        }
    }

    return retVal;
}
// ---------------------------------------------------------------------------
// Name:        GraphicsServerCommunication::ConnectServer
// Description: Connect to server with specified Address or existing address
// Arguments:   strServer - optional address to override and update existing information
// Return Val:  bool  - Success / failure
// ---------------------------------------------------------------------------
bool GraphicsServerCommunication::ConnectServer(const gtASCIIString strServer)
{
    bool retVal = false;
    m_isStopSignaled = false;

    if (0 < strServer.length())
    {
        m_serverURL = strServer;
    }

    // Set the server address and port
    m_GPSServer = osPortAddress(m_serverURL, m_Port);

    m_httpClient = osHTTPClient(m_GPSServer);
    m_httpClient.GetTCPSocket().setReadOperationTimeOut(GRAPHIC_SERVER_READ_TIMEOUT);

    if (true == m_httpClient.connect())
    {
        retVal = true;
    }

    return retVal;
}
// ---------------------------------------------------------------------------
// Name:        GraphicsServerCommunication::SendCommandPid
// Description: locate available Graphics server
// Arguments:   strWebResponse(return) - web string returned.
//              strCommand - URL command to send to server
//              strPid - optional Process ID string, leave empty, "", to use existing Process ID
// Return Val:  bool  - Success / failure
// ---------------------------------------------------------------------------
bool GraphicsServerCommunication::SendCommandPid(const gtASCIIString& strCommand, gtASCIIString& strWebResponse, const gtASCIIString& strPid, bool isResendAllowed)
{
    bool retVal = false;
    gtASCIIString strConnectPid;
    gtASCIIString strQueryUrl;

    if ((0 < strPid.length()) && (true == strPid.isIntegerNumber()))
    {
        strConnectPid = strPid;
    }
    else if ((0 < m_strPid.length()) && (true == m_strPid.isIntegerNumber()))
    {
        strConnectPid = m_strPid;
    }

    strQueryUrl = "/";
    strQueryUrl.append(strConnectPid);
    strQueryUrl.append(strCommand);

    retVal = RequestData(strQueryUrl, strWebResponse, isResendAllowed);

    return retVal;
}
//--------------------------------------------------------------
//  LaunchAppInNewProcess
//--------------------------------------------------------------
PROCESS_INFORMATION ProcessTracker::LaunchAppInNewProcess(gtASCIIString strApp, gtASCIIString strDir, gtASCIIString strArgs, osModuleArchitecture binaryType)
{
#ifdef _LINUX
    PS_UNREFERENCED_PARAMETER(binaryType);
#endif
    LogConsole(logMESSAGE, "About to launch: %s\n", strApp.asCharArray());
    LogConsole(logMESSAGE, "Params: %s\n", strArgs.asCharArray());
    LogConsole(logMESSAGE, "Working Directory: %s\n", strDir.asCharArray());

    // Get app directory and make it default
    if (strDir.isEmpty())
    {
        size_t pos = strApp.find_last_of("\\");

        if (pos != std::string::npos)
        {
            strDir = strApp.substr(0, (int)pos);

            if (strApp[0] == '\"')
            {
                strDir += "\"";
            }
        }
    }

    PROCESS_INFORMATION pi;
    ZeroMemory(&pi, sizeof(pi));

#ifdef _WIN32
    DWORD dwFlags = CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED;

    SetLastError(0);

    STARTUPINFO si;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
#endif

    // Cmd line has to include the exe name since many apps expect the executable name to be in argv[0]!
    // Note argv[0] on the command line needs to be surrounded with quotes if it contains spaces.
    // The arguments in strArgs have already been "quoted" as they are parsed.

    gtASCIIString strCmdLine = AddQuotesIfStringHasSpaces(strApp.asCharArray());
    strCmdLine += " ";
    strCmdLine += strArgs;

    LogConsole(logMESSAGE, "strApp: %s\n", strApp.asCharArray());
    LogConsole(logMESSAGE, "strCmdLine: %s\n", strCmdLine.asCharArray());

    // Attempt to initialize the environment that the new process will run in. The child process should inherit "this" environment.
    if (!PrelaunchEnvironmentInitialization())
    {
        // Log a warning if this failed- initializing the environment for the new process can fail if Mantle support isn't installed.
        // In these cases, if the user is attempting to debug a Mantle application, they will have bigger problems to deal with.
        // In cases where a DX/GL app is being debugged, this warning can be ignored without any side effects.
        Log(logWARNING, "Environment initialization failed. If using DX/GL, it is safe to ignore this warning.\n");
    }

    BOOL succeeded = FALSE;

#ifdef _WIN32

    char microDLLPath[PS_MAX_PATH];
    const char* strServerPath;
    strServerPath = SG_GET_PATH(ServerPath);

    if (SG_GET_BOOL(OptionDllReplacement) == true)
    {
        DllReplacement::SetDllDirectory(binaryType == OS_X86_64_ARCHITECTURE);
    }

    // if using manual dll replacement or the AppInit_DLLs registry setting, don't use any kind of dll injection
    if (SG_GET_BOOL(OptionManualDllReplacement) == true || SG_GET_BOOL(OptionAppInitDll))
    {
        succeeded = CreateProcess(strApp.asCharArray(), (LPSTR)strCmdLine.asCharArray(), NULL, NULL, TRUE, dwFlags, NULL, strDir.asCharArray(), &si, &pi);
    }
    else
    {
#ifdef X64

        // can only launch 64 bit applications
        if (binaryType != OS_X86_64_ARCHITECTURE)
        {
            sprintf_s(microDLLPath, PS_MAX_PATH, "%s" MICRODLLNAME "%s%s.dll", SG_GET_PATH(ServerPath), GDT_DEBUG_SUFFIX, GDT_BUILD_SUFFIX);
            succeeded = AMDT::CreateProcessAndInjectDll(strApp.asCharArray(), (LPSTR)strCmdLine.asCharArray(),
                                                        NULL, NULL, TRUE, dwFlags, NULL,
                                                        strDir.asCharArray(),
                                                        &si, &pi,
                                                        microDLLPath);
        }

#else

        if (binaryType != OS_I386_ARCHITECTURE)
        {
            sprintf_s(microDLLPath, PS_MAX_PATH, "%s" MICRODLLNAME "-x64%s%s.dll", SG_GET_PATH(ServerPath), GDT_DEBUG_SUFFIX, GDT_BUILD_SUFFIX);
            succeeded = AMDT::CreateProcessAndInjectDll(strApp.asCharArray(), (LPSTR)strCmdLine.asCharArray(),
                                                        NULL, NULL, TRUE, dwFlags, NULL,
                                                        strDir.asCharArray(),
                                                        &si, &pi,
                                                        microDLLPath);
        }

#endif // X64
        else
        {
            succeeded = AMDT::CreateProcessAndInjectDll(strApp.asCharArray(), (LPSTR)strCmdLine.asCharArray(),
                                                        NULL, NULL, TRUE, dwFlags, NULL,
                                                        strDir.asCharArray(),
                                                        &si, &pi,
                                                        SG_GET_PATH(MicroDLLPath));
        }
    }

#else

    // Create the app process
    succeeded = CreateProcess(strApp.asCharArray(), strCmdLine.asCharArray(), strDir.asCharArray(), &pi);
#endif // _WIN32

    if (!succeeded)
    {
        osSystemErrorCode systemLastError = osGetLastSystemError();

        gtString systemErrorString;
        osGetLastSystemErrorAsString(systemErrorString);

        Log(logERROR, "CreateProcessAndInjectDll failed; Error %d: %s\n", systemLastError, systemErrorString.asASCIICharArray());
        pi.dwProcessId = 0;
    }

#ifdef _WIN32
    else
    {
        // Check to see if the Steam.exe has been hooked and if so, set the value in shared memory
        // If Steam.exe was used to launch the target application, then the checks for cmd.exe and fcx.exe
        // need to be ignored.
        if (strApp.length() > 0)
        {
            if (strstr(strApp.toLowerCase().asCharArray(), "steam.exe") != NULL)
            {
                SG_SET_BOOL(SteamInjected, true);
            }

            ShowLauncherReminder(strApp.toLowerCase().asCharArray());
        }
        else
        {
            if (strstr(strCmdLine.toLowerCase().asCharArray(), "steam.exe") != NULL)
            {
                SG_SET_BOOL(SteamInjected, true);
            }

            ShowLauncherReminder(strCmdLine.toLowerCase().asCharArray());
        }
    }

#endif // _WIN32

    return pi;
}