// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
// orginally written by Christian Stehno, modified by Nikolaus Gebhardt

#ifndef __C_OGRE_MESH_FILE_LOADER_H_INCLUDED__
#define __C_OGRE_MESH_FILE_LOADER_H_INCLUDED__

#include "IMeshLoader.h"
#include "IFileSystem.h"
#include "IVideoDriver.h"
#include "irrString.h"
#include "SMesh.h"
#include "SMeshBuffer.h"
#include "SMeshBufferLightMap.h"
#include "IMeshManipulator.h"
#include "matrix4.h"
#include "quaternion.h"
#include "CSkinnedMesh.h"

namespace irr
{
namespace scene
{

//! Meshloader capable of loading ogre meshes.
class COgreMeshFileLoader : public IMeshLoader
{
public:

	//! Constructor
	COgreMeshFileLoader(io::IFileSystem* fs, video::IVideoDriver* driver);

	//! destructor
	virtual ~COgreMeshFileLoader();

	//! returns true if the file maybe is able to be loaded by this class
	//! based on the file extension (e.g. ".cob")
	virtual bool isALoadableFileExtension(const io::path& filename) const _IRR_OVERRIDE_;

	//! creates/loads an animated mesh from the file.
	//! \return Pointer to the created mesh. Returns 0 if loading failed.
	//! If you no longer need the mesh, you should call IAnimatedMesh::drop().
	//! See IReferenceCounted::drop() for more information.
	virtual IAnimatedMesh* createMesh(io::IReadFile* file) _IRR_OVERRIDE_;

private:

// byte-align structures
#include "irrpack.h"

	struct ChunkHeader
	{
		u16 id;
		u32 length;
	} PACK_STRUCT;

// Default alignment
#include "irrunpack.h"


	struct ChunkData
	{
		ChunkData() : read(0) {}

		ChunkHeader header;
		u32 read;
	};

	struct OgreTexture
	{
		core::array<core::stringc> Filename;
		core::stringc Alias;
		core::stringc CoordsType;
		core::stringc MipMaps;
		core::stringc Alpha;
	};

	struct OgrePass
	{
		OgrePass() : AmbientTokenColor(false),
			DiffuseTokenColor(false), SpecularTokenColor(false),
			EmissiveTokenColor(false),
			MaxLights(8), PointSize(1.0f), PointSprites(false),
			PointSizeMin(0), PointSizeMax(0) {}

		video::SMaterial Material;
		OgreTexture Texture;
		bool AmbientTokenColor;
		bool DiffuseTokenColor;
		bool SpecularTokenColor;
		bool EmissiveTokenColor;
		u32 MaxLights;
		f32 PointSize;
		bool PointSprites;
		u32 PointSizeMin;
		u32 PointSizeMax;
	};

	struct OgreTechnique
	{
		OgreTechnique() : Name(""), LODIndex(0) {}

		core::stringc Name;
		core::stringc Scheme;
		u16 LODIndex;
		core::array<OgrePass> Passes;
	};

	struct OgreMaterial
	{
		OgreMaterial() : Name(""), ReceiveShadows(true),
			TransparencyCastsShadows(false) {}

		core::stringc Name;
		bool ReceiveShadows;
		bool TransparencyCastsShadows;
		core::array<f32> LODDistances;
		core::array<OgreTechnique> Techniques;
	};

	struct OgreVertexBuffer
	{
		OgreVertexBuffer() : BindIndex(0), VertexSize(0), Data(0) {}

		u16 BindIndex;
		u16 VertexSize;
		core::array<f32> Data;
	};

	struct OgreVertexElement
	{
		u16 Source,
		Type,
		Semantic,
		Offset,
		Index;
	};

	struct OgreGeometry
	{
		s32 NumVertex;
		core::array<OgreVertexElement> Elements;
		core::array<OgreVertexBuffer> Buffers;
		core::array<core::vector3df> Vertices;
		core::array<core::vector3df> Normals;
		core::array<s32> Colors;
		core::array<core::vector2df> TexCoords;
	};

	struct OgreTextureAlias
	{
		OgreTextureAlias() {};
		OgreTextureAlias(const core::stringc& a, const core::stringc& b) : Texture(a), Alias(b) {};
		core::stringc Texture;
		core::stringc Alias;
	};

	struct OgreBoneAssignment
	{
		s32 VertexID;
		u16 BoneID;
		f32 Weight;
	};

	struct OgreSubMesh
	{
		core::stringc Material;
		bool SharedVertices;
		core::array<s32> Indices;
		OgreGeometry Geometry;
		u16 Operation;
		core::array<OgreTextureAlias> TextureAliases;
		core::array<OgreBoneAssignment> BoneAssignments;
		bool Indices32Bit;
	};

	struct OgreMesh
	{
		bool SkeletalAnimation;
		OgreGeometry Geometry;
		core::array<OgreSubMesh> SubMeshes;
		core::array<OgreBoneAssignment> BoneAssignments;
		core::vector3df BBoxMinEdge;
		core::vector3df BBoxMaxEdge;
		f32 BBoxRadius;
	};

	struct OgreBone
	{
		core::stringc Name;
		core::vector3df Position;
		core::quaternion Orientation;
		core::vector3df Scale;
		u16 Handle;
		u16 Parent;
	};

	struct OgreKeyframe
	{
		u16 BoneID;
		f32 Time;
		core::vector3df Position;
		core::quaternion Orientation;
		core::vector3df Scale;
	};

	struct OgreAnimation
	{
		core::stringc Name;
		f32 Length;
		core::array<OgreKeyframe> Keyframes;
	};

	struct OgreSkeleton
	{
		core::array<OgreBone> Bones;
		core::array<OgreAnimation> Animations;
	};

	bool readChunk(io::IReadFile* file);
	bool readObjectChunk(io::IReadFile* file, ChunkData& parent, OgreMesh& mesh);
	bool readGeometry(io::IReadFile* file, ChunkData& parent, OgreGeometry& geometry);
	bool readVertexDeclaration(io::IReadFile* file, ChunkData& parent, OgreGeometry& geometry);
	bool readVertexBuffer(io::IReadFile* file, ChunkData& parent, OgreGeometry& geometry);
	bool readSubMesh(io::IReadFile* file, ChunkData& parent, OgreSubMesh& subMesh);

	void readChunkData(io::IReadFile* file, ChunkData& data);
	void readString(io::IReadFile* file, ChunkData& data, core::stringc& out);
	void readBool(io::IReadFile* file, ChunkData& data, bool& out);
	void readInt(io::IReadFile* file, ChunkData& data, s32* out, u32 num=1);
	void readShort(io::IReadFile* file, ChunkData& data, u16* out, u32 num=1);
	void readFloat(io::IReadFile* file, ChunkData& data, f32* out, u32 num=1);
	void readVector(io::IReadFile* file, ChunkData& data, core::vector3df& out);
	void readQuaternion(io::IReadFile* file, ChunkData& data, core::quaternion& out);

	void composeMeshBufferMaterial(scene::IMeshBuffer* mb, const core::stringc& materialName);
	scene::SMeshBuffer* composeMeshBuffer(const core::array<s32>& indices, const OgreGeometry& geom);
	scene::SMeshBufferLightMap* composeMeshBufferLightMap(const core::array<s32>& indices, const OgreGeometry& geom);
	scene::IMeshBuffer* composeMeshBufferSkinned(scene::CSkinnedMesh& mesh, const core::array<s32>& indices, const OgreGeometry& geom);
	void composeObject(void);
	bool readColor(io::IReadFile* meshFile, video::SColor& col);
	void getMaterialToken(io::IReadFile* file, core::stringc& token, bool noNewLine=false);
	void readTechnique(io::IReadFile* meshFile, OgreMaterial& mat);
	void readPass(io::IReadFile* file, OgreTechnique& technique);
	void loadMaterials(io::IReadFile* file);
	bool loadSkeleton(io::IReadFile* meshFile, const core::stringc& name);
	void clearMeshes();

	io::IFileSystem* FileSystem;
	video::IVideoDriver* Driver;

	core::stringc Version;
	bool SwapEndian;
	core::array<OgreMesh> Meshes;
	io::path CurrentlyLoadingFromPath;

	core::array<OgreMaterial> Materials;
	OgreSkeleton Skeleton;

	IMesh* Mesh;
	u32 NumUV;
};

} // end namespace scene
} // end namespace irr

#endif

