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

  Creation date: 18.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/>.  *

*/

#ifndef _I3DG_H_
#define _I3DG_H_

#include <stdint.h>
#include <math.h>
#include <map>

typedef uint32_t MeshBase3DgUID;
typedef uint32_t Mesh3DgUID;

//errors
enum Error3Dg {
	ERR3DG_OK,
	ERR3DG_OUTOFMEMORY,
	ERR3DG_INVALIDCALL,
	ERR3DG_INVALIDARG
};

//light types
enum LightType3Dg {
	POINT_LIGHT = 1,
	SPOT_LIGHT = 2,
	DIRECTIONAL_LIGHT = 3,
	LIGHT_FORCE_DWORD = 0xFFFFFFFF
};

//texture types
enum TextureType3Dg {
	UNKNOWN_TEX_TYPE = 0,
	PIXEL_MAP = 1,              //24 bpp or 32 bpp
	BUMP_MAP = 2,               //8 bpp
	LIGHT_MAP = 3,              //24 bpp
	ALPHA_MAP = 4,              //8 bpp
	ENVIRONMENT_MAP = 5,        //24 bpp
	TEX_TYPE_FORCE_DWORD = 0xFFFFFFFF
};

//texture wrap modes
enum TextureWrapMode3Dg {
	TEX_WRAP_REPEAT = 1,
	TEX_WRAP_MIRRORED_REPEAT,
	TEX_WRAP_CLAMP,
	TEX_WRAP_CLAMP_TO_EDGE,
	TEX_WRAP_CLAMP_TO_BORDER,
	TEX_WRAP_FORCE_DWORD = 0xFFFFFFFF
};

class ColorRGBA3Dg {
public:
	float r;
	float g;
	float b;
	float a;
};

class Object3Dg {
protected:
	Object3Dg *mParent;
	Object3Dg *mChild;
public:
	Object3Dg() : mParent(0), mChild(0) {}

	void setParent(Object3Dg *parent) { mParent = parent; }
	void setChild(Object3Dg *child) { mChild = child; }
	Object3Dg *getParent() { return mParent; }
	Object3Dg *getChild() { return mChild; }
};

class Material3Dg {
private:
	int32_t    *mtlTextures;

public:
	ColorRGBA3Dg diffuse;
	ColorRGBA3Dg ambient;
	ColorRGBA3Dg specular;
	ColorRGBA3Dg emissive;
	float        shininess;
	uint32_t     texturesQuantity;

	Material3Dg();
	~Material3Dg();
	bool addTexture(int32_t texNum);
	/**
	 * Returns an identifier of mtlTexNum'th material's texture, or -1 if there isn't such
	 */
	int32_t getTexture(uint32_t mtlTexNum);
};

class Texture3Dg {
public:
	TextureType3Dg      type;
	TextureWrapMode3Dg  wrapModeS;
	TextureWrapMode3Dg  wrapModeT;
	uint16_t            pathBufferSize;
	char               *path;
};

class Point3Dg {
public:
	Point3Dg();
	Point3Dg(float x, float y, float z);
	union {
		class {
		public:
			float x;
			float y;
			float z;
		};
		float pt[3];
	};
};

typedef class {
public:
	union {
		class {
		public:
			float a;
			float b;
			float c;
			float d;
		};
		float pPl[4];
	};
} Plane3Dg;

class Vector3Dg : public Point3Dg {
public:
	Vector3Dg() : Point3Dg() {}
	Vector3Dg(float x, float y, float z) : Point3Dg(x, y, z) {}
	Vector3Dg(const Point3Dg &point) : Point3Dg(point) {}

	void operator +=(Vector3Dg &v) { x += v.x; y += v.y; z += v.z; }
	void operator -=(Vector3Dg &v) { x -= v.x; y -= v.y; z -= v.z; }
	void operator *=(float m) { x *= m; y *= m; z *= m; }
	void operator /=(float d) { x /= d; y /= d; z /= d; }

	Vector3Dg operator *(float m) { return Vector3Dg(x*m, y*m, z*m); }
	Vector3Dg operator +(const Vector3Dg &v) { return Vector3Dg(x + v.x, y + v.y, z  + v.z); }
	Vector3Dg operator -(const Vector3Dg &v) { return Vector3Dg(x - v.x, y - v.y, z - v.z); }

	float dotProduct(const Vector3Dg &vec);
	Vector3Dg crossProduct(const Vector3Dg &vec);

	void normalize();
	float length() { return sqrtf(x*x + y*y + z*z); }
	float lengthPow2() { return (x*x + y*y + z*z); }
};

class Vertex3Dg {
public:
	Point3Dg  coords;
	Point3Dg  normal;
	float     tex[2];
};

class Triangle3Dg {
public:
	Vertex3Dg vertexA;
	Vertex3Dg vertexB;
	Vertex3Dg vertexC;
};

class Matrix3Dg {
public:
	union {
		class {
		public:
			float r1c1, r1c2, r1c3, r1c4;
			float r2c1, r2c2, r2c3, r2c4;
			float r3c1, r3c2, r3c3, r3c4;
			float r4c1, r4c2, r4c3, r4c4;
		};
		float m[4][4];
	};
};

class MeshBase3Dg : public Object3Dg {
private:
	//unique identifier
	MeshBase3DgUID mUID;
	uint32_t       mTriansQuantity;

	//stream buffers of triangles' data
	Point3Dg *mVCoordsStream;
	Point3Dg *mVNormalsStream;
	float    *mTCoordsStream;

public:

	MeshBase3Dg(MeshBase3DgUID uid);
	~MeshBase3Dg();

	MeshBase3DgUID getUID() { return mUID; }

	///@note this function may be very slow
	bool addTrian(Triangle3Dg &triangle);
	void releaseTriangles();

	uint32_t getTriansQuantity() { return mTriansQuantity; }
	///@note this function may be very slow
	Triangle3Dg getTriangle(uint32_t number);

	///@note the addTrian() function invalidates the returned pointer
	const Point3Dg *getVCoordsStream() { return (const Point3Dg *)mVCoordsStream; }
	///@note the addTrian() function invalidates the returned pointer
	const Point3Dg *getVNormalsStream() { return (const Point3Dg *)mVNormalsStream; }
	///@note the addTrian() function invalidates the returned pointer
	const float *getTCoordsStream() { return (const float *)mTCoordsStream; }
};

class MeshAnim3Dg {
public:
	Point3Dg   translation;
	Point3Dg   rotation;
	Point3Dg   scaling;
	int32_t    elapsedTime;
	int32_t    animStatus;
	//if this variable is non-zero then there is a geometry animation
	uint32_t   geomAnimTime;
	//if this variable is non-zero then there is a texture animation
	uint32_t   texAnimTime;
	uint32_t   beginMtl;
	uint32_t   numMtls;
};

enum {
	MRP_HAS_BASE =       1 << 0,
	MRP_RENDERABLE =     1 << 1,
	MRP_TRANSPARENT =    1 << 2,
	MRP_Z_PLUS_OFFSET =  1 << 3,
	MRP_Z_MINUS_OFFSET = 1 << 4,
	MRP_Z_WRITE =        1 << 5,
};

class Mesh3Dg : public Object3Dg {
private:
	//mesh's base
	MeshBase3Dg *mBase;
	//properties
	uint32_t mProps;
	//unique identifier
	Mesh3DgUID mUID;

public:
	//mesh's transformations
	Matrix3Dg     rotation;
	Matrix3Dg     translation;
	Matrix3Dg     scaling;

	//mesh's animation
	MeshAnim3Dg   animation;
	//material number
	uint32_t      materialNumber;

	Mesh3Dg(Mesh3DgUID uid);
	~Mesh3Dg();

	void rotate(float x, float y, float z);
	void setPosition(float x, float y, float z);
	void addTranslation(float x, float y, float z);
	void scale(float x, float y, float z);

	Vector3Dg geomCenter();

	//set

	//mesh's base (returns pointer to previous, or 0 if there was no previous)
	MeshBase3Dg *setBase(MeshBase3Dg *base);
	//rendering properties
	void setProperty(uint32_t property, bool set);
	void setProps(uint32_t props) { mProps = props; }

	//get

	//mesh's base
	MeshBase3Dg *getBase() { return mBase; }
	//rendering properties
	bool getProperty(uint32_t property) { return mProps & property; }
	uint32_t getProps() { return mProps; }

	Mesh3DgUID getUID() { return mUID; }
};

class Light3Dg {
public:
	LightType3Dg lightType;
	ColorRGBA3Dg diffuse;
	ColorRGBA3Dg specular;
	ColorRGBA3Dg ambient;
	Point3Dg     position;
	Point3Dg     direction;
	float        range;
	float        falloff;
	float        attenuation0;
	float        attenuation1;
	float        attenuation2;
	float        theta;
	float        phi;
};

typedef std::map<Mesh3DgUID, Mesh3Dg *> MeshMap3Dg;
typedef std::map<MeshBase3DgUID, MeshBase3Dg *> MeshBaseMap3Dg;

//3Dgeom quasi-interface; it is never instantiated across the application, but it is not
//a real interface (it is not abstract in any sense)
class I3Dg : public Object3Dg {
protected:
	Error3Dg    mError;

	uint32_t  mLightsQuantity;
	uint32_t  mTexturesQuantity;
	uint32_t  mMaterialsQuantity;

	MeshBaseMap3Dg  mMeshBases;
public:
	MeshMap3Dg      meshes;
	Light3Dg      **lights;
	Texture3Dg    **textures;
	Material3Dg   **materials;

	I3Dg();
	virtual ~I3Dg();

	MeshBase3Dg *createMeshBase(MeshBase3DgUID muid);
	Mesh3Dg     *createMesh(Mesh3DgUID muid);
	Light3Dg    *createLight();
	Material3Dg *createMaterial();
	Texture3Dg  *createTexture(const char *texPath, uint16_t pathBufferSize);

	MeshBase3DgUID generateMeshBaseUID();
	Mesh3DgUID generateMeshUID();

	void destroyMesh(Mesh3DgUID muid);

	uint32_t getMaterialsQuantity() { return mMaterialsQuantity; }
	uint32_t getTexturesQuantity() { return mTexturesQuantity; }
	uint32_t getLightsQuantity() { return mLightsQuantity; }

	Error3Dg getLastError() { return mError; }
	const char *getLastErrorDesc();
	const char *getErrorDesc(Error3Dg error);
};

#endif //_I3DG_H_
