/**
 * This file is part of the Zephir.
 *
 * (c) Phalcon Team <team@zephir-lang.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code. If you did not receive
 * a copy of the license it is available through the world-wide-web at the
 * following url: https://docs.zephir-lang.com/en/latest/license
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <php.h>
#include <php_ext.h>
#include <php_main.h>
#include <Zend/zend_exceptions.h>
#include <Zend/zend_interfaces.h>
#include <ext/spl/spl_exceptions.h>

#include "kernel/main.h"
#include "kernel/memory.h"
#include "kernel/fcall.h"
#include "kernel/object.h"
#include "kernel/exception.h"
#include "kernel/generator.h"


zend_string* i_parent = NULL;
zend_string* i_static = NULL;
zend_string* i_self   = NULL;

int zephir_is_iterable_ex(zval *arr, int duplicate)
{
	if (UNEXPECTED(Z_TYPE_P(arr) == IS_OBJECT && zephir_instance_of_ev(arr, (const zend_class_entry *)zend_ce_iterator))) {
		return 1;
	} else if (UNEXPECTED(Z_TYPE_P(arr) != IS_ARRAY)) {
		return 0;
	}
    //TODO: duplicate
    return 1;
}

/**
 * Parses method parameters with minimum overhead
 */
int zephir_fetch_parameters(int num_args, int required_args, int optional_args, ...)
{
	va_list va;
	int arg_count = ZEND_CALL_NUM_ARGS(EG(current_execute_data));
	zval *arg, **p;
	int i;

	if (num_args < required_args || (num_args > (required_args + optional_args))) {
		zephir_throw_exception_string(spl_ce_BadMethodCallException, SL("Wrong number of parameters"));
		return FAILURE;
	}

	if (num_args > arg_count) {
		zephir_throw_exception_string(spl_ce_BadMethodCallException, SL("Could not obtain parameters for parsing"));
		return FAILURE;
	}

	if (!num_args) {
		return SUCCESS;
	}

	va_start(va, optional_args);

	i = 0;
	while (num_args-- > 0) {
		arg = ZEND_CALL_ARG(EG(current_execute_data), i + 1);
		p = va_arg(va, zval **);
		*p = arg;

		i++;
	}

	va_end(va);

	return SUCCESS;
}

int zephir_fetch_parameters_variadic(int num_args, int required_args, int optional_args, ...)
{
	va_list va;
	zval *arg, **p;
	int i, fixed_args;

	if (num_args < required_args) {
		zephir_throw_exception_string(spl_ce_BadMethodCallException, SL("Wrong number of parameters"));
		return FAILURE;
	}

	fixed_args = required_args + optional_args;

	va_start(va, optional_args);

	for (i = 0; i < fixed_args; i++) {
		p = va_arg(va, zval **);
		if (i < num_args) {
			arg = ZEND_CALL_ARG(EG(current_execute_data), i + 1);
			*p = arg;
		}
	}

	va_end(va);

	return SUCCESS;
}

/**
 * Gets the global zval into PG macro
 */
int zephir_get_global(zval *arr, const char *global, unsigned int global_length)
{
	zend_array *symbol_table;
	zend_string *str = zend_string_init(global, global_length, 0);

	if (PG(auto_globals_jit)) {
		zend_is_auto_global(str);
	}

	if (&EG(symbol_table)) {
		zval *gv;

		if ((gv = zend_hash_find_ind(&EG(symbol_table), str)) != NULL) {
			ZVAL_DEREF(gv);
			if (Z_TYPE_P(gv) == IS_ARRAY) {
				if (Z_REFCOUNTED_P(gv) && Z_REFCOUNT_P(gv) <= 1) {
					ZVAL_COPY_VALUE(arr, gv);
				} else {
					ZVAL_DUP(arr, gv);
					zend_hash_update(&EG(symbol_table), str, arr);
				}

				zend_string_release(str);
				return SUCCESS;
			}
		}
	}

	array_init(arr);
	if (!(&EG(symbol_table))) {
		symbol_table = zend_rebuild_symbol_table();
	} else {
		symbol_table = &EG(symbol_table);
	}

	zend_hash_update(symbol_table, str, arr);
	zend_string_release(str);

	return FAILURE;
}

/**
 * Makes fast count on implicit array types
 */
void zephir_fast_count(zval *result, zval *value)
{
	if (Z_TYPE_P(value) == IS_ARRAY) {
		ZVAL_LONG(result, zend_hash_num_elements(Z_ARRVAL_P(value)));
		return;
	}

	if (Z_TYPE_P(value) == IS_OBJECT) {
		zval retval;

		if (Z_OBJ_HT_P(value)->count_elements) {
			ZVAL_LONG(result, 1);
			if (SUCCESS == Z_OBJ_HT(*value)->count_elements(Z_OBJ_P(value), &Z_LVAL_P(result))) {
				return;
			}
		}

		if (instanceof_function(Z_OBJCE_P(value), zend_ce_countable)) {
			zend_call_method_with_0_params(Z_OBJ_P(value), NULL, NULL, "count", &retval);
			if (Z_TYPE(retval) != IS_UNDEF) {
				convert_to_long_ex(&retval);
				ZVAL_LONG(result, Z_LVAL(retval));
				zval_ptr_dtor(&retval);
			}
			return;
		}

		ZVAL_LONG(result, 0);
		return;
	}

	if (Z_TYPE_P(value) == IS_NULL) {
		ZVAL_LONG(result, 0);
		return;
	}

	ZVAL_LONG(result, 1);
}

/**
 * Makes fast count on implicit array types without creating a return zval value
 */
int zephir_fast_count_ev(zval *value)
{
	zend_long count = 0;

	if (Z_TYPE_P(value) == IS_ARRAY) {
		return zend_hash_num_elements(Z_ARRVAL_P(value)) > 0;
	}

	if (Z_TYPE_P(value) == IS_OBJECT) {
		zval retval;

		if (Z_OBJ_HT_P(value)->count_elements) {
			Z_OBJ_HT(*value)->count_elements(Z_OBJ_P(value), &count);
			return (int) count > 0;
		}

		if (instanceof_function(Z_OBJCE_P(value), zend_ce_countable)) {
			zend_call_method_with_0_params(Z_OBJ_P(value), NULL, NULL, "count", &retval);
			if (Z_TYPE(retval) != IS_UNDEF) {
				convert_to_long_ex(&retval);
				count = Z_LVAL(retval);
				zval_ptr_dtor(&retval);
				return (int) count > 0;
			}
			return 0;
		}

		return 0;
	}

	if (Z_TYPE_P(value) == IS_NULL) {
		return 0;
	}

	return 1;
}

/**
 * Makes fast count on implicit array types without creating a return zval value
 */
int zephir_fast_count_int(zval *value)
{
	zend_long count = 0;

	if (Z_TYPE_P(value) == IS_ARRAY) {
		return zend_hash_num_elements(Z_ARRVAL_P(value));
	}

	if (Z_TYPE_P(value) == IS_OBJECT) {
		zval retval;

		if (Z_OBJ_HT_P(value)->count_elements) {
			Z_OBJ_HT(*value)->count_elements(Z_OBJ_P(value), &count);
			return (int) count;
		}

		if (instanceof_function(Z_OBJCE_P(value), zend_ce_countable)) {
			zend_call_method_with_0_params(Z_OBJ_P(value), NULL, NULL, "count", &retval);
			if (Z_TYPE(retval) != IS_UNDEF) {
				convert_to_long_ex(&retval);
				count = Z_LVAL(retval);
				zval_ptr_dtor(&retval);
				return (int) count;
			}
			return 0;
		}

		return 0;
	}

	if (Z_TYPE_P(value) == IS_NULL) {
		return 0;
	}

	return 1;
}

/**
 * Check if a function exists
 *
 * @param function_name
 * @return
 */
int zephir_function_exists(const zval *function_name)
{
	if (zend_hash_str_exists(CG(function_table), Z_STRVAL_P(function_name), Z_STRLEN_P(function_name))) {
		return SUCCESS;
	}

	return FAILURE;
}

/**
 * Check if a function exists using explicit function length
 *
 * TODO: Check if make sense to merge all logic of IS_STRING inside zephir_function_exists() function.
 * @param function_name
 * @param function_len strlen(function_name) + 1
 */
int zephir_function_exists_ex(const char *function_name, unsigned int function_len)
{
	if (zend_hash_str_exists(CG(function_table), function_name, function_len)) {
        return SUCCESS;
    }

    return FAILURE;
}

/**
 * Checks if a zval is callable
 */
int zephir_is_callable(zval *var)
{
	char *error = NULL;
	zend_bool retval;

	retval = zend_is_callable_ex(var, NULL, 0, NULL, NULL, &error);
	if (error) {
		efree(error);
	}

	return (int) retval;
}

/**
 * Checks whether a variable has a scalar type
 */
int zephir_is_scalar(zval *var)
{
	switch (Z_TYPE_P(var)) {
		case IS_TRUE:
		case IS_FALSE:
		case IS_DOUBLE:
		case IS_LONG:
		case IS_STRING:
			return 1;
			break;
	}

	return 0;
}

/**
 * Returns the type of a variable as a string
 */
void zephir_gettype(zval *return_value, zval *arg)
{
	switch (Z_TYPE_P(arg)) {

		case IS_NULL:
			RETVAL_STRING("NULL");
			break;

		case IS_TRUE:
		case IS_FALSE:
			RETVAL_STRING("boolean");
			break;

		case IS_LONG:
			RETVAL_STRING("integer");
			break;

		case IS_DOUBLE:
			RETVAL_STRING("double");
			break;

		case IS_STRING:
			RETVAL_STRING("string");
			break;

		case IS_ARRAY:
			RETVAL_STRING("array");
			break;

		case IS_OBJECT:
			RETVAL_STRING("object");
			break;

		case IS_RESOURCE:
			{
				const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(arg));

				if (type_name) {
					RETVAL_STRING("resource");
					break;
				}
			}
			/* no break */

		default:
			RETVAL_STRING("unknown type");
	}
}

zend_class_entry* zephir_get_internal_ce(const char *class_name, unsigned int class_name_len)
{
    zend_class_entry* temp_ce;

    if ((temp_ce = zend_hash_str_find_ptr(CG(class_table), class_name, class_name_len)) == NULL) {
        zend_error(E_ERROR, "Class '%s' not found", class_name);
        return NULL;
    }

    return temp_ce;
}

/* Declare constants */
int zephir_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value)
{
	zend_string *key;

	if (ce->type == ZEND_INTERNAL_CLASS) {
		key = zend_string_init_interned(name, name_length, 1);
	} else {
		key = zend_string_init(name, name_length, 0);
	}

	zend_declare_class_constant_ex(ce, key, value, ZEND_ACC_PUBLIC, NULL);

	if (ce->type != ZEND_INTERNAL_CLASS) {
		zend_string_release(key);
	}

	return SUCCESS;
}

/**
 * Deep-copies a (request) zval into persistent, immutable memory so it can be
 * stored as a constant on a persistently-registered (internal) class. Only the
 * value kinds that may appear in a Zephir array constant are handled: scalars,
 * strings and (nested) arrays.
 *
 * @see https://github.com/zephir-lang/zephir/issues/2533
 */
static void zephir_persist_constant_zval(zval *dst, zval *src)
{
	switch (Z_TYPE_P(src)) {
		case IS_STRING:
			ZVAL_STR(dst, zend_string_init(Z_STRVAL_P(src), Z_STRLEN_P(src), 1));
			GC_ADD_FLAGS(Z_STR_P(dst), IS_STR_PERSISTENT);
			break;

		case IS_ARRAY: {
			zend_array *ht;
			zend_ulong idx;
			zend_string *key;
			zval *val;

			ht = (zend_array *) pemalloc(sizeof(zend_array), 1);
			zend_hash_init(ht, zend_hash_num_elements(Z_ARRVAL_P(src)), NULL, NULL, 1);

			ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(src), idx, key, val) {
				zval copy;
				zephir_persist_constant_zval(&copy, val);
				if (key) {
					zend_string *pkey = zend_string_init(ZSTR_VAL(key), ZSTR_LEN(key), 1);
					GC_ADD_FLAGS(pkey, IS_STR_PERSISTENT);
					zend_hash_add_new(ht, pkey, &copy);
					zend_string_release(pkey);
				} else {
					zend_hash_index_add_new(ht, idx, &copy);
				}
			} ZEND_HASH_FOREACH_END();

			ZVAL_ARR(dst, ht);
			GC_ADD_FLAGS(ht, IS_ARRAY_IMMUTABLE);
			GC_SET_REFCOUNT(ht, 1);
			/* store as a non-refcounted (immutable) array zval */
			Z_TYPE_INFO_P(dst) = IS_ARRAY;
			break;
		}

		default:
			/* IS_LONG / IS_DOUBLE / IS_TRUE / IS_FALSE / IS_NULL: no allocation */
			ZVAL_COPY_VALUE(dst, src);
			break;
	}
}

int zephir_declare_class_constant_array(zend_class_entry *ce, const char *name, size_t name_length, zval *value)
{
	zval persisted;

	zephir_persist_constant_zval(&persisted, value);
	zval_ptr_dtor(value);

	return zephir_declare_class_constant(ce, name, name_length, &persisted);
}

/**
 * Declares a class property whose default value is an array.
 *
 * Unlike a regular class, a trait cannot rely on the runtime create_object
 * initializer to build an array default: PHP's zend_do_bind_traits() copies a
 * trait's property_info and default zvals into a using class but not its object
 * handlers, so the initializer never runs for a userland class that `use`s the
 * trait. Storing the array as a persistent, immutable (non-refcounted) zval in
 * the ce's default_properties_table makes the engine carry it natively, exactly
 * like a hand-written PHP trait property default.
 *
 * @see https://github.com/zephir-lang/zephir/issues/2607
 */
int zephir_declare_property_array(zend_class_entry *ce, const char *name, size_t name_length, zval *value, int access_type)
{
	zval persisted;

	zephir_persist_constant_zval(&persisted, value);
	zval_ptr_dtor(value);

	zend_declare_property(ce, name, name_length, &persisted, access_type);

	return SUCCESS;
}

/**
 * Declares a class property with a PHP type (issue #2608).
 *
 * Emits the engine's typed-property machinery so Reflection reports the type
 * and PHP enforces it. `type_mask` is a MAY_BE_* bitmask for builtin/array
 * types with MAY_BE_NULL folded in for `?type`. When `class_name` is non-NULL
 * the property is a class type: the name is stored persistently and resolved
 * lazily by the engine, with nullability taken from the MAY_BE_NULL bit.
 *
 * The default zval is made persistent exactly like zephir_declare_property_array
 * so native trait binding and the immutable default_properties_table carry it.
 * A default of IS_UNDEF yields an uninitialized typed property, matching PHP.
 */
zend_property_info *zephir_declare_typed_property(zend_class_entry *ce, const char *name, size_t name_length, zval *value, int access_type, uint32_t type_mask, const char *class_name, size_t class_name_length)
{
	zend_string *key = zend_string_init_interned(name, name_length, 1);
	zval persisted;
	zend_type type;

	if (class_name != NULL) {
		zend_string *cn = zend_string_init(class_name, class_name_length, 1);
		GC_ADD_FLAGS(cn, IS_STR_PERSISTENT);
		type = (zend_type) ZEND_TYPE_INIT_CLASS(cn, (type_mask & MAY_BE_NULL) ? 1 : 0, 0);
	} else {
		type = (zend_type) ZEND_TYPE_INIT_MASK(type_mask);
	}

	zephir_persist_constant_zval(&persisted, value);
	zval_ptr_dtor(value);

	return zend_declare_typed_property(ce, key, &persisted, access_type, NULL, type);
}

zend_property_info *zephir_declare_typed_property_union(zend_class_entry *ce, const char *name, size_t name_length, zval *value, int access_type, uint32_t type_mask, const char **class_names, uint32_t num_classes)
{
	zend_string *key = zend_string_init_interned(name, name_length, 1);
	zval persisted;
	zend_type type;
	uint32_t i;

	if (num_classes == 0) {
		/* Scalar-only union (e.g. int|float|null): a plain OR-ed type mask. */
		type = (zend_type) ZEND_TYPE_INIT_MASK(type_mask);
	} else if (num_classes == 1) {
		/* One class plus optional scalar/null bits (e.g. <Foo>|int, <Foo>|null).
		 * The scalar/null bits ride along in the class type's extra flags. */
		zend_string *cn = zend_string_init(class_names[0], strlen(class_names[0]), 1);
		GC_ADD_FLAGS(cn, IS_STR_PERSISTENT);
		type = (zend_type) ZEND_TYPE_INIT_CLASS(cn, 0, type_mask);
	} else {
		/* Two or more classes (e.g. <A>|<B>|int): a persistent zend_type_list of
		 * class names, with any scalar/null bits kept in the outer union mask.
		 * PHP 8.1+ tags the list with _ZEND_TYPE_UNION_BIT (to distinguish it from
		 * intersection lists); PHP 8.0 has no intersection types and only
		 * _ZEND_TYPE_LIST_BIT, and lacks ZEND_TYPE_INIT_UNION — so build the type
		 * via the portable ZEND_TYPE_INIT_PTR and add the union bit only when it
		 * exists. */
		uint32_t list_kind   = _ZEND_TYPE_LIST_BIT;
		zend_type_list *list = pemalloc(ZEND_TYPE_LIST_SIZE(num_classes), 1);
#ifdef _ZEND_TYPE_UNION_BIT
		list_kind |= _ZEND_TYPE_UNION_BIT;
#endif
		list->num_types = num_classes;
		for (i = 0; i < num_classes; i++) {
			zend_string *cn = zend_string_init(class_names[i], strlen(class_names[i]), 1);
			GC_ADD_FLAGS(cn, IS_STR_PERSISTENT);
			list->types[i] = (zend_type) ZEND_TYPE_INIT_CLASS(cn, 0, 0);
		}
		type = (zend_type) ZEND_TYPE_INIT_PTR(list, list_kind, 0, type_mask);
	}

	zephir_persist_constant_zval(&persisted, value);
	zval_ptr_dtor(value);

	return zend_declare_typed_property(ce, key, &persisted, access_type, NULL, type);
}

int zephir_declare_class_constant_null(zend_class_entry *ce, const char *name, size_t name_length)
{
	zval constant;

	ZVAL_NULL(&constant);
	return zephir_declare_class_constant(ce, name, name_length, &constant);
}

int zephir_declare_class_constant_long(zend_class_entry *ce, const char *name, size_t name_length, zend_long value)
{
	zval constant;

	ZVAL_LONG(&constant, value);
	return zephir_declare_class_constant(ce, name, name_length, &constant);
}

int zephir_declare_class_constant_bool(zend_class_entry *ce, const char *name, size_t name_length, zend_bool value)
{
	zval constant;

	ZVAL_BOOL(&constant, value);
	return zephir_declare_class_constant(ce, name, name_length, &constant);
}

int zephir_declare_class_constant_double(zend_class_entry *ce, const char *name, size_t name_length, double value)
{
	zval constant;

	ZVAL_DOUBLE(&constant, value);
	return zephir_declare_class_constant(ce, name, name_length, &constant);
}

int zephir_declare_class_constant_stringl(zend_class_entry *ce, const char *name, size_t name_length, const char *value, size_t value_length)
{
	zval constant;

	ZVAL_NEW_STR(&constant, zend_string_init(value, value_length, ce->type & ZEND_INTERNAL_CLASS));
	return zephir_declare_class_constant(ce, name, name_length, &constant);
}

int zephir_declare_class_constant_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value)
{
	return zephir_declare_class_constant_stringl(ce, name, name_length, value, strlen(value));
}

/**
 * Check is PHP Version equals to Runtime PHP Version
 */
int zephir_is_php_version(unsigned int id)
{
	int php_major = PHP_MAJOR_VERSION * 10000;
	int php_minor = PHP_MINOR_VERSION * 100;
	int php_release = PHP_RELEASE_VERSION;

	int zep_major = id / 10000;
	int zep_minor = id / 100 - zep_major * 100;
	int zep_release = id - (zep_major * 10000 + zep_minor * 100);

	if (zep_minor == 0)
	{
		php_minor = 0;
	}

	if (zep_release == 0)
	{
		php_release = 0;
	}

	return ((php_major + php_minor + php_release) == id ? 1 : 0);
}

void zephir_get_args(zval *return_value)
{
	zend_execute_data *ex = EG(current_execute_data);
	uint32_t arg_count    = ZEND_CALL_NUM_ARGS(ex);

	array_init_size(return_value, arg_count);
	if (arg_count) {
		uint32_t first_extra_arg = ex->func->op_array.num_args;
		zval *p    = ZEND_CALL_ARG(ex, 1);
		uint32_t i = 0;

		if (arg_count > first_extra_arg) {
			while (i < first_extra_arg) {
				zval *q = p;

				if (Z_TYPE_P(q) != IS_UNDEF) {
					ZVAL_DEREF(q);
					Z_TRY_ADDREF_P(q);
					zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), q);
				}

				++p;
				++i;
			}

			p = ZEND_CALL_VAR_NUM(ex, i);
		}

		while (i < arg_count) {
			zval *q = p;

			if (Z_TYPE_P(q) != IS_UNDEF) {
				ZVAL_DEREF(q);
				Z_TRY_ADDREF_P(q);
				zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), q);
			}

			++p;
			++i;
		}
	}
}

void zephir_get_args_from(zval *return_value, uint32_t skip)
{
	zend_execute_data *ex = EG(current_execute_data);
	uint32_t arg_count    = ZEND_CALL_NUM_ARGS(ex);

	array_init(return_value);

	if (arg_count > skip) {
		uint32_t first_extra_arg = ex->func->op_array.num_args;
		zval *p    = ZEND_CALL_ARG(ex, 1);
		uint32_t i = 0;

		if (arg_count > first_extra_arg) {
			while (i < first_extra_arg) {
				if (i >= skip) {
					zval *q = p;

					if (Z_TYPE_P(q) != IS_UNDEF) {
						ZVAL_DEREF(q);
						Z_TRY_ADDREF_P(q);
						zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), q);
					}
				}

				++p;
				++i;
			}

			p = ZEND_CALL_VAR_NUM(ex, i);
		}

		while (i < arg_count) {
			if (i >= skip) {
				zval *q = p;

				if (Z_TYPE_P(q) != IS_UNDEF) {
					ZVAL_DEREF(q);
					Z_TRY_ADDREF_P(q);
					zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), q);
				}
			}

			++p;
			++i;
		}
	}
}

void zephir_get_arg(zval *return_value, zend_long idx)
{
	zend_execute_data *ex = EG(current_execute_data);
	uint32_t arg_count;
	zval *arg;

	if (UNEXPECTED(idx < 0)) {
		zend_error(E_WARNING, "func_get_arg():  The argument number should be >= 0");
		RETURN_FALSE;
	}

	arg_count = ZEND_CALL_NUM_ARGS(ex);
#if PHP_VERSION_ID >= 80200
	if (zend_forbid_dynamic_call() == FAILURE) {
		RETURN_FALSE;
	}
#else
	if (zend_forbid_dynamic_call("func_get_arg()") == FAILURE) {
		RETURN_FALSE;
	}
#endif

	if (UNEXPECTED((zend_ulong)idx >= arg_count)) {
		zend_error(E_WARNING, "func_get_arg():  Argument " ZEND_LONG_FMT " not passed to function", idx);
		RETURN_FALSE;
	}

	arg = ZEND_CALL_VAR_NUM(ex, idx);

	if (EXPECTED(!Z_ISUNDEF_P(arg))) {
		ZVAL_DEREF(arg);
		ZVAL_COPY(return_value, arg);
		return;
	}

	RETURN_NULL();
}

void zephir_module_init()
{
	/* Though these strings won't be interned in ZTS,
	 * we still benefit from using zend_string* instead of char*
	 * in hash tables
	 */
	i_parent = zend_new_interned_string(zend_string_init(ZEND_STRL("parent"), 1));
	i_static = zend_new_interned_string(zend_string_init(ZEND_STRL("static"), 1));
	i_self   = zend_new_interned_string(zend_string_init(ZEND_STRL("self"), 1));

	zephir_generator_module_init();
}
