/*
 * PROJECT:     ReactOS Magnify
 * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
 * PURPOSE:     Magnification of parts of the screen.
 * COPYRIGHT:   Copyright 2007-2019 Marc Piulachs <marc.piulachs@codexchange.net>
 *              Copyright 2015-2019 David Quintana <gigaherz@gmail.com>
 */

/* TODO: Support AppBar types other than ABE_TOP */

#include "magnifier.h"

#include <winbase.h>
#include <winuser.h>
#include <wingdi.h>
#include <winnls.h>
#include <commctrl.h>
#include <shellapi.h>
#include <windowsx.h>
#include <stdlib.h>
#include <tchar.h>

#include "resource.h"

#define APPMSG_NOTIFYICON (WM_APP+1)
#define APPMSG_APPBAR     (WM_APP+2)

const TCHAR szWindowClass[] = TEXT("MAGNIFIER");

/* Global Variables */
HINSTANCE hInst;
HWND hMainWnd;

#define MAX_LOADSTRING 100
TCHAR szTitle[MAX_LOADSTRING];

#define TIMER_SPEED   1
#define REPAINT_SPEED 100

DWORD lastTicks = 0;

HWND hDesktopWindow = NULL;

NOTIFYICONDATA nid;
HICON notifyIcon;
HMENU notifyMenu;
HWND  hOptionsDialog;
BOOL  bOptionsDialog = FALSE;
BOOL  bRecreateOffscreenDC = TRUE;
LONG  sourceWidth = 0;
LONG  sourceHeight = 0;
HDC     hdcOffscreen = NULL;
HBITMAP hbmpOffscreen = NULL;
HANDLE  hbmpOld;
POINT ptDragOffset;
INT nearEdge;

/* Current magnified area */
POINT cp;

/* Last positions */
POINT pMouse;
POINT pCaret;
POINT pFocus;
HWND pCaretWnd;
HWND pFocusWnd;

ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    AboutProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    OptionsProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    WarningProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    MSG msg;
    HACCEL hAccelTable;
    INITCOMMONCONTROLSEX iccex;

    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    switch (GetUserDefaultUILanguage())
    {
        case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT):
            SetProcessDefaultLayout(LAYOUT_RTL);
            break;

        default:
            break;
    }

    /* Initialize global strings */
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    /* Perform application initialization */
    if (!InitInstance(hInstance, nCmdShow))
        return FALSE;

    hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MAGNIFIER));

    /* Main message loop */
    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (!TranslateAccelerator(hMainWnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    /* Load the common controls */
    iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    iccex.dwICC = ICC_STANDARD_CLASSES | ICC_WIN95_CLASSES;
    InitCommonControlsEx(&iccex);

    SelectObject(hdcOffscreen, hbmpOld);
    DeleteObject (hbmpOffscreen);
    DeleteDC(hdcOffscreen);

    return (int) msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASS wc;

    wc.style            = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc      = WndProc;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = 0;
    wc.hInstance        = hInstance;
    wc.hIcon            = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));
    wc.hCursor          = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground    = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName     = NULL; //MAKEINTRESOURCE(IDC_MAGNIFIER);
    wc.lpszClassName    = szWindowClass;

    return RegisterClass(&wc);
}

void DoAppBarStuff(DWORD mode)
{
    UINT uState;
    APPBARDATA data = {0};
    data.cbSize = sizeof(data);
    data.hWnd = hMainWnd;
    data.uCallbackMessage = APPMSG_APPBAR;

    if (mode == ABM_NEW || mode == ABM_SETPOS)
    {
        HWND hWndOrder = HWND_BOTTOM;
        int rcw, rch;
        RECT rcWorkArea;
        SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWorkArea, 0);

        if (mode == ABM_NEW)
        {
            SHAppBarMessage(ABM_NEW, &data);

            switch(AppBarConfig.uEdge)
            {
                case ABE_LEFT:
                    data.rc.top = rcWorkArea.top;
                    data.rc.bottom = rcWorkArea.bottom;
                    data.rc.left = rcWorkArea.left;
                    data.rc.right = data.rc.left + AppBarConfig.appBarSizes.left;
                    break;
                case ABE_TOP:
                    data.rc.left = rcWorkArea.left;
                    data.rc.right = rcWorkArea.right;
                    data.rc.top = rcWorkArea.top;
                    data.rc.bottom = data.rc.top + AppBarConfig.appBarSizes.top;
                    break;
                case ABE_RIGHT:
                    data.rc.top = rcWorkArea.top;
                    data.rc.bottom = rcWorkArea.bottom;
                    data.rc.right = rcWorkArea.left;
                    data.rc.left = data.rc.right - AppBarConfig.appBarSizes.right;
                    break;
                case ABE_BOTTOM:
                    data.rc.left = rcWorkArea.left;
                    data.rc.right = rcWorkArea.right;
                    data.rc.bottom = rcWorkArea.bottom;
                    data.rc.top = data.rc.bottom - AppBarConfig.appBarSizes.bottom;
                    break;
            }
        }
        else
        {
            GetWindowRect(hMainWnd, &data.rc);
        }

        data.uEdge = AppBarConfig.uEdge;
        uState = SHAppBarMessage(ABM_QUERYPOS, &data);
        uState = SHAppBarMessage(ABM_SETPOS, &data);

        rcw = data.rc.right-data.rc.left;
        rch = data.rc.bottom-data.rc.top;

        uState = SHAppBarMessage(ABM_GETSTATE, &data);
        if (uState & ABS_ALWAYSONTOP)
            hWndOrder = HWND_TOPMOST;

        SetWindowPos(hMainWnd, hWndOrder, data.rc.left, data.rc.top, rcw, rch, SWP_SHOWWINDOW | SWP_NOCOPYBITS);

    }
    else if (mode == ABM_GETSTATE)
    {
        HWND hWndOrder = HWND_BOTTOM;
        uState = SHAppBarMessage(ABM_GETSTATE, &data);
        if (uState & ABS_ALWAYSONTOP)
            hWndOrder = HWND_TOPMOST;
        SetWindowPos(hMainWnd, hWndOrder, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
    }
    else if (mode == ABM_ACTIVATE)
    {
        SHAppBarMessage(ABM_ACTIVATE, &data);
    }
    else if (mode == ABM_WINDOWPOSCHANGED)
    {
        SHAppBarMessage(ABM_WINDOWPOSCHANGED, &data);
    }
    else if (mode == ABM_REMOVE)
    {
        SHAppBarMessage(ABM_REMOVE, &data);
    }
}

void AttachAppBar(INT uEdge)
{
    if (AppBarConfig.uEdge == uEdge)
        return;

    if (AppBarConfig.uEdge < 0 && uEdge >= 0)
    {
        SetWindowLongPtr(hMainWnd, GWL_STYLE, GetWindowLongPtr(hMainWnd, GWL_STYLE) & (~WS_CAPTION));
    }
    else if (uEdge < 0 && AppBarConfig.uEdge >= 0)
    {
        SetWindowLongPtr(hMainWnd, GWL_STYLE, GetWindowLongPtr(hMainWnd, GWL_STYLE) | WS_CAPTION);
        SetWindowPos(hMainWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_FRAMECHANGED);
    }

    if (AppBarConfig.uEdge >= 0)
    {
        DoAppBarStuff(ABM_REMOVE);
    }

    if (uEdge >= 0)
    {
        AppBarConfig.uEdge = uEdge;
        DoAppBarStuff(ABM_NEW);
    }
    else
    {
        RECT rc = AppBarConfig.rcFloating;
        SetWindowPos(hMainWnd, HWND_TOPMOST, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, 0);
    }

    AppBarConfig.uEdge = uEdge;
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    RECT rc;
    DWORD exStyles = WS_EX_TOOLWINDOW | WS_EX_CONTROLPARENT;
    DWORD dwStyles = WS_SIZEBOX | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP;

    /* Load settings from registry */
    LoadSettings();

    rc = AppBarConfig.rcFloating;

    hInst = hInstance; // Store instance handle in our global variable

    if (AppBarConfig.uEdge <0 )
    {
        dwStyles |= WS_CAPTION;
        exStyles |= WS_EX_TOPMOST;
    }

    /* Create the Window */
    hMainWnd = CreateWindowEx(exStyles,
                              szWindowClass,
                              szTitle,
                              dwStyles,
                              rc.left,
                              rc.top,
                              rc.right-rc.left,
                              rc.bottom-rc.top,
                              NULL,
                              NULL,
                              hInstance,
                              NULL);
    if (!hMainWnd)
        return FALSE;

    if (AppBarConfig.uEdge >= 0)
        DoAppBarStuff(ABM_NEW);
    else
        SetWindowPos(hMainWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);

    // In Windows 2003's Magnifier, the "Start Minimized" setting
    // refers exclusively to the options dialog, not the main window itself.
    hOptionsDialog = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOGOPTIONS), hMainWnd, OptionsProc);
    ShowWindow(hOptionsDialog, (bStartMinimized ? SW_HIDE : SW_SHOW));

    if (bShowWarning)
        DialogBox(hInstance, MAKEINTRESOURCE(IDD_WARNINGDIALOG), hMainWnd, WarningProc);

    return TRUE;
}

void Refresh(void)
{
    if (!IsIconic(hMainWnd))
    {
        /* Invalidate the client area forcing a WM_PAINT message */
        InvalidateRgn(hMainWnd, NULL, TRUE);
    }
}

void GetBestOverlapWithMonitors(LPRECT rect)
{
    int rcLeft, rcTop;
    int rcWidth, rcHeight;
    RECT rcMon;
    HMONITOR hMon = MonitorFromRect(rect, MONITOR_DEFAULTTONEAREST);
    MONITORINFO info;
    info.cbSize = sizeof(info);

    GetMonitorInfo(hMon, &info);

    rcMon = info.rcMonitor;

    rcLeft = rect->left;
    rcTop = rect->top;
    rcWidth  = (rect->right - rect->left);
    rcHeight = (rect->bottom - rect->top);

    if (rcLeft < rcMon.left)
       rcLeft = rcMon.left;

    if (rcTop < rcMon.top)
       rcTop = rcMon.top;

    if (rcLeft > (rcMon.right - rcWidth))
        rcLeft = (rcMon.right - rcWidth);

    if (rcTop > (rcMon.bottom - rcHeight))
        rcTop = (rcMon.bottom - rcHeight);

    OffsetRect(rect, (rcLeft-rect->left), (rcTop-rect->top));
}

void Draw(HDC aDc)
{
    HDC desktopHdc = NULL;

    RECT sourceRect, intersectedRect;
    RECT targetRect, appRect;
    DWORD rop = SRCCOPY;
    CURSORINFO cinfo;
    ICONINFO iinfo;

    int AppWidth, AppHeight;

    if (bInvertColors)
        rop = NOTSRCCOPY;

    desktopHdc = GetDC(0);

    GetClientRect(hMainWnd, &appRect);
    AppWidth  = (appRect.right - appRect.left);
    AppHeight = (appRect.bottom - appRect.top);

    ZeroMemory(&cinfo, sizeof(cinfo));
    ZeroMemory(&iinfo, sizeof(iinfo));
    cinfo.cbSize = sizeof(cinfo);
    GetCursorInfo(&cinfo);
    GetIconInfo(cinfo.hCursor, &iinfo);

    targetRect = appRect;
    ClientToScreen(hMainWnd, (POINT*)&targetRect.left);
    ClientToScreen(hMainWnd, (POINT*)&targetRect.right);

    if (bRecreateOffscreenDC || !hdcOffscreen)
    {
        bRecreateOffscreenDC = FALSE;

        if (hdcOffscreen)
        {
            SelectObject(hdcOffscreen, hbmpOld);
            DeleteObject (hbmpOffscreen);
            DeleteDC(hdcOffscreen);
        }

        sourceWidth  = AppWidth / uiZoom;
        sourceHeight = AppHeight / uiZoom;

         /* Create a memory DC compatible with client area DC */
        hdcOffscreen = CreateCompatibleDC(desktopHdc);

        /* Create a bitmap compatible with the client area DC */
        hbmpOffscreen = CreateCompatibleBitmap(desktopHdc,
                                               sourceWidth,
                                               sourceHeight);

        /* Select our bitmap in memory DC and save the old one */
        hbmpOld = SelectObject(hdcOffscreen, hbmpOffscreen);
    }

    GetWindowRect(hDesktopWindow, &sourceRect);
    sourceRect.left = (cp.x) - (sourceWidth /2);
    sourceRect.top = (cp.y) - (sourceHeight /2);
    sourceRect.right = sourceRect.left + sourceWidth;
    sourceRect.bottom = sourceRect.top + sourceHeight;

    GetBestOverlapWithMonitors(&sourceRect);

    /* Paint the screen bitmap to our in memory DC */
    BitBlt(hdcOffscreen,
           0,
           0,
           sourceWidth,
           sourceHeight,
           desktopHdc,
           sourceRect.left,
           sourceRect.top,
           rop);

    if (IntersectRect(&intersectedRect, &sourceRect, &targetRect))
    {
        OffsetRect(&intersectedRect, -sourceRect.left, -sourceRect.top);
        FillRect(hdcOffscreen, &intersectedRect, GetStockObject(DC_BRUSH));
    }

    /* Draw the mouse pointer in the right position */
    DrawIcon(hdcOffscreen,
             pMouse.x - iinfo.xHotspot - sourceRect.left, // - 10,
             pMouse.y - iinfo.yHotspot - sourceRect.top, // - 10,
             cinfo.hCursor);

    /* Blast the stretched image from memory DC to window DC */
    StretchBlt(aDc,
               0,
               0,
               AppWidth,
               AppHeight,
               hdcOffscreen,
               0,
               0,
               sourceWidth,
               sourceHeight,
               SRCCOPY | NOMIRRORBITMAP);

    /* Cleanup */
    if (iinfo.hbmMask)
        DeleteObject(iinfo.hbmMask);
    if (iinfo.hbmColor)
        DeleteObject(iinfo.hbmColor);
    ReleaseDC(hDesktopWindow, desktopHdc);
}

void HandleNotifyIconMessage(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    POINT pt;

    switch(lParam)
    {
        case WM_LBUTTONUP:
            PostMessage(hMainWnd, WM_COMMAND, IDM_OPTIONS, 0);
            break;
        case WM_RBUTTONUP:
            GetCursorPos(&pt);
            TrackPopupMenu(notifyMenu, 0, pt.x, pt.y, 0, hWnd, NULL);
            break;
    }
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId;

    switch (message)
    {
        case WM_TIMER:
        {
            BOOL hasMoved = FALSE;

            GUITHREADINFO guiInfo;
            guiInfo.cbSize = sizeof(guiInfo);

            GetGUIThreadInfo(0, &guiInfo);

            if (bFollowMouse)
            {
                POINT pNewMouse;

                //Get current mouse position
                GetCursorPos (&pNewMouse);

#define PointsAreEqual(pt1, pt2) (((pt1).x == (pt2).x) && ((pt1).y == (pt2).y))

                //If mouse has moved ...
                if (!PointsAreEqual(pMouse, pNewMouse))
                {
                    //Update to new position
                    pMouse = pNewMouse;
                    cp = pNewMouse;
                    hasMoved = TRUE;
                }
            }

            if (guiInfo.hwndActive != hMainWnd)
            {
                if (bFollowCaret)
                {
                    if (guiInfo.hwndCaret)
                    {
                        POINT ptCaret;
                        ptCaret.x = (guiInfo.rcCaret.left + guiInfo.rcCaret.right) / 2;
                        ptCaret.y = (guiInfo.rcCaret.top + guiInfo.rcCaret.bottom) / 2;

                        if ((pCaretWnd != guiInfo.hwndCaret) || !PointsAreEqual(pCaret, ptCaret))
                        {
                            //Update to new position
                            pCaret = ptCaret;
                            pCaretWnd = guiInfo.hwndCaret;
                            if (!hasMoved)
                            {
                                ClientToScreen (guiInfo.hwndCaret, (LPPOINT) &ptCaret);
                                cp = ptCaret;
                                hasMoved = TRUE;
                            }
                        }
                    }
                    else
                    {
                        pCaretWnd = NULL;
                    }
                }

                if (bFollowFocus)
                {
                    if (guiInfo.hwndFocus && !guiInfo.hwndCaret)
                    {
                        POINT ptFocus;
                        RECT activeRect;

                        //Get current control focus
                        GetWindowRect(guiInfo.hwndFocus, &activeRect);
                        ptFocus.x = (activeRect.left + activeRect.right) / 2;
                        ptFocus.y = (activeRect.top + activeRect.bottom) / 2;

                        if ((guiInfo.hwndFocus != pFocusWnd) || !PointsAreEqual(pFocus, ptFocus))
                        {
                            //Update to new position
                            pFocus = ptFocus;
                            pFocusWnd = guiInfo.hwndFocus;
                            if (!hasMoved)
                            {
                                cp = ptFocus;
                                hasMoved = TRUE;
                            }
                        }
                    }
                    else
                    {
                        pFocusWnd = NULL;
                    }
                }
            }

            if (!hasMoved)
            {
                DWORD newTicks = GetTickCount();
                DWORD elapsed = (newTicks - lastTicks);
                if (elapsed > REPAINT_SPEED)
                {
                    hasMoved = TRUE;
                }
            }

            if (hasMoved)
            {
                lastTicks = GetTickCount();
                Refresh();
            }

            return 0;
        }

        case WM_COMMAND:
        {
            wmId = LOWORD(wParam);
            /* Parse the menu selections */
            switch (wmId)
            {
                case IDM_OPTIONS:
                    ShowWindow(hOptionsDialog, (bOptionsDialog ? SW_HIDE : SW_SHOW));
                    break;
                case IDM_ABOUT:
                    DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, AboutProc);
                    break;
                case IDM_EXIT:
                    DestroyWindow(hWnd);
                    break;
                default:
                    return DefWindowProc(hWnd, message, wParam, lParam);
            }
            return 0;
        }

        case WM_PAINT:
        {
            PAINTSTRUCT PaintStruct;
            HDC dc;
            dc = BeginPaint(hWnd, &PaintStruct);
            Draw(dc);
            EndPaint(hWnd, &PaintStruct);
            return 0;
        }

        case WM_CONTEXTMENU:
            TrackPopupMenu(notifyMenu, 0, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0, hWnd, NULL);
            return 0;

        case WM_LBUTTONDOWN:
        {
            RECT rc;
            POINT pt;
            SetCapture(hWnd);

            GetCursorPos(&pt);
            GetWindowRect(hWnd, &rc);
            ptDragOffset.x = pt.x - rc.left;
            ptDragOffset.y = pt.y - rc.top;

            nearEdge = AppBarConfig.uEdge;

            break;
        }

        case WM_MOUSEMOVE:
            if (GetCapture() == hWnd)
            {
                RECT rc;
                POINT pt;
                RECT rcWorkArea;
                SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWorkArea, 0);
                GetCursorPos(&pt);
                GetWindowRect(hWnd, &rc);

                if (AppBarConfig.uEdge >= 0)
                {
                    if (pt.x >= rcWorkArea.left && pt.x <= rcWorkArea.right &&
                        pt.y >= rcWorkArea.top && pt.y <= rcWorkArea.bottom)
                    {
                        AttachAppBar(-2);

                        // Fixup offset
                        GetWindowRect(hWnd, &rc);
                        ptDragOffset.x = (rc.right-rc.left)/2;
                        ptDragOffset.y = 2;

                        rc.left = pt.x - ptDragOffset.x;
                        rc.top = pt.y - ptDragOffset.y;

                        SetWindowPos(hWnd, HWND_TOPMOST, rc.left, rc.top, 0, 0, SWP_NOSIZE);
                    }
                }
                else
                {
                    if (pt.x <= rcWorkArea.left+8 && nearEdge != ABE_LEFT)
                    {
                        AttachAppBar(ABE_LEFT);
                        nearEdge = ABE_LEFT;
                    }
                    else if (pt.y <= rcWorkArea.top+8 && nearEdge != ABE_TOP)
                    {
                        AttachAppBar(ABE_TOP);
                        nearEdge = ABE_TOP;
                    }
                    else if (pt.x >= rcWorkArea.right-8 && nearEdge != ABE_RIGHT)
                    {
                        AttachAppBar(ABE_RIGHT);
                        nearEdge = ABE_RIGHT;
                    }
                    else if (pt.y >= rcWorkArea.bottom-8 && nearEdge != ABE_BOTTOM)
                    {
                        AttachAppBar(ABE_BOTTOM);
                        nearEdge = ABE_BOTTOM;
                    }
                    else
                    {
                        rc.left = pt.x - ptDragOffset.x;
                        rc.top = pt.y - ptDragOffset.y;

                        SetWindowPos(hWnd, HWND_TOPMOST, rc.left, rc.top, 0, 0, SWP_NOSIZE);
                        nearEdge = -1;
                    }
                }

                pMouse = pt;
                Refresh();
            }
            break;

        case WM_LBUTTONUP:
            if (GetCapture() == hWnd)
            {
                if (AppBarConfig.uEdge >= 0)
                    DoAppBarStuff(ABM_GETSTATE);
                else
                    SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
                ReleaseCapture();
            }
            break;

        case WM_SIZE:
            if (AppBarConfig.uEdge >= 0)
                DoAppBarStuff(ABM_SETPOS);
            /* fallthrough */
        case WM_DISPLAYCHANGE:
            bRecreateOffscreenDC = TRUE;
            Refresh();
            break;

        case WM_ERASEBKGND:
            // handle WM_ERASEBKGND by simply returning non-zero because we did all the drawing in WM_PAINT.
            return 0;

        case WM_DESTROY:
        {
            if (AppBarConfig.uEdge >= 0)
                DoAppBarStuff(ABM_REMOVE);

            KillTimer(hWnd, 1);

            /* Save settings to registry */
            SaveSettings();

            /* Cleanup notification icon */
            ZeroMemory(&nid, sizeof(nid));
            nid.cbSize = sizeof(nid);
            nid.uFlags = NIF_MESSAGE;
            nid.hWnd = hWnd;
            nid.uCallbackMessage = APPMSG_NOTIFYICON;
            Shell_NotifyIcon(NIM_DELETE, &nid);
            DestroyIcon(notifyIcon);

            DestroyWindow(hOptionsDialog);

            PostQuitMessage(0);
            return 0;
        }

        case WM_CREATE:
        {
            HMENU tempMenu;

            /* Get the desktop window */
            hDesktopWindow = GetDesktopWindow();

            /* Set the timer */
            SetTimer(hWnd, 1, TIMER_SPEED, NULL);

            /* Notification icon */
            notifyIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);

            ZeroMemory(&nid, sizeof(nid));
            nid.cbSize = sizeof(nid);
            nid.uFlags = NIF_ICON | NIF_MESSAGE;
            nid.hWnd = hWnd;
            nid.uCallbackMessage = APPMSG_NOTIFYICON;
            nid.hIcon = notifyIcon;
            Shell_NotifyIcon(NIM_ADD, &nid);

            tempMenu = LoadMenu(hInst, MAKEINTRESOURCE(IDC_MAGNIFIER));
            notifyMenu = GetSubMenu(tempMenu, 0);
            RemoveMenu(tempMenu, 0, MF_BYPOSITION);
            DestroyMenu(tempMenu);
            return 0;
        }

        case APPMSG_APPBAR:
        {
            switch (wParam)
            {
                case ABN_STATECHANGE:
                    DoAppBarStuff(ABM_GETSTATE);
                    break;
                case ABN_POSCHANGED:
                    DoAppBarStuff(ABM_SETPOS);
                    break;
                case ABN_FULLSCREENAPP:
                {
                    if (!lParam)
                    {
                        DoAppBarStuff(ABM_GETSTATE);
                        break;
                    }

                    SetWindowPos(hMainWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
                    break;
                }
               case ABN_WINDOWARRANGE:
                    ShowWindow(hMainWnd, (lParam ? SW_HIDE : SW_SHOW));
                    break;
            }
            return 0;
        }

        case APPMSG_NOTIFYICON:
            HandleNotifyIconMessage(hWnd, wParam, lParam);
            return 0;

        case WM_ACTIVATE:
            if (AppBarConfig.uEdge >= 0)
                DoAppBarStuff(ABM_ACTIVATE);
            break;

        case WM_WINDOWPOSCHANGED:
            if (AppBarConfig.uEdge >= 0)
                DoAppBarStuff(ABM_WINDOWPOSCHANGED);
            Refresh();
            break;

        default:
            break;
    }

    return DefWindowProc(hWnd, message, wParam, lParam);
}

INT_PTR CALLBACK AboutProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
        case WM_INITDIALOG:
            return (INT_PTR)TRUE;

        case WM_COMMAND:
            if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
            {
                EndDialog(hDlg, LOWORD(wParam));
                return (INT_PTR)TRUE;
            }
            break;
    }

    return (INT_PTR)FALSE;
}

INT_PTR CALLBACK OptionsProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);

    switch (message)
    {
        case WM_INITDIALOG:
        {
            /* Add the zoom items */
            SendDlgItemMessage(hDlg, IDC_ZOOM, CB_ADDSTRING, 0, (LPARAM)("1"));
            SendDlgItemMessage(hDlg, IDC_ZOOM, CB_ADDSTRING, 0, (LPARAM)("2"));
            SendDlgItemMessage(hDlg, IDC_ZOOM, CB_ADDSTRING, 0, (LPARAM)("3"));
            SendDlgItemMessage(hDlg, IDC_ZOOM, CB_ADDSTRING, 0, (LPARAM)("4"));
            SendDlgItemMessage(hDlg, IDC_ZOOM, CB_ADDSTRING, 0, (LPARAM)("5"));
            SendDlgItemMessage(hDlg, IDC_ZOOM, CB_ADDSTRING, 0, (LPARAM)("6"));
            SendDlgItemMessage(hDlg, IDC_ZOOM, CB_ADDSTRING, 0, (LPARAM)("7"));
            SendDlgItemMessage(hDlg, IDC_ZOOM, CB_ADDSTRING, 0, (LPARAM)("8"));
            SendDlgItemMessage(hDlg, IDC_ZOOM, CB_ADDSTRING, 0, (LPARAM)("9"));

            if (uiZoom >= 1 && uiZoom <= 9)
                SendDlgItemMessage(hDlg, IDC_ZOOM, CB_SETCURSEL, uiZoom - 1, 0);

            if (bFollowMouse)
                SendDlgItemMessage(hDlg,IDC_FOLLOWMOUSECHECK,BM_SETCHECK, wParam, 0);

            if (bFollowFocus)
                SendDlgItemMessage(hDlg,IDC_FOLLOWKEYBOARDCHECK,BM_SETCHECK, wParam, 0);

            if (bFollowCaret)
                SendDlgItemMessage(hDlg,IDC_FOLLOWTEXTEDITINGCHECK,BM_SETCHECK, wParam, 0);

            if (bInvertColors)
                SendDlgItemMessage(hDlg,IDC_INVERTCOLORSCHECK,BM_SETCHECK, wParam, 0);

            if (bStartMinimized)
                SendDlgItemMessage(hDlg,IDC_STARTMINIMIZEDCHECK,BM_SETCHECK, wParam, 0);

            if (bShowMagnifier)
                SendDlgItemMessage(hDlg,IDC_SHOWMAGNIFIERCHECK,BM_SETCHECK, wParam, 0);

            return (INT_PTR)TRUE;
        }

        case WM_SHOWWINDOW:
            bOptionsDialog = wParam;
            break;

        case WM_COMMAND:
        switch (LOWORD(wParam))
        {
            case IDOK:
            case IDCANCEL:
                ShowWindow(hDlg, SW_HIDE);
                return (INT_PTR)TRUE;

            case IDC_BUTTON_HELP:
                /* Unimplemented */
                MessageBox(hDlg, TEXT("Magnifier help not available yet!"), TEXT("Help"), MB_OK);
                break;

            case IDC_ZOOM:
                if (HIWORD(wParam) == CBN_SELCHANGE)
                {
                    HWND hCombo = GetDlgItem(hDlg,IDC_ZOOM);
                    TCHAR currentZoomValue[2] = TEXT("");

                    /* Get index of current selection and the text of that selection */
                    int currentSelectionIndex = ComboBox_GetCurSel(hCombo);
                    ComboBox_GetLBText(hCombo, currentSelectionIndex, currentZoomValue);
                    uiZoom = (UINT)_ttoi(currentZoomValue);
                    /* The zoom factor cannot be smaller than 1 (and not zero) or greater than 9 */
                    if (uiZoom < 1 || uiZoom > 9)
                        uiZoom = 1;

                    /* Trigger the Draw function to rezoom (which will be set false automatically after rezooming) */
                    bRecreateOffscreenDC = TRUE;

                    /* Update the magnifier UI */
                    Refresh();
                }
                break;

            case IDC_INVERTCOLORSCHECK:
                bInvertColors = IsDlgButtonChecked(hDlg, IDC_INVERTCOLORSCHECK);
                Refresh();
                break;
            case IDC_FOLLOWMOUSECHECK:
                bFollowMouse = IsDlgButtonChecked(hDlg, IDC_FOLLOWMOUSECHECK);
                break;
            case IDC_FOLLOWKEYBOARDCHECK:
                bFollowFocus = IsDlgButtonChecked(hDlg, IDC_FOLLOWKEYBOARDCHECK);
                break;
            case IDC_FOLLOWTEXTEDITINGCHECK:
                bFollowCaret = IsDlgButtonChecked(hDlg, IDC_FOLLOWTEXTEDITINGCHECK);
                break;
            case IDC_STARTMINIMIZEDCHECK:
                bStartMinimized = IsDlgButtonChecked(hDlg, IDC_STARTMINIMIZEDCHECK);
                break;
            case IDC_SHOWMAGNIFIER:
                bShowMagnifier = IsDlgButtonChecked(hDlg, IDC_SHOWMAGNIFIERCHECK);
                ShowWindow(hMainWnd, (bShowMagnifier ? SW_SHOW : SW_HIDE));
                break;
        }
    }

    return (INT_PTR)FALSE;
}

INT_PTR CALLBACK WarningProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);

    switch (message)
    {
        case WM_INITDIALOG:
            return (INT_PTR)TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam))
            {
                case IDC_SHOWWARNINGCHECK:
                    bShowWarning = !IsDlgButtonChecked(hDlg, IDC_SHOWWARNINGCHECK);
                    break;
            }
            if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
            {
                EndDialog(hDlg, LOWORD(wParam));
                return (INT_PTR)TRUE;
            }
            break;
    }

    return (INT_PTR)FALSE;
}
