/******************************************************************************
*	File		-	content_manager.cpp
*	Author		-	Joey Pollack
*	Date		-	2022/02/22 (y/m/d)
*	Mod Date	-	2022/02/22 (y/m/d)
*	Description	-	Keeps track of all resource files in the project.
*                   Reads/Writes meta-data to the contents_meta.xml file.
*                   Also manages the physical location of each asset file.
******************************************************************************/

#include "content_manager.h"
#include <editor/editor.h>
#include "../project.h"
#include <pugixml.hpp>
#include "editor_asset.h"

// Asset types
#include "tile_set.h"

namespace lunarium { namespace editor 
{
    ContentManager* ContentManager::mpInstance = nullptr;

    ContentManager::ContentManager()
        : mpProject(nullptr), mNextID(0)
    {

    }

    ContentManager& ContentManager::GetInstance()
    {
        if (!mpInstance)
        {
            mpInstance = new ContentManager();
        }

        return *mpInstance;
    }

    void ContentManager::FreeInstance()
    {
        if (mpInstance)
        {
            delete mpInstance;
            mpInstance = nullptr;
        }
    }

    OpRes ContentManager::Load(Project* project)
    {
        Unload();

        mpProject = project;
        std::filesystem::path root = mpProject->GetRootDirectory();
        mContentFile = root / "contents/content_meta.xml";

        // If this is a new project being generated just create a base contents file
        if (!std::filesystem::exists(mContentFile))
        {
            // pugi::xml_node proj_node = doc.append_child("Project");
            // proj_node.append_attribute("Name").set_value(mpProject->GetName().c_str());

            // mNextID = 0;
            // proj_node.append_child("NextID").append_attribute("ID").set_value(mNextID);
            // // pugi::xml_node proj_node = doc.append_child("Contents");
            // proj_node.append_child("Contents");
            // if (!doc.save_file(mContentFile.string().c_str()))
            // {
            //     return OpRes::Fail("ContentManager could not save file: %s", mContentFile.string().c_str());
            // }

            return Save();
        }

        // Load the file
        pugi::xml_document doc;
        pugi::xml_parse_result result = doc.load_file(mContentFile.string().c_str());
        if (result.status != pugi::xml_parse_status::status_ok)
        {
            return OpRes::Fail("Could not open contents file: %s, %s", mContentFile.string().c_str(), result.description());
        }

        pugi::xml_node proj_node = doc.child("Project");
        if (!proj_node)
        {
            return OpRes::Fail("content_meta.xml missing Project root node");
        }

        // Get ID
        pugi::xml_node ID = proj_node.child("NextID");

        if (!ID)
        {
            return OpRes::Fail("content_meta.xml missing NextID node");
        }

        mNextID = ID.attribute("ID").as_ullong();

        pugi::xml_node contents = proj_node.child("Contents");

        if (!contents)
        {
            return OpRes::Fail("content_meta.xml missing Contents node");
        }

        // Iterate through content
        for (pugi::xml_node asset = contents.child("Asset"); asset; asset = asset.next_sibling("Asset"))
        {
            if (!IsValidAsset(asset))
            {
                return OpRes::Fail("Invalid asset node in content_meta.xml");
            }

            // Load Type
            AssetType type = (AssetType)asset.attribute("Type").as_int();
            EditorAsset* pAsset = CreateAsset(type);

            if (!pAsset)
            {
                return OpRes::Fail("Could not create Editor Asset. Unknown type: %n", (int)type);
            }

            // Load ID
            pAsset->mID = (uint64_t)asset.attribute("ID").as_ullong();

            // Load Location
            pAsset->mLocation = std::filesystem::path(asset.attribute("Location").as_string());

            // Load type specific data
            if (Failed(pAsset->LoadFromXML(asset).LogIfFailed(Editor::LogCat)))
            {
                return OpRes::Fail("Could not load asset type specific data for asset with ID: %llu, File: %s", 
                                    pAsset->GetID(), pAsset->GetFileLocation().filename().string().c_str());
            }

            // Store asset
            if (mAssets.find(pAsset->mID) != mAssets.end())
            {
                return OpRes::Fail("Asset ID collision, ID: %llu, File: %s", 
                                    pAsset->GetID(), pAsset->GetFileLocation().filename().string().c_str());
            }

            mAssets[pAsset->mID] = pAsset;
        }

        return OpRes::OK();
    }

    OpRes ContentManager::Save()
    {
        if (!mpProject)
        {
            return OpRes::Fail("ConentManager::Save failed: no project set");
        }

        // Save header info
        pugi::xml_document doc;
        pugi::xml_node proj_node = doc.append_child("Project");
        proj_node.append_attribute("Name").set_value(mpProject->GetName().c_str());
        proj_node.append_child("NextID").append_attribute("ID").set_value(mNextID);
        // pugi::xml_node proj_node = doc.append_child("Contents");

        // Save all assets
        pugi::xml_node contents = proj_node.append_child("Contents");
        for (auto iter = mAssets.begin(); iter != mAssets.end(); iter++)
        {
            EditorAsset* pAsset = iter->second;
            pugi::xml_node asset = contents.append_child("Asset");

            asset.append_attribute("Type").set_value((int)pAsset->GetType());
            asset.append_attribute("ID").set_value(pAsset->GetID());
            asset.append_attribute("Location").set_value(pAsset->GetFileLocation().string().c_str());

            if (Failed(pAsset->SaveToXML(asset).LogIfFailed(Editor::LogCat)))
            {
                return OpRes::Fail("Could not save asset meta data for file: %s", pAsset->GetFileLocation().string().c_str());
            }
        }

        if (!doc.save_file(mContentFile.string().c_str()))
        {
            return OpRes::Fail("ContentManager could not save file: %s", mContentFile.string().c_str());
        }

        return OpRes::OK();
    }

    void ContentManager::Unload()
    {
        mpProject = nullptr;
        FreeAssets();
        mContentFile = "";
        mNextID = 0;
    }
    
    void ContentManager::GetAllAssetIDs(std::vector<uint64_t>& container) const
    {
        container.clear();
        for (auto iter = mAssets.begin(); iter != mAssets.end(); iter++)
        {
            container.push_back(iter->first);
        }
    }

    void ContentManager::GetAllAssetsByType(std::vector<EditorAsset*>& container, AssetType type) const
    {
        container.clear();
        for (auto iter = mAssets.begin(); iter != mAssets.end(); iter++)
        {
            if (iter->second->GetType() == type)
            {
                container.push_back(iter->second);      
            }
        }
    }

    EditorAsset* ContentManager::GetAsset(uint64_t id)
    {   
        auto iter = mAssets.find(id);
        if (iter == mAssets.end())
        {
            return nullptr;
        }

        return iter->second;
    }    

    
    OpRes ContentManager::AddGeneratedAsset(EditorAsset* asset, uint64_t& id)
    {
        if (mAssets.find(mNextID) != mAssets.end())
        {
            return OpRes::Fail("Could not import asset, ID collision. ID: %llu", mNextID);
        }

        asset->mID = mNextID;
        mNextID++; 
        mAssets[asset->mID] = asset;
        id = asset->mID;

        return OpRes::OK();        
    }

    OpRes ContentManager::ImportFile(std::filesystem::path file, std::filesystem::path to_location, AssetType type, uint64_t& id)
    {
        if (mAssets.find(mNextID) != mAssets.end())
        {
            return OpRes::Fail("Could not import asset, ID collision. ID: %llu", mNextID);
        }

        if (!std::filesystem::copy_file(file, to_location))
        {
            return OpRes::Fail("Could not copy asset file from: %s to: %s", file.string().c_str(), to_location.string().c_str());
        }

        EditorAsset* pAsset = CreateAsset(type);
        pAsset->mID = mNextID;
        mNextID++;
        pAsset->mLocation = to_location;
        if (Failed(pAsset->LoadRawFile().LogIfFailed(Editor::LogCat)))
        {
            delete pAsset;
            return OpRes::Fail("Could not load asset data from raw file: %s", file.string().c_str());
        }

        mAssets[pAsset->mID] = pAsset;
        id = pAsset->mID;

        return OpRes::OK();        
    }

    void ContentManager::RemoveAsset(uint64_t asset_id)
    {
        auto iter = mAssets.find(asset_id);
        if (iter == mAssets.end())
        {
            return;
        }

        delete iter->second;
        mAssets.erase(iter);
    }

    bool ContentManager::IsValidAsset(pugi::xml_node& node)
    {
        if (!node.attribute("ID")) { return false; }
        if (!node.attribute("Type")) { return false; }
        if (!node.attribute("Location")) { return false; }

        return true;
    }

    EditorAsset* ContentManager::CreateAsset(AssetType type)
    {
        switch (type)
        {
            case AssetType::EATYPE_TILE_SET: return new TileSet();

            default: return nullptr;
        }
    }
    
    void ContentManager::FreeAssets()
    {
        for (auto iter = mAssets.begin(); iter != mAssets.end(); iter++)
        {
            delete iter->second;
        }

        mAssets.clear();
    }
}}