/*
 *   Windows Calendar
 *   Copyright (c) 1985 by Microsoft Corporation, all rights reserved.
 *   Written by Mark L. Chamberlin, consultant to Microsoft.
 *
 ***** calmain.c - small segment containing main loop and caltimer stuff
 *
 */

#include "cal.h"


/**** WinMain ****/

MMain(hInstance, hPrevInstance, lpszCmdLine, cmdShow)
/* { */
    MSG  msg;


    if (!CalInit (hInstance, hPrevInstance, lpszCmdLine, cmdShow))
         return (FALSE);

        // OK to process WM_ACTIVATE from now onwards
        fInitComplete = TRUE;

    while (GetMessage (&msg, NULL, 0, 0))
        {
        /* Filter the special keys BEFORE calling translate message.
         * This way, the WM_KEYDOWN messages will get trapped before
         * the WM_CHAR messages are created so we need not worry about
         * trapping the latter messages.
         */
        if (!FKeyFiltered (&msg))
            {
            if (TranslateAccelerator (vhwnd0, vhAccel, &msg) == 0)
                {
                TranslateMessage (&msg);
                DispatchMessage (&msg);
                }
            }
        }

    return (int)msg.wParam;
    }




/**** FKeyFiltered - return TRUE if the key has been filtered. ****/

BOOL APIENTRY FKeyFiltered (MSG *pmsg)
    {
    register WPARAM wParam;

    wParam = pmsg -> wParam;

    /* Handle TIMER message here so we don't pull in another segment. */
    if (pmsg -> message == WM_TIMER)
       {
       CalTimer(FALSE);
       return TRUE;
       }

    /* Look for key down messages going to the edit controls.
       Karl Stock says there is no need to filter out the
       key up messages, and we will not call TranslateMessage
       for the filtered keys so there will be no WM_CHAR messages
       to filter.
    */
    if (pmsg -> message == WM_KEYDOWN)
        {
        if (pmsg -> hwnd == vhwnd2C)
            {
            /* In the notes area.  Tab means leave the notes area. */
            if (wParam == VK_TAB)
                {
                /* Leave the notes area. */
                if (!vfDayMode)
                    {
                    /* Give the focus to the monthly calendar. */
                    CalSetFocus (vhwnd2B);
                    }
                else
                    {
                    /* In day mode - give focus to the appointment
                       description edit control.
                    */
                    CalSetFocus (vhwnd3);
                    }
                return (TRUE);
                }
            return (FALSE);
            }

        else if (vfDayMode)
            {
            switch (wParam)
                {
                case VK_RETURN:
                case VK_DOWN:
                    /* If on last appointment, scroll up one appoinment.
                     * If not on last appointment in window, change
                     * focus to next appointment in window.
                     */
                    if (vlnCur == vlnLast)
                         ScrollUpDay (1, FALSE);
                    else
                         SetQdEc (vlnCur + 1);

                    break;

                case VK_UP:
                    /* If on first appointment in window, scroll down
                     * one appointment.
                     * If not on first appointment in window, change
                     * focus to previous appointment in window.
                     */
                    if (vlnCur == 0)
                        ScrollDownDay (1, FALSE, FALSE);
                    else
                        SetQdEc (vlnCur-1);

                    break;

                case VK_NEXT:
                case VK_PRIOR:
                    if (GetKeyState (VK_CONTROL) < 0)
                        {
                        /* Control Pg Up and Control Pg Dn are
                         * the accelerators for Show Previous and
                         * Show Next.  We want TranslateAccelerator
                         * to see them, so return FALSE.
                         */
                        return (FALSE);
                        }

                    /* Translate into a scroll command (as if area
                     * below or above thumb had been clicked).
                     */
                    SendMessage(vhwnd2B, WM_VSCROLL,
                                wParam==VK_NEXT ? SB_PAGEDOWN : SB_PAGEUP, 0L);
                    break;

                case VK_TAB:
                    /* Switch to the notes area. */
                    CalSetFocus (vhwnd2C);
                    break;

                default:
                    return (FALSE);
                }
            return (TRUE);
            }
        }

    return (FALSE);
    }


/**** CalTimer ****/

VOID APIENTRY CalTimer (BOOL fAdjust)
    {
    HDC  hDC;
    D3   d3New;
    DT   dtNew;
    TM   tmNew;
    FT   ftPrev;

    if (vfFlashing)
         FlashWindow (vhwnd0, TRUE);

    if (vcAlarmBeeps != 0)
         {
         MessageBeep (ALARMBEEP);
         vcAlarmBeeps--;
         }

    /* Fetch the date and time. */
    ReadClock (&d3New, &tmNew);

    /* See if the time or date has changed.  Note that it's necessary
     * to check all parts in order to immediartely detect all changes.
     * (I used to just check the time, but that meant a date change was
     * not detected until the minute changed.)
     */

    if (tmNew != vftCur.tm || d3New.wMonth != vd3Cur.wMonth
         || d3New.wDay != vd3Cur.wDay || d3New.wYear != vd3Cur.wYear)
         {
         /* Remember the old date and time */
         ftPrev = vftCur;
         vftCur.tm = tmNew;

         /* Show new date/time only if not iconic */
         if (!IsIconic(vhwnd0))
           {
           hDC = CalGetDC (vhwnd2A);
           DispTime (hDC);

           if ((dtNew = DtFromPd3 (&d3New)) != vftCur.dt)
                {
                vftCur.dt = dtNew;
                vd3Cur = d3New;
                if (!vfDayMode)
                     {
                     /* Display the new date. */
                     DispDate (hDC, &vd3Cur);

                     /* If the old or new date is in the
                        month currently being displayed, redisplay to get rid of
                        the >< on the old date.
                        Also, if the new date is in the month being displayed,
                        it will get marked with the >< as a result.
                     */
                     if ((vd3Cur.wMonth == vd3Sel.wMonth && vd3Cur.wYear ==
                      vd3Sel.wYear) || (d3New.wMonth == vd3Sel.wMonth
                      && d3New.wYear == vd3Sel.wYear))
                           {
                           /* Note - neither vcDaysMonth nor vwDaySticky
                              has changed, so UpdateMonth will end up selecting
                              the same day that's currently selected (which
                              is what we want).
                           */
                           vd3To = vd3Sel;
                           UpdateMonth ();
                           }
                     }
                }

           ReleaseDC (vhwnd2A, hDC);
           }

         /* If the new date/time is less than the previous one, or the
            new one is a day (1440 minutes) or more greater than the
            previous one, we want to resynchronize the next alarm.
            Obviously, if the date/time is less than the previouse one,
            the system clock has been adjusted (except in the case
            where it wraps on December 31, 2099, which I am not worried
            about).  However, it is not obvious when the clock has been
            set forward.  For example, if the user is running Calendar
            and then switches to an old application that grabs the
            whole machine (.g., Lotus 123), Calendar will not get
            timer messages while the olf app is running.  It is completely
            reasonable to expect that the user may not return to Windows
            for a long time (on the order of hours), so we only assume
            the clock has been set forward if it changes by a day or
            more (1440 minutes).  We don't want to make this period
            too great either since if we don't think the clock has been
            set ahead, we will put all the alarms that have passed into
            the alarm acknowledgement listbox.  In fact, avoiding this
            was the main reason for detecting clock adjustments.  Without
            setting the date/time on a machine without a hardware clock,
            the date/time would start out back in January, 1980.  If he
            then noticed the date was wrong and set it, all the alarms
            since January 1980 would be put into the listbox, which is
            not only rediculous, but could take a long time to read
            the disk for a bunch of old dates.  With the one day adjustment
            period, this is no longer a problem, because we ignore alarms
            that go off due to a forward clock adjustment.
            Note - do not set vfMustSyncAlarm FALSE in any case since
            it may already be TRUE and hasn't been serviced yet
            (because uProcessAlarms is locked).
         */

         /* If there is no NextAlarm present, then we dont have to resync
          * any alaram at all;
          * Fix for Bug #6196 --SANKAR-- 11-14-89
          */
         if (fAdjust && CompareFt (&vftCur, &ftPrev) != 0
              && vftAlarmNext.dt != DTNIL)
             {
             /* The clock has been adjusted - set flag to tell
                uProcessAlarms we want to resync, and force the
                call to AlarmCheck (below) to trigger an alarm
                immediately by setting the alarm time to the current
                time.
              */

             vfMustSyncAlarm=TRUE;
             vftAlarmNext=vftCur;
             }

         /* See if it's time to trigger the alarm (also handle
            resynchronization).
         */
         AlarmCheck ();
         }
    }


/**** AlarmCheck ****/

VOID APIENTRY AlarmCheck ()
     {

     FT   ftTemp;

     /* If the current time plus the early ring period is greater than or
        equal to the next alarm time, trigger the alarm.
     */
     ftTemp = vftCur;
     AddMinsToFt (&ftTemp, vcMinEarlyRing);
     if (CompareFt (&ftTemp, &vftAlarmNext) > -1)
          {
          /* Sound the alarm if sound is enabled.  Give the first beep
             right now and the rest at one second intervals in the timer
             message routine.
          */
          if (vfSound)
               {
               MessageBeep (ALARMBEEP);
               vcAlarmBeeps = CALARMBEEPS - 1;
               }

          if (vftAlarmFirst.dt == DTNIL)
               {
               /* This is the first unacknowledged alarm - remember it. */
               vftAlarmFirst = vftAlarmNext;

               if (GetActiveWindow () == vhwnd0)
                    {
                    /* We are the active window, so process the alarm now. */
                    uProcessAlarms ();
                    return;
                    }

               /* Not the active window, so fall through. */
               }

          /* Let the user know there are unacknowledged alarms. */
          StartStopFlash (TRUE);

          /* The next alarm is the first one > the one that just went off.
             GetNextAlarm looks for >=, so add one minute.
             Do not go to the disk - only arm the next alarm if it's in
             memory.  Note that this is absolutely necessary in the case
             where we don't have the focus (the user is doing something
             else, so it would be rude to start spinning the the disk and
             possibly asking for the correct floppy to be inserted).  In
             the case where we are active but the alarm acknowledgement
             dialog is already up, it would actually be OK to go to the disk,
             but I have decided it would be too confusing for the user if
             a disk I/O error were to occur at this point.
          */
          ftTemp = vftAlarmNext;
          AddMinsToFt (&ftTemp, 1);
          GetNextAlarm (&vftAlarmNext, &ftTemp, FALSE, (HWND)NULL);
          }
     }


/**** AddMinsToFt ****/

VOID APIENTRY AddMinsToFt (
    FT   *pft,
    UINT cMin)          /* Not to exceed the minutes in one day (1440). */
    {
    /* Add cMin to the time.  Note that the highest legitimate
       TM and the largest cMin cannot overflow a WORD, which
       is what a TM is, so we needn't worry about overflow here.
    */
    if ((pft -> tm += (TM)cMin) > TMLAST)
         {
         /* The time wrapped into the next day.  Adjust down the time,
            and increment the day.  If the date goes beyond DTLAST (that
            of December 31, 2099, the value should still be OK for the
            caller since it will only be used for comparison purposes.
            Anyway, I will be dead when that case comes up, so if it doesn't
            work correctly, it won't be my problem.
         */
         pft -> tm -= TMLAST + 1;
         pft -> dt++;
         }
    }



/**** CompareFt - compare the two FTs returning:
      -1 iff ft1 < ft2
      0  iff ft1 = ft2
      +1 iff ft1 > ft2
****/

INT  APIENTRY CompareFt (
    FT   *pft1,
    FT   *pft2)
    {
    register FT *pft1Temp;
    register FT *pft2Temp;

    if ((pft1Temp = pft1) -> dt < (pft2Temp = pft2) -> dt)
         return (-1);

    if (pft1Temp -> dt > pft2Temp -> dt)
         return (1);

    /* DTs are equal, compare the TMs. */
    if (pft1Temp -> tm < pft2Temp -> tm)
         return (-1);

    if (pft1Temp -> tm > pft2Temp -> tm)
         return (1);

    return (0);
    }