#include "mesh.h"

using namespace std;
using namespace kLibrary;
using namespace cbk;

namespace CBK
{
	Mesh::Mesh(const MeshParam& params, const std::string& name) : item(name, params), _bb(Vector(), Vector())
	{
		_subsets = 0;
		_mesh = 0;
		_yOffset = 0.0f;

		setDevice(params.device);
		setMaterialLoader(params.materialLoader);
		setTextureLoader(params.textureLoader);
	}

	Mesh::~Mesh(void)
	{
		d3d::Release<ID3DXMesh*>(_mesh);

		const size_t numMaterials = _materials.size();
		const size_t numTextures = _textures.size();

		for (register size_t x = 0; x < numMaterials; x++)
			_materialLoader->unload(_materials.at(x));

		for (register size_t x = 0; x < numTextures; x++)
			_textureLoader->unload(_textures.at(x));
	}

	Mesh& Mesh::reset(void)
	{
		if (_mesh != 0)
			_mesh->Release();
		_mesh = 0;
		load(getName(), MeshParam(_device, _materialLoader, _textureLoader));
		return *this;
	}

	/* http://www.chadvernon.com/blog/tutorials/directx9/frustum-culling/ */
	void Mesh::calcBoundaries(void)
	{
		D3DXVECTOR3 center;

		D3DXVECTOR3 min, max;

		if (_mesh != 0)
		{
			DWORD numVertices = _mesh->GetNumVertices();
			DWORD fvfSize = D3DXGetFVFVertexSize( _mesh->GetFVF() );
			char* pData = NULL;
			_mesh->LockVertexBuffer( 0, (void**)&pData);
			D3DXComputeBoundingSphere( (D3DXVECTOR3*)pData, numVertices, fvfSize, &center, &_CollideSphereRadius );
			D3DXComputeBoundingBox( (D3DXVECTOR3*)pData, numVertices, fvfSize, &min, &max );
			_bb.setBoundaries(cbk::Vector(min.x, min.y, min.z), cbk::Vector(max.x, max.y, max.z));
			_mesh->UnlockVertexBuffer();
		}
	}

	const float& Mesh::getCollisionSphereRadius(void) const
	{
		return _CollideSphereRadius;
	}

	const BoundingBox& Mesh::getBoundingBox(void) const
	{
		return _bb;
	}

	const float& Mesh::getTerrainYOffset(void) const
	{
		return _yOffset;
	}

	Mesh& Mesh::setTerrainYOffset(const float& yOffset)
	{
		_yOffset = yOffset;
		return *this;
	}
	
	Mesh& Mesh::setDevice(IDirect3DDevice9* device)
	{
		_device = device;

		return *this;
	}

	IDirect3DDevice9* Mesh::getDevice(void)
	{
		return _device;
	}

	void Mesh::apply(void)
	{
		for (register DWORD x = 0; x < _subsets; x++)
			draw(x);
	}

	void Mesh::draw(const DWORD& subset)
	{
		const size_t numMaterials = _materials.size();
		const size_t numTextures = _textures.size();

		if (numMaterials == 1)
			_materials.at(0)->apply();
		else if (numMaterials == 0)
			CBK::Material::clear(_device);
		else if (numMaterials >= subset)
			_materials.at(subset)->apply();
		else
			CBK::Material::clear(_device);

		if (numTextures == 1)
			_textures.at(0)->apply();
		else if (numTextures == 0)
			CBK::Texture::clear(_device);
		else if (numTextures >= subset)
			_textures.at(subset)->apply();
		else
			CBK::Texture::clear(_device);

		if (_mesh != 0)
			_mesh->DrawSubset(DWORD(subset));
	}

	Mesh& Mesh::drawBoundingBox(void)
	{
		_bb.draw(_device);

		return *this;
	}

	const size_t& Mesh::getSubsetCount(void) const
	{
		return _subsets;
	}

	//Allow material setting
	materialFactory* Mesh::getMaterialLoader(void) const
	{
		return _materialLoader;
	}

	Mesh& Mesh::setMaterialLoader(materialFactory* materialLoader)
	{
		const size_t numMaterials = _materials.size();

		for (register size_t x = 0; x < numMaterials; x++)
		{
			Material* cMaterial = _materials.at(x);
			if (cMaterial != 0)
			{
				const string cName = cMaterial->getName();
				_materialLoader->unload(cMaterial);
				_materials.at(x) = reinterpret_cast<CBK::Material*>(_materialLoader->load(cName, _device));
			}
		}

		_materialLoader = materialLoader;
		
		return *this;
	}

	Mesh& Mesh::setMaterial(const std::string& name, const size_t& subset)
	{
		if (_materialLoader == 0) return *this;

		if (_materials.size() <= subset)
			_materials.resize(subset + 1);

		Material* cMaterial = _materials.at(subset);
		if (cMaterial != 0)
			_materialLoader->unload(cMaterial);

		_materials.at(subset) = _materialLoader->load(name, _device);

		return *this;
	}

	const Material* Mesh::getMaterial(const size_t& subset) const
	{
		if (size_t(_materials.size()) <= subset)
			return 0;

		return _materials.at(subset);
	}

	//Allow texture setting
	Mesh& Mesh::setTextureLoader(textureFactory* textureLoader)
	{
		const size_t numTextures = _textures.size();

		for (register size_t x = 0; x < numTextures; x++)
		{
			Texture* cTexture = _textures.at(x);
			if (cTexture != 0)
			{
				const string& cName = cTexture->getName();
				_textureLoader->unload(cTexture);
				_textures.at(x) = _textureLoader->load(cName, _device);
			}
		}

		_textureLoader = textureLoader;
		
		return *this;
	}

	textureFactory* Mesh::getTextureLoader(void) const
	{
		return this->_textureLoader;
	}

	Mesh& Mesh::setTexture(const std::string& name, const size_t& subset)
	{
		if (_textureLoader == 0) return *this;

		if (_textures.size() <= subset)
			_textures.resize(subset + 1);

		Texture* cTexture = _textures.at(subset);
		if (cTexture != 0)
			_textureLoader->unload(cTexture);

		_textures.at(subset) = reinterpret_cast<CBK::Texture*>(_textureLoader->load(name, _device));

		return *this;
	}

	const Texture* Mesh::getTexture(const size_t& subset) const
	{
		if (size_t(_textures.size()) <= subset)
			return 0;

		return _textures.at(subset);
	}
}