//
//    CardLib - CardRegion class
//
//    Freeware
//    Copyright J Brown 2001
//

#include "cardlib.h"

HBITMAP CreateSinkBmp(HDC hdcCompat, HDC hdc, int width, int height);

void PaintRect(HDC hdc, RECT *rect, COLORREF colour);

CardRegion::CardRegion(CardWindow &parent, int Id, bool visible, int x, int y, int xOffset, int yOffset)
: id(Id), parentWnd(parent), xpos(x), ypos(y), xoffset(xOffset), yoffset(yOffset), fVisible(visible)
{
    width  = __cardwidth;
    height = __cardheight;

    crBackgnd  = RGB(0, 64, 100);

    uFaceDirType   = CS_FACE_UP;
    nFaceDirOption = 0;
    uEmptyImage  = CS_EI_SUNK;

    fVisible     = visible;

    nThreedCount = 1;

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

    hdcBackGnd = 0;
    hbmBackGnd = 0;
    hdcDragCard = 0;
    hbmDragCard = 0;

    nDragCardWidth = 0;
    nDragCardHeight = 0;

    CanDragCallback  = 0;
    CanDropCallback  = 0;
    AddCallback      = 0;
    RemoveCallback   = 0;
    ClickCallback    = 0;
    ClickReleaseCallback = 0;
    DblClickCallback = 0;

    uDragRule = CS_DRAG_ALL;
    uDropRule = CS_DROP_ALL;

    xjustify = yjustify = xadjust = yadjust = 0;

    nFlashCount        = 0;
    fFlashVisible    = false;
    uFlashTimer        = (UINT)-1;

    fMouseDragging = false;

    mxlock = CreateMutex(0, FALSE, 0);
}

CardRegion::~CardRegion()
{
    CloseHandle(mxlock);
}

void CardRegion::SetBackColor(COLORREF cr)
{
    crBackgnd = cr;
}

int CardRegion::CalcApparentCards(int realnum)
{
    return ((realnum + nThreedCount - 1) - (realnum + nThreedCount - 1) % nThreedCount) / nThreedCount;
}

void CardRegion::CalcApparentCards()
{
    nNumApparentCards = CalcApparentCards(cardstack.NumCards());
}


void CardRegion::UpdateSize(void)
{
    if(cardstack.NumCards() > 0)
    {
        if(xoffset > 0)
            width  = (nNumApparentCards - 1) * xoffset + __cardwidth;
        else
            width  = (nNumApparentCards - 1) * -xoffset + __cardwidth;

        if(yoffset > 0)
            height = (nNumApparentCards - 1) * yoffset + __cardheight;
        else
            height = (nNumApparentCards - 1) * -yoffset + __cardheight;
    }
    else
    {
        width = __cardwidth;
        height = __cardheight;
    }
}

CardRegion *CardWindow::CreateRegion(int id, bool fVisible, int x, int y, int xoffset, int yoffset)
{
    CardRegion *cr;

    if(nNumCardRegions == MAXCARDSTACKS)
        return FALSE;

    cr = new CardRegion(*this, id, fVisible, x, y, xoffset, yoffset);
    cr->SetBackColor(crBackgnd);
    cr->SetBackCardIdx(nBackCardIdx);

    Regions[nNumCardRegions++] = cr;

    return cr;
}

int CardRegion::GetOverlapRatio(int x, int y, int w, int h)
{
    RECT me, him;
    RECT inter;
    SetRect(&him, x, y, x+w, y+h);
    SetRect(&me,  xpos, ypos, xpos+width, ypos+height);

    //see if the specified rectangle overlaps us
    if(IntersectRect(&inter, &me, &him))
    {
        int wi = inter.right  - inter.left;
        int hi = inter.bottom - inter.top;

        int overlap = wi * hi;
        int total   = width * height;

        int percent = (overlap << 16) / total;
        return (percent * 100) >> 16;
    }
    //do not overlap
    else
    {
        return 0;
    }
}

bool CardRegion::SetDragRule(UINT uDragType, pCanDragProc proc)
{
    switch(uDragType)
    {
    case CS_DRAG_NONE: case CS_DRAG_ALL: case CS_DRAG_TOP:
        uDragRule = uDragType;
        return true;

    case CS_DRAG_CALLBACK:
        uDragRule = uDragType;
        CanDragCallback = proc;
        return true;

    default:
        return false;
    }
}

bool CardRegion::SetDropRule(UINT uDropType, pCanDropProc proc)
{
    switch(uDropType)
    {
    case CS_DROP_NONE: case CS_DROP_ALL:
        uDropRule = uDropType;
        return true;

    case CS_DROP_CALLBACK:
        uDropRule = uDropType;
        CanDropCallback = proc;
        return true;

    default:
        return false;
    }
}

void CardRegion::SetClickProc(pClickProc proc)
{
    ClickCallback = proc;
}

void CardRegion::SetClickReleaseProc(pClickProc proc)
{
    ClickReleaseCallback = proc;
}

void CardRegion::SetDblClickProc(pClickProc proc)
{
    DblClickCallback = proc;
}

void CardRegion::SetAddCardProc(pAddProc proc)
{
    AddCallback = proc;
}

void CardRegion::SetRemoveCardProc(pRemoveProc proc)
{
    RemoveCallback = proc;
}

void CardRegion::Update()
{
    CalcApparentCards();
    UpdateSize();
    UpdateFaceDir(cardstack);
}


bool CardRegion::SetThreedCount(int count)
{
    if(count < 1)
    {
        return false;
    }
    else
    {
        nThreedCount = count;
        return true;
    }
}

void CardRegion::SetOffsets(int x, int y)
{
    xoffset = x;
    yoffset = y;
}

void CardRegion::SetPos(int x, int y)
{
    xpos = x;
    ypos = y;
}

void CardRegion::Show(bool fShow)
{
    fVisible = fShow;
}

bool CardRegion::IsVisible()
{
    return fVisible;
}

void CardRegion::SetPlacement(UINT xJustify, UINT yJustify, int xAdjust, int yAdjust)
{
    xjustify = xJustify;
    yjustify = yJustify;
    xadjust  = xAdjust;
    yadjust  = yAdjust;
}

void CardRegion::SetFaceDirection(UINT uDirType, int nOption)
{
    switch(uDirType)
    {
    case CS_FACE_UP:     case CS_FACE_DOWN: case CS_FACE_DOWNUP:
    case CS_FACE_UPDOWN: case CS_FACE_ANY:
        uFaceDirType    = uDirType;
        nFaceDirOption  = nOption;

        UpdateFaceDir(cardstack);

        break;
    }
}

UINT CardRegion::GetFaceDirection(int *pnOption)
{
    if(pnOption)
        *pnOption = nFaceDirOption;

    return uFaceDirType;
}

void CardRegion::AdjustPosition(int winwidth, int winheight)
{
    Update();            //Update this stack's card count + size

    switch(xjustify)
    {
    default: case CS_XJUST_NONE: break;

    case CS_XJUST_CENTER:        //centered
        xpos = (winwidth - (width & ~0x1)) / 2;
        xpos += xadjust;

        if(xoffset < 0)    xpos += (width - __cardwidth);

        break;

    case CS_XJUST_RIGHT:        //right-aligned
        xpos = winwidth - __cardwidth;//width - 20;
        xpos += xadjust;
        break;
    }

    switch(yjustify)
    {
    default: case CS_YJUST_NONE: break;

    case CS_YJUST_CENTER:        //centered
        ypos = (winheight - height) / 2;
        ypos += yadjust;
        if(yoffset < 0)    ypos += (height - __cardheight);
        break;

    case CS_YJUST_BOTTOM:        //bottom-aligned
        ypos = winheight - __cardheight;//height - 20;
        ypos += yadjust;
        break;
    }

}


void CardRegion::Flash(int count, int milliseconds)
{
    if(count <= 0) return;

    nFlashCount        = count;
    fFlashVisible   = false;
    uFlashTimer        = SetTimer((HWND)parentWnd, (WPARAM)this, milliseconds, 0);

    parentWnd.Redraw();
}

void CardRegion::StopFlash()
{
    if(uFlashTimer != (UINT)-1)
    {
        KillTimer((HWND)parentWnd, uFlashTimer);
        nFlashCount        = 0;
        uFlashTimer        = (UINT)-1;
        fFlashVisible    = true;
    }
}

void CardRegion::DoFlash()
{
    if(uFlashTimer != (UINT)-1)
    {
        fFlashVisible = !fFlashVisible;

        if(--nFlashCount == 0)
        {
            KillTimer((HWND)parentWnd, uFlashTimer);
            uFlashTimer = (UINT)-1;
            fFlashVisible = true;
        }

        parentWnd.Redraw();
    }
}

int CardRegion::Id()
{
    return id;
}

void CardRegion::SetEmptyImage(UINT uImage)
{
    switch(uImage)
    {
    case CS_EI_NONE:
    case CS_EI_SUNK:
    case CS_EI_CIRC:
    case CS_EI_X:
        uEmptyImage = uImage;
        break;

    default:
        uEmptyImage = CS_EI_NONE;
        break;
    }

}

void CardRegion::SetBackCardIdx(UINT uBackIdx)
{
    if(uBackIdx >= 52 && uBackIdx <= 68)
        nBackCardIdx = uBackIdx;
}

void CardRegion::SetCardStack(const CardStack &cs)
{
    //make a complete copy of the specified stack..
    cardstack = cs;

    // Update the face-direction and stack-size
    Update();
}

const CardStack & CardRegion::GetCardStack()
{
    //return reference to our internal stack
    return cardstack;
}

//
//    Update specified card-stack using THIS stack's
//  face direction rules!
//
void CardRegion::UpdateFaceDir(CardStack &cards)
{
    int i, n, num;

    num = cards.NumCards();

    //Now apply the face direction rules..
    switch(uFaceDirType)
    {
    case CS_FACE_UP:

        for(i = 0; i < num; i++)
        {
            cards[i].SetFaceUp(true);
        }

        break;

    case CS_FACE_DOWN:

        for(i = 0; i < num; i++)
        {
            cards[i].SetFaceUp(false);
        }

        break;

    case CS_FACE_DOWNUP:

        num = cardstack.NumCards();
        n = min(nFaceDirOption, num);

        //bottom n cards..
        for(i = 0; i < n; i++)
        {
            cards[num - i - 1].SetFaceUp(false);
        }

        for(i = n; i < num; i++)
        {
            cards[num - i - 1].SetFaceUp(true);
        }

        break;

    case CS_FACE_UPDOWN:

        num = cardstack.NumCards();
        n = min(nFaceDirOption, num);

        for(i = 0; i < n; i++)
        {
            cards[num - i - 1].SetFaceUp(true);
        }

        for(i = n; i < num; i++)
        {
            cards[num - i - 1].SetFaceUp(false);
        }

        break;

    case CS_FACE_ANY:    //cards can be any orientation
    default:
        break;
    }
}

bool CardRegion::MoveCard(CardRegion *pDestStack, int nNumCards, bool fAnimate)
{
    HDC hdc;

    int x, y;

    if(pDestStack == 0) return false; //{ forcedfacedir = -1 ;return 0; }

    if(nNumCards < 0 || nNumCards > cardstack.NumCards())
        return false;

    x = xpos + xoffset * (nNumApparentCards - nNumCards);
    y = ypos + yoffset * (nNumApparentCards - nNumCards);

    oldx = x;
    oldy = y;

    dragstack = cardstack.Pop(nNumCards);

    //Alter the drag-stack so that it's cards are the same way up
    //as the destination. Use the destination's drag-rules
    //instead of this ones!!
    CardStack temp;
    temp.Push(pDestStack->GetCardStack());
    temp.Push(dragstack);

    pDestStack->UpdateFaceDir(temp);

    dragstack = temp.Pop(nNumCards);

    if(fAnimate)
    {
        iNumDragCards = nNumCards;
        PrepareDragBitmaps(nNumCards);
    }

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

    if(fAnimate)
    {
        hdc = GetDC((HWND)parentWnd);

        ZoomCard(hdc, x, y, pDestStack);

        ReleaseDC((HWND)parentWnd, hdc);
        ReleaseDragBitmaps();
    }

    // Get a copy of the cardstack
    CardStack cs = pDestStack->GetCardStack();
    cs.Push(dragstack);

    pDestStack->SetCardStack(cs);

    //cs = pDestStack->GetCardStack();
    //pDestStack->Update();
    //pDestStack->UpdateFaceDir(cs);

    RedrawIfNotDim(pDestStack, false);

    //forcedfacedir = -1;
    return true;
}

//
//    Simple wrappers
//
int CardRegion::NumCards() const
{
    if(fMouseDragging)
        return cardstack.NumCards() + dragstack.NumCards();
    else
        return cardstack.NumCards();
}

bool CardRegion::Lock()
{
    DWORD dw = WaitForSingleObject(mxlock, 0);

    if(dw == WAIT_OBJECT_0)
    {
        //TRACE("LockStack succeeded\n");
        return true;
    }
    else
    {
        //TRACE("LockStack failed\n");
        return false;
    }
    return false;
}

bool CardRegion::UnLock()
{
    if(ReleaseMutex(mxlock))
    {
        //TRACE("Unlocking stack\n");
        return true;
    }
    else
    {
        //TRACE("Unlocking stack failed\n");
        return false;
    }
}

bool CardRegion::PlayCard(CardRegion *pDestStack, int value, int num)
{
    //search the stack for the specified card value...
    while(num--)
    {
        for(int i = 0; i < cardstack.NumCards(); i++)
        {
            if(cardstack[i].HiVal() == value)
            {
                //swap the card with one at top pos...
                Card card = cardstack.RemoveCard(i);
                cardstack.Push(card);

                Redraw();

                MoveCard(pDestStack, 1, true);
                break;
            }
        }
    }

    return true;
}

//
//    Redraw the current stack if it has a different
//    layout than the comparison stack.
//
void CardRegion::RedrawIfNotDim(CardRegion *pCompare, bool fFullRedraw)
{
    //
    //
    //
    if( pCompare->xoffset != xoffset ||
        pCompare->yoffset != yoffset ||
        pCompare->nThreedCount != nThreedCount ||
        pCompare->uFaceDirType != uFaceDirType ||
        pCompare->uFaceDirType != CS_FACE_ANY
        )
    {
        if(fFullRedraw)
            parentWnd.Redraw();
        else
            pCompare->Redraw();
    }

}

//
//    SimulateDrag mimicks the complete drag+drop process.
//  It basically just a MoveCard(..), but it calls the
//  event callbacks as well.
//
bool CardRegion::SimulateDrag(CardRegion *pDestStack, int iNumDragCards, bool fAnimate)
{
    if(pDestStack == 0)
        return false;

    if(CanDragCards(iNumDragCards) != false)
    {
        if(pDestStack->CanDropCards(cardstack))
        {
            MoveCard(pDestStack, iNumDragCards, fAnimate);

            if(RemoveCallback)
                RemoveCallback(*this, iNumDragCards);

            if(pDestStack->AddCallback)
                pDestStack->AddCallback(*pDestStack, pDestStack->cardstack);

            RedrawIfNotDim(pDestStack, true);
        }

    }

    return true;
}