/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
***************************************************************************/

#pragma once

#include "glmCoreAPI.h"
#include "glmMap.h"
#include "glmString.h"
#include "glmAssert.h"
#include "glmFileDir.h"
#include "glmMutex.h"
#include "glmScopedLock.h"
#ifdef _MSC_VER
#include <direct.h>
#include <io.h>
#else
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <pwd.h>
#endif

#ifdef GLM_ENABLE_TRACY_MEMORY_PROFILER //in header files, tracy needs to be protected beacause it is not fully exported in the golaem devkit
    #include "glmTracy.h"
#endif


namespace glm
{
    /**
     * \brief A cache that can store the instances corresponding to files, and automatically reload them only if the time stamp changes on files
     *
     * \author dev@golaem.com
     *
     */
    template<class T>
    class FilesCache
    {
        //TODO: clear FbxManager and CachedFbxFiles
    public: //methods
        FilesCache();
        ~FilesCache();
        void setLoadUnloadFileCallbacks(T (*loadFileCallback) (const char*), void (*unloadFileCallback) (T, const char*));

        T getOrLoadFile(const char* filePath);
        glm::SpinLock& getFilesCacheLock() { return _filesHolderLock; }
        void clear();

    private:    //data
        T(*_loadFileCallback)(const char*) = NULL;
        void (*_unloadFileCallback)(T, const char*) = NULL;
        GlmMap<GlmString, std::pair<T, uint64_t>>  _filesHolder;
        glm::SpinLock _filesHolderLock;
    };

    //------------------------------------------------------------
    template<class T>
    FilesCache<T>::FilesCache()
    {
    }

    //------------------------------------------------------------
    template<class T>
    FilesCache<T>::~FilesCache()
    {
        clear();
    }

    //------------------------------------------------------------
    template<class T>
    void FilesCache<T>::setLoadUnloadFileCallbacks(T (*loadFileCallback) (const char*), void (*unloadFileCallback) (T, const char*))
    {
        _loadFileCallback = loadFileCallback;
        _unloadFileCallback = unloadFileCallback;
    }

    //------------------------------------------------------------
    template<class T>
    T FilesCache<T>::getOrLoadFile(const char* filePath)
    {
#ifdef GLM_ENABLE_TRACY_MEMORY_PROFILER //in header files, tracy needs to be protected beacause it is not fully exported in the golaem devkit
        ZoneScopedNC("getOrLoadFile", GLM_COLOR_CACHE);
#endif


        GLM_ASSERT(_loadFileCallback != NULL);
        GLM_ASSERT(_unloadFileCallback != NULL);

        //find files for which computing last modification time
        Array<GlmString> allFiles;
        
        struct stat s;
        if (stat(filePath, &s) == 0)
        {
            if (s.st_mode & S_IFDIR)
            {
                // Process a directory.

#if _MSC_VER
                struct _finddata_t fileinfo;
                long fret;
                intptr_t fndhand;

                GlmString filePathWithWildCard = filePath;
                filePathWithWildCard += "/*.*";
                filePathWithWildCard = glm::FileDir::correctPath(filePathWithWildCard);
                fndhand = _findfirst(filePathWithWildCard.c_str(), &fileinfo);
                if (fndhand != -1)
                {
                    do
                    {
                        if((strcmp(fileinfo.name, ".")!=0) && (strcmp(fileinfo.name, "..") != 0))
                            allFiles.push_back(GlmString(filePath) + "/" + fileinfo.name);
                        fret = _findnext(fndhand, &fileinfo);
                    } while (fret != -1);
                }
                _findclose(fndhand);
#else
                struct dirent* lecture;
                DIR* rep = opendir(glm::FileDir::correctPath(filePath).c_str());

                while ((rep != NULL) && (lecture = readdir(rep)))
                {
                    allFiles.push_back(lecture->d_name);
                }

                closedir(rep);
#endif

            }
        }
        else
            allFiles.push_back(filePath);

        //keep only the newest modification time
        uint64_t newestModificationTime(0);
        for (size_t iFile = 0; iFile < allFiles.size(); ++iFile)
        {
            uint64_t newModificationTime(0);
            if (FileDir::getLastModificationTime(allFiles[iFile].c_str(), newModificationTime))
                newestModificationTime = newestModificationTime > newModificationTime ? newestModificationTime : newModificationTime;
        }


        //now check if the file was already loaded
        glm::ScopedLock<glm::SpinLock> lock(_filesHolderLock);

        typename GlmMap<GlmString, std::pair<T, uint64_t> >::iterator foundScene = _filesHolder.find(filePath);
        if (foundScene != _filesHolder.end())
        {
            //reload if file was modified since it was loaded
            if (newestModificationTime != foundScene.getValue().second)
            {
                T reloadedFileHolder = (*_loadFileCallback)(filePath);
                if (reloadedFileHolder)
                {
                    (*_unloadFileCallback)(foundScene.getValue().first, filePath);
                    foundScene.getValue().first = reloadedFileHolder;
                    foundScene.getValue().second = newestModificationTime;
                }
            }

            return foundScene.getValue().first;
        }

        //load and set modification time if it was never loaded
        T newFileHolder = (*_loadFileCallback)(filePath);
        if (newFileHolder)
            _filesHolder[filePath] = std::make_pair(newFileHolder, newestModificationTime);

        return newFileHolder;
    }

    //------------------------------------------------------------
    template<class T>
    void FilesCache<T>::clear()
    {
        if (!_filesHolder.empty())
        {
            GLM_ASSERT(_unloadFileCallback != NULL);
            for (typename GlmMap<GlmString, std::pair<T, uint64_t> >::iterator it = _filesHolder.begin(); it != _filesHolder.end(); ++it)
            {
                (*_unloadFileCallback)(it.getValue().first, it.getKey().c_str());
                it.getValue().first = NULL;
            }
            _filesHolder.clear();
        }

    }
}