#include "../server/exe_headers.h"

#include "cm_local.h"
#include "cm_patch.h"
#include "cm_landscape.h"
#include "../game/genericparser2.h"
#include "cm_randomterrain.h"


#define NOISE_SIZE			256
#define NOISE_MASK			(NOISE_SIZE - 1)

static float					noiseTable[NOISE_SIZE];
static int						noisePerm[NOISE_SIZE];

static void CM_NoiseInit( CCMLandScape *landscape )
{
	int		i;

	for ( i = 0; i < NOISE_SIZE; i++ )
	{
		noiseTable[i] = landscape->flrand(-1.0f, 1.0f);
		noisePerm[i] = (byte)landscape->irand(0, 255);
	}
}

#define VAL( a ) noisePerm[ ( a ) & ( NOISE_MASK )]
#define INDEX( x, y, z, t ) VAL( x + VAL( y + VAL( z + VAL( t ) ) ) )

#define LERP( a, b, w ) ( a * ( 1.0f - w ) + b * w )

static float GetNoiseValue( int x, int y, int z, int t )
{
	int index = INDEX( ( int ) x, ( int ) y, ( int ) z, ( int ) t );

	return noiseTable[index];
}

#if 0
static float GetNoiseTime( int t )
{
	int index = VAL( t );

	return (1 + noiseTable[index]);
}
#endif

static float CM_NoiseGet4f( float x, float y, float z, float t )
{
	int i;
	int ix, iy, iz, it;
	float fx, fy, fz, ft;
	float front[4];
	float back[4];
	float fvalue, bvalue, value[2], finalvalue;

	ix = ( int ) floor( x );
	fx = x - ix;
	iy = ( int ) floor( y );
	fy = y - iy;
	iz = ( int ) floor( z );
	fz = z - iz;
	it = ( int ) floor( t );
	ft = t - it;

	for ( i = 0; i < 2; i++ )
	{
		front[0] = GetNoiseValue( ix, iy, iz, it + i );
		front[1] = GetNoiseValue( ix+1, iy, iz, it + i );
		front[2] = GetNoiseValue( ix, iy+1, iz, it + i );
		front[3] = GetNoiseValue( ix+1, iy+1, iz, it + i );

		back[0] = GetNoiseValue( ix, iy, iz + 1, it + i );
		back[1] = GetNoiseValue( ix+1, iy, iz + 1, it + i );
		back[2] = GetNoiseValue( ix, iy+1, iz + 1, it + i );
		back[3] = GetNoiseValue( ix+1, iy+1, iz + 1, it + i );

		fvalue = LERP( LERP( front[0], front[1], fx ), LERP( front[2], front[3], fx ), fy );
		bvalue = LERP( LERP( back[0], back[1], fx ), LERP( back[2], back[3], fx ), fy );

		value[i] = LERP( fvalue, bvalue, fz );
	}

	finalvalue = LERP( value[0], value[1], ft );
	return finalvalue;
}








/****** lincrv.c ******/
/* Ken Shoemake, 1994 */

/* Perform a generic vector unary operation. */
#define V_Op(vdst,gets,vsrc,n) {register int V_i;\
    for(V_i=(n)-1;V_i>=0;V_i--) (vdst)[V_i] gets ((vsrc)[V_i]);}

static void lerp(float t, float a0, float a1, vec4_t p0, vec4_t p1, int m, vec4_t p)
{
    register float t0=(a1-t)/(a1-a0), t1=1-t0;
    register int i;
    for (i=m-1; i>=0; i--) p[i] = t0*p0[i] + t1*p1[i];
}

/* DialASpline(t,a,p,m,n,work,Cn,interp,val) computes a point val at parameter
    t on a spline with knot values a and control points p. The curve will have
    Cn continuity, and if interp is TRUE it will interpolate the control points.
    Possibilities include Langrange interpolants, Bezier curves, Catmull-Rom
    interpolating splines, and B-spline curves. Points have m coordinates, and
    n+1 of them are provided. The work array must have room for n+1 points.
 */
static int DialASpline(float t, float a[], vec4_t p[], int m, int n, vec4_t work[],
                    unsigned int Cn, bool interp, vec4_t val)
{
    register int i, j, k, h, lo, hi;

    if (Cn>n-1) Cn = n-1;       /* Anything greater gives one polynomial */
    for (k=0; t> a[k]; k++);    /* Find enclosing knot interval */
    for (h=k; t==a[k]; k++);    /* May want to use fewer legs */
    if (k>n) {k = n; if (h>k) h = k;}
    h = 1+Cn - (k-h); k--;
    lo = k-Cn; hi = k+1+Cn;

    if (interp) {               /* Lagrange interpolation steps */
        int drop=0;
        if (lo<0) {lo = 0; drop += Cn-k;
                   if (hi-lo<Cn) {drop += Cn-hi; hi = Cn;}}
        if (hi>n) {hi = n; drop += k+1+Cn-n;
                   if (hi-lo<Cn) {drop += lo-(n-Cn); lo = n-Cn;}}
        for (i=lo; i<=hi; i++) V_Op(work[i],=,p[i],m);
        for (j=1; j<=Cn; j++) {
            for (i=lo; i<=hi-j; i++) {
                lerp(t,a[i],a[i+j],work[i],work[i+1],m,work[i]);
            }
        }
        h = 1+Cn-drop;
    } else {                    /* Prepare for B-spline steps */
        if (lo<0) {h += lo; lo = 0;}
        for (i=lo; i<=lo+h; i++) V_Op(work[i],=,p[i],m);
        if (h<0) h = 0;
    }
    for (j=0; j<h; j++) {
        int tmp = 1+Cn-j;
        for (i=h-1; i>=j; i--) {
            lerp(t,a[lo+i],a[lo+i+tmp],work[lo+i],work[lo+i+1],m,work[lo+i+1]);
        }
    }
    V_Op(val,=,work[lo+h],m);
    return (k);
}

#define BIG (1.0e12)

static vec_t Vector2Normalize( vec2_t v ) 
{
	float	length, ilength;

	length = v[0]*v[0] + v[1]*v[1];
	length = sqrt (length);

	if ( length ) 
	{
		ilength = 1/length;
		v[0] *= ilength;
		v[1] *= ilength;
	}
		
	return length;
}

CPathInfo::CPathInfo(CCMLandScape *landscape, int numPoints, float bx, float by, float ex, float ey, 
					 float minWidth, float maxWidth, float depth, float deviation, float breadth,
					 CPathInfo *Connected, unsigned CreationFlags) :
	mNumPoints(numPoints),
	mMinWidth(minWidth),
	mMaxWidth(maxWidth),
	mDepth(depth),
	mDeviation(deviation),
	mBreadth(breadth)
{
	int		i, numConnected, index;
	float	position, goal, deltaGoal;
//	float	random, delta;
	bool	horizontal;
	float	*point;
	float	currentWidth;
	float	currentPosition;
	vec2_t	testPoint, percPoint, diffPoint, normalizedPath;
	float	distance, length;
	
	CreateCircle();

	numConnected = -1;
	if (Connected)
	{	// we are connecting to an existing spline
		numConnected = Connected->GetNumPoints();
		if (numConnected >= SPLINE_MERGE_SIZE)
		{	// plenty of points to choose from
			mNumPoints += SPLINE_MERGE_SIZE;
		}
		else
		{	// the existing spline doesn't have enough points
			mNumPoints += numConnected;
		}
	}

	mPoints = (vec4_t *)malloc(sizeof(vec4_t) * mNumPoints);
	mWork = (vec4_t *)malloc(sizeof(vec4_t) * (mNumPoints+1));
	mWeights = (vec_t *)malloc(sizeof(vec_t) * (mNumPoints+1));

	length = sqrt((ex-bx)*(ex-bx) + (ey-by)*(ey-by));
	if (fabs(ex - bx) >= fabs(ey - by))
	{	// this appears to be a horizontal path
		mInc = 1.0 / fabs(ex - bx);
		horizontal = true;
		position = by;
		goal = ey;
		deltaGoal = (ey-by) / (numPoints-1);
	}
	else
	{	// this appears to be a vertical path
		mInc = 1.0 / fabs(ey - by);
		horizontal = false;
		position = bx;
		goal = ex;
		deltaGoal = (ex-bx) / (numPoints-1);
	}
	normalizedPath[0] = (ex-bx);
	normalizedPath[1] = (ey-by);
	Vector2Normalize(normalizedPath);
	// approx calculate how much we need to iterate through the spline to hit every point
	mInc /= 16;

	currentWidth = landscape->flrand(minWidth, maxWidth);
	currentPosition = 0.0;

	for(i=0;i<mNumPoints;i++)
	{
		// weights are evenly distributed
		mWeights[i] = (float)i / (mNumPoints-1);

		if (i < numConnected && i < SPLINE_MERGE_SIZE)
		{	// we are connecting to an existing spline, so copy over the first few points
			if (CreationFlags & PATH_CREATION_CONNECT_FRONT)
			{	// copy from the front
				index = i;
			}
			else
			{	// copy from the end
				index = numConnected-SPLINE_MERGE_SIZE+i;
			}
			point = Connected->GetPoint(index);
			mPoints[i][0] = point[0];
			mPoints[i][1] = point[1];
			mPoints[i][3] = point[3];
		}
		else
		{
			if (horizontal)
			{	// we appear to be going horizontal, so spread the randomness across the vertical
				mPoints[i][0] = ((ex - bx) * currentPosition) + bx;
				mPoints[i][1] = position;
			}
			else
			{	// we appear to be going vertical, so spread the randomness across the horizontal
				mPoints[i][0] = position;
				mPoints[i][1] = ((ey - by) * currentPosition) + by;
			}
			currentPosition += 1.0 / (numPoints-1);

			// set the width of the spline
			mPoints[i][3] = currentWidth;
			currentWidth += landscape->flrand(-0.1f, 0.1f);
			if (currentWidth < minWidth)
			{
				currentWidth = minWidth;
			}
			else if (currentWidth > maxWidth)
			{
				currentWidth = maxWidth;
			}

			// see how far we are from the goal
/*			delta = (goal - position) * currentPosition;
			// calculate the randomness we are allowed at this place
			random = landscape->flrand(-mDeviation/1.0, mDeviation/1.0) * (1.0 - currentPosition);
			position += delta + random;*/

			if (i == mNumPoints-2)
			{	// -2 because we are calculating for the next point
				position = goal;
			}
			else
			{
				if (i == 0)
				{
					position += deltaGoal + landscape->flrand(-mDeviation/10.0, mDeviation/10.0);
				}
				else
				{
					position += deltaGoal + landscape->flrand(-mDeviation*1.5, mDeviation*1.5);
				}
			}


			if (position > 0.9)
			{	// too far over, so move back a bit
				position = 0.9 - landscape->flrand(0.02f, 0.1f);
			}
			if (position < 0.1)
			{	// too near, so move bakc a bit
				position = 0.1 + landscape->flrand(0.02f, 0.1f);
			}

			// check our deviation from the straight line to the end
			if (horizontal)
			{
				testPoint[0] = ((ex - bx) * currentPosition) + bx;
				testPoint[1] = position;
			}
			else
			{
				testPoint[0] = position;
				testPoint[1] = ((ey - by) * currentPosition) + by;
			}
			// dot product of the normal of the path to the point we are at
			distance = ((testPoint[0]-bx)*normalizedPath[0]) + ((testPoint[1]-by)*normalizedPath[1]);
			// find the perpendicular place that is intersected by the point and the path
			percPoint[0] = (distance * normalizedPath[0]) + bx;
			percPoint[1] = (distance * normalizedPath[1]) + by;
			// calculate the difference between the perpendicular point and the test point
			diffPoint[0] = testPoint[0] - percPoint[0];
			diffPoint[1] = testPoint[1] - percPoint[1];
			// calculate the distance
			distance = sqrt((diffPoint[0]*diffPoint[0]) + (diffPoint[1]*diffPoint[1]));
			if (distance > mDeviation)
			{	// we are beyond our allowed deviation, so head back
				if (horizontal)
				{
					position = (ey-by) * currentPosition + by;
				}
				else
				{
					position = (ex-bx) * currentPosition + bx;
				}
				position += landscape->flrand(-mDeviation/2.0, mDeviation/2.0);
			}
		}
	}
	mWeights[mNumPoints] = (float)BIG;
}

CPathInfo::~CPathInfo(void)
{
	free(mWeights);
	free(mWork);
	free(mPoints);
}

void CPathInfo::CreateCircle(void)
{
	int		x, y;
	float	r, d;

	memset(mCircleStamp, 0, sizeof(mCircleStamp));
	r = CIRCLE_STAMP_SIZE;
	for(x=0;x<CIRCLE_STAMP_SIZE;x++)
	{
		for(y=0;y<CIRCLE_STAMP_SIZE;y++)
		{
			d = sqrt((float)(x*x + y*y));
			if (d > r)
			{
				mCircleStamp[y][x] = 255;
			}
			else
			{
				mCircleStamp[y][x] = pow(sin((float)(d / r * M_PI / 2)), mBreadth) * 255;
			}
		}
	}
}

void CPathInfo::Stamp(int x, int y, int size, int depth, unsigned char *Data, int DataWidth, int DataHeight)
{
//	int xPos;
//	float yPos;
	int		dx, dy, fx, fy;
	float	offset;
	byte	value;
	byte	invDepth;

	offset = (float)(CIRCLE_STAMP_SIZE-1) / size;
	invDepth = 255-depth;

	for(dx = -size; dx <= size; dx++)
	{
		for ( dy = -size; dy <= size; dy ++ )
		{
			float d;

			d = dx * dx + dy * dy ;
			if ( d > size * size )
			{
				continue;
			}

			fx = x + dx;
			if (fx < 2 || fx > DataWidth-2)
			{
				continue;
			}
			
			fy = y + dy;
			if (fy < 2 || fy > DataHeight-2)
			{
				continue;
			}

			value = pow ( sin ( (float)(d / (size * size) * M_PI / 2)), mBreadth ) * invDepth + depth;
			if (value < Data[(fy * DataWidth) + fx])
			{
				Data[(fy * DataWidth) + fx] = value;
			}
		}
	}
/*

		fx = x + dx;
		if (fx < 2 || fx > DataWidth-2)
		{
			continue;
		}
		xPos = abs((int)(dx*offset));
		yPos = offset*size + offset;
		for(dy = -size; dy < 0; dy++)
		{
			yPos -= offset;
			fy = y + dy;
			if (fy < 2 || fy > DataHeight-2)
			{
				continue;
			}

			value = (invDepth * mCircleStamp[(int)yPos][xPos] / 256) + depth;
			if (value < Data[(fy * DataWidth) + fx])
			{
				Data[(fy * DataWidth) + fx] = value;
			}
		}

		yPos = -offset;
		for(; dy <= size; dy++)
		{
			yPos += offset;

			fy = y + dy;
			if (fy < 2 || fy > DataHeight-2)
			{
				continue;
			}

			value = (invDepth * mCircleStamp[(int)yPos][xPos] / 256) + depth;
			if (value < Data[(fy * DataWidth) + fx])
			{
				Data[(fy * DataWidth) + fx] = value;
			}
		}
	}
*/
}

void CPathInfo::GetInfo(float PercentInto, vec4_t Coord, vec4_t Vector)
{
	vec4_t	before, after;
	float	testPercent;

	DialASpline(PercentInto, mWeights, mPoints, sizeof(vec4_t) / sizeof(vec_t), mNumPoints-1, mWork, 2, true, Coord);

	testPercent = PercentInto - 0.01;
	if (testPercent < 0)
	{
		testPercent = 0;
	}
	DialASpline(testPercent, mWeights, mPoints, sizeof(vec4_t) / sizeof(vec_t), mNumPoints-1, mWork, 2, true, before);

	testPercent = PercentInto + 0.01;
	if (testPercent > 1.0)
	{
		testPercent = 1.0;
	}
	DialASpline(testPercent, mWeights, mPoints, sizeof(vec4_t) / sizeof(vec_t), mNumPoints-1, mWork, 2, true, after);

	Coord[2] = mDepth;

	Vector[0] = after[0] - before[0];
	Vector[1] = after[1] - before[1];
}

void CPathInfo::DrawPath(unsigned char *Data, int DataWidth, int DataHeight )
{
	float			t;
	vec4_t			val, vector;//, perp;
	int				size;
	float			inc;
	int				x, y, lastX, lastY;
	float			depth;

	inc = mInc / DataWidth;

	lastX = lastY = -999;

	for (t=0.0; t<=1.0; t+=inc) 
	{
		GetInfo(t, val, vector);

/*		perp[0] = -vector[1];
		perp[1] = vector[0];

		if (fabs(perp[0]) > fabs(perp[1]))
		{
			perp[1] /= fabs(perp[0]);
			perp[0] /= fabs(perp[0]);
		}
		else
		{
			perp[0] /= fabs(perp[1]);
			perp[1] /= fabs(perp[1]);
		}
*/
		x = val[0] * DataWidth;
		y = val[1] * DataHeight;

		if (x == lastX && y == lastY)
		{
			continue;
		}

		lastX = x;
		lastY = y;

		size = val[3] * DataWidth;

		depth = mDepth * 255.0f;

		Stamp(x, y, size, (int)depth, Data, DataWidth, DataHeight);
	}
}







CRandomTerrain::CRandomTerrain(void)
{
	memset(mPaths, 0, sizeof(mPaths));
}

CRandomTerrain::~CRandomTerrain(void)
{
	Shutdown();
}

void CRandomTerrain::Init(CCMLandScape *landscape, byte *grid, int width, int height)
{
	Shutdown();
	mLandScape = landscape;
	mWidth = width;
	mHeight = height;
	mArea = mWidth * mHeight;
	mBorder = (width + height) >> 6;
	mGrid = grid;
}

void CRandomTerrain::ClearPaths(void)
{
	int	i;

	for(i=0;i<MAX_RANDOM_PATHS;i++)
	{
		if (mPaths[i])
		{
			delete mPaths[i];
			mPaths[i] = 0;
		}
	}

	memset(mPaths, 0, sizeof(mPaths));
}

void CRandomTerrain::Shutdown(void)
{
	ClearPaths ( );
}

bool CRandomTerrain::CreatePath(int PathID, int ConnectedID, unsigned CreationFlags, int numPoints, 
					float bx, float by, float ex, float ey, 
					float minWidth, float maxWidth, float depth, float deviation, float breadth )
{
	CPathInfo	*connected = 0;

	if (PathID < 0 || PathID >= MAX_RANDOM_PATHS || mPaths[PathID])
	{
		return false;
	}

	if (ConnectedID >= 0 && ConnectedID < MAX_RANDOM_PATHS)
	{
		connected = mPaths[ConnectedID];
	}

	mPaths[PathID] = new CPathInfo(mLandScape, numPoints, bx, by, ex, ey, 
		minWidth, maxWidth, depth, deviation, breadth,
		connected, CreationFlags );

	return true;
}

bool CRandomTerrain::GetPathInfo(int PathNum, float PercentInto, vec4_t Coord, vec4_t Vector)
{
	if (PathNum < 0 || PathNum >= MAX_RANDOM_PATHS || !mPaths[PathNum])
	{
		return false;
	}

	mPaths[PathNum]->GetInfo(PercentInto, Coord, Vector);

	return true;
}

void CRandomTerrain::ParseGenerate(const char *GenerateFile)
{
}

void CRandomTerrain::Smooth ( void )
{
	// Scale down to 1/4 size then back up to smooth out the terrain
	byte	*temp;
	int		x, y, o;

	temp = mLandScape->GetFlattenMap ( );

	// Copy over anything in the flatten map
	if (temp) 
	{
		for ( o = 0; o < mHeight * mWidth; o++)
		{
			if ( temp[o] > 0 )
			{
				mGrid[o] = (byte)temp[o] & 0x7F;
			}
		}
	}
	temp = (byte *)Z_Malloc(mWidth * mHeight, TAG_TEMP_WORKSPACE, qfalse);
#if 1
	unsigned	total, count;
	for(x=1;x<mWidth-1;x++)
	{
		for(y=1;y<mHeight-1;y++)
		{
			total = 0;
			count = 2;

			// Left
			total += mGrid[((y)*mWidth)+(x-1)];
			count++;

			// Right
			total += mGrid[((y)*mWidth)+(x+1)];
			count++;

			// Up
			total += mGrid[((y-1)*mWidth)+(x)];
			count++;

			// Down
			total += mGrid[((y+1)*mWidth)+(x)];
			count++;

			// Up-Left
			total += mGrid[((y-1)*mWidth)+(x-1)];
			count++;

			// Down-Left
			total += mGrid[((y+1)*mWidth)+(x-1)];
			count++;

			// Up-Right
			total += mGrid[((y-1)*mWidth)+(x+1)];
			count++;

			// Down-Right
			total += mGrid[((y+1)*mWidth)+(x+1)];
			count++;

			total += (unsigned)mGrid[((y)*mWidth)+(x)] * 2;

			temp[((y)*mWidth)+(x)] = total / count;
		}
	}

	memcpy(mGrid, temp, mWidth * mHeight);

#else
	float	smoothKernel[FILTER_SIZE][FILTER_SIZE];
	int		xx, yy, dx, dy;
	float	total, num;

	R_Resample(mGrid, mWidth, mHeight, temp, mWidth >> 1, mHeight >> 1, 1);
	R_Resample(temp, mWidth >> 1, mHeight >> 1, mGrid, mWidth, mHeight, 1);

	// now lets filter it.
	memcpy(temp, mGrid, mWidth * mHeight);

	for (dy = -KERNEL_SIZE; dy <= KERNEL_SIZE; dy++)
	{
		for (dx = -KERNEL_SIZE; dx <= KERNEL_SIZE; dx++)
		{
			smoothKernel[dy + KERNEL_SIZE][dx + KERNEL_SIZE] =
				1.0f / (1.0f + fabs(float(dx) * float(dx) * float(dx)) + fabs(float(dy) * float(dy) * float(dy)));
		}
	}

	for (y = 0; y < mHeight; y++)
	{
		for (x = 0; x < mWidth; x++)
		{
			total = 0.0f;
			num = 0.0f;
			for (dy = -KERNEL_SIZE; dy <= KERNEL_SIZE; dy++)
			{
				for (dx = -KERNEL_SIZE; dx <= KERNEL_SIZE; dx++)
				{
					xx = x + dx;
					if (xx >= 0 && xx < mWidth)
					{
						yy = y + dy;
						if (yy >= 0 && yy < mHeight)
						{
							total += smoothKernel[dy + KERNEL_SIZE][dx + KERNEL_SIZE] * (float)temp[yy * mWidth + xx];
							num += smoothKernel[dy + KERNEL_SIZE][dx + KERNEL_SIZE];
						}
					}
				}
			}
			total /= num;
			mGrid[y * mWidth + x] = (byte)Com_Clamp(0, 255, (int)Round(total));
		}
	}
#endif

	Z_Free(temp);

/* Uncomment to see the symmetry line on the map

	for ( x = 0; x < mWidth; x ++ )
	{
		mGrid[x * mWidth + x] = 255;
	}
*/
}

void CRandomTerrain::Generate(int symmetric)
{
	int	i,j;

	// Clear out all existing data
	memset(mGrid, 255, mArea);

	// make landscape a little bumpy
	float t1 = mLandScape->flrand(0, 2);
	float t2 = mLandScape->flrand(0, 2);
	float t3 = mLandScape->flrand(0, 2);

	CM_NoiseInit(mLandScape);

	int x, y;
	for (y = 0; y < mHeight; y++)
		for (x = 0; x < mWidth; x++)
		{
			i = x + y*mWidth;
			byte val = (byte)Com_Clamp(0, 255, (int)(220 + (CM_NoiseGet4f( x*0.25, y*0.25, 0, t3 ) * 20)) + (CM_NoiseGet4f( x*0.5, y*0.5, 0, t2 ) * 15));
			mGrid[i] = val;
		}

	for ( i = 0; mPaths[i] != 0; i ++ )
	{
		mPaths[i]->DrawPath(mGrid, mWidth, mHeight);
	}

	for (y = 0; y < mHeight; y++)
		for (x = 0; x < mWidth; x++)
		{
			i = x + y*mWidth;
			byte val = (byte)Com_Clamp(0, 255, (int)(mGrid[i] + (CM_NoiseGet4f( x, y, 0, t1 ) * 5))); 
			mGrid[i] = val;
		}

	// if symmetric, do this now
	if (symmetric)
	{
		assert (mWidth == mHeight); // must be square

		for (y = 0; y < mHeight; y++)
			for (x = 0; x < (mWidth-y); x++)
			{
				i = x + y*mWidth;
				j = (mWidth-1 - x) + (mHeight-1 - y)*mWidth;
				byte val = mGrid[i] < mGrid[j] ? mGrid[i] : mGrid[j];
				mGrid[i] = mGrid[j] = val;
			}
	}
}




typedef enum
{
	RMG_CP_NONE = -1,
	RMG_CP_CONSONANT,
	RMG_CP_COMPLEX_CONSONANT,
	RMG_CP_VOWEL,
	RMG_CP_COMPLEX_VOWEL,
	RMG_CP_ENDING,

	RMG_CP_NUM_PIECES,
} ECPType;

typedef struct SCharacterPiece
{
	char	*mPiece;
	int		mCommonality;
} TCharacterPiece;

static TCharacterPiece	Consonants[] = 
{
	{	"b", 6 },
	{	"c", 8 },
	{	"d", 6 },
	{	"f", 5 },
	{	"g", 4 },
	{	"h", 5 },
	{	"j", 2 },
	{	"k", 4 },
	{	"l", 4 },
	{	"m", 7 },
	{	"n", 7 },
	{	"r", 6 },
	{	"s", 10 },
	{	"t", 10 },
	{	"v", 1 },
	{	"w", 2 },
	{	"x", 1 },
	{	"z", 1 },

	{	0, 0 }
};

static TCharacterPiece	ComplexConsonants[] = 
{
	{	"st", 10 },
	{	"ck", 10 },
	{	"ss", 10 },
	{	"tt", 7 },
	{	"ll", 8 },
	{	"nd", 10 },
	{	"rn", 6 },
	{	"nc", 6 },
	{	"mp", 4 },
	{	"sc", 10 }, 
	{	"sl", 10 }, 
	{	"tch", 6 }, 
	{	"th", 4 }, 
	{	"rn", 5 }, 
	{	"cl", 10 }, 
	{	"sp", 10 }, 
	{	"st", 10 }, 
	{	"fl", 4 }, 
	{	"sh", 7 }, 
	{	"ng", 4 }, 
//	{	"" },

	{	0, 0 }
};

static TCharacterPiece	Vowels[] = 
{
	{	"a", 10 },
	{	"e", 10 },
	{	"i", 10 },
	{	"o", 10 },
	{	"u", 2 },
//	{	"" },

	{	0, 0 }
};

static TCharacterPiece	ComplexVowels[] = 
{
	{	"ea", 10  },
	{	"ue", 3 },
	{	"oi", 10 },
	{	"ai", 8 },
	{	"oo", 10 },
	{	"io", 10 },
	{	"oe", 10 },
	{	"au", 3 },
	{	"ee", 7 },
	{	"ei", 7 },
	{	"ou", 7 }, 
	{	"ia", 4 }, 
//	{	"" },

	{	0, 0 }
};

static TCharacterPiece	Endings[] = 
{
	{	"ing", 10 },
	{	"ed", 10 },
	{	"ute", 10 },
	{	"ance", 10 },
	{	"ey", 10 },
	{	"ation", 10 },
	{	"ous", 10 },
	{	"ent", 10 },
	{	"ate", 10 },
	{	"ible", 10 },
	{	"age", 10 },
	{	"ity", 10 },
	{	"ist", 10 },
	{	"ism", 10 },
	{	"ime", 10 },
	{	"ic", 10 },
	{	"ant", 10 },
	{	"etry", 10 },
	{	"ious", 10 },
	{	"ative", 10 },
	{	"er", 10 },
	{	"ize", 10 }, 
	{	"able", 10 }, 
	{	"itude", 10 }, 
//	{	"" },

	{	0, 0 }
};

static void FindPiece(ECPType type, char *&pos)
{
	TCharacterPiece		*search, *start;
	int					count = 0;

	switch(type)
	{
		case RMG_CP_CONSONANT:
		default:
			start = Consonants;
			break;

		case RMG_CP_COMPLEX_CONSONANT:
			start = ComplexConsonants;
			break;

		case RMG_CP_VOWEL:
			start = Vowels;
			break;

		case RMG_CP_COMPLEX_VOWEL:
			start = ComplexVowels;
			break;

		case RMG_CP_ENDING:
			start = Endings;
			break;
	}

	search = start;
	while(search->mPiece)
	{
		count += search->mCommonality;
		search++;
	}

	count = Q_irand(0, count-1);
	search = start;
	while(count > search->mCommonality)
	{
		count -= search->mCommonality;
		search++;
	}

	strcpy(pos, search->mPiece);
	pos += strlen(search->mPiece);
}

unsigned RMG_CreateSeed(char *TextSeed)
{
	int			Length;
	char		Ending[256], *pos;
	int			ComplexVowelChance, ComplexConsonantChance;
	ECPType		LookingFor;
	unsigned	SeedValue = 0, high;

	Length = Q_irand(4, 9);

	if (Q_irand(0, 100) < 20)
	{ 
		LookingFor = RMG_CP_VOWEL;
	}
	else
	{
		LookingFor = RMG_CP_CONSONANT;
	}

	Ending[0] = 0;

	if (Q_irand(0, 100) < 55)
	{
		pos = Ending;
		FindPiece(RMG_CP_ENDING, pos);
		Length -= (pos - Ending);
	}

	pos = TextSeed;
	*pos = 0;

	ComplexVowelChance = -1;
	ComplexConsonantChance	= -1;

	while((pos - TextSeed) < Length || LookingFor == RMG_CP_CONSONANT)
	{
		if (LookingFor == RMG_CP_VOWEL)
		{
			if (Q_irand(0, 100) < ComplexVowelChance)
			{
				ComplexVowelChance = -1;
				LookingFor = RMG_CP_COMPLEX_VOWEL;
			}
			else
			{
				ComplexVowelChance += 10;
			}

			FindPiece(LookingFor, pos);
			LookingFor = RMG_CP_CONSONANT;
		}
		else
		{
			if (Q_irand(0, 100) < ComplexConsonantChance)
			{
				ComplexConsonantChance = -1;
				LookingFor = RMG_CP_COMPLEX_CONSONANT;
			}
			else
			{
				ComplexConsonantChance += 45;
			}

			FindPiece(LookingFor, pos);
			LookingFor = RMG_CP_VOWEL;
		}
	}

	if (Ending[0])
	{
		strcpy(pos, Ending);
	}

	pos = TextSeed;
	while(*pos)
	{
		high = SeedValue >> 28;
		SeedValue ^= (SeedValue << 4) + ((*pos)-'a');
		SeedValue ^= high;
		pos++;
	}

	return SeedValue;
}
