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

#include "precomp.h"
#include "ColorsPage.h"
#include "ColorControl.h"

// The property sheet will never load on OneCore, so we do
// not need to redirect users to the OneCore safe version
// of these functions.
#undef VkKeyScanW
#undef MapVirtualKeyW
#undef GetKeyState

static BYTE ColorArray[4];
static int iColor;

// Routine Description:
// - Window proc for the color buttons
[[nodiscard]] LRESULT CALLBACK ColorTableControlProc(HWND hColor, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    int ColorId;
    RECT rColor;
    RECT rTemp;
    HDC hdc;
    HWND hWnd;
    HWND hDlg;

    ColorId = GetWindowLong(hColor, GWL_ID);
    hDlg = GetParent(hColor);

    switch (wMsg)
    {
    case WM_SETFOCUS:
        if (ColorArray[iColor] != (BYTE)(ColorId - IDD_COLOR_1))
        {
            hWnd = GetDlgItem(hDlg, ColorArray[iColor] + IDD_COLOR_1);
            SetFocus(hWnd);
        }
        __fallthrough;
    case WM_KILLFOCUS:
        hdc = GetDC(hDlg);
        hWnd = GetDlgItem(hDlg, IDD_COLOR_1);
        GetWindowRect(hWnd, &rColor);
        hWnd = GetDlgItem(hDlg, IDD_COLOR_16);
        GetWindowRect(hWnd, &rTemp);
        rColor.right = rTemp.right;
        ScreenToClient(hDlg, (LPPOINT)&rColor.left);
        ScreenToClient(hDlg, (LPPOINT)&rColor.right);
        InflateRect(&rColor, 2, 2);
        DrawFocusRect(hdc, &rColor);
        ReleaseDC(hDlg, hdc);
        break;
    case WM_KEYDOWN:
        switch (wParam)
        {
        case VK_UP:
        case VK_LEFT:
            if (ColorId > IDD_COLOR_1)
            {
                SendMessage(hDlg, CM_SETCOLOR, ColorId - 1 - IDD_COLOR_1, (LPARAM)hColor);
            }
            break;
        case VK_DOWN:
        case VK_RIGHT:
            if (ColorId < IDD_COLOR_16)
            {
                SendMessage(hDlg, CM_SETCOLOR, ColorId + 1 - IDD_COLOR_1, (LPARAM)hColor);
            }
            break;
        case VK_TAB:
            hWnd = GetDlgItem(hDlg, IDD_COLOR_1);
            hWnd = GetNextDlgTabItem(hDlg, hWnd, GetKeyState(VK_SHIFT) < 0);
            SetFocus(hWnd);
            break;
        default:
            return DefWindowProc(hColor, wMsg, wParam, lParam);
        }
        break;
    case WM_RBUTTONDOWN:
    case WM_LBUTTONDOWN:
        SendMessage(hDlg, CM_SETCOLOR, ColorId - IDD_COLOR_1, (LPARAM)hColor);
        break;
    case WM_PAINT:

        BeginPaint(hColor, &ps);
        GetClientRect(hColor, &rColor);

        // are we the selected color for the current object?
        if (ColorArray[iColor] == (BYTE)(ColorId - IDD_COLOR_1))
        {
            // highlight the selected color
            FrameRect(ps.hdc, &rColor, (HBRUSH)GetStockObject(BLACK_BRUSH));
            InflateRect(&rColor, -1, -1);
            FrameRect(ps.hdc, &rColor, (HBRUSH)GetStockObject(BLACK_BRUSH));
        }

        SimpleColorDoPaint(hColor, ps, ColorId);
        EndPaint(hColor, &ps);
        break;
    default:
        return SimpleColorControlProc(hColor, wMsg, wParam, lParam);
        break;
    }
    return TRUE;
}

bool InitColorsDialog(HWND hDlg)
{
    ColorArray[IDD_COLOR_SCREEN_TEXT - IDD_COLOR_SCREEN_TEXT] =
        LOBYTE(gpStateInfo->ScreenAttributes) & 0x0F;
    ColorArray[IDD_COLOR_SCREEN_BKGND - IDD_COLOR_SCREEN_TEXT] =
        LOBYTE(gpStateInfo->ScreenAttributes >> 4);
    ColorArray[IDD_COLOR_POPUP_TEXT - IDD_COLOR_SCREEN_TEXT] =
        LOBYTE(gpStateInfo->PopupAttributes) & 0x0F;
    ColorArray[IDD_COLOR_POPUP_BKGND - IDD_COLOR_SCREEN_TEXT] =
        LOBYTE(gpStateInfo->PopupAttributes >> 4);
    CheckRadioButton(hDlg, IDD_COLOR_SCREEN_TEXT, IDD_COLOR_POPUP_BKGND, IDD_COLOR_SCREEN_BKGND);
    iColor = IDD_COLOR_SCREEN_BKGND - IDD_COLOR_SCREEN_TEXT;

    // initialize size of edit controls

    SendDlgItemMessage(hDlg, IDD_COLOR_RED, EM_LIMITTEXT, 3, 0);
    SendDlgItemMessage(hDlg, IDD_COLOR_GREEN, EM_LIMITTEXT, 3, 0);
    SendDlgItemMessage(hDlg, IDD_COLOR_BLUE, EM_LIMITTEXT, 3, 0);

    // initialize arrow controls

    SendDlgItemMessage(hDlg, IDD_COLOR_REDSCROLL, UDM_SETRANGE, 0, MAKELONG(255, 0));
    SendDlgItemMessage(hDlg, IDD_COLOR_REDSCROLL, UDM_SETPOS, 0, MAKELONG(GetRValue(AttrToRGB(ColorArray[iColor])), 0));
    SendDlgItemMessage(hDlg, IDD_COLOR_GREENSCROLL, UDM_SETRANGE, 0, MAKELONG(255, 0));
    SendDlgItemMessage(hDlg, IDD_COLOR_GREENSCROLL, UDM_SETPOS, 0, MAKELONG(GetGValue(AttrToRGB(ColorArray[iColor])), 0));
    SendDlgItemMessage(hDlg, IDD_COLOR_BLUESCROLL, UDM_SETRANGE, 0, MAKELONG(255, 0));
    SendDlgItemMessage(hDlg, IDD_COLOR_BLUESCROLL, UDM_SETPOS, 0, MAKELONG(GetBValue(AttrToRGB(ColorArray[iColor])), 0));

    CreateAndAssociateToolTipToControl(IDD_TRANSPARENCY, hDlg, IDS_TOOLTIP_OPACITY);

    SendMessage(GetDlgItem(hDlg, IDD_TRANSPARENCY), TBM_SETRANGE, FALSE, (LPARAM)MAKELONG(TRANSPARENCY_RANGE_MIN, BYTE_MAX));
    ToggleV2ColorControls(hDlg);

    return TRUE;
}

// Routine Description:
// - Dialog proc for the color selection dialog box.
//
INT_PTR WINAPI ColorDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
    UINT Value;
    UINT Red;
    UINT Green;
    UINT Blue;
    UINT Item;
    HWND hWnd;
    HWND hWndOld;
    BOOL bOK;
    static auto fHaveInitialized = false;

    switch (wMsg)
    {
    case WM_INITDIALOG:
    {
        fHaveInitialized = true;
        return InitColorsDialog(hDlg);
    }

    case WM_COMMAND:
    {
        if (!fHaveInitialized)
        {
            return FALSE;
        }

        Item = LOWORD(wParam);
        switch (Item)
        {
        case IDD_COLOR_SCREEN_TEXT:
        case IDD_COLOR_SCREEN_BKGND:
        case IDD_COLOR_POPUP_TEXT:
        case IDD_COLOR_POPUP_BKGND:
            hWndOld = GetDlgItem(hDlg, ColorArray[iColor] + IDD_COLOR_1);

            iColor = Item - IDD_COLOR_SCREEN_TEXT;

            // repaint new color
            hWnd = GetDlgItem(hDlg, ColorArray[iColor] + IDD_COLOR_1);
            InvalidateRect(hWnd, nullptr, TRUE);

            // repaint old color
            if (hWndOld != hWnd)
            {
                InvalidateRect(hWndOld, nullptr, TRUE);
            }

            // update color value fields to reflect the new color
            UpdateItem(hDlg, IDD_COLOR_RED, GetRValue(AttrToRGB(ColorArray[iColor])));
            UpdateItem(hDlg, IDD_COLOR_GREEN, GetGValue(AttrToRGB(ColorArray[iColor])));
            UpdateItem(hDlg, IDD_COLOR_BLUE, GetBValue(AttrToRGB(ColorArray[iColor])));

            return TRUE;

        case IDD_COLOR_RED:
        case IDD_COLOR_GREEN:
        case IDD_COLOR_BLUE:
        {
            switch (HIWORD(wParam))
            {
            case EN_UPDATE:
                if (!CheckNum(hDlg, Item))
                {
                    Undo((HWND)lParam);
                }
                break;

            case EN_CHANGE:
                /*
                             * Update the state info structure
                             */
                Value = GetDlgItemInt(hDlg, Item, &bOK, TRUE);
                if (bOK)
                {
                    if (Value > 255)
                    {
                        UpdateItem(hDlg, Item, 255);
                        Value = 255;
                    }
                    if (Item == IDD_COLOR_RED)
                    {
                        Red = Value;
                    }
                    else
                    {
                        Red = GetRValue(AttrToRGB(ColorArray[iColor]));
                    }
                    if (Item == IDD_COLOR_GREEN)
                    {
                        Green = Value;
                    }
                    else
                    {
                        Green = GetGValue(AttrToRGB(ColorArray[iColor]));
                    }
                    if (Item == IDD_COLOR_BLUE)
                    {
                        Blue = Value;
                    }
                    else
                    {
                        Blue = GetBValue(AttrToRGB(ColorArray[iColor]));
                    }
                    UpdateStateInfo(hDlg, ColorArray[iColor] + IDD_COLOR_1, RGB(Red, Green, Blue));
                    UpdateApplyButton(hDlg);
                }
                __fallthrough;

            case EN_KILLFOCUS:
                /*
                             * Update the preview windows with the new value
                             */
                hWnd = GetDlgItem(hDlg, IDD_COLOR_SCREEN_COLORS);
                InvalidateRect(hWnd, nullptr, FALSE);
                hWnd = GetDlgItem(hDlg, IDD_COLOR_POPUP_COLORS);
                InvalidateRect(hWnd, nullptr, FALSE);
                hWnd = GetDlgItem(hDlg, ColorArray[iColor] + IDD_COLOR_1);
                InvalidateRect(hWnd, nullptr, FALSE);
                break;
            }
            return TRUE;
        }
        }
        break;
    }

    case WM_NOTIFY:
    {
        const PSHNOTIFY* const pshn = (LPPSHNOTIFY)lParam;
        switch (pshn->hdr.code)
        {
        case PSN_APPLY:
            /*
                     * Write out the state values and exit.
                     */

            // Ignore opacity in v1 console
            if (g_fForceV2)
            {
                gpStateInfo->bWindowTransparency = (BYTE)SendDlgItemMessage(hDlg, IDD_TRANSPARENCY, TBM_GETPOS, 0, 0);
            }

            EndDlgPage(hDlg, !pshn->lParam);
            return TRUE;

        case PSN_RESET:
            // Ignore opacity in v1 console
            if (g_fForceV2 && g_bPreviewOpacity != gpStateInfo->bWindowTransparency)
            {
                SetLayeredWindowAttributes(gpStateInfo->hWnd, 0, gpStateInfo->bWindowTransparency, LWA_ALPHA);
            }

            return 0;

        case PSN_SETACTIVE:
            ToggleV2ColorControls(hDlg);
            return 0;

        case PSN_KILLACTIVE:
            /*
                     * Fake the dialog proc into thinking the edit control just
                     * lost focus so it'll update properly
                     */
            Item = GetDlgCtrlID(GetFocus());
            if (Item)
            {
                SendMessage(hDlg, WM_COMMAND, MAKELONG(Item, EN_KILLFOCUS), 0);
            }
            return TRUE;
        }
        break;
    }

    case WM_VSCROLL:
        /*
             * Fake the dialog proc into thinking the edit control just
             * lost focus so it'll update properly
             */
        Item = GetDlgCtrlID((HWND)lParam) - 1;
        SendMessage(hDlg, WM_COMMAND, MAKELONG(Item, EN_KILLFOCUS), 0);
        return TRUE;

    case WM_HSCROLL:
        if (IDD_TRANSPARENCY == GetDlgCtrlID((HWND)lParam))
        {
            switch (LOWORD(wParam))
            {
            //When moving slider with the mouse
            case TB_THUMBPOSITION:
            case TB_THUMBTRACK:
                g_bPreviewOpacity = (BYTE)HIWORD(wParam);
                break;

                //moving via keyboard
            default:
                g_bPreviewOpacity = (BYTE)SendMessage((HWND)lParam, TBM_GETPOS, 0, 0);
            }

            PreviewOpacity(hDlg, g_bPreviewOpacity);
            UpdateApplyButton(hDlg);

            return TRUE;
        }
        break;

    case CM_SETCOLOR:
        UpdateStateInfo(hDlg, iColor + IDD_COLOR_SCREEN_TEXT, (UINT)wParam);
        UpdateApplyButton(hDlg);

        hWndOld = GetDlgItem(hDlg, ColorArray[iColor] + IDD_COLOR_1);

        ColorArray[iColor] = (BYTE)wParam;

        /* Force the preview window to repaint */

        if (iColor < (IDD_COLOR_POPUP_TEXT - IDD_COLOR_SCREEN_TEXT))
        {
            hWnd = GetDlgItem(hDlg, IDD_COLOR_SCREEN_COLORS);
        }
        else
        {
            hWnd = GetDlgItem(hDlg, IDD_COLOR_POPUP_COLORS);
        }
        InvalidateRect(hWnd, nullptr, TRUE);

        // repaint new color
        hWnd = GetDlgItem(hDlg, ColorArray[iColor] + IDD_COLOR_1);
        InvalidateRect(hWnd, nullptr, TRUE);
        SetFocus(hWnd);

        UpdateItem(hDlg, IDD_COLOR_RED, GetRValue(AttrToRGB(ColorArray[iColor])));
        UpdateItem(hDlg, IDD_COLOR_GREEN, GetGValue(AttrToRGB(ColorArray[iColor])));
        UpdateItem(hDlg, IDD_COLOR_BLUE, GetBValue(AttrToRGB(ColorArray[iColor])));

        // repaint old color
        if (hWndOld != hWnd)
        {
            InvalidateRect(hWndOld, nullptr, TRUE);
        }
        return TRUE;

    default:
        break;
    }

    return FALSE;
}

// enables or disables color page dialog controls depending on whether V2 is enabled or not
void ToggleV2ColorControls(const __in HWND hDlg)
{
    EnableWindow(GetDlgItem(hDlg, IDD_TRANSPARENCY), g_fForceV2);
    SetOpacitySlider(hDlg);

    EnableWindow(GetDlgItem(hDlg, IDD_OPACITY_GROUPBOX), g_fForceV2);
    EnableWindow(GetDlgItem(hDlg, IDD_OPACITY_LOW_LABEL), g_fForceV2);
    EnableWindow(GetDlgItem(hDlg, IDD_OPACITY_HIGH_LABEL), g_fForceV2);
    EnableWindow(GetDlgItem(hDlg, IDD_OPACITY_VALUE), g_fForceV2);
}

void PreviewOpacity(HWND hDlg, BYTE bOpacity)
{
    // Ignore opacity in v1 console
    if (g_fForceV2)
    {
        WCHAR wszOpacityValue[4];
        auto hWndConsole = gpStateInfo->hWnd;

        StringCchPrintf(wszOpacityValue, ARRAYSIZE(wszOpacityValue), L"%d", (int)((float)bOpacity / BYTE_MAX * 100));
        SetDlgItemText(hDlg, IDD_OPACITY_VALUE, wszOpacityValue);

        if (hWndConsole)
        {
            // TODO: CONSIDER:What happens when this code is eventually ported directly into the shell. Which "hWnd" does this apply to then? Desktop?
            // Hopefully it is simply null. -RSE   http://osgvsowi/2136073
            SetLayeredWindowAttributes(hWndConsole, 0, bOpacity, LWA_ALPHA);
        }
    }
}

void SetOpacitySlider(__in HWND hDlg)
{
    if (g_fForceV2)
    {
        if (0 == g_bPreviewOpacity) //if no preview opacity has been set yet...
        {
            g_bPreviewOpacity = (gpStateInfo->bWindowTransparency >= TRANSPARENCY_RANGE_MIN) ? gpStateInfo->bWindowTransparency : BYTE_MAX;
        }
    }
    else
    {
        g_bPreviewOpacity = BYTE_MAX; //always fully opaque in V1
    }

    SendMessage(GetDlgItem(hDlg, IDD_TRANSPARENCY), TBM_SETPOS, TRUE, (LPARAM)(g_bPreviewOpacity));
    PreviewOpacity(hDlg, g_bPreviewOpacity);
}
