/*
  File name: I3Dg.cpp
  Copyright: (C) 2003 - 2008 Tomasz Kazmierczak

  Creation date: 20.10.2003
  Last modification date: 15.12.2008

  This file is part of Cytadela

 *   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/>.  *

*/

#include <memory.h>
#include <cassert>
#include "I3Dg.h"

I3Dg::I3Dg() :
	mError(ERR3DG_OK), mLightsQuantity(0), mTexturesQuantity(0),
	mMaterialsQuantity(0), lights(0), textures(0), materials(0)
{
}

I3Dg::~I3Dg()
{
	if(this->textures != 0) {
		for(uint32_t i = 0; i < mTexturesQuantity; i++) {
			if(this->textures[i]) {
				if(this->textures[i]->path)
					delete[] this->textures[i]->path;
				delete this->textures[i];
			}
		}
		delete[] this->textures;
	}
	if(this->materials != 0) {
		for(uint32_t i = 0; i < mMaterialsQuantity; i++) {
			if(this->materials[i])
				delete this->materials[i];
		}
		delete[] this->materials;
	}
	while(not this->meshes.empty()) {
		if(this->meshes.begin()->second)
			delete this->meshes.begin()->second;
		this->meshes.erase(this->meshes.begin());
	}
	while(not mMeshBases.empty()) {
		if(mMeshBases.begin()->second)
			delete mMeshBases.begin()->second;
		mMeshBases.erase(mMeshBases.begin());
	}
	if(this->lights) {
		for(uint32_t i = 0; i < mLightsQuantity; i++) {
			if(this->lights[i])
				delete this->lights[i];
		}
		delete[] this->lights;
	}
}

MeshBase3DgUID I3Dg::generateMeshBaseUID()
{
	if(mMeshBases.empty())
		return 0;
	MeshBaseMap3Dg::iterator i = mMeshBases.end();
	i--;
	MeshBase3DgUID uid = i->second->getUID();
	//uid is a 32 bit data type, so this loop is quite safe
	do {
		uid++;
		i = mMeshBases.find(uid);
	}
	while(i != mMeshBases.end());
	return uid;
}

Mesh3DgUID I3Dg::generateMeshUID()
{
	if(this->meshes.empty())
		return 0;
	MeshMap3Dg::iterator i = this->meshes.end();
	i--;
	Mesh3DgUID uid = i->second->getUID();
	//uid is a 32 bit data type, so this loop is quite safe
	do {
		uid++;
		i = this->meshes.find(uid);
	}
	while(i != this->meshes.end());
	return uid;
}

MeshBase3Dg *I3Dg::createMeshBase(MeshBase3DgUID mbuid)
{
	//first check if there is already a base with this uid
	MeshBaseMap3Dg::iterator i = mMeshBases.find(mbuid);
	//if there is
	if(i != mMeshBases.end()) {
		mError = ERR3DG_INVALIDARG;
		return 0;
	}

	MeshBase3Dg *base = new MeshBase3Dg(mbuid);
	if(base == 0) {
		mError = ERR3DG_OUTOFMEMORY;
		return 0;
	}
	base->setParent(this);
	mMeshBases[mbuid] = base;
	return base;
}

Mesh3Dg *I3Dg::createMesh(Mesh3DgUID muid)
{
	//first check if there is already a mesh with this uid
	MeshMap3Dg::iterator i = this->meshes.find(muid);
	//if there is
	if(i != this->meshes.end()) {
		mError = ERR3DG_INVALIDARG;
		return 0;
	}

	Mesh3Dg *mesh = new Mesh3Dg(muid);
	if(mesh == 0) {
		mError = ERR3DG_OUTOFMEMORY;
		return 0;
	}
	mesh->setParent(this);
	this->meshes[muid] = mesh;
	return mesh;
}

void I3Dg::destroyMesh(Mesh3DgUID muid)
{
	//find the mesh
	MeshMap3Dg::iterator i = this->meshes.find(muid);
	//if no such mesh
	if(i == this->meshes.end())
		return;

	//remove the mesh from memory
	delete i->second;
	//erase the map entry
	this->meshes.erase(i);
}

Light3Dg *I3Dg::createLight()
{
	Light3Dg **tempLights = new Light3Dg*[mLightsQuantity+1];
	if(tempLights == 0) {
		mError = ERR3DG_OUTOFMEMORY;
		return 0;
	}

	Light3Dg *light = new Light3Dg;
	//FIXME: when light won't be a simple struct it will have to be removed
	memset(light, 0, sizeof(Light3Dg));
	if(light == 0) {
		delete[] tempLights;
		mError = ERR3DG_OUTOFMEMORY;
		return 0;
	}

	for(uint32_t i = 0; i < mLightsQuantity; i++)
		tempLights[i] = this->lights[i];

	delete[] this->lights;
	this->lights = tempLights;
	this->lights[mLightsQuantity] = light;
	mLightsQuantity++;
	return light;
}

Material3Dg *I3Dg::createMaterial()
{
	Material3Dg **tempMaterials = new Material3Dg*[mMaterialsQuantity+1];
	if(tempMaterials == 0) {
		mError = ERR3DG_OUTOFMEMORY;
		return 0;
	}

	Material3Dg *material = new Material3Dg;
	if(material == 0) {
		delete[] tempMaterials;
		mError = ERR3DG_OUTOFMEMORY;
		return 0;
	}

	for(uint32_t i = 0; i < mMaterialsQuantity; i++)
		tempMaterials[i] = this->materials[i];

	delete[] this->materials;
	this->materials = tempMaterials;
	this->materials[mMaterialsQuantity] = material;
	mMaterialsQuantity++;
	return material;
}

Texture3Dg *I3Dg::createTexture(const char *texPath, uint16_t pathBufferSize)
{
	//new textures array
	Texture3Dg **tmpTex = new Texture3Dg*[mTexturesQuantity+1];
	if(tmpTex == 0) {
		mError = ERR3DG_OUTOFMEMORY;
		return 0;
	}

	//new texture
	Texture3Dg *texture = new Texture3Dg;
	//FIXME: when texture won't be a simple struct this will have to be removed
	memset(texture, 0, sizeof(Texture3Dg));
	if(texture == 0) {
		delete[] tmpTex;
		mError = ERR3DG_OUTOFMEMORY;
		return 0;
	}

	//and the path to it's file
	texture->path = new char[pathBufferSize];
	if(texture->path == 0) {
		delete texture;
		delete[] tmpTex;
		mError = ERR3DG_OUTOFMEMORY;
		return 0;
	}
	memcpy(texture->path, texPath, pathBufferSize * sizeof(char));
	texture->pathBufferSize = pathBufferSize;

	//copy pointers to the new array
	for(uint32_t i = 0; i < mTexturesQuantity; i++)
		tmpTex[i] = this->textures[i];

	//delete the old array
	delete[] this->textures;
	//and copy the address to the new array into the global pointer
	this->textures = tmpTex;
	this->textures[mTexturesQuantity] = texture;
	mTexturesQuantity++;
	return texture;
}

/************************************************\
*                 Material3Dg                    *
\************************************************/

Material3Dg::Material3Dg()
{
	memset(&this->diffuse, 0, sizeof(this->diffuse));
	memset(&this->ambient, 0, sizeof(this->ambient));
	memset(&this->specular, 0, sizeof(this->specular));
	memset(&this->emissive, 0, sizeof(this->emissive));
	this->shininess = 0.0f;
	this->texturesQuantity = 0;
	this->mtlTextures = 0;
}

Material3Dg::~Material3Dg()
{
	if(this->mtlTextures != 0)
		delete[] this->mtlTextures;
}

bool Material3Dg::addTexture(int32_t texNum)
{
	int32_t *tmpNums = new int32_t[texturesQuantity + 1];
	if(tmpNums == 0)
		return false;

	for(uint32_t i = 0; i < texturesQuantity; i++)
		tmpNums[i] = mtlTextures[i];

	tmpNums[texturesQuantity] = texNum;
	delete[] mtlTextures;
	mtlTextures = tmpNums;
	texturesQuantity++;
	return true;
}

int32_t Material3Dg::getTexture(uint32_t mtlTexNum)
{
	if(mtlTexNum < texturesQuantity)
		return mtlTextures[mtlTexNum];
	else return -1;
}

/************************************************\
*                  Point3Dg                      *
\************************************************/

Point3Dg::Point3Dg()
{
	this->x = this->y = this->z = 0.0f;
}

Point3Dg::Point3Dg(float x, float y, float z)
{
	this->x = x;
	this->y = y;
	this->z = z;
}

/************************************************\
*                  Vector3Dg                     *
\************************************************/

float Vector3Dg::dotProduct(const Vector3Dg &vec)
{
	return this->x * vec.x + this->y * vec.y + this->z * vec.z;
}

Vector3Dg Vector3Dg::crossProduct(const Vector3Dg &vec)
{
	return Vector3Dg(this->y * vec.z - this->z * vec.y,
	                 this->z * vec.x - this->x * vec.z,
	                 this->x * vec.y - this->y * vec.x);
}

void Vector3Dg::normalize()
{
	float length = this->length();
	if(length == 0.0f or length == 1.0f)
		return;
	this->x /= length;
	this->y /= length;
	this->z /= length;
}

/************************************************\
*                     Mesh3Dg                    *
\************************************************/

Mesh3Dg::Mesh3Dg(Mesh3DgUID uid)
{
	memset(&this->translation, 0, sizeof(Matrix3Dg));
	this->translation.r1c1 = 1.0f;
	this->translation.r2c2 = 1.0f;
	this->translation.r3c3 = 1.0f;
	this->translation.r4c4 = 1.0f;
	memset(&this->rotation, 0, sizeof(Matrix3Dg));
	this->rotation.r1c1 = 1.0f;
	this->rotation.r2c2 = 1.0f;
	this->rotation.r3c3 = 1.0f;
	this->rotation.r4c4 = 1.0f;
	memset(&this->scaling, 0, sizeof(Matrix3Dg));
	this->scaling.r1c1 = 1.0f;
	this->scaling.r2c2 = 1.0f;
	this->scaling.r3c3 = 1.0f;
	this->scaling.r4c4 = 1.0f;

	memset(&this->animation, 0, sizeof(MeshAnim3Dg));

	mBase = 0;
	this->materialNumber = 0;
	mProps = MRP_RENDERABLE;
	mUID = uid;
}

Mesh3Dg::~Mesh3Dg()
{
}

void Mesh3Dg::rotate(float x, float y, float z)
{
	float sinX = sinf(x), cosX = cosf(x);
	float sinY = sinf(y), cosY = cosf(y);
	float sinZ = sinf(z), cosZ = cosf(z);
	this->rotation.r1c1 = cosY * cosZ;
	this->rotation.r1c2 = sinZ;
	this->rotation.r1c3 = -sinY;
	this->rotation.r2c1 = -sinZ;
	this->rotation.r2c2 = cosX * cosZ;
	this->rotation.r2c3 = sinX;
	this->rotation.r3c1 = sinY;
	this->rotation.r3c2 = -sinX;
	this->rotation.r3c3 = cosX * cosY;
}

void Mesh3Dg::setPosition(float x, float y, float z)
{
	this->translation.r4c1 = x;
	this->translation.r4c2 = y;
	this->translation.r4c3 = z;
}

void Mesh3Dg::addTranslation(float x, float y, float z)
{
	this->translation.r4c1 += x;
	this->translation.r4c2 += y;
	this->translation.r4c3 += z;
}

void Mesh3Dg::scale(float x, float y, float z)
{
	this->scaling.r1c1 = x;
	this->scaling.r2c2 = y;
	this->scaling.r3c3 = z;
}

Vector3Dg Mesh3Dg::geomCenter()
{
	Vector3Dg center(this->translation.r4c1, this->translation.r4c2, this->translation.r4c3);
	if(mProps & MRP_HAS_BASE) {
		const Point3Dg *a = mBase->getVCoordsStream();
		const Point3Dg *b = &a[1];
		Vector3Dg basecenter((a->x + b->x) / 2.0f, (a->y + b->y) / 2.0f, (a->z + b->z) / 2.0f);
		center += basecenter;
	}
	return center;
}

MeshBase3Dg *Mesh3Dg::setBase(MeshBase3Dg *base)
{
	//previous base
	MeshBase3Dg *prev = mBase;
	//set new base
	mBase = base;
	mProps |= MRP_HAS_BASE;
	//return the previous
	return prev;
}

void Mesh3Dg::setProperty(uint32_t property, bool set)
{
	if(set)
		mProps |= property;
	else mProps &= ~property;
}

/************************************************\
*                  MeshBase3Dg                   *
\************************************************/
MeshBase3Dg::MeshBase3Dg(MeshBase3DgUID uid) :
	mUID(uid),
	mTriansQuantity(0),
	mVCoordsStream(0),
	mVNormalsStream(0),
	mTCoordsStream(0)
{

}

MeshBase3Dg::~MeshBase3Dg()
{
	releaseTriangles();
}

bool MeshBase3Dg::addTrian(Triangle3Dg &triangle)
{
	Point3Dg *newVCoords = new Point3Dg[3 * (mTriansQuantity + 1)];
	if(newVCoords == 0)
		return false;
	Point3Dg *newVNormals = new Point3Dg[3 * (mTriansQuantity + 1)];
	if(newVNormals == 0) {
		delete[] newVCoords;
		return false;
	}
	float *newTCoords = new float[2 * (3 * (mTriansQuantity + 1))];
	if(newTCoords == 0) {
		delete[] newVNormals;
		delete[] newVCoords;
		return false;
	}

	for(uint32_t i = 0; i < mTriansQuantity * 3; i++) {
		newVCoords[i] = mVCoordsStream[i];
		newVNormals[i] = mVNormalsStream[i];
		newTCoords[2*i] = mTCoordsStream[2*i];
		newTCoords[2*i + 1] = mTCoordsStream[2*i + 1];
	}

	newVCoords[mTriansQuantity * 3] = triangle.vertexA.coords;
	newVCoords[mTriansQuantity * 3 + 1] = triangle.vertexB.coords;
	newVCoords[mTriansQuantity * 3 + 2] = triangle.vertexC.coords;
	newVNormals[mTriansQuantity * 3] = triangle.vertexA.normal;
	newVNormals[mTriansQuantity * 3 + 1] = triangle.vertexB.normal;
	newVNormals[mTriansQuantity * 3 + 2] = triangle.vertexC.normal;
	newTCoords[2 * mTriansQuantity * 3] = triangle.vertexA.tex[0];
	newTCoords[2 * mTriansQuantity * 3 + 1] = triangle.vertexA.tex[1];
	newTCoords[2 * (mTriansQuantity * 3 + 1)] = triangle.vertexB.tex[0];
	newTCoords[2 * (mTriansQuantity * 3 + 1) + 1] = triangle.vertexB.tex[1];
	newTCoords[2 * (mTriansQuantity * 3 + 2)] = triangle.vertexC.tex[0];
	newTCoords[2 * (mTriansQuantity * 3 + 2) + 1] = triangle.vertexC.tex[1];

	delete[] mVCoordsStream;
	delete[] mVNormalsStream;
	delete[] mTCoordsStream;

	mVCoordsStream = newVCoords;
	mVNormalsStream = newVNormals;
	mTCoordsStream = newTCoords;

	mTriansQuantity++;
	return true;
}

void MeshBase3Dg::releaseTriangles()
{
	if(mVCoordsStream)
		delete[] mVCoordsStream;
	if(mVNormalsStream)
		delete[] mVNormalsStream;
	if(mTCoordsStream)
		delete[] mTCoordsStream;
	mTriansQuantity = 0;

	mVCoordsStream = mVNormalsStream = 0;
	mTCoordsStream = 0;
}

Triangle3Dg MeshBase3Dg::getTriangle(uint32_t number)
{
	assert(number < mTriansQuantity);

	Triangle3Dg trian;
	trian.vertexA.coords = mVCoordsStream[3*number];
	trian.vertexB.coords = mVCoordsStream[3*number + 1];
	trian.vertexC.coords = mVCoordsStream[3*number + 2];
	trian.vertexA.normal = mVNormalsStream[3*number];
	trian.vertexB.normal = mVNormalsStream[3*number + 1];
	trian.vertexC.normal = mVNormalsStream[3*number + 2];
	trian.vertexA.tex[0] = mTCoordsStream[2 * 3*number];
	trian.vertexA.tex[1] = mTCoordsStream[2 * 3*number + 1];
	trian.vertexB.tex[0] = mTCoordsStream[2 * (3*number + 1)];
	trian.vertexB.tex[1] = mTCoordsStream[2 * (3*number + 1) + 1];
	trian.vertexC.tex[0] = mTCoordsStream[2 * (3*number + 2)];
	trian.vertexC.tex[1] = mTCoordsStream[2 * (3*number + 2) + 1];
	return trian;
}

const char *I3Dg::getLastErrorDesc()
{
	return getErrorDesc(getLastError());
}

const char *I3Dg::getErrorDesc(Error3Dg error)
{
	switch(error)
	{
	case ERR3DG_OK:
		return "No error";
	case ERR3DG_OUTOFMEMORY:
		return "Out of memory";
	case ERR3DG_INVALIDCALL:
		return "Invalid call of a function";
	case ERR3DG_INVALIDARG:
		return "Invalid argument to a function";
	default:
		return "Unknown error";
	}
	return 0;
}
