/*
 * OpenClonk, http://www.openclonk.org
 *
 * Copyright (c) 2010-2016, The OpenClonk Team and contributors
 *
 * Distributed under the terms of the ISC license; see accompanying file
 * "COPYING" for details.
 *
 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
 * See accompanying file "TRADEMARK" for details.
 *
 * To redistribute this file separately, substitute the full license texts
 * for the above references.
 */

#ifndef INC_StdMeshLoaderChunks
#define INC_StdMeshLoaderChunks

#include "lib/StdMesh.h"
#include "lib/StdMeshLoaderDataStream.h"

// ==== Ogre file format ====
// The Ogre file format is a chunked format similar to PNG.
// Each chunk except for the file header (type 0x1000) has the following format:
//  uint32_t chunk_type, values dependent on file type (mesh or skeleton)
//  uint32_t chunk_length, in bytes; includes the size of the header
//  uint8_t data[]
// The file header omits the length field.
// Ogre files can be stored in either big-endian or little-endian byte order. The
// byte order is determined by the first two bytes in the file, which must always
// be the chunk type of the file header (0x1000). If the bytes are 0x00 0x10, the
// file is assumed to be little-endian, if they are 0x10 0x00, the file is big-
// endian. If any other bytes are encountered, the file is invalid.
// This implementation only reads files stored in the platform's native byte order.
//
// ==== Data types ====
// Most of the numeric data is stored directly in binary as a memory representation.
// Strings are stored without a leading length field, and terminated by a 0x0A byte.
// Boolean fields are stored as a single byte, which is 0x00 for false or anything
// else for true.
//
// ==== Mesh chunks ====
// Unless specified, this section will only describe the data part of each chunk.
// Any values that are in parentheses are not available in this implementation.
//
// Type 0x1000: File Header
//  string version
//    Depending on the version, different features are enabled or disabled while
//    loading the file.
//    This implementation does not support fallback to different versions of the
//    file format.
//  This chunk does not contain a length field; the type id is directly followed
//  by the version string.
//  The file header must be immediately followed by a 0x3000 Mesh Data chunk.
// ----------
// Type 0x3000: Mesh Data
//  bool skeletal_animation
//    This field is only used by Ogre to optimize rendering. Set if the mesh has
//    animation data associated with it.
//  Allowed subchunks:
//    0x4000 Submesh Data
//    0x5000 Geometry Data (may occur at most once)
//    0x6000 Skeleton Link (may occur at most once)
//    0x7000 Mesh Bone Assignments
//    (0x8000 Precomputed Level of Detail Data)
//    0x9000 Mesh Boundaries
//    (0xA000 Submesh Name Table)
//    (0xB000 Edge List)
//    (0xC000 Animation Poses)
//    (0xD000 Animation List)
//    (0xE000 Table of Extremes)
// ----------
// Type 0x4000: Submesh data
//  string material
//    The name of a material to be used for this submesh. Must be loaded externally.
//  bool vertices_shared
//    If set, the submesh uses the geometry data of its parent mesh. In this case, the
//    submesh may not contain its own 0x5000 Geometry Data chunk.
//  uint32_t index_count
//    The number of vertex indices used by this submesh.
//  bool indices_are_32_bit
//    If set, each vertex index is 32 bits wide. If unset, each vertex index is 16 bits
//    wide.
//  uint16_t/uint32_t indices[index_count]
//    A list of vertex indices used by this submesh.
//  Allowed subchunks:
//    0x5000 Geometry Data (only if vertices_shared == false)
//    0x4010 Submesh Operation
//    0x4100 Submesh Bone Assignments
//    (0x4200 Submesh Texture Replacement)
//  If no 0x4010 Submesh Operation chunk is present, the submesh uses SO_TriList.
// ----------
// Type 0x4010: Submesh Operation
//  uint16_t operation
//    This value specifies how the indices of a submesh are to be interpreted.
//    Acceptable values:
//      (0x01 SO_PointList)
//        Each index defines a single point.
//      (0x02 SO_LineList)
//        Each pair (2k, 2k+1) of indices defines a line.
//      (0x03 SO_LineStrip)
//        Each pair (k, k+1) of indices defines a line.
//      0x04 SO_TriList
//        Each triplet (3k, 3k+1, 3k+2) of indices defines a triangle.
//      (0x05 SO_TriStrip)
//        For odd k, (k, k+1, k+2) define a triangle. For even k, (k+1, k, k+2) define a triangle.
//      (0x06 SO_TriFan)
//        Indices (1, k+1, k+2) define a triangle.
// ----------
// Type 0x4100: Submesh Bone Assignments
// Type 0x7000: Mesh Bone Assignments
//  uint32_t vertex_index
//  uint32_t bone_index
//  float weight
//    This defines the strength of the influence a bone has on a vertex. The sum of
//    the weights on each vertex is not guaranteed to be 1.0.
//  These values repeat until the size of the chunk is exhausted.
// ----------
// Type 0x5000: Geometry Data
//  uint32_t vertex_count
//    The number of vertices stored in each contained 0x5020 Vertex Buffer chunk.
//  Allowed subchunks:
//    0x5100 Vertex Declaration (required; must occur exactly once)
//    0x5200 Vertex Buffer (required; must occur at least once)
// ----------
// Type 0x5100 Vertex Declaration
//  This chunk does not store any values itself. It only acts as a container for
//  0x5110 Vertex Declaration Element chunks.
//  Allowed subchunks:
//    0x5110 Vertex Declaration Element (required; repeats until the size of the chunk is exhausted)
// ----------
// Type 0x5110 Vertex Declaration Element
//  uint16_t source
//    The index of the stream where this element is found.
//  uint16_t type
//    The type of data this element contains.
//    Acceptable values:
//      0x00 VDET_Float1
//        A single 32-bit float, expanded to (a, 0, 0, 1)
//      0x01 VDET_Float2
//        Two 32-bit floats, expanded to (a, b, 0, 1)
//      0x02 VDET_Float3
//        Three 32-bit floats, expanded to (a, b, c, 1)
//      0x03 VDET_Float4
//        Four 32-bit floats
//      (0x04 VDET_Reserved)
//      (0x05 VDET_Short1)
//        A single 16-bit integer
//      (0x06 VDET_Short2)
//        Two 16-bit integers
//      (0x07 VDET_Short3)
//        Three 16-bit integers
//      (0x08 VDET_Short4)
//        Four 16-bit integers
//      (0x09 VDET_UByte4)
//        Four 8-bit integers
//      0x0A VDET_Color_ARGB
//        Four 8-bit integers, describing a color in the order Alpha, Red, Green, Blue.
//        Acceptable values range from 0 to 255.
//      0x0B VDET_Color_ABGR
//        Four 8-bit integers, describing a color in the order Alpha, Blue, Green, Red.
//        Acceptable values range from 0 to 255.
//  uint16_t semantic
//    The semantic of the data this element contains.
//    Acceptable values:
//      0x01 VDES_Position
//        The element contains the vertex's position in world space.
//      (0x02 VDES_Blend_Weights)
//      (0x03 VDES_Blend_Indices)
//      0x04 VDES_Normals
//        The element contains vertex normals.
//      (0x05 VDES_Diffuse)
//        The element contains the diffuse color of the vertex.
//      (0x06 VDES_Specular)
//        The element contains the specular color of the vertex.
//      0x07 VDES_Texcoords
//        The element contains the texture coordinates of the vertex.
//  uint16_t offset
//    The element's offset in bytes from the beginning of the stream.
//  uint16_t index
//    Index of the element semantic. Used with colors and texture coordinates.
// ----------
// 0x5200 Vertex Buffer
//  uint16_t index
//    The index of the stream this buffer will get bound to.
//  uint16_t stride
//    The distance in bytes between two elements inside the buffer.
//  Allowed subchunks:
//    0x5210 Vertex Buffer Data (required; must occur exactly once)
// ----------
// 0x5210 Vertex Buffer Data
//  uint8_t data[]
//    The buffered data. This field spans the whole extent of the chunk.
// ----------
// 0x6000 Skeleton Link
//  string file
//    The name of the file that contains the skeleton of this mesh.
// ----------
// 0x9000 Mesh Boundaries
//  float min[3]
//    The minimum extents of the axis-aligned bounding box of the mesh.
//  float max[3]
//    The maximum extents of the axis-aligned bounding box of the mesh.
//  float radius
//    The radius of the minimal enclosing sphere of the mesh.
//
// ==== Skeleton files ====
// Each skeleton file must begin with a 0x1000 File Header chunk. Afterwards,
// any combination of the following chunks may appear:
//  0x2000 Bone Data
//  0x3000 Bone Hierarchy
//  0x4000 Animation
//  (0x5000 Animation Link)
//
// Unless specified, this section will only describe the data part of each chunk.
// Any values that are in parentheses are not available in this implementation.
//
// Type 0x1000: File Header
//  string version
//    Depending on the version, different features are enabled or disabled while
//    loading the file.
//    This implementation does not support fallback to different versions of the
//    file format.
//  This chunk does not contain a length field; the type id is directly followed
//  by the version string.
// ----------
// Type 0x2000: Bone Data
//  string name
//    The name of the bone. This is only used to produce human-readable output.
//  uint16_t handle
//    The internal handle of the bone. All other chunks that refer to a bone reference
//    this.
//  float position[3]
//    The position of the bone, relative to its parent.
//  float orientation[4]
//    The orientation of the bone, as a quaternion (x,y,z,w). Relative to the parent.
//  float scale[3]
//    The scale of the bone, relative to its parent. This element only appears if the
//    chunk size is large enough; if it is omitted, it defaults to (1,1,1).
// ----------
// Type 0x3000 Bone Hierarchy
//  uint16_t child
//    The handle of the parent bone.
//  uint16_t parent
//    The handle of the parent bone.
// ----------
// Type 0x4000 Animation
//  string name
//    The name of this animation.
//  float duration
//    The length of this animation, in seconds.
//  Allowed subchunks:
//    0x4100 Animation Track
// ----------
// Type 0x4100 Animation Track
//  uint16_t bone
//    The handle of the bone this track belongs to.
//  Allowed subchunks:
//    0x4110 Animation Track Keyframe
// ----------
// Type 0x4110 Animation Track Keyframe
//  float time
//    The time this keyframe matches.
//  float rotation[4]
//    The rotation of the bone at the corresponding time, as a quaternion (x,y,z,w).
//  float translation[3]
//    The translation of the bone at the corresponding time.
//  float scale[3]
//    The scale of the bone at the time of the keyframe. This element only appears if the
//    chunk size is large enough; if it is omitted, it defaults to (1,1,1).

// Most of the chunk classes below faithfully match the abovementioned file format.
namespace Ogre
{
	// used to have boost::ptr_vector. Behaves reasonably similar
	template<typename T>
	using unique_ptr_vector = std::vector<std::unique_ptr<T>>;

	class DataStream;
	template<class _Type>
	class ChunkBase
	{
	protected:
		ChunkBase() : type(static_cast<Type>(0)) {}
		virtual void ReadImpl(DataStream *stream) = 0;
		typedef _Type Type;
		Type type;
		size_t size;
	public:
		virtual ~ChunkBase() = default;
		Type GetType() const { return type; }
		size_t GetSize() const { return size; }

		static const size_t ChunkHeaderLength = sizeof(uint16_t) /* chunk type */ + sizeof(uint32_t) /* chunk length */;
		static Type Peek(const DataStream *stream)
		{
			return static_cast<Type>(stream->Peek<uint16_t>());
		}
	};

	namespace Mesh
	{
		enum ChunkID
		{
			CID_Invalid = 0,
			CID_Header = 0x1000,
			CID_Mesh = 0x3000,
			CID_Submesh = 0x4000,
			CID_Submesh_Op = 0x4010,
			CID_Submesh_Bone_Assignment = 0x4100,
			CID_Submesh_Texture_Alias = 0x4200,
			CID_Geometry = 0x5000,
			CID_Geometry_Vertex_Decl = 0x5100,
			CID_Geometry_Vertex_Decl_Element = 0x5110,
			CID_Geometry_Vertex_Buffer = 0x5200,
			CID_Geometry_Vertex_Data = 0x5210,
			CID_Mesh_Skeleton_Link = 0x6000,
			CID_Mesh_Bone_Assignment = 0x7000,
			CID_Mesh_LOD = 0x8000,
			CID_Mesh_LOD_Usage = 0x8100,
			CID_Mesh_LOD_Manual = 0x8110,
			CID_Mesh_LOD_Generated = 0x8120,
			CID_Mesh_Bounds = 0x9000,
			CID_Submesh_Name_Table = 0xA000,
			CID_Submesh_Name_Table_Entry = 0xA100,
			CID_Edge_List = 0xB000,
			CID_Edge_List_LOD = 0xB100,
			CID_Edge_Group = 0xB110,
			CID_Pose_List = 0xC000,
			CID_Pose = 0xC100,
			CID_Pose_Vertex = 0xC111,
			CID_Animation_List = 0xD000,
			CID_Animation = 0xD100,
			CID_Animation_Track = 0xD110,
			CID_Animation_Morph_Keyframe = 0xD111,
			CID_Animation_Pose_Keyframe = 0xD112,
			CID_Animation_Pose_Ref = 0xD113,
			CID_Table_Extremes = 0xE000
		};

		struct BoneAssignment
		{
			uint32_t vertex;
			uint32_t bone;
			float weight;
		};

		class Chunk : public ChunkBase<ChunkID>
		{
		public:
			static std::unique_ptr<Chunk> Read(DataStream *stream);
		};

		class ChunkUnknown; class 
		ChunkFileHeader;
		class ChunkMesh; class ChunkMeshSkeletonLink; class ChunkMeshBoneAssignments; class ChunkMeshBounds;
		class ChunkSubmesh; class ChunkSubmeshOp;
		class ChunkGeometry; class ChunkGeometryVertexDecl; class ChunkGeometryVertexDeclElement; class ChunkGeometryVertexBuffer; class ChunkGeometryVertexData;
		class ChunkPoseList; class ChunkPose; class ChunkPoseVertex;
		class ChunkAnimationList; class ChunkAnimation; class ChunkAnimationTrack;
		class ChunkAnimationMorphKF; class ChunkAnimationPoseKF; class ChunkAnimationPoseRef;

		class ChunkUnknown : public Chunk
		{
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkFileHeader : public Chunk
		{
			typedef std::map<std::string, uint32_t> VersionTable_t;
			static const VersionTable_t VersionTable;
			static const uint32_t CurrentVersion;
		public:
			std::string version;

		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkMesh : public Chunk
		{
		public:
			ChunkMesh() = default;
			bool hasAnimatedSkeleton{false};
			std::string skeletonFile;
			std::unique_ptr<ChunkGeometry> geometry;
			unique_ptr_vector<ChunkSubmesh> submeshes;
			std::vector<BoneAssignment> boneAssignments;
			StdMeshBox bounds;
			float radius{0.0f};

		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkMeshSkeletonLink : public Chunk
		{
		public:
			std::string skeleton;
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkSubmesh : public Chunk
		{
		public:
			ChunkSubmesh() = default;
			std::string material;
			bool hasSharedVertices;
			std::vector<size_t> faceVertices;
			std::unique_ptr<ChunkGeometry> geometry;
			enum SubmeshOperation
			{
				SO_PointList = 1,
				SO_LineList = 2,
				SO_LineStrip = 3,
				SO_TriList = 4,
				SO_TriStrip = 5,
				SO_TriFan = 6,
				SO_MIN = SO_PointList,
				SO_MAX = SO_TriFan
			} operation{SO_TriList};
			std::vector<BoneAssignment> boneAssignments;
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkSubmeshOp : public Chunk
		{
		public:
			ChunkSubmesh::SubmeshOperation operation;
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkMeshBoneAssignments : public Chunk
		{
		public:
			std::vector<BoneAssignment> assignments;
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkMeshBounds : public Chunk
		{
		public:
			StdMeshBox bounds;
			float radius;
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkGeometry : public Chunk
		{
		public:
			size_t vertexCount;
			unique_ptr_vector<ChunkGeometryVertexDeclElement> vertexDeclaration;
			unique_ptr_vector<ChunkGeometryVertexBuffer> vertexBuffers;
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkGeometryVertexDecl : public Chunk
		{
		public:
			unique_ptr_vector<ChunkGeometryVertexDeclElement> declaration;
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkGeometryVertexDeclElement : public Chunk
		{
		public:
			uint16_t source;
			uint16_t offset;
			enum Type
			{
				VDET_Float1 = 0,
				VDET_Float2 = 1,
				VDET_Float3 = 2,
				VDET_Float4 = 3,
				/* 4 omitted intentionally */
				VDET_Short1 = 5,
				VDET_Short2 = 6,
				VDET_Short3 = 7,
				VDET_Short4 = 8,
				VDET_UByte4 = 9,
				VDET_Color_ARGB = 10,
				VDET_Color_ABGR = 11,
				VDET_MIN = VDET_Float1,
				VDET_MAX = VDET_Color_ABGR
			} type;
			enum Semantic
			{
				VDES_Position = 1,
				VDES_Blend_Weights = 2,
				VDES_Blend_Indices = 3,
				VDES_Normals = 4,
				VDES_Diffuse = 5,
				VDES_Specular = 6,
				VDES_Texcoords = 7,
				VDES_Binormal = 8,
				VDES_Tangent = 9,
				VDES_MIN = VDES_Position,
				VDES_MAX = VDES_Tangent
			} semantic;
			uint16_t index;
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkGeometryVertexBuffer : public Chunk
		{
		public:
			uint16_t index;
			uint16_t vertexSize;
			std::unique_ptr<ChunkGeometryVertexData> data;
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkGeometryVertexData : public Chunk
		{
		public:
			ChunkGeometryVertexData() = default;
			~ChunkGeometryVertexData() override { delete[] static_cast<char*>(data); }
			void *data{nullptr};
		protected:
			void ReadImpl(DataStream *stream) override;
		};
	}

	namespace Skeleton
	{
		enum ChunkID
		{
			CID_Invalid = 0,
			CID_Header = 0x1000,
			CID_BlendMode=  0x1010,
			CID_Bone = 0x2000,
			CID_Bone_Parent = 0x3000,
			CID_Animation = 0x4000,
			CID_Animation_BaseInfo = 0x4010,
			CID_Animation_Track = 0x4100,
			CID_Animation_Track_KF = 0x4110,
			CID_Animation_Link = 0x5000
		};

		class Chunk : public ChunkBase<ChunkID>
		{
		public:
			static std::unique_ptr<Chunk> Read(DataStream *stream);
		};

		class ChunkUnknown; class ChunkFileHeader;
		class ChunkBone; class ChunkBoneParent;
		class ChunkAnimation; class ChunkAnimationTrack; class ChunkAnimationTrackKF; class ChunkAnimationLink;

		class ChunkUnknown : public Chunk
		{
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkFileHeader : public Chunk
		{
			typedef std::map<std::string, uint32_t> VersionTable_t;
			static const VersionTable_t VersionTable;
			static const uint32_t CurrentVersion;
		public:
			std::string version;

		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkBlendMode : public Chunk
		{
		public:
			uint16_t blend_mode;
		protected:
			void ReadImpl(DataStream* stream) override;
		};

		class ChunkBone : public Chunk
		{
		public:
			std::string name;
			uint16_t handle;

			StdMeshVector position;
			StdMeshQuaternion orientation;
			StdMeshVector scale;
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkBoneParent : public Chunk
		{
		public:
			uint16_t childHandle;
			uint16_t parentHandle;
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkAnimation : public Chunk
		{
		public:
			std::string name;
			float duration;
			unique_ptr_vector<ChunkAnimationTrack> tracks;
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkAnimationBaseInfo : public Chunk
		{
		public:
			std::string base_animation_name;
			float base_key_frame_time;
		protected:
			void ReadImpl(DataStream* stream) override;
		};

		class ChunkAnimationTrack : public Chunk
		{
		public:
			uint16_t bone;
			unique_ptr_vector<ChunkAnimationTrackKF> keyframes;
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkAnimationTrackKF : public Chunk
		{
		public:
			float time;
			StdMeshQuaternion rotation;
			StdMeshVector translation;
			StdMeshVector scale;
		protected:
			void ReadImpl(DataStream *stream) override;
		};

		class ChunkAnimationLink : public Chunk
		{
		public:
			std::string file;
			StdMeshVector scale;
		protected:
			void ReadImpl(DataStream *stream) override;
		};
	}
}

#endif
