/* See LICENSE.txt for the full license governing this code. */
/**
 * \file windows_process.c
 *
 * Source file for the process API on windows.
 */

#include <SDL.h>
#include <SDL_test.h>
#include <string.h>
#include <stdlib.h>

#include "SDL_visualtest_process.h"

#if defined(__WIN32__)

void
LogLastError(const char* str)
{
    LPVOID buffer;
    DWORD dw = GetLastError();
    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|
                    FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw,
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&buffer,
                    0, NULL);
    SDLTest_LogError("%s: %s", str, (char*)buffer);
    LocalFree(buffer);
}

int
SDL_LaunchProcess(char* file, char* args, SDL_ProcessInfo* pinfo)
{
    BOOL success;
    char* working_directory;
    char* command_line;
    int path_length, args_length;
    STARTUPINFO sui = {0};
    sui.cb = sizeof(sui);

    if(!file)
    {
        SDLTest_LogError("Path to executable to launched cannot be NULL.");
        return 0;
    }
    if(!pinfo)
    {
        SDLTest_LogError("pinfo cannot be NULL.");
        return 0;
    }

    /* get the working directory of the process being launched, so that
        the process can load any resources it has in it's working directory */
    path_length = SDL_strlen(file);
    if(path_length == 0)
    {
        SDLTest_LogError("Length of the file parameter is zero.");
        return 0;
    }

    working_directory = (char*)SDL_malloc(path_length + 1);
    if(!working_directory)
    {
        SDLTest_LogError("Could not allocate working_directory - SDL_malloc() failed.");
        return 0;
    }

    SDL_memcpy(working_directory, file, path_length + 1);
    PathRemoveFileSpec(working_directory);
    if(SDL_strlen(working_directory) == 0)
    {
        SDL_free(working_directory);
        working_directory = NULL;
    }

    /* join the file path and the args string together */
    if(!args)
        args = "";
    args_length = SDL_strlen(args);
    command_line = (char*)SDL_malloc(path_length + args_length + 2);
    if(!command_line)
    {
        SDLTest_LogError("Could not allocate command_line - SDL_malloc() failed.");
        return 0;
    }
    SDL_memcpy(command_line, file, path_length);
    command_line[path_length] = ' ';
    SDL_memcpy(command_line + path_length + 1, args, args_length + 1);

    /* create the process */
    success = CreateProcess(NULL, command_line, NULL, NULL, FALSE,
                            NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,
                            NULL, working_directory, &sui, &pinfo->pi);
    if(working_directory)
    {
        SDL_free(working_directory);
        working_directory = NULL;
    }
    SDL_free(command_line);
    if(!success)
    {
        LogLastError("CreateProcess() failed");
        return 0;
    }

    return 1;
}

int
SDL_GetProcessExitStatus(SDL_ProcessInfo* pinfo, SDL_ProcessExitStatus* ps)
{
    DWORD exit_status;
    BOOL success;

    if(!pinfo)
    {
        SDLTest_LogError("pinfo cannot be NULL");
        return 0;
    }
    if(!ps)
    {
        SDLTest_LogError("ps cannot be NULL");
        return 0;
    }

    /* get the exit code */
    success = GetExitCodeProcess(pinfo->pi.hProcess, &exit_status);
    if(!success)
    {
        LogLastError("GetExitCodeProcess() failed");
        return 0;
    }

    if(exit_status == STILL_ACTIVE)
        ps->exit_status = -1;
    else
        ps->exit_status = exit_status;
    ps->exit_success = 1;
    return 1;
}


int
SDL_IsProcessRunning(SDL_ProcessInfo* pinfo)
{
    DWORD exit_status;
    BOOL success;

    if(!pinfo)
    {
        SDLTest_LogError("pinfo cannot be NULL");
        return -1;
    }

    success = GetExitCodeProcess(pinfo->pi.hProcess, &exit_status);
    if(!success)
    {
        LogLastError("GetExitCodeProcess() failed");
        return -1;
    }
    
    if(exit_status == STILL_ACTIVE)
        return 1;
    return 0;
}

static BOOL CALLBACK
CloseWindowCallback(HWND hwnd, LPARAM lparam)
{
    DWORD pid;
    SDL_ProcessInfo* pinfo;

    pinfo = (SDL_ProcessInfo*)lparam;

    GetWindowThreadProcessId(hwnd, &pid);
    if(pid == pinfo->pi.dwProcessId)
    {
        DWORD_PTR result;
        if(!SendMessageTimeout(hwnd, WM_CLOSE, 0, 0, SMTO_BLOCK,
                               1000, &result))
        {
            if(GetLastError() != ERROR_TIMEOUT)
            {
                LogLastError("SendMessageTimeout() failed");
                return FALSE;
            }
        }
    }
    return TRUE;
}

int
SDL_QuitProcess(SDL_ProcessInfo* pinfo, SDL_ProcessExitStatus* ps)
{
    DWORD wait_result;
    if(!pinfo)
    {
        SDLTest_LogError("pinfo argument cannot be NULL");
        return 0;
    }
    if(!ps)
    {
        SDLTest_LogError("ps argument cannot be NULL");
        return 0;
    }

    /* enumerate through all the windows, trying to close each one */
    if(!EnumWindows(CloseWindowCallback, (LPARAM)pinfo))
    {
        SDLTest_LogError("EnumWindows() failed");
        return 0;
    }

    /* wait until the process terminates */
    wait_result = WaitForSingleObject(pinfo->pi.hProcess, 1000);
    if(wait_result == WAIT_FAILED)
    {
        LogLastError("WaitForSingleObject() failed");
        return 0;
    }
    if(wait_result != WAIT_OBJECT_0)
    {
        SDLTest_LogError("Process did not quit.");
        return 0;
    }

    /* get the exit code */
    if(!SDL_GetProcessExitStatus(pinfo, ps))
    {
        SDLTest_LogError("SDL_GetProcessExitStatus() failed");
        return 0;
    }

    return 1;
}

int
SDL_KillProcess(SDL_ProcessInfo* pinfo, SDL_ProcessExitStatus* ps)
{
    BOOL success;
    DWORD exit_status, wait_result;

    if(!pinfo)
    {
        SDLTest_LogError("pinfo argument cannot be NULL");
        return 0;
    }
    if(!ps)
    {
        SDLTest_LogError("ps argument cannot be NULL");
        return 0;
    }

    /* initiate termination of the process */
    success = TerminateProcess(pinfo->pi.hProcess, 0);
    if(!success)
    {
        LogLastError("TerminateProcess() failed");
        return 0;
    }

    /* wait until the process terminates */
    wait_result = WaitForSingleObject(pinfo->pi.hProcess, INFINITE);
    if(wait_result == WAIT_FAILED)
    {
        LogLastError("WaitForSingleObject() failed");
        return 0;
    }

    /* get the exit code */
    success = GetExitCodeProcess(pinfo->pi.hProcess, &exit_status);
    if(!success)
    {
        LogLastError("GetExitCodeProcess() failed");
        return 0;
    }

    ps->exit_status = exit_status;
    ps->exit_success = 1;

    return 1;
}

#endif
