#ifndef LV_GLTFDATA_HPP
#define LV_GLTFDATA_HPP

#include "../../../lv_conf_internal.h"

#if LV_USE_GLTF

#include "../gltf_view/lv_gltf.h"
#include "lv_gltf_data_internal.h"

#include "../../../misc/lv_array.h"
#include "../../../drivers/opengles/lv_opengles_private.h"

#include "../../../misc/lv_types.h"
#include "../../../misc/lv_ll.h"
#include "../../../misc/lv_event.h"

#ifdef __cplusplus

#include <fastgltf/math.hpp>
#include <string>
#include <vector>
#include <map>
#include <fastgltf/types.hpp>

// Vector of int32_t's
using UintVector = std::vector<uint32_t>;
// Vector of int32_t's
using IntVector = std::vector<int32_t>;
// Vector of int64_t's
using LongVector = std::vector<int64_t>;
// Pointer to fastgltf::Node
using NodePtr = fastgltf::Node *;
// A standard 4x4 transform matrix
using Transform = fastgltf::math::fmat4x4;
// Pair of Node pointer and int32_t
using NodeIndexPair = std::pair<NodePtr, size_t>;
// Pair of float and Node/Index pair
using NodeIndexDistancePair = std::pair<float, NodeIndexPair>;
// Vector of NodeIndexPair
using NodePairVector = std::vector<NodeIndexPair>;
// Vector of NodeIndexDistancePair
using NodeDistanceVector = std::vector<NodeIndexDistancePair>;
// Map of uint32_t to NodePairVector
using MaterialIndexMap = std::map<uint32_t, NodePairVector>;
// Map of Node Pointers to Transforms
using NodeTransformMap = std::map<NodePtr, Transform>;
// Map of Nodes by string (name)
using StringNodeMap = std::map<std::string, NodePtr>;
// Map of Nodes by string (name)
using NodeIntMap = std::map<NodePtr, uint32_t>;
// Map of Nodes by string (name)
using NodeVector = std::vector<NodePtr>;
// Map of Node Index to Map of Prim Index to CenterXYZ+RadiusW Vec4
using NodePrimCenterMap = std::map<uint32_t, std::map<uint32_t, fastgltf::math::fvec4> >;

#define LV_GLTF_NODE_CHANNEL_X 0
#define LV_GLTF_NODE_CHANNEL_Y 1
#define LV_GLTF_NODE_CHANNEL_Z 2
#define LV_GLTF_NODE_CHANNEL_W 4

typedef enum {
    LV_GLTF_NODE_PROP_POSITION,
    LV_GLTF_NODE_PROP_ROTATION,
    LV_GLTF_NODE_PROP_SCALE,
} lv_gltf_node_prop_t;

typedef struct {
    lv_3dpoint_t local_position;
    lv_3dpoint_t world_position;
    lv_3dpoint_t scale;
    lv_3dpoint_t rotation;
} lv_gltf_model_node_data_t;

typedef struct {
    lv_gltf_node_prop_t prop;
    uint8_t channel;
    float value;
} lv_gltf_write_op_t;

typedef struct {
    GLuint drawsBuffer;
    std::vector<lv_gltf_primitive_t> primitives;
} lv_gltf_mesh_data_t;

typedef struct  {
    lv_event_list_t event_list;
    lv_gltf_model_node_data_t node_data;
    bool read_world_position;
    bool value_changed;
} lv_gltf_model_node_attr_t;


struct _lv_gltf_model_node_t {
    lv_gltf_model_t * model;
    const char * numeric_path;
    const char * path;
    fastgltf::Node * fastgltf_node;
    lv_gltf_model_node_attr_t * read_attrs;
    lv_array_t write_ops;
};

struct _lv_gltf_model_t {
    const char * filename;
    fastgltf::Asset asset;
    lv_array_t nodes;
    NodeVector node_by_light_index;
    NodeTransformMap node_transform_cache;
    MaterialIndexMap opaque_nodes_by_material_index;
    MaterialIndexMap blended_nodes_by_material_index;
    std::vector<size_t> validated_skins;
    std::vector<GLuint> skin_tex;
    NodePrimCenterMap local_mesh_to_center_points_by_primitive;
    lv_gltf_t * viewer;

    std::vector<lv_gltf_mesh_data_t> meshes;
    std::vector<GLuint> textures;
    lv_array_t compiled_shaders;
    std::map<fastgltf::Node *, std::vector<uint32_t> > channel_set_cache;
    fastgltf::math::fmat4x4 view_mat;
    fastgltf::math::fvec3 view_pos;
    fastgltf::math::fvec3 vertex_max;
    fastgltf::math::fvec3 vertex_min;
    fastgltf::math::fvec3 vertex_cen;

    lv_timer_t * animation_update_timer;

    size_t current_animation;
    size_t last_material_index;

    uint32_t last_camera_index;
    int32_t last_anim_num;

    float bound_radius;

    uint32_t current_animation_max_time;
    uint32_t local_timestamp;
    uint32_t last_tick;
    uint32_t camera;

    bool is_animation_enabled;
    bool last_pass_was_transmission;
    bool last_frame_was_antialiased;
    bool last_frame_no_motion;
    bool _last_frame_no_motion;
    bool write_ops_pending;
    bool write_ops_flushed;
    struct _lv_gltf_model_t * linked_view_source;
};

/**
 * @brief Retrieve a specific texture from the GLTF model data.
 *
 * @param data Pointer to the lv_gltf_data_t object containing the model data.
 * @param index The index of the texture to retrieve.
 * @return Pointer to the texture object.
 */
GLuint lv_gltf_data_get_texture(lv_gltf_model_t * data, size_t index);


/**
 * @brief Retrieve the minimum bounds (X/Y/Z) of the model from the GLTF data.
 *
 * @param data Pointer to the lv_gltf_data_t object containing the model data.
 * @return Pointer to a 3-element float array representing the minimum bounds.
 */
fastgltf::math::fvec3 lv_gltf_data_get_bounds_min(const lv_gltf_model_t * data);

/**
 * @brief Retrieve the maximum bounds (X/Y/Z) of the model from the GLTF data.
 *
 * @param D Pointer to the lv_gltf_data_t object containing the model data.
 * @return Pointer to a 3-element float array representing the maximum bounds.
 */
fastgltf::math::fvec3 lv_gltf_data_get_bounds_max(const lv_gltf_model_t * data);

/**
 * @brief Retrieve the center coordinates of the GLTF data object.
 *
 * @param D Pointer to the lv_gltf_data_t object from which to get the center.
 * @return Pointer to an array containing the center coordinates (x, y, z).
 */
fastgltf::math::fvec3 lv_gltf_data_get_center(const lv_gltf_model_t * data);

/**
 * @brief Retrieve the filename of the GLTF model.
 *
 * @param D Pointer to the lv_gltf_data_t object containing the model data.
 * @return Pointer to a constant character string representing the filename.
 */
const char * lv_gltf_get_filename(const lv_gltf_model_t * data);

/**
 * @brief Check if the centerpoint cache contains a specific entry.
 *
 * @param data Pointer to the lv_gltf_data_t object containing the model data.
 * @param index The index of the entry to check.
 * @param element The specific parameter to check within the cache.
 * @return True if the cache contains the entry, false otherwise.
 */
bool lv_gltf_data_centerpoint_cache_contains(lv_gltf_model_t * data, size_t index, int32_t element);

/**
 * @brief Retrieve a specific primitive from a mesh.
 *
 * @param M Pointer to the MeshData structure containing the mesh data.
 * @param I The index of the primitive to retrieve.
 * @return Pointer to the primitive data.
 */
lv_gltf_primitive_t * lv_gltf_data_get_primitive_from_mesh(lv_gltf_mesh_data_t * M, size_t I);

/**
 * @brief Retrieve the asset associated with the GLTF model data.
 *
 * @param D Pointer to the lv_gltf_data_t object containing the model data.
 * @return Pointer to the asset data.
 */
fastgltf::Asset * lv_gltf_data_get_asset(lv_gltf_model_t * data);

/**
 * @brief Retrieve mesh data for a specific index from the GLTF model data.
 *
 * @param D Pointer to the lv_gltf_data_t object containing the model data.
 * @param I The index of the mesh data to retrieve.
 * @return Pointer to the MeshData structure containing the mesh data.
 */
lv_gltf_mesh_data_t * lv_gltf_data_get_mesh(lv_gltf_model_t * data, size_t index);

/**
 * @brief Retrieve the skin texture index for a specific entry in the GLTF model data.
 *
 * @param data Pointer to the lv_gltf_data_t object containing the model data.
 * @param index The index of the entry for which to retrieve the skin texture index.
 * @return The skin texture index.
 */
GLuint lv_gltf_data_get_skin_texture_at(lv_gltf_model_t * data, size_t index);

/**
 * @brief Check if the validated skins contain a specific entry.
 *
 * @param data Pointer to the lv_gltf_data_t object containing the model data.
 * @param index The index of the skin to check.
 * @return True if the validated skins contain the entry, false otherwise.
 */
bool lv_gltf_data_validated_skins_contains(lv_gltf_model_t * data, size_t index);

/**
 * @brief Validate a specific skin in the GLTF model data.
 *
 * @param data Pointer to the lv_gltf_data_t object containing the model data.
 * @param index The index of the skin to validate.
 */
void lv_gltf_data_validate_skin(lv_gltf_model_t * data, size_t index);

/**
 * @brief Add an opaque node primitive to the GLTF model data.
 *
 * @param D Pointer to the lv_gltf_data_t object containing the model data.
 * @param I The index of the primitive to add.
 * @param N Pointer to the NodePtr representing the node to add.
 * @param P The specific parameter associated with the primitive.
 */
void lv_gltf_data_add_opaque_node_primitive(lv_gltf_model_t * data, size_t index, fastgltf::Node * node,
                                            size_t primitive_index);

/**
 * @brief Add a blended node primitive to the GLTF model data.
 *
 * @param D Pointer to the lv_gltf_data_t object containing the model data.
 * @param I The index of the primitive to add.
 * @param N Pointer to the NodePtr representing the node to add.
 * @param P The specific parameter associated with the primitive.
 */
void lv_gltf_data_add_blended_node_primitive(lv_gltf_model_t * data, size_t mesh_index, fastgltf::Node * node,
                                             size_t primitive_index);

/**
 * @brief Set the cached transformation matrix for a specific node in the GLTF model data.
 *
 * @param D Pointer to the lv_gltf_data_t object containing the model data.
 * @param N Pointer to the NodePtr representing the node for which to set the transformation.
 * @param M The transformation matrix to cache.
 */
void lv_gltf_data_set_cached_transform(lv_gltf_model_t * data, fastgltf::Node * node, fastgltf::math::fmat4x4 M);

/**
 * @brief Clear the transformation cache for the GLTF model data.
 *
 * @param D Pointer to the lv_gltf_data_t object containing the model data.
 */
void lv_gltf_data_clear_transform_cache(lv_gltf_model_t * data);

/**
 * @brief Retrieve the cached transformation matrix for a specific node in the GLTF model data.
 *
 * @param D Pointer to the lv_gltf_data_t object containing the model data.
 * @param N Pointer to the NodePtr representing the node for which to retrieve the transformation.
 * @return The cached transformation matrix.
 */
fastgltf::math::fmat4x4 lv_gltf_data_get_cached_transform(lv_gltf_model_t * data, fastgltf::Node * node);

/**
 * @brief Check if a cached transformation matrix exists for a given node.
 *
 * @param D Pointer to the lv_gltf_data_t object containing the model data.
 * @param N Pointer to the NodePtr representing the node for which to retrieve the transformation.
 * @return true if a cache item exists, false otherwise
 int32_t*/
bool lv_gltf_data_has_cached_transform(lv_gltf_model_t * data, fastgltf::Node * node);

/**
 * @brief Check if the transformation cache is empty.
 *
 * @param D Pointer to the lv_gltf_data_t object containing the model data.
 * @return True if the transformation cache is empty, false otherwise.
 */
bool lv_gltf_data_transform_cache_is_empty(lv_gltf_model_t * data);

/**
 * @brief Retrieve the size of the skins in the GLTF model data.
 *
 * @param D Pointer to the lv_gltf_data_t object containing the model data.
 * @return The size of the skins.
 */
size_t lv_gltf_data_get_skins_size(lv_gltf_model_t * data);

/**
 * @brief Retrieve a specific skin from the GLTF model data.
 *
 * @param D Pointer to the lv_gltf_data_t object containing the model data.
 * @param I The index of the skin to retrieve.
 * @return The skin index.
 */
size_t lv_gltf_data_get_skin(lv_gltf_model_t * data, size_t index);

/**
 * @brief Ingest and discover defines for a specific node and primitive in the GLTF model data.
 *
 * @param data_obj Pointer to the lv_gltf_data_t object containing the model data.
 * @param node Pointer to the node for which to ingest defines.
 * @param prim Pointer to the primitive for which to ingest defines.
 */
void lv_gltf_data_injest_discover_defines(lv_gltf_model_t * data, fastgltf::Node * node, fastgltf::Primitive * prim);

/**
 * @brief Retrieve the center point of a specific mesh element from the GLTF model data.
 *
 * @param gltf_data Pointer to the lv_gltf_data_t object containing the model data.
 * @param matrix The transformation matrix to apply when calculating the center point.
 * @param meshIndex The index of the mesh from which to retrieve the center point.
 * @param elem The specific element index within the mesh.
 * @return The center point as a fastgltf::math::fvec3 structure.
 */
fastgltf::math::fvec3 lv_gltf_data_get_centerpoint(lv_gltf_model_t * gltf_data, fastgltf::math::fmat4x4 matrix,
                                                   size_t mesh_index,
                                                   int32_t elem);


lv_gltf_mesh_data_t * lv_gltf_get_new_meshdata(lv_gltf_model_t * _data);

lv_gltf_model_t * lv_gltf_data_create_internal(const char * gltf_path, fastgltf::Asset);

lv_gltf_model_t * lv_gltf_data_load_internal(const void * data_source, size_t data_size,
                                             lv_opengl_shader_manager_t * shaders);

fastgltf::math::fvec4 lv_gltf_get_primitive_centerpoint(lv_gltf_model_t * data, fastgltf::Mesh & mesh,
                                                        uint32_t prim_num);

fastgltf::math::fvec3 get_cached_centerpoint(lv_gltf_model_t * data, size_t index, int32_t element,
                                             fastgltf::math::fmat4x4 matrix);

void lv_gltf_data_delete_textures(lv_gltf_model_t * data);
GLuint lv_gltf_data_create_texture(lv_gltf_model_t * data);
void lv_gltf_model_node_init(lv_gltf_model_t * model, lv_gltf_model_node_t * node, fastgltf::Node * fastgltf_node,
                             const char * path,
                             const char * num_path);

void lv_gltf_model_node_deinit(lv_gltf_model_node_t * node);

/**
 * @brief Retrieve the pixel data for a specific texture in a GLTF model.
 *
 * @param pixels Pointer to the memory where the pixel data will be stored.
 * @param data_obj Pointer to the lv_gltf_data_t object containing the model data.
 * @param model_texture_index The index of the texture in the model.
 * @param mipmapnum The mipmap level to retrieve pixel data for.
 * @param width The width of the texture.
 * @param height The height of the texture.
 * @param has_alpha Flag indicating whether the texture includes an alpha channel.
 * @return True if the pixel data was successfully retrieved, false otherwise.
 */
bool lv_gltf_data_get_texture_pixels(void * pixels, lv_gltf_model_t * data_obj, uint32_t model_texture_index,
                                     uint32_t mipmapnum,
                                     uint32_t width, uint32_t height, bool has_alpha);

uint32_t lv_gltf_data_get_animation_total_time(lv_gltf_model_t * data, uint32_t index);
std::vector<uint32_t> * lv_gltf_data_animation_get_channel_set(std::size_t anim_num, lv_gltf_model_t * data,
                                                               fastgltf::Node * node);
void lv_gltf_data_animation_matrix_apply(float timestamp, std::size_t anim_num, lv_gltf_model_t * gltf_data,
                                         fastgltf::Node * node,
                                         fastgltf::math::fmat4x4 & matrix);

lv_gltf_model_node_t * lv_gltf_model_node_get_by_internal_node(lv_gltf_model_t * model,
                                                               const fastgltf::Node * fastgltf_node);

void lv_gltf_model_send_new_values(lv_gltf_model_t * model);

#endif


#endif /*LV_USE_GLTF*/
#endif /*LV_GLTFVIEW_H*/
