#include "empire.h"

#include "logic.h"  //needed for the special case where a unit is teleporting
extern bool ai_unit_animating;
extern bool translucent_units;

unsigned short MAP_TILE_SIZE = 32;
unsigned short XTILES        = (800 / MAP_TILE_SIZE) - 5;  //5 tiles needed for the sidebar
unsigned short YTILES        = 600 / MAP_TILE_SIZE;  //(XTILES and YTILES are reset once the resolution is set at startup)
unsigned short MAP_MAX_L     = 50;
unsigned short MAP_MAX_H     = 50;
unsigned short XMIN          = 10;
unsigned short YMIN          = 10;

extern void save_pathmap_info();
extern int moves_needed(int mtype, int tx, int ty, int plyr);
extern int any_unit_here(int tx, int ty);
extern bitmapdat *set_datafile(int set);

int HQglow = 0;
int HQglowdir = 4;

vector<animtiles> animated_tiles;

_tile::_tile() :
	unit (-1),
	set (0),
	number (3),  //set the tile to grass
	step (UNREACHABLE),
	temp (UNREACHABLE),
	animated (-1),
	owner (-1),
	oldset (-1),
	oldnum (-1),
	animframe (0),
	burnframe (0),
	burning (false),
	turnsused (0),
	burnedfor (-1),
	x (-1),
	y (-1)
{}

bool _tile::is_building() const
{
	if (set == 4)
	{  // set 4 is the building set
		return true;
	}
	return false;
}

bool _tile::is_building(int type) const
{
	if (set == 4)
	{
		if (type == number) return true;
	}
	return false;
}

int _tile::building_type() const
{
	if (set == 4)
	{
		return number;
	}
	return -1;
}

bool _tile::is_unit_producing() const
{
	if (set == 4)
	{  //only buildings could possibly build units
		if (number > CITY)
		{
			return true;
		}
	}
	return false;
}

bool _tile::is_HQ() const
{
	if (set == 4)
	{
		if (number == HQ)
		{
			return true;
		}
	}
	return false;
}

bool _tile::owned_by(int plyr) const
{
	if (set == 4)
	{  //only buildings can be owned
		return (owner == plyr);
	}
	return false;
}

bool _tile::same_type(int t) const
{
	if (set == t / 100)
	{
		if (number == t % 100)
		{
			return true;
		}
	}
	return false;
}

bool _tile::same_type(int tset, int tnum) const
{
	if (set == tset)
	{
		if (number == tnum)
		{
			return true;
		}
	}
	return false;
}

void _tile::reset()
{
	unit = -1;
	set = 0;
	animated = -1;
	number = 3;  //set the tile to grass
	oldset = -1;
	oldnum = -1;
	step = UNREACHABLE;
	temp = UNREACHABLE;
	animframe = 0;
	burnframe = 0;
	burning = false;
	burnedfor = 0;
	owner = -1;
	turnsused = 0;
}

void _tile::change_type(int new_set, int new_number)
{
	set = new_set;
	number = new_number;
}

void _tile::change_owner(int plyr)
{
	if (set == 4)
	{
		if ((plyr >= -1) && (plyr < 4)) owner = plyr;  //plyr can only be 0-3 inclusive or -1
		else owner = -1;
	}
}

void _tile::change_to_city()
{
	set = 4;
	number = CITY;
}

void _tile::draw(int x, int y)
{
	int i, s;
	ol::Bitmap *bmp = set_datafile(set)->bmp(number);

	if ((set != oldset) || (number != oldnum))
	{  //if this tile has changed type
		animframe = 0;
		oldset = set;
		oldnum = number;
		i = 0;
		s = animated_tiles.size();
		animated = -1;
		while (i < s)
		{
			if ((animated_tiles[i].set == set) && (animated_tiles[i].num == number))
			{
				animated = i;
				i = s;
			}
			i++;
		}
	}

	if (animated == -1)
	{
		if (set == 4)  //buildings set
		{
			buffer_draw_sprite(land.bmp(3), x, y - (land.bmp(2)->Height() - MAP_TILE_SIZE));
			if (owner == -1)
			{
				buffer_draw_sprite(bmp, x, y - (bmp->Height() - MAP_TILE_SIZE));
			}
			else
			{
				if (is_HQ()) buffer_draw_tinted_sprite(bmp, x, y, ol::Rgba(player[owner].tintcolor, 150).MixWith(ol::Rgba(WHITE, 255), float(HQglow) / 255.0));
				else buffer_draw_tinted_sprite(bmp, x, y, ol::Rgba(player[owner].tintcolor, 150));
			}
		}
		else
		{
			buffer_draw_sprite(bmp, x, y - (bmp->Height() - MAP_TILE_SIZE));
		}
	}
	else
	{
		animframe += animated_tiles[animated].frameinc;
		if (int(animframe) >= animated_tiles[animated].framenum)
		{
			animframe = 0;
		}
		buffer_blit_clipped(bmp, x, y - (MAP_TILE_SIZE - bmp->Height()), int(animframe) * MAP_TILE_SIZE, 0, MAP_TILE_SIZE, bmp->Height());
	}

	if (burning)
	{
		burnframe += 0.2;
		if (burnframe >= 3) burnframe = 0;
		buffer_draw_sprite(glb.bmp(29 + int(burnframe)), x, y, 0.7);
		if (rand()%2 == 0)
		{
			create_particle(x + rand()%12 - 6 + int(MAP_TILE_SIZE * (worldmap.camera.get_scroll_x() + 0.5)) - worldmap.camera.get_offset_x(),
			                y + rand()%12 - 6 + int(MAP_TILE_SIZE * (worldmap.camera.get_scroll_y() + 0.5)) - worldmap.camera.get_offset_y(), P_FIRE);
		}
	}
}

void _tile::draw_unit(int x, int y, _unit *other_unit_here)
{
	int i, s;

	if (other_unit_here != NULL)
	{
		other_unit_here->draw();
	}

	if (unit != -1)
	{
		i = unit / 100;
		s = unit % 100;
		if (player[i].unit[s].exists == 1)
		{
			if (!player[i].unit[s].is_moving())
			{
				player[i].unit[s].draw();
			}
		}
		else  //if the unit doesn't exist but still is in the tile's unit thing
		{  //it must be about to explode (as in, the map is scrolling to the unit so it can blow up)
			if ((game->state != TELEPORT_ANIMATION) && (!ai_unit_animating))
			{  //UNLESS a unit is currently teleporting, in which case the unit shouldn't appear if it doesn't exist
				player[i].unit[s].draw();
			}
		}
	}
}

void _tile::set_unit(int plyr, int number)
{
	if ((plyr == -1) || (number == -1))
	{
		unit = -1;
	}
	else
	{
		unit = (plyr * 100) + number;
	}
}

void _tile::set_temp(short t)
{
	temp = t;
}

void _tile::set_step(short s)
{
	step = s;
}

short _tile::get_temp() const
{
	return temp;
}

short _tile::get_step() const
{
	return step;
}

int _tile::unit_here() const
{
	return unit;
}

_unit *_tile::unit_here_ptr()
{
	if (unit < 0)
		return 0;

	return &(player[unit / 100].unit[unit % 100]);
}

int _tile::get_owner() const
{
	return owner;
}

int _tile::get_set() const
{
	return set;
}

int _tile::get_number() const
{
	return number;
}




//==========================================================
//==========================================================
//==========================================================


_camera::_camera() :
	scrollx (0),
	scrolly (0),
	offsetx (0),
	offsety (0)
{
}

void _camera::scroll(int offset_x, int offset_y)
{
	offsetx += offset_x;
	offsety += offset_y;

	while (offsetx >= MAP_TILE_SIZE)
	{
		scrollx--;
		offsetx -= MAP_TILE_SIZE;
	}
	while (offsetx < 0)
	{
		scrollx++;
		offsetx += MAP_TILE_SIZE;
	}
	while (offsety < 0)
	{
		scrolly++;
		offsety += MAP_TILE_SIZE;
	}
	while (offsety >= MAP_TILE_SIZE)
	{
		scrolly--;
		offsety -= MAP_TILE_SIZE;
	}
}

void _camera::jump_to(int tile_x, int tile_y)
{
	scrollx = tile_x;
	scrolly = tile_y;
	offsetx = 0;
	offsety = 0;
}

void _camera::jump_to_x(int tile_x)
{
	scrollx = tile_x;
	offsetx = 0;
}

void _camera::jump_to_y(int tile_y)
{
	scrolly = tile_y;
	offsety = 0;
}

int _camera::get_scroll_x() const
{
	return scrollx;
}

int _camera::get_scroll_y() const
{
	return scrolly;
}

int _camera::get_offset_x() const
{
	return offsetx;
}

int _camera::get_offset_y() const
{
	return offsety;
}




//==========================================================
//==========================================================
//==========================================================



_map::_map() :
	name ("newmap"),
	length (0),
	height (0),
	numplayers (0),
	tiles (0),
	camera (_camera())
{
}

_map::_map(int newlength, int newheight) :
	name ("newmap"),
	length (newlength),
	height (newheight),
	numplayers (0),
	tiles (0),
	camera (_camera())
{
	if (tiles != 0)
		delete[] tiles;

	tiles = new _tile[length * height];
	if (tiles == NULL)
	{
		char c[200];
		sprintf(c, "Error allocating memory for map (%d x %d)", length, height);
		empdebug(c);
		exit(1);
	}

	for (int y = 0; y < height; y++)
	{
		for (int x = 0; x < length; x++)
		{
			tiles[y * length + x].x = x;
			tiles[y * length + x].y = y;
			tiles[y * length + x].change_type(0, 3);
		}
	}
}

_map::_map(const string &filename) :
	name ("newmap"),
	length (0),
	height (0),
	numplayers (0),
	tiles (0),
	camera (_camera())
{
	load(filename);
}

_map &_map::operator=(const _map &m)
{
	close();

	name = m.name;
	length = m.length;
	height = m.height;
	numplayers = m.numplayers;
	camera = m.camera;

	tiles = new _tile[length * height];
	if (tiles == NULL)
	{
		char c[200];
		sprintf(c, "Error allocating memory for map (%d x %d)", length, height);
		empdebug(c);
		exit(1);
	}

	for (int i = 0; i < length * height; i++)
		tiles[i] = m.tiles[i];

	return *this;
}

_map::~_map()
{
	close();
}

void _map::close()
{
	if (tiles != 0)
	{
		delete[] tiles;
		tiles = 0;
	}

	camera = _camera();
	name = "";
	length = 0;
	height = 0;
	numplayers = 0;
}

string mapname_from_filename(const string &filename)
{
	size_t lastslash = filename.find_last_of("/");
	string base;

	if (lastslash != string::npos)
		base = filename.substr(lastslash + 1, filename.length());
	else
		base = filename;

	base = base.substr(0, base.length() - 4);  //remove the .map from the filename
	return base;
}

bool _map::load(const string &filename, bool reset_players)
{
	ifstream m(filename.c_str());

	if (m.bad())
	{
		char c[50 + filename.length()];
		sprintf(c, "Couldn't load map file: %s", filename.c_str());
		empdebug(c);
		return false;
	}

	close();

	if (reset_players)
	{
		player[0].reset();
		player[1].reset();
		player[2].reset();
		player[3].reset();
		player[0].number = 0;
		player[1].number = 1;
		player[2].number = 2;
		player[3].number = 0;
		player[0].tintcolor = player_color(0, 0);
		player[1].tintcolor = player_color(1, 0);
		player[2].tintcolor = player_color(2, 0);
		player[3].tintcolor = player_color(0, 2);
	}

	m >> length >> height >> numplayers;

	if (length < 0)
	{
		char c[100 + filename.length()];
		sprintf(c, "Error loading map %s, length is %d", filename.c_str(), length);
		empdebug(c);
		return false;
	}

	if (height < 0)
	{
		char c[100 + filename.length()];
		sprintf(c, "Error loading map %s, height is %d", filename.c_str(), height);
		empdebug(c);
		return false;
	}

	if ((numplayers < 2) || (numplayers > 4))
	{
		char c[100 + filename.length()];
		sprintf(c, "Error loading map %s, number of players is %d", filename.c_str(), numplayers);
		empdebug(c);
		return false;
	}

	if (tiles != 0)
		delete[] tiles;

	tiles = new _tile[length * height];
	if (tiles == 0)
	{
		char c[200];
		sprintf(c, "Error allocating memory for map (%d x %d)", length, height);
		empdebug(c);
		exit(1);
	}

	for (int y = 0; y < worldmap.get_height(); y++)
	{
		for (int x = 0; x < worldmap.get_length(); x++)
		{
			int temp, temp2;
			_tile *tile = worldmap.get_tile(x, y);

			m >> temp >> temp2;
			tile->x = x;
			tile->y = y;
			tile->change_type(temp / 100, temp % 100);
			tile->change_owner(temp2);
		}
	}

	for (int i = 0; i < 50 * numplayers; i++)  //loading any predeployed units
	{
		int a, b, c, d;

		m >> a;
		if (a != -1)  //if there is a saved unit for this i
		{
			m >> b >> c >> d;

			if (player[d].create_unit(c, a, b, d) < 0)
			{
				empdebug("Error loading predeployed units.  Probably managed to have more units than the limit allows for a player");
				return false;
			}
		}
		else  //if a reads -1, there are no more saved units
			break;
	}

	refresh_numplayers();
	set_name(mapname_from_filename(filename));

	m.close();
	return true;
}

void _map::resize(int newlength, int newheight)
{
	_tile *newtiles = new _tile[newlength * newheight];
	if (newtiles == 0)
	{
		empdebug("Error resizing map!");
		return;
	}

	for (int y = 0; y < newheight; y++)
	{
		for (int x = 0; x < newlength; x++)
		{
			if ((x < length) && (y < height))
				newtiles[y * newlength + x] = tiles[y * length + x];
			else
			{
				newtiles[y * newlength + x].x = x;
				newtiles[y * newlength + x].y = y;
				newtiles[y * newlength + x].change_type(0, 3);
			}
		}
	}

	refresh_numplayers();

	delete[] tiles;
	length = newlength;
	height = newheight;
	tiles = newtiles;
}

int _map::get_length() const
{
	return length;
}

int _map::get_height() const
{
	return height;
}

void _map::get_dimensions(unsigned int *l, unsigned int *h) const
{
	*l = length;
	*h = height;
}

int _map::get_num_players() const
{
	return numplayers;
}

string _map::get_name() const
{
	return name;
}

void _map::set_name(string new_name)
{
	name = new_name;
}

_tile *_map::get_mouseover_tile(int mx, int my)
{
	int tilex = int(floor(float(mx - camera.get_offset_x()) / MAP_TILE_SIZE) + signed(camera.get_scroll_x()));
	int tiley = int(floor(float(my - camera.get_offset_y()) / MAP_TILE_SIZE) + signed(camera.get_scroll_y()));

#if 0
	stringstream s;
	s << "(" << tilex << ", " << tiley << ")   offsetx = " << camera.get_offset_x() << ", offsety = " << camera.get_offset_y();
	buffer_textout_ex(normal, s.str(), 0, 0, WHITE);
#endif

	return get_tile(tilex, tiley);
}

_tile *_map::get_tile(int tx, int ty)
{
	if (!worldmap.tile_in_bounds(tx, ty))
		return 0;

	return &(tiles[ty * length + tx]);
}

void _map::draw(bool showunits)
{
	list<_tile *> tileunits;

	HQglow += HQglowdir;
	if (HQglow > 150)
	{
		HQglow = 150;
		HQglowdir = -HQglowdir;
	}
	else if (HQglow < 0)
	{
		HQglow = 0;
		HQglowdir = -HQglowdir;
	}

	buffer_rectfill(0, 0, MAP_TILE_SIZE * XTILES, MAP_TILE_SIZE * YTILES, BLACK);

	for (int y = -1; y < YTILES + 1; y++)
	{  //starts at -1 to account for scroll offset, goes 1 past to account for any non-tile-size gaps between the map and the UI
		for (int x = -1; x < XTILES + 1; x++)
		{
			int tilex = x + camera.get_scroll_x();
			int tiley = y + camera.get_scroll_y();
			_tile *tile = get_tile(tilex, tiley);

			if (tile != 0)
			{
				tile->draw(x * MAP_TILE_SIZE + camera.get_offset_x(), y * MAP_TILE_SIZE + camera.get_offset_y());

				if (tile->unit_here() >= 0)
					tileunits.push_back(tile);
			}
		}
	}

	if (showunits)
	{
		for (list<_tile*>::iterator it = tileunits.begin(); it != tileunits.end(); it++)
		{
			const int tx = (*it)->x * camera.get_scroll_x();
			const int ty = (*it)->y * camera.get_scroll_y();

			(*it)->draw_unit(tx * MAP_TILE_SIZE + camera.get_offset_x(), ty * MAP_TILE_SIZE + camera.get_offset_y(), NULL);
		}
	}
}

void _map::refresh_numplayers()
{
	numplayers = 0;

	for (int y = 0; y < height; y++)
	{
		for (int x = 0; x < length; x++)
		{
			const _tile *t = get_tile(x, y);
			if (t->is_HQ())
				numplayers++;
		}
	}

	if ((numplayers < 2) || (numplayers > 4))
	{
		stringstream s;
		s << "Map error!  " << name << " has " << numplayers << "HQs";
		empdebug(s.str());
	}
}

void _map::force_numplayers(int players)
{
	numplayers = players;

	if ((numplayers < 2) || (numplayers > 4))
	{
		stringstream s;
		s << "Forcing numplayers to " << numplayers << ", but I don't think this'll end well...";
		empdebug(s.str());
	}
}

void _map::create_pathmap(int plyr, int mtype, int startx, int starty)
{
	if (!tile_in_bounds(startx, starty))
		return;

	create_pathmap(plyr, mtype, startx, starty, false);
}

int pathmap_limit;
void _map::create_limited_pathmap(int plyr, int mtype, int startx, int starty, int limit)
{
	if (!tile_in_bounds(startx, starty))
		return;

	pathmap_limit = limit;
	create_pathmap(plyr, mtype, startx, starty, true);
}

void _map::create_pathmap(int plyr, int mtype, int startx, int starty, bool limited)
{
	for (int y = 0; y < height; y++)
		for (int x = 0; x < length; x++)
			get_tile(x, y)->set_step(UNREACHABLE);

	for (int i = 0; i < 4; i++)
	{
		int x = -1;
		int y = -1;

		switch(i)
		{
			case 0:
				x = startx + 1;
				y = starty;
				break;
			case 1:
				x = startx - 1;
				y = starty;
				break;
			case 2:
				x = startx;
				y = starty - 1;
				break;
			case 3:
				x = startx;
				y = starty + 1;
				break;
		}

		if ((x >= 0) && (x < signed(length)) && (y >= 0) && (y < signed(height)))
		{
			pathmap_recursive(plyr, mtype, x, y, 0, limited);
		}
	}

	get_tile(startx, starty)->set_step(0);
}

void _map::pathmap_recursive(int plyr, int mtype, int ox, int oy, int moves, bool limited)
{
	int m = moves_needed(mtype, ox, oy, plyr);
	int x, y, z;

	z = any_unit_here(ox, oy);
	if ((z != -1) && (z / 100 != plyr) && (player[z / 100].team != player[plyr].team))
	{
		m = UNREACHABLE;
	}

	moves += m;
	if ((moves >= get_tile(ox, oy)->get_step()) || (m == UNREACHABLE))
		return;

	if (limited && (moves > pathmap_limit))
		return;

	get_tile(ox, oy)->set_step(moves);
	z = 0;
	while (z < 4)
	{
		switch(z)
		{
		case 0:
			x = ox + 1;
			y = oy;
			break;
		case 1:
			x = ox - 1;
			y = oy;
			break;
		case 2:
			x = ox;
			y = oy - 1;
			break;
		case 3:
			x = ox;
			y = oy + 1;
			break;
		}
		if ((x >= 0) && (x < length) && (y >= 0) && (y < height))
		{
			pathmap_recursive(plyr, mtype, x, y, moves, limited);
		}
		z++;
	}
}

void _map::backup_pathmap()
{
	for (int y = 0; y < height; y++)
		for (int x = 0; x < length; x++)
			get_tile(x, y)->set_temp(get_tile(x, y)->get_step());
}

void _map::restore_pathmap()
{
	for (int y = 0; y < height; y++)
		for (int x = 0; x < length; x++)
			get_tile(x, y)->set_step(get_tile(x, y)->get_temp());
}

bool _map::tile_in_bounds(int x, int y) const
{
	if ((x < 0) || (y < 0) || (x >= length) || (y >= height))
		return false;
	return true;
}

void load_map(string mapname, const char *path, bool reset_players)
{
	worldmap.close();
	if (reset_players)
	{
		player[0].reset();  //get rid of any previous settings or units
		player[1].reset();
		player[2].reset();
		player[3].reset();
		player[0].number = 0;
		player[1].number = 1;
		player[2].number = 2;
		player[3].number = 0;
		player[0].tintcolor = player_color(0, 0);
		player[1].tintcolor = player_color(1, 0);
		player[2].tintcolor = player_color(2, 0);
		player[3].tintcolor = player_color(0, 2);
	}
	else
	{
		player[0].clear_units();  //just get rid of old units
		player[0].stats.reset();
		player[1].clear_units();
		player[1].stats.reset();
		player[2].clear_units();
		player[2].stats.reset();
		player[3].clear_units();
		player[3].stats.reset();
	}

	worldmap.load(path, reset_players);
	worldmap.set_name(mapname);

	for (int i = 0; i < 4; i++)
	{
		bool in_game = (i < worldmap.get_num_players());
		player[i].playing = in_game;
		player[i].everplaying = in_game;
	}

	worldmap.camera.jump_to((worldmap.get_length() / 2) - (XTILES / 2), (worldmap.get_height() / 2) - (YTILES / 2));  //center the camera
}
