// This file may be redistributed and modified only under the terms of
// the GNU General Public License (See COPYING for details).
// Copyright (C) 2003 Alistair Riddoch, Damien McGinnes

#ifndef MERCATOR_TERRAIN_H
#define MERCATOR_TERRAIN_H

#include "Mercator.h"
#include "BasePoint.h"

#include <wfmath/axisbox.h>
#include <wfmath/point.h>

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <tuple>
#include <functional>

namespace Mercator {

class Segment;

class Shader;

class TerrainMod;

class Area;

class Effector;

/// \brief Class storing centrally all data about an instance of some terrain.
///
/// All the data includes:
/// - Base Points which define the terrain.
/// - Segment data which is the actual heightfields.
/// - Shaders which control how the surface appears.
/// - Surfaces which are generated by the Shaders (delegated to Segment).
/// - TerrainMods which modify the terrain data (delegated to Segment).
class Terrain {
public:
	/// \brief Bounding box
	typedef WFMath::AxisBox<2> Rect;

	/// \brief STL map to store sparse array of BasePoints.
	typedef std::map<int, BasePoint> Pointcolumn;
	/// \brief STL map to store sparse array of Segments.
	typedef std::map<int, std::unique_ptr<Segment>> Segmentcolumn;

	/// \brief STL map to store sparse array of BasePoint columns.
	typedef std::map<int, Pointcolumn> Pointstore;
	/// \brief STL map to store sparse array of Segment pointer columns.
	typedef std::map<int, Segmentcolumn> Segmentstore;

	/// \brief STL map to store sparse array of Shader pointers.
	typedef std::map<int, const Shader*> Shaderstore;

	/// \brief value provided for no flags set.
	static const unsigned int DEFAULT = 0x0000;
	/// \brief set if shaders are going to be used on this terrain.
	static const unsigned int SHADED = 0x0001;
	// More options go here as bit flags, and below should be a private
	// test function
private:
	/// \brief Bitset of option flags controlling various aspects of terrain.
	const unsigned int m_options;
	/// \brief BasePoint resolution, or distance between adjacent points.
	const int m_res;
	/// \brief BasePoints spacing, same as m_res in float form for efficiency
	const float m_spacing;

	/// \brief 2D spatial container with all BasePoints.
	Pointstore m_basePoints;
	/// \brief 2D spatial container with pointers to all Segments.
	Segmentstore m_segments;
	/// \brief List of shaders to be applied to terrain.
	Shaderstore m_shaders;

	struct TerrainModEntry {
		/**
		 * The terrain mod.
		 */
		std::unique_ptr<TerrainMod> terrainMod;

		/**
		 * The area it last affected.
		 */
		Rect rect;
	};

	/**
	 * \brief Stores all terrain mods, identified using a long identifier.
	 *
	 * The reason for using an identifier is that we must have a stable sorting
	 * mechanism for terrain mods, so that they are applied in the same order to
	 * Segments.
	 */
	std::map<long, TerrainModEntry> m_terrainMods;

	struct TerrainAreaEntry {
		/**
		 * The terrain area.
		 */
		std::unique_ptr<Area> terrainArea;

		/**
		 * The area it last affected.
		 */
		Rect rect;
	};

	/**
	 * Stores all terrain areas, identified using a long identifier.
	 */
	std::map<long, TerrainAreaEntry> m_terrainAreas;

	/// \brief Add the required Surface objects to a Segment.
	///
	/// If shading is enabled, each Segment has a set of Surface objects
	/// corresponding to the Shader objects available for this terrain.
	/// This function creates the necessary Surface objects, and adds them
	/// to the list in the Segment object. At this point the Segment is
	/// not yet populated with heightfield data, so the Surface cannot
	/// be populated. A newly constructed surface does not allocate its
	/// buffer memory, so there is very little overhead to creating it.
	void addSurfaces(Segment&);

	/// \brief Populate the Surface objects associated with a Segment.
	///
	/// This is called after a Segment has been populated with heightfield
	/// data. The actual responsibility for populating the Surface objects
	/// is in Segment::populateSurfaces().
	static void shadeSurfaces(Segment&);

	/// \brief Determine whether this terrain object has shading enabled.
	///
	/// @return true if shading is enabled, false otherwise.
	bool isShaded() const {
		return ((m_options & SHADED) == SHADED);
	}

public:
	/// \brief Height value used when no data is available.
	static constexpr float defaultLevel = 8.f;

	/// \brief Construct a new Terrain object with optional options and resolution.
	///
	/// @param options a bitfield of option flags. Defaults to no flags set.
	/// - DEFAULT value provided for no flags set.
	/// - SHADED is set if shaders are going to be used on this terrain.
	/// @param resolution the spacing between adjacent base points. Defaults to 64.
	explicit Terrain(unsigned int options = DEFAULT,
					 int resolution = defaultResolution);

	/// \brief Destroy Terrain object, deleting contained objects.
	///
	/// All Segment objects are deleted, but Shader objects are not yet deleted.
	/// Probably need to be fixed.
	~Terrain();

	/// \brief Get the height value at a given coordinate x,z.
	///
	/// This is a convenience function provided to quickly get a height
	/// value at a given point. It always succeeds, as if no height data
	/// is available it just returns the default value. If a Segment does
	/// exist in the right place and is populated, the coords within that
	/// Segment are determined, and the heightfield queried. This function
	/// does not cause any data to be populated, and does not attempt to
	/// do any interpolation to get an accurate height figure. For more
	/// accurate results see Terrain::getHeightAndNormal.
	float get(float x, float z) const;

	/// \brief Get an accurate height at a given coordinate x,z.
	///
	/// This is a more expensive function that Terrain::get() for getting an
	/// accurate height value.
	/// The main  body of functionality is in the Segment::getHeight() function
	/// called from here.
	/// The height is interpolated based on a model where each
	/// tile of the heightfield comprises two triangles. If no heightfield data
	/// is available for the given location, this function returns false, and
	/// no data is returned.
	/// @param x coordinate of point to be returned.
	/// @param z coordinate of point to be returned.
	/// @param h reference to variable which will be used to store the resulting
	/// height value.
	/// @return true if heightdata was available, false otherwise.
	bool getHeight(float x, float z, float& h) const;

	/// \brief Get an accurate height and normal vector at a given coordinate
	/// x,z.
	///
	/// This is a more expensive function that Terrain::get() for getting an
	/// accurate height value and surface normal at a given point. The main
	/// body of functionality is in the Segment::getHeightAndNormal() function
	/// called from here.
	/// The height and normal are interpolated based on a model where each
	/// tile of the heightfield comprises two triangles. If no heightfield data
	/// is available for the given location, this function returns false, and
	/// no data is returned.
	/// @param x coordinate of point to be returned.
	/// @param z coordinate of point to be returned.
	/// @param h reference to variable which will be used to store the resulting
	/// height value.
	/// @param n reference to variable which will be used to store the resulting
	/// normal value.
	/// @return true if heightdata was available, false otherwise.
	bool getHeightAndNormal(float x, float z, float& h, WFMath::Vector<3>& n) const;

	/// \brief Get the BasePoint at a given base point coordinate.
	///
	/// Get the BasePoint value for the given coordinate on the base
	/// point grid.
	/// @param x coordinate on the base point grid.
	/// @param z coordinate on the base point grid.
	/// @param y reference to variable which will be used to store the
	/// BasePoint data.
	/// @return true if a BasePoint is defined at the given coordinate, false
	/// otherwise.
	bool getBasePoint(int x, int z, BasePoint& y) const;

	/// \brief Set the BasePoint value at a given base point coordinate.
	///
	/// Set the BasePoint value for the given coordinate on the base
	/// point grid.
	/// If inserting this BasePoint completes the set of points required
	/// to define one or more Segment objects which were not yet defined,
	/// new Segment objects are created. If this replaces a point for one
	/// or more Segment objects that were already defined, the contents of
	/// those Segment objects are invalidated.
	/// @param x coordinate on the base point grid.
	/// @param z coordinate on the base point grid.
	/// @param y BasePoint value to be used at the given coordinate.
	void setBasePoint(int x, int z, const BasePoint& y);

	/// \brief Set the height of the basepoint at x,y to z.
	void setBasePoint(int x, int y, float z) {
		BasePoint bp(z);
		setBasePoint(x, y, bp);
	}

	/// \brief Get a pointer to the segment which contains the coord x,y
	///
	/// @return Null if no segment is defined at that location, or a pointer
	/// to a Segment otherwise.
	Segment* getSegmentAtPos(float x, float z) const;

	/// \brief Get the Segment at a given index.
	///
	/// Get the Segment pointer for the given coordinate on the base
	/// point grid. The Segment in question may not have been populated
	/// with heightfield or surface data.
	/// @param x coordinate on the base point grid.
	/// @param z coordinate on the base point grid.
	/// @return a valid pointer if a Segment is defined at the given coordinate,
	/// zero otherwise.
	Segment* getSegmentAtIndex(int x, int z) const;

	/// \brief Accessor for base point resolution.
	int getResolution() const {
		return m_res;
	}

	/// \brief Accessor for base point spacing.
	float getSpacing() const {
		return m_spacing;
	}

	/// \brief Accessor for 2D sparse array of Segment pointers.
	const Segmentstore& getTerrain() const {
		return m_segments;
	}

	/// \brief Accessor for 2D sparse array of BasePoint objects.
	const Pointstore& getPoints() const {
		return m_basePoints;
	}

	/// \brief Accessor for list of Shader pointers.
	const Shaderstore& getShaders() const {
		return m_shaders;
	}

	/// \brief Add a new Shader to the list for this terrain.
	///
	/// As each shader is added, surfaces are added to all existing segments
	/// to store the result of the shader.
	void addShader(const Shader* t, int id);

	/// \brief remove a Shader from the list for this terrain.
	///
	/// As each shader is removed, surfaces are removed from existing segments
	void removeShader(const Shader* t, int id);

	/// \brief Updates the terrain with a mod.
	///
	/// @param id The id of the mod, which is also used for ordering.
	/// @param mod The terrain mod, or null if the entry for the id should be removed.
	/// @return The area affected by the terrain mod before it was updated.
	Rect updateMod(long id, std::unique_ptr<TerrainMod> mod);

	/// \brief Checks if a mod with the supplied id has been registered with the terrain.
	///
	/// @param id The id of the mod to check for.
	/// @return True if the mod is added to the terrain.
	bool hasMod(long id) const;

	const TerrainMod* getMod(long id) const;

	/// \brief Updates the terrain affected by an area.
	///
	/// @param a The terrain area which has changed, or null if the entry should be removed.
	/// @return The area affected by the terrain area before it was updated.
	Rect updateArea(long id, std::unique_ptr<Area> a);


	const Area* getArea(long id) const;

	/**
	 * \brief Converts the supplied position into a segment index.
	 * @param pos A position, either x or y.
	 * @return The index
	 */
	int posToIndex(float pos) const;

	/**
	 * Processes all segments within the supplied area.
	 * @param area An area.
	 * @param func Function called for each segment. X and Y index are submitted as second and third arguments.
	 */
	void processSegments(const WFMath::AxisBox<2>& area, const std::function<void(Segment&, int, int)>& func) const;
};

inline int Terrain::posToIndex(float pos) const {
	return (int) std::lround(std::floor(pos / m_spacing));
}

inline Segment* Terrain::getSegmentAtPos(float x, float z) const {
	return getSegmentAtIndex(posToIndex(x), posToIndex(z));
}


} // namespace Mercator

#endif // MERCATOR_TERRAIN_H
