/*
   +----------------------------------------------------------------------+
   | Copyright © The PHP Group and Contributors.                          |
   +----------------------------------------------------------------------+
   | This source file is subject to the Modified BSD License that is      |
   | bundled with this package in the file LICENSE, and is available      |
   | through the World Wide Web at <https://www.php.net/license/>.        |
   |                                                                      |
   | SPDX-License-Identifier: BSD-3-Clause                                |
   +----------------------------------------------------------------------+
   | Authors: Bogdan Ungureanu <bogdanungureanu21@gmail.com>              |
   +----------------------------------------------------------------------+
*/

extern "C" {
    #include "php.h"
    #include "zend_API.h"
    #include "../intl_common.h"
}

#if U_ICU_VERSION_MAJOR_NUM >= 63
#include <unicode/numberrangeformatter.h>
#include <unicode/numberformatter.h>
#include <unicode/unistr.h>
#include <unicode/locid.h>
#include "../intl_convertcpp.h"

extern "C" {
    #include "../intl_error.h"
    #include "../php_intl.h"
    #include "../intl_data.h"
    #include "rangeformatter_arginfo.h"
    #include "rangeformatter_class.h"
}

using icu::number::NumberRangeFormatter;
using icu::number::NumberFormatter;
using icu::number::UnlocalizedNumberFormatter;
using icu::number::LocalizedNumberRangeFormatter;
using icu::UnicodeString;
using icu::MeasureUnit;

static zend_object_handlers rangeformatter_handlers;
zend_class_entry *class_entry_IntlNumberRangeFormatter;

zend_object *IntlNumberRangeFormatter_object_create(zend_class_entry *ce)
{
    IntlNumberRangeFormatter_object* intern;

    intern = reinterpret_cast<IntlNumberRangeFormatter_object*>(zend_object_alloc(sizeof(IntlNumberRangeFormatter_object), ce));
    zend_object_std_init(&intern->zo, ce);
    object_properties_init(&intern->zo, ce);

    // Initialize rangeformatter_data structure
    intl_error_init(&intern->nrf_data.error);
    intern->nrf_data.unumrf = nullptr;

    intern->zo.handlers = &rangeformatter_handlers;

    return &intern->zo;
}

static icu::Formattable rangeformatter_create_formattable(const zval *number)
{
    icu::Formattable formattable;

    if (Z_TYPE_P(number) == IS_DOUBLE) {
        formattable.setDouble(Z_DVAL_P(number));
    } else {
        formattable.setInt64(static_cast<int64_t>(Z_LVAL_P(number)));
    }

    return formattable;
}

U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, __construct)
{
    ZEND_PARSE_PARAMETERS_NONE();
    zend_throw_error(NULL, "Cannot directly construct %s, use createFromSkeleton method instead", ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name));
}

U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton)
{
    char* skeleton;
    char* locale;
    size_t locale_len;
    size_t skeleton_len;
    zend_long collapse;
    zend_long identityFallback;

    ZEND_PARSE_PARAMETERS_START(4,4)
        Z_PARAM_STRING(skeleton, skeleton_len)
        Z_PARAM_STRING(locale, locale_len)
        Z_PARAM_LONG(collapse)
        Z_PARAM_LONG(identityFallback)
    ZEND_PARSE_PARAMETERS_END();

    intl_error_reset(NULL);

    if (locale_len == 0) {
        locale = (char *)intl_locale_get_default();
    }

    if (UNEXPECTED(locale_len > INTL_MAX_LOCALE_LEN)) {
        zend_argument_value_error(2, "must be no longer than %d characters", INTL_MAX_LOCALE_LEN);
        RETURN_THROWS();
    }

    if (icu::Locale(locale).getISO3Language()[0] == '\0') {
        zend_argument_value_error(2, "\"%s\" is invalid", locale);
        RETURN_THROWS();
    }

    if (collapse != UNUM_RANGE_COLLAPSE_AUTO && collapse != UNUM_RANGE_COLLAPSE_NONE && collapse != UNUM_RANGE_COLLAPSE_UNIT && collapse != UNUM_RANGE_COLLAPSE_ALL) {
        zend_argument_value_error(3, "must be one of IntlNumberRangeFormatter::COLLAPSE_AUTO, IntlNumberRangeFormatter::COLLAPSE_NONE, IntlNumberRangeFormatter::COLLAPSE_UNIT, or IntlNumberRangeFormatter::COLLAPSE_ALL");
        RETURN_THROWS();
    }

    if (identityFallback != UNUM_IDENTITY_FALLBACK_SINGLE_VALUE && identityFallback != UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE && identityFallback != UNUM_IDENTITY_FALLBACK_APPROXIMATELY && identityFallback != UNUM_IDENTITY_FALLBACK_RANGE) {
        zend_argument_value_error(4, "must be one of IntlNumberRangeFormatter::IDENTITY_FALLBACK_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY, or IntlNumberRangeFormatter::IDENTITY_FALLBACK_RANGE");
        RETURN_THROWS();
    }

    UErrorCode status = U_ZERO_ERROR;

    UnicodeString skeleton_ustr(skeleton, skeleton_len);

    UnlocalizedNumberFormatter nf = NumberFormatter::forSkeleton(skeleton_ustr, status);

    if (U_FAILURE(status)) {
        // override error level and use exceptions
        const bool old_use_exception = INTL_G(use_exceptions);
        const zend_long old_error_level = INTL_G(error_level);
        INTL_G(use_exceptions) = true;
        INTL_G(error_level) = 0;

        intl_error_set(NULL, status, "Failed to create the number skeleton");

        INTL_G(use_exceptions) = old_use_exception;
        INTL_G(error_level) = old_error_level;

        RETURN_THROWS();
    }

    LocalizedNumberRangeFormatter* nrf = new LocalizedNumberRangeFormatter(
        NumberRangeFormatter::with()
            .locale(locale)
            .numberFormatterBoth(nf)
            .collapse(static_cast<UNumberRangeCollapse>(collapse))
            .identityFallback(static_cast<UNumberRangeIdentityFallback>(identityFallback))
    );

    zend_object* obj = IntlNumberRangeFormatter_object_create(class_entry_IntlNumberRangeFormatter);

    RANGEFORMATTER_OBJECT(php_intl_numberrangeformatter_fetch_object(obj)) = nrf;

    RETURN_OBJ(obj);
}

U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, format)
{
    zval *start;
    zval *end;

    IntlNumberRangeFormatter_object* obj = Z_INTL_RANGEFORMATTER_P(ZEND_THIS);

    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_NUMBER(start)
        Z_PARAM_NUMBER(end)
    ZEND_PARSE_PARAMETERS_END();

    intl_errors_reset(RANGEFORMATTER_ERROR_P(obj));

    ZEND_ASSERT(RANGEFORMATTER_OBJECT(obj) != NULL);

    UErrorCode error = U_ZERO_ERROR;

    icu::Formattable start_formattable = rangeformatter_create_formattable(start);
    icu::Formattable end_formattable = rangeformatter_create_formattable(end);

    UnicodeString result = RANGEFORMATTER_OBJECT(obj)->formatFormattableRange(start_formattable, end_formattable, error).toString(error);

    // override error level and use exceptions
    const bool old_use_exception = INTL_G(use_exceptions);
    const zend_long old_error_level = INTL_G(error_level);
    INTL_G(use_exceptions) = true;
    INTL_G(error_level) = 0;

    zend_string *ret = NULL;

    if (U_FAILURE(error)) {
        intl_errors_set(RANGEFORMATTER_ERROR_P(obj), error, "Failed to format number range");
    } else {
        ret = intl_charFromString(result, &error);

        if (UNEXPECTED(ret == NULL)) {
            if (U_SUCCESS(error)) {
                error = U_ILLEGAL_ARGUMENT_ERROR;
            }

            intl_errors_set(RANGEFORMATTER_ERROR_P(obj), error, "Failed to convert result to UTF-8");
        }
    }

    INTL_G(use_exceptions) = old_use_exception;
    INTL_G(error_level) = old_error_level;

    if (UNEXPECTED(ret == NULL)) {
        RETURN_THROWS();
    }

    RETVAL_NEW_STR(ret);
}

U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, getErrorCode)
{
    ZEND_PARSE_PARAMETERS_NONE();

    IntlNumberRangeFormatter_object* obj = Z_INTL_RANGEFORMATTER_P(ZEND_THIS);

    RETURN_LONG(intl_error_get_code(&obj->nrf_data.error));
}

U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, getErrorMessage)
{
    ZEND_PARSE_PARAMETERS_NONE();

    IntlNumberRangeFormatter_object* obj = Z_INTL_RANGEFORMATTER_P(ZEND_THIS);

    RETURN_STR(intl_error_get_message(&obj->nrf_data.error));
}

void IntlNumberRangeFormatter_object_free(zend_object *object)
{
    IntlNumberRangeFormatter_object* nfo = php_intl_numberrangeformatter_fetch_object(object);

    if (nfo->nrf_data.unumrf) {
        delete nfo->nrf_data.unumrf;
        nfo->nrf_data.unumrf = nullptr;
    }

    intl_error_reset(&nfo->nrf_data.error);

    zend_object_std_dtor(&nfo->zo);
}

void rangeformatter_register_class(void)
{
    class_entry_IntlNumberRangeFormatter = register_class_IntlNumberRangeFormatter();
    class_entry_IntlNumberRangeFormatter->create_object = IntlNumberRangeFormatter_object_create;

    memcpy(&rangeformatter_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
    rangeformatter_handlers.offset = offsetof(IntlNumberRangeFormatter_object, zo);
    rangeformatter_handlers.free_obj = IntlNumberRangeFormatter_object_free;
    rangeformatter_handlers.clone_obj = NULL;
}
#endif
