// MobileGL - MobileGL/Init.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
//   https://www.gnu.org/licenses/gpl-3.0.txt
//   https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header

#include "Init.h"
#include "Config.h"
#include <MG_Backend/BackendObjects.h>
#include <MG_Backend/DirectVulkan/DirectVulkan.h>
#include <MG_State/GLState/Core.h>
#include <MG_State/EGLState/Core.h>
#include <MG_Impl/GLImpl/Texture/ProxyTexture.h>
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
#include <MG_Impl/GLImpl/Sync/GL_Sync.h>
#include <MG_Impl/GLImpl/Query/GL_Query.h>
#include <MG_Util/Async/ShaderCompilePool.h>
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
#include <MG_State/GLState/ProgramState/ProgramTranslationCache.h>
#include <MG_Util/ShaderTranspiler/TranslationCache.h>

#include <atomic>
#include <mutex>

namespace MobileGL {
    namespace {
        std::atomic<Bool> g_isInitialized = false;
        thread_local Bool tl_initializing = false;

        std::mutex& InitMutex() {
            static std::mutex mutex;
            return mutex;
        }

        void DestroyImpl(Bool logLifecycle) {
            if (!g_isInitialized) {
                return;
            }

            if (logLifecycle) {
                MGLOG_I("MobileGL closing...");
            }
            // First, before anything else is torn down. In-flight compile/link jobs own
            // their own inputs and are safe against everything below EXCEPT glslang's
            // process globals and the TShader/TProgram objects hanging off pGLContext,
            // both of which this function is about to destroy. This is the one
            // cancellation path in the whole design that waits.
            MG_Util::Async::ShaderCompilePool::Get().StopAndDrain();
            // GL syncs die with their contexts, and every context is gone by the
            // time full teardown runs: drain the live-sync registry while the
            // backend function table can still release the backend handles (and
            // before a re-initialized library could pair them with the wrong
            // backend's DeleteSync).
            MG_Impl::GLImpl::DestroyAllSyncObjects();
            // Queries die with their contexts for the same reason, and their registry
            // is the same shape of process-global map: drain it here too, while the
            // function table can still pair each backend handle with the backend that
            // minted it.
            MG_Impl::GLImpl::DestroyAllQueryObjects();
            MG_Backend::pActiveBackendObject.reset();
            MG_State::pGLContext.reset();
            MG_State::pEGLContext.reset();
            MG_Impl::GLImpl::TextureImpl::pProxyTextureManager.reset();
            MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo.reset();
            // Must run AFTER pGLContext.reset(). FinalizeProcess -> ShFinalize deletes
            // glslang's process-wide pool allocator and every cached built-in symbol table,
            // while the TShader/TProgram objects owned by the shader and program objects
            // still reference levels adopted from those tables. Finalizing first left live
            // glslang objects pointing at freed memory for the rest of the teardown.
            glslang::FinalizeProcess();
            // Immediately after, and never apart from it: FinalizeProcess just deleted the
            // built-in symbol tables the prewarm latch stands for, so leaving it set would
            // make the next Initialize() skip a prewarm it genuinely needs.
            MG_Util::ShaderTranspiler::ShaderCompiler::ResetPrewarmLatch();
            // The two-level translation memo. Nothing in it references a glslang object -
            // both levels hold plain bytes - so this is RSS hygiene rather than a lifetime
            // requirement, and it is safe either side of FinalizeProcess. Stats first: an
            // fordebug build gets one line per level saying how the run went.
            MG_Util::ShaderTranspiler::LogShaderTranslationCacheStats();
            MG_Util::ShaderTranspiler::ClearShaderTranslationCaches();
            MG_State::GLState::LogProgramTranslationCacheStats();
            MG_State::GLState::ClearProgramTranslationCache();
            MG_Backend::gBackendFunctionsTable = {};
            g_isInitialized = false;
            if (logLifecycle) {
                MG_Util::Debug::Close();
            }

            // TODO: add and use Destroy functions for other subsystems
        }
    }

    void Initialize() {
        if (g_isInitialized) {
            MGLOG_D("MobileGL already initialized; skipping duplicate Initialize()");
            return;
        }

        MG_Util::Debug::InitFile();
        MGLOG_I("Initializing MobileGL...");
        MG_ConfigLoader::Init();
        MGLOG_I("Config loaded");
        MG_State::Init();
        MGLOG_D("MG_State initialized");
        MG_Backend::Init();
        MGLOG_D("MG_Backend initialized");
        MG_Impl::Init();
        MGLOG_D("MG_Impl initialized");
        glslang::InitializeProcess();
        // On the GL thread, before any worker can exist. glslang builds its built-in symbol
        // tables lazily under a process-wide lock held for the whole build, so without this
        // the first concurrent compiles of a shaderpack all serialize behind the very first
        // parse and asynchronous compilation looks like it is doing nothing.
        //
        // Gated on the flag, because the problem it solves only exists when there are
        // workers: with compilation synchronous, nothing ever contends for that lock and the
        // three throwaway parses buy nothing - they just add to every eglInitialize. Read the
        // flag here rather than inside PrewarmBuiltins so ShaderCompiler keeps no dependency
        // on the async subsystem (ProgramUtilTest compiles that file without it).
        if (MG_Util::Async::AsyncShaderCompileEnabled()) {
            MG_Util::ShaderTranspiler::ShaderCompiler::PrewarmBuiltins();
        }
        MGLOG_D("glslang initialized");
        g_isInitialized = true;
        MGLOG_I("MobileGL initialized");
    }

    void EnsureInitialized() {
        if (g_isInitialized.load(std::memory_order_acquire)) {
            return;
        }
        // Re-entrant call while this thread is already inside Initialize()
        // (e.g. an init step routing back through a public entry point).
        if (tl_initializing) {
            return;
        }
        const std::lock_guard<std::mutex> lock(InitMutex());
        if (g_isInitialized.load(std::memory_order_acquire)) {
            return;
        }
        tl_initializing = true;
        Initialize();
        tl_initializing = false;
    }

    void Destroy() {
        DestroyImpl(true);
    }

    // MobileGL's lifecycle is owned entirely by the host-API layers
    // (EGL/WGL/CGL): initialization happens lazily on the first entry point
    // via EnsureInitialized(), and full teardown happens deterministically
    // when the last EGL display is terminated with nothing current (EGLImpl
    // calls Destroy()). There is intentionally no backend-initializing static
    // constructor, no static destructor, and no DllMain: the global singletons
    // use leak-at-exit storage (see GlobalObjects.cpp), so a process that exits
    // without eglTerminate simply leaks them to the OS instead of running
    // backend destructors during static teardown. macOS has a lightweight
    // dyld constructor that installs NSOpenGL dispatch hooks only; full backend
    // initialization still enters here from the first hooked CGL context.
} // namespace MobileGL
