/*
 * PROJECT:     ReactOS VGA Font Editor
 * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
 * PURPOSE:     Implements the MDI child window for a font
 * COPYRIGHT:   Copyright 2008 Colin Finck (colin@reactos.org)
 */

#include "precomp.h"

static const WCHAR szFontWndClass[] = L"VGAFontEditFontWndClass";

static BOOL
InitFont(IN PFONT_WND_INFO Info)
{
    Info->Font = (PBITMAP_FONT) HeapAlloc( hProcessHeap, 0, sizeof(BITMAP_FONT) );

    if(Info->OpenInfo->bCreateNew)
    {
        ZeroMemory( Info->Font, sizeof(BITMAP_FONT) );
        return TRUE;
    }
    else
    {
        // Load a font
        BOOL bRet = FALSE;
        DWORD dwTemp;
        HANDLE hFile;

        hFile = CreateFileW(Info->OpenInfo->pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

        if(hFile == INVALID_HANDLE_VALUE)
        {
            LocalizedError( IDS_OPENERROR, GetLastError() );
            return FALSE;
        }

        // Let's first check the file size to determine the file type
        dwTemp = GetFileSize(hFile, NULL);

        switch(dwTemp)
        {
            case 2048:
                // It should be a binary font file
                Info->OpenInfo->bBinaryFileOpened = TRUE;

                if( ReadFile(hFile, Info->Font, sizeof(BITMAP_FONT), &dwTemp, NULL) )
                    bRet = TRUE;
                else
                    LocalizedError( IDS_READERROR, GetLastError() );

                break;

            case 2052:
            {
                PSF1_HEADER Header;

                // Probably it's a PSFv1 file, check the header to make sure
                if( !ReadFile(hFile, &Header, sizeof(PSF1_HEADER) , &dwTemp, NULL) )
                {
                    LocalizedError( IDS_READERROR, GetLastError() );
                    break;
                }
                else
                {
                    if(Header.uMagic[0] == PSF1_MAGIC0 && Header.uMagic[1] == PSF1_MAGIC1)
                    {
                        // Yes, it is a PSFv1 file.
                        // Check the mode and character size. We only support 8x8 fonts with no special mode.
                        if(Header.uCharSize == 8 && Header.uMode == 0)
                        {
                            // Perfect! The file pointer is already set correctly, so we can just read the font bitmap now.
                            if( ReadFile(hFile, Info->Font, sizeof(BITMAP_FONT), &dwTemp, NULL) )
                                bRet = TRUE;
                            else
                                LocalizedError( IDS_READERROR, GetLastError() );
                        }
                        else
                            LocalizedError(IDS_UNSUPPORTEDPSF);

                        break;
                    }

                    // Fall through if the magic numbers aren't there
                }
            }

            default:
                LocalizedError(IDS_UNSUPPORTEDFORMAT);
        }

        CloseHandle(hFile);
        return bRet;
    }
}

static LRESULT CALLBACK
FontWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    PFONT_WND_INFO Info;

    Info = (PFONT_WND_INFO) GetWindowLongPtrW(hwnd, GWLP_USERDATA);

    if(Info || uMsg == WM_CREATE)
    {
        switch(uMsg)
        {
            case WM_CHILDACTIVATE:
                Info->MainWndInfo->CurrentFontWnd = Info;
                SetToolbarFileButtonState(Info->MainWndInfo, TRUE);
                SetPasteButtonState(Info->MainWndInfo);
                break;

            case WM_CREATE:
                Info = (PFONT_WND_INFO)( ( (LPMDICREATESTRUCT) ( (LPCREATESTRUCT)lParam )->lpCreateParams )->lParam );
                Info->hSelf = hwnd;

                SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)Info);

                CreateFontBoxesWindow(Info);

                return 0;

            case WM_USER_APPCLOSE:
            case WM_CLOSE:
                // The user has to close all open edit dialogs first
                if(Info->FirstEditGlyphWnd)
                {
                    PWSTR pszMessage;

                    AllocAndLoadString(&pszMessage, IDS_CLOSEEDIT);
                    MessageBoxW(hwnd, pszMessage, szAppName, MB_OK | MB_ICONEXCLAMATION);
                    HeapFree(hProcessHeap, 0, pszMessage);

                    return 0;
                }

                // Prompt if the current file has been modified
                if(Info->OpenInfo->bModified)
                {
                    INT nMsgBoxResult;
                    PWSTR pszPrompt;
                    WCHAR szFile[MAX_PATH];

                    GetWindowTextW(hwnd, szFile, MAX_PATH);
                    LoadAndFormatString(IDS_SAVEPROMPT, &pszPrompt, szFile);

                    nMsgBoxResult = MessageBoxW(hwnd, pszPrompt, szAppName, MB_YESNOCANCEL | MB_ICONQUESTION);
                    LocalFree(pszPrompt);

                    switch(nMsgBoxResult)
                    {
                        case IDYES:
                            DoFileSave(Info->MainWndInfo, FALSE);
                            break;

                        case IDCANCEL:
                            // 0 = Stop the process of closing the windows (same value for both WM_CLOSE and WM_USER_APPCLOSE)
                            return 0;

                        // IDNO is handled automatically
                    }
                }

                // If there is another child, it will undo the following actions through its WM_CHILDACTIVATE handler.
                // Otherwise CurrentFontWnd will stay NULL, so the main window knows that no more childs are opened.
                Info->MainWndInfo->CurrentFontWnd = NULL;
                SetToolbarFileButtonState(Info->MainWndInfo, FALSE);
                SetPasteButtonState(Info->MainWndInfo);

                if(uMsg == WM_USER_APPCLOSE)
                {
                    // First do the tasks we would do for a normal WM_CLOSE message, then return the value for WM_USER_APPCLOSE
                    // Anything other than 0 indicates that the application shall continue closing the windows
                    DefMDIChildProcW(hwnd, WM_CLOSE, 0, 0);
                    return 1;
                }
                break;

            case WM_DESTROY:
                // Remove the window from the linked list
                if(Info->PrevFontWnd)
                    Info->PrevFontWnd->NextFontWnd = Info->NextFontWnd;
                else
                    Info->MainWndInfo->FirstFontWnd = Info->NextFontWnd;

                if(Info->NextFontWnd)
                    Info->NextFontWnd->PrevFontWnd = Info->PrevFontWnd;
                else
                    Info->MainWndInfo->LastFontWnd = Info->PrevFontWnd;

                // Free memory
                if(Info->Font)
                    HeapFree(hProcessHeap, 0, Info->Font);

                if(Info->OpenInfo->pszFileName)
                    HeapFree(hProcessHeap, 0, Info->OpenInfo->pszFileName);

                HeapFree(hProcessHeap, 0, Info->OpenInfo);
                HeapFree(hProcessHeap, 0, Info);

                SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
                return 0;

            case WM_SETFOCUS:
                // Set the keyboard focus to the FontBoxes window every time the Font window gets the focus
                SetFocus(Info->hFontBoxesWnd);
                break;

            case WM_SIZE:
            {
                INT nHeight = HIWORD(lParam);
                INT nWidth = LOWORD(lParam);
                POINT pt;
                RECT WndRect;

                // This ugly workaround is necessary for not setting either the Height or the Width of the window with SetWindowPos
                GetWindowRect(Info->hFontBoxesWnd, &WndRect);
                pt.x = WndRect.left;
                pt.y = WndRect.top;
                ScreenToClient(hwnd, &pt);

                if(nHeight < FONT_BOXES_WND_HEIGHT)
                {
                    SCROLLINFO si;

                    // Set the vertical scroll bar
                    si.cbSize = sizeof(si);
                    si.fMask = SIF_RANGE | SIF_PAGE;
                    si.nMin = 0;
                    si.nMax = FONT_BOXES_WND_HEIGHT;
                    si.nPage = nHeight;
                    SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
                }
                else
                {
                    ShowScrollBar(hwnd, SB_VERT, FALSE);

                    // Store the new y coordinate in pt.y as well (needed for the SetWindowPos call for setting a new x coordinate)
                    pt.y = nHeight / 2 - FONT_BOXES_WND_HEIGHT / 2;
                    SetWindowPos(Info->hFontBoxesWnd,
                                 NULL,
                                 pt.x,
                                 pt.y,
                                 0,
                                 0,
                                 SWP_NOSIZE | SWP_NOZORDER);
                }

                if(nWidth < FONT_BOXES_WND_WIDTH)
                {
                    SCROLLINFO si;

                    // Set the horizontal scroll bar
                    si.cbSize = sizeof(si);
                    si.fMask = SIF_RANGE | SIF_PAGE;
                    si.nMin = 0;
                    si.nMax = FONT_BOXES_WND_WIDTH;
                    si.nPage = nWidth;
                    SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
                }
                else
                {
                    ShowScrollBar(hwnd, SB_HORZ, FALSE);

                    SetWindowPos(Info->hFontBoxesWnd,
                                 NULL,
                                 nWidth / 2 - FONT_BOXES_WND_WIDTH / 2,
                                 pt.y,
                                 0,
                                 0,
                                 SWP_NOSIZE | SWP_NOZORDER);
                }

                // We have to call DefMDIChildProcW here as well, otherwise we won't get the Minimize/Maximize/Close buttons for a maximized MDI child.
                break;
            }

            case WM_HSCROLL:
            case WM_VSCROLL:
            {
                INT nBar;
                INT nOrgPos;
                SCROLLINFO si;

                if(uMsg == WM_HSCROLL)
                    nBar = SB_HORZ;
                else
                    nBar = SB_VERT;

                si.cbSize = sizeof(si);
                si.fMask = SIF_ALL;
                GetScrollInfo(hwnd, nBar, &si);

                nOrgPos = si.nPos;

                switch( LOWORD(wParam) )
                {
                    // Constant is the same as SB_LEFT for WM_HSCROLL
                    case SB_TOP:
                        si.nPos = si.nMin;
                        break;

                    // Constant is the same as SB_RIGHT for WM_HSCROLL
                    case SB_BOTTOM:
                        si.nPos = si.nMax;
                        break;

                    // Constant is the same as SB_LINELEFT for WM_HSCROLL
                    case SB_LINEUP:
                        si.nPos -= 20;
                        break;

                    // Constant is the same as SB_LINERIGHT for WM_HSCROLL
                    case SB_LINEDOWN:
                        si.nPos += 20;
                        break;

                    // Constant is the same as SB_PAGELEFT for WM_HSCROLL
                    case SB_PAGEUP:
                        si.nPos -= si.nPage;
                        break;

                    // Constant is the same as SB_PAGERIGHT for WM_HSCROLL
                    case SB_PAGEDOWN:
                        si.nPos += si.nPage;
                        break;

                    case SB_THUMBTRACK:
                        si.nPos = si.nTrackPos;
                        break;
                }

                si.fMask = SIF_POS;
                SetScrollInfo(hwnd, nBar, &si, TRUE);
                GetScrollInfo(hwnd, nBar, &si);

                if(si.nPos != nOrgPos)
                {
                    // This ugly workaround is necessary for not setting the x coordinate
                    POINT pt;
                    RECT WndRect;

                    GetWindowRect(Info->hFontBoxesWnd, &WndRect);
                    pt.x = WndRect.left;
                    pt.y = WndRect.top;
                    ScreenToClient(hwnd, &pt);

                    if(uMsg == WM_HSCROLL)
                        SetWindowPos(Info->hFontBoxesWnd, NULL, -si.nPos, pt.y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
                    else
                        SetWindowPos(Info->hFontBoxesWnd, NULL, pt.x, -si.nPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
                }

                return 0;
            }
        }
    }

    return DefMDIChildProcW(hwnd, uMsg, wParam, lParam);
}

BOOL
CreateFontWindow(IN PMAIN_WND_INFO MainWndInfo, IN PFONT_OPEN_INFO OpenInfo)
{
    HWND hFontWnd;
    PFONT_WND_INFO Info;

    Info = (PFONT_WND_INFO) HeapAlloc( hProcessHeap, HEAP_ZERO_MEMORY, sizeof(FONT_WND_INFO) );

    if(Info)
    {
        Info->MainWndInfo = MainWndInfo;
        Info->OpenInfo = OpenInfo;

        if( InitFont(Info) )
        {
            PWSTR pch, pszWindowTitle;

            if(OpenInfo->pszFileName)
            {
                pch = wcsrchr(OpenInfo->pszFileName, '\\');
                pszWindowTitle = (pch ? (pch + 1) : OpenInfo->pszFileName);
            }
            else
            {
                LoadAndFormatString(IDS_DOCNAME, &pszWindowTitle, ++MainWndInfo->uDocumentCounter);
            }

            hFontWnd = CreateMDIWindowW( szFontWndClass,
                                         pszWindowTitle,
                                         0,
                                         CW_USEDEFAULT,
                                         CW_USEDEFAULT,
                                         FONT_WND_MIN_WIDTH,
                                         FONT_WND_MIN_HEIGHT,
                                         MainWndInfo->hMdiClient,
                                         hInstance,
                                         (LPARAM)Info );

            if(!OpenInfo->pszFileName)
                LocalFree(pszWindowTitle);

            if(hFontWnd)
            {
                // Add the new window to the linked list
                Info->PrevFontWnd = Info->MainWndInfo->LastFontWnd;

                if(Info->MainWndInfo->LastFontWnd)
                    Info->MainWndInfo->LastFontWnd->NextFontWnd = Info;
                else
                    Info->MainWndInfo->FirstFontWnd = Info;

                Info->MainWndInfo->LastFontWnd = Info;

                return TRUE;
            }
        }

        HeapFree(hProcessHeap, 0, Info);
    }

    return FALSE;
}

BOOL
InitFontWndClass(VOID)
{
    WNDCLASSW wc = {0,};

    wc.lpfnWndProc    = FontWndProc;
    wc.hInstance      = hInstance;
    wc.hCursor        = LoadCursor( NULL, IDC_ARROW );
    wc.hIcon          = LoadIconW( hInstance, MAKEINTRESOURCEW(IDI_DOC) );
    wc.hbrBackground  = (HBRUSH)( COLOR_BTNFACE + 1 );
    wc.lpszClassName  = szFontWndClass;

    return RegisterClassW(&wc) != 0;
}

VOID
UnInitFontWndClass(VOID)
{
    UnregisterClassW(szFontWndClass, hInstance);
}
