/*
   +----------------------------------------------------------------------+
   | Xdebug                                                               |
   +----------------------------------------------------------------------+
   | Copyright (c) 2002-2023 Derick Rethans                               |
   +----------------------------------------------------------------------+
   | This source file is subject to version 1.01 of the Xdebug license,   |
   | that is bundled with this package in the file LICENSE, and is        |
   | available at through the world-wide-web at                           |
   | https://xdebug.org/license.php                                       |
   | If you did not receive a copy of the Xdebug license and are unable   |
   | to obtain it through the world-wide-web, please send a note to       |
   | derick@xdebug.org so we can mail you a copy immediately.             |
   +----------------------------------------------------------------------+
 */

#include "lib/php-header.h"
#include "php_xdebug.h"

#include "develop_private.h"
#include "stack.h"

#include "lib/lib_private.h"
#include "lib/var_export_html.h"
#include "lib/var_export_line.h"
#include "lib/var_export_text.h"

ZEND_EXTERN_MODULE_GLOBALS(xdebug)

#define MODE_MUST_BE(m,n) \
	if (!XDEBUG_MODE_IS((m))) { \
		php_error(E_WARNING, "Function must be enabled in php.ini by setting 'xdebug.mode' to '%s'", (n)); \
		return; \
	}

/* {{{ proto void xdebug_var_dump(mixed var [, ...] )
   Outputs a fancy string representation of a variable */
PHP_FUNCTION(xdebug_var_dump)
{
	zval       *args;
	int         argc;
	int         i;
	xdebug_str *val;

	argc = ZEND_NUM_ARGS();

	args = safe_emalloc(argc, sizeof(zval), 0);
	if (ZEND_NUM_ARGS() == 0 || zend_get_parameters_array_ex(argc, args) == FAILURE) {
		efree(args);
		WRONG_PARAM_COUNT;
	}

	for (i = 0; i < argc; i++) {
		if (PG(html_errors)) {
			val = xdebug_get_zval_value_html(NULL, (zval*) &args[i], 0, NULL);
			PHPWRITE(val->d, val->l);
			xdebug_str_free(val);
		}
		else if ((XINI_DEV(cli_color) == 1 && xdebug_is_output_tty()) || (XINI_DEV(cli_color) == 2)) {
			val = xdebug_get_zval_value_ansi((zval*) &args[i], 0, NULL);
			PHPWRITE(val->d, val->l);
			xdebug_str_free(val);
		}
		else {
			val = xdebug_get_zval_value_text((zval*) &args[i], 0, NULL);
			PHPWRITE(val->d, val->l);
			xdebug_str_free(val);
		}
	}

	efree(args);
}
/* }}} */

/* {{{ proto void xdebug_debug_zval(mixed var [, ...] )
   Outputs a fancy string representation of a variable */
PHP_FUNCTION(xdebug_debug_zval)
{
	zval       *args;
	int         argc;
	int         i;
	xdebug_str *val;

	argc = ZEND_NUM_ARGS();

	args = safe_emalloc(argc, sizeof(zval), 0);
	if (ZEND_NUM_ARGS() == 0 || zend_get_parameters_array_ex(argc, args) == FAILURE) {
		efree(args);
		WRONG_PARAM_COUNT;
	}

	if (!(ZEND_CALL_INFO(EG(current_execute_data)->prev_execute_data) & ZEND_CALL_HAS_SYMBOL_TABLE)) {
		zend_rebuild_symbol_table();
	}

	for (i = 0; i < argc; i++) {
		if (Z_TYPE(args[i]) == IS_STRING) {
			zval debugzval;
			xdebug_str *tmp_name;

			xdebug_lib_set_active_symbol_table(EG(current_execute_data)->prev_execute_data->symbol_table);
			xdebug_lib_set_active_data(EG(current_execute_data)->prev_execute_data);

			tmp_name = xdebug_str_create(Z_STRVAL(args[i]), Z_STRLEN(args[i]));
			xdebug_get_php_symbol(&debugzval, tmp_name);
			xdebug_str_free(tmp_name);

			/* Reduce refcount for dumping */
			Z_TRY_DELREF(debugzval);

			php_printf("%s: ", Z_STRVAL(args[i]));
			if (Z_TYPE(debugzval) != IS_UNDEF) {
				if (PG(html_errors)) {
					val = xdebug_get_zval_value_html(NULL, &debugzval, 1, NULL);
					PHPWRITE(val->d, val->l);
				}
				else if ((XINI_DEV(cli_color) == 1 && xdebug_is_output_tty()) || (XINI_DEV(cli_color) == 2)) {
					val = xdebug_get_zval_value_ansi(&debugzval, 1, NULL);
					PHPWRITE(val->d, val->l);
				}
				else {
					val = xdebug_get_zval_value_line(&debugzval, 1, NULL);
					PHPWRITE(val->d, val->l);
				}
				xdebug_str_free(val);
				PHPWRITE("\n", 1);
			} else {
				PHPWRITE("no such symbol\n", 15);
			}

			/* Restore original refcount */
			Z_TRY_ADDREF(debugzval);
			zval_ptr_dtor_nogc(&debugzval);
		}
	}

	efree(args);
}
/* }}} */

/* {{{ proto void xdebug_debug_zval_stdout(mixed var [, ...] )
   Outputs a fancy string representation of a variable */
PHP_FUNCTION(xdebug_debug_zval_stdout)
{
	zval   *args;
	int     argc;
	int     i;

	argc = ZEND_NUM_ARGS();

	args = safe_emalloc(argc, sizeof(zval), 0);
	if (ZEND_NUM_ARGS() == 0 || zend_get_parameters_array_ex(argc, args) == FAILURE) {
		efree(args);
		WRONG_PARAM_COUNT;
	}

	if (!(ZEND_CALL_INFO(EG(current_execute_data)->prev_execute_data) & ZEND_CALL_HAS_SYMBOL_TABLE)) {
		zend_rebuild_symbol_table();
	}

	for (i = 0; i < argc; i++) {
		if (Z_TYPE(args[i]) == IS_STRING) {
			zval        debugzval;
			xdebug_str *tmp_name;
			xdebug_str *val;

			xdebug_lib_set_active_symbol_table(EG(current_execute_data)->prev_execute_data->symbol_table);
			xdebug_lib_set_active_data(EG(current_execute_data)->prev_execute_data);

			tmp_name = xdebug_str_create(Z_STRVAL(args[i]), Z_STRLEN(args[i]));
			xdebug_get_php_symbol(&debugzval, tmp_name);
			xdebug_str_free(tmp_name);

			/* Reduce refcount for dumping */
			Z_TRY_DELREF(debugzval);

			printf("%s: ", Z_STRVAL(args[i]));
			if (Z_TYPE(debugzval) != IS_UNDEF) {
				val = xdebug_get_zval_value_line(&debugzval, 1, NULL);
				printf("%s(%zd)", val->d, val->l);
				xdebug_str_free(val);
				printf("\n");
			} else {
				printf("no such symbol\n\n");
			}

			/* Restore original refcount */
			Z_TRY_ADDREF(debugzval);
			zval_ptr_dtor_nogc(&debugzval);
		}
	}

	efree(args);
}
/* }}} */

PHP_FUNCTION(xdebug_start_error_collection)
{
	MODE_MUST_BE(XDEBUG_MODE_DEVELOP, "develop");

	if (XG_LIB(do_collect_errors) == 1) {
		php_error(E_NOTICE, "Error collection was already started");
	}

	XG_LIB(do_collect_errors) = 1;
}

PHP_FUNCTION(xdebug_stop_error_collection)
{
	MODE_MUST_BE(XDEBUG_MODE_DEVELOP, "develop");

	if (XG_LIB(do_collect_errors) == 0) {
		php_error(E_NOTICE, "Error collection was not started");
	}

	XG_LIB(do_collect_errors) = 0;
}

PHP_FUNCTION(xdebug_memory_usage)
{
	RETURN_LONG(zend_memory_usage(0));
}

PHP_FUNCTION(xdebug_peak_memory_usage)
{
	RETURN_LONG(zend_memory_peak_usage(0));
}

PHP_FUNCTION(xdebug_time_index)
{
	if (!XDEBUG_MODE_IS((XDEBUG_MODE_DEVELOP))) {
		php_error(E_WARNING, "Function must be enabled in php.ini by setting 'xdebug.mode' to 'develop'");
		RETURN_DOUBLE(0.0);
	}

	RETURN_DOUBLE(XDEBUG_SECONDS_SINCE_START(xdebug_get_nanotime()));
}

/* {{{ proto void xdebug_print_function_stack([string message [, int options])
   Displays a stack trace */
PHP_FUNCTION(xdebug_print_function_stack)
{
	char *message = NULL;
	size_t message_len;
	function_stack_entry *fse;
	char *tmp;
	zend_long options = 0;

	MODE_MUST_BE(XDEBUG_MODE_DEVELOP, "develop");

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|sl", &message, &message_len, &options) == FAILURE) {
		return;
	}

	fse = xdebug_get_stack_frame(0);
	if (message) {
		tmp = xdebug_get_printable_stack(PG(html_errors), 0, message, ZSTR_VAL(fse->filename), fse->lineno, !(options & XDEBUG_STACK_NO_DESC));
	} else {
		tmp = xdebug_get_printable_stack(PG(html_errors), 0, "user triggered", ZSTR_VAL(fse->filename), fse->lineno, !(options & XDEBUG_STACK_NO_DESC));
	}
	php_printf("%s", tmp);
	xdfree(tmp);
}
/* }}} */

/* {{{ proto string xdebug_call_class()
   Returns the name of the calling class */
PHP_FUNCTION(xdebug_call_class)
{
	function_stack_entry *fse;
	zend_long depth = 2;

	MODE_MUST_BE(XDEBUG_MODE_DEVELOP, "develop");

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &depth) == FAILURE) {
		return;
	}

	fse = xdebug_get_stack_frame(depth);
	if (!fse) {
		return;
	}

	if (!fse->function.object_class) {
		RETURN_FALSE;
	}

	RETURN_STR_COPY(fse->function.object_class);
}
/* }}} */

/* {{{ proto string xdebug_call_function()
   Returns the function name from which the current function was called from. */
PHP_FUNCTION(xdebug_call_function)
{
	function_stack_entry *fse;
	zend_long depth = 2;

	MODE_MUST_BE(XDEBUG_MODE_DEVELOP, "develop");

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &depth) == FAILURE) {
		return;
	}

	fse = xdebug_get_stack_frame(depth);
	if (!fse) {
		return;
	}

	if (!fse->function.function) {
		RETURN_FALSE;
	}

	RETURN_STR_COPY(fse->function.function);
}
/* }}} */

/* {{{ proto int xdebug_call_line()
   Returns the line number where the current function was called from. */
PHP_FUNCTION(xdebug_call_line)
{
	function_stack_entry *fse;
	zend_long depth = 2;

	MODE_MUST_BE(XDEBUG_MODE_DEVELOP, "develop");

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &depth) == FAILURE) {
		return;
	}

	fse = xdebug_get_stack_frame(depth);
	if (!fse) {
		return;
	}

	RETURN_LONG(fse->lineno);
}
/* }}} */

/* {{{ proto string xdebug_call_file()
   Returns the filename where the current function was called from. */
PHP_FUNCTION(xdebug_call_file)
{
	function_stack_entry *fse;
	zend_long depth = 2;

	MODE_MUST_BE(XDEBUG_MODE_DEVELOP, "develop");

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &depth) == FAILURE) {
		return;
	}

	fse = xdebug_get_stack_frame(depth);
	if (!fse) {
		return;
	}

	RETURN_STR_COPY(fse->filename);
}
/* }}} */

PHP_FUNCTION(xdebug_get_collected_errors)
{
	xdebug_llist_element *le;
	char                 *string;
	zend_bool             clear = 0;

	MODE_MUST_BE(XDEBUG_MODE_DEVELOP, "develop");

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &clear) == FAILURE) {
		return;
	}

	array_init(return_value);
	for (le = XDEBUG_LLIST_HEAD(XG_DEV(collected_errors)); le != NULL; le = XDEBUG_LLIST_NEXT(le))	{
		string = XDEBUG_LLIST_VALP(le);
		add_next_index_string(return_value, string);
	}

	if (!clear) {
		return;
	}

	xdebug_llist_destroy(XG_DEV(collected_errors), NULL);
	XG_DEV(collected_errors) = xdebug_llist_alloc(xdebug_llist_string_dtor);
}
