/*
 * PROJECT:     ReactOS Timedate Control Panel
 * LICENSE:     GPL - See COPYING in the top level directory
 * FILE:        dll/cpl/timedate/monthcal.c
 * PURPOSE:     Calander implementation
 * COPYRIGHT:   Copyright 2006 Thomas Weidenmueller <w3seek@reactos.com>
 *
 */

#include "timedate.h"

static const WCHAR szMonthCalWndClass[] = L"MonthCalWnd";

#define MONTHCAL_HEADERBG   COLOR_INACTIVECAPTION
#define MONTHCAL_HEADERFG   COLOR_INACTIVECAPTIONTEXT
#define MONTHCAL_CTRLBG     COLOR_WINDOW
#define MONTHCAL_CTRLFG     COLOR_WINDOWTEXT
#define MONTHCAL_SELBG      COLOR_ACTIVECAPTION
#define MONTHCAL_SELFG      COLOR_CAPTIONTEXT
#define MONTHCAL_DISABLED_HEADERBG  COLOR_INACTIVECAPTION
#define MONTHCAL_DISABLED_HEADERFG  COLOR_INACTIVECAPTIONTEXT
#define MONTHCAL_DISABLED_CTRLBG    COLOR_WINDOW
#define MONTHCAL_DISABLED_CTRLFG    COLOR_WINDOWTEXT
#define MONTHCAL_DISABLED_SELBG     COLOR_INACTIVECAPTION
#define MONTHCAL_DISABLED_SELFG     COLOR_INACTIVECAPTIONTEXT

#define ID_DAYTIMER 1

typedef struct _MONTHCALWND
{
    HWND hSelf;
    HWND hNotify;
    WORD Day;
    WORD Month;
    WORD Year;
    WORD FirstDayOfWeek;
    BYTE Days[6][7];
    WCHAR Week[7];
    SIZE CellSize;
    SIZE ClientSize;

    HFONT hFont;
    HBRUSH hbHeader;
    HBRUSH hbSelection;

    DWORD UIState;
    UINT Changed : 1;
    UINT DayTimerSet : 1;
    UINT Enabled : 1;
    UINT HasFocus : 1;
} MONTHCALWND, *PMONTHCALWND;

static LRESULT
MonthCalNotifyControlParent(IN PMONTHCALWND infoPtr,
                            IN UINT code,
                            IN OUT PVOID data)
{
    LRESULT Ret = 0;

    if (infoPtr->hNotify != NULL)
    {
        LPNMHDR pnmh = (LPNMHDR)data;

        pnmh->hwndFrom = infoPtr->hSelf;
        pnmh->idFrom = GetWindowLongPtrW(infoPtr->hSelf,
                                         GWLP_ID);
        pnmh->code = code;

        Ret = SendMessageW(infoPtr->hNotify,
                           WM_NOTIFY,
                           (WPARAM)pnmh->idFrom,
                           (LPARAM)pnmh);
    }

    return Ret;
}

/*
 * For the year range 1..9999
 * return 1 if is leap year otherwise 0
 */
static WORD LeapYear(IN WORD Year)
{
	return 
#ifdef WITH_1752
		(Year <= 1752) ? !(Year % 4) :
#endif	       
		!(Year % 4) && ((Year % 100) || !(Year % 400));
}

static WORD
MonthCalMonthLength(IN WORD Month,
                    IN WORD Year)
{
    const BYTE MonthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};

    if(Month == 2)
        return MonthDays[Month - 1] + LeapYear(Year);
    else
    {
#ifdef WITH_1752
        if ((Year == 1752) && (Month == 9))
	   return 19; // Special case: September 1752 has no 3rd-13th
	else
#endif
     	   return MonthDays[Month - 1];
    }
}

static WORD
MonthCalWeekInMonth(IN WORD Day,
                    IN WORD DayOfWeek)
{
    return (Day - DayOfWeek + 5) / 7;
}

static WORD
MonthCalDayOfWeek(IN PMONTHCALWND infoPtr,
                  IN WORD Day,
                  IN WORD Month,
                  IN WORD Year)
{
    const BYTE DayOfWeek[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
    WORD Ret;

    Year -= (Month < 3);
    Ret = (Year + (Year / 4) - (Year / 100) + (Year / 400) + DayOfWeek[Month - 1] + Day + 6) % 7;

    return (7 + Ret - infoPtr->FirstDayOfWeek) % 7;
}

static WORD
MonthCalFirstDayOfWeek(VOID)
{
    WCHAR szBuf[2] = {0};
    WORD Ret = 0;

    if (GetLocaleInfoW(LOCALE_USER_DEFAULT,
                       LOCALE_IFIRSTDAYOFWEEK,
                       szBuf,
                       sizeof(szBuf) / sizeof(szBuf[0])) != 0)
    {
        Ret = (WORD)(szBuf[0] - TEXT('0'));
    }

    return Ret;
}

static BOOL
MonthCalValidDate(IN WORD Day,
                  IN WORD Month,
                  IN WORD Year)
{
    if (Month < 1 || Month > 12 ||
        Day == 0 || Day > MonthCalMonthLength(Month,
                                              Year) ||
        Year < 1899 || Year > 9999)
    {
        return FALSE;
    }

    return TRUE;
}

static VOID
MonthCalUpdate(IN PMONTHCALWND infoPtr)
{
    PBYTE pDay, pDayEnd;
    WORD DayOfWeek, MonthLength, d = 0;
    SIZE NewCellSize;
    BOOL RepaintHeader = FALSE;

    NewCellSize.cx = infoPtr->ClientSize.cx / 7;
    NewCellSize.cy = infoPtr->ClientSize.cy / 7;

    if (infoPtr->CellSize.cx != NewCellSize.cx ||
        infoPtr->CellSize.cy != NewCellSize.cy)
    {
        infoPtr->CellSize = NewCellSize;
        RepaintHeader = TRUE;
    }

    /* Update the days layout of the current month */
    ZeroMemory(infoPtr->Days,
               sizeof(infoPtr->Days));

    DayOfWeek = MonthCalDayOfWeek(infoPtr,
                                  1,
                                  infoPtr->Month,
                                  infoPtr->Year);

    MonthLength = MonthCalMonthLength(infoPtr->Month,
                                      infoPtr->Year);

    pDay = &infoPtr->Days[0][DayOfWeek];
    pDayEnd = pDay + MonthLength;
    while (pDay != pDayEnd)
    {
        *(pDay++) = (BYTE)++d;
    }

    /* Repaint the control */
    if (RepaintHeader)
    {
        InvalidateRect(infoPtr->hSelf,
                       NULL,
                       TRUE);
    }
    else
    {
        RECT rcClient;

        rcClient.left = 0;
        rcClient.top = infoPtr->CellSize.cy;
        rcClient.right = infoPtr->ClientSize.cx;
        rcClient.bottom = infoPtr->ClientSize.cy;

        InvalidateRect(infoPtr->hSelf,
                       &rcClient,
                       TRUE);
    }
}

static VOID
MonthCalSetupDayTimer(IN PMONTHCALWND infoPtr)
{
    SYSTEMTIME LocalTime = {0};
    UINT uElapse;

    /* Update the current date */
    GetLocalTime(&LocalTime);

    /* Calculate the number of remaining milliseconds until midnight */
    uElapse = 1000 - (UINT)LocalTime.wMilliseconds;
    uElapse += (59 - (UINT)LocalTime.wSecond) * 1000;
    uElapse += (59 - (UINT)LocalTime.wMinute) * 60 * 1000;
    uElapse += (23 - (UINT)LocalTime.wHour) * 60 * 60 * 1000;

    if (uElapse < USER_TIMER_MINIMUM || uElapse > USER_TIMER_MAXIMUM)
        uElapse = 1000;
    else
        uElapse += 100; /* Add a delay of 0.1 seconds */

    /* Setup the new timer */
    if (SetTimer(infoPtr->hSelf,
                 ID_DAYTIMER,
                 uElapse,
                 NULL) != 0)
    {
        infoPtr->DayTimerSet = TRUE;
    }
}

static VOID
MonthCalReload(IN PMONTHCALWND infoPtr)
{
    WCHAR szBuf[64];
    UINT i;

    infoPtr->UIState = (DWORD)SendMessageW(GetAncestor(infoPtr->hSelf,
                                                       GA_PARENT),
                                            WM_QUERYUISTATE,
                                            0,
                                            0);

    /* Cache the configuration */
    infoPtr->FirstDayOfWeek = MonthCalFirstDayOfWeek();

    infoPtr->hbHeader = GetSysColorBrush(infoPtr->Enabled ? MONTHCAL_HEADERBG : MONTHCAL_DISABLED_HEADERBG);
    infoPtr->hbSelection = GetSysColorBrush(infoPtr->Enabled ? MONTHCAL_SELBG : MONTHCAL_DISABLED_SELBG);

    for (i = 0; i < 7; i++)
    {
        if (GetLocaleInfoW(LOCALE_USER_DEFAULT,
                           LOCALE_SABBREVDAYNAME1 +
                               ((i + infoPtr->FirstDayOfWeek) % 7),
                           szBuf,
                           sizeof(szBuf) / sizeof(szBuf[0])) != 0)
        {
            infoPtr->Week[i] = szBuf[0];
        }
    }

    /* Update the control */
    MonthCalUpdate(infoPtr);
}

static BOOL
MonthCalGetDayRect(IN PMONTHCALWND infoPtr,
                   IN WORD Day,
                   OUT RECT *rcCell)
{
    if (Day >= 1 && Day <= MonthCalMonthLength(infoPtr->Month,
                                               infoPtr->Year))
    {
        WORD DayOfWeek;

        DayOfWeek = MonthCalDayOfWeek(infoPtr,
                                      Day,
                                      infoPtr->Month,
                                      infoPtr->Year);

        rcCell->left = DayOfWeek * infoPtr->CellSize.cx;
        rcCell->top = (MonthCalWeekInMonth(Day,
                                           DayOfWeek) + 1) * infoPtr->CellSize.cy;
        rcCell->right = rcCell->left + infoPtr->CellSize.cx;
        rcCell->bottom = rcCell->top + infoPtr->CellSize.cy;

        return TRUE;
    }

    return FALSE;
}

static VOID
MonthCalChange(IN PMONTHCALWND infoPtr)
{
    infoPtr->Changed = TRUE;

    /* Kill the day timer */
    if (infoPtr->DayTimerSet)
    {
        KillTimer(infoPtr->hSelf,
                  ID_DAYTIMER);
        infoPtr->DayTimerSet = FALSE;
    }
}


static BOOL
MonthCalSetDate(IN PMONTHCALWND infoPtr,
                IN WORD Day,
                IN WORD Month,
                IN WORD Year)
{
    NMMCCSELCHANGE sc;
    BOOL Ret = FALSE;

    sc.OldDay = infoPtr->Day;
    sc.OldMonth = infoPtr->Month;
    sc.OldYear = infoPtr->Year;
    sc.NewDay = Day;
    sc.NewMonth = Month;
    sc.NewYear = Year;

    /* Notify the parent */
    if (!MonthCalNotifyControlParent(infoPtr,
                                     MCCN_SELCHANGE,
                                     &sc))
    {
        /* Check if we actually need to update */
        if (infoPtr->Month != sc.NewMonth ||
            infoPtr->Year != sc.NewYear)
        {
            infoPtr->Day = sc.NewDay;
            infoPtr->Month = sc.NewMonth;
            infoPtr->Year = sc.NewYear;

            MonthCalChange(infoPtr);

            /* Repaint the entire control */
            MonthCalUpdate(infoPtr);

            Ret = TRUE;
        }
        else if (infoPtr->Day != sc.NewDay)
        {
            RECT rcUpdate;

            infoPtr->Day = sc.NewDay;

            MonthCalChange(infoPtr);

            if (MonthCalGetDayRect(infoPtr,
                                   sc.OldDay,
                                   &rcUpdate))
            {
                /* Repaint the day cells that need to be updated */
                InvalidateRect(infoPtr->hSelf,
                               &rcUpdate,
                               TRUE);
                if (MonthCalGetDayRect(infoPtr,
                                       sc.NewDay,
                                       &rcUpdate))
                {
                    InvalidateRect(infoPtr->hSelf,
                                   &rcUpdate,
                                   TRUE);
                }
            }

            Ret = TRUE;
        }
    }

    return Ret;
}

static VOID
MonthCalSetLocalTime(IN PMONTHCALWND infoPtr,
                     OUT SYSTEMTIME *Time)
{
    NMMCCAUTOUPDATE au;
    SYSTEMTIME LocalTime = {0};

    GetLocalTime(&LocalTime);

    au.SystemTime = LocalTime;
    if (!MonthCalNotifyControlParent(infoPtr,
                                     MCCN_AUTOUPDATE,
                                     &au))
    {
        if (MonthCalSetDate(infoPtr,
                            LocalTime.wDay,
                            LocalTime.wMonth,
                            LocalTime.wYear))
        {
            infoPtr->Changed = FALSE;
        }
    }

    /* Kill the day timer */
    if (infoPtr->DayTimerSet)
    {
        KillTimer(infoPtr->hSelf,
                  ID_DAYTIMER);
        infoPtr->DayTimerSet = FALSE;
    }

    /* Setup the new day timer */
    MonthCalSetupDayTimer(infoPtr);

    if (Time != NULL)
    {
        *Time = LocalTime;
    }
}

static VOID
MonthCalRepaintDay(IN PMONTHCALWND infoPtr,
                   IN WORD Day)
{
    RECT rcCell;

    if (MonthCalGetDayRect(infoPtr,
                           Day,
                           &rcCell))
    {
        InvalidateRect(infoPtr->hSelf,
                       &rcCell,
                       TRUE);
    }
}

static VOID
MonthCalPaint(IN PMONTHCALWND infoPtr,
              IN HDC hDC,
              IN LPRECT prcUpdate)
{
    LONG x, y;
    RECT rcCell;
    COLORREF crOldText, crOldCtrlText = CLR_INVALID;
    HFONT hOldFont;
    INT iOldBkMode;

#if MONTHCAL_CTRLBG != MONTHCAL_DISABLED_CTRLBG
    if (!infoPtr->Enabled)
    {
        FillRect(hDC,
                 prcUpdate,
                 GetSysColorBrush(MONTHCAL_DISABLED_CTRLBG));
    }
#endif

    iOldBkMode = SetBkMode(hDC,
                           TRANSPARENT);
    hOldFont = (HFONT)SelectObject(hDC,
                                   infoPtr->hFont);

    for (y = prcUpdate->top / infoPtr->CellSize.cy;
         y <= prcUpdate->bottom / infoPtr->CellSize.cy && y < 7;
         y++)
    {
        rcCell.top = y * infoPtr->CellSize.cy;
        rcCell.bottom = rcCell.top + infoPtr->CellSize.cy;

        if (y == 0)
        {
            RECT rcHeader;

            /* Paint the header */
            rcHeader.left = prcUpdate->left;
            rcHeader.top = rcCell.top;
            rcHeader.right = prcUpdate->right;
            rcHeader.bottom = rcCell.bottom;

            FillRect(hDC,
                     &rcHeader,
                     infoPtr->hbHeader);

            crOldText = SetTextColor(hDC,
                                     GetSysColor(infoPtr->Enabled ? MONTHCAL_HEADERFG : MONTHCAL_DISABLED_HEADERFG));

            for (x = prcUpdate->left / infoPtr->CellSize.cx;
                 x <= prcUpdate->right / infoPtr->CellSize.cx && x < 7;
                 x++)
            {
                rcCell.left = x * infoPtr->CellSize.cx;
                rcCell.right = rcCell.left + infoPtr->CellSize.cx;

                /* Write the first letter of each weekday */
                DrawTextW(hDC,
                          &infoPtr->Week[x],
                          1,
                          &rcCell,
                          DT_SINGLELINE | DT_NOPREFIX | DT_CENTER | DT_VCENTER);
            }

            SetTextColor(hDC,
                         crOldText);
        }
        else
        {
            if (crOldCtrlText == CLR_INVALID)
            {
                crOldCtrlText = SetTextColor(hDC,
                                             GetSysColor(infoPtr->Enabled ? MONTHCAL_CTRLFG : MONTHCAL_DISABLED_CTRLFG));
            }

            for (x = prcUpdate->left / infoPtr->CellSize.cx;
                 x <= prcUpdate->right / infoPtr->CellSize.cx && x < 7;
                 x++)
            {
                UINT Day = infoPtr->Days[y - 1][x];

                rcCell.left = x * infoPtr->CellSize.cx;
                rcCell.right = rcCell.left + infoPtr->CellSize.cx;

                /* Write the day number */
                if (Day != 0 && Day < 100)
                {
                    WCHAR szDay[3];
                    INT szDayLen;
                    RECT rcText;
                    SIZE TextSize;

                    szDayLen = swprintf(szDay,
                                         L"%lu",
                                         Day);

                    if (GetTextExtentPoint32W(hDC,
                                              szDay,
                                              szDayLen,
                                              &TextSize))
                    {
                        RECT rcHighlight = { 0, 0, 0, 0 };

                        rcText.left = rcCell.left + (infoPtr->CellSize.cx / 2) - (TextSize.cx / 2);
                        rcText.top = rcCell.top + (infoPtr->CellSize.cy / 2) - (TextSize.cy / 2);
                        rcText.right = rcText.left + TextSize.cx;
                        rcText.bottom = rcText.top + TextSize.cy;

                        if (Day == infoPtr->Day)
                        {
                            SIZE TextSel;

                            TextSel.cx = (infoPtr->CellSize.cx * 2) / 3;
                            TextSel.cy = (infoPtr->CellSize.cy * 3) / 4;

                            if (TextSel.cx < rcText.right - rcText.left)
                                TextSel.cx = rcText.right - rcText.left;
                            if (TextSel.cy < rcText.bottom - rcText.top)
                                TextSel.cy = rcText.bottom - rcText.top;

                            rcHighlight.left = rcCell.left + (infoPtr->CellSize.cx / 2) - (TextSel.cx / 2);
                            rcHighlight.right = rcHighlight.left + TextSel.cx;
                            rcHighlight.top = rcCell.top + (infoPtr->CellSize.cy / 2) - (TextSel.cy / 2);
                            rcHighlight.bottom = rcHighlight.top + TextSel.cy;

                            InflateRect(&rcHighlight,
                                        GetSystemMetrics(SM_CXFOCUSBORDER),
                                        GetSystemMetrics(SM_CYFOCUSBORDER));

                            if (!FillRect(hDC,
                                          &rcHighlight,
                                          infoPtr->hbSelection))
                            {
                                goto FailNoHighlight;
                            }

                            /* Highlight the selected day */
                            crOldText = SetTextColor(hDC,
                                                     GetSysColor(infoPtr->Enabled ? MONTHCAL_SELFG : MONTHCAL_DISABLED_SELFG));
                        }
                        else
                        {
FailNoHighlight:
                            /* Don't change the text color, we're not highlighting it... */
                            crOldText = CLR_INVALID;
                        }

                        TextOutW(hDC,
                                 rcText.left,
                                 rcText.top,
                                 szDay,
                                 szDayLen);

                        if (Day == infoPtr->Day && crOldText != CLR_INVALID)
                        {
                            if (infoPtr->HasFocus && infoPtr->Enabled && !(infoPtr->UIState & UISF_HIDEFOCUS))
                            {
                                COLORREF crOldBk;

                                crOldBk = SetBkColor(hDC,
                                                     GetSysColor(infoPtr->Enabled ? MONTHCAL_SELBG : MONTHCAL_DISABLED_SELBG));

                                DrawFocusRect(hDC,
                                              &rcHighlight);

                                SetBkColor(hDC,
                                           crOldBk);
                            }

                            SetTextColor(hDC,
                                         crOldText);
                        }
                    }
                }
            }
        }
    }

    if (crOldCtrlText != CLR_INVALID)
    {
        SetTextColor(hDC,
                     crOldCtrlText);
    }

    SetBkMode(hDC,
              iOldBkMode);
    SelectObject(hDC,
                 (HGDIOBJ)hOldFont);
}

static HFONT
MonthCalChangeFont(IN PMONTHCALWND infoPtr,
                   IN HFONT hFont,
                   IN BOOL Redraw)
{
    HFONT hOldFont = infoPtr->hFont;
    infoPtr->hFont = hFont;

    if (Redraw)
    {
        InvalidateRect(infoPtr->hSelf,
                       NULL,
                       TRUE);
    }

    return hOldFont;
}

static WORD
MonthCalPtToDay(IN PMONTHCALWND infoPtr,
                IN INT x,
                IN INT y)
{
    WORD Ret = 0;

    if (infoPtr->CellSize.cx != 0 && infoPtr->CellSize.cy != 0 &&
        x >= 0 && y >= 0)
    {
        x /= infoPtr->CellSize.cx;
        y /= infoPtr->CellSize.cy;

        if (x < 7 && y != 0 && y < 7)
        {
            Ret = (WORD)infoPtr->Days[y - 1][x];
        }
    }

    return Ret;
}

static LRESULT CALLBACK
MonthCalWndProc(IN HWND hwnd,
                IN UINT uMsg,
                IN WPARAM wParam,
                IN LPARAM lParam)
{
    PMONTHCALWND infoPtr;
    LRESULT Ret = 0;

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

    if (infoPtr == NULL && uMsg != WM_CREATE)
    {
        goto HandleDefaultMessage;
    }

    switch (uMsg)
    {
#if MONTHCAL_CTRLBG != MONTHCAL_DISABLED_CTRLBG
        case WM_ERASEBKGND:
            Ret = !infoPtr->Enabled;
            break;
#endif

        case WM_PAINT:
        case WM_PRINTCLIENT:
        {
            if (infoPtr->CellSize.cx != 0 && infoPtr->CellSize.cy != 0)
            {
                PAINTSTRUCT ps;
                HDC hDC;

                if (wParam != 0)
                {
                    if (!GetUpdateRect(hwnd,
                                       &ps.rcPaint,
                                       TRUE))
                    {
                        break;
                    }
                    hDC = (HDC)wParam;
                }
                else
                {
                    hDC = BeginPaint(hwnd,
                                     &ps);
                    if (hDC == NULL)
                    {
                        break;
                    }
                }

                MonthCalPaint(infoPtr,
                              hDC,
                              &ps.rcPaint);

                if (wParam == 0)
                {
                    EndPaint(hwnd,
                             &ps);
                }
            }
            break;
        }

        case WM_LBUTTONDBLCLK:
        case WM_LBUTTONDOWN:
        {
            WORD SelDay;

            SelDay = MonthCalPtToDay(infoPtr,
                                     GET_X_LPARAM(lParam),
                                     GET_Y_LPARAM(lParam));
            if (SelDay != 0 && SelDay != infoPtr->Day)
            {
                MonthCalSetDate(infoPtr,
                                SelDay,
                                infoPtr->Month,
                                infoPtr->Year);
            }

            /* Fall through */
        }

        case WM_MBUTTONDOWN:
        case WM_RBUTTONDOWN:
        {
            if (!infoPtr->HasFocus)
            {
                SetFocus(hwnd);
            }
            break;
        }

        case WM_KEYDOWN:
        {
            WORD NewDay = 0;

            switch (wParam)
            {
                case VK_UP:
                {
                    if (infoPtr->Day > 7)
                    {
                        NewDay = infoPtr->Day - 7;
                    }
                    break;
                }

                case VK_DOWN:
                {
                    if (infoPtr->Day + 7 <= MonthCalMonthLength(infoPtr->Month,
                                                                infoPtr->Year))
                    {
                        NewDay = infoPtr->Day + 7;
                    }
                    break;
                }

                case VK_LEFT:
                {
                    if (infoPtr->Day > 1)
                    {
                        NewDay = infoPtr->Day - 1;
                    }
                    break;
                }

                case VK_RIGHT:
                {
                    if (infoPtr->Day < MonthCalMonthLength(infoPtr->Month,
                                                           infoPtr->Year))
                    {
                        NewDay = infoPtr->Day + 1;
                    }
                    break;
                }
            }

            /* Update the selection */
            if (NewDay != 0)
            {
                MonthCalSetDate(infoPtr,
                                NewDay,
                                infoPtr->Month,
                                infoPtr->Year);
            }

            goto HandleDefaultMessage;
        }

        case WM_GETDLGCODE:
        {
            INT virtKey;

            virtKey = (lParam != 0 ? (INT)((LPMSG)lParam)->wParam : 0);
            switch (virtKey)
            {
                case VK_TAB:
                {
                    /* Change the UI status */
                    SendMessageW(GetAncestor(hwnd,
                                             GA_PARENT),
                                 WM_CHANGEUISTATE,
                                 MAKEWPARAM(UIS_INITIALIZE,
                                            0),
                                 0);
                    break;
                }
            }

            Ret |= DLGC_WANTARROWS;
            break;
        }

        case WM_SETFOCUS:
        {
            infoPtr->HasFocus = TRUE;
            MonthCalRepaintDay(infoPtr,
                               infoPtr->Day);
            break;
        }

        case WM_KILLFOCUS:
        {
            infoPtr->HasFocus = FALSE;
            MonthCalRepaintDay(infoPtr,
                               infoPtr->Day);
            break;
        }

        case WM_UPDATEUISTATE:
        {
            DWORD OldUIState;

            Ret = DefWindowProcW(hwnd,
                                 uMsg,
                                 wParam,
                                 lParam);

            OldUIState = infoPtr->UIState;
            switch (LOWORD(wParam))
            {
                case UIS_SET:
                    infoPtr->UIState |= HIWORD(wParam);
                    break;

                case UIS_CLEAR:
                    infoPtr->UIState &= ~HIWORD(wParam);
                    break;
            }

            if (infoPtr->UIState != OldUIState)
            {
                MonthCalRepaintDay(infoPtr,
                                   infoPtr->Day);
            }
            break;
        }

        case MCCM_SETDATE:
        {
            WORD Day, Month, Year, DaysCount;

            Day = LOWORD(wParam);
            Month = HIWORD(wParam);
            Year = LOWORD(lParam);

            if (Day == (WORD)-1)
                Day = infoPtr->Day;
            if (Month == (WORD)-1)
                Month = infoPtr->Month;
            if (Year == (WORD)-1)
                Year = infoPtr->Year;

            DaysCount = MonthCalMonthLength(Month,
                                            Year);
            if (Day > DaysCount)
                Day = DaysCount;

            if (MonthCalValidDate(Day,
                                  Month,
                                  Year))
            {
                if (Day != infoPtr->Day ||
                    Month != infoPtr->Month ||
                    Year != infoPtr->Year)
                {
                    Ret = MonthCalSetDate(infoPtr,
                                          Day,
                                          Month,
                                          Year);
                }
            }
            break;
        }

        case MCCM_GETDATE:
        {
            LPSYSTEMTIME lpSystemTime = (LPSYSTEMTIME)wParam;

            lpSystemTime->wYear = infoPtr->Year;
            lpSystemTime->wMonth = infoPtr->Month;
            lpSystemTime->wDay = infoPtr->Day;

            Ret = TRUE;
            break;
        }

        case MCCM_RESET:
        {
            MonthCalSetLocalTime(infoPtr,
                                 NULL);
            Ret = TRUE;
            break;
        }

        case MCCM_CHANGED:
        {
            Ret = infoPtr->Changed;
            break;
        }

        case WM_TIMER:
        {
            switch (wParam)
            {
                case ID_DAYTIMER:
                {
                    /* Kill the timer */
                    KillTimer(hwnd,
                              ID_DAYTIMER);
                    infoPtr->DayTimerSet = FALSE;

                    if (!infoPtr->Changed)
                    {
                        /* Update the system time and setup the new day timer */
                        MonthCalSetLocalTime(infoPtr,
                                             NULL);

                        /* Update the control */
                        MonthCalUpdate(infoPtr);
                    }
                    break;
                }
            }
            break;
        }

        case WM_SETFONT:
        {
            Ret = (LRESULT)MonthCalChangeFont(infoPtr,
                                              (HFONT)wParam,
                                              (BOOL)LOWORD(lParam));
            break;
        }

        case WM_SIZE:
        {
            infoPtr->ClientSize.cx = LOWORD(lParam);
            infoPtr->ClientSize.cy = HIWORD(lParam);
            infoPtr->CellSize.cx = infoPtr->ClientSize.cx / 7;
            infoPtr->CellSize.cy = infoPtr->ClientSize.cy / 7;

            /* Repaint the control */
            InvalidateRect(hwnd,
                           NULL,
                           TRUE);
            break;
        }

        case WM_GETFONT:
        {
            Ret = (LRESULT)infoPtr->hFont;
            break;
        }

        case WM_ENABLE:
        {
            infoPtr->Enabled = ((BOOL)wParam != FALSE);
            MonthCalReload(infoPtr);
            break;
        }

        case WM_STYLECHANGED:
        {
            if (wParam == GWL_STYLE)
            {
                unsigned int OldEnabled = infoPtr->Enabled;
                infoPtr->Enabled = !(((LPSTYLESTRUCT)lParam)->styleNew & WS_DISABLED);

                if (OldEnabled != infoPtr->Enabled)
                {
                    MonthCalReload(infoPtr);
                }
            }
            break;
        }

        case WM_CREATE:
        {
            infoPtr = (MONTHCALWND*) HeapAlloc(GetProcessHeap(),
                                0,
                                sizeof(MONTHCALWND));
            if (infoPtr == NULL)
            {
                Ret = (LRESULT)-1;
                break;
            }

            SetWindowLongPtrW(hwnd,
                              0,
                              (LONG_PTR)infoPtr);

            ZeroMemory(infoPtr,
                       sizeof(MONTHCALWND));

            infoPtr->hSelf = hwnd;
            infoPtr->hNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
            infoPtr->Enabled = !(((LPCREATESTRUCTW)lParam)->style & WS_DISABLED);

            MonthCalSetLocalTime(infoPtr,
                                 NULL);

            MonthCalReload(infoPtr);
            break;
        }

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

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

    return Ret;
}

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

    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = MonthCalWndProc;
    wc.cbWndExtra = sizeof(PMONTHCALWND);
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursorW(NULL,
                             (LPWSTR)IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(MONTHCAL_CTRLBG + 1);
    wc.lpszClassName = szMonthCalWndClass;

    return RegisterClassW(&wc) != 0;
}

VOID
UnregisterMonthCalControl(IN HINSTANCE hInstance)
{
    UnregisterClassW(szMonthCalWndClass,
                     hInstance);
}
