/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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/>.
 *
 */

/*
 * Copyright (C) 2006-2010 - Frictional Games
 *
 * This file is part of HPL1 Engine.
 */

#include "hpl1/engine/scene/SubMeshEntity.h"

#include "hpl1/engine/scene/MeshEntity.h"

#include "hpl1/engine/graphics/Material.h"
#include "hpl1/engine/graphics/Mesh.h"
#include "hpl1/engine/graphics/SubMesh.h"
#include "hpl1/engine/graphics/VertexBuffer.h"
#include "hpl1/engine/resources/MaterialManager.h"
#include "hpl1/engine/resources/MeshManager.h"

#include "hpl1/engine/graphics/Animation.h"
#include "hpl1/engine/graphics/AnimationTrack.h"
#include "hpl1/engine/graphics/Bone.h"
#include "hpl1/engine/graphics/Skeleton.h"

#include "hpl1/engine/scene/AnimationState.h"
#include "hpl1/engine/scene/NodeState.h"

#include "hpl1/engine/physics/PhysicsBody.h"

#include "hpl1/engine/math/Math.h"

namespace hpl {
//////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////

cSubMeshEntity::cSubMeshEntity(const tString &asName, cMeshEntity *apMeshEntity, cSubMesh *apSubMesh,
							   cMaterialManager *apMaterialManager) : iRenderable(asName) {
	mpMeshEntity = apMeshEntity;
	mpSubMesh = apSubMesh;

	mbIsOneSided = mpSubMesh->GetIsOneSided();
	mvOneSidedNormal = mpSubMesh->GetOneSidedNormal();

	mpMaterialManager = apMaterialManager;

	mbCastShadows = false;

	mbGraphicsUpdated = false;

	mpBody = NULL;

	if (mpMeshEntity->GetMesh()->GetSkeleton()) {
		mpDynVtxBuffer = mpSubMesh->GetVertexBuffer()->CreateCopy(eVertexBufferUsageType_Dynamic);
		mvDynTriangles = *mpSubMesh->GetTriangleVecPtr();
	} else {
		mpDynVtxBuffer = NULL;
	}

	mpLocalNode = NULL;

	mpEntityCallback = hplNew(cSubMeshEntityBodyUpdate, ());
	mbUpdateBody = false;

	mpMaterial = NULL;
}

cSubMeshEntity::~cSubMeshEntity() {
	hplDelete(mpEntityCallback);

	if (mpDynVtxBuffer)
		hplDelete(mpDynVtxBuffer);

	/* Clear any custom textures here*/
	if (mpMaterial)
		mpMaterialManager->Destroy(mpMaterial);
}

//-----------------------------------------------------------------------

//////////////////////////////////////////////////////////////////////////
// BODY CALLBACK
//////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------

void cSubMeshEntityBodyUpdate::OnTransformUpdate(iEntity3D *apEntity) {
	/*cSubMeshEntity *pSubEntity = static_cast<cSubMeshEntity*>(apEntity);

	if(pSubEntity->GetBody())
	{
		if(apEntity->GetWorldMatrix() != pSubEntity->GetBody()->GetLocalMatrix())
		{
			Log("Setting matrix on %s from\n",pSubEntity->GetBody()->GetName().c_str());
			Log("  %s\n",apEntity->GetWorldMatrix().ToString().c_str());
			Log("  %s\n",pSubEntity->GetBody()->GetLocalMatrix().ToString().c_str());

			pSubEntity->GetBody()->SetMatrix(apEntity->GetWorldMatrix());
		}
	}*/
}

//-----------------------------------------------------------------------

//////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS
//////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------

void cSubMeshEntity::UpdateLogic(float afTimeStep) {
	if (mbUpdateBody && mpBody) {
		mpBody->SetMatrix(GetWorldMatrix());
	}
}

//-----------------------------------------------------------------------

iMaterial *cSubMeshEntity::GetMaterial() {
	if (mpMaterial == NULL && mpSubMesh->GetMaterial() == NULL) {
		Error("Materials for sub entity %s are NULL!\n", GetName().c_str());
	}

	if (mpMaterial)
		return mpMaterial;
	else
		return mpSubMesh->GetMaterial();
}

//-----------------------------------------------------------------------

// Set Src as private variable to give this a little boost! Or?
static inline void MatrixFloatTransformSet(float *pDest, const cMatrixf &a_mtxA, const float *pSrc, const float fWeight) {
	pDest[0] = (a_mtxA.m[0][0] * pSrc[0] + a_mtxA.m[0][1] * pSrc[1] + a_mtxA.m[0][2] * pSrc[2] + a_mtxA.m[0][3]) * fWeight;
	pDest[1] = (a_mtxA.m[1][0] * pSrc[0] + a_mtxA.m[1][1] * pSrc[1] + a_mtxA.m[1][2] * pSrc[2] + a_mtxA.m[1][3]) * fWeight;
	pDest[2] = (a_mtxA.m[2][0] * pSrc[0] + a_mtxA.m[2][1] * pSrc[1] + a_mtxA.m[2][2] * pSrc[2] + a_mtxA.m[2][3]) * fWeight;
}

static inline void MatrixFloatRotateSet(float *pDest, const cMatrixf &a_mtxA, const float *pSrc, const float fWeight) {
	pDest[0] = (a_mtxA.m[0][0] * pSrc[0] + a_mtxA.m[0][1] * pSrc[1] + a_mtxA.m[0][2] * pSrc[2]) * fWeight;
	pDest[1] = (a_mtxA.m[1][0] * pSrc[0] + a_mtxA.m[1][1] * pSrc[1] + a_mtxA.m[1][2] * pSrc[2]) * fWeight;
	pDest[2] = (a_mtxA.m[2][0] * pSrc[0] + a_mtxA.m[2][1] * pSrc[1] + a_mtxA.m[2][2] * pSrc[2]) * fWeight;
}

////////////////////////////////////////////////////////////

// Set Src as private variable to give this a little boost!Or?
static inline void MatrixFloatTransformAdd(float *pDest, const cMatrixf &a_mtxA, const float *pSrc, const float fWeight) {
	pDest[0] += (a_mtxA.m[0][0] * pSrc[0] + a_mtxA.m[0][1] * pSrc[1] + a_mtxA.m[0][2] * pSrc[2] + a_mtxA.m[0][3]) * fWeight;
	pDest[1] += (a_mtxA.m[1][0] * pSrc[0] + a_mtxA.m[1][1] * pSrc[1] + a_mtxA.m[1][2] * pSrc[2] + a_mtxA.m[1][3]) * fWeight;
	pDest[2] += (a_mtxA.m[2][0] * pSrc[0] + a_mtxA.m[2][1] * pSrc[1] + a_mtxA.m[2][2] * pSrc[2] + a_mtxA.m[2][3]) * fWeight;
}

static inline void MatrixFloatRotateAdd(float *pDest, const cMatrixf &a_mtxA, const float *pSrc, const float fWeight) {
	pDest[0] += (a_mtxA.m[0][0] * pSrc[0] + a_mtxA.m[0][1] * pSrc[1] + a_mtxA.m[0][2] * pSrc[2]) * fWeight;
	pDest[1] += (a_mtxA.m[1][0] * pSrc[0] + a_mtxA.m[1][1] * pSrc[1] + a_mtxA.m[1][2] * pSrc[2]) * fWeight;
	pDest[2] += (a_mtxA.m[2][0] * pSrc[0] + a_mtxA.m[2][1] * pSrc[1] + a_mtxA.m[2][2] * pSrc[2]) * fWeight;
}

////////////////////////////////////////////////////////////

void cSubMeshEntity::UpdateGraphics(cCamera3D *apCamera, float afFrameTime, cRenderList *apRenderList) {
	if (mpDynVtxBuffer) {
		if (mpMeshEntity->mbSkeletonPhysicsSleeping && mbGraphicsUpdated) {
			return;
		}

		mbGraphicsUpdated = true;

		const float *pBindPos = mpSubMesh->GetVertexBuffer()->GetArray(eVertexFlag_Position);
		const float *pBindNormal = mpSubMesh->GetVertexBuffer()->GetArray(eVertexFlag_Normal);
		const float *pBindTangent = mpSubMesh->GetVertexBuffer()->GetArray(eVertexFlag_Texture1);

		float *pSkinPos = mpDynVtxBuffer->GetArray(eVertexFlag_Position);
		float *pSkinNormal = mpDynVtxBuffer->GetArray(eVertexFlag_Normal);
		float *pSkinTangent = mpDynVtxBuffer->GetArray(eVertexFlag_Texture1);

		const int lVtxStride = kvVertexElements[cMath::Log2ToInt(eVertexFlag_Position)];
		const int lVtxNum = mpDynVtxBuffer->GetVertexNum();

		for (int vtx = 0; vtx < lVtxNum; vtx++) {
			// To count the bone bindings
			int lCount = 0;
			// Get pointer to weights and bone index.
			const float *pWeight = &mpSubMesh->mpVertexWeights[vtx * 4];
			if (*pWeight == 0)
				continue;

			const unsigned char *pBoneIdx = &mpSubMesh->mpVertexBones[vtx * 4];

			{
				const cMatrixf &mtxTransform = mpMeshEntity->mvBoneMatrices[*pBoneIdx];

				// Log("Vtx: %d\n",vtx);
				// Log("Boneidx: %d Count %d Weight: %f vtx: %d\n",(int)*pBoneIdx,lCount, *pWeight,vtx);

				// ATTENTION: Some optimizing might be done by accumulating the matrix instead.
				// THIS is really unsure since it seems like it will result in more math, matrix mul = 8*4 calc
				// Vertex mul with matrix is 3 * 3 calculations
				// this means: vertex= 9*3 = 27, matrix = 32

				MatrixFloatTransformSet(pSkinPos, mtxTransform, pBindPos, *pWeight);

				MatrixFloatRotateSet(pSkinNormal, mtxTransform, pBindNormal, *pWeight);

				MatrixFloatRotateSet(pSkinTangent, mtxTransform, pBindTangent, *pWeight);

				++pWeight;
				++pBoneIdx;
				++lCount;
			}

			// Iterate weights until 0 is found or count < 4
			while (*pWeight != 0 && lCount < 4) {
				// Log("Boneidx: %d Count %d Weight: %f\n",(int)*pBoneIdx,lCount, *pWeight);
				const cMatrixf &mtxTransform = mpMeshEntity->mvBoneMatrices[*pBoneIdx];

				// Transform with the local movement of the bone.
				MatrixFloatTransformAdd(pSkinPos, mtxTransform, pBindPos, *pWeight);

				MatrixFloatRotateAdd(pSkinNormal, mtxTransform, pBindNormal, *pWeight);

				MatrixFloatRotateAdd(pSkinTangent, mtxTransform, pBindTangent, *pWeight);

				++pWeight;
				++pBoneIdx;
				++lCount;
			}

			pBindPos += lVtxStride;
			pSkinPos += lVtxStride;

			pBindNormal += 3;
			pSkinNormal += 3;

			pBindTangent += 4;
			pSkinTangent += 4;
		}

		float *pSkinPosArray = mpDynVtxBuffer->GetArray(eVertexFlag_Position);
		if (mpMeshEntity->IsShadowCaster()) {
			// Update the shadow double
			memcpy(&pSkinPosArray[lVtxStride * lVtxNum], pSkinPosArray, sizeof(float) * lVtxStride * lVtxNum);
			for (int vtx = lVtxStride * lVtxNum + lVtxStride - 1; vtx < lVtxStride * lVtxNum * 2; vtx += lVtxStride) {
				pSkinPosArray[vtx] = 0;
			}
		}

		// Update buffer
		mpDynVtxBuffer->UpdateData(eVertexFlag_Position | eVertexFlag_Normal | eVertexFlag_Texture1, false);

		if (mpMeshEntity->IsShadowCaster()) {
			// Update triangles
			cMath::CreateTriangleData(mvDynTriangles,
									  mpDynVtxBuffer->GetIndices(), mpDynVtxBuffer->GetIndexNum(),
									  pSkinPosArray, lVtxStride, lVtxNum);
		}
	}

	/*if(mpDynVtxBuffer)
	{
	const float *pBindPosArray = mpSubMesh->GetVertexBuffer()->GetArray(eVertexFlag_Position);
	const float *pBindNormalArray = mpSubMesh->GetVertexBuffer()->GetArray(eVertexFlag_Normal);
	const float *pBindTangentArray = mpSubMesh->GetVertexBuffer()->GetArray(eVertexFlag_Texture1);

	float *pSkinPosArray = mpDynVtxBuffer->GetArray(eVertexFlag_Position);
	float *pSkinNormalArray = mpDynVtxBuffer->GetArray(eVertexFlag_Normal);
	float *pSkinTangentArray = mpDynVtxBuffer->GetArray(eVertexFlag_Texture1);

	int lVtxStride = kvVertexElements[cMath::Log2ToInt(eVertexFlag_Position)];
	int lVtxNum = mpDynVtxBuffer->GetVertexNum();

	memset(pSkinPosArray,0,sizeof(float)*lVtxStride*lVtxNum);
	memset(pSkinNormalArray,0,sizeof(float)*3*lVtxNum);
	memset(pSkinTangentArray,0,sizeof(float)*4*lVtxNum);

	int lSize = mpSubMesh->GetVertexBonePairNum();
	for(int i=0; i<lSize; i++)
	{
	const cVertexBonePair& VBPair = mpSubMesh->GetVertexBonePair(i);

	//Log("%d: Vtx: %d Bone: %d\n",i,VBPair.vtxIdx, VBPair.boneIdx);

	const float* pBindPos = &pBindPosArray[VBPair.vtxIdx*lVtxStride];
	float* pSkinPos = &pSkinPosArray[VBPair.vtxIdx*lVtxStride];

	const float* pBindNorm = &pBindNormalArray[VBPair.vtxIdx*3];
	float* pSkinNorm = &pSkinNormalArray[VBPair.vtxIdx*3];

	const float* pBindTan = &pBindTangentArray[VBPair.vtxIdx*4];
	float* pSkinTan = &pSkinTangentArray[VBPair.vtxIdx*4];

	const cMatrixf &mtxTransform = mpMeshEntity->mvBoneMatrices[VBPair.boneIdx];

	//Transform with the local movement of the bone.
	MatrixFloatTransform(pSkinPos,mtxTransform, pBindPos, VBPair.weight);
	pSkinPos[3] = 1;

	MatrixFloatRotate(pSkinNorm,mtxTransform, pBindNorm, VBPair.weight);

	MatrixFloatRotate(pSkinTan,mtxTransform, pBindTan, VBPair.weight);
	pSkinTan[3] = pBindTan[3];

	//cVector3f vSkin = cMath::MatrixMul(mpMeshEntity->mvBoneMatrices[VBPair.boneIdx],
	//									cVector3f(pBindPos[0],pBindPos[1],pBindPos[2]));

	//pSkinPos[0] += vSkin.x * VBPair.weight;
	//pSkinPos[1] += vSkin.y * VBPair.weight;
	//pSkinPos[2] += vSkin.z * VBPair.weight;
	}

	//Update the shadow double
	memcpy(&pSkinPosArray[lVtxStride*lVtxNum],pSkinPosArray,sizeof(float)*lVtxStride*lVtxNum);
	for(int vtx=lVtxStride*lVtxNum + lVtxStride-1; vtx < lVtxStride*lVtxNum*2; vtx+=lVtxStride)
	{
	pSkinPosArray[vtx] = 0;
	}

	//Update buffer
	mpDynVtxBuffer->UpdateData(eVertexFlag_Position | eVertexFlag_Normal | eVertexFlag_Texture1,false);

	//Update triangles
	cMath::CreateTriangleData(mvDynTriangles,
	mpDynVtxBuffer->GetIndices(), mpDynVtxBuffer->GetIndexNum(),
	pSkinPosArray, lVtxStride, lVtxNum);
	}*/
}

iVertexBuffer *cSubMeshEntity::GetVertexBuffer() {
	if (mpDynVtxBuffer) {
		return mpDynVtxBuffer;
	} else {
		return mpSubMesh->GetVertexBuffer();
	}
}

cBoundingVolume *cSubMeshEntity::GetBoundingVolume() {
	return mpMeshEntity->GetBoundingVolume();
}

int cSubMeshEntity::GetMatrixUpdateCount() {
	if (mpMeshEntity->HasNodes()) {
		return GetTransformUpdateCount();
	} else {
		return mpMeshEntity->GetMatrixUpdateCount();
	}
}

cMatrixf *cSubMeshEntity::GetModelMatrix(cCamera3D *apCamera) {
	if (mpMeshEntity->HasNodes()) {
		// Log("%s Matrix from local node!\n",msName.c_str());
		return &GetWorldMatrix();
	} else {
		// Log("%s Matrix from mesh!\n",msName.c_str());

		if (mpMeshEntity->IsStatic())
			return NULL;

		return mpMeshEntity->GetModelMatrix(NULL);
	}
}

//-----------------------------------------------------------------------

void cSubMeshEntity::SetLocalNode(cNode3D *apNode) {
	mpLocalNode = apNode;

	mpLocalNode->AddEntity(this);
}

//-----------------------------------------------------------------------

cNode3D *cSubMeshEntity::GetLocalNode() {
	return mpLocalNode;
}

//-----------------------------------------------------------------------

tRenderContainerDataList *cSubMeshEntity::GetRenderContainerDataList() {
	// Log("Get from parent %s\n",mpMeshEntity->GetName().c_str());
	return mpMeshEntity->GetRenderContainerDataList();
}

//-----------------------------------------------------------------------

void cSubMeshEntity::SetUpdateBody(bool abX) {
	mbUpdateBody = abX;

	/*if(mbUpdateBody)
	{
		AddCallback(mpEntityCallback);
	}
	else
	{
		RemoveCallback(mpEntityCallback);
	}*/
}

bool cSubMeshEntity::GetUpdateBody() {
	return mbUpdateBody;
}

//-----------------------------------------------------------------------

cTriangleData &cSubMeshEntity::GetTriangle(int alIndex) {
	if (mpDynVtxBuffer)
		return mvDynTriangles[alIndex];
	else
		return (*mpSubMesh->GetTriangleVecPtr())[alIndex];
}
int cSubMeshEntity::GetTriangleNum() {
	if (mpDynVtxBuffer)
		return (int)mvDynTriangles.size();
	else
		return (int)mpSubMesh->GetTriangleVecPtr()->size();
}

tTriangleDataVec *cSubMeshEntity::GetTriangleVecPtr() {
	if (mpDynVtxBuffer)
		return &mvDynTriangles;
	else
		return mpSubMesh->GetTriangleVecPtr();
}

//-----------------------------------------------------------------------

void cSubMeshEntity::SetCustomMaterial(iMaterial *apMaterial, bool abDestroyOldCustom) {
	if (abDestroyOldCustom) {
		if (mpMaterial)
			mpMaterialManager->Destroy(mpMaterial);
	}

	mpMaterial = apMaterial;
}

//-----------------------------------------------------------------------

cSector *cSubMeshEntity::GetCurrentSector() const {
	return mpMeshEntity->GetCurrentSector();
}

//-----------------------------------------------------------------------

//////////////////////////////////////////////////////////////////////////
// SAVE OBJECT STUFF
//////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------

kBeginSerialize(cSaveData_cSubMeshEntity, cSaveData_iRenderable)
	kSerializeVar(msMaterial, eSerializeType_String)
		kSerializeVar(mbCastShadows, eSerializeType_Bool)
			kSerializeVar(mlBodyId, eSerializeType_Int32)
				kSerializeVar(mbUpdateBody, eSerializeType_Bool)
					kEndSerialize()

	//-----------------------------------------------------------------------

	iSaveData *cSubMeshEntity::CreateSaveData() {
	return hplNew(cSaveData_cSubMeshEntity, ());
}

//-----------------------------------------------------------------------

void cSubMeshEntity::SaveToSaveData(iSaveData *apSaveData) {
	kSaveData_SaveToBegin(cSubMeshEntity);

	kSaveData_SaveTo(mbCastShadows);
	kSaveData_SaveTo(mbUpdateBody);

	pData->msMaterial = mpMaterial == NULL ? "" : mpMaterial->GetName();

	kSaveData_SaveObject(mpBody, mlBodyId);
}

//-----------------------------------------------------------------------

void cSubMeshEntity::LoadFromSaveData(iSaveData *apSaveData) {
	kSaveData_LoadFromBegin(cSubMeshEntity);

	kSaveData_LoadFrom(mbCastShadows);
	kSaveData_LoadFrom(mbUpdateBody);

	if (pData->msMaterial != "") {
		iMaterial *pMat = mpMaterialManager->CreateMaterial(pData->msMaterial);
		if (pMat)
			SetCustomMaterial(pMat);
	}
}

//-----------------------------------------------------------------------

void cSubMeshEntity::SaveDataSetup(cSaveObjectHandler *apSaveObjectHandler, cGame *apGame) {
	kSaveData_SetupBegin(cSubMeshEntity);

	kSaveData_LoadObject(mpBody, mlBodyId, iPhysicsBody *);

	if (mpBody && mbUpdateBody == false) {
		mpBody->CreateNode()->AddEntity(this);
	}
}

//-----------------------------------------------------------------------
} // namespace hpl
