/**
 * @file LLGLTFLoader.h
 * @brief LLGLTFLoader class definition
 *
 * $LicenseInfo:firstyear=2022&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2022, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#ifndef LL_LLGLTFLoader_H
#define LL_LLGLTFLoader_H

#include "tinygltf/tiny_gltf.h"

#include "llglheaders.h"
#include "llmodelloader.h"

// gltf_* structs are temporary, used to organize the subset of data that eventually goes into the material LLSD

class gltf_sampler
{
public:
    // Uses GL enums
    S32 minFilter;      // GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR or GL_LINEAR_MIPMAP_LINEAR
    S32 magFilter;      // GL_NEAREST or GL_LINEAR
    S32 wrapS;          // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT
    S32 wrapT;          // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT
    //S32 wrapR;        // Found in some sample files, but not part of glTF 2.0 spec. Ignored.
    std::string name;   // optional, currently unused
    // extensions and extras are sampler optional fields that we don't support - at least initially
};

class gltf_image
{
public:// Note that glTF images are defined with row 0 at the top (opposite of OpenGL)
    U8* data;               // ptr to decoded image data
    U32 size;               // in bytes, regardless of channel width
    U32 width;
    U32 height;
    U32 numChannels;        // range 1..4
    U32 bytesPerChannel;    // converted from gltf "bits", expects only 8, 16 or 32 as input
    U32 pixelType;          // one of (TINYGLTF_COMPONENT_TYPE)_UNSIGNED_BYTE, _UNSIGNED_SHORT, _UNSIGNED_INT, or _FLOAT
};

class gltf_texture
{
public:
    U32 imageIdx;
    U32 samplerIdx;
    LLUUID imageUuid = LLUUID::null;
};

class gltf_render_material
{
public:
    std::string name;

    // scalar values
    LLColor4    baseColor;      // linear encoding. Multiplied with vertex color, if present.
    double      metalness;
    double      roughness;
    double      normalScale;    // scale applies only to X,Y components of normal
    double      occlusionScale; // strength multiplier for occlusion
    LLColor4    emissiveColor;  // emissive mulitiplier, assumed linear encoding (spec 2.0 is silent)
    std::string alphaMode;      // "OPAQUE", "MASK" or "BLEND"
    double      alphaMask;      // alpha cut-off

    // textures
    U32 baseColorTexIdx;    // always sRGB encoded
    U32 metalRoughTexIdx;   // always linear, roughness in G channel, metalness in B channel
    U32 normalTexIdx;       // linear, valid range R[0-1], G[0-1], B[0.5-1]. Normal = texel * 2 - vec3(1.0)
    U32 occlusionTexIdx;    // linear, occlusion in R channel, 0 meaning fully occluded, 1 meaning not occluded
    U32 emissiveTexIdx;     // always stored as sRGB, in nits (candela / meter^2)

    // texture coordinates
    U32 baseColorTexCoords;
    U32 metalRoughTexCoords;
    U32 normalTexCoords;
    U32 occlusionTexCoords;
    U32 emissiveTexCoords;

    // TODO: Add traditional (diffuse, normal, specular) UUIDs here, or add this struct to LL_TextureEntry??

    bool        hasPBR;
    bool        hasBaseTex, hasMRTex, hasNormalTex, hasOcclusionTex, hasEmissiveTex;

    // This field is populated after upload
    LLUUID      material_uuid = LLUUID::null;

};

class gltf_mesh
{
public:
    std::string name;

    // TODO add mesh import DJH 2022-04

};

class LLGLTFLoader : public LLModelLoader
{
  public:
    typedef std::map<std::string, LLImportMaterial> material_map;

    LLGLTFLoader(std::string filename,
                    S32                                 lod,
                    LLModelLoader::load_callback_t      load_cb,
                    LLModelLoader::joint_lookup_func_t  joint_lookup_func,
                    LLModelLoader::texture_load_func_t  texture_load_func,
                    LLModelLoader::state_callback_t     state_cb,
                    void *                              opaque_userdata,
                    JointTransformMap &                 jointTransformMap,
                    JointNameSet &                      jointsFromNodes,
                    std::map<std::string, std::string> &jointAliasMap,
                    U32                                 maxJointsPerMesh,
                    U32                                 modelLimit); //,
                    //bool                                preprocess );
    virtual ~LLGLTFLoader();

    virtual bool OpenFile(const std::string &filename);

protected:
    tinygltf::Model mGltfModel;
    bool            mGltfLoaded;
    bool            mMeshesLoaded;
    bool            mMaterialsLoaded;

    std::vector<gltf_mesh>              mMeshes;
    std::vector<gltf_render_material>   mMaterials;

    std::vector<gltf_texture>           mTextures;
    std::vector<gltf_image>             mImages;
    std::vector<gltf_sampler>           mSamplers;

private:
    bool parseMeshes();
    void uploadMeshes();
    bool parseMaterials();
    void uploadMaterials();
    bool populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh);
    LLUUID imageBufferToTextureUUID(const gltf_texture& tex);

    //    bool mPreprocessGLTF;

    /*  Below inherited from dae loader - unknown if/how useful here

    void processElement(gltfElement *element, bool &badElement, GLTF *gltf);
    void processGltfModel(LLModel *model, GLTF *gltf, gltfElement *pRoot, gltfMesh *mesh, gltfSkin *skin);

    material_map     getMaterials(LLModel *model, gltfInstance_geometry *instance_geo, GLTF *gltf);
    LLImportMaterial profileToMaterial(gltfProfile_COMMON *material, GLTF *gltf);
    LLColor4         getGltfColor(gltfElement *element);

    gltfElement *getChildFromElement(gltfElement *pElement, std::string const &name);

    bool isNodeAJoint(gltfNode *pNode);
    void processJointNode(gltfNode *pNode, std::map<std::string, LLMatrix4> &jointTransforms);
    void extractTranslation(gltfTranslate *pTranslate, LLMatrix4 &transform);
    void extractTranslationViaElement(gltfElement *pTranslateElement, LLMatrix4 &transform);
    void extractTranslationViaSID(gltfElement *pElement, LLMatrix4 &transform);
    void buildJointToNodeMappingFromScene(gltfElement *pRoot);
    void processJointToNodeMapping(gltfNode *pNode);
    void processChildJoints(gltfNode *pParentNode);

    bool verifyCount(int expected, int result);

    // Verify that a controller matches vertex counts
    bool verifyController(gltfController *pController);

    static bool addVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh, LLSD &log_msg);
    static bool createVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh);

    static LLModel *loadModelFromGltfMesh(gltfMesh *mesh);

    // Loads a mesh breaking it into one or more models as necessary
    // to get around volume face limitations while retaining >8 materials
    //
    bool loadModelsFromGltfMesh(gltfMesh *mesh, std::vector<LLModel *> &models_out, U32 submodel_limit);

    static std::string getElementLabel(gltfElement *element);
    static size_t      getSuffixPosition(std::string label);
    static std::string getLodlessLabel(gltfElement *element);

    static std::string preprocessGLTF(std::string filename);
    */

};
#endif  // LL_LLGLTFLLOADER_H