/*
 * PROJECT:     ReactOS Console Utilities Library
 * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
 * PURPOSE:     Base set of functions for loading string resources
 *              and message strings, and handle type identification.
 * COPYRIGHT:   Copyright 2017-2021 ReactOS Team
 *              Copyright 2017-2021 Hermes Belusca-Maito
 */

/**
 * @file    utils.c
 * @ingroup ConUtils
 *
 * @brief   General-purpose utility functions (wrappers around
 *          or reimplementations of Win32 APIs).
 **/

/* FIXME: Temporary HACK before we cleanly support UNICODE functions */
#define UNICODE
#define _UNICODE

#include <windef.h>
#include <winbase.h>
#include <winnls.h>
#include <winuser.h> // MAKEINTRESOURCEW, RT_STRING
#include <wincon.h>  // Console APIs (only if kernel32 support included)
#include <strsafe.h>

/* PSEH for SEH Support */
#include <pseh/pseh2.h>

// #include "conutils.h"
#include "utils.h"

#if 0 // The following function may be useful in the future...

// Performs MultiByteToWideChar then WideCharToMultiByte .
// See https://github.com/pcman-bbs/pcman-windows/blob/master/Lite/StrUtils.h#l33
// and http://www.openfoundry.org/svn/pcman/branches/OpenPCMan_2009/Lite/StrUtils.cpp
// for the idea.
int
MultiByteToMultiByte(
    // IN WORD wTranslations,
    IN DWORD dwFlags,
    IN UINT SrcCodePage,
    IN LPCSTR lpSrcString,
    IN int cbSrcChar,
    IN UINT DestCodePage,
    OUT LPSTR wDestString OPTIONAL,
    IN int cbDestChar
);

#endif

/**
 * @name K32LoadStringExW
 *     Loads a string resource from the executable file associated with a
 *     specified module, copies the string into a buffer, and appends a
 *     terminating null character.
 *     This is basically the LoadString() API ripped from user32.dll to
 *     remove any dependency of ConUtils from user32.dll, and to add support
 *     for loading strings from other languages than the current one.
 *
 * @param[in]   hInstance
 *     Optional handle to an instance of the module whose executable file
 *     contains the string resource. Can be set to NULL to get the handle
 *     to the application itself.
 *
 * @param[in]   uID
 *     The identifier of the string to be loaded.
 *
 * @param[in]   LanguageId
 *     The language identifier of the resource. If this parameter is
 *     <tt>MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)</tt>, the current language
 *     associated with the calling thread is used. To specify a language other
 *     than the current language, use the @c MAKELANGID macro to create this
 *     parameter.
 *
 * @param[out]  lpBuffer
 *     The buffer that receives the string. Must be of sufficient length
 *     to hold a pointer (8 bytes).
 *
 * @param[in]   nBufferMax
 *     The size of the buffer, in characters. The string is truncated and
 *     NULL-terminated if it is longer than the number of characters specified.
 *     If this parameter is 0, then @p lpBuffer receives a read-only pointer
 *     to the resource itself.
 *
 * @return
 *     If the function succeeds, the return value is the number of characters
 *     copied into the buffer, not including the terminating null character,
 *     or zero if the string resource does not exist. To get extended error
 *     information, call GetLastError().
 *
 * @see LoadString(), K32LoadStringW()
 **/
INT
WINAPI
K32LoadStringExW(
    IN  HINSTANCE hInstance OPTIONAL,
    IN  UINT   uID,
    IN  LANGID LanguageId,
    OUT LPWSTR lpBuffer,
    IN  INT    nBufferMax)
{
    HRSRC hrsrc;
    HGLOBAL hmem;
    WCHAR *p;
    UINT i;

    if (!lpBuffer)
        return 0;

    /* Use LOWORD (incremented by 1) as ResourceID */
    /* There are always blocks of 16 strings */
    hrsrc = FindResourceExW(hInstance,
                            (LPCWSTR)RT_STRING,
                            MAKEINTRESOURCEW((LOWORD(uID) >> 4) + 1),
                            LanguageId);
    if (!hrsrc) return 0;

    hmem = LoadResource(hInstance, hrsrc);
    if (!hmem) return 0;

    p = LockResource(hmem);
    // FreeResource(hmem);

    /* Find the string we're looking for */
    uID &= 0x000F; /* Position in the block, same as % 16 */
    for (i = 0; i < uID; i++)
        p += *p + 1;

    /*
     * If nBufferMax == 0, then return a read-only pointer
     * to the resource itself in lpBuffer it is assumed that
     * lpBuffer is actually a (LPWSTR*).
     */
    if (nBufferMax == 0)
    {
        *((LPWSTR*)lpBuffer) = p + 1;
        return *p;
    }

    i = min(nBufferMax - 1, *p);
    if (i > 0)
    {
        memcpy(lpBuffer, p + 1, i * sizeof(WCHAR));
        lpBuffer[i] = L'\0';
    }
    else
    {
        if (nBufferMax > 1)
        {
            lpBuffer[0] = L'\0';
            return 0;
        }
    }

    return i;
}

/**
 * @name K32LoadStringW
 *     Loads a string resource from the executable file associated with a
 *     specified module, copies the string into a buffer, and appends a
 *     terminating null character.
 *     This is a restricted version of K32LoadStringExW().
 *
 * @see LoadString(), K32LoadStringExW()
 **/
INT
WINAPI
K32LoadStringW(
    IN  HINSTANCE hInstance OPTIONAL,
    IN  UINT   uID,
    OUT LPWSTR lpBuffer,
    IN  INT    nBufferMax)
{
    // NOTE: Instead of using LANG_NEUTRAL, one might use LANG_USER_DEFAULT...
    return K32LoadStringExW(hInstance, uID,
                            MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
                            lpBuffer, nBufferMax);
}

/**
 * @name FormatMessageSafeW
 *     Loads and formats a message string. The function requires a message
 *     definition as input. The message definition can come from a buffer
 *     passed to the function. It can come from a message table resource in
 *     an already-loaded module, or the caller can ask the function to search
 *     the system's message table resource(s) for the message definition.
 *     Please refer to the Win32 FormatMessage() function for more details.
 *
 * @param[in]   dwFlags
 *     The formatting options, and how to interpret the @p lpSource parameter.
 *     See FormatMessage() for more details.
 *
 * @param[in]   lpSource
 *     The location of the message definition. The type of this parameter
 *     depends upon the settings in the @p dwFlags parameter.
 *
 * @param[in]   dwMessageId
 *     The message identifier for the requested message. This parameter
 *     is ignored if @p dwFlags includes @b FORMAT_MESSAGE_FROM_STRING.
 *
 * @param[in]   dwLanguageId
 *     The language identifier for the requested message. This parameter
 *     is ignored if @p dwFlags includes @b FORMAT_MESSAGE_FROM_STRING.
 *
 * @param[out]  lpBuffer
 *     A pointer to a buffer that receives the null-terminated string that
 *     specifies the formatted message. If @p dwFlags includes
 *     @b FORMAT_MESSAGE_ALLOCATE_BUFFER, the function allocates a buffer
 *     using the LocalAlloc() function, and places the pointer to the buffer
 *     at the address specified in @p lpBuffer.
 *     This buffer cannot be larger than 64kB.
 *
 * @param[in]   nSize
 *     If the @b FORMAT_MESSAGE_ALLOCATE_BUFFER flag is not set, this parameter
 *     specifies the size of the output buffer, in @b TCHARs.
 *     If @b FORMAT_MESSAGE_ALLOCATE_BUFFER is set, this parameter specifies
 *     the minimum number of @b TCHARs to allocate for an output buffer.
 *     The output buffer cannot be larger than 64kB.
 *
 * @param[in]   Arguments
 *     Optional pointer to an array of values describing a variable number of
 *     arguments, depending on the message string. Each argument is used to
 *     replace an <em>insert sequence</em> in the message string.
 *     By default, the @p Arguments parameter is of type @c va_list*, initialized
 *     with va_start(). The state of the @c va_list argument is undefined upon
 *     return from the function. To use the @c va_list again, destroy the variable
 *     argument list pointer using va_end() and reinitialize it with va_start().
 *     If you do not have a pointer of type @c va_list*, then specify the
 *     @b FORMAT_MESSAGE_ARGUMENT_ARRAY flag and pass a pointer to an array
 *     of @c DWORD_PTR values; those values are input to the message formatted
 *     as the insert values. Each insert must have a corresponding element in
 *     the array.
 *
 * @return
 *     If the function succeeds, the return value is the number of characters
 *     copied into the buffer, not including the terminating null character,
 *     or zero if the string resource does not exist. To get extended error
 *     information, call GetLastError().
 *
 * @remark
 *     This function is a "safe" version of FormatMessage(), that does not
 *     crash if a malformed source string is retrieved and then being used
 *     for formatting. It basically wraps calls to FormatMessage() within SEH.
 *
 * @see <a href="https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessage">FormatMessage() (on MSDN)</a>
 **/
DWORD
WINAPI
FormatMessageSafeW(
    IN  DWORD   dwFlags,
    IN  LPCVOID lpSource OPTIONAL,
    IN  DWORD   dwMessageId,
    IN  DWORD   dwLanguageId,
    OUT LPWSTR  lpBuffer,
    IN  DWORD   nSize,
    IN  va_list *Arguments OPTIONAL)
{
    DWORD dwLength = 0;

    _SEH2_TRY
    {
        /*
         * Retrieve the message string. Wrap in SEH
         * to protect from invalid string parameters.
         */
        _SEH2_TRY
        {
            dwLength = FormatMessageW(dwFlags,
                                      lpSource,
                                      dwMessageId,
                                      dwLanguageId,
                                      lpBuffer,
                                      nSize,
                                      Arguments);
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            dwLength = 0;

            /*
             * An exception occurred while calling FormatMessage, this is usually
             * the sign that a parameter was invalid, either 'lpBuffer' was NULL
             * but we did not pass the flag FORMAT_MESSAGE_ALLOCATE_BUFFER, or the
             * array pointer 'Arguments' was NULL or did not contain enough elements,
             * and we did not pass the flag FORMAT_MESSAGE_IGNORE_INSERTS, and the
             * message string expected too many inserts.
             * In this last case only, we can call again FormatMessage but ignore
             * explicitly the inserts. The string that we will return to the user
             * will not be pre-formatted.
             */
            if (((dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) || lpBuffer) &&
                !(dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS))
            {
                /* Remove any possible harmful flags and always ignore inserts */
                dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY;
                dwFlags |=  FORMAT_MESSAGE_IGNORE_INSERTS;

                /* If this call also throws an exception, we are really dead */
                dwLength = FormatMessageW(dwFlags,
                                          lpSource,
                                          dwMessageId,
                                          dwLanguageId,
                                          lpBuffer,
                                          nSize,
                                          NULL /* Arguments */);
            }
        }
        _SEH2_END;
    }
    _SEH2_FINALLY
    {
    }
    _SEH2_END;

    return dwLength;
}

/**
 * @name ConSetThreadUILanguage
 *     Sets the current thread's user interface language.
 *     Mostly used by console applications for selecting a
 *     language identifier that best supports the NT Console.
 *     This function dynamically loads and calls kernel32!SetThreadUILanguage()
 *     so as to be able to work on older environments where this
 *     API is not supported.
 *     The FormatMessage() API also bases itself on the thread's
 *     current language for its default behaviour (unless an explicit
 *     language identifier has been provided).
 *
 * @param[in,opt]   LangId
 *     (Vista+) A non-zero language identifier that specifies the
 *     current thread's user interface language to set.
 *     (XP/2003) Set the language identifier to 0 for selecting a
 *     language identifier that best supports the NT Console.
 *
 * @return
 *     Returns LangId in case of success, or 0 in case of failure.
 *     If LangId was set to 0, the function always succeeds and returns
 *     the language identifier that best supports the NT Console.
 *
 * @remark
 *     This function is thread-safe.
 *
 * @see <a href="https://docs.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-setthreaduilanguage">SetThreadUILanguage() (on MSDN)</a>
 **/
LANGID
ConSetThreadUILanguage(
    IN LANGID LangId OPTIONAL)
{
    /* The function pointer is shared amongst all threads */
    static volatile LANGID (WINAPI *pfnSetThreadUILanguage)(LANGID) = NULL;

    if (!pfnSetThreadUILanguage)
    {
        /* Load the API from kernel32 */
        PVOID pFunc = (PVOID)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "SetThreadUILanguage");
        if (!pFunc)
        {
            /* Fail since the API is not available */
            return 0;
        }
        /* Set the function pointer in case it hasn't been already set by another thread */
        InterlockedCompareExchangePointer((PVOID*)&pfnSetThreadUILanguage, pFunc, NULL);
        // ASSERT(pfnSetThreadUILanguage);
    }
    return pfnSetThreadUILanguage(LangId);
}

/**
 * @name IsTTYHandle
 *     Checks whether a handle refers to a valid TTY object.
 *     A TTY object may be a console or a "communications" (e.g. serial) port.
 *
 * @param[in]   hHandle
 *     Handle to the TTY object to check for.
 *
 * @return
 *     @b TRUE when the handle refers to a valid TTY object,
 *     @b FALSE if it does not.
 *
 * @remark
 *     This test is more general than IsConsoleHandle() as it is not limited
 *     to Win32 console objects only.
 *
 * @see IsConsoleHandle()
 **/
BOOL
IsTTYHandle(IN HANDLE hHandle)
{
    /*
     * More general test than IsConsoleHandle(). Consoles, as well as serial
     * (communications) ports, etc... verify this test, but only consoles
     * verify the IsConsoleHandle() test: indeed the latter checks whether
     * the handle is really handled by the console subsystem.
     */
    return ((GetFileType(hHandle) & ~FILE_TYPE_REMOTE) == FILE_TYPE_CHAR);
}

/**
 * @name IsConsoleHandle
 *     Checks whether a handle refers to a valid Win32 console object.
 *
 * @param[in]   hHandle
 *     Handle to the Win32 console object to check for:
 *     console input buffer, console output buffer.
 *
 * @return
 *     @b TRUE when the handle refers to a valid Win32 console object,
 *     @b FALSE if it does not.
 *
 * @see IsTTYHandle()
 **/
BOOL
IsConsoleHandle(IN HANDLE hHandle)
{
    DWORD dwMode;

    /* Check whether the handle may be that of a console... */
    if ((GetFileType(hHandle) & ~FILE_TYPE_REMOTE) != FILE_TYPE_CHAR)
        return FALSE;

    /*
     * It may be. Perform another test. The idea comes from the
     * MSDN description of the WriteConsole API:
     *
     * "WriteConsole fails if it is used with a standard handle
     *  that is redirected to a file. If an application processes
     *  multilingual output that can be redirected, determine whether
     *  the output handle is a console handle (one method is to call
     *  the GetConsoleMode function and check whether it succeeds).
     *  If the handle is a console handle, call WriteConsole. If the
     *  handle is not a console handle, the output is redirected and
     *  you should call WriteFile to perform the I/O."
     */
    return GetConsoleMode(hHandle, &dwMode);
}

/* EOF */