#ifndef _RUENTITYTERRAIN_COMMON_H_
#define _RUENTITYTERRAIN_COMMON_H_

// Rune entity base
#include "../../Scene/Base/RuEntityBase.h"
#include "../../Scene/Particle/RuEntity_Particle_Grass.h"
#include "../../Scene/Terrain/RuEntityWater.h"
#include "../../Core/Fractal/RuFractal_Base.h"
#include "../../Mojo/RuMojo_Base.h"
#include "../../Mojo/RuMojo_Helper.h"

#pragma unmanaged

// ************************************************************************************************************************************************************

#define ruWORLD_MAKECHUNKKEY(x, z)		((((x) & 0x0000FFFF) << 16) | ((z) & 0x0000FFFF))
#define ruWORLD_CHUNKKEYX(chunkKey)		(((chunkKey) >> 16) & 0x0000FFFF)
#define ruWORLD_CHUNKKEYZ(chunkKey)		((chunkKey) & 0x0000FFFF)


// ************************************************************************************************************************************************************

class CRuSplatIDMap;
class CRuSplatMapBuilder;

// ************************************************************************************************************************************************************

namespace RuWorld
{
	/*
		Neighbor ID Defined
		-------------------

		| 0 | 1 | 2 |
		-------------
		| 3 | X | 4 |
		-------------
		| 5 | 6 | 7 |

		Neighbor ID is defined as an integer, specifying the spatial location
		relative to the current chunk. For instance, 0 specifies the neighboring
		chunk at (-1, -1). 
	*/

	typedef boost::function<CRuSplatIDMap * (INT32, INT32)> GetNeighborSplat;		// Parameters: (chunk key, neighbor ID)
}

// ************************************************************************************************************************************************************

enum RuTerrainUtility_PaintCondition
{
	ruTUPAINTCONDITION_ALL					= 0,
	ruTUPAINTCONDITION_AFFECTEDONLY			= 1,

	ruTUPAINTCONDITION_FORCE_DWORD			= 0x7FFFFFFF
};

// ************************************************************************************************************************************************************

enum RuTerrainUtility_BooleanType
{
	ruTUBOOLEANTYPE_OVERWRITE				= 0,
	ruTUBOOLEANTYPE_UNION					= 1,
	ruTUBOOLEANTYPE_INTERSECTION			= 2,

	ruTUBOOLEANTYPE_FORCE_DWORD				= 0x7FFFFFFF
};

// ************************************************************************************************************************************************************

enum RuTerrainUtility_FalloffType
{
	ruTUFALLOFFTYPE_NONE					= 0,
	ruTUFALLOFFTYPE_ALL						= 1,
	ruTUFALLOFFTYPE_LINEAR					= 2,
	ruTUFALLOFFTYPE_QUADRATIC				= 3,
	ruTUFALLOFFTYPE_CUBIC					= 4,
	ruTUFALLOFFTYPE_QUARTIC					= 5,
	ruTUFALLOFFTYPE_QUINTIC					= 6,
	ruTUFALLOFFTYPE_BEVEL					= 7,
	ruTUFALLOFFTYPE_SMOOTHBEVEL				= 8,

	ruTUFALLOFFTYPE_FORCE_DWORD				= 0x7FFFFFFF
};

// ************************************************************************************************************************************************************

class CRuSplatIDMap_PaintSetting
{
public:
	INT32									m_splat_0;
	INT32									m_splat_1;
	INT32									m_splat_2;
	INT32									m_splat_3;
	INT32									m_splat_4;
	INT32									m_splat_5;

	REAL									m_threshold_0;
	REAL									m_threshold_1;
	REAL									m_threshold_2;
	REAL									m_threshold_3;
	REAL									m_threshold_4;
	REAL									m_threshold_5;

	REAL									m_slope_0;
	REAL									m_slope_1;
	REAL									m_slope_2;
	REAL									m_slope_3;
	REAL									m_slope_4;
	REAL									m_slope_5;

	REAL									m_heightMultiplier;
	REAL									m_heightThreshold;
	REAL									m_heightOffset;

	REAL									m_brushCenterX;
	REAL									m_brushCenterY;
	REAL									m_brushLimit;

	RuTerrainUtility_PaintCondition			m_paintCondition;
	RuTerrainUtility_BooleanType			m_heightBooleanType;
	RuTerrainUtility_FalloffType			m_heightFalloffType;
	RuTerrainUtility_FalloffType			m_noiseFalloffType;

	// Run-time only data
	BOOL									m_heightAutoLevelOn;
	REAL									m_heightAutoLevel;

											CRuSplatIDMap_PaintSetting()
											{
												m_splat_0 = 0;
												m_splat_1 = 0;
												m_splat_2 = 0;
												m_splat_3 = 0;
												m_splat_4 = 0;
												m_splat_5 = 0;

												m_threshold_0 = 1.00f;
												m_threshold_1 = 1.00f;
												m_threshold_2 = 1.00f;
												m_threshold_3 = 1.00f;
												m_threshold_4 = 1.00f;
												m_threshold_5 = 1.00f;

												m_slope_0 = 0.00f;
												m_slope_1 = 0.00f;
												m_slope_2 = 0.00f;
												m_slope_3 = 0.00f;
												m_slope_4 = 0.00f;
												m_slope_5 = 0.00f;

												m_heightMultiplier = 0.0f;
												m_heightThreshold = 0.0f;
												m_heightOffset = 0.0f;

												m_brushCenterX = 0.0f;
												m_brushCenterY = 0.0f;
												m_brushLimit = -1.0f;

												m_paintCondition = ruTUPAINTCONDITION_ALL;
												m_heightBooleanType = ruTUBOOLEANTYPE_OVERWRITE;
												m_heightFalloffType = ruTUFALLOFFTYPE_NONE;
												m_noiseFalloffType = ruTUFALLOFFTYPE_NONE;

												m_heightAutoLevelOn = TRUE;
												m_heightAutoLevel = 0.0f;
											}

	void									SerializeFrom(IRuStream *stream)
											{
												INT32 version = 1;
												if(stream->Read(&version, sizeof(INT32)) == sizeof(INT32))
												{
													switch(version)
													{
														case 1:
															{
																stream->Read(&m_splat_0, sizeof(INT32));
																stream->Read(&m_splat_1, sizeof(INT32));
																stream->Read(&m_splat_2, sizeof(INT32));
																stream->Read(&m_splat_3, sizeof(INT32));

																stream->Read(&m_threshold_0, sizeof(REAL));
																stream->Read(&m_threshold_1, sizeof(REAL));
																stream->Read(&m_threshold_2, sizeof(REAL));
																stream->Read(&m_threshold_3, sizeof(REAL));

																stream->Read(&m_heightMultiplier, sizeof(REAL));
																stream->Read(&m_heightThreshold, sizeof(REAL));
																stream->Read(&m_heightOffset, sizeof(REAL));
															}

															break;

														case 2:
															{
																stream->Read(&m_splat_0, sizeof(INT32));
																stream->Read(&m_splat_1, sizeof(INT32));
																stream->Read(&m_splat_2, sizeof(INT32));
																stream->Read(&m_splat_3, sizeof(INT32));
																stream->Read(&m_splat_4, sizeof(INT32));
																stream->Read(&m_splat_5, sizeof(INT32));

																stream->Read(&m_threshold_0, sizeof(REAL));
																stream->Read(&m_threshold_1, sizeof(REAL));
																stream->Read(&m_threshold_2, sizeof(REAL));
																stream->Read(&m_threshold_3, sizeof(REAL));
																stream->Read(&m_threshold_4, sizeof(REAL));
																stream->Read(&m_threshold_5, sizeof(REAL));

																stream->Read(&m_slope_0, sizeof(REAL));
																stream->Read(&m_slope_1, sizeof(REAL));
																stream->Read(&m_slope_2, sizeof(REAL));
																stream->Read(&m_slope_3, sizeof(REAL));
																stream->Read(&m_slope_4, sizeof(REAL));
																stream->Read(&m_slope_5, sizeof(REAL));

																stream->Read(&m_heightMultiplier, sizeof(REAL));
																stream->Read(&m_heightThreshold, sizeof(REAL));
																stream->Read(&m_heightOffset, sizeof(REAL));

																stream->Read(&m_brushCenterX, sizeof(REAL));
																stream->Read(&m_brushCenterY, sizeof(REAL));
																stream->Read(&m_brushLimit, sizeof(REAL));

																stream->Read(&m_paintCondition, sizeof(RuTerrainUtility_PaintCondition));
																stream->Read(&m_heightBooleanType, sizeof(RuTerrainUtility_BooleanType));
																stream->Read(&m_heightFalloffType, sizeof(RuTerrainUtility_FalloffType));
																stream->Read(&m_noiseFalloffType, sizeof(RuTerrainUtility_FalloffType));
															}

															break;
													}
												}
											}

	void									SerializeTo(IRuStream *stream)
											{
												INT32 version = 2;
												stream->Write(&version, sizeof(INT32));

												stream->Write(&m_splat_0, sizeof(INT32));
												stream->Write(&m_splat_1, sizeof(INT32));
												stream->Write(&m_splat_2, sizeof(INT32));
												stream->Write(&m_splat_3, sizeof(INT32));
												stream->Write(&m_splat_4, sizeof(INT32));
												stream->Write(&m_splat_5, sizeof(INT32));

												stream->Write(&m_threshold_0, sizeof(REAL));
												stream->Write(&m_threshold_1, sizeof(REAL));
												stream->Write(&m_threshold_2, sizeof(REAL));
												stream->Write(&m_threshold_3, sizeof(REAL));
												stream->Write(&m_threshold_4, sizeof(REAL));
												stream->Write(&m_threshold_5, sizeof(REAL));

												stream->Write(&m_slope_0, sizeof(REAL));
												stream->Write(&m_slope_1, sizeof(REAL));
												stream->Write(&m_slope_2, sizeof(REAL));
												stream->Write(&m_slope_3, sizeof(REAL));
												stream->Write(&m_slope_4, sizeof(REAL));
												stream->Write(&m_slope_5, sizeof(REAL));

												stream->Write(&m_heightMultiplier, sizeof(REAL));
												stream->Write(&m_heightThreshold, sizeof(REAL));
												stream->Write(&m_heightOffset, sizeof(REAL));

												stream->Write(&m_brushCenterX, sizeof(REAL));
												stream->Write(&m_brushCenterY, sizeof(REAL));
												stream->Write(&m_brushLimit, sizeof(REAL));

												stream->Write(&m_paintCondition, sizeof(RuTerrainUtility_PaintCondition));
												stream->Write(&m_heightBooleanType, sizeof(RuTerrainUtility_BooleanType));
												stream->Write(&m_heightFalloffType, sizeof(RuTerrainUtility_FalloffType));
												stream->Write(&m_noiseFalloffType, sizeof(RuTerrainUtility_FalloffType));
											}

	inline REAL								EvaluateFalloff(RuTerrainUtility_FalloffType falloffType, REAL t, REAL range) const
											{
												switch(falloffType)
												{
													case ruTUFALLOFFTYPE_NONE:
														return RuFalloff_None(t, range);
														break;

													case ruTUFALLOFFTYPE_ALL:
														return RuFalloff_All(t, range);
														break;

													case ruTUFALLOFFTYPE_LINEAR:
														return RuFalloff_Linear(t, range);
														break;

													case ruTUFALLOFFTYPE_QUADRATIC:
														return RuFalloff_Quadratic(t, range);
														break;

													case ruTUFALLOFFTYPE_CUBIC:
														return RuFalloff_Cubic(t, range);
														break;

													case ruTUFALLOFFTYPE_QUARTIC:
														return RuFalloff_Quadratic(t, range);
														break;

													case ruTUFALLOFFTYPE_QUINTIC:
														return RuFalloff_Quintic(t, range);
														break;

													case ruTUFALLOFFTYPE_BEVEL:
														return RuFalloff_Bevel(t, range);
														break;

													case ruTUFALLOFFTYPE_SMOOTHBEVEL:
														return RuFalloff_SmoothBevel(t, range);
														break;
												}

												return 1.0f;
											}
};

// ************************************************************************************************************************************************************

class CRuTerrainUtility_ChunkInfo
{
protected:
	CRuVector3*								m_normalField;
	BYTE*									m_autoPaintField;
	CRuVector3								m_origin;
	INT32									m_fieldWidth;
	REAL									m_gridSize;

public:
											CRuTerrainUtility_ChunkInfo(CRuVector3 *normalField, BYTE *autoPaintField, CRuVector3 &origin, INT32 fieldWidth, REAL gridSize);

	REAL									EvaluateSlope(const CRuVector3 &position, REAL range);
	BOOL									PaintOK(const CRuVector3 &position);
};

// ************************************************************************************************************************************************************

class CRuImageConvolver
{
protected:
	INT32									m_kernelSize;						// Convolution kernel size
	float*									m_kernel;							// Convolution kernel

	INT32									m_dimension;						// Dimensions of the image
	INT32									m_paddingSize;						// Padding size of the image
	INT32									m_realDimension;					// Real dimensions of the image data
	UINT8*									m_image;							// Image data
	UINT8*									m_workBuffer;						// Work buffer for convolution

public:
											CRuImageConvolver(INT32 kernelSize, float *kernel, INT32 imageSize);
	virtual									~CRuImageConvolver();

	void									ClearImageData();
	UINT8*									GetImageData();
	UINT8*									GetRawImageData();
	INT32									GetImagePitch();

	BOOL									Convolve();
};

// ************************************************************************************************************************************************************

class CRuSplatIDMap : public IRuObject
{
	ruRTTI_DECLARE
	ruHEAP_DECLARE
	ruCLASSGUID_DECLARE

protected:
	struct FloodFillDesc
	{
		INT32								m_chunkKey;
		CRuSplatIDMap*						m_splatIDMap;
		CRuStack<INT32>						m_texelStack;
	};

	INT32									m_dimension;						// Dimensions of the splat ID map
	CRuRectangle							m_splatRect;						// Area of real space the splat covers
	UINT16*									m_idMap;							// ID map data

	// Currently constructed composition data
	BOOL									m_isFiltered;						// Splat map is filtered properly
	BOOL									m_isDirty;							// Splat map is dirty
	CRuArrayList<UINT16>					m_layerIDs;
	INT32									m_numAlphaMaps;
	IRuTexture**							m_alphaMaps;

public:
											CRuSplatIDMap();
	virtual									~CRuSplatIDMap();

	// IRuObject serialization
	virtual IRuObject*						Clone(IRuObject *clonedObject = NULL);
	virtual BOOL							SerializeFrom(IRuStream *stream);
	virtual BOOL							SerializeTo(IRuStream *stream);
	virtual BOOL							SerializeToAlphaMaps(IRuStream *stream);
	static BOOL								SkipStream(IRuStream *stream);

	BOOL									Initialize(INT32 dimension, const CRuRectangle &splatRect);

	INT32									GetNumLayers() const;
	void									GetIDListAsString(char *splatIDListOut);
	UINT16									GetLayerID(INT32 layerIndex) const;
	INT32									GetNumAlphaMaps() const;
	IRuTexture*								GetAlphaMap(INT32 mapIndex);

	INT32									GetDimension() const;

	// Painting
	BOOL									PaintSplat(const CRuVector3 &center, float radius, UINT16 splatID);
	BOOL									SprayPaintSplat(const CRuVector3 &center, float radius, UINT16 splatID, float sprayProbability);
	BOOL									BlockFillSplat(const CRuVector3 &center, float radius, UINT16 splatID);
	BOOL									FloodFillSplat(const CRuVector3 &center, float radius, UINT16 splatID, INT32 chunkKey, RuWorld::GetNeighborSplat getSplatCallback);
	BOOL									SmudgeSplat(const CRuVector3 &center, float radius, float smudgeProbability);
	BOOL									ReplaceSplat(const CRuVector3 &center, float radius, UINT16 splatID);
	BOOL									AttractSplat(const CRuVector3 &center, float radius, UINT16 attractedSplatID, float attractProbability);
	UINT16									PickSplatID(const CRuVector3 &center);

	// Auto & fractal painting
	BOOL									MojoPaint(const CRuVector3 &center, float radius, CRuMojo_AttributeParcel<REAL> *refHF, CRuMojo_AttributeParcel<REAL> *chunkHF, CRuFractal_Base *srcFractal, const CRuSplatIDMap_PaintSetting &paintSetting);

	BOOL									AutoPaintSplat(CRuMojo_AttributeParcel<REAL> *refHF, CRuMojo_AttributeParcel<REAL> *chunkHF, CRuFractal_Base *srcFractal, const CRuSplatIDMap_PaintSetting &paintSetting);
	BOOL									AutoPaintSplat(const CRuVector3 &center, float radius, CRuFractal_Base *srcFractal, const CRuSplatIDMap_PaintSetting &paintSetting, CRuTerrainUtility_ChunkInfo &normalField);

	INT32									GetMemoryFootprint();

	// Static helpers
	static INT32							GetSplatIDMapSize(INT32 dimension);

	friend									CRuSplatMapBuilder;
};

// ************************************************************************************************************************************************************

class CRuSplatMapBuilder : public IRuObject
{
	ruRTTI_DECLARE
	ruHEAP_DECLARE
	ruCLASSGUID_DECLARE

protected:
	INT32									m_idMapDimension;					// Dimension of the ID map
	INT32									m_idMapPadding;						// Padding size on each side of the ID map
	INT32									m_mapSizeFactor;
	INT32									m_convolverDimension;
	INT32									m_alphaMapDimension;				//
//	CRuImageConvolver*						m_imageConvolvers[3];				// Image convolvers used for generating alpha maps


	INT32									m_maxLayers;
	CRuImageConvolver**						m_imageConvolvers;					// Image convolvers used for generating alpha maps

public:
											CRuSplatMapBuilder();
	virtual									~CRuSplatMapBuilder();

	BOOL									Initialize(INT32 idMapDimension, INT32 idMapPadding, INT32 mapSizeFactor);

	BOOL									BuildAlphaMaps(CRuSplatIDMap *splatIDMap, BOOL enableFilter = TRUE);

	void									BeginBuildSplatMap(IRuTexture *targetTexture);
	BOOL									BuildSplatMap(CRuSplatIDMap *splatIDMap, IRuTexture *targetTexture, IRuTexture **sourceTextures);
	BOOL									BuildSplatMap(CRuSplatIDMap *splatIDMap, const CRuRectangle *targetRect, IRuTexture *targetTexture, IRuTexture **sourceTextures, float alphaFalloff = 0.5f);
	BOOL									BuildSplatMap(INT32 numSplatIDMaps, CRuSplatIDMap **splatIDMaps, const CRuRectangle *targetRects, IRuTexture *targetTexture, IRuTexture **sourceTextures, float alphaFalloff);
};

// ************************************************************************************************************************************************************

class CRuSplatMapManager : public IRuObject
{
	ruRTTI_DECLARE
	ruHEAP_DECLARE
	ruCLASSGUID_DECLARE

protected:
	CRuSplatMapBuilder*						m_mapBuilder;

	INT32									m_idMapDimension;

	// Management
	INT32									m_baseTextureResolution;
	CRuStack<IRuTexture *>					m_recycledBaseTextures;
	CRuLRUQueue<DWORD, IRuTexture *>*		m_baseTextureQueue;					// LRU queue of base textures, hashed on chunk key


	INT32									m_detailTextureResolution;
	CRuStack<IRuTexture *>					m_recycledDetailTextures;
	CRuLRUQueueEx<DWORD, DWORD, IRuTexture *>*		m_detailTextureQueue;		// LRU queue of detail textures, hashed on chunk key and sub chunk key

public:
											CRuSplatMapManager();
	virtual									~CRuSplatMapManager();

	BOOL									Initialize(INT32 idMapDimension, INT32 numBaseTextures, INT32 numDetailTextures, INT32 baseTextureResolution, INT32 detailTextureResolution);

	BOOL									BuildAlphaMaps(CRuSplatIDMap *splatIDMap);

	// Texture retrieval
	IRuTexture*								GetBaseTexture(DWORD chunkKey, INT32 numSubChunkGrids, CRuSplatIDMap **idMaps, IRuTexture **sourceTextures);
	IRuTexture*								GetDetailTexture(DWORD chunkKey, DWORD subChunkKey, CRuSplatIDMap *idMap, IRuTexture **sourceTextures);

	void									EvictCachedTexture(DWORD chunkKey);

protected:
	void									ClearBaseTextureCache();
	void									ClearDetailTextureCache();

	static BOOL								ClearBaseTextureCacheCallback(const void *key, const void *data, void *userData);
	static BOOL								ClearDetailTextureCacheCallback(const void *key, const void *data, void *userData);

	static BOOL								EvictCachedTextureCallback(const void *key, const void *data, void *userData);
};

// ************************************************************************************************************************************************************

class CRuTextureList : public IRuObject
{
	ruRTTI_DECLARE
	ruHEAP_DECLARE
	ruCLASSGUID_DECLARE

protected:
	CRuArrayList<BOOL>						m_textureDirtyList;
	CRuArrayList<IRuBaseTexture *>			m_textureList;

public:
											CRuTextureList();
	virtual									~CRuTextureList();

	// IRuObject serialization
	virtual BOOL							SerializeFrom(IRuStream *stream);
	virtual BOOL							SerializeTo(IRuStream *stream);

	// Texture management
	INT32									AddTexture(IRuBaseTexture *texture);
	INT32									GetNumTextures() const;
	IRuBaseTexture*							GetTexture(INT32 index);
	BOOL									SetTexture(INT32 index, IRuBaseTexture *texture);

	void									ClearTextureDirty();
	void									SetTextureDirty();
	BOOL									IsTextureDirty(INT32 index) const;

	IRuBaseTexture**						GetTextureArray() { return m_textureList.GetArray(); }
};

// ************************************************************************************************************************************************************

class CRuWaterBodyDescriptor : public IRuObject
{
	ruRTTI_DECLARE
	ruHEAP_DECLARE
	ruCLASSGUID_DECLARE

public:
	// Identifying data
	CRuGUID									m_guid;											//!< Water body GUID

	// Water settings
	RuWaterEffectType						m_effectType;									//!< Water effect type

	REAL									m_reflectionFactor;								//!< Water reflection factor
	REAL									m_refractionFactor;								//!< Water refraction factor
	REAL									m_alphaFactor;									//!< Water alpha factor
	REAL									m_baseFactor;									//!< Base map factor
	REAL									m_specularStrength;								//!< Specular strength

	RUCOLOR									m_baseColor;									//!< Water base color

	REAL									m_uvScale;										//!< UV scale
	INT32									m_baseMapFrames;								//!< Number of frames for the base map texture
	char*									m_baseMapName;									//!< Base map name, frames are built as follows: [m_baseMapName] + "_" + [frame index]
	INT32									m_normalMapFrames;								//!< Number of frames for the normal map texture
	char*									m_normalMapName;								//!< Normal map name, frames are built as follows: [m_normalMapName] + "_" + [frame index]
	INT32									m_cubeMapFrames;								//!< Number of frames for the cube map texture
	char*									m_cubeMapName;									//!< Cube map name, frames are built as follows: [m_normalMapName] + "_" + [frame index]

	// Runtime
	BOOL									m_isDirty;										//!< The record is dirty
	CRuMaterial*							m_waterMaterial;								//!< Instanced material
	CRuController_MaterialTexture*			m_baseController;								//!< Controller that alters water base texture
	CRuController_MaterialTexture*			m_causticsController;							//!< Controller that alters water caustics texture

public:
											CRuWaterBodyDescriptor();
	virtual									~CRuWaterBodyDescriptor();

	// IRuObject serialization
	virtual BOOL							SerializeFrom(IRuStream *stream);
	virtual BOOL							SerializeTo(IRuStream *stream);

	void									Update(REAL elapsedTime);

	CRuMaterial*							GetWaterBodyMaterial();

	void									BuildWaterBodyMaterial();
};

// ************************************************************************************************************************************************************

extern INT32								RuTerrainUtility_BuildGridIndices(INT32 chunkDimension, INT32 gridDimension, INT32 vertexGap, INT32 stride, BYTE *vertexFlags, UINT16 *indicesOut, INT32 *numMainBodyIndices = NULL, BOOL ignoreSkirt = FALSE);

// ************************************************************************************************************************************************************

ruOBJECTMODULE_DEFINE(CRuObjectModule_SplatIDMap, CRuSplatIDMap)

// ************************************************************************************************************************************************************

#pragma managed

#endif
