/*
   +----------------------------------------------------------------------+
   | Copyright (c) The PHP Group                                          |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | https://www.php.net/license/3_01.txt                                 |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Authors: Máté Kocsis <kocsismate@php.net>                            |
   +----------------------------------------------------------------------+
*/

#include "php.h"
#include "Zend/zend_interfaces.h"
#include "Zend/zend_exceptions.h"
#include "php_uri_common.h"

const uri_property_handler_t *uri_property_handler_from_internal_uri(const uri_internal_t *internal_uri, uri_property_name_t property_name)
{
	switch (property_name) {
		case URI_PROPERTY_NAME_SCHEME:
			return &internal_uri->handler->property_handlers.scheme;
		case URI_PROPERTY_NAME_USERNAME:
			return &internal_uri->handler->property_handlers.username;
		case URI_PROPERTY_NAME_PASSWORD:
			return &internal_uri->handler->property_handlers.password;
		case URI_PROPERTY_NAME_HOST:
			return &internal_uri->handler->property_handlers.host;
		case URI_PROPERTY_NAME_PORT:
			return &internal_uri->handler->property_handlers.port;
		case URI_PROPERTY_NAME_PATH:
			return &internal_uri->handler->property_handlers.path;
		case URI_PROPERTY_NAME_QUERY:
			return &internal_uri->handler->property_handlers.query;
		case URI_PROPERTY_NAME_FRAGMENT:
			return &internal_uri->handler->property_handlers.fragment;
		EMPTY_SWITCH_DEFAULT_CASE()
	}
}

static zend_string *get_known_string_by_property_name(uri_property_name_t property_name)
{
	switch (property_name) {
		case URI_PROPERTY_NAME_SCHEME:
			return ZSTR_KNOWN(ZEND_STR_SCHEME);
		case URI_PROPERTY_NAME_USERNAME:
			return ZSTR_KNOWN(ZEND_STR_USERNAME);
		case URI_PROPERTY_NAME_PASSWORD:
			return ZSTR_KNOWN(ZEND_STR_PASSWORD);
		case URI_PROPERTY_NAME_HOST:
			return ZSTR_KNOWN(ZEND_STR_HOST);
		case URI_PROPERTY_NAME_PORT:
			return ZSTR_KNOWN(ZEND_STR_PORT);
		case URI_PROPERTY_NAME_PATH:
			return ZSTR_KNOWN(ZEND_STR_PATH);
		case URI_PROPERTY_NAME_QUERY:
			return ZSTR_KNOWN(ZEND_STR_QUERY);
		case URI_PROPERTY_NAME_FRAGMENT:
			return ZSTR_KNOWN(ZEND_STR_FRAGMENT);
		EMPTY_SWITCH_DEFAULT_CASE()
	}
}

void uri_read_component(INTERNAL_FUNCTION_PARAMETERS, uri_property_name_t property_name, uri_component_read_mode_t component_read_mode)
{
	ZEND_PARSE_PARAMETERS_NONE();

	uri_internal_t *internal_uri = Z_URI_INTERNAL_P(ZEND_THIS);
	URI_ASSERT_INITIALIZATION(internal_uri);

	const uri_property_handler_t *property_handler = uri_property_handler_from_internal_uri(internal_uri, property_name);
	ZEND_ASSERT(property_handler != NULL);

	if (UNEXPECTED(property_handler->read_func(internal_uri, component_read_mode, return_value) == FAILURE)) {
		zend_throw_error(NULL, "The %s component cannot be retrieved", ZSTR_VAL(get_known_string_by_property_name(property_name)));
		RETURN_THROWS();
	}
}

static void uri_write_component_ex(INTERNAL_FUNCTION_PARAMETERS, uri_property_name_t property_name, zval *property_zv)
{
	uri_internal_t *internal_uri = Z_URI_INTERNAL_P(ZEND_THIS);
	URI_ASSERT_INITIALIZATION(internal_uri);

	const uri_property_handler_t *property_handler = uri_property_handler_from_internal_uri(internal_uri, property_name);
	ZEND_ASSERT(property_handler != NULL);

	zend_object *new_object = uri_clone_obj_handler(Z_OBJ_P(ZEND_THIS));
	ZEND_ASSERT(new_object != NULL);

	uri_internal_t *new_internal_uri = uri_internal_from_obj(new_object);
	URI_ASSERT_INITIALIZATION(new_internal_uri);
	if (UNEXPECTED(property_handler->write_func == NULL)) {
		zend_readonly_property_modification_error_ex(ZSTR_VAL(Z_OBJ_P(ZEND_THIS)->ce->name),
			ZSTR_VAL(get_known_string_by_property_name(property_name)));
		zend_object_release(new_object);
		RETURN_THROWS();
	}

	zval errors;
	ZVAL_UNDEF(&errors);
	if (UNEXPECTED(property_handler->write_func(new_internal_uri, property_zv, &errors) == FAILURE)) {
		zval_ptr_dtor(&errors);
		zend_object_release(new_object);
		RETURN_THROWS();
	}

	ZEND_ASSERT(Z_ISUNDEF(errors));
	RETVAL_OBJ(new_object);
}

void uri_write_component_str(INTERNAL_FUNCTION_PARAMETERS, uri_property_name_t property_name)
{
	zend_string *value;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_PATH_STR(value)
	ZEND_PARSE_PARAMETERS_END();

	zval zv;
	ZVAL_STR(&zv, value);

	uri_write_component_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, property_name, &zv);
}

void uri_write_component_str_or_null(INTERNAL_FUNCTION_PARAMETERS, uri_property_name_t property_name)
{
	zend_string *value;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_PATH_STR_OR_NULL(value)
	ZEND_PARSE_PARAMETERS_END();

	zval zv;
	if (value == NULL) {
		ZVAL_NULL(&zv);
	} else {
		ZVAL_STR(&zv, value);
	}

	uri_write_component_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, property_name, &zv);
}

void uri_write_component_long_or_null(INTERNAL_FUNCTION_PARAMETERS, uri_property_name_t property_name)
{
	zend_long value;
	bool value_is_null;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_LONG_OR_NULL(value, value_is_null)
	ZEND_PARSE_PARAMETERS_END();

	zval zv;
	if (value_is_null) {
		ZVAL_NULL(&zv);
	} else {
		ZVAL_LONG(&zv, value);
	}

	uri_write_component_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, property_name, &zv);
}
