//Anything above this #include will be ignored by the compiler
#include "../qcommon/exe_headers.h"

#include "../qcommon/cm_local.h"
#include "../cgame/tr_types.h"
#include "RM_Headers.h"

//#include "../qcommon/q_imath.h"

#pragma optimize("", off)

void R_LoadDataImage	( const char *name, byte **pic, int *width, int *height);
void R_InvertImage		( byte *data, int width, int height, int depth);
void R_Resample			( byte *source, int swidth, int sheight, byte *dest, int dwidth, int dheight, int components);
void RE_GetModelBounds	(refEntity_t *refEnt, vec3_t bounds1, vec3_t bounds2);

static CRMLandScape		*rm_landscape;
static CCMLandScape		*origin_land;

CRMLandScape::CRMLandScape(void)
{
	common = NULL;
	mDensityMap = NULL;
}

CRMLandScape::~CRMLandScape(void)
{
	if(mDensityMap)
	{
		Z_Free(mDensityMap);
		mDensityMap = NULL;
	}
}

void CCGHeightDetails::AddModel(const CRandomModel *hd)
{
	if(mNumModels < MAX_RANDOM_MODELS)
	{
		mTotalFrequency += hd->GetFrequency();
		mModels[mNumModels++] = *hd;
	}
}

void CRMLandScape::AddModel(const int height, int maxheight, const CRandomModel *hd)
{
	int		i;

	if(maxheight > HEIGHT_RESOLUTION)
	{
		maxheight = HEIGHT_RESOLUTION;
	}

	for(i = height; hd->GetModel() && (i < maxheight); i++)
	{
		mHeightDetails[i].AddModel(hd);
	}
}

void CRMLandScape::LoadMiscentDef(const char *td)
{
	char				miscentDef[MAX_QPATH];
	CGenericParser2		parse;
	CGPGroup			*basegroup, *classes, *items, *model;
	CGPValue			*pair;

	Com_sprintf(miscentDef, MAX_QPATH, "ext_data/RMG/%s.miscents", Info_ValueForKey(td, "miscentDef"));
	Com_DPrintf("CG_Terrain: Loading and parsing miscentDef %s.....\n", Info_ValueForKey(td, "miscentDef"));

	if(!Com_ParseTextFile(miscentDef, parse))
	{
		Com_sprintf(miscentDef, MAX_QPATH, "ext_data/arioche/%s.miscents", Info_ValueForKey(td, "miscentDef"));
		if(!Com_ParseTextFile(miscentDef, parse))
		{
			Com_Printf("Could not open %s\n", miscentDef);
			return;
		}
	}
	// The whole file....
	basegroup = parse.GetBaseParseGroup();

	// The root { } struct
	classes = basegroup->GetSubGroups();
	while(classes)
	{
		items = classes->GetSubGroups();
		while(items)
		{
			if(!stricmp(items->GetName(), "miscent"))
			{
				int			height, maxheight;

				// Height must exist - the rest are optional
				height = atol(items->FindPairValue("height", "0"));
				maxheight = atol(items->FindPairValue("maxheight", "255"));

				model = items->GetSubGroups();
				while(model)
				{
					if(!stricmp(model->GetName(), "model"))
					{
						CRandomModel	hd;

						// Set defaults
						hd.SetModel("");
						hd.SetFrequency(1.0f);
						hd.SetMinScale(1.0f);
						hd.SetMaxScale(1.0f);

						pair = model->GetPairs();
						while(pair)
						{
							if(!stricmp(pair->GetName(), "name"))
							{
								hd.SetModel(pair->GetTopValue());
							}
							else if(!stricmp(pair->GetName(), "frequency"))
							{
								hd.SetFrequency((float)atof(pair->GetTopValue()));
							}
							else if(!stricmp(pair->GetName(), "minscale"))
							{
								hd.SetMinScale((float)atof(pair->GetTopValue()));
							}
							else if(!stricmp(pair->GetName(), "maxscale"))
							{
								hd.SetMaxScale((float)atof(pair->GetTopValue()));
							}
							pair = (CGPValue *)pair->GetNext();
						}
						AddModel(height, maxheight, &hd);
					}
 					model = (CGPGroup *)model->GetNext();
				}
			}
			items = (CGPGroup *)items->GetNext();
		}
		classes = (CGPGroup *)classes->GetNext();
	}
	Com_ParseTextFileDestroy(parse);
}

void CG_Decrease(byte *work, float lerp, int *info)
{
	int		val;

	val = *work - origin_land->irand(2, 5);
	*work = (byte)Com_Clampi(1, 255, val);
}

void CRMLandScape::CreateRandomDensityMap(byte *density, int width, int height, int seed)
{
//	int			i, border, inc;
	int			x, y, count;
//	byte		*work, *work2;
	CArea		*area;
	vec3_t		derxelSize, pos;
	ivec3_t		dmappos;
	byte		*hm_map = common->GetHeightMap();
	int			hm_width = common->GetRealWidth();
	int			hm_height = common->GetRealHeight();
	int			xpos, ypos, dx, dy;
	byte		*densityPos = density;
	bool		foundUneven;

	// Init to linear spread
	memset(density, 0, width * height);

/*	// Make more prevalent towards the edges
	border = Com_Clamp(6, 12, (width + height) >> 4);

	for(i = 0; i < border; i++)
	{
		inc = (border - i + 1) * 9;

		// Top line
		work = density + i + (i * width);
		for(x = i; x < width - i; x++, work++)
		{
			*work += (byte)common->irand(inc >> 1, inc);
		}

		// Left and right edges
		work = density + i + ((i + 1) * width);
		work2 = density + (width - i) + ((i + 1) * width);
		for(y = i + 1; y < height - i - 2; y++, work += width, work2 += width)
		{
			*work += (byte)common->irand(inc >> 1, inc);
			*work2 += (byte)common->irand(inc >> 1, inc);
		}

		// Bottom line
		work = density + i + ((height - i - 1) * width);
		for(x = i; x < width - i; x++, work++)
		{
			*work += (byte)common->irand(inc >> 1, inc);
		}
	}
*/
	count = 0;

	for(y=0;y<height;y++)
	{
		for(x=0;x<width;x++,densityPos++)
		{
			xpos = (x * hm_width / width);
			ypos = (y * hm_height / height);
			ypos = hm_height - ypos - 1;

			if (hm_map[ypos*hm_width + xpos] < 150)
			{
				continue;
			}

			foundUneven = false;
			for(dx=-4;(dx<=4 && !foundUneven);dx++)
			{
				for(dy=-4;(dy<=4 && !foundUneven);dy++)
				{
					if (dx == 0 && dy == 0)
					{
						continue;
					}
					if ((xpos+dx) >= 0 && (xpos+dx) < hm_width && (ypos+dy) >= 0 && (ypos+dy) < hm_height)
					{
						if (hm_map[(ypos+dy)*hm_width + (xpos+dx)] < 190)
						{
							*densityPos = 205;
							count++;
							foundUneven = true;
						}
					}
				}
			}
		}
	}

/*	FILE	*FH;

	FH = fopen("c:\o.raw", "wb");
	fwrite(hm_map, 1, common->GetRealWidth() * common->GetRealHeight(), FH);
	fclose(FH);

	FH = fopen("c:\d.raw", "wb");
	fwrite(density, 1, width*height, FH);
	fclose(FH);
*/
	// Reduce severely for any settlements/buildings/objectives
	VectorScale(common->GetSize(), 1.0f / width, derxelSize);

	origin_land = common;
	area = common->GetFirstArea();
	while(area)
	{
		// Skip group types since they encompass to much open area
		if ( area->GetType ( ) == AT_GROUP )
		{
			area = common->GetNextArea();
			continue;
		}

		VectorSubtract(area->GetPosition(), common->GetMins(), pos);
		VectorInverseScaleVector(pos, derxelSize, dmappos);
		// Damn upside down gensurf
		dmappos[1] = height - dmappos[1];

		count = ceilf(area->GetRadius() / derxelSize[1]);

		while(count > 0)
		{
			CM_CircularIterate(density, width, height, dmappos[0], dmappos[1], 0, count, NULL, CG_Decrease);
			count--;
		}
		area = common->GetNextArea();
	}
}

void CRMLandScape::LoadDensityMap(const char *td)
{
	char		densityMap[MAX_QPATH];
	byte		*imageData;
	int			iWidth, iHeight, seed;
	char 		*ptr;

	// Fill in with default values
	mDensityMap = (byte *)Z_Malloc(common->GetBlockCount(), TAG_TERRAIN);
	memset(mDensityMap, 128, common->GetBlockCount());

	// Load in density map (if any)
	Com_sprintf(densityMap, MAX_QPATH, "%s", Info_ValueForKey(td, "densityMap"));
	if(strlen(densityMap))
	{
		Com_DPrintf("CG_Terrain: Loading density map %s.....\n", densityMap);
#ifdef DEDICATED
		imageData = NULL;
#else
		R_LoadDataImage(densityMap, &imageData, &iWidth, &iHeight);
		if(imageData)
		{
			if(strstr(densityMap, "density_"))
			{
				seed = strtoul(Info_ValueForKey(td, "seed"),&ptr,10);
				CreateRandomDensityMap(imageData, iWidth, iHeight, seed);
			}
			R_Resample(imageData, iWidth, iHeight, mDensityMap, common->GetBlockWidth(), common->GetBlockHeight(), 1);
			R_InvertImage(mDensityMap, common->GetBlockWidth(), common->GetBlockHeight(), 1);
			Z_Free(imageData);
		}
#endif
	}
}

CRandomModel *CCGHeightDetails::GetRandomModel(CCMLandScape *land)
{
	int		seek, i;

	seek = land->irand(0, mTotalFrequency);
	for(i = 0; i < mNumModels; i++)
	{
		seek -= mModels[i].GetFrequency();
		if(seek <= 0)
		{
			return(mModels + i);
		}
	}
	assert(0);
	return(NULL);
}
#ifndef DEDICATED
void CRMLandScape::Sprinkle(CCMPatch *patch, CCGHeightDetails *hd, int level)
{
	int				i, count, px, py;
	float			density;
	vec3_t			origin, scale, angles, bounds[2];
	refEntity_t		refEnt;
	CRandomModel	*rm;
	CArea			area;
//	int				areaTypes[] = { AT_BSP, AT_OBJECTIVE };
	TCGMiscEnt		*data = (TCGMiscEnt *)cl.mSharedMemory;
	TCGTrace		*td = (TCGTrace *)cl.mSharedMemory;

//	memset(&refEnt, 0, sizeof(refEntity_t));

	px = patch->GetHeightMapX() / common->GetTerxels();
	py = patch->GetHeightMapY() / common->GetTerxels();
	// Get a number -5.3f to 5.3f
	density = (mDensityMap[px + (common->GetBlockWidth() * py)] - 128) / 24.0f;
	// ..and multiply that into the count
	count = Round(common->GetPatchScalarSize() * hd->GetAverageFrequency() * powf(2.0f, density) * 0.001);

	for(i = 0; i < count; i++)
	{
		if(!common->irand(0, 10))
		{
			vec3_t temp;
			float  average;

			rm = hd->GetRandomModel(common);

			refEnt.hModel = re.RegisterModel(rm->GetModelName());
			refEnt.frame = 0;
			RE_GetModelBounds(&refEnt, bounds[0], bounds[1]);

			// Calculate the scale using some magic to help ensure that the
			// scales are never too different from eachother.  Otherwise you
			// could get an entity that is really small on one axis but huge 
			// on another.
			temp[0] = common->flrand(rm->GetMinScale(), rm->GetMaxScale());
			temp[1] = common->flrand(rm->GetMinScale(), rm->GetMaxScale());
			temp[2] = common->flrand(rm->GetMinScale(), rm->GetMaxScale());

			// Average of the three random numbers and divide that by two
			average = ( ( temp[0] + temp[1] + temp[2] ) / 3) / 2;

			// Add in half of the other two numbers and then subtract half the average to prevent.
			// any number from going beyond the range. If all three numbers were the same then
			// they would remain unchanged after this calculation.
			scale[0] = temp[0] + (temp[1]+temp[2]) / 2 - average;
			scale[1] = temp[1] + (temp[0]+temp[2]) / 2 - average;
			scale[2] = temp[2] + (temp[0]+temp[1]) / 2 - average;

			angles[0] = 0.0f;
			angles[1] = common->flrand(-M_PI, M_PI);
			angles[2] = 0.0f;

			VectorCopy(patch->GetMins(), origin);
			origin[0] += common->flrand(0.0f, common->GetPatchWidth());
			origin[1] += common->flrand(0.0f, common->GetPatchHeight());
			// Get above world height
			float slope = common->GetWorldHeight(origin, bounds, true);

			if (slope > 1.33)
			{	// spot has too steep of a slope
				continue;
			}
			if(origin[2] < common->GetWaterHeight())
			{
				continue;
			}
			// very that we aren't dropped too low
			if (origin[2] < common->CalcWorldHeight(level))
			{
				continue;
			}

			// Hack-ariffic, don't allow them to drop below the big player clip brush.
			if (origin[2] < 1280 )
			{
				continue;
			}
			// FIXME: shouldn't be using a hard-coded 1280 number, only allow to spawn if inside player clip brush? 
	//		if( !(CONTENTS_PLAYERCLIP & VM_Call( cgvm, CG_POINT_CONTENTS )) )
	//		{
	//			continue;
	//		}
			// Simple radius check for buildings
/*			area.Init(origin, VectorLength(bounds[0]));
			if(common->AreaCollision(&area, areaTypes, sizeof(areaTypes) / sizeof(int)))
			{
				continue;
			}*/
			// Make sure there is no architecture around - doesn't work for ents though =(

			memset(td, sizeof(*td), 0);
			VectorCopy(origin, td->mStart);
			VectorCopy(bounds[0], td->mMins);
			VectorCopy(bounds[1], td->mMaxs);
			VectorCopy(origin, td->mEnd);
			td->mSkipNumber = -1;
			td->mMask = MASK_PLAYERSOLID;

			VM_Call( cgvm, CG_TRACE );
			if(td->mResult.surfaceFlags & SURF_NOMISCENTS)
			{
				continue;
			}
			if(td->mResult.startsolid)
			{
//				continue;
			}
			// Get minimum height of area
			common->GetWorldHeight(origin, bounds, false);
			// Account for relative origin
			origin[2] -= bounds[0][2] * scale[2];
			origin[2] -= common->flrand(2.0, (bounds[1][2] - bounds[0][2]) / 4);
			
			// Spawn the client model
			strcpy(data->mModel, rm->GetModelName());
			VectorCopy(origin, data->mOrigin);
			VectorCopy(angles, data->mAngles);
			VectorCopy(scale, data->mScale);
			VM_Call( cgvm, CG_MISC_ENT);
			mModelCount++;
		}
	}
}
#endif // !DEDICATED

void CRMLandScape::SpawnPatchModels(CCMPatch *patch)
{
#ifndef DEDICATED
	int					i;
	CCGHeightDetails	*hd;

//	Rand_Init(10);
	for(i = 0; i < 4; i++)
	{
		hd = mHeightDetails + patch->GetHeight(i);
		if(hd->GetNumModels())
		{
			Sprinkle(patch, hd, patch->GetHeight(i));
		}
	}
#endif // !DEDICATED
}

void SpawnPatchModelsWrapper(CCMPatch *patch, void *userdata)
{
	CRMLandScape *landscape = (CRMLandScape *)userdata;
	landscape->SpawnPatchModels(patch);
}

void RM_CreateRandomModels(int terrainId, const char *terrainInfo)
{
	CRMLandScape	*landscape;

	landscape = rm_landscape = new CRMLandScape;
	landscape->SetCommon(cmg.landScape);

	Com_DPrintf("CG_Terrain: Creating random models.....\n");
	landscape->LoadMiscentDef(terrainInfo);
	landscape->LoadDensityMap(terrainInfo);
	landscape->ClearModelCount();
	CM_TerrainPatchIterate(landscape->GetCommon(), SpawnPatchModelsWrapper, landscape);

	Com_DPrintf(".....%d random client models spawned\n", landscape->GetModelCount());
}

void RM_InitTerrain(void)
{
	rm_landscape = NULL;
}

void RM_ShutdownTerrain(void)
{
	CRMLandScape 	*landscape;

	landscape = rm_landscape;
	if(landscape)
	{
		delete landscape;
		rm_landscape = NULL;
	}
}

// end

#pragma optimize("", on)