/*
 * PROJECT:     ReactOS Character Map
 * LICENSE:     GPL - See COPYING in the top level directory
 * FILE:        base/applications/charmap/map.c
 * PURPOSE:     class implementation for painting glyph region
 * COPYRIGHT:   Copyright 2007 Ged Murphy <gedmurphy@reactos.org>
 *
 */

#include "precomp.h"

#include <stdlib.h>

static const WCHAR szMapWndClass[] = L"FontMapWnd";
static const WCHAR szLrgCellWndClass[] = L"LrgCellWnd";

#define MAX_ROWS 9830

static
VOID
TagFontToCell(PCELL pCell,
              DWORD ch)
{
    pCell->ch = ch;
}


static
VOID
SetGrid(PMAP infoPtr)
{
    INT x, y;

    for (y = 0; y < YCELLS; y++)
    for (x = 0; x < XCELLS; x++)
    {
        infoPtr->Cells[y][x].CellExt.left = x * infoPtr->CellSize.cx + 1;
        infoPtr->Cells[y][x].CellExt.top = y * infoPtr->CellSize.cy + 1;
        infoPtr->Cells[y][x].CellExt.right = (x + 1) * infoPtr->CellSize.cx + 2;
        infoPtr->Cells[y][x].CellExt.bottom = (y + 1) * infoPtr->CellSize.cy + 2;

        CopyRect(&infoPtr->Cells[y][x].CellInt,
                 &infoPtr->Cells[y][x].CellExt);

        InflateRect(&infoPtr->Cells[y][x].CellInt,
                    -1,
                    -1);
    }
}

static
VOID
DrawActiveCell(PMAP infoPtr,
               HDC hdc)
{
    Rectangle(hdc,
              infoPtr->pActiveCell->CellInt.left,
              infoPtr->pActiveCell->CellInt.top,
              infoPtr->pActiveCell->CellInt.right,
              infoPtr->pActiveCell->CellInt.bottom);

}


static
VOID
DrawGrid(PMAP infoPtr,
         PAINTSTRUCT *ps)
{
    INT x, y;
    RECT rc;
    PCELL Cell;

    for (y = 0; y < YCELLS; y++)
    for (x = 0; x < XCELLS; x++)
    {
        Cell = &infoPtr->Cells[y][x];

        if (!IntersectRect(&rc,
                           &ps->rcPaint,
                           &Cell->CellExt))
        {
            continue;
        }

        Rectangle(ps->hdc,
                  Cell->CellExt.left,
                  Cell->CellExt.top,
                  Cell->CellExt.right,
                  Cell->CellExt.bottom);

        if (infoPtr->pActiveCell == Cell)
        {
            DrawActiveCell(infoPtr, ps->hdc);
        }
    }
}


static
VOID
FillGrid(PMAP infoPtr,
         PAINTSTRUCT *ps)
{
    HFONT hOldFont;
    DWORD ch;
    INT x, y;
    RECT rc;
    PCELL Cell;
    WCHAR surrogate[2];

    hOldFont = SelectObject(ps->hdc,
                            infoPtr->hFont);

    for (y = 0; y < YCELLS; y++)
    for (x = 0; x < XCELLS; x++)
    {
        Cell = &infoPtr->Cells[y][x];

        if (!IntersectRect(&rc,
                           &ps->rcPaint,
                           &Cell->CellExt))
        {
            continue;
        }

        ch = (DWORD)((XCELLS * (y + infoPtr->iYStart)) + x);

        TagFontToCell(Cell, ch);

        if(ch < 0x10000)
        {
            surrogate[0] = (ch & 0xFFFF);
            surrogate[1] = 0;
            DrawTextW(ps->hdc,
                  surrogate,
                  1,
                  &Cell->CellInt,
                  DT_CENTER | DT_VCENTER | DT_SINGLELINE);
        }
        else
        {
            surrogate[0] = (0xD800 + (((ch-0x10000)>>10)&0x3ff));
            surrogate[1] = (0xDC00 + (((ch-0x10000)    )&0x3ff));
            DrawTextW(ps->hdc,
                  surrogate,
                  2,
                  &Cell->CellInt,
                  DT_CENTER | DT_VCENTER | DT_SINGLELINE);
        }
    }

    SelectObject(ps->hdc,
                 hOldFont);
}


static
BOOL
CreateLargeCell(PMAP infoPtr)
{
    RECT rLarge;

    CopyRect(&rLarge,
             &infoPtr->pActiveCell->CellExt);

    MapWindowPoints(infoPtr->hMapWnd,
                    infoPtr->hParent,
                    (VOID*)&rLarge,
                    2);

    InflateRect(&rLarge,
                XLARGE - XCELLS,
                YLARGE - YCELLS);

    infoPtr->hLrgWnd = CreateWindowExW(0,
                                       szLrgCellWndClass,
                                       NULL,
                                       WS_CHILDWINDOW | WS_VISIBLE,
                                       rLarge.left,
                                       rLarge.top,
                                       rLarge.right - rLarge.left,
                                       rLarge.bottom - rLarge.top,
                                       infoPtr->hParent,
                                       NULL,
                                       hInstance,
                                       infoPtr);
    if (!infoPtr->hLrgWnd)
        return FALSE;

    return TRUE;
}


static
VOID
MoveLargeCell(PMAP infoPtr)
{
    RECT rLarge;

    CopyRect(&rLarge,
             &infoPtr->pActiveCell->CellExt);

    MapWindowPoints(infoPtr->hMapWnd,
                    infoPtr->hParent,
                    (VOID*)&rLarge,
                    2);

    InflateRect(&rLarge,
                XLARGE - XCELLS,
                YLARGE - YCELLS);

    MoveWindow(infoPtr->hLrgWnd,
               rLarge.left,
               rLarge.top,
               rLarge.right - rLarge.left,
               rLarge.bottom - rLarge.top,
               TRUE);

    InvalidateRect(infoPtr->hLrgWnd,
                   NULL,
                   TRUE);
}


static
VOID
SetFont(PMAP infoPtr,
        LPWSTR lpFontName)
{
    HDC hdc;

    /* Destroy Zoom window, since it was created with older font */
    DestroyWindow(infoPtr->hLrgWnd);
    infoPtr->hLrgWnd = NULL;

    if (infoPtr->hFont)
        DeleteObject(infoPtr->hFont);

    ZeroMemory(&infoPtr->CurrentFont,
               sizeof(LOGFONTW));

    hdc = GetDC(infoPtr->hMapWnd);
    infoPtr->CurrentFont.lfHeight = GetDeviceCaps(hdc,
                                                  LOGPIXELSY) / 5;
    ReleaseDC(infoPtr->hMapWnd, hdc);

    infoPtr->CurrentFont.lfCharSet =  DEFAULT_CHARSET;
    wcsncpy(infoPtr->CurrentFont.lfFaceName,
            lpFontName,
            sizeof(infoPtr->CurrentFont.lfFaceName) / sizeof(infoPtr->CurrentFont.lfFaceName[0]));

    infoPtr->hFont = CreateFontIndirectW(&infoPtr->CurrentFont);

    InvalidateRect(infoPtr->hMapWnd,
                   NULL,
                   TRUE);

    /* Test if zoom window must be reopened */
    if (infoPtr->pActiveCell != NULL &&
        infoPtr->pActiveCell->bLarge)
    {
        CreateLargeCell(infoPtr);
    }
}


static
LRESULT
NotifyParentOfSelection(PMAP infoPtr,
                        UINT code,
                        WCHAR ch)
{
    LRESULT Ret = 0;

    if (infoPtr->hParent != NULL)
    {
        DWORD dwIdc = GetWindowLongPtr(infoPtr->hMapWnd, GWLP_ID);
        /*
         * Push directly into the event queue instead of waiting
         * the parent to be unlocked.
         * High word of LPARAM is still available for future needs...
         */
        Ret = PostMessage(infoPtr->hParent,
                          WM_COMMAND,
                          MAKELPARAM((WORD)dwIdc, (WORD)code),
                          (LPARAM)LOWORD(ch));
    }

    return Ret;
}


static
VOID
OnClick(PMAP infoPtr,
        WORD ptx,
        WORD pty)
{
    POINT pt;
    INT x, y;

    pt.x = ptx;
    pt.y = pty;

    for (x = 0; x < XCELLS; x++)
    for (y = 0; y < YCELLS; y++)
    {
        if (PtInRect(&infoPtr->Cells[y][x].CellInt,
                     pt))
        {
            /* if the cell is not already active */
            if (!infoPtr->Cells[y][x].bActive)
            {
                /* set previous active cell to inactive */
                if (infoPtr->pActiveCell)
                {
                    /* invalidate normal cells, required when
                     * moving a small active cell via keyboard */
                    if (!infoPtr->pActiveCell->bLarge)
                    {
                        InvalidateRect(infoPtr->hMapWnd,
                                       &infoPtr->pActiveCell->CellInt,
                                       TRUE);
                    }

                    infoPtr->pActiveCell->bActive = FALSE;
                    infoPtr->pActiveCell->bLarge = FALSE;
                }

                /* set new cell to active */
                infoPtr->pActiveCell = &infoPtr->Cells[y][x];
                infoPtr->pActiveCell->bActive = TRUE;
                infoPtr->pActiveCell->bLarge = TRUE;
                if (infoPtr->hLrgWnd)
                    MoveLargeCell(infoPtr);
                else
                    CreateLargeCell(infoPtr);
            }
            else
            {
                /* flick between large and small */
                if (infoPtr->pActiveCell->bLarge)
                {
                    DestroyWindow(infoPtr->hLrgWnd);
                    infoPtr->hLrgWnd = NULL;
                }
                else
                {
                    CreateLargeCell(infoPtr);
                }

                infoPtr->pActiveCell->bLarge = (infoPtr->pActiveCell->bLarge) ? FALSE : TRUE;
            }

            break;
        }
    }
}


static
BOOL
OnCreate(PMAP infoPtr,
         HWND hwnd,
         HWND hParent)
{
    RECT rc;
    BOOL Ret = FALSE;

    infoPtr = HeapAlloc(GetProcessHeap(),
                        0,
                        sizeof(MAP));
    if (infoPtr)
    {
        SetLastError(0);
        SetWindowLongPtrW(hwnd,
                          0,
                          (DWORD_PTR)infoPtr);
        if (GetLastError() == 0)
        {
            ZeroMemory(infoPtr,
                       sizeof(MAP));

            infoPtr->hMapWnd = hwnd;
            infoPtr->hParent = hParent;

            GetClientRect(hwnd, &rc);
            infoPtr->ClientSize.cx = rc.right;
            infoPtr->ClientSize.cy = rc.bottom;
            infoPtr->CellSize.cx = infoPtr->ClientSize.cx / XCELLS;
            infoPtr->CellSize.cy = infoPtr->ClientSize.cy / YCELLS;

            infoPtr->pActiveCell = NULL;

            SetGrid(infoPtr);

            SetScrollRange(hwnd, SB_VERT, 0, MAX_ROWS, FALSE);
            SetScrollPos(hwnd, SB_VERT, 0, TRUE);

            Ret = TRUE;
        }
    }

    return Ret;
}


static
VOID
OnVScroll(PMAP infoPtr,
          INT Value,
          INT Pos)
{
    INT iYDiff, iOldYStart = infoPtr->iYStart;

    switch (Value)
    {
        case SB_LINEUP:
            infoPtr->iYStart -=  1;
            break;

        case SB_LINEDOWN:
            infoPtr->iYStart +=  1;
            break;

        case SB_PAGEUP:
            infoPtr->iYStart -= YCELLS;
            break;

        case SB_PAGEDOWN:
            infoPtr->iYStart += YCELLS;
            break;

        case SB_THUMBTRACK:
            infoPtr->iYStart = Pos;
            break;

       default:
            break;
       }

    infoPtr->iYStart = max(0,
                         min(infoPtr->iYStart, MAX_ROWS-YCELLS));

    iYDiff = iOldYStart - infoPtr->iYStart;
    if (iYDiff)
    {
        if (infoPtr->hLrgWnd != NULL)
        {
            ShowWindow(infoPtr->hLrgWnd, SW_HIDE);
        }

        SetScrollPos(infoPtr->hMapWnd,
                     SB_VERT,
                     infoPtr->iYStart,
                     TRUE);

        if (abs(iYDiff) < YCELLS)
        {
            RECT rect;
            GetClientRect(infoPtr->hMapWnd, &rect);
            rect.top += 2;
            rect.bottom -= 2;
            ScrollWindowEx(infoPtr->hMapWnd,
                           0,
                           iYDiff * infoPtr->CellSize.cy,
                           &rect,
                           &rect,
                           NULL,
                           NULL,
                           SW_INVALIDATE);
        }
        else
        {
            InvalidateRect(infoPtr->hMapWnd,
                           NULL,
                           TRUE);
        }

        if (infoPtr->hLrgWnd != NULL)
        {
            NotifyParentOfSelection(infoPtr,
                                    FM_GETHICHAR,
                                    infoPtr->pActiveCell->ch >> 16);
            NotifyParentOfSelection(infoPtr,
                                    FM_GETCHAR,
                                    infoPtr->pActiveCell->ch);
            ShowWindow(infoPtr->hLrgWnd, SW_SHOW);
        }
    }
}


static
VOID
OnPaint(PMAP infoPtr,
        WPARAM wParam)
{
    PAINTSTRUCT ps;
    HDC hdc;


    if (wParam != 0)
    {
        if (!GetUpdateRect(infoPtr->hMapWnd,
                           &ps.rcPaint,
                           TRUE))
        {
            return;
        }
        hdc = (HDC)wParam;
    }
    else
    {
        hdc = BeginPaint(infoPtr->hMapWnd,
                         &ps);
        if (hdc == NULL)
        {
            return;
        }
    }

    DrawGrid(infoPtr, &ps);

    FillGrid(infoPtr, &ps);

    if (wParam == 0)
    {
        EndPaint(infoPtr->hMapWnd,
                 &ps);
    }
}


LRESULT
CALLBACK
MapWndProc(HWND hwnd,
           UINT uMsg,
           WPARAM wParam,
           LPARAM lParam)
{
    PMAP infoPtr;
    LRESULT Ret = 0;

    infoPtr = (PMAP)GetWindowLongPtrW(hwnd,
                                      0);

    switch (uMsg)
    {
        case WM_CREATE:
        {
            if (!OnCreate(infoPtr,
                          hwnd,
                          ((LPCREATESTRUCTW)lParam)->hwndParent))
            {
                return (LRESULT)-1;
            }

            break;
        }

        case WM_MOUSEACTIVATE:
        {
            SetFocus(hwnd);
            Ret = MA_ACTIVATE;
            break;
        }

        case WM_LBUTTONDOWN:
        {
            OnClick(infoPtr,
                    LOWORD(lParam),
                    HIWORD(lParam));
            NotifyParentOfSelection(infoPtr,
                                    FM_GETHICHAR,
                                    infoPtr->pActiveCell->ch >> 16);
            NotifyParentOfSelection(infoPtr,
                                    FM_GETCHAR,
                                    infoPtr->pActiveCell->ch);

            break;
        }

        case WM_LBUTTONDBLCLK:
        {
            NotifyParentOfSelection(infoPtr,
                                    FM_GETHICHAR,
                                    infoPtr->pActiveCell->ch >> 16);
            NotifyParentOfSelection(infoPtr,
                                    FM_SETCHAR,
                                    infoPtr->pActiveCell->ch);


            break;
        }

        case WM_SETFOCUS:
        case WM_KILLFOCUS:
        {
            InvalidateRect(hwnd, NULL, FALSE);
            break;
        }

        case WM_MOUSEWHEEL:
        {
            if (((short) HIWORD(wParam))/120 > 0 )
                PostMessage (hwnd, WM_VSCROLL, SB_LINEUP, (LPARAM) 0);
            if (((short) HIWORD(wParam))/120 < 0 )
                PostMessage (hwnd, WM_VSCROLL, SB_LINEDOWN, (LPARAM) 0);
            break;
        }

        case WM_VSCROLL:
        {
            OnVScroll(infoPtr,
                      LOWORD(wParam),
                      HIWORD(wParam));

            break;
        }

        case FM_SETFONT:
            SetFont(infoPtr, (LPWSTR)lParam);
            break;

        case FM_GETCHAR:
        {
            if (!infoPtr->pActiveCell) return 0;
            return infoPtr->pActiveCell->ch & 0xFFFF;
        }

        case FM_GETHICHAR:
        {
            if (!infoPtr->pActiveCell) return 0;
            return infoPtr->pActiveCell->ch >> 16;
        }

        case FM_GETHFONT:
            return (LRESULT)infoPtr->hFont;

        case WM_PAINT:
        {
            OnPaint(infoPtr,
                    wParam);
            break;
        }

        case WM_DESTROY:
        {
            DeleteObject(infoPtr->hFont);
            HeapFree(GetProcessHeap(),
                     0,
                     infoPtr);
            SetWindowLongPtrW(hwnd,
                              0,
                              (DWORD_PTR)NULL);
            break;
        }

        default:
        {
            Ret = DefWindowProcW(hwnd,
                                 uMsg,
                                 wParam,
                                 lParam);
            break;
        }
    }

    return Ret;
}


BOOL
RegisterMapClasses(HINSTANCE hInstance)
{
    WNDCLASSW wc = {0};

    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = MapWndProc;
    wc.cbWndExtra = sizeof(PMAP);
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursorW(NULL,
                            (LPWSTR)IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszClassName = szMapWndClass;

    if (RegisterClassW(&wc))
    {
        wc.lpfnWndProc = LrgCellWndProc;
        wc.cbWndExtra = 0;
        wc.lpszClassName = szLrgCellWndClass;

        return RegisterClassW(&wc) != 0;
    }

    return FALSE;
}

VOID
UnregisterMapClasses(HINSTANCE hInstance)
{
    UnregisterClassW(szMapWndClass,
                    hInstance);

    UnregisterClassW(szLrgCellWndClass,
                    hInstance);
}
