#include "main.h"
#include "game.h"


#define BALL_TRUE_Y(ball) ((ball)->y + ((ball)->dir == TILE_BALL_DOWN))
#define BALL_SORT_HASH(ball) (BALL_TRUE_Y(ball)*2 - ((ball)->dir == TILE_BALL_UP))


static BALL *ball_mergesort(BALL *b1)
{
	BALL *ball = b1, **bp = &b1, *b2;
	while (ball) {
		ball = ball->next;
		if (!ball) break;
		bp = &(*bp)->next;
		ball = ball->next;
	}
	b2 = *bp;
	*bp = NULL;
	if (!b1) return b2;
	if (!b2) return b1;

	b1 = ball_mergesort(b1);
	b2 = ball_mergesort(b2);

	bp = &ball;
	while (b1 && b2) {
		if (BALL_SORT_HASH(b1) < BALL_SORT_HASH(b2)) {
			*bp = b1;
			bp = &b1->next;
			b1 = b1->next;
		} else {
			*bp = b2;
			bp = &b2->next;
			b2 = b2->next;
		}
	}
	if (b1)
		*bp = b1;
	else
		*bp = b2;
	return ball;
}


static void draw_sprite_helper(BITMAP *bmp, BITMAP *sprite, int x, int y)
{
	if (color_map)
		draw_lit_sprite(bmp, sprite, x, y, 128);
	else
		draw_sprite(bmp, sprite, x, y);
}


void draw_tile(BITMAP *bmp, unsigned char c, int x, int y, int xs, int ys, int frame)
{
	static const int bomb_datid[] = {
		GFX_BOMB_0, GFX_BOMB_0, GFX_BOMB_0, GFX_BOMB_0,
		GFX_BOMB_0, GFX_BOMB_1, GFX_BOMB_1, GFX_BOMB_1,
		GFX_BOMB_2, GFX_BOMB_2, GFX_BOMB_2, GFX_BOMB_2,
		GFX_BOMB_2, GFX_BOMB_1, GFX_BOMB_1, GFX_BOMB_1
	};
	switch (c) {
		case TILE_WALL:
			draw_sprite_helper(bmp, dat[GFX_WALL].dat, xs, ys);
			break;
		case TILE_WEAK_WALL:
			draw_sprite_helper(bmp, dat[GFX_WEAK_WALL].dat, xs, ys);
			break;
		case TILE_DEFLECTOR_UP_RIGHT:
			draw_sprite_helper(bmp, dat[GFX_DEFLECTOR_UP_RIGHT].dat, xs, WALL_H+ys);
			break;
		case TILE_DEFLECTOR_DOWN_RIGHT:
			draw_sprite_helper(bmp, dat[GFX_DEFLECTOR_DOWN_RIGHT].dat, xs, WALL_H+ys);
			break;
		case TILE_MOUND:
			draw_sprite_helper(bmp, dat[GFX_MOUND].dat, xs, WALL_H+ys);
			break;
		case TILE_PIT:
			draw_sprite_helper(bmp, dat[GFX_PIT].dat, xs, WALL_H+ys);
			break;
		case TILE_GOAL:
			draw_sprite_helper(bmp, dat[GFX_GOAL].dat, xs, WALL_H+ys);
			break;
		case TILE_FILLED_GOAL:
			draw_sprite_helper(bmp, dat[GFX_FILLED_GOAL].dat, xs, WALL_H+ys);
			break;
		case TILE_BOMB:
			draw_sprite_helper(bmp, dat[GFX_BASETILE].dat, xs, WALL_H+ys);
			draw_sprite_helper(bmp, dat[bomb_datid[((frame>>2)-x-y)&15]].dat, xs, ys - 6);
			break;
		default:
			draw_sprite_helper(bmp, dat[GFX_BASETILE].dat, xs, WALL_H+ys);
			break;
	}
}


#define DBE_EXPLOSION         1
#define DBE_LEFT_RIGHT_STUCK  2
#define DBE_UP                4
#define DBE_DOWN              8
#define DBE_ARROW            16

void draw_ball_or_explosion(BITMAP *bmp, int flags, unsigned char tile, int x, int y, int xs, int ys, int advance, int frame, int y_offset)
{
	static const int explosion_datid[] = {
		GFX_EXPLOSION_0, GFX_EXPLOSION_0, GFX_EXPLOSION_1, GFX_EXPLOSION_1,
		GFX_EXPLOSION_2, GFX_EXPLOSION_2, GFX_EXPLOSION_2, GFX_EXPLOSION_3,
		GFX_EXPLOSION_3, GFX_EXPLOSION_3, GFX_EXPLOSION_4, GFX_EXPLOSION_4,
		GFX_EXPLOSION_4, GFX_EXPLOSION_5, GFX_EXPLOSION_5, GFX_EXPLOSION_5
	};
	static const int arrow_y_offset[] = {
		-4,-4,-4,-4,-4,-3,-3,-3,-2,-2,-2,-2,-2,-3,-3,-3
	};
	int xd, yd, datid;
	COLOR_MAP *temp_color_map;
	switch (tile) {
		case TILE_EXPLOSION_BASE:
		case TILE_EXPLOSION_SOURCE_BASE:
			if (flags & DBE_EXPLOSION) draw_sprite_helper(bmp, dat[explosion_datid[frame&15]].dat, xs, ys + 2);
			return;
		case TILE_EXPLOSION_BASE + 1:
		case TILE_EXPLOSION_SOURCE_BASE + 1:
			if (flags & DBE_EXPLOSION) draw_sprite_helper(bmp, dat[explosion_datid[(frame+12)&15]].dat, xs, ys + 2);
			return;
		case TILE_EXPLOSION_BASE + 2:
		case TILE_EXPLOSION_SOURCE_BASE + 2:
			if (flags & DBE_EXPLOSION) draw_sprite_helper(bmp, dat[explosion_datid[(frame+8)&15]].dat, xs, ys + 2);
			return;
		case TILE_EXPLOSION_BASE + 3:
		case TILE_EXPLOSION_SOURCE_BASE + 3:
			if (flags & DBE_EXPLOSION) draw_sprite_helper(bmp, dat[explosion_datid[(frame+4)&15]].dat, xs, ys + 2);
			return;
		case TILE_BALL_LEFT:
			if (!(flags & DBE_LEFT_RIGHT_STUCK)) return;
			xd = (advance * -TILE_W + ADVANCE_MAX/2) >> ADVANCE_MAX_SHIFT;
			yd = 0;
			datid = GFX_BALL_RIGHT_00 + (-frame & 15);
			break;
		case TILE_BALL_RIGHT:
			if (!(flags & DBE_LEFT_RIGHT_STUCK)) return;
			xd = (advance * TILE_W + ADVANCE_MAX/2) >> ADVANCE_MAX_SHIFT;
			yd = 0;
			datid = GFX_BALL_RIGHT_00 + (frame & 15);
			break;
		case TILE_BALL_UP:
			if (!(flags & DBE_UP)) return;
			xd = 0;
			yd = (advance * -TILE_H + ADVANCE_MAX/2) >> ADVANCE_MAX_SHIFT;
			datid = GFX_BALL_DOWN_00 + (-frame & 15);
			break;
		case TILE_BALL_DOWN:
			if (!(flags & DBE_DOWN)) return;
			xd = 0;
			yd = (advance * TILE_H + ADVANCE_MAX/2) >> ADVANCE_MAX_SHIFT;
			datid = GFX_BALL_DOWN_00 + (frame & 15);
			break;
		case TILE_BALL_LEFT_STUCK:
		case TILE_BALL_RIGHT_STUCK:
		case TILE_BALL_UP_STUCK:
		case TILE_BALL_DOWN_STUCK:
			if (!(flags & DBE_LEFT_RIGHT_STUCK)) return;
			xd = 0;
			yd = 0;
			datid = GFX_BALL_DOWN_00;
			break;
		default:
			return;
	}
	temp_color_map = color_map;
	color_map = &light_table;
	draw_trans_sprite(bmp, dat[GFX_SHADOW].dat, xs + (32-24)/2 + xd, ys + WALL_H + (28-21)/2 + yd);
	color_map = temp_color_map;
	yd += y_offset;
	draw_sprite_helper(bmp, dat[datid].dat, xs + (32-24)/2 + xd, ys + 8 + yd);
	if (flags & DBE_ARROW)
		draw_sprite_helper(bmp, dat[GFX_ARROW_0_LEFT + TILE_RAW_DIR(tile)].dat, xs + (32-24)/2 + xd, ys + arrow_y_offset[((frame>>1)-x-y)&15] + yd);
}


void draw_level(BITMAP *bmp, GAME *game, LEVEL *level, int frame)
{
	int x, y;
	BALL *ball = game ? game->ball = ball_mergesort(game->ball) : NULL;
	int advance = game ? game->advance : 0;
	int flags = game ? 0 : DBE_ARROW;

	color_map = NULL;

	for (y = 0; y < LEVEL_H; y++) {
		for (x = 0; x < LEVEL_W; x++) {
			unsigned char c = level->map[y][x];
			if (TILE_IS_BALL(c) || TILE_IS_EXPLOSION(c)) c = game ? game->under[y][x] : TILE_BLANK;
			draw_tile(bmp, c, x, y, x*TILE_W, y*TILE_H, frame);
		}
		if (advance && y > 0) for (x = 0; x < LEVEL_W; x++) {
			draw_ball_or_explosion(bmp, flags | DBE_DOWN, level->map[y-1][x], x, y-1, x*TILE_W, (y-1)*TILE_H, advance, game ? advance : frame, 0);
		}
		for (x = 0; x < LEVEL_W; x++) {
			draw_ball_or_explosion(bmp, flags | DBE_UP, level->map[y][x], x, y, x*TILE_W, y*TILE_H, advance, game ? advance : frame, 0);
		}
		for (x = 0; x < LEVEL_W; x++) {
			draw_ball_or_explosion(bmp,
				flags | DBE_EXPLOSION | DBE_LEFT_RIGHT_STUCK | (advance ? 0 : DBE_DOWN),
				level->map[y][x], x, y, x*TILE_W, y*TILE_H, advance, game ? advance : frame, 0);
		}
		while (ball && (y == LEVEL_H-1 || BALL_TRUE_Y(ball) <= y)) {
			draw_ball_or_explosion(bmp,
				DBE_LEFT_RIGHT_STUCK | DBE_UP | DBE_DOWN,
				ball->dir, ball->x, ball->y, ball->x*TILE_W, ball->y*TILE_H,
				advance, advance,
				ball->phase ?
					(advance*advance - advance*(ADVANCE_MAX*2)) >> (ADVANCE_MAX_SHIFT*2 - 5) :
					(advance*advance - ADVANCE_MAX*ADVANCE_MAX) >> (ADVANCE_MAX_SHIFT*2 - 5));
			ball = ball->next;
		}
	}

	rectfill(bmp, 0, LEVEL_H*TILE_H+WALL_H, 639, 479, 0);
}
