// © 2023 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html

#include <utility>

#include "unicode/bytestream.h"
#include "unicode/localebuilder.h"
#include "unicode/locid.h"
#include "unicode/stringpiece.h"
#include "unicode/umachine.h"
#include "unicode/ulocbuilder.h"
#include "cstring.h"
#include "ustr_imp.h"

using icu::CheckedArrayByteSink;
using icu::StringPiece;

#define EXTERNAL(i) (reinterpret_cast<ULocaleBuilder*>(i))
#define INTERNAL(e) (reinterpret_cast<icu::LocaleBuilder*>(e))
#define CONST_INTERNAL(e) (reinterpret_cast<const icu::LocaleBuilder*>(e))

ULocaleBuilder* ulocbld_open() {
    return EXTERNAL(new icu::LocaleBuilder());
}

void ulocbld_close(ULocaleBuilder* builder) {
    if (builder == nullptr) return;
    delete INTERNAL(builder);
}

void ulocbld_setLocale(ULocaleBuilder* builder, const char* locale, int32_t length) {
    if (builder == nullptr) return;
    icu::Locale l;
    if (length < 0 || locale[length] == '\0') {
        l = icu::Locale(locale);
    } else {
        if (length >= ULOC_FULLNAME_CAPACITY) {
            l.setToBogus();
        } else {
            // locale is not null termined but Locale API require one.
            // Create a null termined version in buf.
            char buf[ULOC_FULLNAME_CAPACITY];
            uprv_memcpy(buf, locale, length);
            buf[length] = '\0';
            l = icu::Locale(buf);
        }
    }
    INTERNAL(builder)->setLocale(l);
}

void
ulocbld_adoptULocale(ULocaleBuilder* builder, ULocale* locale) {
    if (builder == nullptr) return;
    INTERNAL(builder)->setLocale(*(reinterpret_cast<const icu::Locale*>(locale)));
    ulocale_close(locale);
}

#define STRING_PIECE(s, l) ((l)<0 ? StringPiece(s) : StringPiece((s), (l)))

#define IMPL_ULOCBLD_SETTER(N) \
void ulocbld_##N(ULocaleBuilder* bld, const char* s, int32_t l) { \
    if (bld == nullptr) return; \
    INTERNAL(bld)->N(STRING_PIECE(s,l)); \
}

IMPL_ULOCBLD_SETTER(setLanguageTag)
IMPL_ULOCBLD_SETTER(setLanguage)
IMPL_ULOCBLD_SETTER(setScript)
IMPL_ULOCBLD_SETTER(setRegion)
IMPL_ULOCBLD_SETTER(setVariant)
IMPL_ULOCBLD_SETTER(addUnicodeLocaleAttribute)
IMPL_ULOCBLD_SETTER(removeUnicodeLocaleAttribute)

void ulocbld_setExtension(ULocaleBuilder* builder, char key, const char* value, int32_t length) {
    if (builder == nullptr) return;
    INTERNAL(builder)->setExtension(key, STRING_PIECE(value, length));
}

void ulocbld_setUnicodeLocaleKeyword(
    ULocaleBuilder* builder, const char* key, int32_t keyLength,
    const char* type, int32_t typeLength) {
    if (builder == nullptr) return;
    INTERNAL(builder)->setUnicodeLocaleKeyword(
        STRING_PIECE(key, keyLength), STRING_PIECE(type, typeLength));
}

void ulocbld_clear(ULocaleBuilder* builder) {
    if (builder == nullptr) return;
    INTERNAL(builder)->clear();
}

void ulocbld_clearExtensions(ULocaleBuilder* builder) {
    if (builder == nullptr) return;
    INTERNAL(builder)->clearExtensions();
}


ULocale* ulocbld_buildULocale(ULocaleBuilder* builder, UErrorCode* err) {
    if (builder == nullptr) {
        *err = U_ILLEGAL_ARGUMENT_ERROR;
        return nullptr;
    }
    icu::Locale l = INTERNAL(builder)->build(*err);
    if (U_FAILURE(*err)) return nullptr;
    icu::Locale* r = l.clone();
    if (r == nullptr) {
        *err = U_MEMORY_ALLOCATION_ERROR;
        return nullptr;
    }
    return reinterpret_cast<ULocale*>(r);
}

int32_t ulocbld_buildLocaleID(ULocaleBuilder* builder,
                              char* buffer, int32_t bufferCapacity, UErrorCode* err) {
    if (builder == nullptr) {
        *err = U_ILLEGAL_ARGUMENT_ERROR;
        return 0;
    }
    icu::Locale l = INTERNAL(builder)->build(*err);
    if (U_FAILURE(*err)) return 0;
    int32_t length = (int32_t)(uprv_strlen(l.getName()));
    if (0 < length && length <= bufferCapacity) {
        uprv_memcpy(buffer, l.getName(), length);
    }
    return u_terminateChars(buffer, bufferCapacity, length, err);
}

int32_t ulocbld_buildLanguageTag(ULocaleBuilder* builder,
                  char* buffer, int32_t bufferCapacity, UErrorCode* err) {
    if (builder == nullptr) {
        *err = U_ILLEGAL_ARGUMENT_ERROR;
        return 0;
    }
    icu::Locale l = INTERNAL(builder)->build(*err);
    if (U_FAILURE(*err)) return 0;
    CheckedArrayByteSink sink(buffer, bufferCapacity);
    l.toLanguageTag(sink, *err);
    int32_t reslen = sink.NumberOfBytesAppended();
    if (U_FAILURE(*err)) {
        return reslen;
    }
    if (sink.Overflowed()) {
        *err = U_BUFFER_OVERFLOW_ERROR;
    } else {
        u_terminateChars(buffer, bufferCapacity, reslen, err);
    }
    return reslen;
}

UBool ulocbld_copyErrorTo(const ULocaleBuilder* builder, UErrorCode *outErrorCode) {
    if (builder == nullptr) {
        *outErrorCode = U_ILLEGAL_ARGUMENT_ERROR;
        return true;
    }
    return CONST_INTERNAL(builder)->copyErrorTo(*outErrorCode);
}
