/*
 * 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/builtins/builtins_locale.h"

#include "ecmascript/ecma_vm.h"
#include "ecmascript/global_env.h"
#include "ecmascript/js_locale.h"
#include "ecmascript/object_factory.h"

namespace panda::ecmascript::builtins {
// 10.1.3 Intl.Locale( tag [, options] )
JSTaggedValue BuiltinsLocale::LocaleConstructor(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    EcmaVM *ecmaVm = thread->GetEcmaVM();
    ObjectFactory *factory = ecmaVm->GetFactory();

    // 1. If NewTarget is undefined, throw a TypeError exception.
    JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
    if (newTarget->IsUndefined()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "newTarget is undefined", JSTaggedValue::Exception());
    }

    // 6. Let locale be ? OrdinaryCreateFromConstructor(NewTarget, %LocalePrototype%, internalSlotsList).
    JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
    JSHandle<JSLocale> locale =
        JSHandle<JSLocale>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), newTarget));
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // 7. If Type(tag) is not String or Object, throw a TypeError exception.
    JSHandle<JSTaggedValue> tag = GetCallArg(argv, 0);
    if (!tag->IsString() && !tag->IsJSObject()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "tag is not String or Object", JSTaggedValue::Exception());
    }

    // 8. If Type(tag) is Object and tag has an [[InitializedLocale]] internal slot, then
    //    a.Let tag be tag.[[Locale]].
    // 9. Else,
    //    a.Let tag be ? ToString(tag).
    JSHandle<EcmaString> localeString = factory->GetEmptyString();
    if (!tag->IsJSLocale()) {
        localeString = JSTaggedValue::ToString(thread, tag);
    } else {
        icu::Locale *icuLocale = (JSHandle<JSLocale>::Cast(tag))->GetIcuLocale();
        localeString = JSLocale::ToLanguageTag(thread, *icuLocale);
    }
    // 10. If options is undefined, then
    //    a.Let options be ! ObjectCreate(null).
    // 11. Else
    //    a.Let options be ? ToObject(options).
    JSHandle<JSTaggedValue> options = GetCallArg(argv, 1);
    JSHandle<JSObject> optionsObj;
    if (options->IsUndefined()) {
        optionsObj = factory->OrdinaryNewJSObjectCreate(JSHandle<JSTaggedValue>(thread, JSTaggedValue::Null()));
    } else {
        optionsObj = JSTaggedValue::ToObject(thread, options);
    }
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    JSHandle<JSLocale> result = JSLocale::InitializeLocale(thread, locale, localeString, optionsObj);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    return result.GetTaggedValue();
}

JSTaggedValue BuiltinsLocale::Maximize(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    // 1. Let loc be the this value.
    JSHandle<JSTaggedValue> loc = GetThis(argv);

    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
    if (!loc->IsJSLocale()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
    }
    // 3. Let maximal be the result of the Add Likely Subtags algorithm applied to loc.[[Locale]]. If an error is
    //    signaled, set maximal to loc.[[Locale]].
    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
    icu::Locale source(*(locale->GetIcuLocale()));
    UErrorCode status = U_ZERO_ERROR;
    source.addLikelySubtags(status);
    ASSERT(U_SUCCESS(status));
    ASSERT(!source.isBogus());

    // 4. Return ! Construct(%Locale%, maximal).
    EcmaVM *ecmaVm = thread->GetEcmaVM();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    ObjectFactory *factory = ecmaVm->GetFactory();

    JSHandle<JSTaggedValue> ctor = env->GetLocaleFunction();
    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(ctor), ctor);
    factory->NewJSIntlIcuData(JSHandle<JSLocale>::Cast(obj), source, JSLocale::FreeIcuLocale);
    return obj.GetTaggedValue();
}

JSTaggedValue BuiltinsLocale::Minimize(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    // 1. Let loc be the this value.
    JSHandle<JSTaggedValue> loc = GetThis(argv);

    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
    if (!loc->IsJSLocale()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
    }

    // 3. Let minimal be the result of the Remove Likely Subtags algorithm applied to loc.[[Locale]].
    //    If an error is signaled, set minimal to loc.[[Locale]].
    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
    icu::Locale source(*(locale->GetIcuLocale()));
    UErrorCode status = U_ZERO_ERROR;
    source.minimizeSubtags(status);
    ASSERT(U_SUCCESS(status));
    ASSERT(!source.isBogus());

    [[maybe_unused]] auto res = source.toLanguageTag<CString>(status);

    // 4. Return ! Construct(%Locale%, minimal).
    EcmaVM *ecmaVm = thread->GetEcmaVM();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    ObjectFactory *factory = ecmaVm->GetFactory();

    JSHandle<JSTaggedValue> ctor = env->GetLocaleFunction();
    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(ctor), ctor);
    factory->NewJSIntlIcuData(JSHandle<JSLocale>::Cast(obj), source, JSLocale::FreeIcuLocale);
    return obj.GetTaggedValue();
}

JSTaggedValue BuiltinsLocale::ToString(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    // 1. Let loc be the this value.
    JSHandle<JSTaggedValue> loc = GetThis(argv);
    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
    if (!loc->IsJSLocale()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
    }
    // 3. Return loc.[[Locale]].
    JSHandle<EcmaString> result = JSLocale::ToString(thread, JSHandle<JSLocale>::Cast(loc));
    return result.GetTaggedValue();
}

JSTaggedValue BuiltinsLocale::GetBaseName(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    // 1. Let loc be the this value.
    JSHandle<JSTaggedValue> loc = GetThis(argv);
    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
    if (!loc->IsJSLocale()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
    }
    // 3. Let locale be loc.[[Locale]].
    // 4. Return the substring of locale corresponding to the unicode_language_id production.
    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
    icu::Locale icuLocale = icu::Locale::createFromName(locale->GetIcuLocale()->getBaseName());
    JSHandle<EcmaString> baseName = JSLocale::ToLanguageTag(thread, icuLocale);
    return baseName.GetTaggedValue();
}

JSTaggedValue BuiltinsLocale::GetCalendar(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    // 1. Let loc be the this value.
    JSHandle<JSTaggedValue> loc = GetThis(argv);
    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
    if (!loc->IsJSLocale()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
    }
    // 3. Return loc.[[Calendar]].
    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
    JSHandle<EcmaString> calendar = JSLocale::NormalizeKeywordValue(thread, locale, "ca");
    return calendar.GetTaggedValue();
}

JSTaggedValue BuiltinsLocale::GetCaseFirst(EcmaRuntimeCallInfo *argv)
{
    // This property only exists if %Locale%.[[RelevantExtensionKeys]] contains "kf".
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    // 1. Let loc be the this value.
    JSHandle<JSTaggedValue> loc = GetThis(argv);
    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
    if (!loc->IsJSLocale()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
    }
    // 3. Return loc.[[CaseFirst]].
    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
    JSHandle<EcmaString> caseFirst = JSLocale::NormalizeKeywordValue(thread, locale, "kf");
    return caseFirst.GetTaggedValue();
}

JSTaggedValue BuiltinsLocale::GetCollation(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    // 1. Let loc be the this value.
    JSHandle<JSTaggedValue> loc = GetThis(argv);
    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
    if (!loc->IsJSLocale()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
    }
    // 3. Return loc.[[Collation]].
    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
    JSHandle<EcmaString> collation = JSLocale::NormalizeKeywordValue(thread, locale, "co");
    return collation.GetTaggedValue();
}

JSTaggedValue BuiltinsLocale::GetHourCycle(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    // 1. Let loc be the this value.
    JSHandle<JSTaggedValue> loc = GetThis(argv);
    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
    if (!loc->IsJSLocale()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
    }
    // 3. Return loc.[[HourCycle]].
    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
    JSHandle<EcmaString> hourCycle = JSLocale::NormalizeKeywordValue(thread, locale, "hc");
    return hourCycle.GetTaggedValue();
}

JSTaggedValue BuiltinsLocale::GetNumeric(EcmaRuntimeCallInfo *argv)
{
    // This property only exists if %Locale%.[[RelevantExtensionKeys]] contains "kn".
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    // 1. Let loc be the this value.
    JSHandle<JSTaggedValue> loc = GetThis(argv);
    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
    if (!loc->IsJSLocale()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
    }
    // 3. Return loc.[[Numeric]].
    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
    icu::Locale *icuLocale = locale->GetIcuLocale();
    UErrorCode status = U_ZERO_ERROR;
    auto numeric = icuLocale->getUnicodeKeywordValue<CString>("kn", status);
    JSTaggedValue result = (numeric == "true") ? JSTaggedValue::True() : JSTaggedValue::False();
    return result;
}

JSTaggedValue BuiltinsLocale::GetNumberingSystem(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    // 1. Let loc be the this value.
    JSHandle<JSTaggedValue> loc = GetThis(argv);
    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
    if (!loc->IsJSLocale()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
    }
    // 3. Return loc.[[NumberingSystem]].
    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
    JSHandle<EcmaString> numberingSystem = JSLocale::NormalizeKeywordValue(thread, locale, "nu");
    return numberingSystem.GetTaggedValue();
}

JSTaggedValue BuiltinsLocale::GetLanguage(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    // 1. Let loc be the this value.
    JSHandle<JSTaggedValue> loc = GetThis(argv);
    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
    if (!loc->IsJSLocale()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
    }
    // 3. Let locale be loc.[[Locale]].
    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
    // 4. Assert: locale matches the unicode_locale_id production.
    // 5. Return the substring of locale corresponding to the unicode_language_subtag production of the
    //    unicode_language_id.
    JSHandle<EcmaString> result = factory->NewFromString("undefined");
    CString language = locale->GetIcuLocale()->getLanguage();
    if (language.empty()) {
        return result.GetTaggedValue();
    }
    result = factory->NewFromString(language);
    return result.GetTaggedValue();
}

JSTaggedValue BuiltinsLocale::GetScript(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    // 1. Let loc be the this value.
    JSHandle<JSTaggedValue> loc = GetThis(argv);
    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
    if (!loc->IsJSLocale()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
    }
    // 3. Let locale be loc.[[Locale]].
    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);

    // 4. Assert: locale matches the unicode_locale_id production.
    // 5. If the unicode_language_id production of locale does not contain the ["-" unicode_script_subtag] sequence,
    //    return undefined.
    // 6. Return the substring of locale corresponding to the unicode_script_subtag production of the
    //    unicode_language_id.
    JSHandle<EcmaString> result(thread, JSTaggedValue::Undefined());
    CString script = locale->GetIcuLocale()->getScript();
    if (script.empty()) {
        return result.GetTaggedValue();
    }
    result = factory->NewFromString(script);
    return result.GetTaggedValue();
}

JSTaggedValue BuiltinsLocale::GetRegion(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope scope(thread);
    EcmaVM *ecmaVm = thread->GetEcmaVM();
    const GlobalEnvConstants *globalConst = thread->GlobalConstants();
    ObjectFactory *factory = ecmaVm->GetFactory();
    // 1. Let loc be the this value.
    JSHandle<JSTaggedValue> loc = GetThis(argv);
    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
    if (!loc->IsJSLocale()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
    }
    // 3. Let locale be loc.[[Locale]].
    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
    // 4. Assert: locale matches the unicode_locale_id production.
    // 5. If the unicode_language_id production of locale does not contain the ["-" unicode_region_subtag] sequence,
    //    return undefined.
    // 6. Return the substring of locale corresponding to the unicode_region_subtag production of the
    //    unicode_language_id.
    CString region = locale->GetIcuLocale()->getCountry();
    if (region.empty()) {
        return globalConst->GetUndefined();
    }
    return factory->NewFromString(region).GetTaggedValue();
}
}  // namespace panda::ecmascript::builtins