/*
 * file sprite.c - handling sprites 
 *
 * $Id: sprite.c,v 1.9 2006/02/09 21:21:25 fzago Exp $
 *
 * Program XBLAST 
 * (C) by Oliver Vogel (e-mail: m.vogel@ndh.net)
 *
 * 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; or (at your option)
 * any later version
 *
 * This program is distributed in the hope that it will be entertaining,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILTY 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 "xblast.h"

/*
 * local types
 */

/* function pointers for region and drawing */

/*
 * local variables
 */
AnySprite *spriteFirst = NULL;
AnySprite *spriteLast = NULL;

/* 
 * mark sprite positon to redraw
 */
void
MarkMazeSprite (const Sprite * spr)
{
	const BMRectangle *r = (*spr->any.rect) (spr);
	assert (r != NULL);
	MarkMaze (r->x / BLOCK_WIDTH, r->y / BLOCK_HEIGHT, (r->x + r->w - 1) / BLOCK_WIDTH,
			  (r->y + r->h - 1) / BLOCK_HEIGHT);
}								/* MarkMazeSprite */

/*
 * local function: create sprite
 */
static Sprite *
CreateSprite (SpriteType type, DrawFunc draw, RectFunc rect, int x, int y, int ysort, int anime,
			  int mode)
{
	AnySprite *ptr;
	AnySprite *other;
	/* alloc memory */
	if (NULL == (ptr = (AnySprite *) malloc (sizeof (Sprite)))) {
		return NULL;
	}

	/* init values */
	ptr->type = type;
	ptr->dirty = XBTrue;
	ptr->draw = draw;
	ptr->rect = rect;
	ptr->x = x;
	ptr->y = y;
	ptr->ysort = ysort;
	ptr->anime = anime;
	ptr->mode = mode;
	ptr->next = NULL;
	ptr->prev = NULL;

	/* store sprite in list */
	if (NULL == spriteFirst) {
		spriteFirst = spriteLast = ptr;
	}
	else {
		for (other = spriteFirst; other != NULL; other = other->next) {
			/* list is sorted by inccreasing (y+ysort) value */
			if ((other->ysort + other->y) > (ptr->ysort + ptr->y)) {
				if (NULL == other->prev) {
					/* start of list */
					spriteFirst = ptr;
					ptr->prev = NULL;
				}
				else {
					other->prev->next = ptr;
					ptr->prev = other->prev;
				}
				ptr->next = other;
				other->prev = ptr;
				goto Finish;
			}
		}
		spriteLast->next = ptr;
		ptr->prev = spriteLast;
		spriteLast = ptr;
	}
  Finish:
	return (Sprite *) ptr;
}								/* CreateSprite */

/*
 * rectangle function for player sprite
 */
static const BMRectangle *
RectPlayerSprite (const Sprite * ptr)
{
	static BMRectangle result;

	assert (ptr != NULL);
	result.x = imgRectSprite[ptr->any.anime].x + ptr->any.x;
	result.y = imgRectSprite[ptr->any.anime].y + ptr->any.y;
	result.w = imgRectSprite[ptr->any.anime].w;
	result.h = imgRectSprite[ptr->any.anime].h;

	return &result;
}								/* RectPlayerSprite */

/*
 * public function: CreatePlayerSprite
 */
Sprite *
CreatePlayerSprite (int player, int x, int y, unsigned anime, int mode)
{
	Sprite *ptr;

	ptr =
		CreateSprite (STPlayer, GUI_DrawPlayerSprite, RectPlayerSprite, x, y, BLOCK_HEIGHT, anime,
					  mode);
	if (NULL == ptr) {
		return NULL;
	}
	ptr->player.player = player;
	/* mark maze to redrawn */
	if (ptr->any.mode == SPM_MAPPED) {
		MarkMazeSprite ((Sprite *) ptr);
	}
	return ptr;
}								/* CreatePlayerSprite */

static const BMRectangle *
RectBombSprite (const Sprite * ptr)
{
	static BMRectangle result;

	assert (ptr != NULL);
	result.x = ptr->any.x;
	result.y = ptr->any.y;
	result.w = BLOCK_WIDTH;
	result.h = BLOCK_HEIGHT;

	return &result;
}								/* GUI_RectBombSprite */

/*
 * public function: CreateBombSprite
 */
Sprite *
CreateBombSprite (int bomb, int x, int y, unsigned anime, int mode)
{
	Sprite *ptr;

	ptr = CreateSprite (STBomb, GUI_DrawBombSprite, RectBombSprite, x, y, BASE_Y / 2, anime, mode);
	if (NULL == ptr) {
		return NULL;
	}
	ptr->bomb.bomb = bomb;
	/* mark maze to redrawn */
	if (ptr->any.mode == SPM_MAPPED) {
		MarkMazeSprite ((Sprite *) ptr);
	}
	return ptr;
}								/* CreateBombSprite */

/*
 *
 */
static const BMRectangle *
RectTextSprite (const Sprite * ptr)
{
	static BMRectangle result;

	assert (ptr != NULL);
	result.x = ptr->any.x;
	result.y = ptr->any.y;
	result.w = ptr->text.w;
	result.h = ptr->text.h;

	return &result;
}								/* GUI_RectTextSprite */

/*
 * 
 */
Sprite *
CreateTextSprite (const char *text, int x, int y, int w, int h, unsigned anime, int mode)
{
	Sprite *ptr;

	ptr = CreateSprite (STText, GUI_DrawTextSprite, RectTextSprite, x, y, 0, anime, mode);
	if (NULL == ptr) {
		return NULL;
	}
	ptr->any.anime = anime;
	ptr->text.text = text;
	ptr->text.w = w;
	ptr->text.h = h;
	/* mark maze to redrawn */
	if (ptr->any.mode == SPM_MAPPED) {
		MarkMazeSprite ((Sprite *) ptr);
	}
	return ptr;
}								/* CreateTextSprite */

/*
 *
 */
static const BMRectangle *
RectIconSprite (const Sprite * spr)
{
	static BMRectangle result;

	assert (spr != NULL);
	assert (spr->any.anime < MAX_ICON_SPRITES);

	result.x = imgRectIcon[spr->any.anime].x + spr->any.x;
	result.y = imgRectIcon[spr->any.anime].y + spr->any.y;
	result.w = imgRectIcon[spr->any.anime].w;
	result.h = imgRectIcon[spr->any.anime].h;

	return &result;
}								/* RectColorSprite */

/*
 *
 */
Sprite *
CreateIconSprite (int x, int y, unsigned anime, int mode)
{
	Sprite *ptr;

	ptr = CreateSprite (STIcon, GUI_DrawIconSprite, RectIconSprite, x, y, 0, anime, mode);
	if (NULL == ptr) {
		return NULL;
	}
	/* mark maze to redrawn */
	if (ptr->any.mode == SPM_MAPPED) {
		MarkMazeSprite ((Sprite *) ptr);
	}
	return ptr;
}								/* CreateIconSprite */

/*
 * public function delete_sprite
 */
void
DeleteSprite (Sprite * spr)
{

	/* check for non-dirty sprites to be redrawn */
	if (NULL == spr->any.prev) {
		spriteFirst = spr->any.next;
	}
	else {
		spr->any.prev->next = spr->any.next;
	}
	if (NULL == spr->any.next) {
		spriteLast = spr->any.prev;
	}
	else {
		spr->any.next->prev = spr->any.prev;
	}
	if (spr->any.mode & SPM_MAPPED) {
		/* mark tiles on position */
		MarkMazeSprite (spr);
	}
	free (spr);
}								/* DeleteSprite */

/*
 * local function swap_sprite_prev
 */
static void
SwapSpritePrev (AnySprite * ptr)
{
	AnySprite *prev;

	if (NULL == (prev = ptr->prev)) {
		return;
	}

	/* references from outside */
	if (prev->prev != NULL) {
		prev->prev->next = ptr;
	}
	else {
		spriteFirst = ptr;
	}
	if (ptr->next != NULL) {
		ptr->next->prev = prev;
	}
	else {
		spriteLast = prev;
	}
	/* internal refs */
	prev->next = ptr->next;
	ptr->prev = prev->prev;

	prev->prev = ptr;
	ptr->next = prev;
}								/* SwapSpritePrev */

/*
 * public function: move_sprite
 */
void
MoveSprite (Sprite * sprite, int x, int y)
{
	AnySprite *spr = (AnySprite *) sprite;
	AnySprite *ptr;

	if ((spr->y == y) && (spr->x == x)) {
		return;
	}

	if (spr->mode & SPM_MAPPED) {
		/* mark sprite as dirty */
		spr->dirty = XBTrue;
		/* mark tiles on old position */
		MarkMazeSprite ((Sprite *) spr);
	}

	spr->x = x;
	if (spr->y < y) {
		spr->y = y;
		/* sprite has move downwards */
		for (ptr = spr->next;
			 (ptr != NULL) && ((ptr->y + ptr->ysort) < (spr->y + spr->ysort)); ptr = spr->next) {
			SwapSpritePrev (ptr);
		}
	}
	else if (spr->y > y) {
		spr->y = y;
		/* sprite has moved upwards */
		for (ptr = spr->prev;
			 (ptr != NULL) && ((ptr->y + ptr->ysort) > (spr->y + spr->ysort)); ptr = spr->prev) {
			SwapSpritePrev (spr);
		}
	}

	/* mark tiles on new position */
	if (spr->mode & SPM_MAPPED) {
		MarkMazeSprite ((Sprite *) spr);
	}
}								/* MoveSprite */

/*
 * public function: set_sprite_mode
 */
void
SetSpriteMode (Sprite * sprite, int mode)
{
	if (mode == sprite->any.mode) {
		return;
	}
	MarkMazeSprite ((Sprite *) sprite);
	sprite->any.mode = mode;
	sprite->any.dirty = XBTrue;
}								/* SetSpriteMode */

/*
 * 
 */
void
SetSpriteText (Sprite * sprite, const char *text)
{
	assert (sprite != NULL);
	assert (sprite->type = STText);

	MarkMazeSprite ((Sprite *) sprite);
	sprite->text.text = text;
	sprite->any.dirty = XBTrue;
}								/* SetSpriteText */

/*
 * 
 */
void
SetSpriteAnime (Sprite * sprite, unsigned anime)
{
	if (anime == sprite->any.anime) {
		return;
	}
	if (sprite->any.mode & SPM_MAPPED) {
		MarkMazeSprite ((Sprite *) sprite);
		sprite->any.dirty = XBTrue;
	}
	sprite->any.anime = anime;
	if (sprite->any.mode & SPM_MAPPED) {
		MarkMazeSprite ((Sprite *) sprite);
	}
}								/* SetSpriteAnime */

/*
 * set color for an icon/xolor sprite
 */
void
SetSpriteColor (Sprite * sprite, XBColor color)
{
	assert (sprite != NULL);
	assert (sprite->type == STIcon);

	GUI_LoadIconSprite (sprite->any.anime, color);
	MarkMazeSprite (sprite);
}								/* SetSpriteColor */

/*
 * local function: sprite_intersect
 */
static void
SpriteIntersect (AnySprite * a, AnySprite * b)
{
	BMRectangle rect_a, rect_b;
	int left, right, top, bottom;

	if ((a->mode & SPM_MAPPED) && (b->mode & SPM_MAPPED)) {
		rect_a = *(*a->rect) ((Sprite *) a);
		rect_b = *(*b->rect) ((Sprite *) b);

		left = MAX (rect_a.x, rect_b.x);
		right = MIN (rect_a.x + rect_a.w, rect_b.x + rect_b.w);

		if (left < right) {
			top = MAX (rect_a.y, rect_b.y);
			bottom = MIN (rect_a.y + rect_a.h, rect_b.y + rect_b.h);
			if (top < bottom) {
				a->dirty = XBTrue;
				b->dirty = XBTrue;
				MarkMazeRect (left, top, right - left, bottom - top);
			}
		}
	}
}								/* SpriteIntersect */

/*
 * 
 */
void
ShuffleAllSprites (void)
{
	AnySprite *ptr;

	/* switch sprites on same (y+ysort) */
	if (spriteFirst != NULL) {
		ptr = spriteFirst;
		while (ptr->next != NULL) {
			if ((ptr->y + ptr->ysort) == (ptr->next->y + ptr->next->ysort)) {
				if ((!ptr->dirty) && (!ptr->next->dirty)) {
					SpriteIntersect (ptr, ptr->next);
				}
				SwapSpritePrev (ptr->next);
			}
			else {
				ptr = ptr->next;
			}
		}
	}
}								/* ShuffleAllSprites */

void
DeleteAllBombSprites (void)
{
	AnySprite *ptr, *tmp;

	/* check for non-dirty sprites to be redrawn */
	for (ptr = spriteFirst; ptr != NULL;) {
		if (ptr->draw == GUI_DrawBombSprite) {
			tmp = ptr->next;
			DeleteSprite ((Sprite *) ptr);
			ptr = tmp;
		}
		else {
			ptr = ptr->next;
		}
	}
}

/*
 * 
 */
void
MarkAllSprites (void)
{
	AnySprite *ptr;

	/* check for non-dirty sprites to be redrawn */
	for (ptr = spriteFirst; ptr != NULL; ptr = ptr->next) {
		if (!ptr->dirty) {
			ptr->dirty = SpriteMarked ((Sprite *) ptr);
		}
	}
}								/* MarkAllSprites */

/*
 * public function draw all sprites
 */
void
DrawAllSprites (void)
{
	AnySprite *ptr;

	for (ptr = spriteFirst; ptr != NULL; ptr = ptr->next) {
		if (ptr->type == STBomb) {
		}
		if ((ptr->mode & SPM_MAPPED) && (ptr->dirty)) {
			(*ptr->draw) ((Sprite *) ptr);
		}
		ptr->dirty = XBFalse;
	}
}								/* DrawAllSprites */

/*
 * get current region of sprite
 */
const BMRectangle *
SpriteRectangle (const Sprite * sprite)
{
	assert (sprite != NULL);
	assert (sprite->any.rect != NULL);
	return (*sprite->any.rect) (sprite);
}								/* SpriteRectangle */

/*
 * get sorite animation phase
 */
int
SpriteAnime (const Sprite * sprite)
{
	assert (sprite != NULL);
	return sprite->any.anime;
}								/* SpriteAnime */

/*
 * get bomb sprite bomb type (mini/normal)
 */
int
SpriteBomb (const Sprite * sprite)
{
	assert (sprite != NULL);
	assert (sprite->type == STBomb);
	return sprite->bomb.bomb;
}								/* SpriteBomb */

/*
 * get player sprite id
 */
int
SpritePlayer (const Sprite * sprite)
{
	assert (sprite != NULL);
	assert (sprite->type == STPlayer);
	return sprite->player.player;
}								/* SpritePlayer */

/*
 * get text sprite text to draw
 */
const char *
SpriteText (const Sprite * sprite)
{
	assert (sprite != NULL);
	assert (sprite->type == STText);
	return sprite->text.text;
}								/* SpriteText */

/*
 * check if sprite is masked
 */
XBBool
SpriteIsMasked (const Sprite * sprite)
{
	assert (sprite != NULL);
	return (sprite->any.mode & SPM_MASKED) ? XBTrue : XBFalse;
}								/* SpriteIsMasked */

/*
 * end of sprite.c
 */
