/* $Id$ */

/*
 * This file is part of OpenTTD.
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 */

/** @file tile_map.h Map writing/reading functions for tiles. */

#ifndef TILE_MAP_H
#define TILE_MAP_H

#include "slope_type.h"
#include "map_func.h"
#include "core/bitmath_func.hpp"
#include "core/math_func.hpp"
#include "settings_type.h"

/**
 * Returns wether more than 16 height levels are allowed.
 * Basically, this returns the setting of the configuration variable
 * allow_more_heightlevels.
 * To avoid having to change multiple occurrences of the config variable
 * in case some additional condition would get necessary,
 * I introduced this function.
 *
 * @return wether more than 16 height levels are allowed, based on allow_more_heightlevels and game mode.
 */
static inline bool AllowMoreHeightlevels()
{
	return _settings_game.construction.allow_more_heightlevels;
}

/**
 * Returns the maximum heightlevel of a tile based on AllowMoreHeightlevels().
 * This deduplicates a few if else statements in the code therefore it is usefull to have it here.
 *
 *@return MAX_TILE_HEIGHT_EXTENDED or MAX_TILE_HEIGHT_OLD based on AllowMoreHeightlevels().
 */
static inline uint GetMaxTileHeight()
{
	if (AllowMoreHeightlevels()) {
		return MAX_TILE_HEIGHT_EXTENDED;
	} else {
		return MAX_TILE_HEIGHT_OLD;
	}
}

/**
 * Returns the maximum snowline height based on AllowMoreHeightlevels().
 * This deduplicates a few if else statements in the code therefore it is usefull to have it here.
 *
 * @return MAX_SNOWLINE_HEIGHT_EXTENDED or MAX_SNOWLINE_HEIGHT_OLD based on AllowMoreHeightlevels(). 
 */
static inline uint GetMaxSnowlineHeight()
{
	if (AllowMoreHeightlevels()) {
		return MAX_SNOWLINE_HEIGHT_EXTENDED;
	} else {
		return MAX_SNOWLINE_HEIGHT_OLD;
	}
}

/**
 * This function returns the maximum treeline height based on AllowMoreHeightlevels().
 * This deduplicates a few if else statements in the code therefore it is usefull to have it here.
 *
 * @return MAX_TREELINE_HEIGHT_EXTENDED or MAX_TREELINE_HEIGHT_OLD based on AllowMoreHeightlevels().
 */
static inline uint GetMaxTreelineHeight()
{
	if (AllowMoreHeightlevels()) {
		return MAX_TREELINE_HEIGHT_EXTENDED;
	} else {
		return MAX_TREELINE_HEIGHT_OLD;
	}
}

/**
 * This function returns the height of the northern corner of a tile based on AllowMoreHeightlevels().
 * This is saved in the global map-array. It does not take affect by
 * any slope-data of the tile.
 *
 * @param tile The tile to get the height from.
 * @return the height of the tile.
 * @pre tile < MapSize()
 */
static inline uint TileHeight(TileIndex tile)
{
	assert(tile < MapSize());

	if (AllowMoreHeightlevels()) {
		return _map_heightdata[tile].heightlevel;
	} else {
		return GB(_m[tile].type_height, 0, 4);
	}
}

/**
 * For a detailed description why we need this see discussion in
 * GetTileMaxZOutsideMap in map_func.h.
 */
static inline uint TileHeightOutsideMap(int x, int y)
{
	/* In all cases: Descend to heightlevel 0 as fast as possible.
	 * So: If we are at the 0-side of the map (x<0 or y<0), we must
	 * subtract the distance to coordinate 0 from the heightlevel.
	 * In other words: Subtract e.g. -x. If we are at the MapMax
	 * side of the map, we also need to subtract the distance to
	 * the edge of map, e.g. MapMaxX - x.
	 *
	 * NOTE: Assuming constant heightlevel outside map would be
	 * simpler here. However, then we run into painting problems,
	 * since whenever a heightlevel change at the map border occurs,
	 * we would need to repaint anything outside map.
	 * In contrast, by doing it this way, we can localize this change,
	 * which means we may assume constant heightlevel for all tiles
	 * at more than <heightlevel at map border> distance from the
	 * map border. */
	if (x < 0) {
		if (y < 0) {
			return max((int)TileHeight(TileXY(0, 0)) - (-x) - (-y), 0);
		} else if (y < (int)MapMaxY()) {
			return max((int)TileHeight(TileXY(0, y)) - (-x), 0);
		} else {
			return max((int)TileHeight(TileXY(0, (int)MapMaxY())) - (-x) - (y - (int)MapMaxY()), 0);
		}
	} else if (x < (int)MapMaxX()) {
		if (y < 0) {
			return max((int)TileHeight(TileXY(x, 0)) - (-y), 0);
		} else if (y < (int)MapMaxY()) {
			return TileHeight(TileXY(x, y));
		} else {
			return max((int)TileHeight(TileXY(x, (int)MapMaxY())) - (y - (int)MapMaxY()), 0);
		}
	} else {
		if (y < 0) {
			return max((int)TileHeight(TileXY((int)MapMaxX(), 0)) - (x - (int)MapMaxX()) - (-y), 0);
		} else if (y < (int)MapMaxY()) {
			return max((int)TileHeight(TileXY((int)MapMaxX(), y)) - (x - (int)MapMaxX()), 0);
		} else {
			return max((int)TileHeight(TileXY((int)MapMaxX(), (int)MapMaxY())) - (x - (int)MapMaxX()) - (y - (int)MapMaxY()), 0);
		}
	}
}

/**
 * This function sets the height of the northern corner of a tile based on AllowMoreHeightlevels().
 *
 * @param tile The tile to change the height
 * @param height The new height value of the tile
 * @pre tile < MapSize()
 * @pre heigth <= GetMaxTileHeight()
 */
static inline void SetTileHeight(TileIndex tile, uint height)
{
	assert(tile < MapSize());

	if (height > GetMaxTileHeight()) {
		/* Make sure height is within allowed range. */
		height = GetMaxTileHeight();
	}

	if (AllowMoreHeightlevels()) {
		_map_heightdata[tile].heightlevel = height;
	} else {
		SB(_m[tile].type_height, 0, 4, height);
	}
}

/**
 * Returns the height of a tile in pixels.
 *
 * This function returns the height of the northern corner of a tile in pixels.
 *
 * @param tile The tile to get the height
 * @return The height of the tile in pixel
 */
static inline uint TilePixelHeight(TileIndex tile)
{
	return TileHeight(tile) * TILE_HEIGHT;
}

/**
 * Get the tiletype of a given tile.
 *
 * @param tile The tile to get the TileType
 * @return The tiletype of the tile
 * @pre tile < MapSize()
 */
static inline TileType GetTileType(TileIndex tile)
{
	assert(tile < MapSize());
	return (TileType)GB(_m[tile].type_height, 4, 4);
}

/**
 * Set the type of a tile
 *
 * This functions sets the type of a tile. If the type
 * MP_VOID is selected the tile must be at the south-west or
 * south-east edges of the map and vice versa.
 *
 * @param tile The tile to save the new type
 * @param type The type to save
 * @pre tile < MapSize()
 * @pre type MP_VOID <=> tile is on the south-east or south-west edge.
 */
static inline void SetTileType(TileIndex tile, TileType type)
{
	assert(tile < MapSize());
	/* VOID tiles (and no others) are exactly allowed at the lower left and right
	 * edges of the map. If _settings_game.construction.freeform_edges is true,
	 * the upper edges of the map are also VOID tiles. */
	assert((TileX(tile) == MapMaxX() || TileY(tile) == MapMaxY() || (_settings_game.construction.freeform_edges && (TileX(tile) == 0 || TileY(tile) == 0))) == (type == MP_VOID));
	SB(_m[tile].type_height, 4, 4, type);
}

/**
 * Checks if a tile is a give tiletype.
 *
 * This function checks if a tile got the given tiletype.
 *
 * @param tile The tile to check
 * @param type The type to check agains
 * @return true If the type matches agains the type of the tile
 */
static inline bool IsTileType(TileIndex tile, TileType type)
{
	return GetTileType(tile) == type;
}

/**
 * Checks if a tile is valid
 *
 * @param tile The tile to check
 * @return True if the tile is on the map and not one of MP_VOID.
 */
static inline bool IsValidTile(TileIndex tile)
{
	return tile < MapSize() && !IsTileType(tile, MP_VOID);
}

/**
 * Returns the owner of a tile
 *
 * This function returns the owner of a tile. This cannot used
 * for tiles which type is one of MP_HOUSE, MP_VOID and MP_INDUSTRY
 * as no company owned any of these buildings.
 *
 * @param tile The tile to check
 * @return The owner of the tile
 * @pre IsValidTile(tile)
 * @pre The type of the tile must not be MP_HOUSE and MP_INDUSTRY
 */
static inline Owner GetTileOwner(TileIndex tile)
{
	assert(IsValidTile(tile));
	assert(!IsTileType(tile, MP_HOUSE));
	assert(!IsTileType(tile, MP_INDUSTRY));

	return (Owner)GB(_m[tile].m1, 0, 5);
}

/**
 * Sets the owner of a tile
 *
 * This function sets the owner status of a tile. Note that you cannot
 * set a owner for tiles of type MP_HOUSE, MP_VOID and MP_INDUSTRY.
 *
 * @param tile The tile to change the owner status.
 * @param owner The new owner.
 * @pre IsValidTile(tile)
 * @pre The type of the tile must not be MP_HOUSE and MP_INDUSTRY
 */
static inline void SetTileOwner(TileIndex tile, Owner owner)
{
	assert(IsValidTile(tile));
	assert(!IsTileType(tile, MP_HOUSE));
	assert(!IsTileType(tile, MP_INDUSTRY));

	SB(_m[tile].m1, 0, 5, owner);
}

/**
 * Checks if a tile belongs to the given owner
 *
 * @param tile The tile to check
 * @param owner The owner to check agains
 * @return True if a tile belongs the the given owner
 */
static inline bool IsTileOwner(TileIndex tile, Owner owner)
{
	return GetTileOwner(tile) == owner;
}

/**
 * Set the tropic zone
 * @param tile the tile to set the zone of
 * @param type the new type
 * @pre tile < MapSize()
 */
static inline void SetTropicZone(TileIndex tile, TropicZone type)
{
	assert(tile < MapSize());
	assert(!IsTileType(tile, MP_VOID) || type == TROPICZONE_NORMAL);
	SB(_m[tile].m6, 0, 2, type);
}

/**
 * Get the tropic zone
 * @param tile the tile to get the zone of
 * @pre tile < MapSize()
 * @return the zone type
 */
static inline TropicZone GetTropicZone(TileIndex tile)
{
	assert(tile < MapSize());
	return (TropicZone)GB(_m[tile].m6, 0, 2);
}

/**
 * Get the current animation frame
 * @param t the tile
 * @pre IsTileType(t, MP_HOUSE) || IsTileType(t, MP_OBJECT) || IsTileType(t, MP_INDUSTRY) ||IsTileType(t, MP_STATION)
 * @return frame number
 */
static inline byte GetAnimationFrame(TileIndex t)
{
	assert(IsTileType(t, MP_HOUSE) || IsTileType(t, MP_OBJECT) || IsTileType(t, MP_INDUSTRY) ||IsTileType(t, MP_STATION));
	return _me[t].m7;
}

/**
 * Set a new animation frame
 * @param t the tile
 * @param frame the new frame number
 * @pre IsTileType(t, MP_HOUSE) || IsTileType(t, MP_OBJECT) || IsTileType(t, MP_INDUSTRY) ||IsTileType(t, MP_STATION)
 */
static inline void SetAnimationFrame(TileIndex t, byte frame)
{
	assert(IsTileType(t, MP_HOUSE) || IsTileType(t, MP_OBJECT) || IsTileType(t, MP_INDUSTRY) ||IsTileType(t, MP_STATION));
	_me[t].m7 = frame;
}

Slope GetTileSlope(TileIndex tile, uint *h);
Slope GetTileSlopeOutsideMap(int x, int y, uint *h);
uint GetTileZ(TileIndex tile);
uint GetTileZOutsideMap(int x, int y);
uint GetTileMaxZ(TileIndex tile);

/**
 * Returns TileMaxZ for points outside map.
 * i.e. < 0 or > MapMax.
 *
 * Example for the southeast corner:
 * Consider point (x, MapMaxY()). It's west corner has height h1,
 * its north corner has height h2. It's south and east corners
 * are somewhere outside in the black.
 *
 * Now, in terms of GetTileMaxZOutsideMap,
 * any point (x, y) with x from above and y > MapMaxY()
 * has west height h1 and north height h2.
 * In order words, we continue the map into the black area.
 *
 * Defining the height of the south corner of tile
 * (MapMaxX(), MapMaxY()) as h3, all corners of all points (x,y)
 * with x > MapMaxX and y > MapMaxY (south of the map) have height
 * h3.
 * For other areas outside the map respectively.
 *
 * This function is needed for painting the black area properly,
 * since the edges of map now can have height > 0.
 *
 * @param x any x coordinate.
 * @param y any y coordinate.
 * @return For points p inside the map GetTileMaxZ(p) (i.e. the usual value) is returned. For points
 *                outside the map see description above.
 */
uint GetTileMaxZOutsideMap(int x, int y);

/**
 * Calculate a hash value from a tile position
 *
 * @param x The X coordinate
 * @param y The Y coordinate
 * @return The hash of the tile
 */
static inline uint TileHash(uint x, uint y)
{
	uint hash = x >> 4;
	hash ^= x >> 6;
	hash ^= y >> 4;
	hash -= y >> 6;
	return hash;
}

/**
 * Get the last two bits of the TileHash
 *  from a tile position.
 *
 * @see TileHash()
 * @param x The X coordinate
 * @param y The Y coordinate
 * @return The last two bits from hash of the tile
 */
static inline uint TileHash2Bit(uint x, uint y)
{
	return GB(TileHash(x, y), 0, 2);
}

#endif /* TILE_MAP_H */
