#include "mungal4.h"
#pragma hdrstop

#include "l4d3d.h"
#include "L4VIDEO.h"
#include <algorithm>

int gNumBatches = 0;

bool				d3d_OBJECT::mLastTextureScrollState = false;
L4TEXOP::WrapType	d3d_OBJECT::mLastWrapU = L4TEXOP::WrapType::REPEAT;
L4TEXOP::WrapType	d3d_OBJECT::mLastWrapV = L4TEXOP::WrapType::REPEAT;
bool				d3d_OBJECT::mLastTexturingState = true;
long				d3d_OBJECT::mNextID = 1;
stdext::hash_map<std::string, L4TEXOP> d3d_OBJECT::mTextureCache;

void chgext(char *filePath, const char *newExtension)
{
	int len = strlen(filePath);
	for (int i=len-1; i>=0; i--)
	{
		if (filePath[i] == '.')
		{
			strcpy(&filePath[i + 1], newExtension);
			return;
		}
	}
}

void d3d_OBJECT::ResetState(LPDIRECT3DDEVICE9 device)
{
	d3d_OBJECT::mLastTextureScrollState = false;
	device->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);

	d3d_OBJECT::mLastWrapU = L4TEXOP::WrapType::REPEAT;
	device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);

	d3d_OBJECT::mLastWrapV = L4TEXOP::WrapType::REPEAT;
	device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);

	d3d_OBJECT::mLastTexturingState = true;
	device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
	device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
	device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
}

d3d_OBJECT *d3d_OBJECT::LoadObject(LPDIRECT3DDEVICE9 device, char *fileName)
{
	char fullPath[512];
	sprintf(fullPath, "VIDEO\\%s", fileName);
	chgext(fullPath, "x");

	//Get a ref to the renderer
	DPLRenderer *renderer = l4_application->GetVideoRenderer();

	LPD3DXMESH mesh;
	LPD3DXBUFFER materialBuffer, adjacencyBuffer;
	DWORD materialCount;

	HRESULT hr = D3DXLoadMeshFromXA(fullPath, D3DXMESH_MANAGED, device, &adjacencyBuffer, &materialBuffer, NULL, &materialCount, &mesh);
	if (FAILED(hr))
	{
		chgext(fullPath, "sph");
		return d3d_OBJECT::LoadSpheres(device, fullPath);
	}

	chgext(fullPath, "det");
	FILE *detFile = fopen(fullPath, "rb");
	int numDetailOps = 0;
	int *detailOps = NULL;

	bool isSky = false;
	
	if (detFile != NULL)
	{
		fread(&numDetailOps, sizeof(int), 1, detFile);

		detailOps = new int[numDetailOps];
		fread(detailOps, sizeof(int), numDetailOps, detFile);
		fclose(detFile);
	}

	chgext(fullPath, "sky");
	FILE *skyFile = fopen(fullPath, "rb");

	if (skyFile != NULL)
	{
		isSky = true;
		fclose(skyFile);
	}

	d3d_OBJECT *object = new d3d_OBJECT(device, mesh, (DWORD*)adjacencyBuffer->GetBufferPointer(), materialCount);

	D3DXMATERIAL *materials = (D3DXMATERIAL*)materialBuffer->GetBufferPointer();

	int nextDetailOp = 0;

	for (DWORD i = 0; i < materialCount; i++)
	{
		chgext(fullPath, "BGF");

		string path(fullPath);

		string actualPath = path.substr(path.find("\\") + 1);
		std::transform(actualPath.begin(), actualPath.end(), actualPath.begin(), toupper);
		const char *matName = opMaterialName(actualPath.c_str(), i);
		char *replaceMatName = NULL;
		char *replaceTexName = NULL;

		if (matName != NULL)
		{
			char *tmpMatName = new char[strlen(matName) + 1];
			strcpy_s(tmpMatName, strlen(matName) + 1, matName);

			replaceMatName = substituteMaterial(tmpMatName);
		}

		object->mDrawOps[i].material = materials[i].MatD3D;

		if (replaceMatName != NULL)
		{
			hash_map<string, ReplacementMaterialData>::const_iterator iter = gReplacementData->find(string(replaceMatName));

			if (iter != gReplacementData->end())
			{
				ReplacementMaterialData data = (*iter).second;

				object->mDrawOps[i].material.Diffuse.r = data.r;
				object->mDrawOps[i].material.Diffuse.g = data.g;
				object->mDrawOps[i].material.Diffuse.b = data.b;

				replaceTexName = new char[data.texName.size() + 1];
				strcpy_s(replaceTexName, data.texName.size() + 1, data.texName.c_str());
			}
			else
			{
				DEBUG_STREAM << "L4D3D.cpp no replacement material data for " << replaceMatName << "\n" << std::flush;
			}

			delete [] replaceMatName;
		}

		//TODO: set ambient of material; doesn't come back from .X file
		object->mDrawOps[i].material.Ambient = object->mDrawOps[i].material.Diffuse;

		object->mDrawOps[i].texture.texture = NULL;
		if (materials[i].pTextureFilename || replaceTexName)
		{
			char textureFilename[512];

			if (replaceTexName)
			{
				sprintf(textureFilename, "VIDEO\\%s.png", replaceTexName);
				delete [] replaceTexName;
			} else
			{
				sprintf(textureFilename, "VIDEO\\%s", materials[i].pTextureFilename);
			}
			object->mDrawOps[i].texture = LoadTexture(device, textureFilename);
		}

		if (nextDetailOp < numDetailOps && detailOps[nextDetailOp] == i)
		{
			object->mDrawOps[i].drawAsDecal = true;
			nextDetailOp++;
		}

		if (abs(object->mDrawOps[i].material.Diffuse.a - 1.0f) > 0.0001f)
		{
			object->mDrawOps[i].alphaTest = true;
		}

		object->mDrawOps[i].drawAsSky = isSky;

		if (isSky)
		{
			object->mDrawOps[i].material.Ambient.r *= renderer->GetCloudRed();
			object->mDrawOps[i].material.Ambient.g *= renderer->GetCloudGreen();
			object->mDrawOps[i].material.Ambient.b *= renderer->GetCloudBlue();
			object->mDrawOps[i].material.Diffuse.r *= renderer->GetCloudRed();
			object->mDrawOps[i].material.Diffuse.g *= renderer->GetCloudGreen();
			object->mDrawOps[i].material.Diffuse.b *= renderer->GetCloudBlue();
			object->mDrawOps[i].material.Emissive.r = renderer->GetCloudEmitRed();
			object->mDrawOps[i].material.Emissive.g = renderer->GetCloudEmitGreen();
			object->mDrawOps[i].material.Emissive.b = renderer->GetCloudEmitBlue();
		}
	}

	materialBuffer->Release();

	return object;
}

d3d_OBJECT* d3d_OBJECT::LoadSpheres(LPDIRECT3DDEVICE9 device, char *fileName)
{
	FILE *file;
	float emissionColor[3];
	int vertexCount;

	if ((file = fopen(fileName, "rb")) == NULL)
		return NULL;

	fread(emissionColor, sizeof(float), 3, file);
	fread(&vertexCount, sizeof(int), 1, file);

	d3d_OBJECT *object = new d3d_OBJECT(device, vertexCount);
	void *buffer = NULL;

	object->mVB->Lock(0, 0, &buffer, 0);

	fread(buffer, sizeof(L4POINTVERTEX), vertexCount, file);

	D3DXVECTOR3 center(0, 0, 0);
	D3DXComputeBoundingSphere((D3DXVECTOR3*)buffer, vertexCount, sizeof(L4POINTVERTEX), &center, &object->mRadius);

	object->mVB->Unlock();

	object->mDrawOps[0].material.Emissive.r = emissionColor[0];
	object->mDrawOps[0].material.Emissive.g = emissionColor[1];
	object->mDrawOps[0].material.Emissive.b = emissionColor[2];
	object->mDrawOps[0].material.Emissive.a = 1.0f;

	fclose(file);

	return object;
}

L4TEXOP d3d_OBJECT::LoadTexture(LPDIRECT3DDEVICE9 device, const char *fileName)
{
	L4TEXOP texOp;
	FILE *file;
	if (mTextureCache.find(fileName) == mTextureCache.end())
	{
		struct
		{	unsigned char minify;
			unsigned char magnify;
			unsigned char alpha;
			unsigned char wrap_u;
			unsigned char wrap_v;
			bool doScroll;
			float scrollUDelta;
			float scrollVDelta;
		} fileTexOp;

		char metFileName[512];
		strcpy(metFileName, fileName);
		chgext(metFileName, "met");

		memset(&texOp, 0, sizeof(L4TEXOP));
		if ((file = fopen(metFileName, "rb")) != NULL)
		{
			fread(&fileTexOp, sizeof(fileTexOp), 1, file);
			fclose(file);

			texOp.wrap_u = (L4TEXOP::WrapType)fileTexOp.wrap_u;
			texOp.wrap_v = (L4TEXOP::WrapType)fileTexOp.wrap_v;
			texOp.doScroll = fileTexOp.doScroll;
			texOp.scrollUDelta = fileTexOp.scrollUDelta;
			texOp.scrollVDelta = fileTexOp.scrollVDelta;
		}
		
		HRESULT hr = D3DXCreateTextureFromFileA(device, fileName, &texOp.texture);
		if (FAILED(hr))
		{
			// texOp.texture stays NULL (memset above); draw ops render untextured,
			// matching the "no texture filename" path in LoadObject.
			DEBUG_STREAM << "L4D3D.cpp couldn't load texture " << fileName << " (hr=0x" << std::hex << hr << std::dec << ")\n" << std::flush;
		}

		mTextureCache.insert(std::pair<std::string,L4TEXOP>(std::string(fileName), texOp));
	}
	else
		texOp = mTextureCache[std::string(fileName)];

	if (texOp.texture != NULL)
		texOp.texture->AddRef();
	return texOp;
}

void d3d_OBJECT::FlushTextureCache()
{
	stdext::hash_map<std::string, L4TEXOP>::iterator iter;
	for (iter = mTextureCache.begin(); iter != mTextureCache.end(); ++iter)
	{
		if ((*iter).second.texture != NULL)
			(*iter).second.texture->Release();
	}
	mTextureCache.clear();
}

d3d_OBJECT::d3d_OBJECT(LPDIRECT3DDEVICE9 device, int vertCount)
  : mDevice(device),
	mVertCount(vertCount),
	mDrawOpCount(1),
	mImmune(false),
	mVB(NULL),
	mMesh(NULL),
	mDrawOps(NULL),
	mLocalToWorld(),
	mOrigin(),
	mRadius(0.0f),
	mID(mNextID++)
{
	HRESULT hr;

	hr = device->CreateVertexBuffer(vertCount * sizeof(L4POINTVERTEX), D3DUSAGE_WRITEONLY, L4POINTVERTEX_FVF, D3DPOOL_MANAGED, &mVB, NULL);

	mDrawOps = new L4DRAWOP();
	memset(mDrawOps, 0, sizeof(L4DRAWOP));

	memset(mNext, 0, sizeof(mNext));
	memset(mPrev, 0, sizeof(mPrev));

	D3DXMatrixIdentity(&mLocalToWorld);
}

d3d_OBJECT::d3d_OBJECT(LPDIRECT3DDEVICE9 device, LPD3DXMESH mesh, DWORD *adjacencyBuffer, int drawOpCount)
  : mDevice(device),
	mVertCount(-1),
	mDrawOpCount(drawOpCount),
	mImmune(false),
	mVB(NULL),
	mMesh(mesh),
	mLocalToWorld(),
	mOrigin(),
	mRadius(0.0f),
	mID(mNextID++)
{
	mDrawOps = new L4DRAWOP[drawOpCount];
	memset(mDrawOps, 0, sizeof(L4DRAWOP) * drawOpCount);

	if (adjacencyBuffer != NULL)
	{
		//Null adjacency buffer means the mesh should have already been optimized
		mesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_VERTEXCACHE, adjacencyBuffer, NULL, NULL, NULL);
	}

	DWORD vertCount = mesh->GetNumVertices();
	DWORD vertStride = mesh->GetNumBytesPerVertex();
	
	D3DXVECTOR3 *data;
	D3DXVECTOR3 center(0, 0, 0);
	mesh->LockVertexBuffer(D3DLOCK_READONLY, (LPVOID*)&data);
	D3DXComputeBoundingSphere(data, vertCount, vertStride, &center, &mRadius);
	mesh->UnlockVertexBuffer();

	memset(mNext, 0, sizeof(mNext));
	memset(mPrev, 0, sizeof(mPrev));

	D3DXMatrixIdentity(&mLocalToWorld);
}

d3d_OBJECT::~d3d_OBJECT()
{
	if (mVB)
	{
		mVB->Release();
		mVB = NULL;
	}
	if (mDrawOps)
	{
		delete[] mDrawOps;
		mDrawOps = NULL;
	}
}

void d3d_OBJECT::Draw(int pass, const D3DXMATRIX *viewTransform, Time targetRenderFrame)
{
	// set our world transform
	mDevice->SetTransform(D3DTS_WORLD, &mLocalToWorld);

	if (mMesh != NULL && pass != PASS_SPHERE)
		return DrawMesh(pass, viewTransform, targetRenderFrame);
	else if (mMesh == NULL && mVB != NULL && pass == PASS_SPHERE)
		return DrawSpheres(pass, viewTransform);
}

void d3d_OBJECT::DrawMesh(int pass, const D3DXMATRIX *viewTransform, Time targetRenderFrame)
{
	for (int iOp = 0; iOp < mDrawOpCount; iOp++)
	{
		L4DRAWOP *drawOp = &mDrawOps[iOp];

		if (drawOp->alphaTest != (pass == PASS_ALPHABLEND))
			continue;

		if (drawOp->drawAsDecal != (pass == PASS_DECAL))
			continue;

		if (drawOp->drawAsSky != (pass == PASS_SKY))
			continue;

		mDevice->SetMaterial(&drawOp->material);

#ifndef RP3_EMULATE
		SetTextureScrolling(&(drawOp->texture), targetRenderFrame);
		SetTexture(drawOp->texture.texture);
#endif

		gNumBatches++;
		mMesh->DrawSubset(iOp);
	}
}

void d3d_OBJECT::DrawSpheres(int pass, const D3DXMATRIX *viewTransform)
{
	mDevice->SetFVF(L4POINTVERTEX_FVF);
	mDevice->SetStreamSource(0, mVB, 0, sizeof(L4POINTVERTEX));
	mDevice->SetMaterial(&mDrawOps[0].material);
	SetTexture(NULL);
	mDevice->DrawPrimitive(D3DPT_POINTLIST, 0, mVertCount);
}

void d3d_OBJECT::SetTextureScrolling(const L4TEXOP *texture, Time targetRenderFrame)
{
	if (d3d_OBJECT::mLastTextureScrollState != texture->doScroll)
	{
		// the texture scrolling state has changed
		if (texture->doScroll)
		{
			mDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
			SetTextureAddressing(texture->wrap_u, texture->wrap_v);
		}
		else
		{
			mDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
			SetTextureAddressing(L4TEXOP::WrapType::REPEAT, L4TEXOP::WrapType::REPEAT);
		}
		d3d_OBJECT::mLastTextureScrollState = texture->doScroll;
	}

	// if the texture is supposed to be scrolled then
	// we have to set the transforms regardless of state
	if (texture->doScroll)
	{
		Scalar time = (Scalar)targetRenderFrame;

		D3DXMATRIX translate;
		D3DXMatrixIdentity(&translate);
		translate._31 = -texture->scrollUDelta * time;
		translate._32 = texture->scrollVDelta * time;
		mDevice->SetTransform(D3DTS_TEXTURE0, &translate);
	} 
}

void d3d_OBJECT::SetTextureAddressing(L4TEXOP::WrapType wrap_u, L4TEXOP::WrapType wrap_v)
{
	if (wrap_u != d3d_OBJECT::mLastWrapU)
	{
		d3d_OBJECT::mLastWrapU = wrap_u;
		if (wrap_u == L4TEXOP::REPEAT)
			mDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
		else if (wrap_u == L4TEXOP::CLAMP)
			mDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
		else if (wrap_u == L4TEXOP::SELECT)
			mDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
	}
	if (wrap_v != d3d_OBJECT::mLastWrapV)
	{
		d3d_OBJECT::mLastWrapV = wrap_v;
		if (wrap_v == L4TEXOP::REPEAT)
			mDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
		else if (wrap_v == L4TEXOP::CLAMP)
			mDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
		else if (wrap_v == L4TEXOP::SELECT)
			mDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
	}
}

void d3d_OBJECT::SetTexture(LPDIRECT3DTEXTURE9 texture)
{
	bool texturingOn = (texture != NULL);
	if (texturingOn != d3d_OBJECT::mLastTexturingState)
	{
		d3d_OBJECT::mLastTexturingState = texturingOn;
		if (texturingOn)
		{
			mDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
			mDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
			mDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
		}
		else
			mDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_DISABLE);
	}
	if (texture != NULL)
	{
		mDevice->SetTexture(0, texture);
	}
}