#include "PosScene.h"
#include <set>

#define INVALID_FILE -1

PosScene::PosScene(void)
{
}

bool PosScene::read(string filename)
{
	int lastSlash = filename.find_last_of("\\/");
	if(lastSlash == string::npos)
	{
		this->sceneDirectory = "./";
		this->filename = filename;
	}
	else
	{
		this->sceneDirectory = filename.substr(0,lastSlash+1);
		this->filename = filename.substr(lastSlash+1);
	}

	bool success = false;
	igzstream iStream(filename.c_str());

	int version = readHeader(iStream);

	switch(version)
	{
	case 1:
		success = read_v1(iStream);
		break;
	case 2:
		success = read_v2(iStream);
		break;
	default:
		break;
	}

	iStream.close();
	return success;
}

int PosScene::readHeader(igzstream &iStream)
{
	char magicBytesReference[13] = {'p', '0', '5', '3', '1', 'd', '0', 'n', 'm', '0', 'd', '3', 'l'};
	
	// read magic bytes
	char magicBytes[13];
	iStream.read(magicBytes, 13*sizeof(char));
	if(iStream.fail())
		return INVALID_FILE;

	// check magic bytes
	if(memcmp(magicBytes, magicBytesReference, 13*sizeof(char)) != 0)
		return INVALID_FILE;

	// read version
	int version;
	iStream.read(reinterpret_cast<char*>(&version), sizeof(int));
	if(iStream.fail())
		return INVALID_FILE;
	return version;
}

bool PosScene::read_v1(igzstream &iStream)
{
	map<string, PosMaterial*> materials;
	map<string, PosGeometry*> geometries;
	map<string, PosModel*> models;
	set<PosSceneEntity*> sceneEntities;

	bool success = true;

	// read materials
	int materialCount;
	iStream.read(reinterpret_cast<char*>(&materialCount), sizeof(int));
	success = !iStream.fail();
	for(int i=0; success && i<materialCount; i++)
	{
		PosMaterial* material = readMaterial_v1(iStream);
		if(!material)
		{
			success = false;
			break;
		}
		addMaterial(material);
		materials[material->getName()] = material;
	}

	// read geometries
	int geometryCount;
	iStream.read(reinterpret_cast<char*>(&geometryCount), sizeof(int));
	success = !iStream.fail();
	for(int i=0; success && i<geometryCount; i++)
	{
		PosGeometry* geometry = readGeometry_v1(iStream);
		if(!geometry)
		{
			success = false;
			break;
		}
		addGeometry(geometry);
		geometries[geometry->getName()] = geometry;
	}

	// read models
	int modelCount;
	iStream.read(reinterpret_cast<char*>(&modelCount), sizeof(int));
	success = !iStream.fail();
	for(int i=0; success && i<modelCount; i++)
	{
		PosModel* model = readModel_v1(iStream, geometries, materials);
		if(!model)
		{
			success = false;
			break;
		}
		addModel(model);
		models[model->getName()] = model;
	}

	// read scene entities
	int sceneEntityCount;
	iStream.read(reinterpret_cast<char*>(&sceneEntityCount), sizeof(int));
	success = !iStream.fail();
	for(int i=0; success && i<sceneEntityCount; i++)
	{
		PosSceneEntity* sceneEntity = readSceneEntity_v1(iStream, models);
		if(!sceneEntity)
		{
			success = false;
			break;
		}
		addSceneEntity(sceneEntity);
		sceneEntities.insert(sceneEntity);
	}
	if(!success)
	{
		for(map<string, PosMaterial*>::const_iterator it = materials.begin(); it != materials.end(); ++it)
			delete it->second;
		for(map<string, PosGeometry*>::const_iterator it = geometries.begin(); it != geometries.end(); ++it)
			delete it->second;
		for(map<string, PosModel*>::const_iterator it = models.begin(); it != models.end(); ++it)
			delete it->second;
		for(set<PosSceneEntity*>::const_iterator it = sceneEntities.begin(); it != sceneEntities.end(); ++it)
			delete (*it);
	}
	return success;
}

PosMaterial* PosScene::readMaterial_v1(igzstream &iStream)
{
	PosMaterial* material = getNewMaterial();

	// read name
	int sizeofName;
	iStream.read(reinterpret_cast<char*>(&sizeofName), sizeof(int));
	if(iStream.fail()) { delete material; return NULL; }
	char* name = new char[sizeofName+1];
	name[sizeofName] = (char)0;
	iStream.read(name, sizeofName*sizeof(char));
	if(iStream.fail()) { delete[] name; delete material; return NULL; }
	material->setName(string(name));
	delete[] name;

	// read ambient
	float ambient[4];
	iStream.read(reinterpret_cast<char*>(ambient), 4*sizeof(float));
	if(iStream.fail()) { delete material; return NULL; }
	material->setAmbientColor(ambient);

	// read diffuse
	float diffuse[4];
	iStream.read(reinterpret_cast<char*>(diffuse), 4*sizeof(float));
	if(iStream.fail()) { delete material; return NULL; }
	material->setDiffuseColor(diffuse);

	// read specular
	float specular[4];
	iStream.read(reinterpret_cast<char*>(specular), 4*sizeof(float));
	if(iStream.fail()) { delete material; return NULL; }

	// read emissive
	float emissive[4];
	iStream.read(reinterpret_cast<char*>(emissive), 4*sizeof(float));
	if(iStream.fail()) { delete material; return NULL; }

	material->setHasEmissive(true);
	material->setEmissiveColor(emissive);

	// read ks
	float ks;
	iStream.read(reinterpret_cast<char*>(&ks), sizeof(float));
	if(iStream.fail()) { delete material; return NULL; }
	
	material->setHasSpecular(true);
	material->setSpecularColor(specular, ks);

	// read the number of textures
	int numTextures;
	iStream.read(reinterpret_cast<char*>(&numTextures), sizeof(int));
	if(iStream.fail()) { delete material; return NULL; }
	material->setNumTextures(numTextures);

	// read textures
	for(int i=0; i<numTextures; i++)
	{
		// read textureFilename
		int sizeofTextureFilename;
		iStream.read(reinterpret_cast<char*>(&sizeofTextureFilename), sizeof(int));
		if(iStream.fail()) {delete material; return NULL; }

		char* textureFilename = new char[sceneDirectory.size() + sizeofTextureFilename+1];
		memcpy(textureFilename,sceneDirectory.c_str(),sceneDirectory.size()*sizeof(char));
		textureFilename[sceneDirectory.size() + sizeofTextureFilename] = (char)0;
		iStream.read(textureFilename + sceneDirectory.size(), sizeofTextureFilename*sizeof(char));
		if(iStream.fail()) {delete[] textureFilename; delete material; return NULL; }
		
		material->setTextureFilename(i, string(textureFilename));
		delete[] textureFilename;
	}

	return material;
}

PosGeometry* PosScene::readGeometry_v1(igzstream &iStream)
{
	PosGeometry* geometry = getNewGeometry();

	// read GeometryName
	int sizeofGeometryName;
	iStream.read(reinterpret_cast<char*>(&sizeofGeometryName), sizeof(int));
	if(iStream.fail()) { delete geometry; return NULL; }
	char* geometryName = new char[sizeofGeometryName+1];
	geometryName[sizeofGeometryName] = (char)0;
	iStream.read(geometryName, sizeofGeometryName*sizeof(char));
	if(iStream.fail()) { delete geometryName; delete geometry; return NULL; }
	geometry->setName(string(geometryName));
	delete[] geometryName;

	// read boundingBoxMin
	float boundingBoxMin[3];
	iStream.read(reinterpret_cast<char*>(boundingBoxMin), 3*sizeof(float));
	if(iStream.fail()) { delete geometry; return NULL; }

	// read boundingBoxMax
	float boundingBoxMax[3];
	iStream.read(reinterpret_cast<char*>(boundingBoxMax), 3*sizeof(float));
	if(iStream.fail()) { delete geometry; return NULL; }

	geometry->setBoundingBox(boundingBoxMin, boundingBoxMax);

	// read boundingSphereCenter
	float boundingSphereCenter[3];
	iStream.read(reinterpret_cast<char*>(boundingSphereCenter), 3*sizeof(float));
	if(iStream.fail()) { delete geometry; return NULL; }

	// read boundingSphereRadius
	float boundingSphereRadius;
	iStream.read(reinterpret_cast<char*>(&boundingSphereRadius), sizeof(float));
	if(iStream.fail()) { delete geometry; return NULL; }

	geometry->setBoundingSphere(boundingSphereCenter, boundingSphereRadius);

	int numVertices;
	iStream.read(reinterpret_cast<char*>(&numVertices), sizeof(int));
	if(iStream.fail()) { delete geometry; return NULL; }

	float* positions = new float[3*numVertices];
	float* normals = new float[3*numVertices];
	float* tangents = NULL;
	float* binormals = NULL;
	float*** texcoords = NULL;

	// read positions
	iStream.read(reinterpret_cast<char*>(positions), numVertices*3*sizeof(float));
	if(iStream.fail()) { delete[] positions; delete[] normals; delete geometry; return NULL; }

	// read normals
	iStream.read(reinterpret_cast<char*>(normals), numVertices*3*sizeof(float));
	if(iStream.fail()) { delete[] positions; delete[] normals; delete geometry; return NULL; }

	// read tangents
	bool hasTangents;
	iStream.read(reinterpret_cast<char*>(&hasTangents), sizeof(bool));
	if(iStream.fail()) { delete[] positions; delete[] normals; delete geometry; return NULL; }
	geometry->setHasTangents(hasTangents);
	if(hasTangents)
	{
		tangents = new float[3*numVertices];
		iStream.read(reinterpret_cast<char*>(tangents), numVertices*3*sizeof(float));
		if(iStream.fail()) { delete[] positions; delete[] normals; delete[] tangents; delete geometry; return NULL; }
	}

	// read binormals
	bool hasBinormals;
	iStream.read(reinterpret_cast<char*>(&hasBinormals), sizeof(bool));
	if(iStream.fail()) { delete[] positions; delete[] normals; delete[] tangents; delete geometry; return NULL; }
	geometry->setHasBinormals(hasBinormals);
	if(hasBinormals)
	{
		binormals = new float[3*numVertices];
		iStream.read(reinterpret_cast<char*>(binormals), numVertices*3*sizeof(float));
		if(iStream.fail()) { delete[] positions; delete[] normals; delete[] tangents; delete[] binormals; delete geometry; return NULL; }
	}

	// read numTexSets
	int numTexSets;
	iStream.read(reinterpret_cast<char*>(&numTexSets), sizeof(int));
	if(iStream.fail()) { delete[] positions; delete[] normals; delete[] tangents; delete[] binormals; delete geometry; return NULL; }

	int* dimensions = NULL;
	int sumTexcoords = 0;

	if(numTexSets > 0)
	{
		dimensions = new int[numTexSets];
		for(int i=0; i<numTexSets; i++)
		{
			iStream.read(reinterpret_cast<char*>(&dimensions[i]), sizeof(int));
			if(iStream.fail()) { delete[] positions; delete[] normals; delete[] tangents; delete[] binormals; delete[] dimensions; delete geometry; return NULL; }
		}

		for(int i=0; i<numTexSets; i++)
			sumTexcoords += dimensions[i];

		texcoords = new float**[numVertices];

		for(int i=0; i<numVertices; i++)
		{
			texcoords[i] = new float*[sumTexcoords];
			for(int j=0; j<numTexSets; j++)
			{
				texcoords[i][j] = new float[dimensions[j]];
				iStream.read(reinterpret_cast<char*>(texcoords[i][j]), dimensions[j]*sizeof(float));
				if(iStream.fail())
				{
					delete[] positions;
					delete[] normals;
					delete[] tangents;
					delete[] binormals;
					delete[] dimensions;
					for(int i2=0; i2<=i; i2++)
					{
						for(int j2=0; j2<=j; j2++)
							delete[] texcoords[i2][j2];
						delete[] texcoords[i2];
					}
					delete[] texcoords;
					delete geometry;
					return NULL;
				}
			}
		}
	}

	geometry->setTextureDimensions(numTexSets, dimensions);

	geometry->setNumVertices(numVertices);

	for(int i=0; i<numVertices; i++)
	{
		float* position = positions+3*i;
		float* normal = normals+3*i;
		float* tangent = hasTangents ? tangents+3*i : NULL;
		float* binormal = hasBinormals ? binormals+3*i : NULL;
		float** texcoord = numTexSets > 0 ? texcoords[i] : NULL;

		geometry->addVertex(position, normal, tangent, binormal, texcoord);
	}

	delete[] positions;
	delete[] normals;
	delete[] tangents;
	delete[] binormals;
	if(numTexSets > 0)
	{
		for(int i=0; i<numVertices; i++)
		{
			for(int j=0; j<numTexSets; j++)
				delete[] texcoords[i][j];
			delete[] texcoords[i];
		}
	}
	delete[] texcoords;
	delete[] dimensions;

	return geometry;
}

PosModel* PosScene::readModel_v1(igzstream &iStream, map<string,PosGeometry*> geometries, map<string,PosMaterial*> materials)
{
	PosModel* model = getNewModel();

	// read sizeofModelName
	int sizeofModelName;
	iStream.read(reinterpret_cast<char*>(&sizeofModelName), sizeof(int));
	if(iStream.fail()) { delete model; return NULL; }
	char* modelName = new char[sizeofModelName+1];
	modelName[sizeofModelName] = (char)0;
	iStream.read(modelName, sizeofModelName*sizeof(char));
	if(iStream.fail()) { delete[] modelName; delete model; return NULL; }
	model->setName(string(modelName));
	delete[] modelName;

	// read boundingSphereCenter
	float boundingSphereCenter[3];
	iStream.read(reinterpret_cast<char*>(boundingSphereCenter), 3*sizeof(float));
	if(iStream.fail()) { delete model; return NULL; }

	// read boundingSphereRadius
	float boundingSphereRadius;
	iStream.read(reinterpret_cast<char*>(&boundingSphereRadius), sizeof(float));
	if(iStream.fail()) { delete model; return NULL; }

	model->setBoundingSphere(boundingSphereCenter, boundingSphereRadius);

	// read numGeoms
	int numGeoms;
	iStream.read(reinterpret_cast<char*>(&numGeoms), sizeof(int));
	if(iStream.fail()) { delete model; return NULL; }
	for(int i=0; i<numGeoms; i++)
	{
		// read Geometry
		int sizeofGeometryName;
		iStream.read(reinterpret_cast<char*>(&sizeofGeometryName), sizeof(int));
		if(iStream.fail()) { delete model; return NULL; }
		char* geometryName = new char[sizeofGeometryName+1];
		geometryName[sizeofGeometryName] = (char)0;
		iStream.read(geometryName, sizeofGeometryName*sizeof(char));
		if(iStream.fail()) { delete[] geometryName; delete model; return NULL; }
		PosGeometry* geom = geometries.find(string(geometryName))->second;
		delete[] geometryName;

		// read Material
		int sizeofMaterialName;
		iStream.read(reinterpret_cast<char*>(&sizeofMaterialName), sizeof(int));
		if(iStream.fail()) { delete model; return NULL; }
		char* materialName = new char[sizeofMaterialName+1];
		materialName[sizeofMaterialName] = (char)0;
		iStream.read(materialName, sizeofMaterialName*sizeof(char));
		if(iStream.fail()) { delete[] materialName; delete model; return NULL; }
		PosMaterial* mat = materials.find(string(materialName))->second;
		delete[] materialName;

		model->addGeometry(geom, mat);

	}

	return model;
}

PosSceneEntity* PosScene::readSceneEntity_v1(igzstream &iStream, map<string,PosModel*> models)
{
	PosSceneEntity* sceneEntity = getNewSceneEntity();

	// read sizeofSceneEntityName
	int sizeofSceneEntityName;
	iStream.read(reinterpret_cast<char*>(&sizeofSceneEntityName), sizeof(int));
	if(iStream.fail()) { delete sceneEntity; return NULL; }
	char* sceneEntityName = new char[sizeofSceneEntityName+1];
	sceneEntityName[sizeofSceneEntityName] = (char)0;
	iStream.read(sceneEntityName, sizeofSceneEntityName*sizeof(char));
	if(iStream.fail()) { delete[] sceneEntityName; delete sceneEntity; return NULL; }
	sceneEntity->setName(string(sceneEntityName));
	delete[] sceneEntityName;

	// read sceneTransformation
	float sceneTransformation[16];
	iStream.read(reinterpret_cast<char*>(&sceneTransformation), 16*sizeof(float));
	if(iStream.fail()) { delete sceneEntity; return NULL; }
	sceneEntity->setTransformation(sceneTransformation);

	// read Models
	int numModels;
	iStream.read(reinterpret_cast<char*>(&numModels), sizeof(int));
	if(iStream.fail()) { delete sceneEntity; return NULL; }
	for(int i=0; i<numModels; i++)
	{
		// read modelTransformation
		float modelTransformation[16];
		iStream.read(reinterpret_cast<char*>(&modelTransformation), 16*sizeof(float));
		if(iStream.fail()) { delete sceneEntity; return NULL; }
		
		// read sizeofModelName
		int sizeofModelName;
		iStream.read(reinterpret_cast<char*>(&sizeofModelName), sizeof(int));
		if(iStream.fail()) { delete sceneEntity; return NULL; }
		char* modelName = new char[sizeofModelName+1];
		modelName[sizeofModelName] = (char)0;
		iStream.read(modelName, sizeofModelName*sizeof(char));
		if(iStream.fail()) { delete[] modelName; delete sceneEntity; return NULL; }
		PosModel* model = models.find(string(modelName))->second;
		delete[] modelName;

		sceneEntity->addModel(model, modelTransformation);
	}

	return sceneEntity;
}

bool PosScene::read_v2(igzstream &iStream)
{
	map<string, PosMaterial*> materials;
	map<string, PosGeometry*> geometries;
	map<string, PosModel*> models;
	set<PosSceneEntity*> sceneEntities;

	bool success = true;

	// read materials
	int materialCount;
	iStream.read(reinterpret_cast<char*>(&materialCount), sizeof(int));
	success = !iStream.fail();
	for(int i=0; success && i<materialCount; i++)
	{
		PosMaterial* material = readMaterial_v2(iStream);
		if(!material)
		{
			success = false;
			break;
		}
		addMaterial(material);
		materials[material->getName()] = material;
	}

	// read geometries
	int geometryCount;
	iStream.read(reinterpret_cast<char*>(&geometryCount), sizeof(int));
	success = !iStream.fail();
	for(int i=0; success && i<geometryCount; i++)
	{
		PosGeometry* geometry = readGeometry_v2(iStream);
		if(!geometry)
		{
			success = false;
			break;
		}
		addGeometry(geometry);
		geometries[geometry->getName()] = geometry;
	}

	// read models
	int modelCount;
	iStream.read(reinterpret_cast<char*>(&modelCount), sizeof(int));
	success = !iStream.fail();
	for(int i=0; success && i<modelCount; i++)
	{
		PosModel* model = readModel_v2(iStream, geometries, materials);
		if(!model)
		{
			success = false;
			break;
		}
		addModel(model);
		models[model->getName()] = model;
	}

	// read scene entities
	int sceneEntityCount;
	iStream.read(reinterpret_cast<char*>(&sceneEntityCount), sizeof(int));
	success = !iStream.fail();
	for(int i=0; success && i<sceneEntityCount; i++)
	{
		PosSceneEntity* sceneEntity = readSceneEntity_v2(iStream, models);
		if(!sceneEntity)
		{
			success = false;
			break;
		}
		addSceneEntity(sceneEntity);
		sceneEntities.insert(sceneEntity);
	}
	if(!success)
	{
		for(map<string, PosMaterial*>::const_iterator it = materials.begin(); it != materials.end(); ++it)
			delete it->second;
		for(map<string, PosGeometry*>::const_iterator it = geometries.begin(); it != geometries.end(); ++it)
			delete it->second;
		for(map<string, PosModel*>::const_iterator it = models.begin(); it != models.end(); ++it)
			delete it->second;
		for(set<PosSceneEntity*>::const_iterator it = sceneEntities.begin(); it != sceneEntities.end(); ++it)
			delete (*it);
	}
	return success;
}

PosMaterial* PosScene::readMaterial_v2(igzstream &iStream)
{
	PosMaterial* material = getNewMaterial();

	// read name
	int sizeofName;
	iStream.read(reinterpret_cast<char*>(&sizeofName), sizeof(int));
	if(iStream.fail()) { delete material; return NULL; }
	char* name = new char[sizeofName+1];
	name[sizeofName] = (char)0;
	iStream.read(name, sizeofName*sizeof(char));
	if(iStream.fail()) { delete[] name; delete material; return NULL; }
	material->setName(string(name));
	delete[] name;

	// read ambient
	float ambient[4];
	iStream.read(reinterpret_cast<char*>(ambient), 4*sizeof(float));
	if(iStream.fail()) { delete material; return NULL; }
	material->setAmbientColor(ambient);

	// read diffuse
	float diffuse[4];
	iStream.read(reinterpret_cast<char*>(diffuse), 4*sizeof(float));
	if(iStream.fail()) { delete material; return NULL; }
	material->setDiffuseColor(diffuse);

	// read hasSpecular
	bool hasSpecular;
	iStream.read(reinterpret_cast<char*>(&hasSpecular), sizeof(bool));
	material->setHasSpecular(hasSpecular);
	if(hasSpecular)
	{
		// read specular
		float specular[4];
		iStream.read(reinterpret_cast<char*>(specular), 4*sizeof(float));
		if(iStream.fail()) { delete material; return NULL; }

		// read exponent
		float exponent;
		iStream.read(reinterpret_cast<char*>(&exponent), sizeof(float));
		if(iStream.fail()) { delete material; return NULL; }

		material->setSpecularColor(specular, exponent);
	}

	// read hasEmissive
	bool hasEmissive;
	iStream.read(reinterpret_cast<char*>(&hasEmissive), sizeof(bool));
	material->setHasEmissive(hasEmissive);
	if(hasEmissive)
	{
		// read emissive
		float emissive[4];
		iStream.read(reinterpret_cast<char*>(emissive), 4*sizeof(float));
		if(iStream.fail()) { delete material; return NULL; }
		material->setEmissiveColor(emissive);
	}

	// read the number of textures
	int numTextures;
	iStream.read(reinterpret_cast<char*>(&numTextures), sizeof(int));
	if(iStream.fail()) { delete material; return NULL; }
	material->setNumTextures(numTextures);

	// read textures
	for(int i=0; i<numTextures; i++)
	{
		// read textureFilename
		int sizeofTextureFilename;
		iStream.read(reinterpret_cast<char*>(&sizeofTextureFilename), sizeof(int));
		if(iStream.fail()) {delete material; return NULL; }

		char* textureFilename = new char[sceneDirectory.size() + sizeofTextureFilename+1];
		memcpy(textureFilename,sceneDirectory.c_str(),sceneDirectory.size()*sizeof(char));
		textureFilename[sceneDirectory.size() + sizeofTextureFilename] = (char)0;
		iStream.read(textureFilename + sceneDirectory.size(), sizeofTextureFilename*sizeof(char));
		if(iStream.fail()) {delete[] textureFilename; delete material; return NULL; }
		
		material->setTextureFilename(i, string(textureFilename));
		delete[] textureFilename;
	}

	return material;
}

PosGeometry* PosScene::readGeometry_v2(igzstream &iStream)
{
	// nothing changed from v1 to v2 here
	return readGeometry_v1(iStream);
}

PosModel* PosScene::readModel_v2(igzstream &iStream, map<string,PosGeometry*> geometries, map<string,PosMaterial*> materials)
{
	// nothing changed from v1 to v2 here
	return readModel_v1(iStream, geometries, materials);
}

PosSceneEntity* PosScene::readSceneEntity_v2(igzstream &iStream, map<string,PosModel*> models)
{
	// nothing changed from v1 to v2 here
	return readSceneEntity_v1(iStream, models);
}

PosScene::~PosScene(void)
{
}