#pragma once
/**
 * @authors Trevor Hollmann, Mara Vogt, Susanne Dötsch
 * @file include/vkcv/asset/asset_loader.h
 * @brief Interface of the asset loader module for the vkcv framework.
 */

#include <string>
#include <vector>
#include <array>
#include <cstdint>
#include <filesystem>

#include "vkcv/VertexData.hpp"

/* LOADING MESHES
 * The description of meshes is a hierarchy of structures with the Mesh at the
 * top.
 *
 * Each Mesh has an array of one or more vertex groups (called "primitives" in
 * glTF parlance). Specifically, it has an array of indices into an array of
 * vertex groups defined by the Scene struct.
 *
 * Each vertex group describes a part of the meshes vertices by defining how
 * they should be rendered (as points, lines, triangles), how many indices and
 * vertices there are, how the content of the vertex buffer is to be
 * interpreted and which material from the Scenes materials array should be
 * used for the surface of the vertices.
 * As a bonus there is also the axis aligned bounding box of the vertices.
 *
 * The vertex buffer is presented as a single block of binary data with a given
 * length in bytes.
 * The layout of the vertex buffer is described by an array of VertexAttribute
 * structs that define the type of attribute, the offset, length and stride in
 * bytes and number and type of components of the attribute.
 * These values can directly be given to vulkan when describing the content of
 * vertex buffers. */

namespace vkcv::asset {

/**
 * @defgroup vkcv_asset Asset Loader Module
 * A module to load assets like scenes, meshes and textures.
 * @{
 */

/**
 * These return codes are limited to the asset loader module. If unified return
 * codes are defined for the vkcv framework, these will be used instead.
 */
#define ASSET_ERROR 0
#define ASSET_SUCCESS 1

/**
 * This enum matches modes in fx-gltf, the library returns a standard mode
 * (TRIANGLES) if no mode is given in the file.
 */
enum class PrimitiveMode : uint8_t {
	POINTS = 0,
	LINES = 1,
	LINELOOP = 2,
	LINESTRIP = 3,
	TRIANGLES = 4,
	TRIANGLESTRIP = 5,
	TRIANGLEFAN = 6
};

/**
 * The indices in the index buffer can be of different bit width.
 */
enum class IndexType : uint8_t {
	UNDEFINED=0,
	UINT8=1,
	UINT16=2,
	UINT32=3
};

/**
 * This struct defines a sampler for a texture object. All values here can
 * directly be passed to VkSamplerCreateInfo.
 * NOTE that glTF defines samplers based on OpenGL, which can not be directly
 * translated to Vulkan. The vkcv::asset::Sampler struct defined here adheres
 * to the Vulkan spec, having alerady translated the flags from glTF to Vulkan.
 * Since glTF does not specify border sampling for more than two dimensions,
 * the addressModeW is hardcoded to a default: VK_SAMPLER_ADDRESS_MODE_REPEAT.
 */
struct Sampler {
	int minFilter, magFilter;
	int mipmapMode;
	float minLOD, maxLOD;
	int addressModeU, addressModeV, addressModeW;
};

/**
 * This struct describes a (partially) loaded texture.
 * The data member is not populated after calling probeScene() but only when
 * calling loadMesh(), loadScene() or loadTexture(). Note that textures are
 * currently always loaded with 4 channels as RGBA, even if the image has just
 * RGB or is grayscale. In the case where the glTF-file does not provide a URI
 * but references a buffer view for the raw data, the path member will be empty
 * even though the rest is initialized properly.
 * NOTE: Loading textures without URI is untested.
 */
struct Texture {
	std::filesystem::path path;	// URI to the encoded texture data
	int sampler;			// index into the sampler array of the Scene
	
	union { int width; int w; };
	union { int height; int h; };
	int channels;
	
	std::vector<uint8_t> data;	// binary data of the decoded texture
};

/**
 * Flags for the bit-mask in the Material struct. To check if a material has a
 * certain texture target, you can use the hasTexture() function below, passing
 * the material struct and the enum.
 */
enum class PBRTextureTarget {
	baseColor=1,
	metalRough=2,
	normal=4,
	occlusion=8,
	emissive=16
};

/**
 * This macro translates the index of an enum in the defined order to an
 * integer with a single bit set in the corresponding place. It is used for
 * working with the bitmask of texture targets ("textureMask") in the Material
 * struct:
 * 	Material mat = ...;
 * 	if (mat.textureMask & bitflag(PBRTextureTarget::baseColor)) {...}
 * However, this logic is also encapsulated in the convenience-function
 * materialHasTexture() so users of the asset loader module can avoid direct
 * contact with bit-level operations. */
#define bitflag(ENUM) (0x1u << ((unsigned)(ENUM)))

/**
 * The asset loader module only supports the PBR-MetallicRoughness model for
 * materials.
 */
struct Material {
	uint16_t textureMask;	// bit mask with active texture targets
	
	// Indices into the Scene.textures vector
	int baseColor, metalRough, normal, occlusion, emissive;
	
	// Scaling factors for each texture target
	struct { float r, g, b, a; } baseColorFactor;
	float metallicFactor, roughnessFactor;
	float normalScale;
	float occlusionStrength;
	struct { float r, g, b; } emissiveFactor;

	/**
	 * To signal that a certain texture target is active in this Material
	 * struct, its bit is set in the textureMask. You can use this function
	 * to check that:
	 * if (myMaterial.hasTexture(baseColor)) {...}
	 *
	 * @param t The target to query for
	 * @return Boolean to signal whether the texture target is active in
	 * the material.
	 */
	bool hasTexture(PBRTextureTarget target) const;
};

/* With these enums, 0 is reserved to signal uninitialized or invalid data. */
enum class PrimitiveType : uint32_t {
    UNDEFINED = 0,
    POSITION = 1,
    NORMAL = 2,
    TEXCOORD_0 = 3,
    TEXCOORD_1 = 4,
    TANGENT = 5,
    COLOR_0 = 6,
    COLOR_1 = 7,
    JOINTS_0 = 8,
    WEIGHTS_0 = 9
};

/**
 * These integer values are used the same way in OpenGL, Vulkan and glTF. This
 * enum is not needed for translation, it's only for the programmers
 * convenience (easier to read in if/switch statements etc). While this enum
 * exists in (almost) the same definition in the fx-gltf library, we want to
 * avoid exposing that dependency, thus it is re-defined here.
 */
enum class ComponentType : uint16_t {
    NONE = 0,
    INT8 = 5120,
    UINT8 = 5121,
    INT16 = 5122,
    UINT16 = 5123,
    UINT32 = 5125,
    FLOAT32 = 5126
};

/**
 * This struct describes one vertex attribute of a vertex buffer.
 */
struct VertexAttribute {
    PrimitiveType type;			// POSITION, NORMAL, ...
    
    uint32_t offset;			// offset in bytes
    uint32_t length;			// length of ... in bytes
    uint32_t stride;			// stride in bytes
    
    ComponentType componentType;	// eg. 5126 for float
    uint8_t  componentCount;		// eg. 3 for vec3
};

/**
 * This struct represents one (possibly the only) part of a mesh. There is
 * always one vertexBuffer and zero or one indexBuffer (indexed rendering is
 * common but not always used). If there is no index buffer, this is indicated
 * by indexBuffer.data being empty. Each vertex buffer can have one or more
 * vertex attributes.
 */
struct VertexGroup {
	enum PrimitiveMode mode;	// draw as points, lines or triangle?
	size_t numIndices;
	size_t numVertices;
	
	struct {
		enum IndexType type;	// data type of the indices
		std::vector<uint8_t> data; // binary data of the index buffer
	} indexBuffer;
	
	struct {
		std::vector<uint8_t> data; // binary data of the vertex buffer
		std::vector<VertexAttribute> attributes; // description of one
	} vertexBuffer;
	
	struct { float x, y, z; } min;	// bounding box lower left
	struct { float x, y, z; } max;	// bounding box upper right
	
	int materialIndex;		// index to one of the materials
};

/**
 * This struct represents a single mesh as it was loaded from a glTF file. It
 * consists of at least one VertexGroup, which then references other resources
 * such as Materials.
 */
struct Mesh {
	std::string name;
	std::array<float, 16> modelMatrix;
	std::vector<int> vertexGroups;
};

/**
 * The scene struct is simply a collection of objects in the scene as well as
 * the resources used by those objects.
 * Note that parent-child relations are not yet possible.
 */
struct Scene {
	std::vector<Mesh> meshes;
	std::vector<VertexGroup> vertexGroups;
	std::vector<Material> materials;
	std::vector<Texture> textures;
	std::vector<Sampler> samplers;
	std::vector<std::string> uris;
};

/**
 * Parse the given glTF file and create a shallow description of the content.
 * Only the meta-data of the objects in the scene is loaded, not the binary
 * content. The rationale is to provide a means of probing the content of a
 * glTF file without the costly process of loading and decoding large amounts
 * of data. The returned Scene struct can be used to search for specific meshes
 * in the scene, that can then be loaded on their own using the loadMesh()
 * function. Note that the Scene struct received as output argument will be
 * overwritten by this function.
 * After this function completes, the returned Scene struct is completely
 * initialized and all information is final, except for the missing binary
 * data. This means that indices to vectors will remain valid even when the
 * shallow scene struct is filled with data by loadMesh().
 * Note that for URIs only (local) filesystem paths are supported, no
 * URLs using network protocols etc.
 *
 * @param[in] path must be the path to a glTF- or glb-file.
 * @param[out] scene is a reference to a Scene struct that will be filled with the
 * 	meta-data of all objects described in the glTF file.
 * @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS
 */
int probeScene(const std::filesystem::path &path, Scene &scene);

/**
 * This function loads a single mesh from the given file and adds it to the
 * given scene. The scene must already be initialized (via probeScene()).
 * The mesh_index refers to the Scenes meshes array and identifies the mesh to
 * load. To find the mesh you want, iterate over the probed scene and check the
 * meshes details (eg. name).
 * Besides the mesh, this function will also add any associated data to the
 * Scene struct such as Materials and Textures required by the Mesh.
 *
 * @param[in,out] scene	is the scene struct to which the results will be written.
 * @param[in] mesh_index Index of the mesh to load
 * @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS
 */
int loadMesh(Scene &scene, int mesh_index);

/**
 * Load every mesh from the glTF file, as well as materials, textures and other
 * associated objects.
 *
 * @param[in] path	must be the path to a glTF- or glb-file.
 * @param[out] scene is a reference to a Scene struct that will be filled with the
 * 	content of the glTF file being loaded.
 * @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS
 */
int loadScene(const std::filesystem::path &path, Scene &scene);

/**
 * Simply loads a single image at the given path and returns a Texture
 * struct describing it. This is for special use cases only (eg.
 * loading a font atlas) and not meant to be used for regular assets.
 * The sampler is set to -1, signalling that this Texture was loaded
 * outside the context of a glTF-file.
 * If there was an error loading or decoding the image, the returned struct
 * will be cleared to all 0 with path and data being empty; make sure to always
 * check that !data.empty() before using the struct.
 *
 * @param[in] path	must be the path to an image file.
 * @return	Texture struct describing the loaded image.
 */
Texture loadTexture(const std::filesystem::path& path);

/**
 * Loads up the vertex attributes and creates usable vertex buffer bindings
 * to match the desired order of primitive types as used in the vertex
 * shader.
 *
 * @param[in] attributes Vertex attributes
 * @param[in] buffer Buffer handle
 * @param[in] types Primitive type order
 * @return Vertex buffer bindings
 */
VertexBufferBindings loadVertexBufferBindings(const std::vector<VertexAttribute> &attributes,
											  const BufferHandle &buffer,
											  const std::vector<PrimitiveType> &types);

/** @} */

}	// end namespace vkcv::asset