/*****************************************************************************************
 *                                                                                       *
 * OpenSpace                                                                             *
 *                                                                                       *
 * Copyright (c) 2014-2023                                                               *
 *                                                                                       *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this  *
 * software and associated documentation files (the "Software"), to deal in the Software *
 * without restriction, including without limitation the rights to use, copy, modify,    *
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to    *
 * permit persons to whom the Software is furnished to do so, subject to the following   *
 * conditions:                                                                           *
 *                                                                                       *
 * The above copyright notice and this permission notice shall be included in all copies *
 * or substantial portions of the Software.                                              *
 *                                                                                       *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,   *
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A         *
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT    *
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF  *
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE  *
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                                         *
 ****************************************************************************************/

#ifndef __OPENSPACE_CORE___ASSETMANAGER___H__
#define __OPENSPACE_CORE___ASSETMANAGER___H__

#include <ghoul/lua/luastate.h>
#include <filesystem>
#include <optional>
#include <unordered_map>
#include <list>

namespace openspace {

namespace scripting { struct LuaLibrary; }

class Asset;
class ResourceSynchronization;

/**
 * The AssetManager class manages the loading, initialization, and unloading of all assets
 * loaded in OpenSpace. Most actions of this class are operating in a two-step process
 * where first the intent of an action is registered, which is then executed in the next
 * call to the #update function.
 * All assets are loading through the same Lua state.
 */
class AssetManager {
public:
    AssetManager(ghoul::lua::LuaState* state, std::filesystem::path assetRootDirectory);
    ~AssetManager();

    void deinitialize();

    /**
     * Loads the asset at the provided \p path as a new root asset of the AssetManager.
     * If the asset at that path was already loaded, nothing happens.
     *
     * \param path The path from which the Asset is loaded. This path can be either
     *        relative to the base directory (the path starting with . or ..), an absolute
     *        path (that path starting with *:/ or /) or relative to the global asset root
     *        (if the path starts any other way)
     * \pre \p path must not be the empty string
     */
    void add(const std::string& path);

    /**
     * Removes the asset at the provided \p path if it has been loaded by this
     * AssetManager. If the asset at that path was not found, nothing happens.
     *
     * \param path The path from which the Asset is loaded. This path can be either
     *        relative to the base directory (the path starting with . or ..), an absolute
     *        path (that path starting with *:/ or /) or relative to the global asset root
     *        (if the path starts any other way)
     * \pre \p path must not be the empty string
     */
    void remove(const std::string& path);

    /**
     * Update function that should be called at least once per frame that will load all
     * queued asset loads and asset removal.
     */
    void update();

    scripting::LuaLibrary luaLibrary();

    /**
     * Returns all assets that have been loaded by the AssetManager. The order of the
     * assets is undefined.
     *
     * \return A list of all assets that have been previously loaded by the AssetManager
     */
    std::vector<const Asset*> allAssets() const;

    std::vector<const ResourceSynchronization*> allSynchronizations() const;

    /**
     * Loads the provided \p asset as a child of the provided \p parent. Loading an asset
     * means that asset file gets executed and the meta information is extracted from it.
     * The \p parent is the asset file that caused this loading to happen and can be a
     * `nullptr` if the asset is to be loaded as a root asset.
     *
     * \param asset The asset that should be loaded
     * \param parent The parent of the loaded asset file or `nullptr` if the asset is a
     *        root asset
     * \pre \p asset must not be a nullptr
     */
    bool loadAsset(Asset* asset, Asset* parent);

    /**
     * Unload the provided \p asset by removing all information about it from the Lua
     * state and placing the asset onto the deletion queue. Please note that the asset
     * will not actually get destroyed until the next #update call to the AssetManager.
     *
     * \param asset The asset that should get unloaded
     * \pre \p asset must not be a nullptr
     */
    void unloadAsset(Asset* asset);

    /**
     * This function calls the `onInitialize` function that was specified in the file of
     * the provided \p asset.
     *
     * \param asset The asset file whose `onInitialize` function should be called
     */
    void callOnInitialize(Asset* asset) const;

    /**
     * This function calls the `onDeinitialize` function that was specified in the file of
     * the provided \p asset.
     *
     * \param asset The asset file whose `onDeinitialize` function should be called
     */
    void callOnDeinitialize(Asset* asset) const;

private:
    /// Creates and registers all of the callback functions that the asset is expected to
    /// call in the file, for example the `localResource`, `require`, etc.
    void setUpAssetLuaTable(Asset* asset);

    /// Returns the loaded Asset by either trying to load the asset at the provided path
    /// or returning a previously loaded copy
    Asset* retrieveAsset(const std::filesystem::path& path,
        const std::filesystem::path& retriever,
        std::optional<bool> explicitEnable = std::nullopt);

    /// Setup the asset table of the provided asset in the shared Lua state
    void setCurrentAsset(Asset* asset);

    /// Takes the asset path, determines the type of path (relative to base, relative to
    /// root or absolute and returns fully formed path
    std::filesystem::path generateAssetPath(const std::filesystem::path& baseDirectory,
        const std::string& assetPath) const;

    //
    // Assets
    //

    /// The authoritative list of all assets loaded through the AssetManager
    std::vector<std::unique_ptr<Asset>> _assets;

    /// A list of all root assets that have been loaded directly by the `add` function
    std::vector<Asset*> _rootAssets;

    /// This list contains all of the assets that are queued to be loading in the next
    /// update call
    std::list<std::string> _assetAddQueue;

    /// The list contains all of the assets that should be removed in the next update call
    std::list<std::string> _assetRemoveQueue;

    /// This list contains all assets that need to be initialized in the next update call
    std::vector<Asset*> _toBeInitialized;

    /// This list contains all of the assets that will be deleted in the next update call
    std::vector<std::unique_ptr<Asset>> _toBeDeleted;

    //
    // ResourceSynchronizations
    //

    /// Collection that stores the assets that have requested each ResourceSynchronization
    struct SyncItem {
        std::unique_ptr<ResourceSynchronization> synchronization;
        std::vector<Asset*> assets;
    };
    /// Authoritative list over all ResourceSynchronizations that have been requested by
    /// any asset
    std::unordered_map<std::string, std::unique_ptr<SyncItem>> _synchronizations;
    /// The list of ResourceSynchronizations that were not finished in the last update
    /// call
    std::vector<SyncItem*> _unfinishedSynchronizations;

    //
    // Other values
    //

    /// The location of the asset root directory
    std::filesystem::path _assetRootDirectory;

    /// The Lua state that is used for all asset initialization
    ghoul::lua::LuaState* _luaState = nullptr;

    // References to the onInitialize and the onDeinitialize functions for each Asset
    std::unordered_map<Asset*, std::vector<int>> _onInitializeFunctionRefs;
    std::unordered_map<Asset*, std::vector<int>> _onDeinitializeFunctionRefs;

    int _assetsTableRef = 0;
};

} // namespace openspace

#endif // __OPENSPACE_CORE___ASSETMANAGER___H__
