/*
 *  Pundit.c
 *      Source for Pundit
 *
 *  Author:         Jeff Bienstadt
 *
 *  Environment:
 *
 *      Run-Time:   Microsoft Windows 3.0
 *
 *      Compilers/Tools:
 *                  Microsoft C 6.0
 *                  Microsoft Windows SDK 3.0
 *
 */

#include    <windows.h>     // All the Windows goodies
#include    <stdlib.h>      // for rand() and srand()
#include    <stdio.h>       // for sprintf()
#include    <time.h>        // for time()
#include    "pundit.h"      // constants for Pundit

//  The structure for all the settings
typedef struct _settings {
    int         insecs,     // time window is shown (in seconds)
                outsecs,    // time window is gone  (in seconds)
                x,          // X position of window (in device units)
                y,          // Y position of window (in device units)
                width,      // width of window      (in device units)
                height;     // height of window     (in device units)
} SETTINGS;

//  Function Prototypes
long FAR PASCAL     WndProc(HWND, WORD, WORD, LONG);
BOOL FAR PASCAL     Settings(HWND, unsigned, WORD, LONG);
BOOL FAR PASCAL     About(HWND, unsigned, WORD, LONG);
void                FetchSettings(void);
void                SaveSettings(HWND);
void                InitSettings(HWND);
void                InitGlobals(HANDLE);


//  Global variables
int     count;          // counts seconds window is hidden/visible

WORD    string_number;  // store ID from string table here

BOOL    inout_state,    // are we shown or hidden?
        paused;         // ...... paused or couning?

HANDLE  hinst;          // a global Instance Handle

HWND    jkbPundit;      // a global Window Handle (for main window)

SETTINGS    settings;   // the settings structure

char    szAppName[] = "jkbPundit";  // The (internal) Application Name


//  The main Window Procedure
int PASCAL WinMain(hInstance, hPrevInstance, lpszCmdLine, nCmdShow)
HANDLE  hInstance,      // Instance Handle
        hPrevInstance;  // Previous Instance Handle
LPSTR   lpszCmdLine;    // The Command Line (we don't use it here)
int     nCmdShow;       // The SHOW Method  (we don't use this, either)
{
    MSG             msg;        // a place to store Windows' Messages
    WNDCLASS        wndclass;   // a Window Class structure

    //  If we have NOT already defined our Window Class, do so now...
    //  (see the SDK documentation for how this works)
    if (!hPrevInstance) {
        wndclass.style          = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc    = WndProc;
        wndclass.cbClsExtra     = 0;
//        wndclass.cbWndExtra     = DLGWINDOWEXTRA;
        wndclass.cbWndExtra     = 0;
        wndclass.hInstance      = hInstance;
        wndclass.hIcon          = LoadIcon(hInstance, szAppName);
        wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wndclass.hbrBackground  = COLOR_WINDOW+1;
        wndclass.lpszMenuName   = szAppName;
        wndclass.lpszClassName  = szAppName;

        RegisterClass(&wndclass);   // Register the Window Class
    }

    InitGlobals(hInstance);     // Initialize Global Varaibles;

    // Create the Window, and put it's handle into our global Window Handle
    jkbPundit = CreateWindow(szAppName,              // Window Name
                             "Pundit: "
                             "Quote of the Moment",  // Window Caption
                             WS_OVERLAPPED |         // an Overlapped Window...
                                 WS_CAPTION |        //    with a Caption,
                                 WS_THICKFRAME |     //    a Sizeable Frame,
                                 WS_SYSMENU,         //    and a System Menu
                             settings.x,             // X position of Window
                             settings.y,             // Y position of Window
                             settings.width,         // Width of Window
                             settings.height,        // Height of Window
                             NULL,                   // No Parent
                             NULL,                   // Use the Class menu
                             hInstance,              // Instance for Window
                             NULL);                  // No additional params

    //  Try to get a timer... keep trying until we get one or the user gives up
    while (!SetTimer(jkbPundit, ID_TIMER, 1000, NULL)) {
        if (IDCANCEL == MessageBox(jkbPundit,
                                   "I need a Timer, but Windows won't lemme"
                                        "have one\012"
                                        "(maybe you could close a clock"
                                        "or something...)",
                                   szAppName,
                                   MB_ICONEXCLAMATION | MB_RETRYCANCEL))
            return FALSE;   // User gave up, bail out
    }

    ShowWindow(jkbPundit, SW_SHOWNORMAL);   // Display our Window
    UpdateWindow(jkbPundit);                // Update our Window
    while (GetMessage(&msg, NULL, 0, 0)) {  // Fetch a Message from Windows
        TranslateMessage(&msg);     // Translate Keyboard Messages
        DispatchMessage(&msg);      // Pass Message on to our callback function
    }                           // until we get a QUIT Message
    return msg.wParam;          // return to Windows
}   // end of WinMain


//  Initialize Global Variables
void InitGlobals(hInstance)
HANDLE  hInstance;  // our Instance Handle
{
    time_t          ltime;      // here we store the system time

    settings.insecs  =   5;     // "Default" IN seconds
    settings.outsecs =  20;     // "Default" OUT seconds
    settings.x       =   1;     // "Default" X position
    settings.y       =   1;     // "Default" Y position
    settings.width   = 250;     // "Default" Width
    settings.height  = 150;     // "Default" Height
    FetchSettings();            // Now read settings from WIN.INI

    count            =   0;     // Zero out the counter
    paused           = FALSE;   // We're not paused yet
    inout_state      = TRUE;    // We're in the "show" state
    hinst            = hInstance;   // Save our current Instance

    time(&ltime);               // Get the current time
    srand((unsigned)ltime);     // Use it to set the random number "seed"
    // Create the first string table ID
    string_number    = (rand() % MAX_STRING) + 1;
}


//  Our main Window ``callback'' function
long FAR PASCAL WndProc(hwnd, message, wParam, lParam)
HWND    hwnd;       // Window Handle for our Window
WORD    message,    // The Window's Message
        wParam;     // The WORD parameter value
LONG    lParam;     // The LONG parameter value
{
    PAINTSTRUCT     ps;     // a Paint structure (for painting the Window)
    HDC             hdc;    // a Display Context Handle (for writing text)
    RECT            rect;   // a Rectangle structure (for writing text)
    FARPROC         lpProcAbout,    // Pointer to function for "About" dialog
                    lpSettingsDialog;   //.................."Settings" dialog
    char            str_buffer[256];    // buffer for string from string table

    switch (message) {      // check for messages...
    case    WM_INITMENU:        // if a Menu is displayed...
        SendMessage(hwnd, WM_jkb_PAUSE, 0, 0L); // ...stop the timer
        break;

    case    WM_MENUSELECT:      // if a Menu has been canceled...
        if ((LOWORD(lParam) == 0xFFFF) && (HIWORD(lParam) == 0))
            SendMessage(hwnd, WM_jkb_START, 0, 0L); // ...restart the timer
        break;

    case    WM_COMMAND:         // we got a Menu choice...
        switch (wParam) {       // ... which one?
        case    IDM_ABOUT:          // About Pundit...
        // Make a function Instance
            lpProcAbout = MakeProcInstance(About, hinst);
        // Process the "About" dialog box
            DialogBox(hinst, "ABOUTBOX", hwnd, lpProcAbout);
        // Give back the function Instance
            FreeProcInstance(lpProcAbout);
        // Restart the timer
            SendMessage(hwnd, WM_jkb_START, 0, 0L);
            break;

        case    IDM_SETTINGS:       // Settings...
        // Make a function Instance
            lpSettingsDialog = MakeProcInstance(Settings, hinst);
        // Process the "About" dialog box
            DialogBox(hinst, "SETTINGS", hwnd, lpSettingsDialog);
        // Give back the function Instance
            FreeProcInstance(lpSettingsDialog);
        // Restart the timer
            SendMessage(hwnd, WM_jkb_START, 0, 0L);
            break;

        case    IDM_EXIT:           // Exit
        // Tell Windows to shut down this Application
            PostMessage(hwnd, WM_CLOSE, 0, 0L);
            break;
        }
        return 0L;  // return 0 exit code

    case    WM_TIMER:           // We got a TIMER Message
        switch (inout_state) {      // are we visible or hidden?
        case    FALSE:              // hidden...
            if (count >= settings.outsecs) {  // if it's time to show up
                    // create a new string table ID
                string_number = (rand() % MAX_STRING) + 1;
                ShowWindow(hwnd, SW_SHOW);          // Show ourselves
                   // Tell Windows that we want to paint the entire client area
                InvalidateRect(hwnd, NULL, TRUE);
                UpdateWindow(hwnd);                 // Update the window
                inout_state = TRUE;                 // switch the state
                count = 0;                          // reset the counter
            }
            else        // still waiting to show up
                ++count;        // bump the counter
            break;
        case    TRUE:               // shown...
            if (count >= settings.insecs) {  // if it's time to sleep
                ShowWindow(hwnd, SW_HIDE);      // hide
                inout_state = FALSE;            // switch the state
                count = 0;                      // reset the counter
            }
            else        // still waiting to sleep
                ++count;        // bump the counter
            break;
        }
        return 0L;

    case    WM_PAINT:           // We got a PAINT Message
        GetClientRect(hwnd, &rect);  // Get the dimensions of the client area
            // Fetch a new string from the string table
        LoadString(hinst, string_number, str_buffer, 256);
            // Tell Windows we are ready to paint
            // and get a Display Context Handle
        hdc = BeginPaint(hwnd, &ps);
            // Set Background Color to Windows' Background Color
        SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
            // Set Text Color to Windows' Text Color
        SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
            // Write the new quote
        DrawText(hdc,               // our Display Context Handle
                (LPSTR)str_buffer,  // our new Quote String
                -1,                 // let Windows figure the length
                &rect,              // write into entire client area rectangle
                DT_LEFT |           // left-justified,
                    DT_NOPREFIX |   // `&' character are not special
                    DT_WORDBREAK);  // let Windows handle word-wrap
        EndPaint(hwnd, &ps);    // Tell Windows that we're done painting
        return 0L;

    //  These next two Messages are our own messages, NOT Windows'
    case    WM_jkb_PAUSE:       // We need to shut down the timer
        if (paused == FALSE)            // are we already shut down?
            KillTimer(hwnd, ID_TIMER);  // no, shut it down
        paused = TRUE;                  // and keep track of that fact
        return 0L;

    case    WM_jkb_START:       // We need to restart the timer
        if (paused == TRUE)             // are we shut down now?
            SetTimer(hwnd, ID_TIMER, 1000, NULL);   // yes, set timer for 1 sec.
        paused = FALSE;                 // and keep track of that fact
        return 0L;

    case    WM_DESTROY:         //  We got a DESTROY Message
        KillTimer(hwnd, ID_TIMER);  // Shut down the timer
        PostQuitMessage(0);         // Post a QUIT Message
        return 0L;
    }
        // We don't need to handle any other messages, but
        // Windows might, so let it see them
    return DefWindowProc(hwnd, message, wParam, lParam);
}   // end of WndProc


//  About Box manager function
BOOL FAR PASCAL About(hDlg, message, wParam, lParam)
HWND        hDlg;       // Window Handle for our Dialog Box
unsigned    message;    // The Window's Message
WORD        wParam;     // The WORD parameter value
LONG        lParam;     // The LONG parameter value
{
    switch (message) {      // check for messages...
    case    WM_INITDIALOG:      // If the Dialog is just starting...
        PostMessage(jkbPundit, WM_jkb_PAUSE, 0, 0L);  // shut down the timer
        return TRUE;

    case    WM_COMMAND:     // We got a COMMAND
            // if the Dialog's button was pressed
        if (wParam == IDOK || wParam == IDCANCEL) {
            EndDialog(hDlg, TRUE);  // That's the end of the Dialog
            return TRUE;            // Say that we processed this message
        }
        break;
    }
    return FALSE;       // Say that we did NOT process the message
}


HWND    hInScroll,      // Handle for IN scrollbar
        hOutScroll;     // Handle for OUT scrollbar

SETTINGS    s;          // a temporary values for settings

BOOL FAR PASCAL Settings(hDlg, message, wParam, lParam)
HWND        hDlg;       // Window Handle for our Dialog Box
unsigned    message;    // The Window's Message
WORD        wParam;     // The WORD parameter value
LONG        lParam;     // The LONG parameter value
{
    HWND    hsb;        // Handle for a Horizontal Scroll Bar
    char    temp[10];   // string to write "numbers" into

    switch (message) {      // check for messages...
    case    WM_INITDIALOG:      // If the Dialog is just starting...
        PostMessage(jkbPundit, WM_jkb_PAUSE, 0, 0L); // shut down the timer
        InitSettings(hDlg);     // Initialize the Settings Dialog
        return TRUE;

    case    WM_HSCROLL:         // We got a Horizontal Scroll Bar Message...
        if (hsb = HIWORD(lParam)) { // if it's not zero
            if (hsb == hInScroll) {     // If it's from the IN Scroll Bar
                switch (wParam) {       // find out what happened...
                case    SB_PAGEUP:          // User did a PAGE UP
                    settings.insecs -= 4;   // take off 4 seconds and...
                case    SB_LINEUP:          // User did a LINE UP
                        // take off 1 second or set to 1, whichever is greater
                    settings.insecs = max(1, settings.insecs-1);
                    break;
                case    SB_PAGEDOWN:        // User did a PAGE DOWN
                    settings.insecs += 4;   // add 4 seconds and...
                case    SB_LINEDOWN:        // User did a LINE DOWN
                        // add 1 second or set to 1 minute, whichever is lesser
                    settings.insecs = min(MAX_IN, settings.insecs+1);
                    break;
                case    SB_TOP:             // User went to the TOP
                    settings.insecs = 1;    // set to 1 second
                    break;
                case    SB_BOTTOM:          // User went the to BOTTOM
                    settings.insecs = MAX_IN;   // set to 1 minute
                    break;
                case    SB_THUMBPOSITION:   // User moved...
                case    SB_THUMBTRACK:      // or is moving the THUMB BUTTON
                        // Windows tells us how far...
                    settings.insecs = LOWORD(lParam);
                default:
                    break;
                }
                // Put the new value into a string
                sprintf(temp, "%4d", settings.insecs);
                // Display it in the box next to the Scroll Bar
                SetDlgItemText(hDlg, IDC_INSECS, temp);
                // Set the new Scroll Bar position
                SetScrollPos(hsb, SB_CTL, settings.insecs, TRUE);
            }
            else if (hsb == hOutScroll) {   // If it's from the OUT Scroll Bar
                switch (wParam) {       // find out what happened...
                case    SB_PAGEUP:          // User did a PAGE UP
                    settings.outsecs -= 559;   // take off 10 min and...
                case    SB_LINEUP:          // User did a LINE UP
                        // take off 1 second or set to 1, whichever is greater
                    settings.outsecs = max(1, settings.outsecs-1);
                    break;
                case    SB_PAGEDOWN:        // User did a PAGE DOWN
                    settings.outsecs += 559;   // add 10 min and...
                case    SB_LINEDOWN:        // User did a LINE DOWN
                        // add 1 second or set to 1 hour, whichever is lesser
                    settings.outsecs = min(MAX_OUT, settings.outsecs+1);
                    break;
                case    SB_TOP:             // User went to the TOP
                    settings.outsecs = 1;    // set to 1 second
                    break;
                case    SB_BOTTOM:          // User went the to BOTTOM
                    settings.outsecs = MAX_IN;   // set to 1 hour
                    break;
                case    SB_THUMBPOSITION:   // User moved...
                case    SB_THUMBTRACK:      // or is moving the THUMB BUTTON
                        // Windows tells us how far...
                    settings.outsecs = LOWORD(lParam);
                default:
                    break;
                }
                // Put the new value into a string
                sprintf(temp, "%4d", settings.outsecs);
                // Display it in the box next to the Scroll Bar
                SetDlgItemText(hDlg, IDC_OUTSECS, temp);
                // Set the new Scroll Bar position
                SetScrollPos(hsb, SB_CTL, settings.outsecs, TRUE);
            }
        }
        return TRUE;

    case    WM_COMMAND:         // One of the Buttons got pressed
        switch (wParam) {           // which one?
        case    IDC_SAVE:           // Save Settings
            SaveSettings(hDlg);     // Save the Settings
            break;

        case    IDCANCEL:           // CANCEL
            settings = s;           // Restore setting to what they were

        case    IDOK:               // OK
            EndDialog(hDlg, TRUE);  // we're done
            return TRUE;

        default:
            break;
        }
        break;
    }
    return FALSE;       // Say that we did NOT process the message
}


//  Retrieve WIN.INI settings for Pundit
void FetchSettings(void)
{
    //  Here we get each of the 6 settings values
    //  from WIN.INI
    settings.insecs = GetProfileInt((LPSTR)szAppName, (LPSTR)"InSecs",
                                    settings.insecs);
    settings.outsecs = GetProfileInt((LPSTR)szAppName, (LPSTR)"OutSecs",
                                    settings.outsecs);
    settings.x = GetProfileInt((LPSTR)szAppName, (LPSTR)"X",
                                    settings.x);
    settings.y = GetProfileInt((LPSTR)szAppName, (LPSTR)"Y",
                                    settings.y);
    settings.width = GetProfileInt((LPSTR)szAppName, (LPSTR)"Width",
                                    settings.width);
    settings.height = GetProfileInt((LPSTR)szAppName, (LPSTR)"Height",
                                    settings.height);
}


//  Save the Settings (from Settings Dialog)
void SaveSettings(hDlg)
HWND        hDlg;
{
    char    buf[10];            // a string to write "numbers" into
    int     save_size = FALSE;  // flag to save Window size/position
    RECT    rect;               // boundaries of entire Window

    //  write the IN seconds
    sprintf(buf, "%d", settings.insecs);
    WriteProfileString((LPSTR)szAppName, (LPSTR)"InSecs", (LPSTR)buf);

    //  write the OUT seconds
    sprintf(buf, "%d", settings.outsecs);
    WriteProfileString((LPSTR)szAppName, (LPSTR)"OutSecs", (LPSTR)buf);

    //  Ask user if we should save position and size
    save_size = MessageBox(hDlg,
                           (LPSTR)"Save Current Window Position and Size?",
                           (LPSTR)"Saving",
                           MB_ICONQUESTION | MB_YESNO);
    if (save_size == IDYES) {   // User answered "YES"
        // Ask Windows for size of entire window
        GetWindowRect(jkbPundit, (LPRECT)&rect);
        settings.x      = rect.left;                // X position
        settings.y      = rect.top;                 // Y position
        settings.width  = rect.right - rect.left;   // Width
        settings.height = rect.bottom - rect.top;   // Height

        //  write the X position
        sprintf(buf, "%d", settings.x);
        WriteProfileString((LPSTR)szAppName, (LPSTR)"X", (LPSTR)buf);

        //  write the Y position
        sprintf(buf, "%d", settings.y);
        WriteProfileString((LPSTR)szAppName, (LPSTR)"Y", (LPSTR)buf);

        //  write the Width
        sprintf(buf, "%d", settings.width);
        WriteProfileString((LPSTR)szAppName, (LPSTR)"Width", (LPSTR)buf);

        //  write the Height
        sprintf(buf, "%d", settings.height);
        WriteProfileString((LPSTR)szAppName, (LPSTR)"Height", (LPSTR)buf);
    }
}


//  Initialize Settings Dialog info
void InitSettings(hDlg)
HWND    hDlg;
{
    char    temp[10];   // a string to write "numbers" into

    s = settings;       // stash the current settings (in case of CANCEL)

    //  Write the current IN seconds in the box next to the Scroll Bar
    sprintf(temp, "%4d", settings.insecs);
    SetDlgItemText(hDlg, IDC_INSECS, temp);

    //  Write the current OUT seconds in the box next to the Scroll Bar
    sprintf(temp, "%4d", settings.outsecs);
    SetDlgItemText(hDlg, IDC_OUTSECS, temp);

    //  Get handles to each of the 2 Scroll Bar Controls
    hInScroll  = GetDlgItem(hDlg, IDC_INSCROLL);
    hOutScroll = GetDlgItem(hDlg, IDC_OUTSCROLL);

    //  Set the Scroll Bar Ranges
    SetScrollRange(hInScroll,  SB_CTL, 1, MAX_IN,  TRUE);
    SetScrollRange(hOutScroll, SB_CTL, 1, MAX_OUT, TRUE);

    //  Set the Scroll Bar Positions
    SetScrollPos(hInScroll,  SB_CTL, settings.insecs,  TRUE);
    SetScrollPos(hOutScroll, SB_CTL, settings.outsecs, TRUE);
}


