// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include <precomp.h>

#include "adaptDispatch.hpp"
#include "conGetSet.hpp"

#define ENABLE_INTSAFE_SIGNED_FUNCTIONS
#include <intsafe.h>

using namespace Microsoft::Console::VirtualTerminal;
using namespace Microsoft::Console::VirtualTerminal::DispatchTypes;

// Routine Description:
// - Small helper to disable all color flags within a given font attributes field
// Arguments:
// - pAttr - Pointer to font attributes field to adjust
// - fIsForeground - True if we're modifying the FOREGROUND colors. False if we're doing BACKGROUND.
// Return Value:
// - <none>
void AdaptDispatch::s_DisableAllColors(_Inout_ WORD* const pAttr, const bool fIsForeground)
{
    if (fIsForeground)
    {
        *pAttr &= ~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY);
    }
    else
    {
        *pAttr &= ~(BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY);
    }
}

// Routine Description:
// - Small helper to help mask off the appropriate foreground/background bits in the colors bitfield.
// Arguments:
// - pAttr - Pointer to font attributes field to adjust
// - wApplyThis - Color values to apply to the low or high word of the font attributes field.
// - fIsForeground - TRUE = foreground color. FALSE = background color. Specifies which half of the bit field to reset and then apply wApplyThis upon.
// Return Value:
// - <none>
void AdaptDispatch::s_ApplyColors(_Inout_ WORD* const pAttr, const WORD wApplyThis, const bool fIsForeground)
{
    // Copy the new attribute to apply
    WORD wNewColors = wApplyThis;

    // Mask off only the foreground or background
    if (fIsForeground)
    {
        *pAttr &= ~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY);
        wNewColors &= (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY);
    }
    else
    {
        *pAttr &= ~(BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY);
        wNewColors &= (BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY);
    }

    // Apply appropriate flags.
    *pAttr |= wNewColors;
}

// Routine Description:
// - Helper to apply the actual flags to each text attributes field.
// - Placed as a helper so it can be recursive/re-entrant for some of the convenience flag methods that perform similar/multiple operations in one command.
// Arguments:
// - opt - Graphics option sent to us by the parser/requestor.
// - pAttr - Pointer to the font attribute field to adjust
// Return Value:
// - <none>
void AdaptDispatch::_SetGraphicsOptionHelper(const DispatchTypes::GraphicsOptions opt, _Inout_ WORD* const pAttr)
{
    switch (opt)
    {
    case DispatchTypes::GraphicsOptions::Off:
        FAIL_FAST_MSG("GraphicsOptions::Off should be handled by _SetDefaultColorHelper");
        break;
    // MSFT:16398982 - These two are now handled by _SetBoldColorHelper
    // case DispatchTypes::GraphicsOptions::BoldBright:
    // case DispatchTypes::GraphicsOptions::UnBold:
    case DispatchTypes::GraphicsOptions::Negative:
        *pAttr |= COMMON_LVB_REVERSE_VIDEO;
        _fChangedMetaAttrs = true;
        break;
    case DispatchTypes::GraphicsOptions::Underline:
        *pAttr |= COMMON_LVB_UNDERSCORE;
        _fChangedMetaAttrs = true;
        break;
    case DispatchTypes::GraphicsOptions::Positive:
        *pAttr &= ~COMMON_LVB_REVERSE_VIDEO;
        _fChangedMetaAttrs = true;
        break;
    case DispatchTypes::GraphicsOptions::NoUnderline:
        *pAttr &= ~COMMON_LVB_UNDERSCORE;
        _fChangedMetaAttrs = true;
        break;
    case DispatchTypes::GraphicsOptions::ForegroundBlack:
        s_DisableAllColors(pAttr, true); // turn off all color flags first.
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::ForegroundBlue:
        s_DisableAllColors(pAttr, true); // turn off all color flags first.
        *pAttr |= FOREGROUND_BLUE;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::ForegroundGreen:
        s_DisableAllColors(pAttr, true); // turn off all color flags first.
        *pAttr |= FOREGROUND_GREEN;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::ForegroundCyan:
        s_DisableAllColors(pAttr, true); // turn off all color flags first.
        *pAttr |= FOREGROUND_BLUE | FOREGROUND_GREEN;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::ForegroundRed:
        s_DisableAllColors(pAttr, true); // turn off all color flags first.
        *pAttr |= FOREGROUND_RED;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::ForegroundMagenta:
        s_DisableAllColors(pAttr, true); // turn off all color flags first.
        *pAttr |= FOREGROUND_BLUE | FOREGROUND_RED;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::ForegroundYellow:
        s_DisableAllColors(pAttr, true); // turn off all color flags first.
        *pAttr |= FOREGROUND_GREEN | FOREGROUND_RED;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::ForegroundWhite:
        s_DisableAllColors(pAttr, true); // turn off all color flags first.
        *pAttr |= FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::ForegroundDefault:
        FAIL_FAST_MSG("GraphicsOptions::ForegroundDefault should be handled by _SetDefaultColorHelper");
        break;
    case DispatchTypes::GraphicsOptions::BackgroundBlack:
        s_DisableAllColors(pAttr, false); // turn off all color flags first.
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BackgroundBlue:
        s_DisableAllColors(pAttr, false); // turn off all color flags first.
        *pAttr |= BACKGROUND_BLUE;
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BackgroundGreen:
        s_DisableAllColors(pAttr, false); // turn off all color flags first.
        *pAttr |= BACKGROUND_GREEN;
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BackgroundCyan:
        s_DisableAllColors(pAttr, false); // turn off all color flags first.
        *pAttr |= BACKGROUND_BLUE | BACKGROUND_GREEN;
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BackgroundRed:
        s_DisableAllColors(pAttr, false); // turn off all color flags first.
        *pAttr |= BACKGROUND_RED;
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BackgroundMagenta:
        s_DisableAllColors(pAttr, false); // turn off all color flags first.
        *pAttr |= BACKGROUND_BLUE | BACKGROUND_RED;
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BackgroundYellow:
        s_DisableAllColors(pAttr, false); // turn off all color flags first.
        *pAttr |= BACKGROUND_GREEN | BACKGROUND_RED;
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BackgroundWhite:
        s_DisableAllColors(pAttr, false); // turn off all color flags first.
        *pAttr |= BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED;
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BackgroundDefault:
        FAIL_FAST_MSG("GraphicsOptions::BackgroundDefault should be handled by _SetDefaultColorHelper");
        break;
    case DispatchTypes::GraphicsOptions::BrightForegroundBlack:
        _SetGraphicsOptionHelper(GraphicsOptions::ForegroundBlack, pAttr);
        *pAttr |= FOREGROUND_INTENSITY;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightForegroundBlue:
        _SetGraphicsOptionHelper(GraphicsOptions::ForegroundBlue, pAttr);
        *pAttr |= FOREGROUND_INTENSITY;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightForegroundGreen:
        _SetGraphicsOptionHelper(GraphicsOptions::ForegroundGreen, pAttr);
        *pAttr |= FOREGROUND_INTENSITY;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightForegroundCyan:
        _SetGraphicsOptionHelper(GraphicsOptions::ForegroundCyan, pAttr);
        *pAttr |= FOREGROUND_INTENSITY;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightForegroundRed:
        _SetGraphicsOptionHelper(GraphicsOptions::ForegroundRed, pAttr);
        *pAttr |= FOREGROUND_INTENSITY;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightForegroundMagenta:
        _SetGraphicsOptionHelper(GraphicsOptions::ForegroundMagenta, pAttr);
        *pAttr |= FOREGROUND_INTENSITY;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightForegroundYellow:
        _SetGraphicsOptionHelper(GraphicsOptions::ForegroundYellow, pAttr);
        *pAttr |= FOREGROUND_INTENSITY;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightForegroundWhite:
        _SetGraphicsOptionHelper(GraphicsOptions::ForegroundWhite, pAttr);
        *pAttr |= FOREGROUND_INTENSITY;
        _fChangedForeground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightBackgroundBlack:
        _SetGraphicsOptionHelper(GraphicsOptions::BackgroundBlack, pAttr);
        *pAttr |= BACKGROUND_INTENSITY;
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightBackgroundBlue:
        _SetGraphicsOptionHelper(GraphicsOptions::BackgroundBlue, pAttr);
        *pAttr |= BACKGROUND_INTENSITY;
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightBackgroundGreen:
        _SetGraphicsOptionHelper(GraphicsOptions::BackgroundGreen, pAttr);
        *pAttr |= BACKGROUND_INTENSITY;
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightBackgroundCyan:
        _SetGraphicsOptionHelper(GraphicsOptions::BackgroundCyan, pAttr);
        *pAttr |= BACKGROUND_INTENSITY;
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightBackgroundRed:
        _SetGraphicsOptionHelper(GraphicsOptions::BackgroundRed, pAttr);
        *pAttr |= BACKGROUND_INTENSITY;
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightBackgroundMagenta:
        _SetGraphicsOptionHelper(GraphicsOptions::BackgroundMagenta, pAttr);
        *pAttr |= BACKGROUND_INTENSITY;
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightBackgroundYellow:
        _SetGraphicsOptionHelper(GraphicsOptions::BackgroundYellow, pAttr);
        *pAttr |= BACKGROUND_INTENSITY;
        _fChangedBackground = true;
        break;
    case DispatchTypes::GraphicsOptions::BrightBackgroundWhite:
        _SetGraphicsOptionHelper(GraphicsOptions::BackgroundWhite, pAttr);
        *pAttr |= BACKGROUND_INTENSITY;
        _fChangedBackground = true;
        break;
    }
}

// Routine Description:
// Returns true if the GraphicsOption represents an extended color option.
//   These are followed by up to 4 more values which compose the entire option.
// Return Value:
// - true if the opt is the indicator for an extended color sequence, false otherwise.
bool AdaptDispatch::s_IsRgbColorOption(const DispatchTypes::GraphicsOptions opt)
{
    return opt == DispatchTypes::GraphicsOptions::ForegroundExtended ||
           opt == DispatchTypes::GraphicsOptions::BackgroundExtended;
}

// Routine Description:
// Returns true if the GraphicsOption represents an extended color option.
//   These are followed by up to 4 more values which compose the entire option.
// Return Value:
// - true if the opt is the indicator for an extended color sequence, false otherwise.
bool AdaptDispatch::s_IsBoldColorOption(const DispatchTypes::GraphicsOptions opt) noexcept
{
    return opt == DispatchTypes::GraphicsOptions::BoldBright ||
           opt == DispatchTypes::GraphicsOptions::UnBold;
}

// Function Description:
// - checks if this graphics option should set either the console's FG or BG to
//the default attributes.
// Return Value:
// - true if the opt sets either/or attribute to the defaults, false otherwise.
bool AdaptDispatch::s_IsDefaultColorOption(const DispatchTypes::GraphicsOptions opt) noexcept
{
    return opt == DispatchTypes::GraphicsOptions::Off ||
           opt == DispatchTypes::GraphicsOptions::ForegroundDefault ||
           opt == DispatchTypes::GraphicsOptions::BackgroundDefault;
}

// Routine Description:
// - Helper to parse extended graphics options, which start with 38 (FG) or 48 (BG)
//     These options are followed by either a 2 (RGB) or 5 (xterm index)
//      RGB sequences then take 3 MORE params to designate the R, G, B parts of the color
//      Xterm index will use the param that follows to use a color from the preset 256 color xterm color table.
// Arguments:
// - rgOptions - An array of options that will be used to generate the RGB color
// - cOptions - The count of options
// - prgbColor - A pointer to place the generated RGB color into.
// - pfIsForeground - a pointer to place whether or not the parsed color is for the foreground or not.
// - pcOptionsConsumed - a pointer to place the number of options we consumed parsing this option.
// - ColorTable - the windows color table, for xterm indices < 16
// - cColorTable - The number of elements in the windows color table.
// Return Value:
// Returns true if we successfully parsed an extended color option from the options array.
// - This corresponds to the following number of options consumed (pcOptionsConsumed):
//     1 - false, not enough options to parse.
//     2 - false, not enough options to parse.
//     3 - true, parsed an xterm index to a color
//     5 - true, parsed an RGB color.
bool AdaptDispatch::_SetRgbColorsHelper(_In_reads_(cOptions) const DispatchTypes::GraphicsOptions* const rgOptions,
                                        const size_t cOptions,
                                        _Out_ COLORREF* const prgbColor,
                                        _Out_ bool* const pfIsForeground,
                                        _Out_ size_t* const pcOptionsConsumed)
{
    bool fSuccess = false;
    *pcOptionsConsumed = 1;
    if (cOptions >= 2 && s_IsRgbColorOption(rgOptions[0]))
    {
        *pcOptionsConsumed = 2;
        DispatchTypes::GraphicsOptions extendedOpt = rgOptions[0];
        DispatchTypes::GraphicsOptions typeOpt = rgOptions[1];

        if (extendedOpt == DispatchTypes::GraphicsOptions::ForegroundExtended)
        {
            *pfIsForeground = true;
        }
        else if (extendedOpt == DispatchTypes::GraphicsOptions::BackgroundExtended)
        {
            *pfIsForeground = false;
        }

        if (typeOpt == DispatchTypes::GraphicsOptions::RGBColor && cOptions >= 5)
        {
            *pcOptionsConsumed = 5;
            // ensure that each value fits in a byte
            unsigned int red = rgOptions[2] > 255 ? 255 : rgOptions[2];
            unsigned int green = rgOptions[3] > 255 ? 255 : rgOptions[3];
            unsigned int blue = rgOptions[4] > 255 ? 255 : rgOptions[4];

            *prgbColor = RGB(red, green, blue);

            fSuccess = !!_conApi->SetConsoleRGBTextAttribute(*prgbColor, *pfIsForeground);
        }
        else if (typeOpt == DispatchTypes::GraphicsOptions::Xterm256Index && cOptions >= 3)
        {
            *pcOptionsConsumed = 3;
            if (rgOptions[2] <= 255) // ensure that the provided index is on the table
            {
                unsigned int tableIndex = rgOptions[2];

                fSuccess = !!_conApi->SetConsoleXtermTextAttribute(tableIndex, *pfIsForeground);
            }
        }
    }
    return fSuccess;
}

bool AdaptDispatch::_SetBoldColorHelper(const DispatchTypes::GraphicsOptions option)
{
    const bool bold = (option == DispatchTypes::GraphicsOptions::BoldBright);
    return !!_conApi->PrivateBoldText(bold);
}

bool AdaptDispatch::_SetDefaultColorHelper(const DispatchTypes::GraphicsOptions option)
{
    const bool fg = option == GraphicsOptions::Off || option == GraphicsOptions::ForegroundDefault;
    const bool bg = option == GraphicsOptions::Off || option == GraphicsOptions::BackgroundDefault;
    bool success = _conApi->PrivateSetDefaultAttributes(fg, bg);
    if (success && fg && bg)
    {
        // If we're resetting both the FG & BG, also reset the meta attributes (underline)
        //      as well as the boldness
        success = _conApi->PrivateSetLegacyAttributes(0, false, false, true) &&
                  _conApi->PrivateBoldText(false);
    }
    return success;
}

// Routine Description:
// - SGR - Modifies the graphical rendering options applied to the next characters written into the buffer.
//       - Options include colors, invert, underlines, and other "font style" type options.
// Arguments:
// - rgOptions - An array of options that will be applied from 0 to N, in order, one at a time by setting or removing flags in the font style properties.
// - cOptions - The count of options (a.k.a. the N in the above line of comments)
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::SetGraphicsRendition(_In_reads_(cOptions) const DispatchTypes::GraphicsOptions* const rgOptions, const size_t cOptions)
{
    // We use the private function here to get just the default color attributes as a performance optimization.
    // Calling the public GetConsoleScreenBufferInfoEx costs a lot of performance time/power in a tight loop
    // because it has to fill the Largest Window Size by asking the OS and wastes time memcpying colors and other data
    // we do not need to resolve this Set Graphics Rendition request.
    WORD attr;
    bool fSuccess = !!_conApi->PrivateGetConsoleScreenBufferAttributes(&attr);

    if (fSuccess)
    {
        // Run through the graphics options and apply them
        for (size_t i = 0; i < cOptions; i++)
        {
            DispatchTypes::GraphicsOptions opt = rgOptions[i];
            if (s_IsDefaultColorOption(opt))
            {
                fSuccess = _SetDefaultColorHelper(opt);
            }
            else if (s_IsBoldColorOption(opt))
            {
                fSuccess = _SetBoldColorHelper(rgOptions[i]);
            }
            else if (s_IsRgbColorOption(opt))
            {
                COLORREF rgbColor;
                bool fIsForeground = true;

                size_t cOptionsConsumed = 0;

                // _SetRgbColorsHelper will call the appropriate ConApi function
                fSuccess = _SetRgbColorsHelper(&(rgOptions[i]), cOptions - i, &rgbColor, &fIsForeground, &cOptionsConsumed);

                i += (cOptionsConsumed - 1); // cOptionsConsumed includes the opt we're currently on.
            }
            else
            {
                _SetGraphicsOptionHelper(opt, &attr);
                fSuccess = !!_conApi->PrivateSetLegacyAttributes(attr, _fChangedForeground, _fChangedBackground, _fChangedMetaAttrs);

                // Make sure we un-bold
                if (fSuccess && opt == DispatchTypes::GraphicsOptions::Off)
                {
                    fSuccess = _SetBoldColorHelper(opt);
                }

                _fChangedForeground = false;
                _fChangedBackground = false;
                _fChangedMetaAttrs = false;
            }
        }
    }

    return fSuccess;
}
