/***************************************************************************/
/**                  Microsoft Windows                                    **/
/**            Copyright(c) Microsoft Corp., 1991, 1992                   **/
/***************************************************************************/

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

main.cpp

Aug 92, JimH
May 93, JimH    chico port

Main window callback functions
Other CMainWindow member functions are in main2.cpp, welcome.cpp and ddecb.cpp

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

#include "hearts.h"
#include "main.h"
#include "resource.h"
#include "debug.h"
#include <regstr.h>

#if defined (WINDOWS_ME) && ! defined (USE_MIRRORING)
DWORD  meMsgBox=0;
DWORD  meSystem=0;
#define NLS_RESOURCE_LOCALE_KEY   "Control Panel\\desktop\\ResourceLocale"
#endif


// declare static memberes

CBrush  CMainWindow::m_BgndBrush;
CRect   CMainWindow::m_TableRect;

// declare globals

CMainWindow *pMainWnd;

DDE     *dde;               // same as either ddeClient or ddeServer
HSZ     hszJoin;            // string handles used by DDE
HSZ     hszPass;
HSZ     hszMove;
HSZ     hszStatus;
HSZ     hszGameNumber;
HSZ     hszPassUpdate;

MOVE    move;               // describes move for DDE transaction
MOVE    moveq[8];           // queue of moves waiting to be handled
int     cQdMoves;           // number of moves in above queue
PASS3   passq[4];           // queue of passes waiting to be handled
int     cQdPasses;          // number of passes in above queue
int     nStatusHeight;      // height of status window

// Do not translate these registry strings

const TCHAR szRegPath[]     = REGSTR_PATH_WINDOWSAPPLETS TEXT("\\Hearts");
const TCHAR regvalSound[]   = TEXT("sound");
const TCHAR regvalName[]    = TEXT("name");
const TCHAR regvalRole[]    = TEXT("gamemeister");
const TCHAR regvalServer[]  = TEXT("server");
const TCHAR regvalSpeed[]   = TEXT("speed");
const TCHAR *regvalPName[3]  = { TEXT("p1name"), TEXT("p2name"), TEXT("p3name") };

const TCHAR szHelpFileName[]  = TEXT("mshearts.chm");
const TCHAR szShareName[]     = TEXT("HEARTS$");

CTheApp theApp;                     // start Hearts and run it!

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

CTheApp::InitInstance

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

BOOL CTheApp::InitInstance()
{

    m_pMainWnd = new CMainWindow(m_lpCmdLine);
    m_pMainWnd->ShowWindow(SW_SHOW);        // instead of m_nCmdShow
    m_pMainWnd->UpdateWindow();

    // Start the app off by posting Welcome dialog.

    m_pMainWnd->PostMessage(WM_COMMAND, IDM_WELCOME);

    return TRUE;
}


BEGIN_MESSAGE_MAP( CMainWindow, CFrameWnd )
    ON_COMMAND(IDM_ABOUT,       OnAbout)
    ON_COMMAND(IDM_BOSSKEY,     OnBossKey)
    ON_COMMAND(IDM_CHEAT,       OnCheat)
    ON_COMMAND(IDM_EXIT,        OnExit)
    ON_COMMAND(IDM_HELP,        OnHelp)
//    ON_COMMAND(IDM_HELPONHELP,  OnHelpOnHelp)
    ON_COMMAND(IDM_HIDEBUTTON,  OnHideButton)
//    ON_COMMAND(IDM_SEARCH,      OnSearch)
    ON_COMMAND(IDM_NEWGAME,     OnNewGame)
    ON_COMMAND(IDM_OPTIONS,     OnOptions)
    ON_COMMAND(IDM_QUOTE,       OnQuote)
    ON_COMMAND(IDM_REF,         OnRef)
    ON_COMMAND(IDM_SHOWBUTTON,  OnShowButton)
    ON_COMMAND(IDM_SCORE,       OnScore)
    ON_COMMAND(IDM_SOUND,       OnSound)
    ON_COMMAND(IDM_WELCOME,     OnWelcome)

    ON_BN_CLICKED(IDM_BUTTON,   OnPass)

    ON_WM_CHAR()
    ON_MESSAGE(WM_PRINTCLIENT, OnPrintClient)
    ON_WM_CLOSE()
    ON_WM_CREATE()
    ON_WM_ERASEBKGND()
    ON_WM_LBUTTONDOWN()
    ON_WM_PAINT()
END_MESSAGE_MAP()


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

CMainWindow constructor

creates green background brush, and main hearts window

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

CMainWindow::CMainWindow(LPTSTR lpCmdLine) :
    m_lpCmdLine(lpCmdLine), passdir(LEFT), bCheating(FALSE), bSoundOn(FALSE),
    bTimerOn(FALSE), bConstructed(TRUE), m_FatalErrno(0),
    bEnforceFirstBlood(TRUE)
{
#if !defined (MFC1)
    m_bAutoMenuEnable = FALSE;      // MFC 1.0 compatibility, required for MFC2
#endif
	
    ::cQdMoves = 0;                 // no moves in move queue
    ::cQdPasses = 0;                // no passes either

    for (int i = 0; i < MAXPLAYER; i++)
        p[i] = NULL;

    ResetHandInfo(-1);              // set handinfo struct to default values

    // Check for monochrome

    CDC ic;
    ic.CreateIC(TEXT("DISPLAY"), NULL, NULL, NULL);

    if (ic.GetDeviceCaps(NUMCOLORS) == 2)       // if monochrome
        m_bkgndcolor = RGB(255, 255, 255);      // white background for mono
    else
        m_bkgndcolor = RGB(0, 127, 0);

    ic.DeleteDC();

    m_BgndBrush.CreateSolidBrush(m_bkgndcolor); // destroyed in OnClose()
    LoadAccelTable( TEXT("HeartsAccel") );

    RECT rc;
    SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, 0);

    CRect rect;
    int dy = min(WINHEIGHT, (rc.bottom - rc.top));

    int x, y;
    if (GetSystemMetrics(SM_CYSCREEN) <= 480)   // VGA
    {
        x = (((rc.right - rc.left) - WINWIDTH) / 2) + rc.left;  // centered
        y = rc.top;
    }
    else
    {
        x = CW_USEDEFAULT;
        y = CW_USEDEFAULT;
    }

    rect.SetRect(x, y, x+WINWIDTH, y+dy);

    CString sAppname;
    sAppname.LoadString(IDS_APPNAME);

#if defined (WINDOWS_ME) && ! defined (USE_MIRRORING)
	if (GetSystemMetrics(SM_MIDEASTENABLED))
	{
		char sz[10];
		long cb = sizeof(sz);
		//
		// as we are releasing an enabled version, we need to check the
		// resource locale as well.
		//
		sz[0] = '\0';

		if( RegQueryValue( HKEY_CURRENT_USER, NLS_RESOURCE_LOCALE_KEY, sz, &cb) == ERROR_SUCCESS)

			if ( (cb == 9) && (sz[6] == '0') && ((sz[7] == '1') || (sz[7] == 'd') || (sz[7] == 'D')) )
			{
				meSystem = TRUE;
				meMsgBox = MB_RIGHT | MB_RTLREADING;
			}
	}
#endif

#if defined (WINDOWS_ME) && ! defined (USE_MIRRORING)
    Create( NULL,                                              // default class
            sAppname,                                          // window title
            WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
            WS_MINIMIZEBOX | WS_CLIPCHILDREN,                  // window style
            rect,                                              // size
            NULL,                                              // parent
            TEXT("HeartsMenu"),                                      // menu
            (meSystem ? WS_EX_RTLREADING | WS_EX_RIGHT : 0));  // dwStyle
#else
    Create( NULL,                                       // default class
            sAppname,                                   // window title
            WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
            WS_MINIMIZEBOX | WS_CLIPCHILDREN,           // window style
            rect,                                       // size
            NULL,                                       // parent
            TEXT("HeartsMenu"));                              // menu
#endif
}


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

CMainWindow::OnAbout

displays about box

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

//extern "C" int WINAPI ShellAbout(HWND, LPCSTR, LPCSTR, HICON);

void CMainWindow::OnAbout()
{
    HICON hIcon = ::LoadIcon(AfxGetInstanceHandle(),
                          MAKEINTRESOURCE(AFX_IDI_STD_FRAME));

    CString s;
    s.LoadString(IDS_NETWORK);
    ShellAbout(m_hWnd, s, NULL, hIcon);
}


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

CMainWindow::OnQuote

displays quote box and plays quote.

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

void CMainWindow::OnQuote()
{
    CQuoteDlg quote(this);
    // HeartsPlaySound(SND_QUOTE);
    quote.DoModal();
    HeartsPlaySound(OFF);
}


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

CMainWindow::OnChar, looks space, plays first legal move, or pushes button

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

void CMainWindow::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    // We know the cast below is legal because position 0 is always
    // the local human.

    local_human *p0 = (local_human *)p[0];

    int mode = p0->GetMode();

    if ((nChar != (UINT)' ') || (p0->IsTimerOn()))
        return;

    if (mode != PLAYING)
        return;

    p0->SetMode(WAITING);

    POINT loc;

    for (SLOT s = 0; s < MAXSLOT; s++)
    {
        if (p0->GetCardLoc(s, loc))
        {
            if (p0->PlayCard(loc.x, loc.y, handinfo, bCheating, FALSE))
            {
                return;
            }
        }
    }

    p0->SetMode(PLAYING);
}


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

CMainWindow::OnCheat -- toggles bCheating used to show all cards face up.

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

void CMainWindow::OnCheat()
{
    RegEntry    Reg(szRegPath);
    const TCHAR val[] = TEXT("ZB");
    TCHAR        buf[20];

    Reg.GetString(val, buf, sizeof(buf));
    if (buf[0] != TEXT('4') || buf[1] != TEXT('2'))
        return;

    bCheating = !bCheating;
    InvalidateRect(NULL, TRUE);     // redraw main hearts window

    CMenu *pMenu = GetMenu();
    pMenu->CheckMenuItem(IDM_CHEAT, bCheating ? MF_CHECKED : MF_UNCHECKED);
}


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

CMainWindow::OnClose -- cleans up background brush, deletes players, etc.

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

void CMainWindow::OnClose()
{
    m_BgndBrush.DeleteObject();

    for (int i = 0; i < 4; i++)
    {
        if (p[i])
        {
            delete p[i];
            p[i] = NULL;
        }
    }

    DestroyStrHandles();
    if (ddeClient)
        delete ddeClient;

    if (ddeServer)
        delete ddeServer;

    dde = NULL;
    ddeClient = NULL;
    ddeServer = NULL;

    ::HtmlHelp(::GetDesktopWindow(), szHelpFileName, HH_CLOSE_ALL, 0);

    {
        RegEntry Reg(szRegPath);
        Reg.FlushKey();
    }

    DestroyWindow();
}


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

CMainWindow::OnCreate -- creates pass button child window & player objects.
                         also initializes some of the data members

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

int CMainWindow::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    ::pMainWnd = this;

    if (!bConstructed)
    {
        FatalError(IDS_MEMORY);
        return -1;
    }

    // Check for existence of cards.dll

    SetErrorMode(SEM_NOOPENFILEERRORBOX);
    HINSTANCE hCardsDLL = LoadLibrary(TEXT("CARDS.DLL"));
    if (hCardsDLL < (HINSTANCE)HINSTANCE_ERROR)
    {
        FatalError(IDS_CARDSDLL);
        bConstructed = FALSE;
        return -1;
    }
    ::FreeLibrary(hCardsDLL);

    CClientDC dc(this);
    TEXTMETRIC  tm;

    ::srand((unsigned) ::time(NULL));       // set rand() seed
    ddeClient = NULL;
    ddeServer = NULL;

    dc.GetTextMetrics(&tm);
    int nTextHeight = tm.tmHeight + tm.tmExternalLeading;
    m_StatusHeight = nTextHeight + 11;
    GetClientRect(m_TableRect);

    m_TableRect.bottom -= m_StatusHeight;

    bConstructed = TRUE;

    // Player 0 is constructed as the gamemeister.  This
    // initializes lots of good stuff which is used later.
    // If player 0 doesn't happen to be a real gamemeister,
    // this gets fixed up in OnWelcome().

    p[0] = new local_human(0);      // display status bar

    if (p[0] == NULL)
    {
        bConstructed = FALSE;
        return -1;
    }

    // Construct pushbutton

    int cxChar = tm.tmAveCharWidth;
    int cyChar = tm.tmHeight + tm.tmExternalLeading;
    int nWidth = (60 * cxChar) / 4;
    int nHeight = (14 * cyChar) / 8;
    int x = (m_TableRect.right / 2) - (nWidth / 2);
    int y = m_TableRect.bottom - card::dyCrd - (2 * POPSPACING) - nHeight;
    CRect rect;
    rect.SetRect(x, y, x+nWidth, y+nHeight);

    if (!m_Button.Create(TEXT(""), WS_CHILD | BS_PUSHBUTTON, rect, this, IDM_BUTTON))
    {
        bConstructed = FALSE;
        return -1;
    }

    // check for sound capability

    RegEntry    Reg(szRegPath);

    bHasSound = SoundInit();
    if (bHasSound)
    {
        if (Reg.GetNumber(regvalSound, FALSE))
        {
            CMenu *pMenu = GetMenu();
            pMenu->CheckMenuItem(IDM_SOUND, MF_CHECKED);
            bSoundOn = TRUE;
        }
    }
    else
    {
        CMenu *pMenu = GetMenu();
        pMenu->EnableMenuItem(IDM_SOUND, MF_GRAYED);
    }

    card c;
    int  nStepSize;
    DWORD dwSpeed = Reg.GetNumber(regvalSpeed, IDC_NORMAL);

    if (dwSpeed == IDC_FAST)
        nStepSize = 60;
    else if (dwSpeed == IDC_SLOW)
        nStepSize = 5;
    else
        nStepSize = 15;

    c.SetStepSize(nStepSize);

    return (bConstructed ? 0 : -1);
}


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

CMainWindow::OnEraseBkgnd -- required to draw background green

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

BOOL CMainWindow::OnEraseBkgnd(CDC *pDC)
{
    if (!m_BgndBrush.m_hObject)         // if background brush is not valid
        return FALSE;

    m_BgndBrush.UnrealizeObject();
    CBrush *pOldBrush = pDC->SelectObject(&m_BgndBrush);
    pDC->PatBlt(0, 0, WINWIDTH, WINHEIGHT, PATCOPY);
    pDC->SelectObject(pOldBrush);
    return FALSE;
}



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

CMainWindow::OnLButtonDown

Handles human selecting card to play or pass.

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

void CMainWindow::OnLButtonDown(UINT nFlags, CPoint point)
{
    // We know the cast below is legal because position 0 is always
    // the local human.

#ifdef USE_MIRRORING
    CRect rect;
	DWORD ProcessDefaultLayout;
	if (GetProcessDefaultLayout(&ProcessDefaultLayout))
		if (ProcessDefaultLayout == LAYOUT_RTL)
		{
    	GetClientRect(&rect);
		point.x = rect.right - rect.left - point.x;
		}
#endif

    local_human *p0 = (local_human *)p[0];

    if (p0->IsTimerOn())    // ignore mouse clicks if timer running
        return;

    modetype mode = p0->GetMode();

    if (mode == SELECTING)
    {
        p0->PopCard(m_BgndBrush, point.x, point.y);
        return;
    }
    else if (mode != PLAYING)
        return;

    p0->SetMode(WAITING);
    if (p0->PlayCard(point.x, point.y, handinfo, bCheating))    // valid card?
        return;

    // move wasn't legal, so back to PLAYING mode

    p0->SetMode(PLAYING);
}


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

CMainWindow::OnNewGame

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

void CMainWindow::OnNewGame()
{
    passdir = LEFT;                 // each new game must start with LEFT

    bAutostarted = FALSE;           // means dealer has agreed to play at least

    CMenu *pMenu = GetMenu();
    pMenu->EnableMenuItem(IDM_NEWGAME, MF_GRAYED);

    if (role == GAMEMEISTER)
    {

        BOOL    bNewPlayers = FALSE;

        for (int i = 1; i < MAXPLAYER; i++)
        {
            if (!p[i])
            {
                bNewPlayers = TRUE;
                p[i] = new computer(i);
                if (!p[i])
                {
                    bConstructed = FALSE;
                    return;
                }
            }
        }

        if (bNewPlayers)
            ddeServer->PostAdvise(hszStatus);

        m_gamenumber = ::rand();
        ddeServer->PostAdvise(hszGameNumber);
    }

    ResetHandInfo(-1);

    ::srand(m_gamenumber);

    {
        CScoreDlg score(this);
        score.ResetScore();
    }                           // destruct score

    TRACE1("\n\ngame number is %d\n\n", m_gamenumber);
    DUMP();
    TRACE0("\n\n");

    Shuffle();
}


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

CMainWindow::OnOptions -- user requests options dialog from menu

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

void CMainWindow::OnOptions()
{
    COptionsDlg optionsdlg(this);
    optionsdlg.DoModal();
}


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

CMainWindow::OnPaint

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

void CMainWindow::OnPaint()
{
    CPaintDC dc( this );
#ifdef USE_MIRRORING
	SetLayout(dc.m_hDC, 0);
	SetLayout(dc.m_hAttribDC, 0);
#endif

    // players must be painted in order starting with playerled so that
    // cards in centre overlap correctly

    if (bConstructed)
    {
        int start = Id2Pos(handinfo.playerled % 4);

        // check that someone has started

        if (start >= 0)
        {
            for (int i = start; i < (MAXPLAYER+start); i++)
            {
                int pos = i % 4;
                if (p[pos])
                {
                    if (p[pos]->GetMode() == SCORING)
                    {
                        p[pos]->DisplayHeartsWon(dc);
                    }
                    else
                    {
                        p[pos]->Draw(dc, bCheating);
                        p[pos]->MarkSelectedCards(dc);
                    }
                }
            }
        }
    }
}


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

CMainWindow::OnPass

This function handles the local human pressing the button either to
pass selected cards or to accept cards passed.

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

void CMainWindow::OnPass()
{
    if (p[0]->GetMode() == ACCEPTING)       // OK (accepting passed cards)
    {
        m_Button.ShowWindow(SW_HIDE);
        m_Button.SetWindowText(TEXT(""));
        p[0]->SetMode(WAITING);             // local human pushed the button

        CRect rect;
        p[0]->GetCoverRect(rect);

        for (SLOT s = 0; s < MAXSLOT; s++)
            p[0]->Select(s, FALSE);

        InvalidateRect(&rect, TRUE);
        UpdateWindow();

        FirstMove();

        for (int i = 0; i < ::cQdMoves; i++)
            HandleMove(::moveq[i]);

        ::cQdMoves = 0;
        ::cQdPasses = 0;

        return;
    }

    m_Button.EnableWindow(FALSE);
    p[0]->SetMode(DONE_SELECTING);

    BOOL bReady = TRUE;

    for (int i = 1; i < MAXPLAYER; i++)
        if (p[i]->GetMode() != DONE_SELECTING)
            bReady = FALSE;

    if (!bReady)
        p[0]->UpdateStatus(IDS_PASSWAIT);

    if (role == GAMEMEISTER)
    {
        ddeServer->PostAdvise(hszPass);     // let other players know
    }
    else
    {
        PASS3   pass3;

        pass3.id = m_myid;
        pass3.passdir = passdir;
        p[0]->ReturnSelectedCards(pass3.cardid);
        ddeClient->Poke(hszPass, &pass3, sizeof(pass3));
    }

    if (bReady)
        HandlePassing();
}


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

CMainWindow::OnRef

After a human or a computer plays a card, they must
PostMessage(WM_COMMAND, IDM_REF)
which causes this routine (the referee) to be called.

Ref does the following:
    - updates handinfo data struct
    - calls HeartsPlaySound() if appropriate
    - determines if the hand is over or, if not, whose turn is next

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

void CMainWindow::OnRef()
{
    card *c = handinfo.cardplayed[handinfo.turn];

    if (!handinfo.bHeartsBroken)
    {
        if (c->Suit() == HEARTS)
        {
            handinfo.bHeartsBroken = TRUE;
            HeartsPlaySound(SND_BREAK);
        }
    }

    if (c->ID() == BLACKLADY)
    {
        handinfo.bQSPlayed = TRUE;
        HeartsPlaySound(SND_QUEEN);
    }

/* ------------------------------------------------
#if defined(_DEBUG)
    TRACE("[%d] ", m_myid);
    TRACE("h.turn %d, ", handinfo.turn);
    TRACE("led %d, ", handinfo.playerled);
    for (int i = 0; i < 4; i++)
    {
        if (handinfo.cardplayed[i])
            { CDNAME(handinfo.cardplayed[i]); }
        else
            { TRACE("-- "); }
    }
    TRACE("\n",);
#endif
------------------------------------------------ */

    int pos = Id2Pos(handinfo.turn);
    SLOT slot = p[pos]->GetSlot(handinfo.cardplayed[handinfo.turn]->ID());

#if defined(_DEBUG)
    if (p[pos]->IsHuman())
        ((human *)p[pos])->DebugMove(slot);
#endif

    p[pos]->GlideToCentre(slot, pos==0 ? TRUE : bCheating);

    handinfo.turn++;
    handinfo.turn %= 4;

    int newpos = Id2Pos(handinfo.turn);

    if (handinfo.turn == handinfo.playerled)
    {
        EndHand();
    }
    else
    {
        p[newpos]->SelectCardToPlay(handinfo, bCheating);

        if (newpos != 0)
            ((local_human *)p[0])->WaitMessage(p[newpos]->GetName());
    }
}


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

CMainWindow::OnScore -- user requests score dialog from menu

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

void CMainWindow::OnScore()
{
    CScoreDlg scoredlg(this);       // this constructor does not add new info
    scoredlg.DoModal();
}


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

CMainWindow::DoSort

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

void CMainWindow::DoSort()
{
    for (int i = 0; i < (bCheating ? MAXPLAYER : 1); i++)
    {
        CRect   rect;
        int     id;             // card in play for this player

        if (handinfo.cardplayed[i] == NULL)
            id = EMPTY;
        else
            id = handinfo.cardplayed[i]->ID();

        p[i]->Sort();

        if (id != EMPTY)    // if this player has a card in play, restore it
        {
            for (SLOT s = 0; s < MAXSLOT; s++)
            {
                if (p[i]->GetID(s) == id)
                {
                    handinfo.cardplayed[i] = p[i]->Card(s);
                    break;
                }
            }
        }

        p[i]->GetCoverRect(rect);
        InvalidateRect(&rect, TRUE);
    }
}


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

CMainWindow::OnSound()

request sound on or off from menu.

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

void CMainWindow::OnSound()
{
    RegEntry    Reg(szRegPath);

    bSoundOn = !bSoundOn;

    CMenu *pMenu = GetMenu();
    pMenu->CheckMenuItem(IDM_SOUND, bSoundOn ? MF_CHECKED : MF_UNCHECKED);

    if (bSoundOn)
        Reg.SetValue(regvalSound, 1);
    else
        Reg.DeleteValue(regvalSound);
}


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

CMainWindow::OnPrintClient()

Draw background into the specified HDC.  This is used when drawing
the "Pass" button in the Luna style.

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

LRESULT CMainWindow::OnPrintClient(WPARAM wParam, LPARAM lParam)
{
    CDC dc;
    CRect rect;

    dc.Attach((HDC)wParam);
    GetClientRect(&rect);
    dc.FillRect(&rect, &m_BgndBrush);
    dc.Detach();
   
    return 1;
}