//////////////////////////////////////////////////////////////////////////
//
// This file is part of the BWEM Library.
// BWEM is free software, licensed under the MIT/X11 License. 
// A copy of the license is provided with the library in the LICENSE file.
// Copyright (c) 2015, 2017, Igor Dimitrijevic
//
//////////////////////////////////////////////////////////////////////////

#pragma once

#include <BWAPI.h>
#include "area.h"
#include "utils.h"
#include "defs.h"


namespace BWEM {

class Neutral;
class Map;

//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class MiniTile
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////
//
// Corresponds to BWAPI/Starcraft's concept of minitile (8x8 pixels).
// MiniTiles are accessed using WalkPositions (Cf. Map::GetMiniTile).
// A Map holds Map::WalkSize().x * Map::WalkSize().y MiniTiles as its "MiniTile map".
// A MiniTile contains essentialy 3 informations:
//	- its Walkability
//	- its altitude (distance from the nearest non walkable MiniTile, except those which are part of small enough zones (lakes))
//	- the id of the Area it is part of, if ever.
// The whole process of analysis of a Map relies on the walkability information
// from which are derived successively : altitudes, Areas, ChokePoints.

class MiniTile
{
public:
	// Corresponds approximatively to BWAPI::isWalkable
	// The differences are:
	//  - For each BWAPI's unwalkable MiniTile, we also mark its 8 neighbours as not walkable.
	//    According to some tests, this prevents from wrongly pretending one small unit can go by some thin path.
	//  - The relation buildable ==> walkable is enforced, by marking as walkable any MiniTile part of a buildable Tile (Cf. Tile::Buildable)
	// Among the MiniTiles having Altitude() > 0, the walkable ones are considered Terrain-MiniTiles, and the other ones Lake-MiniTiles.
	bool				Walkable() const			{ return m_areaId != 0; }

	// Distance in pixels between the center of this MiniTile and the center of the nearest Sea-MiniTile
	// Sea-MiniTiles all have their Altitude() equal to 0.
	// MiniTiles having Altitude() > 0 are not Sea-MiniTiles. They can be either Terrain-MiniTiles or Lake-MiniTiles.
	altitude_t			Altitude() const			{ return m_altitude; }

	// Sea-MiniTiles are unwalkable MiniTiles that have their Altitude() equal to 0.
	bool				Sea() const					{ return m_altitude == 0; }

	// Lake-MiniTiles are unwalkable MiniTiles that have their Altitude() > 0.
	// They form small zones (inside Terrain-zones) that can be eaysily walked around (e.g. Starcraft's doodads)
	// The intent is to preserve the continuity of altitudes inside Areas.
	bool				Lake() const				{ return (m_altitude != 0) && !Walkable(); }

	// Terrain MiniTiles are just walkable MiniTiles
	bool				Terrain() const				{ return Walkable(); }

	// For Sea and Lake MiniTiles, returns 0
	// For Terrain MiniTiles, returns a non zero id:
	//    - if (id > 0), id uniquely identifies the Area A that contains this MiniTile.
	//      Moreover we have: A.Id() == id and Map::GetArea(id) == A
	//      For more information about positive Area::ids, see Area::Id()
	//    - if (id < 0), then this MiniTile is part of a Terrain-zone that was considered too small to create an Area for it.
	//      Note: negative Area::ids start from -2
	// Note: because of the lakes, Map::GetNearestArea should be prefered over Map::GetArea.
	Area::id			AreaId() const				{ return m_areaId; }


////////////////////////////////////////////////////////////////////////////
//	Details: The functions below are used by the BWEM's internals

	void				SetWalkable(bool walkable)	{ m_areaId = (walkable ? -1 : 0); m_altitude = (walkable ? -1 : 1); }
	bool				SeaOrLake() const			{ return m_altitude == 1; }
	void				SetSea()					{ bwem_assert(!Walkable() && SeaOrLake()); m_altitude = 0; }
	void				SetLake()					{ bwem_assert(!Walkable() && Sea()); m_altitude = -1; }
	bool				AltitudeMissing() const		{ return m_altitude == -1; }
	void				SetAltitude(altitude_t a)	{ bwem_assert_debug_only(AltitudeMissing() && (a > 0)); m_altitude = a; }
	bool				AreaIdMissing() const		{ return m_areaId == -1; }
	void				SetAreaId(Area::id id)		{ bwem_assert(AreaIdMissing() && (id >= 1)); m_areaId = id; }
	void				ReplaceAreaId(Area::id id)	{ bwem_assert((m_areaId > 0) && ((id >= 1) || (id <= -2)) && (id != m_areaId)); m_areaId = id; }
	void				SetBlocked()				{ bwem_assert(AreaIdMissing()); m_areaId = blockingCP; }
	bool				Blocked() const				{ return m_areaId == blockingCP; }
	void				ReplaceBlockedAreaId(Area::id id)	{ bwem_assert((m_areaId == blockingCP) && (id >= 1)); m_areaId = id; }

private:
	altitude_t			m_altitude = -1;		// 0 for seas  ;  != 0 for terrain and lakes (-1 = not computed yet)  ;  1 = SeaOrLake intermediate value
	Area::id			m_areaId = -1;			// 0 -> unwalkable  ;  > 0 -> index of some Area  ;  < 0 -> some walkable terrain, but too small to be part of an Area
	static const Area::id blockingCP;
};



//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class Tile
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////
//
// Corresponds to BWAPI/Starcraft's concept of tile (32x32 pixels).
// Tiles are accessed using TilePositions (Cf. Map::GetTile).
// A Map holds Map::Size().x * Map::Size().y Tiles as its "Tile map".
//
// It should be noted that a Tile exactly overlaps 4 x 4 MiniTiles.
// As there are 16 times as many MiniTiles as Tiles, we allow a Tiles to contain more data than MiniTiles.
// As a consequence, Tiles should be preferred over MiniTiles, for efficiency.
// The use of Tiles is further facilitated by some functions like Tile::AreaId or Tile::MinAltitude
// which somewhat aggregate the MiniTile's corresponding information
//
// Tiles inherit utils::Markable, which provides marking ability
// Tiles inherit utils::UserData, which provides free-to-use data.

class Tile : public utils::Markable<Tile, int>, public utils::UserData
{
public:
	// Corresponds to BWAPI::isBuildable
	// Note: BWEM enforces the relation buildable ==> walkable (Cf. MiniTile::Walkable)
	bool				Buildable() const				{ return m_bits.buildable; }

	// Tile::AreaId() somewhat aggregates the MiniTile::AreaId() values of the 4 x 4 sub-MiniTiles.
	// Let S be the set of MiniTile::AreaId() values for each walkable MiniTile in this Tile.
	// If empty(S), returns 0. Note: in this case, no contained MiniTile is walkable, so all of them have their AreaId() == 0.
	// If S = {a}, returns a (whether positive or negative).
	// If size(S) > 1 returns -1 (note that -1 is never returned by MiniTile::AreaId()).
	Area::id			AreaId() const					{ return m_areaId; }

	// Tile::MinAltitude() somewhat aggregates the MiniTile::Altitude() values of the 4 x 4 sub-MiniTiles.
	// Returns the minimum value.
	altitude_t			MinAltitude() const				{ return m_minAltitude; }

	// Tells if at least one of the sub-MiniTiles is Walkable.
	bool				Walkable() const				{ return m_areaId != 0; }

	// Tells if at least one of the sub-MiniTiles is a Terrain-MiniTile.
	bool				Terrain() const					{ return Walkable(); }

	// 0: lower ground    1: high ground    2: very high ground
	// Corresponds to BWAPI::getGroundHeight / 2
	int					GroundHeight() const			{ return m_bits.groundHeight; }

	// Tells if this Tile is part of a doodad.
	// Corresponds to BWAPI::getGroundHeight % 2
	bool				Doodad() const					{ return m_bits.doodad; }

	// If any Neutral occupies this Tile, returns it (note that all the Tiles it occupies will then return it).
	// Otherwise, returns nullptr.
	// Neutrals are Minerals, Geysers and StaticBuildings (Cf. Neutral).
	// In some maps (e.g. Benzene.scx), several Neutrals are stacked at the same location.
	// In this case, only the "bottom" one is returned, while the other ones can be accessed using Neutral::NextStacked().
	// Because Neutrals never move on the Map, the returned value is guaranteed to remain the same, unless some Neutral
	// is destroyed and BWEM is informed of that by a call of Map::OnMineralDestroyed(BWAPI::Unit u) for exemple. In such a case,
	// BWEM automatically updates the data by deleting the Neutral instance and clearing any reference to it such as the one
	// returned by Tile::GetNeutral(). In case of stacked Neutrals, the next one is then returned.
	Neutral *			GetNeutral() const				{ return m_pNeutral; }

	// Returns the number of Neutrals that occupy this Tile (Cf. GetNeutral).
	int					StackedNeutrals() const;

////////////////////////////////////////////////////////////////////////////
//	Details: The functions below are used by the BWEM's internals

	void				SetBuildable()					{ m_bits.buildable = 1; }
	void				SetGroundHeight(int h)			{ bwem_assert((0 <= h) && (h <= 2)); m_bits.groundHeight = h; }
	void				SetDoodad()						{ m_bits.doodad = 1; }
	void				AddNeutral(Neutral * pNeutral)	{ bwem_assert(!m_pNeutral && pNeutral); m_pNeutral = pNeutral; }
	void				SetAreaId(Area::id id)			{ bwem_assert((id == -1) || !m_areaId && id); m_areaId = id; }
	void				ResetAreaId()					{ m_areaId = 0; }
	void				SetMinAltitude(altitude_t a)	{ bwem_assert(a >= 0); m_minAltitude = a; }
	void				RemoveNeutral(Neutral * pNeutral){ bwem_assert(pNeutral && (m_pNeutral == pNeutral)); utils::unused(pNeutral); m_pNeutral = nullptr; }
	int					InternalData() const			{ return m_internalData; }
	void				SetInternalData(int data) const	{ m_internalData = data; }

						
private:
	struct Bits
	{
						Bits() : buildable(0), groundHeight(0), doodad(0) {}
		uint8_t			buildable:1;
		uint8_t			groundHeight:2;
		uint8_t			doodad:1;
	};
	
	Neutral *			m_pNeutral = nullptr;
	altitude_t			m_minAltitude;
	Area::id			m_areaId = 0;
	mutable int			m_internalData = 0;
	Bits				m_bits;
};

// Note: the following 4 functions may change in the future...
altitude_t minAltitudeTop(const BWAPI::TilePosition & tile, const Map & theMap);
altitude_t minAltitudeBottom(const BWAPI::TilePosition & tile, const Map & theMap);
altitude_t minAltitudeLeft(const BWAPI::TilePosition & tile, const Map & theMap);
altitude_t minAltitudeRight(const BWAPI::TilePosition & tile, const Map & theMap);



namespace utils {


// Helpers for generic use of Tiles and MiniTiles (see Map::BreadthFirstSearch for a use case)

template<class TPosition>
struct TileOfPosition;

template<>
struct TileOfPosition<BWAPI::TilePosition>
{
	typedef Tile type;
};

template<>
struct TileOfPosition<BWAPI::WalkPosition>
{
	typedef MiniTile type;
};



template<class TTile>
struct PositionOfTile;

template<>
struct PositionOfTile<Tile>
{
	typedef BWAPI::TilePosition type;
};

template<>
struct PositionOfTile<MiniTile>
{
	typedef BWAPI::WalkPosition type;
};



} // namespace utils




} // namespace BWEM
