#include "sol.h"
VSZASSERT




VOID FreeGm(GM *pgm)
{
    INT icol;
    COL *pcol;

    if(pgm != NULL)
    {
        for(icol = pgm->icolMac-1; icol >= 0; icol--)
            if((pcol = pgm->rgpcol[icol]) != NULL)
                SendColMsg(pcol, msgcEnd, 0, 0);
        if(pgm == pgmCur)
            pgmCur = NULL;
        FreeUndo(&pgm->udr);
        FreeP(pgm);
    }
}


BOOL FCreateDCBM(HDC hdc, HDC *phdc, HBITMAP *phbmOld, DY dyCol)
{
    HDC hdcT;
    HBITMAP hbm;

    if((hdcT = CreateCompatibleDC(hdc)) == NULL)
        return fFalse;

    if((hbm = CreateCompatibleBitmap(hdc, dxCrd, dyCol)) == NULL)
    {
        Error:
        DeleteDC(hdcT);
        return fFalse;
    }

    if((*phbmOld = SelectObject(hdcT, hbm)) == NULL)
    {
        /* Delete the bitmap */
        DeleteObject(hbm);
        goto Error;
    }
    *phdc = hdcT;

    return fTrue;
}


BOOL FSetDrag(BOOL fOutline)
    {
    HDC hdc;

    fOutlineDrag = fOutline;

    if(fOutline && move.fHdc)
    {
        Assert(move.hdcScreenSave);
        Assert(move.hdcCol);
        Assert(move.hbmScreenSaveOld);
        Assert(move.hbmColOld);
        Assert(move.hdcT);

        DeleteObject(SelectObject(move.hdcCol, move.hbmColOld));
        DeleteDC(move.hdcCol);

        DeleteObject(SelectObject(move.hdcScreenSave, move.hbmScreenSaveOld));
        DeleteDC(move.hdcScreenSave);

        DeleteObject(SelectObject(move.hdcT, move.hbmT));
        DeleteDC(move.hdcT);
        move.fHdc = fFalse;
    }


    if(!fOutline && !move.fHdc)
    {
        hdc = GetDC(hwndApp);
        if(hdc == NULL)
        {
            OOM:
            ErrorIds(idsNoFullDrag);
            fOutlineDrag = fFalse;
            move.fHdc = fFalse;
            return fFalse;
        }

        move.hdcScreen = NULL;

        if(!FCreateDCBM(hdc, &move.hdcScreenSave, &move.hbmScreenSaveOld, pgmCur->dyDragMax))
        {
            ReleaseDC(hwndApp,hdc);
            goto OOM;
        }

        if(!FCreateDCBM(hdc, &move.hdcT, &move.hbmT, pgmCur->dyDragMax))
        {
            OOM1:

            ReleaseDC(hwndApp,hdc);
            DeleteObject(SelectObject(move.hdcScreenSave, move.hbmScreenSaveOld));
            DeleteDC(move.hdcScreenSave);
            goto OOM;
        }

        if(!FCreateDCBM(hdc, &move.hdcCol, &move.hbmColOld, pgmCur->dyDragMax))
        {
            DeleteObject(SelectObject(move.hdcT, move.hbmT));
            DeleteDC(move.hdcT);
            goto OOM1;
        }

        move.fHdc = fTrue;
        ReleaseDC(hwndApp, hdc);
    }
    return fTrue;
}




BOOL FInitGm()
{
    BOOL FInitKlondGm();

    return FInitKlondGm();
}


#ifdef DEBUG
LRESULT SendGmMsg(GM *pgm, INT msgg, WPARAM wp1, LPARAM wp2)
{
    INT imdbg;
    LRESULT wResult;

    Assert(pgm != NULL);
    imdbg = ILogMsg(pgm, msgg, wp1, wp2, fTrue);

    wResult =(*(pgm->lpfnGmProc))(pgm, msgg, wp1, wp2);
    LogMsgResult(imdbg, wResult);
    return wResult;
}

#endif


BOOL DefGmInit(GM *pgm, BOOL fResetScore)
{

    pgm->fDealt = fFalse;
    if(fResetScore)
        pgm->sco = 0;
    pgm->iqsecScore = 0;
    pgm->irep = 0;
    pgm->icolHilight = pgm->icolSel = icolNil;
    pgm->icolKbd = 0;
    pgm->icrdKbd = 0;
    pgm->fInput = fFalse;
    pgm->fWon = fFalse;
    pgm->ccrdDeal = ccrdDeal;
    return fTrue;
}



BOOL DefGmMouseDown(GM *pgm, PT *ppt, INT icolFirst)
{
    INT icol;

    /* sel already in effect */
    if(FSelOfGm(pgm))
        return fFalse;
    if(!pgm->fDealt)
        return fFalse;
    pgm->fInput = fTrue;
    pgm->fButtonDown = fTrue;
    for(icol = icolFirst; icol < pgm->icolMac; icol++)
    {
        if(SendColMsg(pgm->rgpcol[icol], msgcHit, (INT_PTR) ppt, 0) != icrdNil)
        {
            pgm->icolSel = icol;
            pgm->ptMousePrev = ptNil;

            /* KLUDGE:  in col render, we redraw the column after a selection
               is made.  if the mouse isn't moved, no image of the selected
               card shows up.
            */
            if(!fOutlineDrag)
            {
                /* SendGmMsg(pgm, msggMouseMove, (INT_PTR) ppt, 0); */
                pgm->ptMousePrev = *ppt;
            }
            return fTrue;
        }
    }
    return fFalse;
}

BOOL DefGmMouseUp(GM *pgm, PT *pptBogus, BOOL fNoMove)
{
    COL *pcolSel, *pcolHilight;
    BOOL fResult = fFalse;

    pgm->fButtonDown = fFalse;
    if(FSelOfGm(pgm))
    {
        pcolSel = pgm->rgpcol[pgm->icolSel];
        if(FHilightOfGm(pgm))
        {
            pcolHilight = pgm->rgpcol[pgm->icolHilight];
            SendGmMsg(pgm, msggSaveUndo, pgm->icolHilight, pgm->icolSel);
            SendColMsg(pcolHilight, msgcDragInvert, 0, 0);
            if(fNoMove)
            {
                SendColMsg(pcolSel, msgcMouseUp, (INT_PTR) &pgm->ptMousePrev, fTrue);
                fResult = fTrue;
                goto Return;
            }
            SendColMsg(pcolSel, msgcMouseUp, (INT_PTR) &pgm->ptMousePrev, fFalse);
            fResult = SendColMsg(pcolHilight, msgcMove, (INT_PTR) pcolSel, icrdToEnd) &&
                SendGmMsg(pgm, msggScore, (INT_PTR) pcolHilight, (INT_PTR) pcolSel);
            pgm->icolHilight = icolNil;
            if(SendGmMsg(pgm, msggIsWinner, 0, 0))
                SendGmMsg(pgm, msggWinner, 0, 0);
        }
        else
            SendColMsg(pcolSel, msgcMouseUp, (INT_PTR) &pgm->ptMousePrev, fTrue);

        Return:
        SendColMsg(pcolSel, msgcEndSel, fFalse, 0);
        }
    pgm->icolSel = icolNil;
    return fResult;
}



BOOL DefGmMouseDblClk(GM *pgm, PT * ppt)
{
    INT icol;

    for(icol = 0; icol < pgm->icolMac; icol++)
        if(SendColMsg(pgm->rgpcol[icol], msgcDblClk, (INT_PTR) ppt, icol))
            return fTrue;
    return fFalse;
}



// This routine moves all the "playable" cards
// to the four suit stacks.
// It's invoked when the user right-clicks or
// presses Ctrl-A.


BOOL DefGmMouseRightClk(GM *pgm, PT * ppt)
{
    INT icol;
    CRD *pcrd;
    INT icolDest;
    COL *pcolDest;
    BOOL fResult;
    COL *pcol;
    INT  iContinue;

    fResult = fFalse;

    // Keep doing this as long as in every iteration
    // we move one card to the suit stack.
    do
    {
        iContinue = 0;
        for(icol = 0; icol < pgm->icolMac; icol++)
        {
            // We don't want to move cards from one suit stack
            // to another.
            if (icol >= icolFoundFirst && icol < icolFoundFirst+ccolFound)
                continue;

            // Now the column we have is one of the 7 columns
            // or the deck.
            pcol = pgm->rgpcol[icol];

            // If this column contains cards and the top one faces up
            if(pcol->icrdMac > 0 && (pcrd=&pcol->rgcrd[pcol->icrdMac-1])->fUp)
            {
                if(pcol->pmove == NULL)
                    SendColMsg(pcol, msgcSel, icrdEnd, ccrdToEnd);
                Assert(pcol->pmove != NULL);

                // Check if it can be moved to any of the suit stacks.
                for(icolDest = icolFoundFirst; icolDest < icolFoundFirst+ccolFound; icolDest++)
                {
                    pcolDest = pgmCur->rgpcol[icolDest];
                    if(SendColMsg(pcolDest, msgcValidMove, (INT_PTR)pcol, 0))
                    {
                        SendGmMsg(pgmCur, msggSaveUndo, icolDest, icol);
                        fResult = SendColMsg(pcolDest, msgcMove, (INT_PTR) pcol, icrdToEnd) &&
                        (fOutlineDrag || SendColMsg(pcol, msgcRender, pcol->icrdMac-1, icrdToEnd)) &&
                            SendGmMsg(pgmCur, msggScore, (INT_PTR) pcolDest, (INT_PTR) pcol);

                        iContinue ++;

                        if(SendGmMsg(pgmCur, msggIsWinner, 0, 0))
                            SendGmMsg(pgmCur, msggWinner, 0, 0);
                        break;
                    }
                }
            }

            SendColMsg(pcol, msgcEndSel, fFalse, 0);
        }
    } while (iContinue > 0);

    return fResult;
}





BOOL DefGmMouseMove(GM *pgm, PT *ppt)
{
    COL *pcol;
    INT icol;

    if(FSelOfGm(pgm))
    {
        Assert(pgm->icolSel < pgm->icolMac);
        /* draw new outline */
        pcol = pgm->rgpcol[pgm->icolSel];
        SendColMsg(pcol, msgcDrawOutline, (INT_PTR) ppt, (INT_PTR) &pgm->ptMousePrev);
        pgm->ptMousePrev = *ppt;
        for(icol = 0; icol < pgm->icolMac; icol++)
            if(SendColMsg(pgm->rgpcol[icol], msgcValidMovePt, (INT_PTR)pgm->rgpcol[pgm->icolSel], (INT_PTR) ppt) != icrdNil)
            {
                 if(icol != pgm->icolHilight)
                 {
                    if(FHilightOfGm(pgm))
                        SendColMsg(pgm->rgpcol[pgm->icolHilight], msgcDragInvert, 0, 0);
                     pgm->icolHilight = icol;
                     return SendColMsg(pgm->rgpcol[icol], msgcDragInvert, 0, 0);
                 }
                 else
                     return fTrue;
            }
        /* nothing to hilight */
        if(FHilightOfGm(pgm))
        {
            SendColMsg(pgm->rgpcol[pgm->icolHilight], msgcDragInvert, 0, 0);
            pgm->icolHilight = icolNil;
            return fTrue;
        }
    }
    return fFalse;
}


BOOL DefGmPaint(GM *pgm, PAINTSTRUCT *ppaint)
{
    INT icol;
    HDC hdc;

    hdc = HdcSet(ppaint->hdc, 0, 0);

    if(!pgm->fDealt)
        goto Return;
    for(icol = 0; icol < pgm->icolMac; icol++)
        SendColMsg(pgm->rgpcol[icol], msgcPaint, (INT_PTR) ppaint, 0);
Return:
    HdcSet(hdc, 0, 0);
    return fTrue;
}


BOOL DefGmUndo(GM *pgm)
{
    UDR *pudr;

    Assert(!FSelOfGm(pgm));
    pudr = &pgm->udr;
    if(!pudr->fAvail)
        return fFalse;
    Assert(pudr->icol1 != icolNil);
    Assert(pudr->icol2 != icolNil);

    Assert(pudr->icol1 < pgm->icolMax);
    Assert(pudr->icol2 < pgm->icolMax);

    pgm->sco  = pudr->sco;
    pgm->irep = pudr->irep;

    SendGmMsg(pgm, msggChangeScore, 0, 0);


    SendColMsg(pgm->rgpcol[pudr->icol1], msgcCopy, (INT_PTR) pudr->rgpcol[0], fTrue);
    SendColMsg(pgm->rgpcol[pudr->icol2], msgcCopy, (INT_PTR) pudr->rgpcol[1], fTrue);
    /* end any selectons if we had 'em */
    SendColMsg(pgm->rgpcol[pudr->icol1], msgcEndSel, 0, 0);
    SendColMsg(pgm->rgpcol[pudr->icol2], msgcEndSel, 0, 0);

    SendGmMsg(pgm, msggKillUndo, 0, 0);
    return fTrue;
}



/* in future: may want to alloc columns */
BOOL DefGmSaveUndo(GM *pgm, INT icol1, INT icol2)
{
    Assert(icol1 != icolNil);
    Assert(icol2 != icolNil);
    Assert(icol1 < pgm->icolMac);
    Assert(icol2 < pgm->icolMac);
    Assert(icol1 != icol2);

    /* should use msgcCopy, but undo colcls's may not be set correctly */
    bltb(pgm->rgpcol[icol1], pgm->udr.rgpcol[0], sizeof(COL)+(pgm->rgpcol[icol1]->icrdMac-1)*sizeof(CRD));
    bltb(pgm->rgpcol[icol2], pgm->udr.rgpcol[1], sizeof(COL)+(pgm->rgpcol[icol2]->icrdMac-1)*sizeof(CRD));
    pgm->udr.icol1  = icol1;
    pgm->udr.icol2  = icol2;
    pgm->udr.fAvail = fTrue;
    pgm->udr.sco    = pgm->sco;
    pgm->udr.irep   = pgm->irep;

    if(pgm->udr.fEndDeck)
    {
        pgm->udr.fEndDeck = FALSE;
        pgm->udr.irep--;
    }

    return fTrue;
}



#ifdef DEBUG
VOID DisplayKbdSel(GM *pgm)
{
    HDC hdc;
    TCHAR sz[20];
    INT cch;

    hdc = GetDC(hwndApp);
    PszCopy(TEXT("      "), sz);
    cch = CchDecodeInt(sz, pgm->icolKbd);
    TextOut(hdc, 0, 10, sz, 5);
    PszCopy(TEXT("      "), sz);
    cch = CchDecodeInt(sz, pgm->icrdKbd);
    TextOut(hdc, 0, 20, sz, 5);
    PszCopy(TEXT("      "), sz);
    cch = CchDecodeInt(sz, pgm->icolSel);
    TextOut(hdc, 0, 30, sz, 5);
    ReleaseDC(hwndApp, hdc);
}
#endif



VOID NewKbdColAbs(GM *pgm, INT icol)
{
    Assert(icol >= 0);
    Assert(icol < pgm->icolMac);

    if(!SendColMsg(pgm->rgpcol[icol], msgcValidKbdColSel, FSelOfGm(pgm), 0))
        /* beep? */
        return;

    pgm->icolKbd = icol;
    pgm->icrdKbd = SendColMsg(pgm->rgpcol[pgm->icolKbd], msgcNumCards, fFalse, 0)-1;
    if(pgm->icrdKbd < 0)
        pgm->icrdKbd = 0;
}


VOID NewKbdCol(GM *pgm, INT dcol, BOOL fNextGroup)
{
    INT icolNew;

    icolNew = pgm->icolKbd;
    if(icolNew == icolNil)
        icolNew = 0;
    if(dcol != 0)
    {
        do
        {
            icolNew += dcol;
            if(icolNew < 0)
                icolNew = pgm->icolMac-1;
            else if(icolNew >= pgm->icolMac)
                icolNew = 0;

            /* only one col class and looped through all col's */
            if(icolNew == pgm->icolKbd)
                break;
        }
        while (!SendColMsg(pgm->rgpcol[icolNew], msgcValidKbdColSel, FSelOfGm(pgm), 0) ||
                (fNextGroup &&
                    pgm->rgpcol[icolNew]->pcolcls->tcls ==
                       pgm->rgpcol[pgm->icolKbd]->pcolcls->tcls));

    }

    NewKbdColAbs(pgm, icolNew);
}




VOID NewKbdCrd(GM *pgm, INT dcrd)
{
    INT icrdUpMac, icrdMac;
    INT icrdKbdNew;

    icrdUpMac = SendColMsg(pgm->rgpcol[pgm->icolKbd], msgcNumCards, fTrue, 0);
    icrdMac = SendColMsg(pgm->rgpcol[pgm->icolKbd], msgcNumCards, fFalse, 0);

    if(icrdMac == 0)
        icrdKbdNew = 0;
    else
    {
        if(icrdUpMac == 0)
            icrdKbdNew = icrdMac-1;
        else
            icrdKbdNew = PegRange(pgm->icrdKbd+dcrd, icrdMac-icrdUpMac, icrdMac-1);
    }
    if(SendColMsg(pgm->rgpcol[pgm->icolKbd], msgcValidKbdCrdSel, icrdKbdNew, 0))
        pgm->icrdKbd = icrdKbdNew;
}





BOOL DefGmKeyHit(GM *pgm, INT vk)
{
    PT pt, ptCurs;
    COLCLS *pcolcls;

    /* cancel any mouse selections */

    switch(vk)
    {
    case VK_SPACE:
    case VK_RETURN:
        if(!FSelOfGm(pgm))
            {
            /* begin a selection */
            NewKbdCrd(pgm, 0);  /* !!! */
            SendColMsg(pgm->rgpcol[pgm->icolKbd], msgcGetPtInCrd, pgm->icrdKbd, (INT_PTR) &pt);
            if(!SendGmMsg(pgm, msggMouseDown, (INT_PTR) &pt, 0))
                return fFalse;
            NewKbdCol(pgm, 0, fFalse);
            goto Display;
            }
        else
            {
            /* possibly make a move */
            SendGmMsg(pgm, msggMouseUp, 0, fFalse);
            NewKbdCol(pgm, 0, fFalse);
            return fTrue;
            }

    case VK_ESCAPE:
        SendGmMsg(pgm, msggMouseUp, 0, fTrue);
        return fTrue;

    case VK_A:
        if (GetKeyState(VK_CONTROL) < 0)
            SendGmMsg(pgm, msggMouseRightClk, 0, fTrue);
        return fTrue;

    case VK_LEFT:
        /* Should these be VK_CONTROL??? */
        NewKbdCol(pgm, -1, GetKeyState(VK_SHIFT) < 0);
        goto Display;

    case VK_RIGHT:
        NewKbdCol(pgm, 1, GetKeyState(VK_SHIFT) < 0);
        goto Display;

    case VK_UP:
        NewKbdCrd(pgm, -1);
        goto Display;

    case VK_DOWN:
        NewKbdCrd(pgm, 1);
        goto Display;

    case VK_HOME:
        NewKbdColAbs(pgm, 0);
        goto Display;

    case VK_END:
        NewKbdColAbs(pgm, pgm->icolMac-1);
        goto Display;

    case VK_TAB:
        NewKbdCol(pgm, GetKeyState(VK_SHIFT) < 0 ? -1 : 1, fTrue);
Display:
        SendColMsg(pgm->rgpcol[pgm->icolKbd], msgcGetPtInCrd, pgm->icrdKbd, (INT_PTR) &pt);
        ptCurs = pt;
        ClientToScreen(hwndApp, (LPPOINT) &ptCurs);
        if(FSelOfGm(pgm))
        {
            if(SendColMsg(pgm->rgpcol[pgm->icolKbd], msgcNumCards, fFalse, 0) > 0)
            {
                pcolcls = pgm->rgpcol[pgm->icolKbd]->pcolcls;
                ptCurs.y += pcolcls->dyUp;
                /* dxUp ? */
            }
        }

        /* SetCursorPos will cause WM_MOUSEMOVE to be sent */
        SetCursorPos(ptCurs.x, ptCurs.y);
        return fTrue;
    }
    return fFalse;
}



BOOL DefGmChangeScore(GM *pgm, INT cs, INT sco)
{

    if(smd == smdNone)
        return fTrue;
    switch(cs)
        {
    default:
        return fTrue;
    case csAbs:
        pgm->sco = sco;
        break;
    case csDel:
        pgm->sco += sco;
        break;
    case csDelPos:
        pgm->sco = WMax(pgm->sco+sco, 0);
        break;
        }
    StatUpdate();

    return fTrue;
}


BOOL DefGmWinner(GM *pgm)
{
    pgm->fWon = fFalse;
    if(FYesNoAlert(idsDealAgain))
        PostMessage(hwndApp, WM_COMMAND, idsInitiate, 0L);
    return fTrue;
}


INT DefGmProc(GM *pgm, INT msgg, WPARAM wp1, LPARAM wp2)
{

    switch(msgg)
    {
    case msggInit:
        return DefGmInit(pgm, (BOOL)wp1);
    case msggEnd:
        FreeGm(pgm);
        break;

    case msggKeyHit:
        return DefGmKeyHit(pgm, (INT)wp1);

    case msggMouseRightClk:
        return DefGmMouseRightClk(pgm, (PT *)wp1);

    case msggMouseDown: /* wp1 == ppt, wp2 = icolFirst (normally 0) */
        return DefGmMouseDown(pgm, (PT *)wp1, (INT)wp2);

    case msggMouseUp:
        return DefGmMouseUp(pgm, (PT *)wp1, (BOOL)wp2);

    case msggMouseMove:
        return DefGmMouseMove(pgm, (PT *)wp1);

    case msggMouseDblClk:
        return DefGmMouseDblClk(pgm, (PT *)wp1);

    case msggPaint:
        return DefGmPaint(pgm, (PAINTSTRUCT *)wp1);

    case msggDeal:
        Assert(fFalse);
        break;

    case msggUndo:
        return DefGmUndo(pgm);

    case msggSaveUndo:

        return DefGmSaveUndo(pgm, (INT)wp1, (INT)wp2);

    case msggKillUndo:
        /* in future may want to free columns */
        pgm->udr.fAvail = fFalse;
        break;
    case msggIsWinner:
        return fFalse;
    case msggWinner:
        return DefGmWinner(pgm);
    case msggForceWin:
        NYI();
        break;
    case msggTimer:
        return fFalse;
    case msggScore:
        return fTrue;
    case msggChangeScore:
        return DefGmChangeScore(pgm, (INT)wp1, (INT)wp2);

    }

    return fFalse;
}