/**
 * @file   llgltfmateriallist.h
 *
 * $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$
 */


#pragma once

#include "llassettype.h"
#include "llextendedstatus.h"
#include "llfetchedgltfmaterial.h"
#include "llgltfmaterial.h"
#include "llpointer.h"

#include <unordered_map>

class LLFetchedGLTFMaterial;
class LLGLTFOverrideCacheEntry;

class LLGLTFMaterialList
{
public:
    LLGLTFMaterialList() {}


    LLFetchedGLTFMaterial* getMaterial(const LLUUID& id);

    void addMaterial(const LLUUID& id, LLFetchedGLTFMaterial* material);
    void removeMaterial(const LLUUID& id);

    void flushMaterials();

    // Queue an modification of a material that we want to send to the simulator.  Call "flushUpdates" to flush pending updates.
    //  id - ID of object to modify
    //  side - TexureEntry index to modify, or -1 for all sides
    //  mat - material to apply as override, or nullptr to remove existing overrides and revert to asset
    //
    // NOTE: do not use to revert to asset when applying a new asset id, use queueApply below
    static void queueModify(const LLViewerObject* obj, S32 side, const LLGLTFMaterial* mat);

    // Queue an application of a material asset we want to send to the simulator.  Call "flushUpdates" to flush pending updates.
    //  object_id - ID of object to apply material asset to
    //  side - TextureEntry index to apply material to, or -1 for all sides
    //  asset_id - ID of material asset to apply, or LLUUID::null to disassociate current material asset
    //
    // NOTE: Implicitly clears most override data if present
    static void queueApply(const LLViewerObject* obj, S32 side, const LLUUID& asset_id);

    // Queue an application of a material asset we want to send to the simulator.  Call "flushUpdates" to flush pending updates.
    //  object_id - ID of object to apply material asset to
    //  side - TextureEntry index to apply material to, or -1 for all sides
    //  asset_id - ID of material asset to apply, or LLUUID::null to disassociate current material asset
    //  mat - override material, if null, will clear most override data
    static void queueApply(const LLViewerObject* obj, S32 side, const LLUUID& asset_id, const LLGLTFMaterial* mat);

    // flush pending material updates to the simulator
    // Automatically called once per frame, but may be called explicitly
    // for cases that care about the done_callback forwarded to LLCoros::instance().launch
    static void flushUpdates(void(*done_callback)(bool) = nullptr);

    static void addSelectionUpdateCallback(void(*update_callback)(const LLUUID& object_id, S32 side));
    
    // Queue an explicit LLSD ModifyMaterialParams update apply given override data
    //  overrides -- LLSD map (or array of maps) in the format:
    //      object_id   UUID(required)      id of object
    //      side        integer(required)   TE index of face to set, or -1 for all faces
    //      gltf_json   string(optional)    override data to set, empty string nulls out override data, omissions of this parameter keeps existing data
    //      asset_id    UUID(optional)      id of material asset to set, omission of this parameter keeps existing material asset id
    //    
    // NOTE: Unless you already have a gltf_json string you want to send, strongly prefer using queueModify
    // If the queue/flush API is insufficient, extend it.
    static void queueUpdate(const LLSD& data);

    // Called by batch builder to give LLGLTMaterialList an opportunity to apply
    // any override data that arrived before the object was ready to receive it
    void applyQueuedOverrides(LLViewerObject* obj);

    // Apply an override update with the given data
    void applyOverrideMessage(LLMessageSystem* msg, const std::string& data);

private:
    friend class LLGLTFMaterialOverrideDispatchHandler;
    // save an override update that we got from the simulator for later (for example, if an override arrived for an unknown object)
    // NOTE: this is NOT for applying overrides from the UI, see queueModifyMaterial above
    void queueOverrideUpdate(const LLUUID& id, S32 side, LLGLTFMaterial* override_data);

    static void modifyMaterialCoro(std::string cap_url, LLSD overrides, void(*done_callback)(bool));

protected:
    static void onAssetLoadComplete(
        const LLUUID& asset_uuid,
        LLAssetType::EType type,
        void* user_data,
        S32 status,
        LLExtStat ext_status);

    typedef std::unordered_map<LLUUID, LLPointer<LLFetchedGLTFMaterial > > uuid_mat_map_t;
    uuid_mat_map_t mList;

    typedef std::vector<LLPointer<LLGLTFMaterial> > override_list_t;
    typedef std::unordered_map<LLUUID, override_list_t > queued_override_map_t;
    queued_override_map_t mQueuedOverrides;

    LLUUID mLastUpdateKey;

    struct ModifyMaterialData
    {
        LLUUID object_id;
        S32 side = -1;
        LLGLTFMaterial override_data;

        bool has_override = false;
    };

    typedef std::list<ModifyMaterialData> modify_queue_t;
    static modify_queue_t sModifyQueue;

    struct ApplyMaterialAssetData
    {
        LLUUID object_id;
        S32 side = -1;
        LLUUID asset_id;
        LLPointer<LLGLTFMaterial> override_data;
    };

    typedef std::list<ApplyMaterialAssetData> apply_queue_t;
    static apply_queue_t sApplyQueue;

    // data to be flushed to ModifyMaterialParams capability
    static LLSD    sUpdates;
};

extern LLGLTFMaterialList gGLTFMaterialList;