#include "sol.h"
#include <shellapi.h>  // To pick up ShellAbout()
#include <htmlhelp.h>
#include <commctrl.h>   // for fusion classes.

VSZASSERT


#define rgbGreen RGB(0x00,0x80,0x00)
#define rgbWhite RGB(0xff,0xff,0xff)

PT   ptNil = {0x7fff, 0x7fff};
TCHAR szAppName[10];      // name of this app: 'solitaire'
TCHAR szScore[50];        // 'score:' for internationalization

/* Instance info */
static HANDLE  hAccel; // accelerators handle

HWND    hwndApp;       // window handle to this app
HANDLE  hinstApp;      // instance handle to this app
BOOL    fBW=FALSE;     // true if on true monochrome video! (never true on NT)
HBRUSH  hbrTable;      // brush for background of table top
LONG    rgbTable;      // RGB value of table top

BOOL fIconic = fFalse; // true if app is 'iconic'

INT  dyChar;           // tmHeight of font in hdc
INT  dxChar;           // tmMaxCharWidth of font in hdc


#define modeNil -1
INT modeFaceDown = modeNil;  // back of cards ID


GM *pgmCur = NULL;           // current game

/* card extent info */
DEL delCrd;
DEL delScreen;

RC rcClient;                 // client rectangle

INT igmCur;   /* the current game #, srand seeded with this */
#ifdef DEBUG
BOOL fScreenShots = fFalse;
#endif

/* window messages for external app drawing */
static UINT wmCardDraw;


HDC hdcCur = NULL;   // current hdc to draw on
INT usehdcCur = 0;   // hdcCur use count
X xOrgCur = 0;
Y yOrgCur = 0;

static TCHAR szClass[] = TEXT("Solitaire");

TCHAR szOOM[50];

// BUG: some of these should go in gm struct
//
BOOL fStatusBar   = fTrue;
BOOL fTimedGame   = fTrue;
BOOL fKeepScore   = fFalse;
SMD  smd          = smdStandard;  /* Score MoDe */
INT  ccrdDeal     = 3;
BOOL fOutlineDrag = fFalse;

BOOL fHalfCards = fFalse;


INT  xCardMargin;
#define MIN_MARGIN  (dxCrd / 8 + 3)


/********************  Internal Functions ****************/
BOOL FSolInit( HANDLE, HANDLE, LPTSTR, INT );
VOID GetIniFlags( BOOL * );
VOID APIENTRY cdtTerm( VOID );
VOID DoHelp( INT );

LRESULT APIENTRY SolWndProc(HWND, UINT, WPARAM, LPARAM);

// International stuff
//
INT  iCurrency;
TCHAR szCurrency[5];


/******************************************************************************
 * WINMAIN/ENTRY POINT
 *   This is the main entry-point for the application.  It uses the porting
 *   macro MMain() since it was ported from 16bit Windows.
 *
 *   The accelerator-table was added from demo-purposes.
 *
 *
 *****************************************************************************/
MMain( hinst, hinstPrev, lpstrCmdLine, sw )

    MSG msg;
    LPTSTR  lpszCmdLine = GetCommandLine();


    // Initialize the application.
    //
    if (!FSolInit(hinst, hinstPrev, lpszCmdLine, sw))
            return(0);


    // Message-Polling loop.
    //
    msg.wParam = 1;
    while (GetMessage((LPMSG)&msg, NULL, 0, 0))
    {
        if( !TranslateAccelerator( hwndApp, hAccel, &msg ))
        {
            TranslateMessage((LPMSG)&msg);
            DispatchMessage((LPMSG)&msg);
        }
    }

    return ((int)(msg.wParam ? 1 : 0));

    // Eliminate unreferenced-variable warnings from
    // porting macro.
    //
    (void)_argv;
    (void)_argc;
}


/******************************************************************************
 *      FSolInit
 *
 *      Main program initialization.
 *
 *      Arguments:
 *              hinst - instance of this task
 *              hinstPrev - previous instance, or NULL if this is the
 *                      first instance
 *              lpszCmdLine - command line argument string
 *              sw - show window command
 *
 *      Returns:
 *              fFalse on failure.
 *
 *****************************************************************************/
BOOL FSolInit(HANDLE hinst, HANDLE hinstPrev, LPTSTR lpszCmdLine, INT sw)
{
    WNDCLASSEX cls;
    HDC        hdc;
    TEXTMETRIC tm;
    HANDLE     hcrsArrow;
    BOOL       fStartIconic;
    TCHAR FAR  *lpch;
    BOOL       fOutline;
    TCHAR      szT[20];
    RECT       rect;
    INITCOMMONCONTROLSEX icc;   // common control registration.
    WORD APIENTRY TimerProc(HWND, UINT, UINT_PTR, DWORD);

    hinstApp = hinst;

    /* create stock objects */

    CchString(szOOM, idsOOM, ARRAYSIZE(szOOM));
    if(!cdtInit((INT FAR *)&dxCrd, (INT FAR *)&dyCrd))
    {
        goto OOMError;
    }
    hcrsArrow = LoadCursor(NULL, IDC_ARROW);
    hdc = GetDC(NULL);
    if(hdc == NULL)
    {
        OOMError:
        OOM();
        return fFalse;
    }

    GetTextMetrics(hdc, (LPTEXTMETRIC)&tm);
    dyChar = tm.tmHeight;
    dxChar = tm.tmMaxCharWidth;
    if (GetDeviceCaps(hdc, NUMCOLORS) == 2)
        fBW = fTrue;

/* BUG:  if HORZRES not big enough, have to call cdtDrawExt & shrink dxCrd */
/* BUG:  Need to check VERTRES and divide dxCrd by 2 (esp w/ lores ega) */
    dxScreen = GetDeviceCaps(hdc, HORZRES);
    dyScreen = GetDeviceCaps(hdc, VERTRES);
    if(fHalfCards = dyScreen < 300)
        dyCrd /= 2;
    ReleaseDC(NULL, hdc);
    rgbTable = fBW ? rgbWhite : rgbGreen;
    hbrTable = CreateSolidBrush(rgbTable);

    srand((WORD) time(NULL));

    /* load strings */
    CchString(szAppName, idsAppName, ARRAYSIZE(szAppName));
    CchString(szScore, idsScore, ARRAYSIZE(szScore));

    CchString(szT, idsCardDraw, ARRAYSIZE(szT));
    wmCardDraw = RegisterWindowMessage(szT);

    /* scan cmd line to see if should come up iconic */
    /* this may be unnecessary with win3.0 (function may be provided to */
    /* do it automatically */

    fStartIconic = fFalse;
    for(lpch = lpszCmdLine; *lpch != TEXT('\000'); lpch++)
    {
        if(*lpch == TEXT('/') && *(lpch+1) == TEXT('I'))
        {
            fStartIconic = fTrue;
            break;
        }
    }


    // Register the common controls.
    icc.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icc.dwICC  = ICC_ANIMATE_CLASS | ICC_BAR_CLASSES | ICC_COOL_CLASSES | ICC_HOTKEY_CLASS | ICC_LISTVIEW_CLASSES | 
                 ICC_PAGESCROLLER_CLASS | ICC_PROGRESS_CLASS | ICC_TAB_CLASSES | ICC_UPDOWN_CLASS | ICC_USEREX_CLASSES;
    InitCommonControlsEx(&icc);

    /* Load the solitaire icon */

    hIconMain = LoadIcon(hinstApp, MAKEINTRESOURCE(ID_ICON_MAIN));

    /* Load the solitaire icon image */

    hImageMain = LoadImage(hinstApp, MAKEINTRESOURCE(ID_ICON_MAIN),
                         IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);


    /* register window classes */

    if (hinstPrev == NULL)
    {
        ZeroMemory( &cls, sizeof(cls) );
        cls.cbSize= sizeof(cls);
        cls.style = CS_BYTEALIGNWINDOW | CS_DBLCLKS,
        cls.lpfnWndProc = SolWndProc;
        cls.hInstance = hinstApp;
        cls.hIcon =  hIconMain;
        cls.hIconSm= hImageMain;
        cls.hCursor = hcrsArrow;
        cls.hbrBackground = hbrTable;
        cls.lpszMenuName = MAKEINTRESOURCE(idmSol);
        cls.lpszClassName = (LPTSTR)szClass;
        if (!RegisterClassEx(&cls))
        {
            goto OOMError;
        }
     }

	/* Determine the proper starting size for the window */

	/* Card margin is just a little bigger than 1/8 of a card */
	xCardMargin = MIN_MARGIN;
	
	/* We need 7 card widths and 8 margins */
	rect.right = dxCrd * 7 + 8 * xCardMargin;

	/* Compute the window size we need for a client area this big */
	rect.bottom = dyCrd * 4;
	rect.left = rect.top = 0;
	AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, TRUE);
	rect.right -= rect.left;
	rect.bottom -= rect.top;

	/* Make sure it's not too big */
	if (rect.bottom > dyScreen)
	    rect.bottom = dyScreen;

    /* create our windows */
    if (!
    (hwndApp = CreateWindow( (LPTSTR)szClass, (LPTSTR)szAppName,
                    fStartIconic ? WS_OVERLAPPEDWINDOW | WS_MINIMIZE | WS_CLIPCHILDREN:
                    WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                    CW_USEDEFAULT, 0,
			rect.right, rect.bottom,
                        (HWND)NULL, (HMENU)NULL, hinstApp, (LPTSTR)NULL)))
        {
        goto OOMError;
        }

    GetIniFlags(&fOutline);


    if(SetTimer(hwndApp, 666, 250, TimerProc) == 0)
    {
        goto OOMError;
    }

    FInitGm();
    FSetDrag(fOutline);

    ShowWindow(hwndApp, sw);
    UpdateWindow(hwndApp);

    hAccel = LoadAccelerators( hinst, TEXT("HiddenAccel") );

    FRegisterStat(hinstPrev == NULL);
    if(fStatusBar)
        FCreateStat();

    Assert(pgmCur != NULL);
    if(sw != SW_SHOWMINNOACTIVE && sw != SW_MINIMIZE)
        PostMessage(hwndApp, WM_COMMAND, idsInitiate, 0L);

    return(fTrue);
}



VOID DoPaint(HWND hwnd)
{
    PAINTSTRUCT paint;

    BeginPaint(hwnd, (LPPAINTSTRUCT) &paint);
    if(pgmCur)
        SendGmMsg(pgmCur, msggPaint, (INT_PTR) &paint, 0);
    EndPaint(hwnd, (LPPAINTSTRUCT) &paint);
}


/*      SolWndProc
 *
 *      Window procedure for main Sol window.
 *
 *      Arguments:
 *              hwnd - window handle receiving the message - should
 *                      be hwndSol
 *              wm - window message
 *              wParam, lParam - more info as required by wm
 *
 *      Returns:
 *              depends on the message
 */
LRESULT APIENTRY SolWndProc(HWND hwnd, UINT wm, WPARAM wParam, LPARAM lParam)
{
    HMENU hmenu;
    PT pt;
    INT msgg;
    VOID NewGame();
    VOID StatString();

    switch (wm)
    {
        default:
            if(wm == wmCardDraw)
            {
                switch(wParam)
                {
                    case drwInit:
                        return MAKELONG(dxCrd, dyCrd);

                    case drwDrawCard:
                        #define lpcddr ((CDDR FAR *)lParam)
                        return cdtDraw(lpcddr->hdc, lpcddr->x, lpcddr->y, lpcddr->cd, lpcddr->mode, lpcddr->rgbBgnd);
                        #undef lpcddr

                    case drwClose:
                        PostMessage(hwndApp, WM_SYSCOMMAND, SC_CLOSE, 0L);
                        return fTrue;
                }
            }
            break;

    case WM_HELP:
        DoHelp( idsHelpIndex );
        break;

    case WM_DESTROY:
        KillTimer(hwndApp, 666);
        SendGmMsg(pgmCur, msggEnd, 0, 0);
        FSetDrag(fTrue);    /* Free up screen bitmaps if we made em */
        cdtTerm();
        DeleteObject(hbrTable);
        PostQuitMessage(0);
        break;

    case WM_ACTIVATE:
        if( GET_WM_ACTIVATE_STATE(wParam, lParam) &&
		   !GET_WM_ACTIVATE_FMINIMIZED(wParam, lParam) )
            DoPaint(hwnd);
        break;

    case WM_KILLFOCUS:
        if(pgmCur->fButtonDown)
            SendGmMsg(pgmCur, msggMouseUp, 0, fTrue);
        /* Fall through. */
    case WM_SETFOCUS:
        ShowCursor(wm == WM_SETFOCUS);
        break;


    case WM_SIZE:
    {
	    int nNewMargin;
	    int nMinMargin;

	    fIconic = IsIconic(hwnd);
	    GetClientRect(hwnd, (LPRECT) &rcClient);

	    /* Compute the new margin size if any and if necessary, redraw */
	    nNewMargin = ((short)lParam - 7 * (short)dxCrd) / 8;
	    nMinMargin = MIN_MARGIN;
	    if (nNewMargin < nMinMargin && xCardMargin != nMinMargin)
		nNewMargin = nMinMargin;
	    if (nNewMargin >= nMinMargin)
	    {
            xCardMargin = nNewMargin;
            PositionCols();
            InvalidateRect(hwnd, NULL, TRUE);
	    }

	    /* Code always falls through here */
    }


    case WM_MOVE:
        StatMove();
        break;


    case WM_MENUSELECT:
	    // Don't send in garbage if not a menu item
	    if( GET_WM_MENUSELECT_FLAGS( wParam, lParam ) & MF_POPUP     ||
		    GET_WM_MENUSELECT_FLAGS( wParam, lParam ) & MF_SYSMENU   ||
		    GET_WM_MENUSELECT_FLAGS( wParam, lParam ) & MF_SEPARATOR ) {

		    StatString(idsNil);
		}
		else {
		    StatString( GET_WM_MENUSELECT_CMD( wParam, lParam ));
		}
        break;

    case WM_KEYDOWN:
        Assert(pgmCur);
        SendGmMsg(pgmCur, msggKeyHit, wParam, 0);
        break;

    case WM_LBUTTONDOWN:
        /*              ProfStart(); */
        SetCapture(hwnd);
        if(pgmCur->fButtonDown)
            break;
        msgg = msggMouseDown;
        goto DoMouse;

    case WM_LBUTTONDBLCLK:
        msgg = msggMouseDblClk;
        if(pgmCur->fButtonDown)
            break;
        goto DoMouse;

    case WM_RBUTTONDOWN:
        // If the left mousebutton is down, ignore the right click.
        if (GetCapture())
            break;
        msgg = msggMouseRightClk;
        goto DoMouse;

    case WM_LBUTTONUP:
        /*              ProfStop(); */
        ReleaseCapture();
        msgg = msggMouseUp;
        if(!pgmCur->fButtonDown)
            break;
        goto DoMouse;

    case WM_MOUSEMOVE:
        msgg = msggMouseMove;
        if(!pgmCur->fButtonDown)
            break;
DoMouse:
            Assert(pgmCur != NULL);
            LONG2POINT( lParam, pt );
            Assert(pgmCur);
            SendGmMsg(pgmCur, msgg, (INT_PTR) &pt, 0);
            break;


    case WM_COMMAND:
        switch( GET_WM_COMMAND_ID( wParam, lParam ))
        {
            /* Game menu */
            case idsInitiate:
                NewGame(fTrue, fFalse);
                break;
            case idsUndo:
                Assert(pgmCur);
                SendGmMsg(pgmCur, msggUndo, 0, 0);
                break;
            case idsBacks:
                DoBacks();
                break;
            case idsOptions:
                DoOptions();
                break;
            case idsExit:
                PostMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0L);
                break;
            /* Help Menu */
            case (WORD)idsHelpIndex:
            case (WORD)idsHelpSearch:
            case (WORD)idsHelpUsing:
                DoHelp( (INT)(SHORT)GET_WM_COMMAND_ID( wParam, lParam ));
                break;
            case idsAbout:
            {
                TCHAR szExtraInfo[100];
                CchString(szExtraInfo, idsExtraInfo, ARRAYSIZE(szExtraInfo));
#ifndef _GAMBIT_
                ShellAbout(hwnd, szAppName, szExtraInfo, hIconMain);
#endif
                break;
            }
            case idsForceWin:
                SendGmMsg(pgmCur, msggForceWin, 0, 0);
                break;
#ifdef DEBUG
            case idsGameNo:
                if(FSetGameNo())
                    NewGame(fFalse, fFalse);
                break;

            case idsCardMacs:
                PrintCardMacs(pgmCur);
                break;
            case idsAssertFail:
                Assert(fFalse);
                break;
            case idsMarquee:
                break;

            case idsScreenShots:
                fScreenShots ^= 1;
                CheckMenuItem(GetMenu(hwnd), idsScreenShots, fScreenShots ? MF_CHECKED|MF_BYCOMMAND : MF_UNCHECKED|MF_BYCOMMAND);
                InvalidateRect(hwndStat, NULL, fTrue);
                if(fScreenShots)
                    InvalidateRect(hwnd, NULL, fTrue);
                break;
#endif
            default:
                break;
            }
            break;

    case WM_INITMENU:
            hmenu = GetMenu(hwnd);
            Assert(pgmCur);
            EnableMenuItem(hmenu, idsUndo,
                    pgmCur->udr.fAvail && !FSelOfGm(pgmCur) ? MF_ENABLED : MF_DISABLED|MF_GRAYED);
            EnableMenuItem(hmenu, idsInitiate, FSelOfGm(pgmCur) ? MF_DISABLED|MF_GRAYED : MF_ENABLED);
            EnableMenuItem(hmenu, idsBacks,    FSelOfGm(pgmCur) ? MF_DISABLED|MF_GRAYED : MF_ENABLED);
            EnableMenuItem(hmenu, idsAbout,    FSelOfGm(pgmCur) ? MF_DISABLED|MF_GRAYED : MF_ENABLED);
            break;

    case WM_PAINT:
        if(!fIconic)
        {
            DoPaint(hwnd);
            return(0L);
        }
        break;    
    }

    return(DefWindowProc(hwnd, wm, wParam, lParam));
}



HDC HdcSet(HDC hdc, X xOrg, Y yOrg)
{
    HDC hdcT = hdcCur;
    hdcCur = hdc;
    xOrgCur = xOrg;
    yOrgCur = yOrg;
    return hdcT;
}



BOOL FGetHdc()
{
    HDC hdc;

    Assert(hwndApp);
    if(hdcCur != NULL)
    {
        usehdcCur++;
        return fTrue;
    }

    hdc = GetDC(hwndApp);
    if(hdc == NULL)
        return fFalse;
    HdcSet(hdc, 0, 0);
    usehdcCur = 1;
    return fTrue;
}


VOID ReleaseHdc()
{
    if(hdcCur == NULL)
        return;
    if(--usehdcCur == 0)
    {
        ReleaseDC(hwndApp, hdcCur);
        hdcCur = NULL;
    }
}


WORD APIENTRY TimerProc(HWND hwnd, UINT wm, UINT_PTR id, DWORD dwTime)
{

    if(pgmCur != NULL)
		SendGmMsg(pgmCur, msggTimer, 0, 0);
    return fTrue;
}





VOID ChangeBack(INT mode)
{

    if(mode == modeFaceDown)
        return;
    modeFaceDown = mode;
    InvalidateRect(hwndApp, NULL, fTrue);
}


VOID NewGame(BOOL fNewSeed, BOOL fZeroScore)
{

#ifdef DEBUG
    InitDebug();
#endif
    if(fNewSeed)
    {
        static INT lastrnd= -1;     // previous rand() value
        INT rnd1;                   // trial rand() value
        INT Param;

        // It was reported that games never changed.
        // We could not repro it so see if it happens
        // and output a message to the debugger.
        //

        Param= (INT) time(NULL);
        srand( igmCur = ((WORD) Param) & 0x7fff);

#ifdef DEBUG
        rnd1= rand();

        if( lastrnd == rnd1 )
        {
            TCHAR szText[100];
            wsprintf(szText,TEXT("Games repeat: time= %d  GetLastError= %d\n"),
                     Param, GetLastError());
            OutputDebugString(szText);
        }

        lastrnd= rnd1;
#endif

    }

#ifdef DEBUG
    SendGmMsg(pgmCur, msggChangeScore, 0, 0);
#endif
    SendGmMsg(pgmCur, msggDeal, fZeroScore, 0);
}



INT_PTR APIENTRY About(HWND hdlg, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    if (iMessage == WM_COMMAND)
    {
        EndDialog(hdlg,fTrue);
        return fTrue;
    }
    else if (iMessage == WM_INITDIALOG)
        return fTrue;
    else
        return fFalse;
}


VOID DoHelp(INT idContext)
{
    CHAR sz[100];
    HWND hwndResult;

    LoadStringA(hinstApp, (WORD)idsHelpFile, (LPSTR)sz, 100);

#ifndef _GAMBIT_
    switch(idContext)
    {
        case idsHelpUsing:
            hwndResult = HtmlHelpA(GetDesktopWindow(), "NTHelp.chm", HH_DISPLAY_TOPIC, 0);
            break;
        case idsHelpIndex:
            hwndResult = HtmlHelpA(GetDesktopWindow(), sz, HH_DISPLAY_TOPIC, 0);
            break;
        case idsHelpSearch:
            hwndResult = HtmlHelpA(GetDesktopWindow(), sz, HH_DISPLAY_INDEX, 0);
            break;
    }
    if(!hwndResult)
        ErrorIds(idsNoHelp);
#endif
}


VOID GetIniFlags(BOOL *pfOutline)
{
    INI    ini;
    INT    mode;

    ini.w = 0;
    ini.grbit.fStatusBar = fStatusBar;
    ini.grbit.fTimedGame = fTimedGame;
    ini.grbit.fOutlineDrag = fOutlineDrag;
    ini.grbit.fDrawThree = ccrdDeal == 3;
    ini.grbit.fKeepScore = fKeepScore;
    ini.grbit.fSMD = 0;

    ini.w = GetIniInt(idsAppName, idsOpts, ini.w);

    fStatusBar = ini.grbit.fStatusBar ? 1 : 0;
    fTimedGame = ini.grbit.fTimedGame ? 1 : 0;
    *pfOutline = ini.grbit.fOutlineDrag ? 1 : 0;
    ccrdDeal = ini.grbit.fDrawThree ? 3 : 1;
    fKeepScore = ini.grbit.fKeepScore ? 1 : 0;
    switch(ini.grbit.fSMD)
    {
        default:
            smd = smdStandard;
            break;
        case 1:
            smd = smdVegas;
            break;
        case 2:
            smd = smdNone;
            break;
    }

    mode = GetIniInt(idsAppName, idsBack, rand() % cIDFACEDOWN) + IDFACEDOWNFIRST-1;
    ChangeBack(PegRange(mode, IDFACEDOWNFIRST, IDFACEDOWN12));

    iCurrency = GetIniInt(idsIntl, idsiCurrency, 0);
    FGetIniString(idsIntl, idssCurrency, szCurrency, TEXT("$"), sizeof(szCurrency));
}


VOID WriteIniFlags(INT wif)
{
    INI ini;

    if(wif & wifOpts)
    {
        ini.w = 0;
        ini.grbit.fStatusBar = fStatusBar;
        ini.grbit.fTimedGame = fTimedGame;
        ini.grbit.fOutlineDrag = fOutlineDrag;
        ini.grbit.fDrawThree = ccrdDeal == 3;
        ini.grbit.fKeepScore = fKeepScore;
        switch(smd)
        {
            default:
                Assert(fFalse);
                break;
            case smdStandard:
                ini.grbit.fSMD = 0;
                break;
            case smdVegas:
                ini.grbit.fSMD = 1;
                break;
            case smdNone:
                ini.grbit.fSMD = 2;
                break;
        }

        FWriteIniInt(idsAppName, idsOpts, ini.w);
    }
    if(wif & wifBack)
            FWriteIniInt(idsAppName, idsBack, modeFaceDown-IDFACEDOWNFIRST+1);
}
