//
//    CardLib - CardRegion drawing support
//
//    Freeware
//    Copyright J Brown 2001
//

#include "cardlib.h"

HPALETTE UseNicePalette(HDC hdc, HPALETTE hPalette);
void PaintRect(HDC hdc, RECT *rect, COLORREF colour);
void CardBlt(HDC hdc, int x, int y, int nCardNum);
void DrawCard(HDC hdc, int x, int y, HDC hdcSource, int width, int height);

//
//    Draw specified card at position x, y
//    xoff   - source offset from left of card
//    yoff   - source offset from top of card
//    width  - width to draw
//    height - height to draw
//
void CardBlt(HDC hdc, int x, int y, int nCardNum)//, int xoff, int yoff, int width, int height)
{
    int sx = nCardNum * __cardwidth;
    int sy = 0;
    int width  = __cardwidth;
    int height = __cardheight;

    //draw main center band
    BitBlt(hdc, x+2, y, width - 4, height, __hdcCardBitmaps, sx+2, sy+0, SRCCOPY);

    //draw the two bits to the left
    BitBlt(hdc, x,   y+2, 1, height - 4, __hdcCardBitmaps, sx+0, sy+2, SRCCOPY);
    BitBlt(hdc, x+1, y+1, 1, height - 2, __hdcCardBitmaps, sx+1, sy+1, SRCCOPY);

    //draw the two bits to the right
    BitBlt(hdc, x+width-2, y+1, 1, height - 2, __hdcCardBitmaps, sx+width-2, sy+1, SRCCOPY);
    BitBlt(hdc, x+width-1, y+2, 1, height - 4, __hdcCardBitmaps, sx+width-1, sy+2, SRCCOPY);
}

//
//    Draw a shape this this:
//
//   ++++++++++++
//  ++++++++++++++
// ++            ++
//
void DrawHorzCardStrip(HDC hdc, int x, int y, int nCardNum, int height, BOOL fDrawTips)
{
    int sx  = nCardNum * __cardwidth;
    int sy  = 0;
    int one = 1;
    int two = 2;
    BOOL tips = fDrawTips ? FALSE : TRUE;

    if(height == 0) return;

    if(height < 0)
    {
        sy = sy + __cardheight;
        y  -= height;
        one = -one;
        two = -two;
    }

    // draw the main vertical band
    //
    BitBlt(hdc, x + 2, y, __cardwidth - 4, height, __hdcCardBitmaps, sx+2, sy, SRCCOPY);

    //if(height <= 1) return;

    // draw the "lips" at the left and right
    BitBlt(hdc, x+1,             y+one, 1, height-one*tips, __hdcCardBitmaps, sx+1,             sy+one, SRCCOPY);
    BitBlt(hdc, x+__cardwidth-2, y+one, 1, height-one*tips, __hdcCardBitmaps, sx+__cardwidth-2, sy+one, SRCCOPY);

    //if(height <= 2) return;

    // draw the outer-most lips
    BitBlt(hdc, x,               y+two, 1, height-two*tips, __hdcCardBitmaps, sx,               sy+two, SRCCOPY);
    BitBlt(hdc, x+__cardwidth-1, y+two, 1, height-two*tips, __hdcCardBitmaps, sx+__cardwidth-1, sy+two, SRCCOPY);
}

//
//    Draw a shape like this:
//
//     +++
//      +++
//   +++
//   +++
//   +++
//   +++
//   +++
//   +++
//    +++
//     +++
//
//
void DrawVertCardStrip(HDC hdc, int x, int y, int nCardNum, int width, BOOL fDrawTips)
{
    int sx  = nCardNum * __cardwidth;
    int sy  = 0;
    int one = 1;
    int two = 2;
    BOOL tips = fDrawTips ? FALSE : TRUE;

    if(width == 0) return;


    if(width < 0)
    {
        sx = sx + __cardwidth;
        x  -= width;
        one = -1;
        two = -2;
    }

    // draw the main vertical band
    //
    BitBlt(hdc, x, y + 2, width, __cardheight - 4, __hdcCardBitmaps, sx, sy+2, SRCCOPY);

    //if(width <= 1) return;

    // draw the "lips" at the top and bottom
    BitBlt(hdc, x+one, y+1,              width-one*tips, 1, __hdcCardBitmaps, sx+one, sy + 1,              SRCCOPY);
    BitBlt(hdc, x+one, y+__cardheight-2, width-one*tips, 1, __hdcCardBitmaps, sx+one, sy + __cardheight-2, SRCCOPY);

    //if(width <= 2) return;

    // draw the outer-most lips
    BitBlt(hdc, x+two, y,                width-two*tips, 1, __hdcCardBitmaps, sx+two, sy,                  SRCCOPY);
    BitBlt(hdc, x+two, y+__cardheight-1, width-two*tips, 1, __hdcCardBitmaps, sx+two, sy + __cardheight-1, SRCCOPY);
}

//
//    xdir -   <0 or >0
//    ydir -   <0 or >0
//
void DrawCardCorner(HDC hdc, int x, int y, int cardval, int xdir, int ydir)
{
    int sx = cardval * __cardwidth;
    int sy = 0;

    HDC hdcSource = __hdcCardBitmaps;

    if(xdir < 0)
    {
        x  += __cardwidth + xdir - 1;
        sx += __cardwidth + xdir - 1;
    }
    else
    {
        x  += xdir;
        sx += xdir;
    }

    if(ydir < 0)
    {
        y  += __cardheight + ydir - 1;
        sy += __cardheight + ydir - 1;
    }
    else
    {
        y  += ydir;
        sy += ydir;
    }

    //convert x,y directions to -1, +1
    xdir = xdir < 0 ? -1 : 1;
    ydir = ydir < 0 ? -1 : 1;

    SetPixel(hdc, x+xdir, y ,     GetPixel(hdcSource, sx+xdir, sy));
    SetPixel(hdc, x,      y,      GetPixel(hdcSource, sx,      sy));
    SetPixel(hdc, x,      y+ydir, GetPixel(hdcSource, sx,      sy+ydir));

}

//
//    Draw a card (i.e. miss out the corners)
//
void DrawCard(HDC hdc, int x, int y, HDC hdcDragCard, int width, int height)
{
    //draw main center band
    BitBlt(hdc, x+2, y, width - 4, height, hdcDragCard, 2, 0, SRCCOPY);

    //draw the two bits to the left
    BitBlt(hdc, x,   y+2, 1, height - 4, hdcDragCard, 0, 2, SRCCOPY);
    BitBlt(hdc, x+1, y+1, 1, height - 2, hdcDragCard, 1, 1, SRCCOPY);

    //draw the two bits to the right
    BitBlt(hdc, x+width-2, y+1, 1, height - 2, hdcDragCard, width-2, 1, SRCCOPY);
    BitBlt(hdc, x+width-1, y+2, 1, height - 4, hdcDragCard, width-1, 2, SRCCOPY);
}

//
//    Clip a card SHAPE - basically any rectangle
//  with rounded corners
//
int ClipCard(HDC hdc, int x, int y, int width, int height)
{
    ExcludeClipRect(hdc, x+2,        y,     x+2+width-4, y+  height);
    ExcludeClipRect(hdc, x,            y+2, x+1,          y+2+height-4);
    ExcludeClipRect(hdc, x+1,        y+1, x+2,          y+1+height-2);
    ExcludeClipRect(hdc, x+width-2, y+1, x+width-2+1, y+1+height-2);
    ExcludeClipRect(hdc, x+width-1, y+2, x+width-1+1, y+2+height-4);
    return 0;
}

void CardRegion::Clip(HDC hdc)
{
    int numtoclip;

    if(fVisible == false)
        return;

    Update();                //Update this stack's size+card count
    numtoclip = nNumApparentCards;

    //if we are making this stack flash on/off, then only
    //clip the stack for drawing if the flash is in its ON state
    if(nFlashCount != 0)
    {
        if(fFlashVisible == FALSE)
            numtoclip = 0;
    }

    //if offset along a diagonal
    if(xoffset != 0 && yoffset != 0 && cardstack.NumCards() != 0)
    {
        for(int j = 0; j < numtoclip; j ++)
        {
            ClipCard(hdc, xpos + xoffset * j, ypos + yoffset * j, __cardwidth, __cardheight);
        }
    }
    //otherwise if just offset along a horizontal/vertical axis
    else
    {
        if(yoffset < 0 && numtoclip > 0)
        {
            ClipCard(hdc, xpos, ypos-((numtoclip-1)*-yoffset), width, height);
        }
        else if(xoffset < 0 && numtoclip > 0)
        {
            ClipCard(hdc, xpos-((numtoclip-1)*-xoffset), ypos, width, height);
        }
        else
        {
            ClipCard(hdc, xpos, ypos, width, height);
        }
    }

}

void CardRegion::Render(HDC hdc)
{
    int cardnum = 0;
    int numtodraw;
    BOOL fDrawTips;

    Update();            //Update this stack's card count + size

    numtodraw = nNumApparentCards;

    if(nFlashCount != 0)
    {
        if(fFlashVisible == false)
            numtodraw = 0;
    }

    if(fVisible == 0) return;

    cardnum = cardstack.NumCards() - numtodraw;
    int counter;

    for(counter = 0; counter < numtodraw; counter++)
    {
        int cardval;

        int x = xoffset * counter + xpos;
        int y = yoffset * counter + ypos;

        //if about to draw last card, then actually draw the top card
        if(counter == numtodraw - 1) cardnum = cardstack.NumCards() - 1;

        Card card = cardstack.cardlist[cardnum];
        cardval = card.Idx();

        if(card.FaceDown())
            cardval = nBackCardIdx;    //card-back

        //only draw the visible part of the card
        if(counter < numtodraw - 1)
        {
            if(yoffset != 0 && xoffset != 0)
                fDrawTips = FALSE;
            else
                fDrawTips = TRUE;

            if((yoffset != 0 && abs(xoffset) == 1) || (xoffset != 0 && abs(yoffset) == 1))
                fDrawTips = TRUE;

            //draw horizontal strips
            if(yoffset > 0)
            {
                DrawHorzCardStrip(hdc, x, y, cardval, yoffset, fDrawTips);
            }
            else if(yoffset < 0)
            {
                DrawHorzCardStrip(hdc, x, y+__cardheight+yoffset, cardval, yoffset, fDrawTips);
            }

            //draw some vertical bars
            if(xoffset > 0)
            {
                DrawVertCardStrip(hdc, x, y, cardval, xoffset, fDrawTips);
            }
            else if(xoffset < 0)
            {
                DrawVertCardStrip(hdc, x+__cardwidth+xoffset, y, cardval, xoffset, fDrawTips);
            }

            if(yoffset != 0 && xoffset != 0)//fDrawTips == FALSE)
            {
                //if we didn't draw any tips, then this is a 2-dim stack
                //(i.e, it goes at a diagonal).
                //in this case, we need to fill in the small triangle in
                //each corner!
                DrawCardCorner(hdc, x, y, cardval, xoffset, yoffset);
            }
        }
        //if the top card, draw the whole thing
        else
        {
            CardBlt(hdc, x, y, cardval);
        }

        cardnum ++;

    } //end of index

    if(counter == 0)    //if the cardstack is empty, then draw it that way
    {
        int x = xpos;
        int y = ypos;

        switch(uEmptyImage)
        {
        default:
        case CS_EI_NONE:
            //this wipes the RECT variable, so watch out!
            //SetRect(&rect, x, y, x+__cardwidth, y+__cardheight);
            //PaintRect(hdc, &rect, MAKE_PALETTERGB(crBackgnd));
            parentWnd.PaintCardRgn(hdc, x, y, __cardwidth, __cardheight, x, y);
            break;

        case CS_EI_SUNK:
            DrawCard(hdc, x, y, __hdcPlaceHolder, __cardwidth, __cardheight);
            break;

        case CS_EI_CIRC:
        case CS_EI_X:
            CardBlt(hdc, x, y, uEmptyImage);
            break;
        }

    }

    return;
}

int calc_offset(int offset, int numcards, int numtodrag, int realvisible)
{
    if(offset >= 0)
        return -offset * numcards;
    else
        return -offset * (numtodrag)       +
               -offset * (realvisible - 1);
}

void CardRegion::PrepareDragBitmaps(int numtodrag)
{
    RECT rect;
    HDC hdc;
    int icard;
    int numcards = cardstack.NumCards();
    int xoff, yoff;

    if(nThreedCount > 1)
    {
        PrepareDragBitmapsThreed(numtodrag);
        return;
    }

    //work out how big the bitmaps need to be
    nDragCardWidth  = (numtodrag - 1) * abs(xoffset) + __cardwidth;
    nDragCardHeight = (numtodrag - 1) * abs(yoffset) + __cardheight;

    //Create bitmap for the back-buffer
    hdc = GetDC(NULL);
    hdcBackGnd = CreateCompatibleDC(hdc);
    hbmBackGnd = CreateCompatibleBitmap(hdc, nDragCardWidth, nDragCardHeight);
    SelectObject(hdcBackGnd, hbmBackGnd);

    //Create bitmap for the drag-image
    hdcDragCard = CreateCompatibleDC(hdc);
    hbmDragCard = CreateCompatibleBitmap(hdc, nDragCardWidth, nDragCardHeight);
    SelectObject(hdcDragCard, hbmDragCard);
    ReleaseDC(NULL, hdc);

    UseNicePalette(hdcBackGnd,  __hPalette);
    UseNicePalette(hdcDragCard, __hPalette);

    int realvisible = numcards / nThreedCount;

    //if(numcards > 0 && realvisible == 0) realvisible = 1;
    int iwhichcard = numcards - 1;
    if(nThreedCount == 1) iwhichcard = 0;

    //grab the first bit of background so we can prep the back buffer; do this by
    //rendering the card stack (minus the card we are dragging) to the temporary
    //background buffer, so it appears if we have lifted the card from the stack
    //PaintRect(hdcBackGnd, &rect, crBackgnd);
    SetRect(&rect, 0, 0, nDragCardWidth, nDragCardHeight);

    xoff = calc_offset(xoffset, numcards, numtodrag, realvisible);
    yoff = calc_offset(yoffset, numcards, numtodrag, realvisible);

    parentWnd.PaintCardRgn(hdcBackGnd, 0, 0, nDragCardWidth, nDragCardHeight, xpos - xoff,    ypos - yoff);

    //
    //    Render the cardstack into the back-buffer. The stack
    //    has already had the dragcards removed, so just draw
    //    what is left
    //
    for(icard = 0; icard < realvisible; icard++)
    {
        Card card = cardstack.cardlist[iwhichcard];
        int nCardVal;

        nCardVal = card.FaceUp() ? card.Idx() : nBackCardIdx;

        xoff = xoffset * icard + calc_offset(xoffset, numcards, numtodrag, realvisible);//- xoffset * ((numcards+numtodrag) / nThreedCount - numtodrag);
        yoff = yoffset * icard + calc_offset(yoffset, numcards, numtodrag, realvisible);//- yoffset * ((numcards+numtodrag) / nThreedCount - numtodrag);

        CardBlt(hdcBackGnd, xoff, yoff, nCardVal);
        iwhichcard++;
    }

    //
    // If there are no cards under this one, just draw the place holder
    //
    if(numcards == 0)
    {
        int xoff = 0, yoff = 0;

        if(xoffset < 0)    xoff = nDragCardWidth  -  __cardwidth;
        if(yoffset < 0)    yoff = nDragCardHeight -  __cardheight;

        switch(uEmptyImage)
        {
        case CS_EI_NONE:
            //No need to draw anything: We already cleared the
            //back-buffer before the main loop..

            //SetRect(&rc, xoff, yoff, xoff+ __cardwidth, yoff + __cardheight);
            //PaintRect(hdcBackGnd, &rc, MAKE_PALETTERGB(crBackgnd));
            //parentWnd.PaintCardRgn(hdcBackGnd, xoff, yoff, __cardwidth, __cardheight, xpos, ypos);// + xoff, ypos + yoff);
            break;

        case CS_EI_SUNK:
            DrawCard(hdcBackGnd, xoff, yoff, __hdcPlaceHolder, __cardwidth, __cardheight);
            break;

        case CS_EI_CIRC:
        case CS_EI_X:
            CardBlt(hdc, xoff, yoff, uEmptyImage);
            break;
        }
    }

    //
    //    now render the drag-cards into the dragcard image
    //
    PaintRect(hdcDragCard, &rect, crBackgnd);

    for(icard = 0; icard < numtodrag; icard++)
    {
        int nCardVal;

        if(xoffset >= 0) xoff =  xoffset * icard;
        else              xoff = -xoffset * (numtodrag - icard - 1);

        if(yoffset >= 0) yoff =  yoffset * icard;
        else             yoff = -yoffset * (numtodrag - icard - 1);

        Card card = dragstack.cardlist[icard];

        nCardVal = card.FaceUp() ? card.Idx() : nBackCardIdx;

        CardBlt(hdcDragCard, xoff, yoff, nCardVal);
    }
}

void CardRegion::PrepareDragBitmapsThreed(int numtodrag)
{
    RECT rect;
    HDC hdc;
    int icard;
    int numunder = 0;
    int iwhichcard;

    int numcards = cardstack.NumCards();

    //work out how big the bitmaps need to be
    nDragCardWidth  = (numtodrag - 1) * abs(xoffset) + __cardwidth;
    nDragCardHeight = (numtodrag - 1) * abs(yoffset) + __cardheight;

    //Create bitmap for the back-buffer
    hdc = GetDC(NULL);
    hdcBackGnd = CreateCompatibleDC(hdc);
    hbmBackGnd = CreateCompatibleBitmap(hdc, nDragCardWidth, nDragCardHeight);
    SelectObject(hdcBackGnd, hbmBackGnd);

    //create bitmap for the drag-image
    hdcDragCard = CreateCompatibleDC(hdc);
    hbmDragCard = CreateCompatibleBitmap(hdc, nDragCardWidth, nDragCardHeight);
    SelectObject(hdcDragCard, hbmDragCard);
    ReleaseDC(NULL, hdc);

    UseNicePalette(hdcBackGnd,  __hPalette);
    UseNicePalette(hdcDragCard, __hPalette);

    //grab the first bit of background so we can prep the back buffer; do this by
    //rendering the card stack (minus the card we are dragging) to the temporary
    //background buffer, so it appears if we have lifted the card from the stack
    //--SetRect(&rect, 0, 0, nDragCardWidth, nDragCardHeight);
    //--PaintRect(hdcBackGnd, &rect, crBackgnd);

    int threedadjust = numcards  % nThreedCount == 0;

    numunder = CalcApparentCards(numcards);
    iwhichcard = (numcards+numtodrag) - numunder - 1;
    if(nThreedCount == 1) iwhichcard = 0;

    int xoff = calc_offset(xoffset, numunder, numtodrag, numunder);
    int yoff = calc_offset(yoffset, numunder, numtodrag, numunder);

    parentWnd.PaintCardRgn(hdcBackGnd, 0,0,    nDragCardWidth,nDragCardHeight,    xpos - xoff,ypos - yoff);

    //
    //    Render the cardstack into the back-buffer. The stack
    //    has already had the dragcards removed, so just draw
    //    what is left
    //
    for(icard = 0; icard < numunder; icard++)
    {
        Card card = cardstack.cardlist[iwhichcard];
        int nCardVal = card.FaceUp() ? card.Idx() : nBackCardIdx;

        CardBlt(hdcBackGnd,
                xoffset * icard - xoffset*(numunder-numtodrag+threedadjust),
                yoffset * icard - yoffset*(numunder-numtodrag+threedadjust),
                nCardVal);

        iwhichcard++;
    }

    //
    // If there are no cards under this one, just draw the place holder
    //
    if(numcards == 0)
    {
        switch(uEmptyImage)
        {
        case CS_EI_NONE:
            //no need! we've already cleared the whole
            //back-buffer before the main loop!
            //SetRect(&rect, 0, 0, __cardwidth, __cardheight);
            //PaintRect(hdcBackGnd, &rect, MAKE_PALETTERGB(crBackgnd));
            break;

        case CS_EI_SUNK:
            DrawCard(hdcBackGnd, 0, 0, __hdcPlaceHolder, __cardwidth, __cardheight);
            break;

        case CS_EI_CIRC:
        case CS_EI_X:
            CardBlt(hdc, 0, 0, uEmptyImage);
            break;
        }
    }

    //
    //    now render the drag-cards into the dragcard image
    //
    PaintRect(hdcDragCard, &rect, crBackgnd);

    for(icard = 0; icard < numtodrag; icard++)
    {
        Card card = dragstack.cardlist[icard];
        int nCardVal = card.FaceUp() ? card.Idx() : nBackCardIdx;

        CardBlt(hdcDragCard, xoffset * icard, yoffset * icard, nCardVal);
    }
}

void CardRegion::ReleaseDragBitmaps(void)
{
    //SelectObject(hdcBackGnd, hOld1);
    DeleteObject(hbmBackGnd);
    DeleteDC(hdcBackGnd);

    //SelectObject(hdcDragCard, hOld2);
    DeleteObject(hbmDragCard);
    DeleteDC(hdcDragCard);
}


void CardRegion::Redraw()
{
    HDC hdc = GetDC((HWND)parentWnd);

    Update();
    Render(hdc);

    ReleaseDC((HWND)parentWnd, hdc);
}