#include <math.h>
#include <string.h>
#include "main.h"
#include "timeloop.h"
#include "game.h"


DATAFILE *dat;

COLOR_MAP light_table;

static BITMAP *buffer;

AL_DUH_PLAYER *adp = NULL;
static int sound_enabled = 1;

static const char *const credits[] = {
	/*---------------------------, <-- line length limit */
	"",
	"Thank you for playing",
	"",
	"Balls",
	"",
	"entheh's humble entry into",
	"the third Ludum Dare",
	"48-hour competition",
	"",
	"http://www.ludumdare.com/",
	"",
	"That means I wrote all the",
	"code, composed the music,",
	"sampled all the instruments",
	"and sound effects, drew all",
	"the graphics and designed",
	"all the levels in two days",
	"flat. Don't be too harsh!",
	"",
	"This game uses",
	"",
	"Allegro",
	"http://alleg.sf.net/",
	"",
	"DUMB",
	"http://dumb.sf.net/",
	"",
	"Good luck to all",
	"the other entrants",
	"",
	"and",
	"",
	"UNTIL NEXT TIME!",
	"",
	"- Ben \"entheh\" Davis ",
	"See readme.txt for",
	"contact details",
	"and instructions on",
	"designing your own levels!",
	NULL};

#define CREDITS_SPEED_SHIFT 3
#define CREDITS_TIME ((sizeof(credits)/sizeof(credits[0]) * 10 + 48) << CREDITS_SPEED_SHIFT)

typedef struct THE_GAME THE_GAME;

#define TG_SUCCESS 1
#define TG_FAILURE 2
#define TG_CREDITS 4
#define TG_FAST    8

struct THE_GAME
{
	LINKED_LEVEL *ll;
	int n_levels;
	int levelnum; /* 1-based, for user display purposes */
	int max_level; /* maximum level you are allowed to play */
	GAME *game; /* NULL while editing */
	int flags;
	LEVEL level;
	int n_balls_plus_goals; /* mustn't play if this is zero */
	int n_to_place[TILE_N_PLACEABLE]; /* 0 is a special case: destroy */
	int current_tile;
	int old_mouse_b;
	int credits_time;
};


static void draw_tile_and_sprite(int i, int x, int y)
{
	draw_tile(buffer, i, 0, 0, x, y, logic_time);
	if (i == TILE_BLANK)
		draw_ball_or_explosion(buffer,
			DBE_EXPLOSION,
			TILE_EXPLOSION_BASE, 0, 0, x, y, 0, logic_time, 0);
	else if (TILE_IS_BALL(i))
		draw_ball_or_explosion(buffer,
			DBE_LEFT_RIGHT_STUCK | DBE_UP | DBE_DOWN | DBE_ARROW,
			i, i, 0, x, y, 0, logic_time, 0);
}


static void draw_the_game(void *data)
{
	THE_GAME *the_game = data;
	int mx = mouse_x;
	int my = mouse_y;
	if (the_game->game) {
		draw_level(buffer, the_game->game, &the_game->game->level, logic_time);
		draw_sprite(buffer, dat[the_game->flags & TG_FAST ? GFX_BUTTON_SLOW_DOWN : GFX_BUTTON_SPEED_UP].dat, 520, 432);
		if (the_game->flags & TG_SUCCESS) {
			if (the_game->flags & TG_CREDITS) {
				int x = (((BITMAP *)dat[GFX_MESSAGE_FINAL_COMPLETE].dat)->w + 520) >> 1;
				int y;
				buffer->ct = 432;
				for (y = 0; credits[y]; y++)
					textout_centre(buffer, font, credits[y], x, 480 + y*10 - (the_game->credits_time >> CREDITS_SPEED_SHIFT), COL_YELLOW);
				buffer->ct = 0;
				draw_sprite(buffer, dat[GFX_MESSAGE_FINAL_COMPLETE].dat, 0, 432);
			} else
				draw_sprite(buffer, dat[GFX_MESSAGE_COMPLETE].dat, 0, 432);
			draw_sprite(buffer, dat[GFX_BUTTON_REPLAY].dat, 600, 432);
			draw_sprite(buffer, dat[GFX_BUTTON_REDO].dat, 600, 448);
		} else {
			draw_sprite(buffer, dat[the_game->flags & TG_FAILURE ? GFX_MESSAGE_NO_BALLS_LEFT : GFX_MESSAGE_ACTIVATED].dat, 0, 432);
			draw_sprite(buffer, dat[GFX_BUTTON_START_AGAIN].dat, 600, 432);
		}
	} else {
		int i;
		draw_level(buffer, NULL, &the_game->level, logic_time);
		text_mode(-1);
		for (i = 0; i < TILE_N_PLACEABLE; i++) {
			color_map = the_game->n_to_place[i] ? NULL : &light_table;
			if (i == the_game->current_tile) {
#if COL_LAST_BLUE - COL_FIRST_BLUE != 8
#error oops
#endif
				int blue = logic_time & 32 ? COL_LAST_BLUE - ((logic_time & 31) >> 2) : COL_FIRST_BLUE + ((logic_time & 31) >> 2);
				rectfill(buffer, i*(TILE_W+2*4), LEVEL_H*TILE_H+WALL_H, i*(TILE_W+2*4)+TILE_W+2*4-1, 479, blue);
			}
			draw_tile_and_sprite(i, 4+i*(TILE_W+2*4), 480-TILE_H-WALL_H-10);
			textprintf_right(buffer, font, 4+i*(TILE_W+2*4)+TILE_W, 480-8, the_game->n_to_place[i] ? COL_WHITE : COL_GREY, "%d", the_game->n_to_place[i]);
		}

		color_map = &light_table;

		if (the_game->n_balls_plus_goals)
			draw_sprite(buffer, dat[GFX_BUTTON_GO].dat, 520, 432);
		else
			draw_lit_sprite(buffer, dat[GFX_BUTTON_GO].dat, 520, 432, 128);

		draw_sprite(buffer, dat[GFX_BUTTON_START_AGAIN].dat, 600, 432);
	}
	textprintf_right(buffer, font, 572, 468, COL_WHITE, "%d", the_game->levelnum);

	color_map = &light_table;

	if (the_game->ll->prev)
		draw_sprite(buffer, dat[GFX_BUTTON_BACK].dat, 520, 464);
	else
		draw_lit_sprite(buffer, dat[GFX_BUTTON_BACK].dat, 520, 464, 128);

	if (the_game->levelnum < the_game->max_level)
		draw_sprite(buffer, dat[GFX_BUTTON_FORWARD].dat, 576, 464);
	else
		draw_lit_sprite(buffer, dat[GFX_BUTTON_FORWARD].dat, 576, 464, 128);

	draw_sprite(buffer, dat[GFX_BUTTON_EXIT].dat, 600, 464);

	if (!the_game->game && my < LEVEL_H*TILE_H+WALL_H) {
		if (the_game->current_tile >= 0) {
			color_map = &light_table;
			draw_tile_and_sprite(the_game->current_tile, mx + 8, my + 8);
		}
	}
	draw_sprite(buffer, dat[GFX_MOUSE_POINTER].dat, mx, my);

	blit(buffer, screen, 0, 0, 0, 0, 640, 480);
}


static void init_level(THE_GAME *the_game)
{
	int x, y;
	if (the_game->game) {
		destroy_game(the_game->game);
		the_game->game = NULL;
	}
	if (the_game->flags & TG_FAST) timeloop_speed(BPS_TO_TIMER(50));
	the_game->flags &= ~(TG_SUCCESS | TG_FAILURE | TG_CREDITS | TG_FAST);
	the_game->level = the_game->ll->level;
	memcpy(the_game->n_to_place, the_game->ll->n_to_place, sizeof(the_game->n_to_place));
	for (y = 0; y < LEVEL_H; y++)
		for (x = 0; x < LEVEL_W; x++)
			if (TILE_PLACEABLE_IS_BALL_OR_GOAL(the_game->level.map[y][x]))
				the_game->n_balls_plus_goals++;
	for (y = 0; y < TILE_N_PLACEABLE; y++) {
		if (the_game->n_to_place[y]) {
			the_game->current_tile = y;
			return;
		}
	}
	the_game->current_tile = -1;
}


static void set_current_tile(THE_GAME *the_game, int i)
{
	int v;
	the_game->current_tile = i;
	v = allocate_voice(dat[SFX_SELECT_TILE].dat);
	if (v >= 0) {
		double x = pow(2, i/12.0);
		voice_set_frequency(v, (int)(22050 * x));
		voice_set_volume(v, (int)(255 / x));
		voice_set_pan(v, (4+TILE_W/2+i*(TILE_W+2*4)) * 256 / 640);
		voice_start(v);
		voice_ramp_volume(v, 300, 0);
		release_voice(v);
	}
}


static void reduce_tile_count(THE_GAME *the_game)
{
	if (--the_game->n_to_place[the_game->current_tile] == 0) {
		int i = the_game->current_tile;
		for (;;) {
			i++;
			i %= TILE_N_PLACEABLE;
			if (i == the_game->current_tile) {
				the_game->current_tile = -1;
				return;
			} else if (the_game->n_to_place[i]) {
				set_current_tile(the_game, i);
				return;
			}
		}
	}
}


static int update_the_game(void *data)
{
	THE_GAME *the_game = data;
	int mx = mouse_x;
	int my = mouse_y;
	int mb = mouse_b;

	if (the_game->game) {
		propagate_game(the_game->game);
		if (!(the_game->flags & TG_SUCCESS)) {
			if (the_game->game->goals_left == 0) {
				play_sample(dat[SFX_LEVEL_COMPLETE].dat, 255, 128, 1000, 0);
				the_game->flags |= TG_SUCCESS;
				if (the_game->levelnum < the_game->n_levels) {
					if (the_game->levelnum >= the_game->max_level)
						the_game->max_level++;
				} else {
					the_game->flags |= TG_CREDITS;
					the_game->credits_time = 0;
				}
			} else if (!(the_game->flags & TG_FAILURE) && the_game->game->balls_left == 0) {
				play_sample(dat[SFX_NO_BALLS_LEFT].dat, 64, 128, 1000, 0);
				the_game->flags |= TG_FAILURE;
			}
		} else if (the_game->flags & TG_CREDITS) {
			the_game->credits_time++;
			the_game->credits_time %= CREDITS_TIME;
		}
	}

	if (mb & ~the_game->old_mouse_b) {
		/* A button has been pressed. Doesn't matter which. */
		if (my < LEVEL_H*TILE_H+WALL_H) {
			if (the_game->game) {
				if (the_game->flags & TG_SUCCESS) {
					if (the_game->levelnum < the_game->max_level) {
						the_game->ll = the_game->ll->next;
						the_game->levelnum++;
						init_level(the_game);
						play_sample(dat[SFX_CLICK_MOUSE].dat, 128, mx * 256 / 640, 1000, 0);
					}
				} else if (the_game->flags & TG_FAILURE) {
					init_level(the_game);
					play_sample(dat[SFX_CLICK_MOUSE].dat, 128, mx * 256 / 640, 1000, 0);
				}
			} else {
				if (the_game->current_tile >= 0) {
					int x = mx / TILE_W, y = (my - WALL_H) / TILE_H;
					if (x >= 1 && x < LEVEL_W - 1 && y >= 1 && y < LEVEL_H) {
						if (the_game->current_tile == 0) {
							if (the_game->level.map[y][x] != TILE_BLANK && the_game->level.map[y][x] != TILE_GOAL) {
								if (TILE_PLACEABLE_IS_BALL_OR_GOAL(the_game->level.map[y][x]))
									the_game->n_balls_plus_goals--;
								the_game->level.map[y][x] = TILE_BLANK;
								// animate explosion? (play bang instead of click?)
								reduce_tile_count(the_game);
								play_sample(dat[SFX_CLICK_MOUSE].dat, 128, mx * 256 / 640, 1000, 0);
							}
						} else {
							if (the_game->level.map[y][x] == TILE_BLANK) {
								the_game->level.map[y][x] = the_game->current_tile;
								if (TILE_PLACEABLE_IS_BALL_OR_GOAL(the_game->current_tile))
									the_game->n_balls_plus_goals++;
								reduce_tile_count(the_game);
								play_sample(dat[SFX_CLICK_MOUSE].dat, 128, mx * 256 / 640, 1000, 0);
							}
						}
					}
				}
			}
		} else if (mx < TILE_N_PLACEABLE * (TILE_W+2*4)) {
			if (the_game->game) {
				if (the_game->flags & TG_SUCCESS) {
					if (the_game->levelnum < the_game->max_level) {
						the_game->ll = the_game->ll->next;
						the_game->levelnum++;
						init_level(the_game);
						play_sample(dat[SFX_CLICK_MOUSE].dat, 128, mx * 256 / 640, 1000, 0);
					}
				} else if (the_game->flags & TG_FAILURE) {
					init_level(the_game);
					play_sample(dat[SFX_CLICK_MOUSE].dat, 128, mx * 256 / 640, 1000, 0);
				}
			} else {
				int i = mx / (TILE_W+2*4);
				if (the_game->n_to_place[i]) set_current_tile(the_game, i);
			}
		} else if (my >= 464) {
			if (mx >= 600) {
				play_sample(dat[SFX_CLICK_MOUSE].dat, 128, mx * 256 / 640, 1000, 0);
				return 1;
			} else if (mx >= 576) {
				if (the_game->levelnum < the_game->max_level) {
					the_game->ll = the_game->ll->next;
					the_game->levelnum++;
					init_level(the_game);
					play_sample(dat[SFX_CLICK_MOUSE].dat, 128, mx * 256 / 640, 1000, 0);
				}
			} else if (mx < 544) {
				if (the_game->ll->prev) {
					the_game->ll = the_game->ll->prev;
					the_game->levelnum--;
					init_level(the_game);
					play_sample(dat[SFX_CLICK_MOUSE].dat, 128, mx * 256 / 640, 1000, 0);
				}
			}
		} else if (the_game->game) {
			if (mx < 600) {
				the_game->flags ^= TG_FAST;
				timeloop_speed(the_game->flags & TG_FAST ? BPS_TO_TIMER(150) : BPS_TO_TIMER(50));
				play_sample(dat[SFX_CLICK_MOUSE].dat, 128, mx * 256 / 640, 1000, 0);
			} else if (the_game->flags & TG_SUCCESS && my < 448) {
				destroy_game(the_game->game);
				the_game->game = create_game(&the_game->level);
				play_sample(dat[SFX_ACTIVATE_LEVEL].dat, 128, 128, 1000, 0);
			} else {
				init_level(the_game);
				play_sample(dat[SFX_CLICK_MOUSE].dat, 128, mx * 256 / 640, 1000, 0);
			}
		} else {
			if (mx < 600) {
				if (the_game->n_balls_plus_goals) {
					the_game->game = create_game(&the_game->level);
					play_sample(dat[SFX_ACTIVATE_LEVEL].dat, 128, 128, 1000, 0);
				}
			} else {
				init_level(the_game);
				play_sample(dat[SFX_CLICK_MOUSE].dat, 128, mx * 256 / 640, 1000, 0);
			}
		}
	}
	the_game->old_mouse_b = mb;

	while (keypressed()) {
		int k = readkey();
		if (k >> 8 == KEY_ESC) return 1;
	}
	return 0;
}


static void usage(const char *exename)
{
	allegro_message(
		"Usage: %s [options] [filename] [more-options]\n"
		"  filename: levels will be loaded from this (default levels.txt)\n"
		"Valid options are:\n"
		"  --nosound  - run without sound and music\n"
#ifndef ALLEGRO_DOS
		"  --windowed - run in a window\n"
#endif
		, exename);
	exit(1);
}


int main(int argc, const char *const *argv);
int main(int argc, const char *const *argv) /* :P */
{
	const char *level_filename = NULL;
	int i;
	int gfx_mode = GFX_AUTODETECT;

	allegro_init();

	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			if (stricmp(argv[i], "--nosound") == 0)
				sound_enabled = 0;
#ifndef ALLEGRO_DOS
			else if (stricmp(argv[i], "--windowed") == 0)
				gfx_mode = GFX_AUTODETECT_WINDOWED;
#endif
			else
				usage(argv[0]);
		} else if (!level_filename)
			level_filename = argv[i];
		else
			usage(argv[0]);
	}
	if (!level_filename) level_filename = "levels.txt";

	install_timer();
	init_timeloop();
	install_keyboard();
	reserve_voices(64, -1);
	set_volume_per_voice(0);
	if (sound_enabled) {
		set_config_int("sound", "quality", 2);
		sound_enabled = install_sound(DIGI_AUTODETECT, MIDI_NONE, 0) == 0;
	}
	atexit(&dumb_exit);

	dumb_register_dat_it(DUMB_DAT_IT);

	if (install_mouse() < 0) {
		allegro_message("Sorry, this game requires a mouse.\n");
		return 1;
	}

	dat = load_datafile("balls.dat");
	if (!dat) {
		allegro_message("Unable to load balls.dat\n");
		return 1;
	}

	{
		THE_GAME the_game;
		the_game.ll = load_levels(level_filename, &the_game.n_levels);
		if (!the_game.ll) {
			unload_datafile(dat);
			set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
			allegro_message("Unable to load any levels from %s\n", level_filename);
			return 1;
		}

		set_window_title((char *)"Balls");

		if (set_gfx_mode(gfx_mode, 640, 480, 0, 0)) {
			unload_datafile(dat);
			set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
			allegro_message("Unable to set 640x480x8 graphics mode\n%s\n", allegro_error);
			return 1;
		}
		set_palette(dat[THE_PALETTE].dat);

		create_light_table(&light_table, dat[THE_PALETTE].dat, 0, 0, 0, NULL);

		buffer = create_bitmap(640, 480);
		if (!buffer) abort();

		the_game.levelnum = 1;
		the_game.max_level = 1;
		the_game.game = NULL;
		the_game.flags = 0;
		the_game.old_mouse_b = -1;

		init_level(&the_game);

		if (sound_enabled)
			adp = al_start_duh(dat[THE_MUSIC].dat, 2, 0, 0.5f, 4096, 44100);

		timeloop(&draw_the_game, &update_the_game, &the_game, BPS_TO_TIMER(50));

		al_stop_duh(adp);
		adp = NULL;

		unload_levels(the_game.ll);
	}

	destroy_bitmap(buffer);
	unload_datafile(dat);
	return 0;
}
END_OF_MAIN()
