/*
 * Copyright (c) 2026 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ecmascript/tests/test_helper.h"

#include "ecmascript/daemon/daemon_thread.h"
#include "ecmascript/js_array.h"
#include "ecmascript/js_tagged_value.h"
#include "ecmascript/mem/heap-inl.h"
#include "ecmascript/mem/gc_stats.h"
#include "ecmascript/mem/shared_heap/global_gc.h"
#include "ecmascript/mem/shared_heap/global_gc_marker.h"
#include "ecmascript/mem/work_manager-inl.h"

using namespace panda::ecmascript;

namespace panda::test {

class GlobalGCTest : public BaseTestWithScope<false> {
public:
    void SetUp() override
    {
        JSRuntimeOptions options;
        instance = JSNApi::CreateEcmaVM(options);
        ASSERT_TRUE(instance != nullptr) << "Cannot create EcmaVM";
        thread = instance->GetJSThread();
        thread->ManagedCodeBegin();
        scope = new EcmaHandleScope(thread);
        thread->SetCrossThreadExecution(true);
    }

    void TriggerGlobalGC()
    {
        auto *sHeap = SharedHeap::GetInstance();
        sHeap->CollectGarbage<TriggerGCType::GLOBAL_GC, GCReason::OTHER>(thread);
    }
};

HWTEST_F_L0(GlobalGCTest, RunPhases)
{
    auto *sHeap = SharedHeap::GetInstance();
    ASSERT_TRUE(sHeap != nullptr);
    ASSERT_TRUE(sHeap->GetGlobalGC() != nullptr);
    TriggerGlobalGC();
}

HWTEST_F_L0(GlobalGCTest, GetFreedSize)
{
    auto *globalGC = SharedHeap::GetInstance()->GetGlobalGC();
    EXPECT_EQ(globalGC->GetFreedSize(), 0UL);
    TriggerGlobalGC();
    EXPECT_GE(globalGC->GetFreedSize(), 0UL);
}

HWTEST_F_L0(GlobalGCTest, TriggerViaCollectGarbage)
{
    auto *sHeap = SharedHeap::GetInstance();
    ASSERT_TRUE(sHeap != nullptr);
    auto *globalGC = sHeap->GetGlobalGC();
    ASSERT_TRUE(globalGC != nullptr);
    TriggerGlobalGC();
    EXPECT_GE(globalGC->GetFreedSize(), 0UL);
}

HWTEST_F_L0(GlobalGCTest, WorkManagerLifecycle)
{
    auto *workManager = SharedHeap::GetInstance()->GetGlobalGCWorkManager();
    ASSERT_TRUE(workManager != nullptr);
    EXPECT_FALSE(workManager->HasInitialized());
    TriggerGlobalGC();
    EXPECT_FALSE(workManager->HasInitialized());
}

HWTEST_F_L0(GlobalGCTest, GCStatsType)
{
    auto *gcStats = SharedHeap::GetInstance()->GetEcmaGCStats();
    ASSERT_TRUE(gcStats != nullptr);
    EXPECT_EQ(gcStats->GetGCType(TriggerGCType::GLOBAL_GC), GCType::GLOBAL_GC);
}

HWTEST_F_L0(GlobalGCTest, ResetWorkManager)
{
    auto *sHeap = SharedHeap::GetInstance();
    ASSERT_TRUE(sHeap != nullptr);
    auto *globalGC = sHeap->GetGlobalGC();
    ASSERT_TRUE(globalGC != nullptr);
    auto *workManager = sHeap->GetGlobalGCWorkManager();
    ASSERT_TRUE(workManager != nullptr);
    globalGC->ResetWorkManager(workManager);
}

HWTEST_F_L0(GlobalGCTest, CheckCanDistributeTask)
{
    EXPECT_TRUE(SharedHeap::GetInstance()->CheckCanDistributeGlobalGCTask());
}

HWTEST_F_L0(GlobalGCTest, Getters)
{
    auto *sHeap = SharedHeap::GetInstance();
    EXPECT_TRUE(sHeap->GetGlobalGC() != nullptr);
    EXPECT_TRUE(sHeap->GetGlobalGCMarker() != nullptr);
    EXPECT_TRUE(sHeap->GetGlobalGCWorkManager() != nullptr);
    EXPECT_TRUE(sHeap->GetSharedFullGC() != nullptr);
}

HWTEST_F_L0(GlobalGCTest, CleanDeadRSet)
{
    auto *sHeap = SharedHeap::GetInstance();
    ASSERT_TRUE(sHeap != nullptr);
    ObjectFactory *factory = instance->GetFactory();
    size_t sizeBefore = sHeap->GetHeapObjectSize();
    {
        EcmaHandleScope outerScope(thread);
        for (int i = 0; i < 100; i++) {
            JSHandle<TaggedArray> localArray = factory->NewTaggedArray(8, JSTaggedValue::Undefined());
            JSHandle<TaggedArray> sharedArray = factory->NewSOldSpaceTaggedArray(8, JSTaggedValue::Undefined());
            localArray->Set(thread, 0, sharedArray.GetTaggedValue());
        }
    }
    TriggerGlobalGC();
    size_t sizeAfter = sHeap->GetHeapObjectSize();
    EXPECT_TRUE(sizeAfter <= sizeBefore || sHeap->GetGlobalGC()->GetFreedSize() >= 0UL);
}

HWTEST_F_L0(GlobalGCTest, ParallelMarking)
{
    auto *sHeap = SharedHeap::GetInstance();
    ASSERT_TRUE(sHeap != nullptr);
    ObjectFactory *factory = instance->GetFactory();
    {
        EcmaHandleScope outerScope(thread);
        for (int i = 0; i < 2000; i++) {
            JSHandle<TaggedArray> localArray = factory->NewTaggedArray(16, JSTaggedValue::Undefined());
            JSHandle<TaggedArray> sharedArray = factory->NewSOldSpaceTaggedArray(4, JSTaggedValue::Undefined());
            localArray->Set(thread, 0, sharedArray.GetTaggedValue());
        }
    }
    TriggerGlobalGC();
    EXPECT_GE(sHeap->GetGlobalGC()->GetFreedSize(), 0UL);
}

HWTEST_F_L0(GlobalGCTest, MultipleCycles)
{
    auto *sHeap = SharedHeap::GetInstance();
    ASSERT_TRUE(sHeap != nullptr);
    ObjectFactory *factory = instance->GetFactory();
    size_t totalFreed = 0;
    {
        EcmaHandleScope scope(thread);
        factory->NewSOldSpaceTaggedArray(64, JSTaggedValue::Undefined());
    }
    TriggerGlobalGC();
    totalFreed += sHeap->GetGlobalGC()->GetFreedSize();
    {
        EcmaHandleScope scope(thread);
        for (int i = 0; i < 50; i++) {
            factory->NewSOldSpaceTaggedArray(32, JSTaggedValue::Undefined());
        }
    }
    TriggerGlobalGC();
    totalFreed += sHeap->GetGlobalGC()->GetFreedSize();
    {
        EcmaHandleScope scope(thread);
        JSHandle<TaggedArray> localArray = factory->NewTaggedArray(8, JSTaggedValue::Undefined());
        JSHandle<TaggedArray> sharedArray = factory->NewSOldSpaceTaggedArray(8, JSTaggedValue::Undefined());
        localArray->Set(thread, 0, sharedArray.GetTaggedValue());
    }
    TriggerGlobalGC();
    totalFreed += sHeap->GetGlobalGC()->GetFreedSize();
    EXPECT_GE(totalFreed, 0UL);
}

HWTEST_F_L0(GlobalGCTest, AfterSharedGC)
{
    auto *sHeap = SharedHeap::GetInstance();
    ASSERT_TRUE(sHeap != nullptr);
    ObjectFactory *factory = instance->GetFactory();
    {
        EcmaHandleScope scope(thread);
        factory->NewSOldSpaceTaggedArray(64, JSTaggedValue::Undefined());
    }
    sHeap->CollectGarbage<TriggerGCType::SHARED_GC, GCReason::OTHER>(thread);
    TriggerGlobalGC();
    EXPECT_GE(sHeap->GetGlobalGC()->GetFreedSize(), 0UL);
}

HWTEST_F_L0(GlobalGCTest, WithJSObjects)
{
    auto *sHeap = SharedHeap::GetInstance();
    ASSERT_TRUE(sHeap != nullptr);
    ObjectFactory *factory = instance->GetFactory();
    {
        EcmaHandleScope outerScope(thread);
        JSHandle<JSHClass> hclass(thread, thread->GlobalConstants()->GetObjectClass().GetTaggedObject());
        for (int i = 0; i < 100; i++) {
            JSHandle<JSObject> obj = factory->NewJSObject(hclass);
            JSHandle<TaggedArray> sharedArray = factory->NewSOldSpaceTaggedArray(4, JSTaggedValue::Undefined());
            obj->SetElements(thread, sharedArray);
        }
    }
    TriggerGlobalGC();
    EXPECT_GE(sHeap->GetGlobalGC()->GetFreedSize(), 0UL);
}

HWTEST_F_L0(GlobalGCTest, WithWeakRefs)
{
    auto *sHeap = SharedHeap::GetInstance();
    ASSERT_TRUE(sHeap != nullptr);
    ObjectFactory *factory = instance->GetFactory();
    {
        EcmaHandleScope outerScope(thread);
        JSHandle<TaggedArray> localArray = factory->NewTaggedArray(4, JSTaggedValue::Undefined());
        JSHandle<TaggedArray> localArray2 = factory->NewTaggedArray(4, JSTaggedValue::Undefined());
        JSHandle<TaggedArray> sharedArray = factory->NewSOldSpaceTaggedArray(4, JSTaggedValue::Undefined());
        localArray->Set(thread, 0, sharedArray.GetTaggedValue());
        localArray->Set(thread, 1, localArray2.GetTaggedValue().CreateAndGetWeakRef());
    }
    TriggerGlobalGC();
    EXPECT_GE(sHeap->GetGlobalGC()->GetFreedSize(), 0UL);
    EXPECT_FALSE(thread->HasPendingException());
}

HWTEST_F_L0(GlobalGCTest, SharedHeapAllocationPressure)
{
    auto *sHeap = SharedHeap::GetInstance();
    ASSERT_TRUE(sHeap != nullptr);
    ObjectFactory *factory = instance->GetFactory();
    size_t sizeBefore = sHeap->GetHeapObjectSize();
    {
        EcmaHandleScope outerScope(thread);
        for (int i = 0; i < 500; i++) {
            factory->NewSOldSpaceTaggedArray(128, JSTaggedValue::Undefined());
        }
    }
    sHeap->CollectGarbage<TriggerGCType::SHARED_GC, GCReason::OTHER>(thread);
    TriggerGlobalGC();
    size_t sizeAfter = sHeap->GetHeapObjectSize();
    EXPECT_GE(sHeap->GetGlobalGC()->GetFreedSize(), 0UL);
    EXPECT_TRUE(sizeAfter <= sizeBefore || sizeAfter > 0);
}

class GlobalGCSequentialTest : public testing::Test {
public:
    EcmaVM *instance {nullptr};
    EcmaHandleScope *scope {nullptr};
    JSThread *thread {nullptr};

    void SetUp() override
    {
        JSRuntimeOptions options;
        instance = JSNApi::CreateEcmaVM(options);
        ASSERT_TRUE(instance != nullptr) << "Cannot create EcmaVM";
        thread = instance->GetJSThread();
        thread->ManagedCodeBegin();
        scope = new EcmaHandleScope(thread);
        thread->SetCrossThreadExecution(true);
    }

    void TearDown() override
    {
        delete scope;
        scope = nullptr;
        instance->GetJSThread()->ManagedCodeEnd();
    }

    void TriggerGlobalGC()
    {
        auto *sHeap = SharedHeap::GetInstance();
        sHeap->CollectGarbage<TriggerGCType::GLOBAL_GC, GCReason::OTHER>(thread);
    }
};

HWTEST_F_L0(GlobalGCSequentialTest, SequentialCleanAll)
{
    auto *sHeap = SharedHeap::GetInstance();
    ASSERT_TRUE(sHeap != nullptr);
    sHeap->DisableParallelGC(thread);
    ObjectFactory *factory = instance->GetFactory();
    size_t totalFreed = 0;

    {
        EcmaHandleScope testScope(thread);
        for (int i = 0; i < 50; i++) {
            JSHandle<TaggedArray> localArray = factory->NewTaggedArray(8, JSTaggedValue::Undefined());
            JSHandle<TaggedArray> sharedArray = factory->NewSOldSpaceTaggedArray(4, JSTaggedValue::Undefined());
            localArray->Set(thread, 0, sharedArray.GetTaggedValue());
        }
    }
    TriggerGlobalGC();
    totalFreed += sHeap->GetGlobalGC()->GetFreedSize();

    {
        EcmaHandleScope testScope(thread);
        JSHandle<JSHClass> hclass(thread, thread->GlobalConstants()->GetObjectClass().GetTaggedObject());
        for (int i = 0; i < 50; i++) {
            JSHandle<JSObject> obj = factory->NewJSObject(hclass);
            JSHandle<TaggedArray> sharedArray = factory->NewSOldSpaceTaggedArray(4, JSTaggedValue::Undefined());
            obj->SetElements(thread, sharedArray);
        }
    }
    TriggerGlobalGC();
    totalFreed += sHeap->GetGlobalGC()->GetFreedSize();

    {
        EcmaHandleScope testScope(thread);
        factory->NewSOldSpaceTaggedArray(32, JSTaggedValue::Undefined());
    }
    TriggerGlobalGC();
    totalFreed += sHeap->GetGlobalGC()->GetFreedSize();

    {
        EcmaHandleScope testScope(thread);
        JSHandle<TaggedArray> localArray = factory->NewTaggedArray(8, JSTaggedValue::Undefined());
        JSHandle<TaggedArray> sharedArray = factory->NewSOldSpaceTaggedArray(8, JSTaggedValue::Undefined());
        localArray->Set(thread, 0, sharedArray.GetTaggedValue());
    }
    TriggerGlobalGC();
    totalFreed += sHeap->GetGlobalGC()->GetFreedSize();

    EXPECT_GE(totalFreed, 0UL);
    EXPECT_FALSE(thread->HasPendingException());
}

}  // namespace panda::test