#include "stdafx.h"
#include "ttd.h"
#include "table/strings.h"
#include "table/tree_land.h"
#include "map.h"
#include "tile.h"
#include "viewport.h"
#include "command.h"
#include "town.h"
#include "sound.h"

static int GetRandomTreeType(TileIndex tile, uint seed)
{
	switch (_opt.landscape) {
		case LT_NORMAL:
			return seed * 12 >> 8;

		case LT_HILLY:
			return (seed >> 5) + 12;

		case LT_DESERT:
			switch (GetMapExtraBits(tile)) {
				case 0:
					return (seed >> 6) + 28;

				case 1:
					return (seed > 12) ? -1 : 27;

				default:
					return (seed * 7 >> 8) + 20;
			}

		default:
			return (seed * 9 >> 8) + 32;
	}
}

static void PlaceTree(uint tile, uint32 r, byte m5_or)
{
	int tree = GetRandomTreeType(tile, (r >> 24));
	byte m5;

	if (tree >= 0) {
		m5 = (byte)(r >> 16);
		if ((m5 & 0x7) == 7) m5--; // there is no growth state 7

		_map5[tile] = m5 & 0x07;	// growth state;
		_map5[tile] |=  m5 & 0xC0;	// amount of trees

		_map3_lo[tile] = tree;		// set type of tree
		_map3_hi[tile] = 0;		// no hedge

		// above snowline?
		if (_opt.landscape == LT_HILLY && GetTileZ(tile) > _opt.snow_line) {
			_map2[tile] = 0xE0;	// set land type to snow
			_map2[tile] |= (byte)(r >> 24)&0x07; // randomize counter
		}
		else
		{
			_map2[tile] = (byte)(r >> 24)&0x1F; // randomize counter and ground
		}


		// make it tree class
		SetTileType(tile, MP_TREES);
	}
}

static void DoPlaceMoreTrees(uint tile)
{
	int i = 1000;
	int x,y;
	uint cur_tile;
	int dist;

	do {
		uint32 r = Random();
		x = (r & 0x1F) - 16;
		y = ((r>>8) & 0x1F) - 16;

		dist = myabs(x) + myabs(y);

		cur_tile = TILE_MASK(tile + TILE_XY(x,y));

		/* Only on tiles within 13 squares from tile,
		    on clear tiles, and NOT on farm-tiles or rocks */
		if (dist <= 13 && IsTileType(cur_tile, MP_CLEAR) &&
			 (_map5[cur_tile] & 0x1F) != 0x0F && (_map5[cur_tile] & 0x1C) != 8) {
			PlaceTree(cur_tile, r, dist <= 6 ? 0xC0 : 0);
		}
	} while (--i);
}

static void PlaceMoreTrees(void)
{
	int i = ScaleByMapSize((Random() & 0x1F) + 25);
	do {
		DoPlaceMoreTrees(TILE_MASK(Random()));
	} while (--i);
}

void PlaceTreesRandomly(void)
{
	int i;
	uint32 r;
	uint tile;

	i = ScaleByMapSize(1000);
	do {
		r = Random();
		tile = TILE_MASK(r);
		/* Only on clear tiles, and NOT on farm-tiles or rocks */
		if (IsTileType(tile, MP_CLEAR) && (_map5[tile] & 0x1F) != 0x0F && (_map5[tile] & 0x1C) != 8) {
			PlaceTree(tile, r, 0);
		}
	} while (--i);

	/* place extra trees at rainforest area */
	if (_opt.landscape == LT_DESERT) {
		i = ScaleByMapSize(15000);

		do {
			r = Random();
			tile = TILE_MASK(r);
			if (IsTileType(tile, MP_CLEAR) && GetMapExtraBits(tile) == 2) {
				PlaceTree(tile, r, 0);
			}
		} while (--i);
	}
}

void GenerateTrees(void)
{
	int i;

	if (_opt.landscape != LT_CANDY) {
		PlaceMoreTrees();
	}

	i = _opt.landscape == LT_HILLY ? 15 : 6;
	do {
		PlaceTreesRandomly();
	} while (--i);
}

/** Plant a tree.
 * @param x,y start tile of area-drag of tree plantation
 * @param p1 tree type, -1 means random.
 * @param p2 end tile of area-drag
 */
int32 CmdPlantTree(int ex, int ey, uint32 flags, uint32 p1, uint32 p2)
{
	int32 cost;
	int sx;
	int sy;
	int x;
	int y;

	if (p2 > MapSize()) return CMD_ERROR;
	/* Check the tree type. It can be random or some valid value within the current climate */
	if (p1 != (uint)-1 && p1 - _tree_base_by_landscape[_opt.landscape] >= _tree_count_by_landscape[_opt.landscape]) return CMD_ERROR;

	SET_EXPENSES_TYPE(EXPENSES_OTHER);

	// make sure sx,sy are smaller than ex,ey
	sx = TileX(p2) * 16;
	sy = TileY(p2) * 16;
	if (ex < sx) intswap(ex, sx);
	if (ey < sy) intswap(ey, sy);

	cost = 0; // total cost

	for (x = sx; x <= ex; x += 16) {
		for (y = sy; y <= ey; y += 16) {
			TileInfo ti;

			FindLandscapeHeight(&ti, x, y);
			if (!EnsureNoVehicle(ti.tile))
				continue;

			switch (ti.type) {
				case MP_TREES:
					// no more space for trees?
					if (_game_mode != GM_EDITOR && (ti.map5 & 0xC0) == 0xC0) {
						_error_message = STR_2803_TREE_ALREADY_HERE;
						continue;
					}

					if (flags & DC_EXEC) {
						_map5[ti.tile] = ti.map5 + 0x40;
						MarkTileDirtyByTile(ti.tile);
					}
					// 2x as expensive to add more trees to an existing tile
					cost += _price.build_trees * 2;
					break;

				case MP_CLEAR:
					if (_map_owner[ti.tile] != OWNER_NONE) {
						_error_message = STR_2804_SITE_UNSUITABLE;
						continue;
					}

					// it's expensive to clear farmland
					if ((ti.map5 & 0x1F) == 0xF)
						cost += _price.clear_3;
					else if ((ti.map5 & 0x1C) == 8)
						cost += _price.clear_2;

					if (flags & DC_EXEC) {
						int treetype;
						int m2;

						if (_game_mode != GM_EDITOR && _current_player < MAX_PLAYERS) {
							Town *t = ClosestTownFromTile(ti.tile, _patches.dist_local_authority);
							if (t != NULL)
								ChangeTownRating(t, RATING_TREE_UP_STEP, RATING_TREE_MAXIMUM);
						}

						switch (ti.map5 & 0x1C) {
							case 0x04:
								m2 = 16;
								break;

							case 0x10:
								m2 = ((ti.map5 & 3) << 6) | 0x20;
								break;

							default:
								m2 = 0;
								break;
						}

						treetype = p1;
						if (treetype == -1) {
							treetype = GetRandomTreeType(ti.tile, Random() >> 24);
							if (treetype == -1) treetype = 27;
						}

						ModifyTile(ti.tile,
							MP_SETTYPE(MP_TREES) |
							MP_MAP2 | MP_MAP3LO | MP_MAP3HI_CLEAR | MP_MAP5,
							m2, /* map2 */
							treetype, /* map3lo */
							_game_mode == GM_EDITOR ? 3 : 0 /* map5 */
						);

						if (_game_mode == GM_EDITOR && IS_BYTE_INSIDE(treetype, 0x14, 0x1B))
							SetMapExtraBits(ti.tile, 2);
					}
					cost += _price.build_trees;
					break;

				default:
					_error_message = STR_2804_SITE_UNSUITABLE;
					break;
			}
		}
	}

	return (cost == 0) ? CMD_ERROR : cost;
}

typedef struct TreeListEnt {
	uint32 image;
	byte x,y;
} TreeListEnt;

static void DrawTile_Trees(TileInfo *ti)
{
	uint16 m2;
	const uint32 *s;
	const byte *d;
	byte z;
	TreeListEnt te[4];

	m2 = _map2[ti->tile];

	if ( (m2&0x30) == 0) {
		DrawClearLandTile(ti, 3);
	} else if ((m2&0x30) == 0x20) {
		DrawGroundSprite(_tree_sprites_1[m2 >> 6] + _tileh_to_sprite[ti->tileh]);
	} else {
		DrawHillyLandTile(ti);
	}

	DrawClearLandFence(ti, _map3_hi[ti->tile] >> 2);

	z = ti->z;
	if (ti->tileh != 0) {
		z += 4;
		if (ti->tileh & 0x10)
			z += 4;
	}

	{
		uint16 tmp = ti->x;
		uint index;

		tmp = (tmp >> 2) | (tmp << 14);
		tmp -= ti->y;
		tmp = (tmp >> 3) | (tmp << 13);
		tmp -= ti->x;
		tmp = (tmp >> 1) | (tmp << 15);
		tmp += ti->y;

		d = _tree_layout_xy[(tmp & 0x30) >> 4];

		index = ((tmp>>6)&3) + (_map3_lo[ti->tile]<<2);

		/* different tree styles above one of the grounds */
		if ((m2 & 0xB0) == 0xA0 && index >= 48 && index < 80)
			index += 164 - 48;

		assert(index < lengthof(_tree_layout_sprite));
		s = _tree_layout_sprite[index];
	}

	StartSpriteCombine();

	if (!(_display_opt & DO_TRANS_BUILDINGS) || !_patches.invisible_trees)
	{
		int i;

		/* put the trees to draw in a list */
		i = (ti->map5 >> 6) + 1;
		do {
			uint32 image = s[0] + (--i==0 ? (ti->map5 & 7) : 3);
			if (_display_opt & DO_TRANS_BUILDINGS)
				image = (image & 0x3FFF) | 0x3224000;
			te[i].image = image;
			te[i].x = d[0];
			te[i].y = d[1];
			s++;
			d+=2;
		} while (i);

		/* draw them in a sorted way */
		for(;;) {
			byte min = 0xFF;
			TreeListEnt *tep = NULL;

			i = (ti->map5 >> 6) + 1;
			do {
				if (te[--i].image!=0 && (byte)(te[i].x + te[i].y) < min) {
					min = te[i].x + te[i].y;
					tep = &te[i];
				}
			} while (i);

			if (tep == NULL)
				break;

			AddSortableSpriteToDraw(tep->image, ti->x + tep->x, ti->y + tep->y, 5, 5, 0x10, z);
			tep->image = 0;
		}
	}

	EndSpriteCombine();
}


static uint GetSlopeZ_Trees(TileInfo *ti) {
	return GetPartialZ(ti->x&0xF, ti->y&0xF, ti->tileh) + ti->z;
}

static uint GetSlopeTileh_Trees(TileInfo *ti) {
	return ti->tileh;
}

static int32 ClearTile_Trees(uint tile, byte flags) {
	int num;

	if (flags & DC_EXEC && _current_player < MAX_PLAYERS) {
		Town *t = ClosestTownFromTile(tile, _patches.dist_local_authority);
		if (t != NULL)
			ChangeTownRating(t, RATING_TREE_DOWN_STEP, RATING_TREE_MINIMUM);
	}

	num = (_map5[tile] >> 6) + 1;
	if ( (byte)(_map3_lo[tile]-0x14) <= (0x1A-0x14))
		num <<= 2;

	if (flags & DC_EXEC)
		DoClearSquare(tile);

	return num * _price.remove_trees;
}

static void GetAcceptedCargo_Trees(uint tile, AcceptedCargo ac)
{
	/* not used */
}

static void GetTileDesc_Trees(uint tile, TileDesc *td)
{
	byte b;
	StringID str;

	td->owner = _map_owner[tile];

	b = _map3_lo[tile];
	(str=STR_2810_CACTUS_PLANTS, b==0x1B) ||
	(str=STR_280F_RAINFOREST, IS_BYTE_INSIDE(b, 0x14, 0x1A+1)) ||
	(str=STR_280E_TREES, true);
	td->str = str;
}

static void AnimateTile_Trees(uint tile)
{
	/* not used */
}

static SoundFx _desert_sounds[] = {
	SND_42_LOON_BIRD,
	SND_43_LION,
	SND_44_MONKEYS,
	SND_48_DISTANT_BIRD
};

static void TileLoopTreesDesert(uint tile)
{
	byte b;

	if ((b=GetMapExtraBits(tile)) == 2) {
		uint32 r;

		if (CHANCE16I(1,200,r=Random())) {
			SndPlayTileFx(_desert_sounds[(r >> 16) & 3], tile);
		}
	} else if (b == 1) {
		if ((_map2[tile] & 0x30) != 0x20) {
			_map2[tile] &= 0xF;
			_map2[tile] |= 0xE0;
			MarkTileDirtyByTile(tile);
		}
	}
}

static void TileLoopTreesAlps(uint tile)
{
	byte tmp, m2;
	int k;

	/* distance from snow line, in steps of 8 */
	k = GetTileZ(tile) - _opt.snow_line;

	tmp = _map5[tile] & 0xF0;

	if (k < -8) {
		/* snow_m2_down */
		if ((tmp&0x30) != 0x20)
			return;
		m2 = 0;
	} else if (k == -8) {
		/* snow_m1 */
		m2 = 0x20;
		if (tmp == m2)
			return;
	} else if (k < 8) {
		/* snow_0 */
		m2 = 0x60;
		if (tmp == m2)
			return;
	} else if (k == 8) {
		/* snow_p1 */
		m2 = 0xA0;
		if (tmp == m2)
			return;
	} else {
		/* snow_p2_up */
		if (tmp == 0xC0) {
			uint32 r;
			if (CHANCE16I(1,200,r=Random())) {
				SndPlayTileFx((r & 0x80000000) ? SND_39_HEAVY_WIND : SND_34_WIND, tile);
			}
			return;
		} else {
			m2 = 0xE0;
		}
	}

	_map2[tile] &= 0xF;
	_map2[tile] |= m2;
	MarkTileDirtyByTile(tile);
}

static void TileLoop_Trees(uint tile)
{
	byte m5;
	uint16 m2;

	static const TileIndexDiffC _tileloop_trees_dir[] = {
		{-1, -1},
		{ 0, -1},
		{ 1, -1},
		{-1,  0},
		{ 1,  0},
		{-1,  1},
		{ 0,  1},
		{ 1,  1}
	};

	if (_opt.landscape == LT_DESERT) {
		TileLoopTreesDesert(tile);
	} else if (_opt.landscape == LT_HILLY) {
		TileLoopTreesAlps(tile);
	}

	TileLoopClearHelper(tile);

	/* increase counter */
	{
		uint16 m2 = _map2[tile];
		_map2[tile] = m2 = (m2 & 0xF0) | ((m2+1)&0xF);
		if (m2 & 0xF)
			return;
	}

	m5 = _map5[tile];
	if ((m5&7) == 3) {
		/* regular sized tree */
		if (_opt.landscape == LT_DESERT && _map3_lo[tile]!=0x1B && GetMapExtraBits(tile)==1) {
			m5++; /* start destructing */
		} else {
			switch(Random() & 0x7) {
			case 0: /* start destructing */
				m5++;
				break;

			case 1: /* add a tree */
				if (m5 < 0xC0) {
					m5 = (m5 + 0x40) & ~7;
					break;
				}
				/* fall through */

			case 2: { /* add a neighbouring tree */
				byte m3 = _map3_lo[tile];

				tile += ToTileIndexDiff(_tileloop_trees_dir[Random() & 7]);

				if (!IsTileType(tile, MP_CLEAR))
					return;

				if ( (_map5[tile] & 0x1C) == 4) {
					_map2[tile] = 0x10;
				} else if ((_map5[tile] & 0x1C) == 16) {
					_map2[tile] = ((_map5[tile] & 3) << 6) | 0x20;
				} else {
					if ((_map5[tile] & 0x1F) != 3)
						return;
					_map2[tile] = 0;
				}

				_map3_lo[tile] = m3;
				_map3_hi[tile] = 0;
				SetTileType(tile, MP_TREES);

				m5 = 0;
				break;
			}

			default:
				return;
			}
		}
	} else if ((m5&7) == 6) {
		/* final stage of tree destruction */
		if (m5 & 0xC0) {
			/* more than one tree, delete it? */
			m5 = ((m5 - 6) - 0x40) + 3;
		} else {
			/* just one tree, change type into MP_CLEAR */
			SetTileType(tile, MP_CLEAR);

			m5 = 3;
			m2 = _map2[tile];
			if ((m2&0x30) != 0) { // on snow/desert or rough land
				m5 = (m2 >> 6) | 0x10;
				if ((m2&0x30) != 0x20) // if not on snow/desert, then on rough land
					m5 = 7;
			}
			_map_owner[tile] = OWNER_NONE;
		}
	} else {
		/* in the middle of a transition, change to next */
		m5++;
	}

	_map5[tile] = m5;
	MarkTileDirtyByTile(tile);
}

void OnTick_Trees(void)
{
	uint32 r;
	uint tile;
	byte m;
	int tree;

	/* place a tree at a random rainforest spot */
	if (_opt.landscape == LT_DESERT &&
			(r=Random(),tile=TILE_MASK(r),GetMapExtraBits(tile)==2) &&
			IsTileType(tile, MP_CLEAR) &&
			(m=_map5[tile]&0x1C, m<=4) &&
			(tree=GetRandomTreeType(tile, r>>24)) >= 0) {

		ModifyTile(tile,
			MP_SETTYPE(MP_TREES) |
			MP_MAP2 | MP_MAP3LO | MP_MAP3HI | MP_MAP5,
			(m == 4 ? 0x10 : 0),
			tree,
			_map3_hi[tile] & ~3,
			0
		);
	}

	// byte underflow
	if (--_trees_tick_ctr)
		return;

	/* place a tree at a random spot */
	r = Random();
	tile = TILE_MASK(r);
	if (IsTileType(tile, MP_CLEAR) &&
			(m=_map5[tile]&0x1C, m==0 || m==4 || m==0x10) &&
			(tree=GetRandomTreeType(tile, r>>24)) >= 0) {
		int m2;

		if (m == 0) {
			m2 = 0;
		} else if (m == 4) {
			m2 = 0x10;
		} else {
			m2 = ((_map5[tile] & 3) << 6) | 0x20;
		}

		ModifyTile(tile,
			MP_SETTYPE(MP_TREES) |
			MP_MAP2 | MP_MAP3LO | MP_MAP3HI | MP_MAP5,
			m2,
			tree,
			_map3_hi[tile] & ~3,
			0
		);
	}
}

static void ClickTile_Trees(uint tile)
{
	/* not used */
}

static uint32 GetTileTrackStatus_Trees(uint tile, TransportType mode)
{
	return 0;
}

static void ChangeTileOwner_Trees(uint tile, byte old_player, byte new_player)
{
	/* not used */
}

void InitializeTrees(void)
{
	_trees_tick_ctr = 0;
}


const TileTypeProcs _tile_type_trees_procs = {
	DrawTile_Trees,						/* draw_tile_proc */
	GetSlopeZ_Trees,					/* get_slope_z_proc */
	ClearTile_Trees,					/* clear_tile_proc */
	GetAcceptedCargo_Trees,		/* get_accepted_cargo_proc */
	GetTileDesc_Trees,				/* get_tile_desc_proc */
	GetTileTrackStatus_Trees,	/* get_tile_track_status_proc */
	ClickTile_Trees,					/* click_tile_proc */
	AnimateTile_Trees,				/* animate_tile_proc */
	TileLoop_Trees,						/* tile_loop_clear */
	ChangeTileOwner_Trees,		/* change_tile_owner_clear */
	NULL,											/* get_produced_cargo_proc */
	NULL,											/* vehicle_enter_tile_proc */
	NULL,											/* vehicle_leave_tile_proc */
	GetSlopeTileh_Trees,			/* get_slope_tileh_proc */
};