#include <Core/HAL/Filesystem/Filesystem.h>
#include <Core/HotReload/HotReload.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/Memory/MemoryArena.h>
#include <Core/Thread/Thread.h>

#define MAX_TRIES 100

namespace Juliet
{
    void InitHotReloadCode(HotReloadCode& code, String dllName, String transientDllName, String lockFilename)
    {
        code.Arena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Hot Reload"));

        // Get the app base path and build the dll path from there.
        String basePath       = GetBasePath();
        size_t basePathLength = StringLength(basePath);

        // Assign Transient dll path
        code.TransientDLLName = transientDllName;

        // First allocate all the full path.
        // TODO: Add path composition into filesystem + string format + string builder
        const size_t dllFullPathLength =
            basePathLength + StringLength(dllName) + 1; // Need +1 because snprintf needs 0 terminated strings

        code.DLLFullPath.Data = static_cast<char*>(ArenaPush(code.Arena, dllFullPathLength, alignof(char), true JULIET_DEBUG_ONLY(, "DLL Path")));
        int writtenSize = snprintf(CStr(code.DLLFullPath), dllFullPathLength, "%s%s", CStr(basePath), CStr(dllName));
        if (writtenSize < static_cast<int>(dllFullPathLength) - 1)
        {
            // Arena memory persists, no free needed
            Log(LogLevel::Error, LogCategory::Core, "Cannot create DLL Full Path");
            return;
        }
        code.DLLFullPath.Size = static_cast<size_t>(writtenSize);

        // Lock filename path
        const size_t lockPathLength =
            basePathLength + StringLength(lockFilename) + 1; // Need +1 because snprintf needs 0 terminated strings
        code.LockFullPath.Data = static_cast<char*>(ArenaPush(code.Arena, lockPathLength, alignof(char), true JULIET_DEBUG_ONLY(, "Lock File Path")));
        writtenSize = snprintf(CStr(code.LockFullPath), lockPathLength, "%s%s", CStr(basePath), CStr(lockFilename));
        if (writtenSize < static_cast<int>(lockPathLength) - 1)
        {
            code.LockFullPath.Size = 0;
            // Arena memory persists, no free needed
            Log(LogLevel::Error, LogCategory::Core, "Cannot create lock file full path");
            return;
        }
        code.LockFullPath.Size = static_cast<size_t>(writtenSize);

        LoadCode(code);
    }

    void ShutdownHotReloadCode(HotReloadCode& code)
    {
        UnloadCode(code);

        code.DLLFullPath.Size = 0;
        // Arena memory persists until engine shutdown
        code.LockFullPath.Size = 0;
        // Arena memory persists until engine shutdown

        ArenaRelease(code.Arena);
    }

    void ReloadCode(HotReloadCode& code)
    {
        UnloadCode(code);
        for (uint32 tryItr = 0; !code.IsValid && tryItr < MAX_TRIES; ++tryItr)
        {
            LoadCode(code);
            wait_ms(100);
        }
    }
} // namespace Juliet
