/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/

#include "hsTypes.h"
#include "plVertCoder.h"

#include "hsStream.h"

#include "plGBufferGroup.h"

const hsScalar kPosQuantum = 1.f / hsScalar(1 << 10);
const hsScalar kWeightQuantum = 1.f / hsScalar(1 << 15);
const hsScalar kUVWQuantum = 1.f / hsScalar(1 << 16);

UInt32	plVertCoder::fCodedVerts = 0;
UInt32	plVertCoder::fCodedBytes = 0;
UInt32	plVertCoder::fRawBytes = 0;
UInt32	plVertCoder::fSkippedBytes = 0;

static const hsScalar kQuanta[plVertCoder::kNumFloatFields] =
{
	kPosQuantum,
	kWeightQuantum,
	
	kUVWQuantum,
	kUVWQuantum,
	kUVWQuantum,
	kUVWQuantum,

	kUVWQuantum,
	kUVWQuantum,
	kUVWQuantum,
	kUVWQuantum,
};


inline void plVertCoder::ICountFloats(const UInt8* src, UInt16 maxCnt, const hsScalar quant, const UInt32 stride, 
									  hsScalar& lo, hsBool &allSame, UInt16& count)
{
	lo = *(hsScalar*)src;
	lo = floor(lo / quant + 0.5f) * quant;
	allSame = false;
	hsScalar hi = lo;
	
	count = 1;

	const hsScalar maxRange = hsScalar(UInt16(0xffff)) * quant;

	src += stride;
	maxCnt--;

	while( maxCnt-- )
	{
		hsScalar val = *(hsScalar*)src;
		val = floor(val / quant + 0.5f) * quant;
		if( val < lo )
		{
			if( hi - val > maxRange )
				return;
			lo = val;
		}
		else if( val > hi )
		{
			if( val - lo > maxRange )
				return;
			hi = val;
		}
		count++;
		src += stride;
	}
	allSame = (lo == hi);
}

static inline void IWriteFloat(hsStream* s, const UInt8*& src, const hsScalar offset, const hsScalar quantum)
{
	float fval = *(float*)src;
	fval -= offset;
	fval /= quantum;
//	hsAssert(fval < hsScalar(UInt16(0xffff)), "Bad offset?");

	const UInt16 ival = UInt16(floor(fval + 0.5f));
	s->WriteSwap16(ival);

	src += 4;
}

static inline void IReadFloat(hsStream* s, UInt8*& dst, const hsScalar offset, const hsScalar quantum)
{
	const UInt16 ival = s->ReadSwap16();
	float fval = float(ival) * quantum;
	fval += offset;

	hsScalar* val = (hsScalar*)dst;
	*val = fval;

	dst += 4;
}

inline void plVertCoder::IEncodeFloat(hsStream* s, const UInt32 vertsLeft, const int field, const int chan, const UInt8*& src, const UInt32 stride)
{
	if( !fFloats[field][chan].fCount )
	{
		ICountFloats(src, (UInt16)vertsLeft, kQuanta[field], stride, fFloats[field][chan].fOffset, fFloats[field][chan].fAllSame, fFloats[field][chan].fCount);

		s->WriteSwapScalar(fFloats[field][chan].fOffset);
		s->WriteBool(fFloats[field][chan].fAllSame);
		s->WriteSwap16(fFloats[field][chan].fCount);

	}
	if (!fFloats[field][chan].fAllSame)
		IWriteFloat(s, src, fFloats[field][chan].fOffset, kQuanta[field]);
	else
		src += 4;

	fFloats[field][chan].fCount--;
}

inline void plVertCoder::IDecodeFloat(hsStream* s, const int field, const int chan, UInt8*& dst, const UInt32 stride)
{
	if( !fFloats[field][chan].fCount )
	{
		fFloats[field][chan].fOffset = s->ReadSwapScalar();
		fFloats[field][chan].fAllSame = s->ReadBool();
		fFloats[field][chan].fCount = s->ReadSwap16();
	}

	if (!fFloats[field][chan].fAllSame)
		IReadFloat(s, dst, fFloats[field][chan].fOffset, kQuanta[field]);
	else
	{
		*((hsScalar*)dst) = fFloats[field][chan].fOffset;
		dst += 4;
	}

	fFloats[field][chan].fCount--;
}

static inline int INumWeights(const UInt8 format)
{
	return (format & plGBufferGroup::kSkinWeightMask) >> 4;
}

static const hsScalar kNormalScale(Int16(0x7fff));
static const hsScalar kInvNormalScale(1.f / kNormalScale);

inline void plVertCoder::IEncodeNormal(hsStream* s, const UInt8*& src, const UInt32 stride)
{

	hsScalar x = *(hsScalar*)src;
	s->WriteByte((UInt8)((x / 2.f + .5f) * 255.9f));
	src += 4;

	x = *(hsScalar*)src;
	s->WriteByte((UInt8)((x / 2.f + .5f) * 255.9f));
	src += 4;

	x = *(hsScalar*)src;
	s->WriteByte((UInt8)((x / 2.f + .5f) * 255.9f));
	src += 4;
}

inline void plVertCoder::IDecodeNormal(hsStream* s, UInt8*& dst, const UInt32 stride)
{

	UInt8 ix = s->ReadByte();
	hsScalar* x = (hsScalar*)dst;
	*x = (ix / 255.9f - .5f) * 2.f;
	dst += 4;

	ix = s->ReadByte();
	x = (hsScalar*)dst;
	*x = (ix / 255.9f - .5f) * 2.f;
	dst += 4;

	ix = s->ReadByte();
	x = (hsScalar*)dst;
	*x = (ix / 255.9f - .5f) * 2.f;
	dst += 4;
}

inline void plVertCoder::ICountBytes(const UInt32 vertsLeft, const UInt8* src, const UInt32 stride, UInt16& len, UInt8& same)
{
	// We want to run length encode this. So we're looking here for either
	// the number of consecutive bytes of the same value,
	// or the number of consective bytes of different values.
	// The latter is so we don't wind up getting larger when there aren't any 
	// runs of the same value (count=1 and val=c1, count=1 and val=c2, etc.).
	// The break-even point is a run of 3, so we'll look for a minimum run of 4.

	if( vertsLeft < 4 )
	{
		len = (UInt16)vertsLeft;
		same = false;
		
		return;
	}

	// First, count how many values are the same as the first one
	int i;
	for( i = 0; i < vertsLeft; i++ )
	{
		if( src[i * stride] != src[0] )
			break;
	}

	if( i >= 4 )
	{
		// Found a good run.
		len = i;
		same = true;

		return;
	}

	// Okay, we're in a section of varying values. How far to the next 
	// section of sameness?
	same = false;
	for( ; i < vertsLeft-4; i++ )
	{
		if( (src[i*stride] == src[(i+1)*stride])
			&&(src[i*stride] == src[(i+2)*stride])
			&&(src[i*stride] == src[(i+3)*stride]) )
			break;
	}

	if( i < vertsLeft-4 )
	{
		len = i;
		return;
	}

	len = (UInt16)vertsLeft;
	return;
}

static const UInt16 kSameMask(0x8000);

inline void plVertCoder::IEncodeByte(hsStream* s, const int chan, const UInt32 vertsLeft, const UInt8*& src, const UInt32 stride)
{
	if( !fColors[chan].fCount )
	{
		ICountBytes(vertsLeft, src, stride, fColors[chan].fCount, fColors[chan].fSame);

		UInt16 cnt = fColors[chan].fCount;
		if( fColors[chan].fSame )
			cnt |= kSameMask;
		s->WriteSwap16(cnt);

		if( fColors[chan].fSame )
			s->WriteByte(*src);
	}

	if( !fColors[chan].fSame )
		s->WriteByte(*src);
	
	src++;
	fColors[chan].fCount--;
}

inline void plVertCoder::IDecodeByte(hsStream* s, const int chan, UInt8*& dst, const UInt32 stride)
{
	if( !fColors[chan].fCount )
	{
		UInt16 cnt = s->ReadSwap16();
		if( cnt & kSameMask )
		{
			fColors[chan].fSame = true;
			fColors[chan].fVal = s->ReadByte();

			cnt &= ~kSameMask;
		}
		else
		{
			fColors[chan].fSame = false;
		}
		fColors[chan].fCount = cnt;
	}
	if( !fColors[chan].fSame )
		*dst = s->ReadByte();
	else
		*dst = fColors[chan].fVal;

	dst++;
	fColors[chan].fCount--;
}

inline void plVertCoder::IEncodeColor(hsStream* s, const UInt32 vertsLeft, const UInt8*& src, const UInt32 stride)
{
	IEncodeByte(s, 0, vertsLeft, src, stride);
	IEncodeByte(s, 1, vertsLeft, src, stride);
	IEncodeByte(s, 2, vertsLeft, src, stride);
	IEncodeByte(s, 3, vertsLeft, src, stride);
}

inline void plVertCoder::IDecodeColor(hsStream* s, UInt8*& dst, const UInt32 stride)
{
	IDecodeByte(s, 0, dst, stride);
	IDecodeByte(s, 1, dst, stride);
	IDecodeByte(s, 2, dst, stride);
	IDecodeByte(s, 3, dst, stride);
}

inline void plVertCoder::IEncode(hsStream* s, const UInt32 vertsLeft, const UInt8*& src, const UInt32 stride, const UInt8 format)
{
	IEncodeFloat(s, vertsLeft, kPosition, 0, src, stride);
	IEncodeFloat(s, vertsLeft, kPosition, 1, src, stride);
	IEncodeFloat(s, vertsLeft, kPosition, 2, src, stride);

	// Weights and indices?
	const int numWeights = INumWeights(format);
	if( numWeights )
	{
		int j;
		for( j = 0; j < numWeights; j++ )
			IEncodeFloat(s, vertsLeft, kWeight, j, src, stride);

		if( format & plGBufferGroup::kSkinIndices )
		{
			const UInt32 idx = *(UInt32*)src;
			s->WriteSwap32(idx);
			src += 4;
		}
	}

	IEncodeNormal(s, src, stride);

	IEncodeColor(s, vertsLeft, src, stride);

	// COLOR2
	src += 4;

	const int numUVWs = format & plGBufferGroup::kUVCountMask;
	int i;
	for( i = 0; i < numUVWs; i++ )
	{
		IEncodeFloat(s, vertsLeft, kUVW + i, 0, src, stride);
		IEncodeFloat(s, vertsLeft, kUVW + i, 1, src, stride);
		IEncodeFloat(s, vertsLeft, kUVW + i, 2, src, stride);
	}
}

inline void plVertCoder::IDecode(hsStream* s, UInt8*& dst, const UInt32 stride, const UInt8 format)
{
	IDecodeFloat(s, kPosition, 0, dst, stride);
	IDecodeFloat(s, kPosition, 1, dst, stride);
	IDecodeFloat(s, kPosition, 2, dst, stride);

	// Weights and indices?
	const int numWeights = INumWeights(format);
	if( numWeights )
	{
		int j;
		for( j = 0; j < numWeights; j++ )
			IDecodeFloat(s, kWeight, j, dst, stride);

		if( format & plGBufferGroup::kSkinIndices )
		{
			UInt32* idx = (UInt32*)dst;
			*idx = s->ReadSwap32();
			dst += 4;
		}
	}

	IDecodeNormal(s, dst, stride);

	IDecodeColor(s, dst, stride);

	// COLOR2
	UInt32* trash = (UInt32*)dst;
	*trash = 0;
	dst += 4;

	const int numUVWs = format & plGBufferGroup::kUVCountMask;
	int i;
	for( i = 0; i < numUVWs; i++ )
	{
		IDecodeFloat(s, kUVW + i, 0, dst, stride);
		IDecodeFloat(s, kUVW + i, 1, dst, stride);
		IDecodeFloat(s, kUVW + i, 2, dst, stride);
	}
}

void plVertCoder::Read(hsStream* s, UInt8* dst, const UInt8 format, const UInt32 stride, const UInt16 numVerts)
{
	Clear();

	int i = numVerts;
	for( i = 0; i < numVerts; i++ )
		IDecode(s, dst, stride, format);
}


void plVertCoder::Write(hsStream* s, const UInt8* src, const UInt8 format, const UInt32 stride, const UInt16 numVerts)
{
	Clear();

	UInt32 streamStart = s->GetPosition();

	int numLeft = numVerts;
	while( numLeft )
	{
		IEncode(s, numLeft, src, stride, format);
		numLeft--;
	}

	fCodedVerts += numVerts;
	fCodedBytes += (s->GetPosition() - streamStart);
	fRawBytes += numVerts * stride;
}

plVertCoder::plVertCoder()
{
	Clear();
}

plVertCoder::~plVertCoder()
{
}

void plVertCoder::Clear()
{
	memset(this, 0, sizeof(*this));
}