//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

#include "JsrtPch.h"

#if !defined(_WIN32) && !defined(__ANDROID__)
#include <pthread.h>
#endif

#include "jsrtHelper.h"
#include "Base/ThreadContextTlsEntry.h"

#include "Base/VTuneChakraProfile.h"

#ifdef DYNAMIC_PROFILE_STORAGE
#include "Language/DynamicProfileStorage.h"
#endif

#if !defined(_WIN32) || defined(CHAKRA_STATIC_LIBRARY)
#include "Core/ConfigParser.h"
#include "Base/ThreadBoundThreadContextManager.h"

#ifdef CHAKRA_STATIC_LIBRARY
bool ConfigParserAPI::FillConsoleTitle(__ecount(cchBufferSize) LPWSTR buffer, size_t cchBufferSize, __in LPWSTR moduleName)
{
    return false;
}

void ConfigParserAPI::DisplayInitialOutput(__in LPWSTR moduleName)
{
}

LPCWSTR JsUtil::ExternalApi::GetFeatureKeyName()
{
    return _u("");
}
#endif // CHAKRA_STATIC_LIBRARY
#endif

JsrtCallbackState::JsrtCallbackState(ThreadContext* currentThreadContext)
{
    if (currentThreadContext == nullptr)
    {
        originalThreadContext = ThreadContext::GetContextForCurrentThread();
    }
    else
    {
        originalThreadContext = currentThreadContext;
    }
    originalJsrtContext = JsrtContext::GetCurrent();
    Assert(originalJsrtContext == nullptr || originalThreadContext == originalJsrtContext->GetScriptContext()->GetThreadContext());
}

JsrtCallbackState::~JsrtCallbackState()
{
    if (originalJsrtContext != nullptr)
    {
        if (originalJsrtContext != JsrtContext::GetCurrent())
        {
            // This shouldn't fail as the context was previously set on the current thread.
            bool isSet = JsrtContext::TrySetCurrent(originalJsrtContext);
            if (!isSet)
            {
                Js::Throw::FatalInternalError();
            }
        }
    }
    else
    {
        bool isSet = ThreadContextTLSEntry::TrySetThreadContext(originalThreadContext);
        if (!isSet)
        {
            Js::Throw::FatalInternalError();
        }
    }
}

void JsrtCallbackState::ObjectBeforeCallectCallbackWrapper(JsObjectBeforeCollectCallback callback, void* object, void* callbackState, void* threadContext)
{
    JsrtCallbackState scope(reinterpret_cast<ThreadContext*>(threadContext));
    callback(object, callbackState);
}

#if !defined(_WIN32) || defined(CHAKRA_STATIC_LIBRARY)
    void ChakraBinaryAutoSystemInfoInit(AutoSystemInfo * autoSystemInfo)
    {
        autoSystemInfo->buildDateHash = JsUtil::CharacterBuffer<char>::StaticGetHashCode(__DATE__, _countof(__DATE__));
        autoSystemInfo->buildTimeHash = JsUtil::CharacterBuffer<char>::StaticGetHashCode(__TIME__, _countof(__TIME__));
    }

#ifndef _WIN32
    static pthread_key_t s_threadLocalDummy;
#endif
    static THREAD_LOCAL bool s_threadWasEntered = false;

    _NOINLINE void DISPOSE_CHAKRA_CORE_THREAD(void *_)
    {
        free(_);
        ThreadBoundThreadContextManager::DestroyContextAndEntryForCurrentThread();
    }

    _NOINLINE bool InitializeProcess()
    {
#if !defined(_WIN32)
        pthread_key_create(&s_threadLocalDummy, DISPOSE_CHAKRA_CORE_THREAD);
#endif

    // setup the cleanup
    // we do not track the main thread. When it exits do the cleanup below
#ifdef CHAKRA_STATIC_LIBRARY
    atexit([]() {
        ThreadBoundThreadContextManager::DestroyContextAndEntryForCurrentThread();

#ifdef DYNAMIC_PROFILE_STORAGE
        DynamicProfileStorage::Uninitialize();
#endif
        JsrtRuntime::Uninitialize();

        // thread-bound entrypoint should be able to get cleanup correctly, however tlsentry
        // for current thread might be left behind if this thread was initialized.
        ThreadContextTLSEntry::CleanupThread();
        ThreadContextTLSEntry::CleanupProcess();
    });
#endif

#ifndef _WIN32
        PAL_InitializeChakraCore();
#endif

        HMODULE mod = GetModuleHandleW(NULL);
        AutoSystemInfo::SaveModuleFileName(mod);

    #if defined(_M_IX86) && !defined(__clang__)
        // Enable SSE2 math functions in CRT if SSE2 is available
    #pragma prefast(suppress:6031, "We don't require SSE2, but will use it if available")
        _set_SSE2_enable(TRUE);
    #endif

        {
            CmdLineArgsParser parser;
            ConfigParser::ParseOnModuleLoad(parser, mod);
        }

    #if defined(ENABLE_JS_ETW) && !defined(ENABLE_JS_LTTNG)
        EtwTrace::Register();
    #endif
    #ifdef VTUNE_PROFILING
        VTuneChakraProfile::Register();
    #endif
#if PERFMAP_TRACE_ENABLED
        PlatformAgnostic::PerfTrace::Register();
#endif

        ValueType::Initialize();
        ThreadContext::GlobalInitialize();

    #ifdef ENABLE_BASIC_TELEMETRY
        g_TraceLoggingClient = NoCheckHeapNewStruct(TraceLoggingClient);
    #endif

    #ifdef DYNAMIC_PROFILE_STORAGE
        DynamicProfileStorage::Initialize();
    #endif

        return true;
    }

    _NOINLINE void VALIDATE_ENTER_CURRENT_THREAD()
    {
        // We do also initialize the process part here
        // This is thread safe by the standard
        // Let's hope compiler doesn't fail
        static bool _has_init = InitializeProcess();

        if (!_has_init) // do not assert this.
        {
            abort();
        }

        if (s_threadWasEntered) return;
        s_threadWasEntered = true;

    #ifdef HEAP_TRACK_ALLOC
        HeapAllocator::InitializeThread();
    #endif

#ifndef _WIN32
        // put something into key to make sure destructor is going to be called
        pthread_setspecific(s_threadLocalDummy, malloc(1));
#endif
    }
#endif
