// Copyright (c) 2012- PPSSPP Project.

// 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, version 2.0 or later versions.

// 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 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.

#include "math/lin/matrix4x4.h"

#include "../../Core/MemMap.h"
#include "../ge_constants.h"

#include "VertexDecoder.h"

const int tcsize[4] = {0,2,4,8}, tcalign[4] = {0,1,2,4};
const int colsize[8] = {0,0,0,0,2,2,2,4}, colalign[8] = {0,0,0,0,2,2,2,4};
const int nrmsize[4] = {0,3,6,12}, nrmalign[4] = {0,1,2,4};
const int possize[4] = {0,3,6,12}, posalign[4] = {0,1,2,4};
const int wtsize[4] = {0,1,2,4}, wtalign[4] = {0,1,2,4};

inline int align(int n, int align)
{
	return (n+(align-1)) & ~(align-1);
}

void VertexDecoder::SetVertexType(u32 fmt)
{
	fmt = fmt;
	throughmode = (fmt & GE_VTYPE_THROUGH) != 0;

	int biggest = 0;
	size = 0;
	
	tc				 = fmt & 0x3;
	col				= (fmt >> 2) & 0x7;
	nrm				= (fmt >> 5) & 0x3;
	pos				= (fmt >> 7) & 0x3;
	weighttype = (fmt >> 9) & 0x3;
	idx				= (fmt >> 11) & 0x3;
	morphcount = ((fmt >> 18) & 0x7)+1;
	nweights	 = ((fmt >> 14) & 0x7)+1;

	DEBUG_LOG(G3D,"VTYPE: THRU=%i TC=%i COL=%i POS=%i NRM=%i WT=%i NW=%i IDX=%i MC=%i", (int)throughmode, tc,col,pos,nrm,weighttype,nweights,idx,morphcount);
	
	if (weighttype)
	{
		//size = align(size, wtalign[weighttype]);	unnecessary
		size += wtsize[weighttype] * nweights;
		if (wtalign[weighttype] > biggest)
			biggest = wtalign[weighttype];
	}

	if (tc)
	{
		size = align(size, tcalign[tc]);
		tcoff = size;
		size += tcsize[tc];
		if (tcalign[tc] > biggest)
			biggest = tcalign[tc];
	}

	if (col)
	{
		size = align(size, colalign[col]);
		coloff = size;
		size += colsize[col];
		if (colalign[col] > biggest)
			biggest = colalign[col]; 
	}
	else
	{
		coloff = 0;
	}

	if (nrm)
	{
		size = align(size, nrmalign[nrm]);
		nrmoff = size;
		size += nrmsize[nrm];
		if (nrmalign[nrm] > biggest)
			biggest = nrmalign[nrm]; 
	}

	//if (pos)  - there's always a position
	{
		size = align(size, posalign[pos]);
		posoff = size;
		size += possize[pos];
		if (posalign[pos] > biggest)
			biggest = posalign[pos];
	}

	size = align(size, biggest);
	onesize_ = size;
	size *= morphcount;
	DEBUG_LOG(G3D,"SVT : size = %i, aligned to biggest %i", size, biggest);
}

void VertexDecoder::DecodeVerts(DecodedVertex *decoded, const void *verts, const void *inds, int prim, int count, int *indexLowerBound, int *indexUpperBound) const
{
	// TODO: Remove
	if (morphcount == 1)
		gstate_c.morphWeights[0] = 1.0f;

	char *ptr = (char *)verts;

	// Find index bounds. Could cache this in display lists.
	int lowerBound = 0x7FFFFFFF;
	int upperBound = 0;
	if (idx == (GE_VTYPE_IDX_8BIT >> GE_VTYPE_IDX_SHIFT)) {
		const u8 *ind8 = (const u8 *)inds;
		for (int i = 0; i < count; i++) {
			if (ind8[i] < lowerBound)
				lowerBound = ind8[i];
			if (ind8[i] > upperBound)
				upperBound = ind8[i];
		}
	} else if (idx == (GE_VTYPE_IDX_16BIT >> GE_VTYPE_IDX_SHIFT)) {
		const u16 *ind16 = (const u16*)inds;
		for (int i = 0; i < count; i++) {
			if (ind16[i] < lowerBound)
				lowerBound = ind16[i];
			if (ind16[i] > upperBound)
				upperBound = ind16[i];
		}
	} else {
		lowerBound = 0;
		upperBound = count - 1;
	}
	*indexLowerBound = lowerBound;
	*indexUpperBound = upperBound;

	// Decode the vertices within the found bounds, once each (unlike the previous way..)
	for (int index = lowerBound; index <= upperBound; index++)
	{
		ptr = (char*)verts + (index * size);

		// TODO: Should weights be morphed?
		float *wt = decoded[index].weights;
		switch (weighttype)
		{
		case GE_VTYPE_WEIGHT_NONE >> 9:
			break;

		case GE_VTYPE_WEIGHT_8BIT >> 9:
			{
				const u8 *wdata = (const u8*)(ptr);
				for (int j = 0; j < nweights; j++)
					wt[j] = (float)wdata[j] / 128.0f;
			}
			break;

		case GE_VTYPE_WEIGHT_16BIT >> 9:
			{
				const u16 *wdata = (const u16*)(ptr);
				for (int j = 0; j < nweights; j++)
					wt[j] = (float)wdata[j] / 32768.0f;
			}
			break;

		case GE_VTYPE_WEIGHT_FLOAT >> 9:
			{
				const float *wdata = (const float*)(ptr+0);
				for (int j = 0; j < nweights; j++)
					wt[j] = wdata[j];
			}
			break;
		}

		// TODO: Not morphing UV yet
		float *uv = decoded[index].uv;
		switch (tc)
		{
		case GE_VTYPE_TC_NONE:
			uv[0] = 0.0f;
			uv[1] = 0.0f;
			break;

		case GE_VTYPE_TC_8BIT:
			{
				const u8 *uvdata = (const u8*)(ptr + tcoff);
				for (int j = 0; j < 2; j++)
					uv[j] = (float)uvdata[j] / 128.0f;
				break;
			}

		case GE_VTYPE_TC_16BIT:
			{
				const u16 *uvdata = (const u16*)(ptr + tcoff);
				if (throughmode)
				{
					uv[0] = (float)uvdata[0] / (float)(gstate_c.curTextureWidth);
					uv[1] = (float)uvdata[1] / (float)(gstate_c.curTextureHeight);
				}
				else
				{
					uv[0] = (float)uvdata[0] / 32768.0f;
					uv[1] = (float)uvdata[1] / 32768.0f;
				}
			}
			break;

		case GE_VTYPE_TC_FLOAT:
			{
				const float *uvdata = (const float*)(ptr + tcoff);
				if (throughmode) {
					uv[0] = uvdata[0] / (float)(gstate_c.curTextureWidth);
					uv[1] = uvdata[1] / (float)(gstate_c.curTextureHeight);
				} else {
					uv[0] = uvdata[0];
					uv[1] = uvdata[1];
				}
			}
			break;
		}

		// TODO: Not morphing color yet
		u8 *c = decoded[index].color;
		switch (col)
		{
		case GE_VTYPE_COL_4444 >> 2:
			{
				u16 cdata = *(u16*)(ptr + coloff);
				for (int j = 0; j < 4; j++)
					c[j] = Convert4To8((cdata >> (j * 4)) & 0xF);
			}
			break;

		case GE_VTYPE_COL_565 >> 2:
			{
				u16 cdata = *(u16*)(ptr + coloff);
				c[0] = Convert5To8(cdata & 0x1f);
				c[1] = Convert6To8((cdata>>5) & 0x3f);
				c[2] = Convert5To8((cdata>>11) & 0x1f);
				c[3] = 1.0f;
			}
			break;

		case GE_VTYPE_COL_5551 >> 2:
			{
				u16 cdata = *(u16*)(ptr + coloff);
				c[0] = Convert5To8(cdata & 0x1f);
				c[1] = Convert5To8((cdata>>5) & 0x1f);
				c[2] = Convert5To8((cdata>>10) & 0x1f);
				c[3] = (cdata>>15) ? 255 : 0;
			}
			break;

		case GE_VTYPE_COL_8888 >> 2:
			{
				// TODO: speedup
				u8 *cdata = (u8*)(ptr + coloff);
				for (int j = 0; j < 4; j++)
					c[j] = cdata[j];
			}
			break;

		default:
			c[0] = 255;
			c[1] = 255;
			c[2] = 255;
			c[3] = 255;
			break;
		}

		float *normal = decoded[index].normal;
		memset(normal, 0, sizeof(float)*3);
		for (int n = 0; n < morphcount; n++)
		{
			float multiplier = gstate_c.morphWeights[n];
			if (gstate.reversenormals & 0xFFFFFF) {
				multiplier = -multiplier;
			}
			switch (nrm)
			{
			case GE_VTYPE_NRM_8BIT:
				{
					const s8 *sv = (const s8*)(ptr + onesize_*n + nrmoff);
					for (int j = 0; j < 3; j++)
						normal[j] += (sv[j]/127.0f) * multiplier;
				}
				break;

			case GE_VTYPE_NRM_FLOAT >> 5:
				{
					const float *fv = (const float*)(ptr + onesize_*n + nrmoff);
					for (int j = 0; j < 3; j++)
						normal[j] += fv[j] * multiplier;
				}
				break;

			case GE_VTYPE_NRM_16BIT >> 5:
				{
					const short *sv = (const short*)(ptr + onesize_*n + nrmoff);
					for (int j = 0; j < 3; j++)
						normal[j] += (sv[j]/32767.0f) * multiplier;
				}
				break;
			}
		}

		float *v = decoded[index].pos;

		if (morphcount == 1) {
			switch (pos)
			{
			case GE_VTYPE_POS_FLOAT >> 7:
				{
					const float *fv = (const float*)(ptr + posoff);
					for (int j = 0; j < 3; j++)
						v[j] = fv[j];
				}
				break;

			case GE_VTYPE_POS_16BIT >> 7:
				{
					float multiplier = 1.0f / 32767.0f;
					if (throughmode) multiplier = 1.0f;
					const short *sv = (const short*)(ptr + posoff);
					for (int j = 0; j < 3; j++)
						v[j] = sv[j] * multiplier;
				}
				break;

			case GE_VTYPE_POS_8BIT >> 7:
				{
					const s8 *sv = (const s8*)(ptr + posoff);
					for (int j = 0; j < 3; j++)
						v[j] = sv[j] / 127.f;
				}
				break;

			default:
				ERROR_LOG(G3D,"Unknown position format %i",pos);
				break;
			}
		} else {
			memset(v, 0, sizeof(float) * 3);
			for (int n = 0; n < morphcount; n++)
			{
				switch (pos)
				{
				case GE_VTYPE_POS_FLOAT >> 7:
					{
						const float *fv = (const float*)(ptr + onesize_*n + posoff);
						for (int j = 0; j < 3; j++)
							v[j] += fv[j] * gstate_c.morphWeights[n];
					}
					break;

				case GE_VTYPE_POS_16BIT >> 7:
					{
						float multiplier = 1.0f / 32767.0f;
						if (throughmode) multiplier = 1.0f;
						const short *sv = (const short*)(ptr + onesize_*n + posoff);
						for (int j = 0; j < 3; j++)
							v[j] += (sv[j] * multiplier) * gstate_c.morphWeights[n];
					}
					break;

				case GE_VTYPE_POS_8BIT >> 7:
					{
						const s8 *sv = (const s8*)(ptr + onesize_*n + posoff);
						for (int j = 0; j < 3; j++)
							v[j] += (sv[j] / 127.f) * gstate_c.morphWeights[n];
					}
					break;

				default:
					ERROR_LOG(G3D,"Unknown position format %i",pos);
					break;
				}
			}
		}
	}
}

