// ------------------------------------------------------------------
// tmap.cpp
// ------------------------------------------------------------------
// This handles the tile map for the ball game
// ------------------------------------------------------------------
// By Kronoman - In loving memory of my father
// Copyright (c) 2003, Kronoman
// ------------------------------------------------------------------
// ** VERY IMPORTANT NOTICE **
//
// SINCE THE WALLS ARE 3D RENDERED, ALL DRAW ROUTINES *NEED*
// A 3D Allegro's create_scene(int nedge, int npoly); BEFORE CALLING THEM!
// ------------------------------------------------------------------
#include "tmap.h"

// NOTE: instead of printf, strcmp, etc, I use the Allegro's UNICODE equivalents

// ============================================================================
// ============================================================================
// CTileClass class
// ============================================================================
// ============================================================================

// ----------------------------------------------------------------------------
// Constructor
// ----------------------------------------------------------------------------
CTileClass::CTileClass()
{
	adx = ady = adz = 0.0;
	mdx = mdy = mdz = 1.0;
	bounce_factor = 0.9;
	exit_level = false;
	solid = false;
	spr = NULL;
	sound = NULL;

	// relative to prize layer
	score = 0;
	indispensable = false;
	is_a_prize = false;

	usprintf(spr_name,"NULL");
	usprintf(sound_name,"NULL");
}

// ----------------------------------------------------------------------------
// Destructor
// ----------------------------------------------------------------------------
CTileClass::~CTileClass()
{
	// nothing to be done here yet
}

// ----------------------------------------------------------------------------
// This saves parameters to a previously set config *FILE* (not memory pointer)
// nt is the tile number ID
// ----------------------------------------------------------------------------

void CTileClass::save_to_config(int nt)
{
	char str1[2048];

	usprintf(str1,"TILE_%d", nt); // section to read

	set_config_float(str1, "bounce_factor", bounce_factor);

	set_config_float(str1, "adx", adx);
	set_config_float(str1, "ady", ady);
	set_config_float(str1, "adz", adz);

	set_config_float(str1, "mdx", mdx);
	set_config_float(str1, "mdy", mdy);
	set_config_float(str1, "mdz", mdz);

	set_config_int(str1, "exit_level", (int)exit_level);
	set_config_int(str1, "solid", (int)solid);

	set_config_string(str1, "spr", spr_name);
	set_config_string(str1, "sound", sound_name);

	set_config_int(str1, "score", score);
	set_config_int(str1, "is_a_prize", is_a_prize);
	set_config_int(str1, "indispensable", (int)indispensable);

}

// ----------------------------------------------------------------------------
// This loads the tile 'nt' (1..255) from a *previously* set config
// is mean to be called ONLY by CTMap::load_tile_set_from_file()
// ----------------------------------------------------------------------------
void CTileClass::load_from_config(int nt, CWDatafile *data)
{
	char str1[2048];

	usprintf(str1,"TILE_%d", nt); // section to read

	// parameters
	bounce_factor = get_config_float(str1, "bounce_factor", 0.9);
	adx = get_config_float(str1, "adx", 0.0);
	ady = get_config_float(str1, "ady", 0.0);
	adz = get_config_float(str1, "adz", 0.0);

	mdx = get_config_float(str1, "mdx", 1.0);
	mdy = get_config_float(str1, "mdy", 1.0);
	mdz = get_config_float(str1, "mdz", 1.0);

	exit_level = (bool)get_config_int(str1, "exit_level", 0);
	solid = (bool)get_config_int(str1, "solid", 0);

	indispensable = (bool)get_config_int(str1,"indispensable",0);
	score = get_config_int(str1, "score", 0);
	is_a_prize = (bool)get_config_int(str1, "is_a_prize", 0);

	if (indispensable)
		is_a_prize = true; // if is indispensable, it must has the prize flag set

	// sprite
	spr = NULL; // default value for sprite

	usprintf(spr_name, "%s", get_config_string(str1, "spr", "null")); // I'm interested in keep the sprite name for future saving of tile set to disk

	// if spr_name != "null", then the sprite is defined, so fint it or die trying :P
	if (ustricmp(spr_name, "null") != 0)
	{
		spr = (BITMAP *)data->get_resource_dat(spr_name);
	}

	// sound
	sound = NULL;

	usprintf(sound_name, "%s", get_config_string(str1, "sound", "null")); // I'm interested in keep the sound name for future saving of tile set to disk

	if (ustricmp(sound_name, "null") != 0)
	{
		sound = (SAMPLE *)data->get_resource_dat(sound_name);
	}

	// done
}

// ----------------------------------------------------------------------------
// Draws the tile on bmp at x,y (pixel coordinates)
// draw_grid is of use for the map editor, will signal bad sprites
// layer is used to determine highness of the thing.
// * Note: prizes aren't scaled *
// ----------------------------------------------------------------------------

void CTileClass::draw(BITMAP *bmp, int x, int y, bool draw_grid, int layer)
{
	V3D_f *v_xyz_p[4]; // this is needed to pass the array pointer to software renderer
	V3D_f pc_v[8]; // the 8 vertex of the quad are precalculated and stored here

	int poltype = POLYTYPE_ATEX_MASK; // type of polygon to render, usually POLYTYPE_ATEX_MASK

	float dx, dy; // displacement to give '3D look'
	float scale_top = 5; // how much to add to the size of top block (pixels) (this is the height of the walls also)

	if (is_a_prize)
		scale_top = 0; // is a prize? don't scale


	if (spr == NULL)
	{
		if (draw_grid)
		{
			line(bmp,x,y,x+TMAPS_W, y+TMAPS_H, makecol(255,0,0));
			line(bmp,x+TMAPS_W, y, x, y+TMAPS_H, makecol(255,0,0));
		}
		return;
	}

	// maximun displacement of top of wall's box
#define max_displacement 32.0
	dx = ((x+spr->w/2) - (bmp->w/2)) * (max_displacement) / (float)(bmp->w/2.0);
	dy = ((y+spr->h/2) - (bmp->h/2)) * (max_displacement) / (float)(bmp->h/2.0);

#undef max_displacement


	// 3D rendered (by software render, no OpenGL)

	// precalculate the 8 vertex, 4 first are 'on ground'
	// REMEMBER to set 'u' and 'v' parameters for texture for each face!!!
	// 0-->1
	// 3<--2
	//
	// 4 last are 'on roof'
	// 4-->5
	// 7<--6
	pc_v[0].x=x;
	pc_v[0].y=y;

	pc_v[1].x=x+spr->w;
	pc_v[1].y=y;

	pc_v[2].x=x+spr->w;
	pc_v[2].y=y+spr->h;

	pc_v[3].x=x;
	pc_v[3].y=y+spr->h;

	pc_v[0].z=pc_v[1].z=pc_v[2].z=pc_v[3].z=1;

	pc_v[4].x=x + dx - scale_top;
	pc_v[4].y=y + dy - scale_top;

	pc_v[5].x=x + dx + spr->w + scale_top;
	pc_v[5].y=y + dy - scale_top;

	pc_v[6].x=x + spr->w + dx + scale_top;
	pc_v[6].y=y + spr->h + dy + scale_top;

	pc_v[7].x=x + dx - scale_top ;
	pc_v[7].y=y + dy + spr->h + scale_top;

	pc_v[4].z=pc_v[5].z=pc_v[6].z=pc_v[7].z=0.1;

	// is a floor, or wall?
	if (solid)
	{

		// wall box

		// top face (roof)
		v_xyz_p[0] = &pc_v[4];
		v_xyz_p[1] = &pc_v[5];
		v_xyz_p[2] = &pc_v[6];
		v_xyz_p[3] = &pc_v[7];

		pc_v[4].u = 0;
		pc_v[4].v = 0;
		pc_v[5].u = spr->w;
		pc_v[5].v = 0;
		pc_v[6].u = spr->w;
		pc_v[6].v = spr->h;
		pc_v[7].u = 0;
		pc_v[7].v = spr->h;

		scene_polygon3d_f(poltype, spr, 4, v_xyz_p); // top polygon

		// upper face
		v_xyz_p[0] = &pc_v[4];
		v_xyz_p[1] = &pc_v[5];
		v_xyz_p[2] = &pc_v[1];
		v_xyz_p[3] = &pc_v[0];

		pc_v[4].u = 0;
		pc_v[4].v = 0;
		pc_v[5].u = spr->w;
		pc_v[5].v = 0;
		pc_v[1].u = spr->w;
		pc_v[1].v = spr->h;
		pc_v[0].u = 0;
		pc_v[0].v = spr->h;

		scene_polygon3d_f(poltype, spr, 4, v_xyz_p);

		// bottom face
		v_xyz_p[0] = &pc_v[3];
		v_xyz_p[1] = &pc_v[2];
		v_xyz_p[2] = &pc_v[6];
		v_xyz_p[3] = &pc_v[7];

		pc_v[3].u = 0;
		pc_v[3].v = 0;
		pc_v[2].u = spr->w;
		pc_v[2].v = 0;
		pc_v[6].u = spr->w;
		pc_v[6].v = spr->h;
		pc_v[7].u = 0;
		pc_v[7].v = spr->h;

		scene_polygon3d_f(poltype, spr, 4, v_xyz_p);

		// left face
		v_xyz_p[0] = &pc_v[0];
		v_xyz_p[1] = &pc_v[4];
		v_xyz_p[2] = &pc_v[7];
		v_xyz_p[3] = &pc_v[3];

		pc_v[0].u = 0;
		pc_v[0].v = 0;
		pc_v[4].u = spr->w;
		pc_v[4].v = 0;
		pc_v[7].u = spr->w;
		pc_v[7].v = spr->h;
		pc_v[3].u = 0;
		pc_v[3].v = spr->h;

		scene_polygon3d_f(poltype, spr, 4, v_xyz_p);

		// right face
		v_xyz_p[0] = &pc_v[1];
		v_xyz_p[1] = &pc_v[5];
		v_xyz_p[2] = &pc_v[6];
		v_xyz_p[3] = &pc_v[2];

		pc_v[1].u = 0;
		pc_v[1].v = 0;
		pc_v[5].u = spr->w;
		pc_v[5].v = 0;
		pc_v[6].u = spr->w;
		pc_v[6].v = spr->h;
		pc_v[2].u = 0;
		pc_v[2].v = spr->h;

		scene_polygon3d_f(poltype, spr, 4, v_xyz_p);


	}
	else
	{

		// flat surface
		if (layer == 0 || is_a_prize)
		{
			v_xyz_p[0] = &pc_v[0];
			v_xyz_p[1] = &pc_v[1];
			v_xyz_p[2] = &pc_v[2];
			v_xyz_p[3] = &pc_v[3];

			pc_v[0].u = 0;
			pc_v[0].v = 0;
			pc_v[1].u = spr->w;
			pc_v[1].v = 0;
			pc_v[2].u = spr->w;
			pc_v[2].v = spr->h;
			pc_v[3].u = 0;
			pc_v[3].v = spr->h;

			pc_v[0].z=pc_v[1].z=pc_v[2].z=pc_v[3].z= (is_a_prize) ? 0.15 : 1;
		}
		else
		{
			v_xyz_p[0] = &pc_v[4];
			v_xyz_p[1] = &pc_v[5];
			v_xyz_p[2] = &pc_v[6];
			v_xyz_p[3] = &pc_v[7];

			pc_v[4].u = 0;
			pc_v[4].v = 0;
			pc_v[5].u = spr->w;
			pc_v[5].v = 0;
			pc_v[6].u = spr->w;
			pc_v[6].v = spr->h;
			pc_v[7].u = 0;
			pc_v[7].v = spr->h;

			pc_v[4].z=pc_v[5].z=pc_v[6].z=pc_v[7].z=0.1;
		}

		scene_polygon3d_f(poltype, spr, 4, v_xyz_p); // render polygon to scene
	}
}

// ============================================================================
// ============================================================================
// CTMap class
// ============================================================================
// ============================================================================

// ----------------------------------------------------------------------------
// Constructor
// ----------------------------------------------------------------------------

CTMap::CTMap()
{
	empty_the_map(); // reset the map

	timer_tick_rate = 30; // set this to measure timer tick rate, otherwise, the time will run wild (ex: if update logic is called 30 times by second, then set this to 30)
}

// ----------------------------------------------------------------------------
// Destructor - NUNCA DEBE SER LLAMADO AUTOMATICAMENTE POR C++
// PORQUE GENERA SEG FAULT, YA QUE ALLEGRO MUERE *ANTES* QUE ESTO
// Y LA LIBERACION DE MEMORIA *NO* ANDA
// ----------------------------------------------------------------------------

CTMap::~CTMap()
{
	free_memory();
}

// ----------------------------------------------------------------------------
// This free the memory used by the map and the tile set
// ----------------------------------------------------------------------------
void CTMap::free_memory()
{
	tile_set_data.nuke_datafile();
}

// ----------------------------------------------------------------------------
// Resets all map to 0s
// ----------------------------------------------------------------------------
void CTMap::empty_the_map()
{
	for (int l =0; l < MAP_LAYERS; l++)
		for (int x = 0; x < TMAP_SIZE; x++)
			for (int y = 0; y < TMAP_SIZE; y++)
				tile_map[x][y][l] = 0; // empty the map

	// reset start position too (centered)
	sxp = TMAP_SIZE / 2;
	syp = TMAP_SIZE / 2;

	// reset other stuff
	prize_map_indispensable = 0;
	time_m = 60;
	time_s = 0;
	background_index = 0;
	music_index = 0;

	curr_tick = -1; // special flag, means 'start recounting'
}

// ----------------------------------------------------------------------------
// This returns if a tile is walkable only
// Is useful to see if the ball can move or not in some particular direction
// Is not useful for check if the ball must fall (for that purpose,
// use get_tile_type and check against < 1 (empty))
//
// This serves for validation purposes (for the ball), will return true
// if the tile is walkable, false otherwise (outside map counts as non walkable)
// ----------------------------------------------------------------------------

bool CTMap::get_tile_walkable(int x, int y, int layer)
{
	if (x < 0 || y < 0 || x > TMAP_SIZE-1 || y > TMAP_SIZE-1 || layer < 0 || layer > MAP_LAYERS-1)
		return false; // outside map!

	if (tile_set[tile_map[x][y][layer]].solid)
		return false; // the place is solid

	// if the tile is empty, or solid = false; then he can walk
	return true;
}

// ----------------------------------------------------------------------------
// This returns the tile type (1..255), or -1 if outside map, or 0 if empty
// if empty, or outside map, ball should fall free to death
// ----------------------------------------------------------------------------
int CTMap::get_tile_type(int x, int y, int layer)
{
	if (x < 0 || y < 0 || x > TMAP_SIZE-1 || y > TMAP_SIZE-1 || layer < 0 || layer > MAP_LAYERS-1)
		return -1; // outside map!

	return tile_map[x][y][layer];
}

// ----------------------------------------------------------------------------
// this sets the tile type at x,y (0..255) x,y are coordinates of the matrix
// ----------------------------------------------------------------------------
void CTMap::set_tile_type(int x, int y, int layer, int value)
{
	if (x < 0 || y < 0 || x > TMAP_SIZE-1 || y > TMAP_SIZE-1 || layer < 0 || layer > MAP_LAYERS-1)
		return; // outside map!
	tile_map[x][y][layer] = value;
}

// ----------------------------------------------------------------------------
// Loads a tile map from a file, return true if FAILS, false otherwise
// NOTICE: FOR THIS TO COUNT PROPERLY THE PRIZE, THE TILE SET SHOULD BE *ALREADY* LOADED
// NOTICE: the header of the file is '6''6''6''M''A''P' (6 chars = "666MAP"
// ----------------------------------------------------------------------------
bool CTMap::load_map_from_file(const char *filename)
{
	prize_map_indispensable = 0; //reset prize count

	// All file I/O in maps is done using Allegro's packfiles, that way I use compression :D
	PACKFILE *fp = NULL;

	fp = pack_fopen(filename, F_READ);

	if (fp == NULL)
		return true; // error d00d! :(

	// check header
	if (pack_getc(fp) != '6')
		return true; // error
	if (pack_getc(fp) != '6')
		return true; // error
	if (pack_getc(fp) != '6')
		return true; // error
	if (pack_getc(fp) != 'M')
		return true; // error
	if (pack_getc(fp) != 'A')
		return true; // error
	if (pack_getc(fp) != 'P')
		return true; // error

	// start reading data

	// start pos of the player
	sxp = pack_getc(fp);
	syp = pack_getc(fp);

	// time of the map
	time_m = pack_getc(fp);
	time_s = pack_getc(fp);

	// now, get the rest of the map
	for (int l = 0; l < MAP_LAYERS; l ++)
	{
		for (int x = 0; x < TMAP_SIZE; x++)
		{
			for (int y = 0; y < TMAP_SIZE; y++)
			{
				// if we reach eof before ending the loop, a error occurs, I need all the map !
				if (pack_feof(fp))
				{
					pack_fclose(fp);
					return true; // error!
				}
				tile_map[x][y][l] = pack_getc(fp);

				// check if it is a indispensable prize, count it ;
				// DEBUG: this only counts the indispensable intems if the layer == 1,
				// otherwise they are on the floor and can't be pick up
				if (tile_set[tile_map[x][y][l]].indispensable && l == 1)
				{
					prize_map_indispensable++;
				}
			}
		}
	}

	// get background ID
	background_index = pack_getc(fp);
	
	// get music index ID
	music_index = pack_getc(fp);

	pack_fclose(fp); // ready

	return false; // all OK
}

// ----------------------------------------------------------------------------
// Save tile map to filename, return true if FAILS, false otherwise
// NOTICE: the header of the file is '6''6''6''M''A''P' (6 chars = "666MAP"
// ----------------------------------------------------------------------------

bool CTMap::save_map_to_file(const char *filename)
{
	PACKFILE *fp = NULL;

	fp = pack_fopen(filename, F_WRITE); // DEBUG - deberia ser PACKED, pero PIERDE 2 bytes (BUG de Allegro?)

	if (fp == NULL)
		return true; // error d00d! :(

	// Put file's header '666MAP'
	pack_putc('6', fp);
	pack_putc('6', fp);
	pack_putc('6', fp);
	pack_putc('M', fp);
	pack_putc('A', fp);
	pack_putc('P', fp);


	// put 2 bytes, start pos of the player
	pack_putc((char)sxp, fp);
	pack_putc((char)syp, fp);

	// time of the map
	pack_putc((char)time_m, fp);
	pack_putc((char)time_s, fp);

	// now, set the rest of the map
	for (int l = 0; l < MAP_LAYERS; l ++)
		for (int x = 0; x < TMAP_SIZE; x++)
			for (int y = 0; y < TMAP_SIZE; y++)
				pack_putc((char)tile_map[x][y][l], fp);

	// set background ID
	pack_putc((char)background_index, fp);
	
	// set music ID
	pack_putc((char)music_index, fp);

	// DEBUG:: pad with extra bytes, this was used to fix a _PACK bug, but is no more needed, anyways let it here
	for (int pad=0; pad<916; pad++)
		pack_putc(rand()%128+64, fp);

	pack_fclose(fp); // ready
	return false;
}

// ----------------------------------------------------------------------------
// loads a tile set from a DATAFILE (filename is the name of the datafile),
// return true if FAILS, false otherwise
// ----------------------------------------------------------------------------
bool CTMap::load_tile_set_from_file(char *filename)
{
	this->free_memory(); // free used memory first

	if (tile_set_data.load_datafile(filename))
		return true; // failed :P

	// now, cache all tile set sprites and all the stuff

	// first, I need the tile set configuration, text object named "TILE_SET_CFG_TXT"
	if (tile_set_data.get_resource_dat("TILE_SET_CFG_TXT") == NULL)
		return true; // error, can't load tile sets without the configuration thing :(

	push_config_state(); // to later restore config settings
	// set the config to the data
	set_config_data((char *)tile_set_data.get_resource_dat("TILE_SET_CFG_TXT"), tile_set_data.get_resource("TILE_SET_CFG_TXT")->size);

	// get all the tile set
	for (int i = 1; i < 256; i++)
		tile_set[i].load_from_config(i, &tile_set_data);

	pop_config_state(); // restore config settings

	return false; // done
}

// ----------------------------------------------------------------------------
// saves the tile set configuration to a text file (no saves the bitmaps!)
// return true if FAILS, false otherwise
// ----------------------------------------------------------------------------

bool CTMap::save_tile_set_config_to_file(char *filename)
{
	push_config_state();

	// set file
	set_config_file(filename);

	// save all data
	for (int i = 1; i < 256; i++)
		tile_set[i].save_to_config(i);

	pop_config_state();
	return false; // done OK
}


// ----------------------------------------------------------------------------
// Draws a zone of a layer of the map, coordinates are in pixels
//
// ix, iy = top left coordinate of map area to draw
// iw, ih = width, height of map area to draw
// draw_grid = draws the grid on top, only useful for the mapeditor
//
// NOTE: this has 3D software code embedded from Allegro, it *needs*
// a create_scene(), clear_scene() call somewhere before using this, and a
// ----------------------------------------------------------------------------

void CTMap::draw_map(BITMAP *bmp, int ix, int iy, int iw, int ih, int layer, bool draw_grid)
{
	// I want the integer remainder, so I can scroll
	// this returns the remainder of a/b
#define remainder(a,b)	(a-((a/b)*b))

	int ret,xof,yof;

	if (layer < 0 || layer > MAP_LAYERS-1)
		return;

	xof = remainder(ix, TMAPS_W);
	yof = remainder(iy, TMAPS_H);

	for (int y = iy; y < iy + ih + TMAPS_H; y += TMAPS_H)
	{
		for (int x = ix; x < ix + iw + TMAPS_W; x += TMAPS_W)
		{
			ret = get_tile_type( (x / TMAPS_W), (y / TMAPS_H), layer );
			if (ret > 0)
				tile_set[ret].draw(bmp, x-ix-xof, y-iy-yof, draw_grid,layer);

			if (x > TMAP_SIZE*TMAPS_W)
				break; // outside bounds, no need to do it

			// draw the 'start' of the player, and the grid (only useful for the map editor)
			if (draw_grid)
			{
				rect(bmp, x-ix-xof, y-iy-yof, x-ix-xof+TMAPS_W,y-iy-yof+TMAPS_H, makecol(128,128,128));
				if (x / TMAPS_W == sxp && y / TMAPS_H == syp)
				{
					circle(bmp, x-ix-xof+TMAPS_W/2, y-iy-yof+TMAPS_H/2, TMAPS_W/2, makecol(0,0,255));
					circle(bmp, x-ix-xof+TMAPS_W/2, y-iy-yof+TMAPS_H/2, TMAPS_W/3, makecol(255,0,0));
					circle(bmp, x-ix-xof+TMAPS_W/2, y-iy-yof+TMAPS_H/2, TMAPS_W/4, makecol(0,255,0));
				}
			}
		}
		if (y > TMAP_SIZE*TMAPS_H)
			break; // outside bounds, no need to do it
	}


}



// ----------------------------------------------------------------------------
// updates the logic of the map (animations, time remaining, etc)
// ----------------------------------------------------------------------------

void CTMap::update_logic()
{
	curr_tick--;
	if (curr_tick < 0)
	{
		curr_tick = timer_tick_rate;

		time_s--;
		if (time_s < 0)
		{
			time_m--;
			if (time_m < 0)
			{
				time_m = 0; // your time is OVER d00d! HA HA HA HA die bastard die!
				time_s = 0;
			}
			else
			{
				time_s = 59;
			}
		}

	}
}


