/* Bob and entheh */

#include <string.h>

#include "main.h"
#include "options.h"
#include "ripples.h"
#include "level.h"
#include "levelmze.h"
#include "tilemap.h"
#include "sprite.h"
#include "s_anim_s.h"
#include "log.h"
#include "texture.h"
#include "screen.h"


#define MAX_LEVELS 4096


/* These arrays will store all the levels loaded from the disk.
   the_level_names[] is compatible with md_selection() (see menuproc.h).
   n_levels should be initialised to 0.
*/
LEVEL *the_levels[MAX_LEVELS];
char *the_level_names[MAX_LEVELS];
int n_levels = 0;

const char *level_player_names[N_PLAYERS] = {"[Cat]", "[Rabbit]", "[Duck]", "[Chipmunk]"};


LEVEL *create_level() {

	LEVEL *lev = malloc(sizeof(LEVEL));

	if (!lev) {
		LOG("Level::create_level: Ran out of memory while trying to allocate %i bytes.\n", sizeof(LEVEL));
		return NULL;
	}

	memset(lev, 0, sizeof(LEVEL));

	return lev;
}

void destroy_level(LEVEL *lev) {

	if (!lev)
		return;

	if (lev->map)
		destroy_tilemap(lev->map);

	if (lev->HeightMap)
		destroy_bitmap(lev->HeightMap);
	if (lev->TextureMap)
		destroy_bitmap(lev->TextureMap);

	if (lev->portalsprite)
		destroy_all_sprites(lev->portalsprite);
	
	free(lev);

	return;
}



void add_level(LEVEL *lev, const char *name, int pos) {

	int i;

	if (!lev)
		return;

	lev->number = pos;

	/* Add the level to the level array */
	if (pos == -1) {
		/* Find first free spot */
		for (i = 0; i < MAX_LEVELS; i++) {
			if (!the_levels[i]) {
				the_levels[i] = lev;
				the_level_names[i] = ustrdup(name);
                if (!the_level_names[i]) exit(37);
				break;
			}
		}
		/* No free spots? */
		if (i >= MAX_LEVELS) {
			LOG(ERROR "Level::add_level: No more free spots to insert level %s!\n", name);
			return;
		}
	}
	else {
		/* Spot occupied? */
		if (the_levels[pos]) {

			/* Maybe we can shuffle things around */
			if (the_levels[pos]->number == -1) {

				/* Find first free spot */
				for (i = 0; i < MAX_LEVELS; i++) {
					if (!the_levels[i]) {
						the_levels[i] = the_levels[pos];
						the_level_names[i] = the_level_names[pos];
						break;
					}
				}
				/* No free spots? */
				if (i >= MAX_LEVELS) {
					LOG(ERROR "Level::add_level: No more free spots to move level %s!\n", the_level_names[pos]);
					LOG(ERROR "                  Getting rid of that level.\n");
					destroy_level(the_levels[pos]);
					free(the_level_names[pos]);
				}
				the_levels[pos] = lev;
				the_level_names[pos] = ustrdup(name);
                if (!the_level_names[pos]) exit(37);
			}
			/* Level collision */
			else {
				LOG(ERROR "Level::add_level: Level collision between %s and %s!\n", name, the_level_names[pos]);
				LOG(ERROR "                  Level %s will be dumped.\n", the_level_names[pos]);

				destroy_level(the_levels[pos]);
				free(the_level_names[pos]);

				the_levels[pos] = lev;
				the_level_names[pos] = ustrdup(name);
                if (!the_level_names[pos]) exit(37);
			}
		}
		else {
			/* Spot is free - just add it */
			the_levels[pos] = lev;
			the_level_names[pos] = ustrdup(name);
            if (!the_level_names[pos]) exit(37);
		}
	}
	return;
}




/* This creates the tile map and fills it with initial data, according to the
   level data as loaded from disk. The height and texture bitmaps are
   destroyed afterwards. Though this function will improvise if the texture
   map is unavailable, the height map must be there.

   The tile map set up by this function is completely ready for the
   draw_level() function, as required by the preview. Ripple tiles are not
   created; they will be flatshaded.

   Texturemap and Heightmap must be 8bpp bitmaps.
*/
static void level_initialise_tilemap(LEVEL *level) {

	int i, j;
	int w, h;

    if (!level->HeightMap) return;

    level->map = create_tilemap(level->HeightMap->w, level->HeightMap->h);

	if (!level->map) exit(37);

	w = level->map->w;  h = level->map->h;
	level->map->border_texture = level->border;
	level->portalsprite = 0;


	for (j = 0; j < h; j++) {
		for (i = 0; i < w; i++) {

			TILE *tile = &(level->map->tile[j][i]);
			int height = getpixel(level->HeightMap, i, j);
			int texture = level->TextureMap ? getpixel(level->TextureMap, i, j) : 0;


			/* Set tile type */
			tile->type = (((texture == TEXTURE_WATER) || (texture == TEXTURE_ACID)) ? TILE_TYPE_LIQUID
				: ((texture == TEXTURE_LAVA) ? TILE_TYPE_LAVA
				: TILE_TYPE_SOLID));

			/* Set tile texture and ripples */
			tile->texture = texture;
			tile->ripples = NULL;

			/* Set tile extra-info */
			memset(&tile->damage, 0, sizeof(TILE_SURFACES));
			memset(&tile->blood,  0, sizeof(TILE_SURFACES));

			/* Set tile height */
			tile->z = height * (level->scale_factor / 255.0);

			tile->u.subsidence = 5;

			/* Create portal if necessary */
			if (texture == TEXTURE_PORTAL) {

				level->portalsprite = add_portal_sprite(level->map, level->portalsprite, i, j);
			}
		}
	}

    destroy_bitmap(level->HeightMap);
    level->HeightMap = 0;

    destroy_bitmap(level->TextureMap);
    level->TextureMap = 0;
}



/* load_level(): suitable for use with for_each_file(), this function loads a
   level and adds it to 'the_levels'. If it fails to load a level owing to a
   corrupt file or lack of memory, it should free any memory belonging to the
   partial level and return. It should *not* set errno; on the contrary it
   should make sure errno is clear before returning, so that the other levels
   will be loaded. However, it can safely use allegro_message() to report
   levels that could not be loaded. Errors should look like this:

   "Unable to load %s\n"

   %s is the level filename, obviously.

   If the arrays are full (n_levels == MAX_LEVELS), then we should display:

   "Unable to accommodate so many levels!\n"

   (Make sure you spell 'accommodate' right, even though no sane person will
   have 4097 levels on their disk :-P)

   Then set errno to ENOMEM. That way for_each_file() will not try to load
   any further levels.
*/
void load_level(const char *filename, int attrib, int param) {

	char buf[512], buf2[512];
	LEVEL *lev;
	int i, cur_player = 0, r;
	int pos = -1;
	int pass = 0;
	char *lev_name = NULL;
	float f;

	PACKFILE *p;

	(void)attrib;
	(void)param;

	if (!filename)
		return;

	p = pack_fopen(filename, F_READ_PACKED);

	if (!p) {
		p = pack_fopen(filename, F_READ);

		if (!p) {
			allegro_message("Unable to open level file: %s\n  (%s)", filename, allegro_error);
			errno = 0;
			return;
		}
	}

	/* Should make sure color 0 is bright pink for the color conversion */
	lev = create_level();

	if (!lev) {
		errno = 0;
		return;
	}


	while(pack_fgets(buf, 512, p)) {

		if (buf[0] == '#')
			continue;

		r = ustricmp(buf, "[level]");

		if (!r) {
			if (!pass) {
				pass = 1;
				continue;
			}

			/* Add current level */
			if (lev) {
				if (!lev_name) {
					lev_name = ustrdup("Untitled");
                    if (!lev_name) exit(37);
                }

				add_level(lev, lev_name, pos);

				if (lev->HeightMap)
					level_initialise_tilemap(lev);

				if (lev_name)
					free(lev_name);
				lev_name = NULL;
			}
			/* Start a new level */
			lev = create_level();

			if (!lev) {
				LOG(ERROR "Level::load_level: Unable to load level!\n");
				errno = 0;
				return;
			}
			continue;
		}

		r = sscanf(buf, "Name=%[^\n]", buf2);
		if (r <= 0)
			r = sscanf(buf, "Name = %[^\n]", buf2);

		if (r > 0) {
			TRACE("Set level name to %s\n", buf2);

			if (lev_name)
				free(lev_name);

			lev_name = ustrdup(buf2);
            if (!lev_name) exit(37);
			continue;
		}

		r = sscanf(buf, "Number=%i", &i);
		if (r <= 0)
			r = sscanf(buf, "Number = %i", &i);

		if (r > 0) {
			pos = i;
			continue;
		}


		r = sscanf(buf, "BorderTexture=%i", &i);
		if (r <= 0)
			r = sscanf(buf, "BorderTexture = %i", &i);

		if (r > 0) {
			lev->border = i;
			continue;
		}

		r = sscanf(buf, "Scale=%f", &f);
		if (r <= 0)
			r = sscanf(buf, "Scale = %f", &f);

		if (r > 0) {
			lev->scale_factor = f;
			continue;
		}

		r = -1;
		for (i = 0; i < N_PLAYERS; i++) {
			r = ustricmp(buf, level_player_names[i]);
			if (!r) {
				cur_player = i;
				break;
			}
		}
		if (!r)
			continue;

		r = sscanf(buf, "X = %i", &i);
		if (r > 0) {
			lev->player[cur_player].x = i;
			continue;
		}
		r = sscanf(buf, "X=%i", &i);
		if (r > 0) {
			lev->player[cur_player].x = i;
			continue;
		}

		r = sscanf(buf, "Y = %i", &i);
		if (r > 0) {
			lev->player[cur_player].y = i;
			continue;
		}
		r = sscanf(buf, "Y=%i", &i);
		if (r > 0) {
			lev->player[cur_player].y = i;
			continue;
		}
		r = sscanf(buf, "Direction = %i", &i);
		if (r > 0) {
			lev->player[cur_player].dir = i;
			continue;
		}
		r = sscanf(buf, "Direction=%i", &i);
		if (r > 0) {
			lev->player[cur_player].dir = i;
			LOG("etting player direction\n");
			continue;
		}

		r = sscanf(buf, "HeightMap=%[^\n]", buf2);
		if (!r)
			r = sscanf(buf, "HeightMap = %[^\n]", buf2);

		if (r > 0) {
			PALETTE pal;
			char *g;

			ustrcpy(buf, buf2);

			/* Attempt to load it as a datafile object */
			if ((g = ustrstr(buf2, "#"))) {
				DATAFILE *d;
				BITMAP *b = NULL;
				char oldg;

				set_color_conversion(COLORCONV_NONE);

				oldg = ugetc(g);
				usetat(g, 0, 0);
				d = load_datafile_object(buf2, g + ucwidth(0));
				usetat(g, 0, oldg);

				if (!d) {
					LOG("Error loading the datafile. Trying direct bitmap reading\n");
					lev->HeightMap = load_bitmap(buf2, pal);
				}
				else {
					if (d->type == DAT_BITMAP) {
						b = create_bitmap_ex(8, ((BITMAP*)d->dat)->w, ((BITMAP*)d->dat)->h);
                        if (!b) exit(37);
						blit(((BITMAP*)d->dat), b, 0, 0, 0, 0, b->w, b->h);
					}
					unload_datafile_object(d);

					lev->HeightMap = b;
				}
			}
			/* Not datafile, or loading from datafile failed */
			if (!lev->HeightMap) {
				set_color_conversion(COLORCONV_NONE);
				lev->HeightMap = load_bitmap(buf2, pal);
			}
			errno = 0;
			continue;
		}


		r = sscanf(buf, "TextureMap=%[^\n]", buf2);
		if (!r)
			r = sscanf(buf, "TextureMap = %[^\n]", buf2);

		if (r > 0) {
			PALETTE pal;
			char *g;

			ustrcpy(buf, buf2);

			/* Attempt to load it as a datafile object */
			if ((g = ustrstr(buf2, "#"))) {
				DATAFILE *d;
				BITMAP *b = NULL;
				char oldg;

				set_color_conversion(COLORCONV_NONE);

				oldg = ugetc(g);
				usetat(g, 0, 0);
				d = load_datafile_object(buf2, g + ucwidth(0));
				usetat(g, 0, oldg);

				if (!d) {
					LOG("Error loading the datafile. Trying direct bitmap reading\n");
					lev->TextureMap = load_bitmap(buf2, pal);
				}
				else {
					if (d->type == DAT_BITMAP) {
						b = create_bitmap_ex(8, ((BITMAP*)d->dat)->w, ((BITMAP*)d->dat)->h);
                        if (!b) exit(37);
						blit(((BITMAP*)d->dat), b, 0, 0, 0, 0, b->w, b->h);
					}
					unload_datafile_object(d);

					lev->TextureMap = b;
				}
			}
			/* Not datafile, or loading from datafile failed */
			if (!lev->TextureMap) {
				set_color_conversion(COLORCONV_NONE);				
				lev->TextureMap = load_bitmap(buf2, pal);
			}
			errno = 0;
			continue;
		}
	}

	pack_fclose(p);

	if (!lev_name) {
		lev_name = ustrdup("Untitled");
        if (!lev_name) exit(37);
    }

	add_level(lev, lev_name, pos);

	if (lev_name)
		free(lev_name);

	if (lev->HeightMap)
		level_initialise_tilemap(lev);

	/* Reset color 0 of the palette to black */

	errno = 0;
	return;
}


/* pack_level_data()
 *  Removes the holes in the the_levels[] array but shifting its contents. Note
 *  that the order of elements MUST be preserved. This will also set the n_levels
 *  number to reflect the number of loaded levels.
 */
void pack_level_data() {

	int cur_index, packed_index;

	for (cur_index = 0, packed_index = 0; cur_index < MAX_LEVELS; cur_index++) {

		if (the_levels[packed_index])
			packed_index++;
		else if (the_levels[cur_index]) {
			the_levels[packed_index] = the_levels[cur_index];
			the_level_names[packed_index] = the_level_names[cur_index];

			the_levels[cur_index] = NULL;
			the_level_names[cur_index] = NULL;
		}
	}

	n_levels = packed_index;

	return;
}



/* load_levels(): this uses for_each_file() with load_level() to load all
   levels from the disk and put them in 'the_levels'. It should be called
   once at the beginning of the program. If it returns 1, the program should
   promptly exit; this code means no levels could be loaded!

	pack_level_data() should be called right afterwards.

   If, after calling for_each_file() and pack_level_data(), n_levels is 0,
   then use allegro_message() to display the following:

   "No levels available to play!\n"

   Then return 1.

   In all other cases, return 0 - even if errors occurred loading some of the
   levels. These errors have already been handled inasmuch as error messages
   have been printed by load_level().

*/
int load_levels() {

	int r, i;
	int attrib;
	
	/* Clear junk data */
	for (i = 0; i < MAX_LEVELS; i++) {
		the_levels[i] = NULL;
		the_level_names[i] = NULL;
	}

	/* Backward compatibility hack for Allegro's for_each_file function */
#if (ALLEGRO_VERSION <= 3) && (ALLEGRO_SUB_VERSION <= 9) && (ALLEGRO_WIP_VERSION > 38)
	attrib = FA_ARCH | FA_RDONLY;
#else
	attrib = 0;
#endif

	TRACE("Searching for levels...\n");
	r = for_each_file("data/*.lvl", FA_ARCH | FA_RDONLY, load_level, 0);

	if (r <= 0) {
		destroy_screen();
		allegro_message("No levels could be loaded! (%s)\n", allegro_error);
		exit(1);
	}

	pack_level_data();

    add_maze_levels();

	if (n_levels == 0) {
		allegro_message("No levels available to play!\n");
		return 1;
	}

	return 0;
}



/* This duplicates a level's tilemap and generates extra data required, such
   as ripple tiles. It also resets the portals.
*/
TILEMAP *level_initiate_tilemap(LEVEL *level) {

 int x, y;
 TILEMAP *map = create_tilemap(level->map->w, level->map->h);
 SPRITE *spr;

 if (map == 0) return 0;

 map->ripple_info = create_ripple_info();
 if (!map->ripple_info) exit(37);

 for (y = 0; y < map->h; y++) {
  for (x = 0; x < map->w; x++) {

   TILE *tile = &map->tile[y][x];

   *tile = level->map->tile[y][x];

   if (ripple_quality && tile_use_ripples[tile->type])
    tile->ripples = create_ripples();
  }
 }

 spr = level->portalsprite;

 while (spr) {
	 ((PORTAL*)spr->data)->tick = random() % 250;
	 spr = spr->next;
 }

 return map;
}


/* This destroys a level's duplicate tilemap and generated data,
   as ripple tiles.
*/
void level_uninitiate_tilemap(TILEMAP *map) {

	int x, y;

	if (map) {
		destroy_ripple_info(map->ripple_info);

		for (y = 0; y < map->h; y++) {
			for (x = 0; x < map->w; x++) {

				TILE *tile = &map->tile[y][x];				

				if (tile->ripples)
					destroy_ripples(tile->ripples);
			}
		}
	}

	return;
}



/* This frees all memory occupied by the levels. It should be called once at
   the end of the program.
*/
void unload_levels() {

	int i;

	for (i = 0; i < MAX_LEVELS; i++) {
		if (the_levels[i])
			destroy_level(the_levels[i]);
		if (the_level_names[i])
			free(the_level_names[i]);

		the_levels[i] = NULL;
		the_level_names[i] = NULL;
	}

	return;
}

