/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "gfx_common.h"
#include "tfb_draw.h"
#include "drawcmd.h"
#include "libs/log.h"
#include "libs/memlib.h"


static const HOT_SPOT NullHs = {0, 0};

void
TFB_DrawScreen_Line (int x1, int y1, int x2, int y2, Color color,
		DrawMode mode, SCREEN dest, BYTE thickness)
{
	TFB_DrawCommand DC;

	DC.Type = TFB_DRAWCOMMANDTYPE_LINE;
	DC.data.line.x1 = x1;
	DC.data.line.y1 = y1;
	DC.data.line.x2 = x2;
	DC.data.line.y2 = y2;
	DC.data.line.color = color;
	DC.data.line.drawMode = mode;
	DC.data.line.destBuffer = dest;
	DC.data.line.thickness = thickness;

	TFB_EnqueueDrawCommand (&DC);
}

void
TFB_DrawScreen_Rect (RECT *rect, Color color, DrawMode mode, SCREEN dest)
{
	RECT locRect;
	TFB_DrawCommand DC;

	if (!rect)
	{
		locRect.corner.x = locRect.corner.y = 0;
		locRect.extent.width = ScreenWidth;
		locRect.extent.height = ScreenHeight;
		rect = &locRect;
	}

	DC.Type = TFB_DRAWCOMMANDTYPE_RECTANGLE;
	DC.data.rect.rect = *rect;
	DC.data.rect.color = color;
	DC.data.rect.drawMode = mode;
	DC.data.rect.destBuffer = dest;

	TFB_EnqueueDrawCommand (&DC);
}

void
TFB_DrawScreen_Image (TFB_Image *img, int x, int y, int scale,
		int scaleMode, TFB_ColorMap *cmap, DrawMode mode, SCREEN dest)
{
	TFB_DrawCommand DC;
	
	DC.Type = TFB_DRAWCOMMANDTYPE_IMAGE;
	DC.data.image.image = img;
	DC.data.image.colormap = cmap;
	DC.data.image.x = x;
	DC.data.image.y = y;
	DC.data.image.scale = (scale == GSCALE_IDENTITY) ? 0 : scale;
	DC.data.image.scaleMode = scaleMode;
	DC.data.image.drawMode = mode;
	DC.data.image.destBuffer = dest;

	TFB_EnqueueDrawCommand (&DC);
}

void
TFB_DrawScreen_FilledImage (TFB_Image *img, int x, int y, int scale,
		int scaleMode, Color color, DrawMode mode, SCREEN dest)
{
	TFB_DrawCommand DC;
	
	DC.Type = TFB_DRAWCOMMANDTYPE_FILLEDIMAGE;
	DC.data.filledimage.image = img;
	DC.data.filledimage.x = x;
	DC.data.filledimage.y = y;
	DC.data.filledimage.scale = (scale == GSCALE_IDENTITY) ? 0 : scale;
	DC.data.filledimage.scaleMode = scaleMode;
	DC.data.filledimage.color = color;
	DC.data.filledimage.drawMode = mode;
	DC.data.filledimage.destBuffer = dest;

	TFB_EnqueueDrawCommand (&DC);
}

void
TFB_DrawScreen_FontChar (TFB_Char *fontChar, TFB_Image *backing,
		int x, int y, DrawMode mode, SCREEN dest)
{
	TFB_DrawCommand DC;
	
	DC.Type = TFB_DRAWCOMMANDTYPE_FONTCHAR;
	DC.data.fontchar.fontchar = fontChar;
	DC.data.fontchar.backing = backing;
	DC.data.fontchar.x = x;
	DC.data.fontchar.y = y;
	DC.data.fontchar.drawMode = mode;
	DC.data.fontchar.destBuffer = dest;

	TFB_EnqueueDrawCommand (&DC);
}

void
TFB_DrawScreen_CopyToImage (TFB_Image *img, const RECT *r, SCREEN src)
{
	TFB_DrawCommand DC;

	DC.Type = TFB_DRAWCOMMANDTYPE_COPYTOIMAGE;
	DC.data.copytoimage.rect = *r;
	DC.data.copytoimage.image = img;
	DC.data.copytoimage.srcBuffer = src;
	
	TFB_EnqueueDrawCommand (&DC);
}

void
TFB_DrawScreen_Copy (const RECT *r, SCREEN src, SCREEN dest)
{
	RECT locRect;
	TFB_DrawCommand DC;

	if (!r)
	{
		locRect.corner.x = locRect.corner.y = 0;
		locRect.extent.width = ScreenWidth;
		locRect.extent.height = ScreenHeight;
		r = &locRect;
	}

	DC.Type = TFB_DRAWCOMMANDTYPE_COPY;
	DC.data.copy.rect = *r;
	DC.data.copy.srcBuffer = src;
	DC.data.copy.destBuffer = dest;

	TFB_EnqueueDrawCommand (&DC);
}

void
TFB_DrawScreen_SetMipmap (TFB_Image *img, TFB_Image *mmimg, int hotx, int hoty)
{
	TFB_DrawCommand DC;

	DC.Type = TFB_DRAWCOMMANDTYPE_SETMIPMAP;
	DC.data.setmipmap.image = img;
	DC.data.setmipmap.mipmap = mmimg;
	DC.data.setmipmap.hotx = hotx;
	DC.data.setmipmap.hoty = hoty;

	TFB_EnqueueDrawCommand (&DC);
}

void
TFB_DrawScreen_DeleteImage (TFB_Image *img)
{
	if (img)
	{
		TFB_DrawCommand DC;

		DC.Type = TFB_DRAWCOMMANDTYPE_DELETEIMAGE;
		DC.data.deleteimage.image = img;

		TFB_EnqueueDrawCommand (&DC);
	}
}

void
TFB_DrawScreen_DeleteData (void *data)
		// data must be a result of HXalloc() call
{
	if (data)
	{
		TFB_DrawCommand DC;

		DC.Type = TFB_DRAWCOMMANDTYPE_DELETEDATA;
		DC.data.deletedata.data = data;

		TFB_EnqueueDrawCommand (&DC);
	}
}

void
TFB_DrawScreen_WaitForSignal (void)
{
	TFB_DrawCommand DrawCommand;
	Semaphore s;
	s = GetMyThreadLocal ()->flushSem;
	DrawCommand.Type = TFB_DRAWCOMMANDTYPE_SENDSIGNAL;
	DrawCommand.data.sendsignal.sem = s;
	Lock_DCQ (1);
	TFB_BatchReset ();
	TFB_EnqueueDrawCommand (&DrawCommand);
	Unlock_DCQ();
	SetSemaphore (s);	
}

void
TFB_DrawScreen_ReinitVideo (int driver, int flags, int width, int height)
{
	TFB_DrawCommand DrawCommand;
	DrawCommand.Type = TFB_DRAWCOMMANDTYPE_REINITVIDEO;
	DrawCommand.data.reinitvideo.driver = driver;
	DrawCommand.data.reinitvideo.flags = flags;
	DrawCommand.data.reinitvideo.width = width;
	DrawCommand.data.reinitvideo.height = height;
	TFB_EnqueueDrawCommand (&DrawCommand);
}

void
TFB_DrawScreen_Callback (void (*callback) (void *arg), void *arg)
{
	TFB_DrawCommand DrawCommand;
	DrawCommand.Type = TFB_DRAWCOMMANDTYPE_CALLBACK;
	DrawCommand.data.callback.callback = callback;
	DrawCommand.data.callback.arg = arg;
	TFB_EnqueueDrawCommand(&DrawCommand);
}

void
TFB_DrawImage_Line (int x1, int y1, int x2, int y2, Color color,
		DrawMode mode, TFB_Image *target, BYTE thickness)
{
	LockMutex (target->mutex);
	TFB_DrawCanvas_Line (x1, y1, x2, y2, color, mode, target->NormalImg, thickness);
	target->dirty = TRUE;
	UnlockMutex (target->mutex);
}

void
TFB_DrawImage_Rect (RECT *rect, Color color, DrawMode mode, TFB_Image *target)
{
	LockMutex (target->mutex);
	TFB_DrawCanvas_Rect (rect, color, mode, target->NormalImg);
	target->dirty = TRUE;
	UnlockMutex (target->mutex);
}

void
TFB_DrawImage_Image (TFB_Image *img, int x, int y, int scale,
		int scaleMode, TFB_ColorMap *cmap, DrawMode mode, TFB_Image *target)
{
	LockMutex (target->mutex);
	TFB_DrawCanvas_Image (img, x, y, scale, scaleMode, cmap,
			mode, target->NormalImg);
	target->dirty = TRUE;
	UnlockMutex (target->mutex);
}

void
TFB_DrawImage_FilledImage (TFB_Image *img, int x, int y, int scale,
		int scaleMode, Color color, DrawMode mode, TFB_Image *target)
{
	LockMutex (target->mutex);
	TFB_DrawCanvas_FilledImage (img, x, y, scale, scaleMode, color,
			mode, target->NormalImg);
	target->dirty = TRUE;
	UnlockMutex (target->mutex);
}

void
TFB_DrawImage_FontChar (TFB_Char *fontChar, TFB_Image *backing,
		int x, int y, DrawMode mode, TFB_Image *target)
{
	LockMutex (target->mutex);
	TFB_DrawCanvas_FontChar (fontChar, backing, x, y, mode, target->NormalImg);
	target->dirty = TRUE;
	UnlockMutex (target->mutex);
}

void
TFB_DrawImage_MaskImage (TFB_Image *img, DrawMode mode, TFB_Image *target, Color *fill)
{
	LockMutex (target->mutex);
	TFB_DrawCanvas_MaskImage (img, mode, target->NormalImg, fill);
	target->dirty = TRUE;
	UnlockMutex (target->mutex);
}


TFB_Image *
TFB_DrawImage_New (TFB_Canvas canvas)
{
	TFB_Image *img = HMalloc (sizeof (TFB_Image));
	img->mutex = CreateMutex ("image lock", SYNC_CLASS_VIDEO);
	img->ScaledImg = NULL;
	img->MipmapImg = NULL;
	img->FilledImg = NULL;
	img->colormap_index = -1;
	img->colormap_version = 0;
	img->NormalHs = NullHs;
	img->MipmapHs = NullHs;
	img->last_scale_hs = NullHs;
	img->last_scale_type = -1;
	img->last_scale = 0;
	img->dirty = FALSE;
	TFB_DrawCanvas_GetExtent (canvas, &img->extent);

	if (TFB_DrawCanvas_IsPaletted (canvas))
	{
		img->NormalImg = canvas;
	}
	else
	{
		img->NormalImg = TFB_DrawCanvas_ToScreenFormat (canvas);
	}

	return img;
}

TFB_Image*
TFB_DrawImage_CreateForScreen (int w, int h, BOOLEAN withalpha)
{
	TFB_Image* img = HMalloc (sizeof (TFB_Image));
	img->mutex = CreateMutex ("image lock", SYNC_CLASS_VIDEO);
	img->ScaledImg = NULL;
	img->MipmapImg = NULL;
	img->FilledImg = NULL;
	img->colormap_index = -1;
	img->colormap_version = 0;
	img->NormalHs = NullHs;
	img->MipmapHs = NullHs;
	img->last_scale_hs = NullHs;
	img->last_scale_type = -1;
	img->last_scale = 0;
	img->extent.width = w;
	img->extent.height = h;

	img->NormalImg = TFB_DrawCanvas_New_ForScreen (w, h, withalpha);

	return img;
}

TFB_Image *
TFB_DrawImage_New_Rotated (TFB_Image *img, int angle)
{
	TFB_Canvas dst;
	EXTENT size;
	TFB_Image* newimg;

	/* sanity check */
	if (!img->NormalImg)
	{
		log_add (log_Warning, "TFB_DrawImage_New_Rotated: "
				"source canvas is NULL! Failing.");
		return NULL;
	}

	TFB_DrawCanvas_GetRotatedExtent (img->NormalImg, angle, &size);
	dst = TFB_DrawCanvas_New_RotationTarget (img->NormalImg, angle);
	if (!dst)
	{
		log_add (log_Warning, "TFB_DrawImage_New_Rotated: "
				"rotation target canvas not created! Failing.");
		return NULL;
	}
	TFB_DrawCanvas_Rotate (img->NormalImg, dst, angle, size);
	
	newimg = TFB_DrawImage_New (dst);
	return newimg;
}

void
TFB_DrawImage_SetMipmap (TFB_Image *img, TFB_Image *mmimg, int hotx, int hoty)
{
	bool imgpal;
	bool mmpal;

	if (!img || !mmimg)
		return;

	LockMutex (img->mutex);
	LockMutex (mmimg->mutex);
	
	// Either both images must be using the same colormap, or mipmap image
	// must not be paletted. This restriction is due to the current
	// implementation of fill-stamp, which replaces the palette with
	// fill color.
	imgpal = TFB_DrawCanvas_IsPaletted (img->NormalImg);
	mmpal = TFB_DrawCanvas_IsPaletted (mmimg->NormalImg);
	if (!mmpal || (mmpal && imgpal &&
			img->colormap_index == mmimg->colormap_index))
	{
		img->MipmapImg = mmimg->NormalImg;
		img->MipmapHs.x = hotx;
		img->MipmapHs.y = hoty;
	}
	else
	{
		img->MipmapImg = NULL;
	}

	UnlockMutex (mmimg->mutex);
	UnlockMutex (img->mutex);
}

void 
TFB_DrawImage_Delete (TFB_Image *image)
{
	if (image == 0)
	{
		log_add (log_Warning, "INTERNAL ERROR: Tried to delete a null image!");
		/* Should we die here? */
		return;
	}
	LockMutex (image->mutex);

	TFB_DrawCanvas_Delete (image->NormalImg);
			
	if (image->ScaledImg)
	{
		TFB_DrawCanvas_Delete (image->ScaledImg);
		image->ScaledImg = 0;
	}

	if (image->FilledImg)
	{
		TFB_DrawCanvas_Delete (image->FilledImg);
		image->FilledImg = 0;
	}

	UnlockMutex (image->mutex);
	DestroyMutex (image->mutex);
			
	HFree (image);
}

void
TFB_DrawImage_FixScaling (TFB_Image *image, int target, int type)
{
	if (image->dirty || !image->ScaledImg ||
			target != image->last_scale ||
			type != image->last_scale_type)
	{
		image->dirty = FALSE;
		image->ScaledImg = TFB_DrawCanvas_New_ScaleTarget (image->NormalImg,
			image->ScaledImg, type, image->last_scale_type);
		
		if (type == TFB_SCALE_NEAREST)
			TFB_DrawCanvas_Rescale_Nearest (image->NormalImg,
					image->ScaledImg, target, &image->NormalHs,
					&image->extent, &image->last_scale_hs);
		else if (type == TFB_SCALE_BILINEAR)
			TFB_DrawCanvas_Rescale_Bilinear (image->NormalImg,
					image->ScaledImg, target, &image->NormalHs,
					&image->extent, &image->last_scale_hs);
		else
			TFB_DrawCanvas_Rescale_Trilinear (image->NormalImg,
					image->MipmapImg, image->ScaledImg, target,
					&image->NormalHs, &image->MipmapHs,
					&image->extent, &image->last_scale_hs);

		image->last_scale_type = type;
		image->last_scale = target;
	}
}

BOOLEAN
TFB_DrawImage_Intersect (TFB_Image *img1, POINT img1org,
		TFB_Image *img2, POINT img2org, const RECT *interRect)
{
	BOOLEAN ret;

	LockMutex (img1->mutex);
	LockMutex (img2->mutex);
	ret = TFB_DrawCanvas_Intersect (img1->NormalImg, img1org,
			img2->NormalImg, img2org, interRect);
	UnlockMutex (img2->mutex);
	UnlockMutex (img1->mutex);

	return ret;
}

void
TFB_DrawImage_CopyRect (TFB_Image *source, const RECT *srcRect,
		TFB_Image *target, POINT dstPt)
{
	LockMutex (source->mutex);
	LockMutex (target->mutex);
	TFB_DrawCanvas_CopyRect (source->NormalImg, srcRect,
			target->NormalImg, dstPt);
	target->dirty = TRUE;
	UnlockMutex (target->mutex);
	UnlockMutex (source->mutex);
}
