/*

STARS.C

Starfield simulator screensaver.

  History:
       6/17/91        stevecat    ported to NT Windows
       2/10/92        stevecat    snapped to latest ported to NT Windows
*/

#include <windows.h>
#include <scrnsave.h>
#include <commctrl.h>
#include "stars.dlg"
#include "strings.h"
#include "uniconv.h"


#define SCOPE       256
#define MAXWARP     10              // Maximum warp speed
#define MINWARP     0               // Minimum warp speed
#define CLICKRANGE (MAXWARP-MINWARP)// Range for WarpSpeed scroll bar
#define MINSTARS    10              // Minimum number of stars in field
#define MAXSTARS    200             // Maximum number of stars in field
#define WARPFACTOR  10              // Warp Factor 10 Mr. Sulu!
#define SIZE        64
#define DEF_DENSITY 25              // Default number of stars in field
#define RAND(x)     ((rand() % (x))+1)
#define ZRAND(x)    (rand() % (x))
#define MINTIMERSPEED 50

VOID CreateStar            (WORD wIndex);
LONG GetDlgItemLong        (HWND hDlg, WORD wID, BOOL *pfTranslated, BOOL fSigned);
VOID GetIniEntries         (VOID);
LONG GetPrivateProfileLong (LPTSTR pszApp, LPTSTR pszKey, LONG lDefault);
WORD rand                  (VOID);
VOID srand                 (DWORD dwSeed);

DWORD dwRand;                           // Current random seed

TCHAR  szWarpSpeed [] = TEXT("WarpSpeed");     // .INI WarpSpeed key

TCHAR  szDensity [] = TEXT("Density");         // .INI Density key

LONG  nX[MAXSTARS],
      nY[MAXSTARS],
      nZ[MAXSTARS];
WORD  wXScreen,
      wYScreen,
      wX2Screen,
      wY2Screen;
WORD  wWarpSpeed,                       // Global WarpSpeed value
      wDensity;                         // Global starfield density value

//
// Help IDs
//
DWORD aStarsDlgHelpIds[] = {
    ((DWORD) -1), ((DWORD) -1),
    ID_SPEED_SLOW,              IDH_DISPLAY_SCREENSAVER_STARFIELD_WARP,
    ID_SPEED_FAST,              IDH_DISPLAY_SCREENSAVER_STARFIELD_WARP,
    ID_SPEED,                   IDH_DISPLAY_SCREENSAVER_STARFIELD_WARP,
    ID_DENSITY_LABEL,           IDH_DISPLAY_SCREENSAVER_STARFIELD_DENSITY,
    ID_DENSITY,                 IDH_DISPLAY_SCREENSAVER_STARFIELD_DENSITY,
    ID_DENSITYARROW,            IDH_DISPLAY_SCREENSAVER_STARFIELD_DENSITY,
    0,0
};

#define DIVIDE_SAFE(nNumber)            ((0 == (nNumber)) ? 1 : (nNumber))

/* This is the main window procedure to be used when the screen saver is
    activated in a screen saver mode ( as opposed to configure mode ).  This
    function must be declared as an EXPORT in the EXPORTS section of the
    DEFinition file... */

LRESULT ScreenSaverProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    RECT         rRect;
    WORD         wLoop;
    static UINT_PTR wTimer;
    static WORD  wWarp;
    static WORD  wTimerSet=MINTIMERSPEED;
    static WORD  wCurrentWarp;
    static int   nPassCount=0;
    int          nXTemp, nYTemp, nTemp;
    BOOL         fHyperSpace = TRUE;
    HDC          hDC;

    switch (message)
    {
    case WM_CREATE:
        /* Do anything that you need to do when you initialize the window
           here... */
        GetIniEntries ();
        srand (GetCurrentTime ());

        /* Make sure we use the entire virtual desktop size for multiple
           displays... */

        wXScreen = (WORD) ((LPCREATESTRUCT)lParam)->cx;
        wYScreen = (WORD) ((LPCREATESTRUCT)lParam)->cy;


        wX2Screen = wXScreen / 2;
        wY2Screen = wYScreen / 2;
        for (wLoop = 0; wLoop < wDensity; wLoop++)
            CreateStar (wLoop);
        wWarp = wWarpSpeed * WARPFACTOR + WARPFACTOR; // ZRAND (((wWarpSpeed)*WARPFACTOR)+1)+1;

        wTimer = SetTimer (hWnd, 1, wTimerSet, NULL);
        break;

    case WM_SIZE:
        wXScreen = LOWORD(lParam);
        wYScreen = HIWORD(lParam);
        break;


    case WM_TIMER:
    {
        MSG msg;

        hDC = GetDC (hWnd);
        /* Begin to loop through each star, accelerating so it seems that
           we are traversing the starfield... */
        for (wLoop = 0; wLoop < wDensity; wLoop++)
        {
            nXTemp = (int)((nX[wLoop] * (LONG)(SCOPE * WARPFACTOR))
                                                / DIVIDE_SAFE(nZ[wLoop])) + wX2Screen;
            nYTemp = (int)((nY[wLoop] * SCOPE * WARPFACTOR) / DIVIDE_SAFE(nZ[wLoop]))
                                                     + wY2Screen;
            nTemp = (int)((SCOPE * WARPFACTOR - nZ[wLoop]) /
                                                    (SIZE * WARPFACTOR)) + 1;
            PatBlt (hDC, nXTemp, nYTemp, nTemp, nTemp, BLACKNESS);

            if (wCurrentWarp < wWarp)
                wCurrentWarp++;
            else if (wCurrentWarp > wWarp)
                wCurrentWarp--;

            nZ[wLoop] = max (0, (int)(nZ[wLoop] - wCurrentWarp));
            if (!nZ[wLoop])
                CreateStar (wLoop);

            nXTemp = (int)((nX[wLoop] * (LONG)(SCOPE * WARPFACTOR))
                                                    / DIVIDE_SAFE(nZ[wLoop])) + wX2Screen;
            nYTemp = (int)((nY[wLoop] * SCOPE * WARPFACTOR)
                                                    / DIVIDE_SAFE(nZ[wLoop])) + wY2Screen;
            if ((nXTemp < 0 || nYTemp < 0) ||
                (nXTemp > (int) wXScreen || nYTemp > (int) wYScreen))
            {
                CreateStar (wLoop);
                nXTemp = (int)((nX[wLoop] * (LONG)(SCOPE * WARPFACTOR))
                                                 / DIVIDE_SAFE(nZ[wLoop])) + wX2Screen;
                nYTemp = (int)((nY[wLoop] * SCOPE * WARPFACTOR)
                                                 / DIVIDE_SAFE(nZ[wLoop])) + wY2Screen;
            }
            nTemp = (int)((SCOPE * WARPFACTOR - nZ[wLoop]) /
                                                (SIZE * WARPFACTOR)) + 1;
            PatBlt (hDC, nXTemp, nYTemp, nTemp, nTemp, WHITENESS);
        }
        ReleaseDC (hWnd, hDC);

        if (PeekMessage(&msg, hWnd, WM_TIMER, WM_TIMER, PM_REMOVE))
        {
            // There is another WM_TIMER message in the queue.  We have
            // removed it, but now we want to adjust the timer a bit so
            // hopefully we won't get another WM_TIMER message before we
            // finish the screen update. (bug #8423)  TG:11/25/91

            wTimerSet += 10;
            SetTimer(hWnd, 1, wTimerSet, NULL);
            nPassCount = 0;
        }
        else
            ++nPassCount;

        if (nPassCount >= 100)
        {
            nPassCount = 0;
            wTimerSet -= 100;
            if ((short)wTimerSet < MINTIMERSPEED)
                wTimerSet = MINTIMERSPEED;
            SetTimer(hWnd, 1, wTimerSet, NULL);
        }
        break;
    }

    case WM_ERASEBKGND:
            /* If you want something put on the background, do it right here
                using wParam as a handle to a device context.  Remember to
                unrealize a brush if it is not a solid color.  If you do
                something here, you want to use the line:
                    return 0l;
                So the program knows not to take the default action. Otherwise
                just use:
                    break;
                */
        break;
        GetClientRect (hWnd, &rRect);
        FillRect ((HDC) wParam, &rRect, GetStockObject (GRAY_BRUSH));
        return 0l;

    case WM_DESTROY:
        /* Anything that needs to be deleted when the window is closed
                goes here... */
        if (wTimer)
            KillTimer (hWnd, wTimer);
        break;
    }
    /* Unless it is told otherwise, the program will take default actions... */
    return (DefScreenSaverProc (hWnd, message, wParam, lParam));
}


//***************************************************************************

BOOL ScreenSaverConfigureDialog (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    BOOL    fError;                         // Error flag

    UINT    wTemp;
    TCHAR   szTemp[20];                     // Temporary string buffer

    static WORD wPause, wScroll;
    static HWND hWarpSpeed,                 // window handle of Speed scrollbar
                hIDOK,                      // window handle of OK button
                hSetPassword,               // window handle of SetPassword button
                hDensity;                   // window handle of Density EditControl

    static WORD wIncScroll = 1;             // density spin button parameters

    static WORD wStartScroll = 1;
    static WORD wStartPause = 1;
    static WORD wMaxScroll = 10;
    static WORD wPauseScroll = 20;
    static LONG lMinScroll = MINSTARS;
    static LONG lMaxScroll = MAXSTARS;


    switch (message)
    {
    case WM_INITDIALOG:
        GetIniEntries ();
        hWarpSpeed = GetDlgItem (hDlg, ID_SPEED);
        hIDOK = GetDlgItem (hDlg, IDOK);
        hDensity = GetDlgItem (hDlg, ID_DENSITY);
        SendMessage (hDensity, EM_LIMITTEXT, 3, 0);

        SendDlgItemMessage( hDlg, ID_DENSITYARROW, UDM_SETBUDDY, (WPARAM)hDensity, 0);
        SendDlgItemMessage( hDlg, ID_DENSITYARROW, UDM_SETRANGE, 0, MAKELONG(lMaxScroll, lMinScroll));

        SetScrollRange (hWarpSpeed, SB_CTL, MINWARP, MAXWARP, FALSE);
        SetScrollPos (hWarpSpeed, SB_CTL, wWarpSpeed, TRUE);

        SetDlgItemInt (hDlg, ID_DENSITY, wDensity, FALSE);
        return TRUE;

    case WM_HSCROLL:
        switch (LOWORD(wParam))
        {
        case SB_LINEUP:
        case SB_PAGEUP:
            --wWarpSpeed;
            break;

        case SB_LINEDOWN:
        case SB_PAGEDOWN:
            ++wWarpSpeed;
            break;

        case SB_THUMBPOSITION:
            wWarpSpeed = HIWORD (wParam);
            break;

        case SB_TOP:
            wWarpSpeed = MINWARP;
            break;

        case SB_BOTTOM:
            wWarpSpeed = MAXWARP;
            break;

        case SB_THUMBTRACK:
        case SB_ENDSCROLL:
            return TRUE;
            break;
        }
        if ((int)((short)wWarpSpeed) <= MINWARP)
            wWarpSpeed = MINWARP;
        if ((int)wWarpSpeed >= MAXWARP)
            wWarpSpeed = MAXWARP;

        SetScrollPos ((HWND) lParam, SB_CTL, wWarpSpeed, TRUE);
        break;

    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case ID_DENSITY:
            if (HIWORD(wParam) == EN_UPDATE)
            {
                wTemp = GetDlgItemInt (hDlg, ID_DENSITY, &fError, FALSE);
                fError = ((wTemp <= MAXSTARS) && (wTemp >= MINSTARS));
                EnableWindow (GetDlgItem (hDlg, ID_DENSITYARROW), fError);
                EnableWindow (GetDlgItem (hDlg, IDOK), fError);
            }
            break;

        case IDOK:
            wTemp = GetDlgItemInt (hDlg, ID_DENSITY, &fError, FALSE);
            wsprintf (szTemp, TEXT("%d"), wTemp);
            WritePrivateProfileString (szAppName, szDensity, szTemp, szIniFile);
            wsprintf (szTemp, TEXT("%d"), wWarpSpeed);
            WritePrivateProfileString (szAppName, szWarpSpeed, szTemp, szIniFile);

        case IDCANCEL:
            EndDialog (hDlg, LOWORD(wParam) == IDOK);
            return TRUE;

        }
        break;

    case WM_HELP: // F1
        WinHelp(
            (HWND) ((LPHELPINFO) lParam)->hItemHandle,
            szHelpFile,
            HELP_WM_HELP,
            (ULONG_PTR) (LPSTR) aStarsDlgHelpIds
        );
        break;

    case WM_CONTEXTMENU:  // right mouse click
        WinHelp(
            (HWND) wParam,
            szHelpFile,
            HELP_CONTEXTMENU,
            (ULONG_PTR) (LPSTR) aStarsDlgHelpIds
        );
        break;

    default:
        break;
    }
    return FALSE;
}


/* This procedure is called right before the dialog box above is created in
   order to register any child windows that are custom controls.  If no
   custom controls need to be registered, then simply return TRUE.
   Otherwise, register the child controls however is convenient... */

BOOL RegisterDialogClasses (HANDLE hInst)
{
    InitCommonControls();

    return TRUE;
}

VOID srand (DWORD dwSeed)
{
    dwRand = dwSeed;
}

WORD rand (VOID)
{
    dwRand = dwRand * 214013L + 2531011L;
    return (WORD)((dwRand >> 16) & 0xffff);
}

VOID CreateStar (WORD wIndex)
{
    nX[wIndex] = wXScreen ? (LONG)((int)(ZRAND (wXScreen)) - (int)wX2Screen) : 0;
    nY[wIndex] = wXScreen ? (LONG)((int)(ZRAND (wYScreen)) - (int)wY2Screen) : 0;
    nZ[wIndex] = SCOPE * WARPFACTOR;
}

LONG GetDlgItemLong (HWND hDlg, WORD wID, BOOL *pfTranslated, BOOL fSigned)
{
    TCHAR szTemp[20];
    LPTSTR pszTemp;
    LONG lTemp = 0l;
    BOOL fNegative;

    if (!GetDlgItemText (hDlg, wID, szTemp, CharSizeOf(szTemp)))
        goto GetDlgItemLongError;

    szTemp[19] = TEXT('\0');
    pszTemp = szTemp;
    while (*pszTemp == TEXT(' ') || *pszTemp == TEXT('\t'))
        pszTemp++;
    if ((!fSigned && *pszTemp == TEXT('-')) || !*pszTemp)
        goto GetDlgItemLongError;
    fNegative = (*pszTemp == TEXT('-')) ? TRUE : FALSE;
    while (*pszTemp >= TEXT('0') && *pszTemp <= TEXT('9'))
        lTemp = lTemp * 10l + (LONG)(*(pszTemp++) - TEXT('0'));
    if (*pszTemp)
        goto GetDlgItemLongError;
    if (fNegative)
        lTemp *= -1;
    *pfTranslated = TRUE;
    return lTemp;

GetDlgItemLongError:
    *pfTranslated = FALSE;
    return 0l;
}


LONG GetPrivateProfileLong (LPTSTR pszApp, LPTSTR pszKey, LONG lDefault)
{
    LONG    lTemp = 0l;
    TCHAR    szTemp[20];
    LPTSTR pszTemp;

    if (!GetPrivateProfileString (pszApp, pszKey, TEXT(""), szTemp, CharSizeOf(szTemp), szIniFile))
        goto GetProfileLongError;

    szTemp[19] = TEXT('\0');
    pszTemp = szTemp;
    while (*pszTemp >= TEXT('0') && *pszTemp <= TEXT('9'))
        lTemp = lTemp * 10l + (LONG)(*(pszTemp++) - TEXT('0'));
    if (*pszTemp)
        goto GetProfileLongError;
    return lTemp;

GetProfileLongError:
    return lDefault;
}


VOID GetIniEntries (VOID)
{
    LoadString (hMainInstance, idsName, szName, CharSizeOf(szName));
    LoadString (hMainInstance, idsAppName, szAppName, CharSizeOf(szAppName));

    //Load Common Strings from stringtable...
    LoadString (hMainInstance, idsIniFile, szIniFile, CharSizeOf(szIniFile));
    LoadString (hMainInstance, idsScreenSaver, szScreenSaver, CharSizeOf(szScreenSaver));
    LoadString (hMainInstance, idsHelpFile, szHelpFile, CharSizeOf(szHelpFile));
    LoadString (hMainInstance, idsNoHelpMemory, szNoHelpMemory, CharSizeOf(szNoHelpMemory));

    wWarpSpeed = (WORD) GetPrivateProfileInt (szAppName, szWarpSpeed, MINWARP + ((MAXWARP - MINWARP) / 2), szIniFile);
    if (wWarpSpeed > MAXWARP)
        wWarpSpeed = MINWARP + ((MAXWARP - MINWARP) / 2);

    wDensity = (WORD) GetPrivateProfileInt (szAppName, szDensity, DEF_DENSITY, szIniFile);
    if (wDensity > MAXSTARS)
        wDensity = MAXSTARS;
    if (wDensity < MINSTARS)
        wDensity = MINSTARS;
}
