/*
   +----------------------------------------------------------------------+
   | 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: Kirti Velankar <kirtig@yahoo-inc.com>                       |
   |          Gustavo Lopes <cataphract@php.net>                          |
   +----------------------------------------------------------------------+
*/

#include "../intl_cppshims.h"

#include <unicode/timezone.h>
#include <unicode/calendar.h>
#include <unicode/datefmt.h>

extern "C" {
#include <unicode/ustring.h>
#include <unicode/udat.h>
#include <unicode/uloc.h>

#include "php_intl.h"
#include "dateformat_create.h"
#include "dateformat_class.h"
#define USE_CALENDAR_POINTER 1
#include "../calendar/calendar_class.h"
#define USE_TIMEZONE_POINTER 1
#include "../timezone/timezone_class.h"
#include "../intl_convert.h"
}

#include "dateformat_helpers.h"
#include "zend_exceptions.h"

#define INTL_UDATE_FMT_OK(i) \
	(UDAT_FULL == (i) || UDAT_LONG == (i) ||    \
	 UDAT_MEDIUM == (i) || UDAT_SHORT == (i) || \
	 UDAT_RELATIVE == (i) || UDAT_FULL_RELATIVE == (i) || \
	 UDAT_LONG_RELATIVE == (i) || UDAT_MEDIUM_RELATIVE == (i) || \
	 UDAT_SHORT_RELATIVE == (i) || UDAT_NONE == (i) || \
	 UDAT_PATTERN == (i))

/* {{{ */
static zend_result datefmt_ctor(INTERNAL_FUNCTION_PARAMETERS)
{
	zval		*object;
	char	*locale_str;
	size_t		locale_len	= 0;
	Locale		locale;
	zend_long	date_type = UDAT_FULL;
	zend_long	time_type = UDAT_FULL;
	zend_object *calendar_obj = NULL;
	zend_long calendar_long = 0;
	bool calendar_is_null = 1;
	Calendar *cal = NULL;
	zend_long	calendar_type;
	bool		calendar_owned;
	zend_object *timezone_object = nullptr;
	zend_string *timezone_string = nullptr;
	TimeZone	*timezone	= NULL;
	bool		explicit_tz;
	char*       pattern_str		= NULL;
	size_t      pattern_str_len	= 0;
	UChar*      svalue		= NULL;		/* UTF-16 pattern_str */
	int32_t     slength		= 0;
	IntlDateFormatter_object* dfo;

	intl_error_reset(NULL);
	object = return_value;

	ZEND_PARSE_PARAMETERS_START(1, 6)
		Z_PARAM_STRING_OR_NULL(locale_str, locale_len)
		Z_PARAM_OPTIONAL
		Z_PARAM_LONG(date_type)
		Z_PARAM_LONG(time_type)
		Z_PARAM_OBJ_OR_STR_OR_NULL(timezone_object, timezone_string)
		Z_PARAM_OBJ_OF_CLASS_OR_LONG_OR_NULL(calendar_obj, Calendar_ce_ptr, calendar_long, calendar_is_null)
		Z_PARAM_STRING_OR_NULL(pattern_str, pattern_str_len)
	ZEND_PARSE_PARAMETERS_END_EX(return FAILURE);

	DATE_FORMAT_METHOD_FETCH_OBJECT_NO_CHECK;

	if (DATE_FORMAT_OBJECT(dfo) != NULL) {
		intl_errors_set(INTL_DATA_ERROR_P(dfo), U_ILLEGAL_ARGUMENT_ERROR, "cannot call constructor twice");
		return FAILURE;
	}

	if (!INTL_UDATE_FMT_OK(date_type)) {
		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "invalid date format style");
		return FAILURE;
	}
	if (!INTL_UDATE_FMT_OK(time_type)) {
		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "invalid time format style");
		return FAILURE;
	}
	if (date_type == UDAT_PATTERN && time_type != UDAT_PATTERN) {
		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "datefmt_create: time format must be IntlDateFormatter::PATTERN if date format is IntlDateFormatter::PATTERN");
		return FAILURE;
	}

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

	char* canonicalized_locale = canonicalize_locale_string(locale_str);
	const char* final_locale = canonicalized_locale ? canonicalized_locale : locale_str;
	const char* stored_locale = canonicalized_locale ? canonicalized_locale : locale_str;

	locale = Locale::createFromName(final_locale);
	/* get*Name accessors being set does not preclude being bogus */
	if (locale.isBogus() || ((locale_len == 1 && locale_str[0] != 'C') || (locale_len > 1 && strlen(locale.getISO3Language()) == 0))) {
		zend_argument_value_error(1, "\"%s\" is invalid", locale_str);
		goto error;
	}

	/* process calendar */
	if (datefmt_process_calendar_arg(calendar_obj, calendar_long, calendar_is_null, locale,
		INTL_DATA_ERROR_P(dfo), cal, calendar_type, calendar_owned) == FAILURE
	) {
		goto error;
	}

	/* process timezone */
	explicit_tz = timezone_object != nullptr || timezone_string != nullptr;

	if (explicit_tz || calendar_owned ) {
		//we have an explicit time zone or a non-object calendar
		timezone = timezone_process_timezone_argument(timezone_object, timezone_string, INTL_DATA_ERROR_P(dfo), 4);
		if (timezone == nullptr) {
			goto error;
		}
	}

	/* Convert pattern (if specified) to UTF-16. */
	if (pattern_str && pattern_str_len > 0) {
		intl_convert_utf8_to_utf16(&svalue, &slength,
				pattern_str, pattern_str_len, &INTL_DATA_ERROR_CODE(dfo));
		if (U_FAILURE(INTL_DATA_ERROR_CODE(dfo))) {
			/* object construction -> only set global error */
			intl_error_set(NULL, INTL_DATA_ERROR_CODE(dfo),
					"error converting pattern to UTF-16");
			goto error;
		}
	}

	DATE_FORMAT_OBJECT(dfo) = udat_open((UDateFormatStyle)time_type,
			(UDateFormatStyle)date_type, final_locale, NULL, 0, svalue,
			slength, &INTL_DATA_ERROR_CODE(dfo));

	if (pattern_str && pattern_str_len > 0) {
		udat_applyPattern(DATE_FORMAT_OBJECT(dfo), true, svalue, slength);
		if (U_FAILURE(INTL_DATA_ERROR_CODE(dfo))) {
			intl_error_set(NULL, INTL_DATA_ERROR_CODE(dfo), "error applying pattern");
			goto error;
		}
	}

	if (!U_FAILURE(INTL_DATA_ERROR_CODE(dfo))) {
		DateFormat *df = (DateFormat*)DATE_FORMAT_OBJECT(dfo);
		if (calendar_owned) {
			df->adoptCalendar(cal);
			calendar_owned = false;
		} else {
			df->setCalendar(*cal);
		}

		if (timezone != NULL) {
			df->adoptTimeZone(timezone);
		}
	} else {
		intl_error_set(NULL, INTL_DATA_ERROR_CODE(dfo),	"date formatter creation failed");
		goto error;
	}

	/* Set the class variables */
	dfo->date_type			= date_type;
	dfo->time_type			= time_type;
	dfo->calendar			= calendar_type;
	/* Store the canonicalized locale, or fallback to original if canonicalization failed */
	dfo->requested_locale	= estrdup(stored_locale);

error:
	if (canonicalized_locale) {
		efree(canonicalized_locale);
	}
	if (svalue) {
		efree(svalue);
	}
	if (timezone != NULL && DATE_FORMAT_OBJECT(dfo) == NULL) {
		delete timezone;
	}
	if (cal != NULL && calendar_owned) {
		delete cal;
	}

	return U_FAILURE(intl_error_get_code(NULL)) ? FAILURE : SUCCESS;
}
/* }}} */

/* {{{ Create formatter. */
U_CFUNC PHP_FUNCTION( datefmt_create )
{
    object_init_ex( return_value, IntlDateFormatter_ce_ptr );
    if (datefmt_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU) == FAILURE) {
		zval_ptr_dtor(return_value);
		RETURN_NULL();
	}
}
/* }}} */

/* {{{ IntlDateFormatter object constructor. */
U_CFUNC PHP_METHOD( IntlDateFormatter, __construct )
{
	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;

	/* return_value param is being changed, therefore we will always return
	 * NULL here */
	return_value = ZEND_THIS;
	if (datefmt_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU) == FAILURE) {
		ZEND_ASSERT(EG(exception));
	}
	INTL_G(use_exceptions) = old_use_exception;
	INTL_G(error_level) = old_error_level;
}
/* }}} */
