// Copyright (C) 2002-2009 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
//
// This file was written by Saurav Mohapatra and modified by Nikolaus Gebhardt.
// See CCSMLoader.h for details.

#include "IrrCompileConfig.h"
#ifdef _IRR_COMPILE_WITH_CSM_LOADER_

#include "CCSMLoader.h"
#include "os.h"
#include "IFileSystem.h"
#include "IReadFile.h"
#include "ISceneManager.h"
#include "IAttributes.h"
#include "SMesh.h"
#include "IVideoDriver.h"
#include "SAnimatedMesh.h"
#include "SMeshBufferLightMap.h"

namespace irr
{
namespace scene
{
	//
	//	the CSM data types
	//
	struct color_rgb_t
	{
		s32 red;
		s32 green;
		s32 blue;

		void clear()
		{
			red = 0;
			green = 0;
			blue = 0;
		}
	};

	class BinaryFileReader;

	//
	//	The file header
	//
	class Header
	{
	public:

		static const s32 VERSION_4;
		static const s32 VERSION_4_1;

		Header(){ clear(); }
		virtual ~Header(){ clear(); }

		s32 getVersion() const{ return version; }
		void clear(){ version = 0; }
		void load(BinaryFileReader* pReader);

	private:

		s32 version;
	};


	//
	//	The groups
	//
	class Group
	{
	public:

		Group(){ clear(); }
		virtual ~Group(){ clear(); }

		void clear();
		void load(BinaryFileReader* pReader);

		s32 getFlags() const { return flags; }
		s32 getParentGroupID() const { return parentGroup; }
		const core::stringc& getProperties() const { return props; }
		const color_rgb_t* getColor() const { return &color; }

	private:

		s32 flags;
		s32 parentGroup;
		core::stringc props;
		color_rgb_t color;
	};


	//
	//	The visgroups
	//
	class VisGroup
	{
	public:

		VisGroup(){ clear(); }
		virtual ~VisGroup(){ clear(); }
		void clear();
		void load(BinaryFileReader* pReader);

		s32 getFlags() const{ return flags; }
		const core::stringc& getName() const{ return name; }
		const color_rgb_t* getColor() const{ return &color; }

	private:

		core::stringc name;
		s32 flags;
		color_rgb_t color;
	};


	//
	//	Lightmaps
	//
	class LightMap
	{
	public:

		LightMap() : pixelData(0){ clear(); }
		virtual ~LightMap(){ clear(); }
		void clear();
		void load(BinaryFileReader* pReader);
		s32 getWidth() const{ return width; }
		s32 getHeight() const{ return height; }
		const s32* getPixelData() const{ return pixelData; }

	private:

		s32 width;
		s32 height;
		s32* pixelData;
	};

	struct Triangle
	{
		s32 a,b,c;
	};


	struct Line
	{
		s32 a,b;
	};


	class Vertex
	{
	public:

		Vertex(){ clear(); }
		virtual ~Vertex(){ clear(); }
		void clear();
		void load(BinaryFileReader* pReader);

		const core::vector3df* getPosition() const { return &position; }
		const core::vector3df* getNormal() const { return &normal; }
		const color_rgb_t* getColor() const { return &color; }
		const core::vector3df* getTextureCoordinates() const { return &texCoords; }
		const core::vector3df* getLightMapCoordinates() const { return &lmapCoords; }

	private:

		core::vector3df position;
		core::vector3df normal;
		color_rgb_t color;
		core::vector3df texCoords;
		core::vector3df lmapCoords;
	};


	class Surface
	{
	public:

		Surface() { clear(); }
		virtual ~Surface(){ clear(); }

		void clear();
		void load(BinaryFileReader *pReader);

		s32 getFlags() const{ return flags; }
		const core::stringc& getTextureName() const{ return textureName; }
		s32 getLightMapId() const{ return lightMapId; }
		const core::vector2df* getUVOffset() const{ return &uvOffset; }
		const core::vector2df* getUVScale() const{ return &uvScale; }
		f32 getUVRotation() const{ return uvRotation; }

		u32 getVertexCount() const{ return vertices.size(); }
		const Vertex* getVertexAt(const s32 index) const{ return vertices[index]; }

		s32 getTriangleCount() const{ return triangles.size(); }
		const Triangle& getTriangleAt(const s32 index) const{ return triangles[index]; }

	private:

		s32 flags;
		core::stringc textureName;
		s32 lightMapId;
		core::vector2df uvOffset;
		core::vector2df uvScale;
		f32 uvRotation;
		core::array<Vertex*> vertices;
		core::array<Triangle> triangles;
		core::array<Line> lines;
	};

	class Mesh
	{
	public:

		Mesh(){ clear(); }
		virtual ~Mesh(){ clear(); }

		void clear();
		void load(BinaryFileReader* pReader, bool bReadVisGroups);

		s32 getFlags() const { return flags; }
		s32 getGroupID() const { return groupId; }
		const core::stringc& getProperties() const { return props; }
		const color_rgb_t* getColor() const { return &color; }
		const core::vector3df* getPosition() const { return &position; }
		s32 getVisgroupID() const { return visgroupId; }
		s32 getSurfaceCount() const { return surfaces.size(); }
		const Surface* getSurfaceAt(const s32 index) const { return surfaces[index]; }

	private:

		s32 flags;
		s32 groupId;
		core::stringc props;
		color_rgb_t color;
		core::vector3df position;
		s32 visgroupId;

		core::array<Surface*> surfaces;
	};

	class Entity
	{
	public:

		Entity() { clear(); }
		virtual ~Entity() { clear(); }

		void clear();
		void load(BinaryFileReader* pReader);
		s32 getVisgroupID() const { return visgroupId; }
		s32 getGroupID() const { return groupId; }
		const core::stringc& getProperties() const { return props; }
		const core::vector3df* getPosition() const { return &position; }

	private:

		s32 visgroupId;
		s32 groupId;
		core::stringc props;
		core::vector3df position;
	};


	class CameraData
	{
	public:

		CameraData(){ clear(); }
		virtual ~CameraData(){ clear(); }

		void clear();
		void load(BinaryFileReader* pReader);

		const core::vector3df* getPosition(){ return &position; }
		f32 getPitch(){ return pitch; }
		f32 getYaw(){ return yaw; }

	private:

		core::vector3df position;
		f32 pitch;
		f32 yaw;
	};

	//
	//	A CSM File
	//
	class CSMFile
	{
	public:

		CSMFile(){ clear(); }
		virtual ~CSMFile(){ clear(); }
		void clear();
		void load(BinaryFileReader* pReader);

		const Header* getHeader() const{ return &header; }

		s32 getGroupCount() const{ return groups.size(); }
		const Group* getGroupAt(const s32 index) const{ return groups[index]; }

		s32 getVisGroupCount() const{ return visgroups.size(); }
		const VisGroup* getVisGroupAt(const s32 index) const{ return visgroups[index]; }

		s32 getLightMapCount() const{ return lightmaps.size(); }
		const LightMap* getLightMapAt(const s32 index) const { return lightmaps[index]; }

		s32 getMeshCount() const{ return meshes.size(); }
		const Mesh* getMeshAt(const s32 index) const{ return meshes[index]; }

		s32 getEntityCount() const{ return entities.size(); }
		const Entity* getEntityAt(const s32 index) const{ return entities[index]; }

		const CameraData* getCameraData() const{ return &cameraData; }

	private:

		Header header;
		core::array<Group*> groups;
		core::array<VisGroup*> visgroups;
		core::array<LightMap*> lightmaps;
		core::array<Mesh*> meshes;
		core::array<Entity*> entities;
		CameraData cameraData;
	};


	//
	//	A Binary File Reader
	//
	class BinaryFileReader
	{
	public:

		BinaryFileReader(io::IReadFile* pFile, bool closeWhenDone=true);
		virtual ~BinaryFileReader();

		virtual s32 readBuffer(void* buffer, s32 len);

		s32 readLong();
		f32 readFloat();
		u8  readByte();

		core::stringc readString();
		void readVec3f(core::vector3df* v);
		void readVec2f(core::vector2df* v);
		void readColorRGB(color_rgb_t* color);

	private:

		io::IReadFile *file;
		bool autoClose;

	};

	CCSMLoader::CCSMLoader(scene::ISceneManager* manager, io::IFileSystem* fs)
		: FileSystem(fs), SceneManager(manager)
	{

		#ifdef _DEBUG
		setDebugName("CCSMLoader");
		#endif
	}

	CCSMLoader::~CCSMLoader()
	{
	}

	//! returns true if the file maybe is able to be loaded by this class
	//! based on the file extension (e.g. ".bsp")
	bool CCSMLoader::isALoadableFileExtension(const core::string<c16>& filename) const
	{
		return core::hasFileExtension ( filename, "csm" );
	}

	//! creates/loads an animated mesh from the file.
	IAnimatedMesh* CCSMLoader::createMesh(io::IReadFile* file)
	{
		file->grab(); // originally, this loader created the file on its own.

		scene::IMesh* m = createCSMMesh(file);

		if (!m)
			return 0;

		SAnimatedMesh* am = new SAnimatedMesh();
		am->Type = EAMT_CSM;
		am->addMesh(m);
		m->drop();

		am->recalculateBoundingBox();
		return am;
	}

	scene::IMesh* CCSMLoader::createCSMMesh(io::IReadFile* file)
	{
		if (!file)
			return 0;

		if(file)
		{
			BinaryFileReader reader(file, true);
			CSMFile csmFile;
			csmFile.load(&reader);

			scene::IMesh* pMesh = createIrrlichtMesh(&csmFile,
				SceneManager->getParameters()->getAttributeAsString(CSM_TEXTURE_PATH),
				core::stringc ( file->getFileName() ).c_str() );
			return pMesh;
		}

		return 0;
	}

	scene::IMesh* CCSMLoader::createIrrlichtMesh(const CSMFile* csmFile,
		core::stringc textureRoot, const c8* lmprefix)
	{
		scene::SMesh *pMesh = new scene::SMesh();
		video::IVideoDriver* driver = SceneManager->getVideoDriver();

		for(s32 l = 0; l<csmFile->getLightMapCount(); l++)
		{
			const LightMap* lmap = csmFile->getLightMapAt(l);

			core::stringc lmapName = lmprefix;
			lmapName += "LMAP_";
			lmapName += (int)(l+1);
			os::Printer::log("CCSMLoader loading light map", lmapName.c_str());

			video::IImage* lmapImg = driver->createImageFromData(
				video::ECF_A8R8G8B8,
				core::dimension2d<u32>(lmap->getWidth(),lmap->getHeight()),
				(void *)(lmap->getPixelData()));

			driver->addTexture(lmapName.c_str(), lmapImg);
			lmapImg->drop();

		}

		for(s32 m = 0; m<csmFile->getMeshCount(); m++)
		{
			const Mesh* mshPtr = csmFile->getMeshAt(m);

			for(s32 s = 0; s < mshPtr->getSurfaceCount(); s++)
			{
				const Surface* surface = mshPtr->getSurfaceAt(s);

				core::stringc texName = textureRoot;
				texName+= "/";
				texName+= surface->getTextureName();

				video::ITexture* texture = driver->getTexture(texName);
				scene::SMeshBufferLightMap *buffer = new scene::SMeshBufferLightMap();

				//material
				core::stringc lmapName = lmprefix;
				lmapName += "LMAP_";
				lmapName += (int)surface->getLightMapId();

				buffer->Material.setTexture(0, texture);
				buffer->Material.setTexture(1, driver->getTexture(lmapName));
				buffer->Material.Lighting = false;
				buffer->Material.MaterialType = video::EMT_LIGHTMAP_M4;

				for(u32 v = 0; v < surface->getVertexCount(); v++)
				{
					const Vertex *vtxPtr = surface->getVertexAt(v);
					video::S3DVertex2TCoords vtx;
					vtx.Pos = *(vtxPtr->getPosition());
					vtx.Normal = *(vtxPtr->getPosition());
					vtx.Color.set(255,vtxPtr->getColor()->red,vtxPtr->getColor()->green,vtxPtr->getColor()->blue);
					vtx.TCoords.set(vtxPtr->getTextureCoordinates()->X,vtxPtr->getTextureCoordinates()->Y);
					vtx.TCoords2.set(vtxPtr->getLightMapCoordinates()->X,0.0f - vtxPtr->getLightMapCoordinates()->Y);

					buffer->Vertices.push_back(vtx);
				}

				for(s32 t = 0; t < surface->getTriangleCount(); t++)
				{
					const Triangle& tri = surface->getTriangleAt(t);
					buffer->Indices.push_back(tri.a);
					buffer->Indices.push_back(tri.c);
					buffer->Indices.push_back(tri.b);
				}

				buffer->recalculateBoundingBox();
				pMesh->addMeshBuffer(buffer);
				buffer->drop();
			}
		}

		pMesh->recalculateBoundingBox();
		return pMesh;
	}

	const s32 Header::VERSION_4 = 4;
	const s32 Header::VERSION_4_1 = 5;

	void Header::load(BinaryFileReader* pReader)
	{
		version = pReader->readLong();
	}

	void Group::clear()
	{
		color.clear();
		flags = 0;
		parentGroup = 0;
		props = "";
	}

	void Group::load(BinaryFileReader* pReader)
	{
		flags = pReader->readLong();
		parentGroup = pReader->readLong();
		props = pReader->readString();
		pReader->readColorRGB(&color);
	}

	void VisGroup::clear()
	{
		color.clear();
		flags = 0;
		name = "";

	}

	void VisGroup::load(BinaryFileReader* pReader)
	{
		name = pReader->readString();
		flags = pReader->readLong();
		pReader->readColorRGB(&color);
	}

	void LightMap::clear()
	{
		if(pixelData)
		{
			delete[] pixelData;
			pixelData = 0;
		}
		width = height = 0;
	}

	void LightMap::load(BinaryFileReader* pReader)
	{
		width = pReader->readLong();
		height = pReader->readLong();
		pixelData = new s32[width * height];
		pReader->readBuffer(pixelData, width * height * sizeof(s32));
	}

	void Mesh::clear()
	{
		flags = 0;
		groupId = 0;
		visgroupId = 0;
		props = "";
		color.clear();
		position.set(0,0,0);

		for(u32 s = 0; s < surfaces.size(); s++)
		{
			if(surfaces[s])
			{
				delete surfaces[s];
			}
		}
		surfaces.clear();
	}

	void Mesh::load(BinaryFileReader* pReader, bool bReadVisGroups)
	{
		flags = pReader->readLong();
		groupId = pReader->readLong();
		props = pReader->readString();
		pReader->readColorRGB(&color);
		pReader->readVec3f(&position);
		if(bReadVisGroups)
			visgroupId = pReader->readLong();
		else
			visgroupId = 0;

		s32 count = pReader->readLong();

		for(s32 i = 0; i < count; i++)
		{
			Surface* surf = new Surface();
			surf->load(pReader);
			surfaces.push_back(surf);
		}
	}

	void Surface::clear()
	{
		flags = 0;
		lightMapId = 0;
		textureName = "";
		uvOffset.set(0.0f,0.0f);
		uvScale.set(0.0f,0.0f);
		uvRotation = 0.0f;
		triangles.clear();
		lines.clear();

		for(u32 v = 0; v < vertices.size(); v++)
			delete vertices[v];

		vertices.clear();
	}

	void Surface::load(BinaryFileReader* pReader)
	{
		flags = pReader->readLong();
		textureName = pReader->readString();

		lightMapId = pReader->readLong();
		pReader->readVec2f(&uvOffset);
		pReader->readVec2f(&uvScale);
		uvRotation = pReader->readFloat();
		s32 vtxCount = pReader->readLong();
		s32 triCount = pReader->readLong();
		s32 lineCount = pReader->readLong();

		for(s32 v = 0; v < vtxCount; v++)
		{
			Vertex *vtx = new Vertex();
			vtx->load(pReader);
			vertices.push_back(vtx);
		}

		for(s32 t = 0; t < triCount; t++)
		{
			Triangle tri;
			pReader->readBuffer(&tri, sizeof(tri));
			triangles.push_back(tri);
		}

		for(s32 l = 0; l < lineCount; l++)
		{
			Line line;
			pReader->readBuffer(&line,sizeof(line));
			lines.push_back(line);

		}

	}

	void Vertex::clear()
	{
		position.set(0,0,0);
		normal.set(0,0,0);
		color.clear();
		texCoords.set(0,0,0);
		lmapCoords.set(0,0,0);
	}

	void Vertex::load(BinaryFileReader* pReader)
	{
		pReader->readVec3f(&position);
		pReader->readVec3f(&normal);
		pReader->readColorRGB(&color);
		pReader->readVec3f(&texCoords);
		pReader->readVec3f(&lmapCoords);
	}

	void Entity::clear()
	{
		visgroupId = groupId = 0;
		props = "";
		position.set(0,0,0);
	}

	void Entity::load(BinaryFileReader* pReader)
	{
		visgroupId = pReader->readLong();
		groupId = pReader->readLong();
		props = pReader->readString();
		pReader->readVec3f(&position);
	}

	void CameraData::clear()
	{
		position.set(0,0,0);
		pitch = 0;
		yaw = 0;
	}

	void CameraData::load(BinaryFileReader* pReader)
	{
		pReader->readVec3f(&position);
		pitch = pReader->readFloat();
		yaw = pReader->readFloat();
	}

	void CSMFile::clear()
	{
		header.clear();
		cameraData.clear();

		u32 x =0;
		for( x= 0; x < groups.size(); x++)
			delete groups[x];

		groups.clear();

		for(x= 0; x < visgroups.size(); x++)
			delete visgroups[x];

		visgroups.clear();

		for(x= 0; x < lightmaps.size(); x++)
			delete lightmaps[x];

		lightmaps.clear();

		for(x= 0; x < meshes.size(); x++)
			delete meshes[x];

		meshes.clear();

		for(x= 0; x < entities.size(); x++)
			delete entities[x];

		entities.clear();
	}

	void CSMFile::load(BinaryFileReader* pReader)
	{
		clear();

		header.load(pReader);

		//groups
		{
			s32 count = pReader->readLong();

			for (s32 i = 0; i < count; i++)
			{
				Group* grp = new Group();
				grp->load(pReader);
				groups.push_back(grp);
			}
		}
		bool bHasVGroups = (header.getVersion() == Header::VERSION_4_1);

		if (bHasVGroups)
		{
			//visgroups
			s32 count = pReader->readLong();

			for (s32 i = 0; i < count; i++)
			{
				VisGroup* grp = new VisGroup();
				grp->load(pReader);
				visgroups.push_back(grp);
			}
		}

		//lightmaps
		{
			s32 count = pReader->readLong();

			for(s32 i = 0; i < count; i++)
			{
				LightMap* grp = new LightMap();
				grp->load(pReader);
				lightmaps.push_back(grp);
			}
		}

		//meshes
		{
			s32 count = pReader->readLong();

			for(s32 i = 0; i < count; i++)
			{
				Mesh* grp = new Mesh();
				grp->load(pReader,bHasVGroups);
				meshes.push_back(grp);
			}
		}

		//entities
		{
			s32 count = pReader->readLong();

			for(s32 i = 0; i < count; i++)
			{
				Entity* grp = new Entity();
				grp->load(pReader);
				entities.push_back(grp);
			}
		}

		//camera data
		cameraData.load(pReader);


	}

	BinaryFileReader::BinaryFileReader(io::IReadFile* pFile,
		bool closeWhenDone)
		: file(pFile), autoClose(closeWhenDone)
	{
	}

	BinaryFileReader::~BinaryFileReader()
	{
		if(autoClose && file)
		{
			file->drop();
			file = 0;

		}
	}

	s32 BinaryFileReader::readBuffer(void* buffer, s32 len)
	{
		return file->read(buffer,len);

	}

	s32 BinaryFileReader::readLong()
	{
		int ret = 0;
		readBuffer(&ret,sizeof(int));
		return (s32)ret;
	}

	f32 BinaryFileReader::readFloat()
	{
		float ret = 0;
		readBuffer(&ret,sizeof(float));
		return (f32)ret;
	}

	u8  BinaryFileReader::readByte()
	{
		char ret = 0;
		readBuffer(&ret,sizeof(char));
		return (u8)ret;
	}

	core::stringc BinaryFileReader::readString()
	{
		core::stringc str = "";
		c8 c = (c8)readByte();
		while(c != 0)
		{
			str += c;
			c = (c8)readByte();
		}
		return str;
	}

	void BinaryFileReader::readVec3f(core::vector3df* v)
	{
		v->X = readFloat();
		v->Y = readFloat();
		v->Z = readFloat();
	}

	void BinaryFileReader::readVec2f(core::vector2df* v)
	{
		v->X = readFloat();
		v->Y = readFloat();
	}

	void BinaryFileReader::readColorRGB(color_rgb_t* color)
	{
		readBuffer(color,sizeof(color_rgb_t));
	}

} // end namespace
} // end namespace

#endif // _IRR_COMPILE_WITH_CSM_LOADER_
