/*
 * Cards dll implementation
 *
 * Copyright (C) 2004 Sami Nopanen
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"

#include <stdarg.h>

#include "windef.h"
#include "winbase.h"
#include "winuser.h"
#include "wingdi.h"

#include "cards.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(cards);


void WINAPI cdtTerm(void);


static HINSTANCE hInst;
static int cardWidth;
static int cardHeight;
static HBITMAP cardBitmaps[CARD_MAX + 1];


/***********************************************************************
 * Initializes the cards.dll library. Loads the card bitmaps from the
 * resources, and initializes the card size variables.
 */
BOOL WINAPI cdtInit(int *width, int *height)
{
	BITMAP bm;
	int i;

	TRACE("(%p, %p)\n", width, height);

	for(i = 0; i <= CARD_MAX; i++)
		cardBitmaps[i] = 0;

	for(i = 0; i <= CARD_MAX; i++)
	{
		cardBitmaps[i] = LoadBitmapA(hInst, MAKEINTRESOURCEA(i));
		if(cardBitmaps[i] == 0)
		{
			cdtTerm();
			return FALSE;
		}
	}

	GetObjectA(cardBitmaps[0], sizeof(BITMAP), &bm);
	*width = cardWidth = bm.bmWidth;
	*height = cardHeight = bm.bmHeight;
	return TRUE;
}

static DWORD do_blt(HDC hdc, int x, int y, int dx, int dy, HDC hMemoryDC, DWORD rasterOp )
{
	if((cardWidth == dx) && (cardHeight == dy))
		return BitBlt(hdc, x, y, cardWidth, cardHeight, hMemoryDC, 0, 0, rasterOp);
	return StretchBlt(hdc, x, y, dx, dy, hMemoryDC, 0, 0, cardWidth, cardHeight, rasterOp);
}

/***********************************************************************
 * Draw a card. Unlike cdtDrawCard, this version allows you to stretch
 * card bitmaps to the size you specify (dx, dy). See cdtDraw for info
 * on card, mode and color parameters.
 */
BOOL WINAPI cdtDrawExt(HDC hdc, int x, int y, int dx, int dy, int card, int mode, DWORD color)
{
	HDC hMemoryDC;
	HBITMAP hCardBitmap;
	HGDIOBJ result;
	DWORD rasterOp = SRCCOPY;
	BOOL roundCornersFlag;
	BOOL eraseFlag = FALSE;
	BOOL drawFlag = TRUE;

	TRACE("(%p, %d, %d, %d, %d, %d, %d, %d)\n", hdc, x, y, dx, dy, card, mode, color);

	roundCornersFlag = !(mode & MODEFLAG_DONT_ROUND_CORNERS) &&
			   (dx == cardWidth) && (dy == cardHeight);
	mode &= ~MODEFLAG_DONT_ROUND_CORNERS;

	if((card < 0) || (card > CARD_MAX))
	{
		FIXME("Unexpected card: %d\n", card);
		return FALSE;
	}

	if((mode < MODE_FACEUP) || (mode > MODE_DECKO))
	{
		FIXME("Unexpected mode: %d\n", mode);
		return FALSE;
	}

	switch(mode)
	{
	case MODE_FACEUP:
		break;
	case MODE_FACEDOWN:
		break;
	case MODE_HILITE:
		rasterOp = NOTSRCCOPY;
		break;
	case MODE_GHOST:
		card = CARD_FREE_MASK;
		eraseFlag = TRUE;
		rasterOp = SRCAND;
		break;
	case MODE_REMOVE:
		eraseFlag = TRUE;
		drawFlag = FALSE;
		break;
	case MODE_INVISIBLEGHOST:
		card = CARD_FREE_MASK;
		rasterOp = SRCAND;
		break;
	case MODE_DECKX:
		card = CARD_BACK_THE_X;
		break;
	case MODE_DECKO:
		card = CARD_BACK_THE_O;
		break;
	}

	hMemoryDC = CreateCompatibleDC(hdc);
	if(hMemoryDC == 0)
		return FALSE;

	if(eraseFlag)
	{
		HBRUSH hBrush;
		RECT rect;
		hBrush = CreateSolidBrush(color);
		rect.left = x;
		rect.top = y;
		rect.right = x + cardWidth - 1;
		rect.bottom = y + cardHeight - 1;
		FillRect(hdc, &rect, hBrush);
	}

	if(drawFlag)
	{
		hCardBitmap = cardBitmaps[card];
		if(hCardBitmap == 0)
			return FALSE;

		result = SelectObject(hMemoryDC, hCardBitmap);
		if((result == 0) || (result == HGDI_ERROR))
		{
			DeleteDC(hMemoryDC);
			return FALSE;
		}

		SetBkColor(hdc, color);

		if(roundCornersFlag)
		{
                    /* NOTE: native uses Get/SetPixel for corners, but that really
                     * hurts on X11 since it needs a server round-trip for each pixel.
                     * So we use a clip region instead. */
                    HRGN saved = CreateRectRgn( 0, 0, 0, 0 );
                    HRGN line = CreateRectRgn( x + 2, y, x + dx - 2, y + 1 );
                    HRGN clip = CreateRectRgn( x, y + 2, x + dx, y + dy - 2 );

                    CombineRgn( clip, clip, line, RGN_OR );
                    SetRectRgn( line, x + 1, y + 1, x + dx - 1, y + 2 );
                    CombineRgn( clip, clip, line, RGN_OR );
                    SetRectRgn( line, x + 1, y + dy - 2, x + dx - 1, y + dy - 1 );
                    CombineRgn( clip, clip, line, RGN_OR );
                    SetRectRgn( line, x + 2, y + dy - 1, x + dx - 2, y + dy );
                    CombineRgn( clip, clip, line, RGN_OR );
                    DeleteObject( line );

                    if (!GetClipRgn( hdc, saved ))
                    {
                        DeleteObject( saved );
                        saved = 0;
                    }
                    ExtSelectClipRgn( hdc, clip, RGN_AND );
                    DeleteObject( clip );

                    do_blt(hdc, x, y, dx, dy, hMemoryDC, rasterOp);

                    SelectClipRgn( hdc, saved );
                    if (saved) DeleteObject( saved );
		}
		else
			do_blt(hdc, x, y, dx, dy, hMemoryDC, rasterOp);
	}

	DeleteDC(hMemoryDC);

	return TRUE;
}


/***********************************************************************
 * Draws a card at position x, y in its default size (as returned by
 * cdtInit.
 *
 * Mode controls how the card gets drawn:
 *   MODE_FACEUP                ; draw card facing up
 *   MODE_FACEDOWN              ; draw card facing down
 *   MODE_HILITE                ; draw face up, with NOTSRCCOPY
 *   MODE_GHOST                 ; draw 'ghost' card
 *   MODE_REMOVE                ; draw with background color
 *   MODE_INVISIBLEGHOST        ; draw 'ghost' card, without clearing background
 *   MODE_DECKX                 ; draw X
 *   MODE_DECKO                 ; draw O
 *
 * The card parameter defines the card graphic to be drawn. If we are
 * drawing fronts of cards, card should have a value from 0 through 51
 * to represent the card face. If we are drawing card backs, 53 through
 * 68 represent different card backs.
 *
 * When drawing card faces, two lowest bits represent the card suit
 * (clubs, diamonds, hearts, spades), and the bits above that define the
 * card value (ace, 2, ..., king). That is,
 *   card = face * 4 + suit.
 *
 * Color parameter defines the background color, used when drawing some
 * card backs.
 */
BOOL WINAPI cdtDraw(HDC hdc, int x, int y, int card, int mode, DWORD color)
{
	TRACE("(%p, %d, %d, %d, %d, %d)\n", hdc, x, y, card, mode, color);

	return cdtDrawExt(hdc, x, y, cardWidth, cardHeight, card, mode, color);
}


/***********************************************************************
 * Animates the card backs, e.g. blinking lights on the robot, the sun
 * donning sunglasses, bats flying across the caste, etc.. Works only
 * for cards of normal size (as drawn with cdtDraw). To draw frames of
 * the card back animation, start with frame = 0, and increment the
 * frame by one, until cdtAnimate returns FALSE (to indicate that we
 * have gone through all frames of animation).
 */
BOOL WINAPI cdtAnimate(HDC hdc, int cardback, int x, int y, int frame)
{
	TRACE("(%p, %d, %d, %d, %d)\n", hdc, cardback, x, y, frame);
	FIXME("Implement me.\n");

	return FALSE;
}


/***********************************************************************
 * Frees resources reserved by cdtInit.
 */
void WINAPI cdtTerm(void)
{
	int i;

	TRACE("()\n");

	for(i = 0; i <= CARD_MAX; i++)
	{
		if(cardBitmaps[i] != 0)
			DeleteObject(cardBitmaps[i]);
		cardBitmaps[i] = 0;
	}
}


/***********************************************************************
 * DllMain.
 */
BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved)
{
    switch (reason)
    {
    case DLL_PROCESS_ATTACH:
        hInst = inst;
        DisableThreadLibraryCalls( inst );
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}