/*
 * PROJECT:     ReactOS Clipboard Viewer
 * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
 * PURPOSE:     Scrolling related helper functions.
 * COPYRIGHT:   Copyright 2015-2018 Ricardo Hanke
 *              Copyright 2015-2018 Hermes Belusca-Maito
 */

#include "precomp.h"

void OnKeyScroll(HWND hWnd, WPARAM wParam, LPARAM lParam, LPSCROLLSTATE state)
{
    // NOTE: Windows uses an offset of 16 pixels
    switch (wParam)
    {
        case VK_UP:
            OnScroll(hWnd, SB_VERT, MAKELONG(SB_LINEUP, 0), 5, state);
            break;

        case VK_DOWN:
            OnScroll(hWnd, SB_VERT, MAKELONG(SB_LINEDOWN, 0), 5, state);
            break;

        case VK_LEFT:
            OnScroll(hWnd, SB_HORZ, MAKELONG(SB_LINELEFT, 0), 5, state);
            break;

        case VK_RIGHT:
            OnScroll(hWnd, SB_HORZ, MAKELONG(SB_LINERIGHT, 0), 5, state);
            break;

        case VK_PRIOR:
            OnScroll(hWnd, SB_VERT, MAKELONG(SB_PAGEUP, 0), state->nPageY, state);
            break;

        case VK_NEXT:
            OnScroll(hWnd, SB_VERT, MAKELONG(SB_PAGEDOWN, 0), state->nPageY, state);
            break;

        case VK_HOME:
        {
            OnScroll(hWnd, SB_HORZ, MAKELONG(SB_LEFT, 0), 0, state);
            if (GetKeyState(VK_CONTROL) & 0x8000)
                OnScroll(hWnd, SB_VERT, MAKELONG(SB_TOP, 0), 0, state);
            break;
        }

        case VK_END:
        {
            OnScroll(hWnd, SB_HORZ, MAKELONG(SB_RIGHT, 0), 0, state);
            if (GetKeyState(VK_CONTROL) & 0x8000)
                OnScroll(hWnd, SB_VERT, MAKELONG(SB_BOTTOM, 0), 0, state);
            break;
        }

        default:
            break;
    }
}

void OnMouseScroll(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LPSCROLLSTATE state)
{
    INT nBar;
    INT nPage;
    INT iDelta;
    UINT uLinesToScroll = state->uLinesToScroll;
    INT zDelta = GET_WHEEL_DELTA_WPARAM(wParam);
    WORD sbCode;

    assert(uMsg == WM_MOUSEWHEEL || uMsg == WM_MOUSEHWHEEL);

    if (uMsg == WM_MOUSEWHEEL)
    {
        nBar = SB_VERT;
        nPage = state->nPageY;

        /* Accumulate wheel rotation ticks */
        zDelta += state->iWheelCarryoverY;
        state->iWheelCarryoverY = zDelta % WHEEL_DELTA;
    }
    else // if (uMsg == WM_MOUSEHWHEEL)
    {
        nBar = SB_HORZ;
        nPage = state->nPageX;

        /* Accumulate wheel rotation ticks */
        zDelta += state->iWheelCarryoverX;
        state->iWheelCarryoverX = zDelta % WHEEL_DELTA;
    }

    /*
     * If the user specified scrolling by pages, do so.
     * Due to a bug on Windows where, if the window height is
     * less than the scroll lines delta default value (== 3),
     * several lines would be skipped when scrolling if we
     * used the WHEEL_PAGESCROLL value. Instead of this, use
     * the number of lines per page as the limiting value.
     * See https://www.strchr.com/corrections_to_raymond_chen_s_wheel_scrolling_code
     * for more details.
     */
    if (uLinesToScroll > nPage) // (uLinesToScroll == WHEEL_PAGESCROLL)
        uLinesToScroll = nPage;
    /* If the user specified no wheel scrolling, don't do anything */
    else if (uLinesToScroll == 0)
        return;

    /* Compute the scroll direction and the absolute delta value */
    if (zDelta > 0)
    {
        sbCode = SB_LINEUP;
    }
    else
    {
        sbCode = SB_LINEDOWN;
        zDelta = -zDelta;
    }

    /* Compute how many lines we should scroll (in absolute value) */
    iDelta = (INT)uLinesToScroll * zDelta / WHEEL_DELTA;

    OnScroll(hWnd, nBar, MAKELONG(sbCode, 0), iDelta, state);
}

void OnScroll(HWND hWnd, INT nBar, WPARAM wParam, INT iDelta, LPSCROLLSTATE state)
{
    SCROLLINFO si;
    PINT pCurrent;
    INT Maximum;
    INT NewPos;
    INT OldX, OldY;

    assert(nBar == SB_HORZ || nBar == SB_VERT);

    if (Globals.uDisplayFormat == CF_OWNERDISPLAY)
    {
        if (nBar == SB_HORZ)
        {
            SendClipboardOwnerMessage(TRUE, WM_HSCROLLCLIPBOARD,
                                      (WPARAM)hWnd, (LPARAM)wParam);
        }
        else // if (nBar == SB_VERT)
        {
            SendClipboardOwnerMessage(TRUE, WM_VSCROLLCLIPBOARD,
                                      (WPARAM)hWnd, (LPARAM)wParam);
        }
        return;
    }

    if (nBar == SB_HORZ)
    {
        pCurrent = &state->CurrentX;
        Maximum = state->MaxX;
    }
    else // if (nBar == SB_VERT)
    {
        pCurrent = &state->CurrentY;
        Maximum = state->MaxY;
    }

    ZeroMemory(&si, sizeof(si));
    si.cbSize = sizeof(si);
    si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS;
    GetScrollInfo(hWnd, nBar, &si);

    switch (LOWORD(wParam))
    {
        case SB_THUMBPOSITION:
        case SB_THUMBTRACK:
        {
            NewPos = si.nTrackPos;
            break;
        }

        case SB_LINEUP:     // SB_LINELEFT:
        {
            NewPos = si.nPos - iDelta;
            break;
        }

        case SB_LINEDOWN:   // SB_LINERIGHT:
        {
            NewPos = si.nPos + iDelta;
            break;
        }

        case SB_PAGEUP:     // SB_PAGELEFT:
        {
            NewPos = si.nPos - si.nPage;
            break;
        }

        case SB_PAGEDOWN:   // SB_PAGERIGHT:
        {
            NewPos = si.nPos + si.nPage;
            break;
        }

        case SB_TOP:        // SB_LEFT:
        {
            NewPos = si.nMin;
            break;
        }

        case SB_BOTTOM:     // SB_RIGHT:
        {
            NewPos = si.nMax;
            break;
        }

        default:
        {
            NewPos = si.nPos;
            break;
        }
    }

    NewPos = min(max(NewPos, 0), Maximum);

    if (si.nPos == NewPos)
        return;

    OldX = state->CurrentX;
    OldY = state->CurrentY;
    *pCurrent = NewPos;

    ScrollWindowEx(hWnd,
                   OldX - state->CurrentX,
                   OldY - state->CurrentY,
                   NULL,
                   NULL,
                   NULL,
                   NULL,
                   SW_ERASE | SW_INVALIDATE);
    UpdateWindow(hWnd);

    si.fMask = SIF_POS;
    si.nPos = NewPos;
    SetScrollInfo(hWnd, nBar, &si, TRUE);
}

void UpdateLinesToScroll(LPSCROLLSTATE state)
{
    UINT uLinesToScroll;

    if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &uLinesToScroll, 0))
    {
        /* Default value on Windows */
        state->uLinesToScroll = 3;
    }
    else
    {
        state->uLinesToScroll = uLinesToScroll;
    }
}

void UpdateWindowScrollState(HWND hWnd, INT nMaxWidth, INT nMaxHeight, LPSCROLLSTATE lpState)
{
    RECT rc;
    SCROLLINFO si;

    if (!GetClientRect(hWnd, &rc))
        SetRectEmpty(&rc);

    ZeroMemory(&si, sizeof(si));
    si.cbSize = sizeof(si);
    si.fMask  = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_DISABLENOSCROLL;

    lpState->nMaxWidth = nMaxWidth;
    lpState->MaxX = max(nMaxWidth - rc.right, 0);
    lpState->CurrentX = min(lpState->CurrentX, lpState->MaxX);
    lpState->nPageX = rc.right;
    si.nMin  = 0;
    si.nMax  = nMaxWidth;
    si.nPage = lpState->nPageX;
    si.nPos  = lpState->CurrentX;
    SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);

    lpState->nMaxHeight = nMaxHeight;
    lpState->MaxY = max(nMaxHeight - rc.bottom, 0);
    lpState->CurrentY = min(lpState->CurrentY, lpState->MaxY);
    lpState->nPageY = rc.bottom;
    si.nMin  = 0;
    si.nMax  = nMaxHeight;
    si.nPage = lpState->nPageY;
    si.nPos  = lpState->CurrentY;
    SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
}