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

Transfer.c

June 91, JimH     initial code
Oct  91, JimH     port to Win32

Routines for transfering cards and queing cards for transfer are here.

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

#include "freecell.h"
#include "freecons.h"
#include <assert.h>
#include <memory.h>


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

Transfer

This function actually moves the cards.  It both updates the card array,
and draws the bitmaps.  Note that it moves only one card per call.

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

VOID Transfer(HWND hWnd, UINT fcol, UINT fpos, UINT tcol, UINT tpos)
{
    CARD    c;
    HDC     hDC;

    DEBUGMSG(TEXT("Transfer request from (%u, "), fcol);
    DEBUGMSG(TEXT("%u) to ("), fpos);
    DEBUGMSG(TEXT("%u, "), tcol);
    DEBUGMSG(TEXT("%u)\r\n"), tpos);

    assert(fpos < MAXPOS);
    assert(tpos < MAXPOS);

    UpdateWindow(hWnd);     // ensure cards are drawn before animation starts

    if (fcol == TOPROW)     // can't transfer FROM home cells
    {
        if ((fpos > 3) || (card[TOPROW][fpos] == IDGHOST))
            return;
    }
    else
    {
        if ((fpos = FindLastPos(fcol)) == EMPTY)    // or from empty column
            return;

        if (fcol == tcol)               // click and release on same column
        {
            hDC = GetDC(hWnd);
            DrawCard(hDC, fcol, fpos, card[fcol][fpos], FACEUP);
            ReleaseDC(hWnd, hDC);
            return;
        }
    }

    if (tcol == TOPROW)
    {
        if (tpos > 3)                       // if move to home cell
        {
            wCardCount--;
            DisplayCardCount(hWnd);         // update display
            c = card[fcol][fpos];
            home[SUIT(c)] = VALUE(c);       // new card at top of home[suit]
        }
    }
    else
        tpos = FindLastPos(tcol) + 1;       // bottom of column

    Glide(hWnd, fcol, fpos, tcol, tpos);    // send the card on its way

    c = card[fcol][fpos];
    card[fcol][fpos] = EMPTY;
    card[tcol][tpos] = c;

    /* If ACE being moved to home cell, update homesuit array. */

    if (VALUE(c) == ACE && tcol == TOPROW && tpos > 3)
        homesuit[SUIT(c)] = tpos;

    if (tcol == TOPROW)
    {
        hDC = GetDC(hWnd);
        DrawKing(hDC, tpos < 4 ? LEFT : RIGHT, TRUE);
        ReleaseDC(hWnd, hDC);
    }
}


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

MoveCol

User has requested a multi-card move to an empty column

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

VOID MoveCol(UINT fcol, UINT tcol)
{
    UINT freecells;                     // number of free cells
    CARD free[4];                       // locations of free cells
    UINT trans;                         // number to transfer
    INT  i;                             // counter

    assert(fcol != TOPROW);
    assert(tcol != TOPROW);
    assert(card[fcol][0] != EMPTY);

    /* Count number of free cells and put locations in free[] */

    freecells = 0;
    for (i = 0; i < 4; i++)
    {
        if (card[TOPROW][i] == EMPTY)
        {
            free[freecells] = i;
            freecells++;
        }
    }

    /* Find number of cards to transfer */

    if (fcol == TOPROW || tcol == TOPROW)
        trans = 1;
    else
        trans = NumberToTransfer(fcol, tcol);

    if (trans > (freecells+1))                    // don't transfer too many
        trans = freecells+1;

    /* Move to free cells */

    trans--;
    for (i = 0; i < (INT)trans; i++)
        QueueTransfer(fcol, 0, TOPROW, free[i]);

    /* Transfer last card directly */

    QueueTransfer(fcol, 0, tcol, 0);

    /* transfer from free cells to column */

    for (i = trans-1; i >= 0; i--)
        QueueTransfer(TOPROW, free[i], tcol, 0);
}


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

MultiMove

User has chosen to move from one non-empty column to another.

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

VOID MultiMove(UINT fcol, UINT tcol)
{
    CARD free[4];                       // locations of free cells
    UINT freecol[MAXCOL];               // locations of free columns
    UINT freecells;                     // number of free cells
    UINT trans;                         // number to transfer
    UINT col, pos;
    INT  i;                             // counter

    assert(fcol != TOPROW);
    assert(tcol != TOPROW);
    assert(card[fcol][0] != EMPTY);

    /* Count number of free cells and put locations in free[] */

    freecells = 0;
    for (pos = 0; pos < 4; pos++)
    {
        if (card[TOPROW][pos] == EMPTY)
        {
            free[freecells] = pos;
            freecells++;
        }
    }

    /* Find the number of cards to move.  If the number is too big to
       move all at once, push partial results into available columns. */

    trans = NumberToTransfer(fcol, tcol);
    if (trans > (freecells+1))
    {
        i = 0;
        for (col = 1; col < MAXCOL; col++)
            if (card[col][0] == EMPTY)
                freecol[i++] = col;

        /* transfer into free columns until direct transfer can be made */

        i = 0;
        while (trans > (freecells + 1))
        {
            MoveCol(fcol, freecol[i]);
            trans -= (freecells + 1);
            i++;
        }

        MoveCol(fcol, tcol);                    // do last transfer directly

        for (i--; i >= 0; i--)                  // gather cards in free cells
            MoveCol(freecol[i], tcol);
    }
    else                                        // else all one MoveCol()
    {
        MoveCol(fcol, tcol);
    }
}


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

QueueTransfer

In order that multi-card moves happen slowly enough for the user to
watch, they are not moved as soon as they are calculated.  Instead,
they first are queued using this function into the movelist array.

After the request is queued, the card array is updated to reflect the
request.  This is ok because the card array is saved away in shadow
temporarily.  The same logic as in Transfer() is used to update card.

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

VOID QueueTransfer(UINT fcol, UINT fpos, UINT tcol, UINT tpos)
{
    CARD    c;
    MOVE    move;

    assert(moveindex < MAXMOVELIST);
    assert(fpos < MAXPOS);
    assert(tpos < MAXPOS);

    move.fcol = fcol;               // package move request into MOVE type
    move.fpos = fpos;
    move.tcol = tcol;
    move.tpos = tpos;
    movelist[moveindex++] = move;   // store request in array and update index

    /* Now update card array if necessary. */

    if (fcol == TOPROW)
    {
        if ((fpos > 3) || (card[TOPROW][fpos] == IDGHOST))
            return;
    }
    else
    {
        if ((fpos = FindLastPos(fcol)) == EMPTY)
            return;

        if (fcol == tcol)               // click and release on same column
            return;
    }

    if (tcol == TOPROW)
    {
        if (tpos > 3)
        {
            c = card[fcol][fpos];
            home[SUIT(c)] = VALUE(c);
        }
    }
    else
        tpos = FindLastPos(tcol) + 1;

    c = card[fcol][fpos];
    card[fcol][fpos] = EMPTY;
    card[tcol][tpos] = c;

    if (VALUE(c) == ACE && tcol == TOPROW && tpos > 3)
        homesuit[SUIT(c)] = tpos;
}


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

MoveCards

If there are queued transfer requests, this function moves them.

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

VOID MoveCards(HWND hWnd)
{
    UINT     i;

    if (moveindex == 0)             // if there are no queued requests
        return;

    /* restore card to its state before requests got queued. */

    memcpy(&(card[0][0]), &(shadow[0][0]), sizeof(card));

    SetCursor(LoadCursor(NULL, IDC_WAIT));  // set cursor to hourglass
    SetCapture(hWnd);
    ShowCursor(TRUE);

    for (i = 0; i < moveindex; i++)
        Transfer(hWnd, movelist[i].fcol, movelist[i].fpos,
                    movelist[i].tcol, movelist[i].tpos);

    if ((moveindex > 1) || (movelist[0].fcol != movelist[0].tcol))
    {
        cUndo = moveindex;
        EnableMenuItem(GetMenu(hWnd), IDM_UNDO, MF_ENABLED);
    }
    else
    {
        cUndo = 0;
        EnableMenuItem(GetMenu(hWnd), IDM_UNDO, MF_GRAYED);
    }

    moveindex = 0;                      // no cards left to move

    ShowCursor(FALSE);
    SetCursor(LoadCursor(NULL, IDC_ARROW));
    ReleaseCapture();

    if (wCardCount == 0)                // if game is won
    {
        INT     cLifetimeWins;          // wins including .ini stats
        INT     wStreak, wSType;        // streak length and type
        INT     wWins;                  // record win streak
        INT_PTR nResponse;              // dialog box response
        HDC     hDC;
        LONG    lRegResult;

        cUndo = 0;
        EnableMenuItem(GetMenu(hWnd), IDM_UNDO, MF_GRAYED);

        lRegResult = REGOPEN

        if (ERROR_SUCCESS == lRegResult)
        {
            bGameInProgress = FALSE;
            bCheating = FALSE;
            cLifetimeWins = GetInt(pszWon, 0);

            if (gamenumber != oldgamenumber)    // repeats don't count
            {
                cLifetimeWins++;
                cWins++;
                cGames++;
                SetInt(pszWon, cLifetimeWins);
                wSType = GetInt(pszSType, LOST);
                if (wSType == LOST)
                {
                    SetInt(pszSType, WON);
                    wStreak = 1;
                    SetInt(pszStreak, 1);
                }
                else
                {
                    wStreak = GetInt(pszStreak, 0);
                    wStreak++;
                    SetInt(pszStreak, wStreak);
                }

                wWins = GetInt(pszWins, 0);
                if (wWins < wStreak)    // if new record
                {
                    wWins = wStreak;
                    SetInt(pszWins, wWins);
                }
            }

            REGCLOSE
        }

        hDC = GetDC(hWnd);
        Payoff(hDC);
        ReleaseDC(hWnd, hDC);

        bWonState = TRUE;
        nResponse = DialogBox(hInst, TEXT("YouWin"), hWnd, YouWinDlg);

        if (nResponse == IDYES)
            PostMessage(hWnd, WM_COMMAND,
                        bSelecting ? IDM_SELECT : IDM_NEWGAME, 0);

        oldgamenumber = gamenumber;
        gamenumber = 0;                 // turn off mouse handling
    }
    else
        IsGameLost(hWnd);               // check for game lost
}


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

SetCursorShape

This function is called in response to WM_MOUSEMOVE.  If the current pointer
position represents a legal move, the cursor shape changes to indicate this.

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

VOID SetCursorShape(HWND hWnd, UINT x, UINT y)
{
    UINT    tcol = 0, tpos = 0;
    UINT    trans;              // number of cards required to transfer
    BOOL    bFound;             // is cursor over card?
    HDC     hDC;

    /* If we're flipping, cursor is always an hourglass. */

    if (bFlipping)
    {
        SetCursor(LoadCursor(NULL, IDC_WAIT));
        return;
    }

    bFound = Point2Card(x, y, &tcol, &tpos);

    if (bFound && tcol == TOPROW)
    {
        hDC = GetDC(hWnd);

        if (tpos < 4)
            DrawKing(hDC, LEFT, TRUE);
        else
            DrawKing(hDC, RIGHT, TRUE);

        ReleaseDC(hWnd, hDC);
    }

    /* Unless we're chosing a move target, cursor is just an arrow. */

    if (wMouseMode != TO)
    {
        SetCursor(LoadCursor(NULL, IDC_ARROW));
        return;
    }

    /* If we're not on a card, check if we're pointing to an empty
       column (up arrow), otherwise arrow. */

    if (!bFound)
    {
        if ((tcol > 0 && tcol < 9) && (card[tcol][0] == EMPTY))
            SetCursor(LoadCursor(NULL, IDC_UPARROW));
        else
            SetCursor(LoadCursor(NULL, IDC_ARROW));
        return;
    }

    if (tcol != TOPROW)
        tpos = FindLastPos(tcol);

    /* Check for cancel request. */

    if (wFromCol == tcol && wFromPos == tpos)
    {
        SetCursor(LoadCursor(NULL, IDC_ARROW));
        return;
    }

    /* Check moves from or to the top row. */

    if (wFromCol == TOPROW || tcol == TOPROW)
    {
        if (IsValidMove(hWnd, tcol, tpos))
        {
            if (tcol == TOPROW)
                SetCursor(LoadCursor(NULL, IDC_UPARROW));
            else
                SetCursor(LoadCursor(hInst, TEXT("DownArrow")));
        }
        else
            SetCursor(LoadCursor(NULL, IDC_ARROW));
        return;
    }

    /* Check moves between columns. */

    trans = NumberToTransfer(wFromCol, tcol);   // how many required?

    if ((trans > 0) && (trans <= MaxTransfer()))
        SetCursor(LoadCursor(hInst, TEXT("DownArrow")));
    else
        SetCursor(LoadCursor(NULL, IDC_ARROW));
}