/*
 * Copyright (c) 2021 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/global_env.h"

#include "ecmascript/builtins/builtins_number.h"
#include "ecmascript/builtins/builtins_regexp.h"
#include "ecmascript/builtins/builtins_string.h"
#include "ecmascript/dependent_infos.h"
#include "ecmascript/ecma_string_table.h"
#include "ecmascript/global_dictionary.h"
#include "ecmascript/js_object-inl.h"
#include "ecmascript/module/js_module_manager.h"
#include "ecmascript/template_map.h"

namespace panda::ecmascript {
void GlobalEnv::Init(JSThread *thread)
{
    SetGlobalEnv(thread, JSTaggedValue(this));
    SetGlobalRecord(thread, GlobalDictionary::Create(thread));
    auto* vm = thread->GetEcmaVM();
    JSTaggedValue emptyStr = thread->GlobalConstants()->GetEmptyString();
    EcmaStringTable *stringTable = vm->GetEcmaStringTable();
    stringTable->GetOrInternFlattenString(vm, EcmaString::Cast(emptyStr.GetTaggedObject()));
    SetTemplateMap(thread, TemplateMap::Create(thread));
    SetObjectLiteralHClassCache(thread, JSTaggedValue::Hole());
    SetArrayJoinStack(thread, vm->GetFactory()->NewTaggedArray(ArrayJoinStack::MIN_JOIN_STACK_SIZE));
    SetNumberToStringResultCache(thread, builtins::NumberToStringResultCache::CreateCacheTable(thread));
    SetStringSplitResultCache(thread, builtins::StringSplitResultCache::CreateCacheTable(thread));
    SetStringToListResultCache(thread, builtins::StringToListResultCache::CreateCacheTable(thread));
    SetRegExpCache(thread, builtins::RegExpExecResultCache::CreateCacheTable(thread));
    SetRegExpGlobalResult(thread, builtins::RegExpGlobalResult::CreateGlobalResultTable(thread));
#define INIT_JSAPI_CONTAINER(Type, Name, INDEX) Set##Name(thread, JSTaggedValue::Undefined());
    GLOBAL_ENV_CONTAINER_ITERATORS(INIT_JSAPI_CONTAINER)
#undef INIT_JSAPI_CONTAINER
    SetModuleManagerNativePointer(thread, ModuleManager::CreateModuleManagerNativePointer(thread));
    ClearBitField();
    SetJSThread(thread);

    auto array = vm->GetFactory()->NewTaggedArray(LastBitFieldBits::START_BIT, JSTaggedValue::Undefined());
    SetDetectorDependentInfos(thread, array);
}

void GlobalEnv::Iterate(RootVisitor &v)
{
    for (uint16_t i = 0; i < FINAL_INDEX; i++) {
        size_t offset = HEADER_SIZE + i * sizeof(JSTaggedType);
        uintptr_t slotAddress = reinterpret_cast<uintptr_t>(this) + offset;
        ObjectSlot slot(slotAddress);
        v.VisitRoot(Root::ROOT_VM, slot);
    }
}

JSHandle<JSTaggedValue> GlobalEnv::GetSymbol(JSThread *thread, const JSHandle<JSTaggedValue> &string)
{
    JSHandle<JSTaggedValue> symbolFunction(GetSymbolFunction());
    return JSObject::GetProperty(thread, symbolFunction, string).GetValue();
}

JSHandle<JSTaggedValue> GlobalEnv::GetStringPrototypeFunctionByName(JSThread *thread, const char *name)
{
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> stringFuncPrototype(
        thread, JSObject::GetPrototype(thread, JSHandle<JSObject>(GetStringFunction())));
    JSHandle<JSTaggedValue> nameKey(factory->NewFromUtf8(name));
    return JSObject::GetProperty(thread, stringFuncPrototype, nameKey).GetValue();
}

JSHandle<JSTaggedValue> GlobalEnv::GetStringFunctionByName(JSThread *thread, const char *name)
{
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> stringFuncObj = GetStringFunction();
    JSHandle<JSTaggedValue> nameKey(factory->NewFromUtf8(name));
    return JSObject::GetProperty(thread, stringFuncObj, nameKey).GetValue();
}

void GlobalEnv::NotifyDetectorDeoptimize(JSThread *thread, uint32_t detectorID)
{
    JSHandle<JSTaggedValue> dependentInfos = GetDependentInfos(thread, detectorID);
    if (!dependentInfos->IsHeapObject()) {
        return;
    }
    JSThread *initThread = GetJSThread();
    DependentInfos::TriggerLazyDeoptimization(
        JSHandle<DependentInfos>::Cast(dependentInfos), initThread,
        DependentInfos::DependentState::DETECTOR_CHECK);
    SetDependentInfos(detectorID, initThread->GlobalConstants()->GetHandledUndefined());
}

void GlobalEnv::NotifyArrayPrototypeChangedGuardians(JSThread *thread, JSHandle<JSObject> receiver)
{
    if (!GetArrayPrototypeChangedGuardians()) {
        return;
    }
    if (!receiver->GetJSHClass()->IsPrototype() && !receiver->IsJSArray()) {
        return;
    }
    if (receiver.GetTaggedValue() == GetObjectFunctionPrototype().GetTaggedValue() ||
        receiver.GetTaggedValue() == GetArrayPrototype().GetTaggedValue()) {
        NotifyDetectorDeoptimize(thread, ArrayPrototypeChangedGuardiansBits::START_BIT);
        SetArrayPrototypeChangedGuardians(false);
        return;
    }
}

void GlobalEnv::ClearCache(JSThread *thread) const
{
    builtins::StringSplitResultCache::ClearCache(thread, GetStringSplitResultCache());
#if ENABLE_MEMORY_OPTIMIZATION
    builtins::RegExpExecResultCache::ClearCache(thread, GetRegExpCache());
#endif
}

GlobalEnvField GetBuildinTypedArrayHClassOnHeapIndex(JSType jSType)
{
    switch (jSType) {
        case JSType::JS_INT8_ARRAY:
            return GlobalEnvField::INT8_ARRAY_ROOT_HCLASS_ON_HEAP_INDEX;
        case JSType::JS_UINT8_ARRAY:
            return GlobalEnvField::UINT8_ARRAY_ROOT_HCLASS_ON_HEAP_INDEX;
        case JSType::JS_UINT8_CLAMPED_ARRAY:
            return GlobalEnvField::UINT8_CLAMPED_ARRAY_ROOT_HCLASS_ON_HEAP_INDEX;
        case JSType::JS_INT16_ARRAY:
            return GlobalEnvField::INT16_ARRAY_ROOT_HCLASS_ON_HEAP_INDEX;
        case JSType::JS_UINT16_ARRAY:
            return GlobalEnvField::UINT16_ARRAY_ROOT_HCLASS_ON_HEAP_INDEX;
        case JSType::JS_INT32_ARRAY:
            return GlobalEnvField::INT32_ARRAY_ROOT_HCLASS_ON_HEAP_INDEX;
        case JSType::JS_UINT32_ARRAY:
            return GlobalEnvField::UINT32_ARRAY_ROOT_HCLASS_ON_HEAP_INDEX;
        case JSType::JS_FLOAT32_ARRAY:
            return GlobalEnvField::FLOAT32_ARRAY_ROOT_HCLASS_ON_HEAP_INDEX;
        case JSType::JS_FLOAT64_ARRAY:
            return GlobalEnvField::FLOAT64_ARRAY_ROOT_HCLASS_ON_HEAP_INDEX;
        case JSType::JS_BIGINT64_ARRAY:
            return GlobalEnvField::BIGINT64_ARRAY_ROOT_HCLASS_ON_HEAP_INDEX;
        case JSType::JS_BIGUINT64_ARRAY:
            return GlobalEnvField::BIGUINT64_ARRAY_ROOT_HCLASS_ON_HEAP_INDEX;
        default:
            return GlobalEnvField::INVALID;
    }
    return GlobalEnvField::INVALID;
}

GlobalEnvField GetBuildinTypedArrayHClassIndex(JSType jSType)
{
    switch (jSType) {
        case JSType::JS_INT8_ARRAY:
            return GlobalEnvField::INT8_ARRAY_ROOT_HCLASS_INDEX;
        case JSType::JS_UINT8_ARRAY:
            return GlobalEnvField::UINT8_ARRAY_ROOT_HCLASS_INDEX;
        case JSType::JS_UINT8_CLAMPED_ARRAY:
            return GlobalEnvField::UINT8_CLAMPED_ARRAY_ROOT_HCLASS_INDEX;
        case JSType::JS_INT16_ARRAY:
            return GlobalEnvField::INT16_ARRAY_ROOT_HCLASS_INDEX;
        case JSType::JS_UINT16_ARRAY:
            return GlobalEnvField::UINT16_ARRAY_ROOT_HCLASS_INDEX;
        case JSType::JS_INT32_ARRAY:
            return GlobalEnvField::INT32_ARRAY_ROOT_HCLASS_INDEX;
        case JSType::JS_UINT32_ARRAY:
            return GlobalEnvField::UINT32_ARRAY_ROOT_HCLASS_INDEX;
        case JSType::JS_FLOAT32_ARRAY:
            return GlobalEnvField::FLOAT32_ARRAY_ROOT_HCLASS_INDEX;
        case JSType::JS_FLOAT64_ARRAY:
            return GlobalEnvField::FLOAT64_ARRAY_ROOT_HCLASS_INDEX;
        case JSType::JS_BIGINT64_ARRAY:
            return GlobalEnvField::BIGINT64_ARRAY_ROOT_HCLASS_INDEX;
        case JSType::JS_BIGUINT64_ARRAY:
            return GlobalEnvField::BIGUINT64_ARRAY_ROOT_HCLASS_INDEX;
        default:
            return GlobalEnvField::INVALID;
    }
    return GlobalEnvField::INVALID;
}

GlobalEnvField GlobalEnv::GetBuildinTypedArrayHClassIndex(JSType jSType, OnHeapMode mode)
{
    if (OnHeap::IsOnHeap(mode)) {
        return GetBuildinTypedArrayHClassOnHeapIndex(jSType);
    } else if (OnHeap::IsNotOnHeap(mode)) {
        return ::panda::ecmascript::GetBuildinTypedArrayHClassIndex(jSType);
    } else {
        return GlobalEnvField::INVALID;
    }
}
}  // namespace panda::ecmascript