
#ifdef HAVE_CONFIG_H
#include "../../ext_config.h"
#endif

#include <php.h>
#include "../../php_ext.h"
#include "../../ext.h"

#include <Zend/zend_operators.h>
#include <Zend/zend_exceptions.h>
#include <Zend/zend_interfaces.h>

#include "kernel/main.h"
#include "kernel/object.h"
#include "kernel/memory.h"
#include "kernel/operators.h"
#include "kernel/fcall.h"
#include "kernel/array.h"


/**
 * This file is part of the Phalcon Framework.
 *
 * (c) Phalcon Team <team@phalcon.io>
 *
 * For the full copyright and license information, please view the LICENSE.txt
 * file that was distributed with this source code.
 */
/**
 * Instances of Phalcon\Db can generate execution profiles
 * on SQL statements sent to the relational database. Profiled
 * information includes execution time in milliseconds.
 * This helps you to identify bottlenecks in your applications.
 *
 * ```php
 * use Phalcon\Db\Profiler;
 * use Phalcon\Events\Event;
 * use Phalcon\Events\Manager;
 *
 * $profiler = new Profiler();
 * $eventsManager = new Manager();
 *
 * $eventsManager->attach(
 *     "db",
 *     function (Event $event, $connection) use ($profiler) {
 *         if ($event->getType() === "beforeQuery") {
 *             $sql = $connection->getSQLStatement();
 *
 *             // Start a profile with the active connection
 *             $profiler->startProfile($sql);
 *         }
 *
 *         if ($event->getType() === "afterQuery") {
 *             // Stop the active profile
 *             $profiler->stopProfile();
 *         }
 *     }
 * );
 *
 * // Set the event manager on the connection
 * $connection->setEventsManager($eventsManager);
 *
 *
 * $sql = "SELECT buyer_name, quantity, product_name
 * FROM buyers LEFT JOIN products ON
 * buyers.pid=products.id";
 *
 * // Execute a SQL statement
 * $connection->query($sql);
 *
 * // Get the last profile in the profiler
 * $profile = $profiler->getLastProfile();
 *
 * echo "SQL Statement: ", $profile->getSQLStatement(), "\n";
 * echo "Start Time: ", $profile->getInitialTime(), "\n";
 * echo "Final Time: ", $profile->getFinalTime(), "\n";
 * echo "Total Elapsed Time: ", $profile->getTotalElapsedSeconds(), "\n";
 * ```
 */
ZEPHIR_INIT_CLASS(Phalcon_Db_Profiler)
{
	ZEPHIR_REGISTER_CLASS(Phalcon\\Db, Profiler, phalcon, db_profiler, phalcon_db_profiler_method_entry, 0);

	/**
	 * Active Item
	 *
	 * @var Item
	 */
	zend_declare_property_null(phalcon_db_profiler_ce, SL("activeProfile"), ZEND_ACC_PROTECTED);
	/**
	 * All the Items in the active profile
	 *
	 * @var Item[]
	 */
	zend_declare_property_null(phalcon_db_profiler_ce, SL("allProfiles"), ZEND_ACC_PROTECTED);
	/**
	 * Maximum number of profiles to retain. 0 (default) keeps the
	 * original unbounded behavior; a positive value drops the oldest
	 * profile FIFO before a new one is appended.
	 *
	 * @var int
	 */
	zend_declare_property_long(phalcon_db_profiler_ce, SL("maxProfiles"), 0, ZEND_ACC_PROTECTED);
	/**
	 * Total time spent by all profiles to complete in nanoseconds
	 *
	 * @var float
	 */
	zend_declare_property_long(phalcon_db_profiler_ce, SL("totalNanoseconds"), 0, ZEND_ACC_PROTECTED);
	return SUCCESS;
}

/**
 * Returns the last profile executed in the profiler
 */
PHP_METHOD(Phalcon_Db_Profiler, getLastProfile)
{

	RETURN_MEMBER(getThis(), "activeProfile");
}

/**
 * Returns the configured maximum number of retained profiles
 * (0 = unlimited)
 */
PHP_METHOD(Phalcon_Db_Profiler, getMaxProfiles)
{

	RETURN_MEMBER_TYPED(getThis(), "maxProfiles", IS_LONG);
}

/**
 * Returns the total number of SQL statements processed
 */
PHP_METHOD(Phalcon_Db_Profiler, getNumberTotalStatements)
{
	zval _0;
	zval *this_ptr = getThis();

	ZVAL_UNDEF(&_0);
	static zend_string *_zephir_prop_0 = NULL;
	if (UNEXPECTED(!_zephir_prop_0)) {
		_zephir_prop_0 = zend_string_init("allProfiles", 11, 1);
	}
	zephir_read_property_cached(&_0, this_ptr, _zephir_prop_0, 647, PH_NOISY_CC | PH_READONLY);
	RETURN_LONG(zephir_fast_count_int(&_0));
}

/**
 * Returns the total time in nanoseconds spent by the profiles
 */
PHP_METHOD(Phalcon_Db_Profiler, getTotalElapsedNanoseconds)
{

	RETURN_MEMBER_TYPED(getThis(), "totalNanoseconds", IS_DOUBLE);
}

/**
 * Returns all the processed profiles
 */
PHP_METHOD(Phalcon_Db_Profiler, getProfiles)
{

	RETURN_MEMBER_TYPED(getThis(), "allProfiles", IS_ARRAY);
}

/**
 * Resets the profiler, cleaning up all the profiles
 */
PHP_METHOD(Phalcon_Db_Profiler, reset)
{
	zval _0;
	zephir_method_globals *ZEPHIR_METHOD_GLOBALS_PTR = NULL;
	zval *this_ptr = getThis();

	ZVAL_UNDEF(&_0);
	static zend_string *_zephir_prop_0 = NULL;
	if (UNEXPECTED(!_zephir_prop_0)) {
		_zephir_prop_0 = zend_string_init("allProfiles", 11, 1);
	}
	ZEPHIR_METHOD_GLOBALS_PTR = pecalloc(1, sizeof(zephir_method_globals), 0);
	zephir_memory_grow_stack(ZEPHIR_METHOD_GLOBALS_PTR, __func__);

	ZEPHIR_INIT_VAR(&_0);
	array_init(&_0);
	zephir_update_property_zval_cached(this_ptr, _zephir_prop_0, 647, &_0);
	RETURN_THIS();
}

/**
 * Sets the maximum number of retained profiles. 0 disables the cap
 * (the default; preserves the original unbounded behavior).
 */
PHP_METHOD(Phalcon_Db_Profiler, setMaxProfiles)
{
	zval *maxProfiles_param = NULL, _0;
	zend_long maxProfiles;
	zval *this_ptr = getThis();

	ZVAL_UNDEF(&_0);
	static zend_string *_zephir_prop_0 = NULL;
	if (UNEXPECTED(!_zephir_prop_0)) {
		_zephir_prop_0 = zend_string_init("maxProfiles", 11, 1);
	}

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_LONG(maxProfiles)
	ZEND_PARSE_PARAMETERS_END();
	zephir_fetch_params_without_memory_grow(1, 0, &maxProfiles_param);
	ZVAL_UNDEF(&_0);
	ZVAL_LONG(&_0, maxProfiles);
	zephir_update_property_zval_cached(this_ptr, _zephir_prop_0, 648, &_0);
	RETURN_THISW();
}

/**
 * Starts the profile of a SQL sentence
 */
PHP_METHOD(Phalcon_Db_Profiler, startProfile)
{
	zephir_method_globals *ZEPHIR_METHOD_GLOBALS_PTR = NULL;
	zend_long ZEPHIR_LAST_CALL_STATUS;
	zval sqlVariables, sqlBindTypes;
	zval sqlStatement_zv, *sqlVariables_param = NULL, *sqlBindTypes_param = NULL, __$true, activeProfile, _0;
	zend_string *sqlStatement = NULL;
	zval *this_ptr = getThis();

	ZVAL_UNDEF(&sqlStatement_zv);
	ZVAL_BOOL(&__$true, 1);
	ZVAL_UNDEF(&activeProfile);
	ZVAL_UNDEF(&_0);
	ZVAL_UNDEF(&sqlVariables);
	ZVAL_UNDEF(&sqlBindTypes);
	static zend_string *_zephir_prop_0 = NULL;
	if (UNEXPECTED(!_zephir_prop_0)) {
		_zephir_prop_0 = zend_string_init("activeProfile", 13, 1);
	}

	ZEND_PARSE_PARAMETERS_START(1, 3)
		Z_PARAM_STR(sqlStatement)
		Z_PARAM_OPTIONAL
		ZEPHIR_Z_PARAM_ARRAY(sqlVariables, sqlVariables_param)
		ZEPHIR_Z_PARAM_ARRAY(sqlBindTypes, sqlBindTypes_param)
	ZEND_PARSE_PARAMETERS_END();
	ZEPHIR_METHOD_GLOBALS_PTR = pecalloc(1, sizeof(zephir_method_globals), 0);
	zephir_memory_grow_stack(ZEPHIR_METHOD_GLOBALS_PTR, __func__);
	if (ZEND_NUM_ARGS() > 1) {
		sqlVariables_param = ZEND_CALL_ARG(execute_data, 2);
	}
	if (ZEND_NUM_ARGS() > 2) {
		sqlBindTypes_param = ZEND_CALL_ARG(execute_data, 3);
	}
	zephir_memory_observe(&sqlStatement_zv);
	ZVAL_STR_COPY(&sqlStatement_zv, sqlStatement);
	if (!sqlVariables_param) {
		ZEPHIR_INIT_VAR(&sqlVariables);
		array_init(&sqlVariables);
	} else {
		zephir_get_arrval(&sqlVariables, sqlVariables_param);
	}
	if (!sqlBindTypes_param) {
		ZEPHIR_INIT_VAR(&sqlBindTypes);
		array_init(&sqlBindTypes);
	} else {
		zephir_get_arrval(&sqlBindTypes, sqlBindTypes_param);
	}
	ZEPHIR_INIT_VAR(&activeProfile);
	object_init_ex(&activeProfile, phalcon_db_profiler_item_ce);
	if (zephir_has_constructor(&activeProfile)) {
		ZEPHIR_CALL_METHOD(NULL, &activeProfile, "__construct", NULL, 0);
		zephir_check_call_status();
	}

	ZEPHIR_CALL_METHOD(NULL, &activeProfile, "setsqlstatement", NULL, 0, &sqlStatement_zv);
	zephir_check_call_status();
	ZEPHIR_CALL_METHOD(NULL, &activeProfile, "setsqlvariables", NULL, 0, &sqlVariables);
	zephir_check_call_status();
	ZEPHIR_CALL_METHOD(NULL, &activeProfile, "setsqlbindtypes", NULL, 0, &sqlBindTypes);
	zephir_check_call_status();
	ZEPHIR_CALL_FUNCTION(&_0, "hrtime", NULL, 0, &__$true);
	zephir_check_call_status();
	ZEPHIR_CALL_METHOD(NULL, &activeProfile, "setinitialtime", NULL, 0, &_0);
	zephir_check_call_status();
	if ((zephir_method_exists_ex(this_ptr, ZEND_STRL("beforestartprofile")) == SUCCESS)) {
		ZEPHIR_CALL_METHOD(NULL, this_ptr, "beforestartprofile", NULL, 0, &activeProfile);
		zephir_check_call_status();
	}
	zephir_update_property_zval_cached(this_ptr, _zephir_prop_0, 649, &activeProfile);
	RETURN_THIS();
}

/**
 * Stops the active profile
 */
PHP_METHOD(Phalcon_Db_Profiler, stopProfile)
{
	zend_bool _2;
	zephir_method_globals *ZEPHIR_METHOD_GLOBALS_PTR = NULL;
	zend_long ZEPHIR_LAST_CALL_STATUS;
	zval __$true, activeProfile, firstKey, _0, _1, _3, _4, _7, _8, _9, _5$$3, _6$$4;
	zval *this_ptr = getThis();

	ZVAL_BOOL(&__$true, 1);
	ZVAL_UNDEF(&activeProfile);
	ZVAL_UNDEF(&firstKey);
	ZVAL_UNDEF(&_0);
	ZVAL_UNDEF(&_1);
	ZVAL_UNDEF(&_3);
	ZVAL_UNDEF(&_4);
	ZVAL_UNDEF(&_7);
	ZVAL_UNDEF(&_8);
	ZVAL_UNDEF(&_9);
	ZVAL_UNDEF(&_5$$3);
	ZVAL_UNDEF(&_6$$4);
	static zend_string *_zephir_prop_0 = NULL;
	static zend_string *_zephir_prop_1 = NULL;
	static zend_string *_zephir_prop_2 = NULL;
	static zend_string *_zephir_prop_3 = NULL;
	if (UNEXPECTED(!_zephir_prop_0)) {
		_zephir_prop_0 = zend_string_init("activeProfile", 13, 1);
	}
	if (UNEXPECTED(!_zephir_prop_1)) {
		_zephir_prop_1 = zend_string_init("maxProfiles", 11, 1);
	}
	if (UNEXPECTED(!_zephir_prop_2)) {
		_zephir_prop_2 = zend_string_init("allProfiles", 11, 1);
	}
	if (UNEXPECTED(!_zephir_prop_3)) {
		_zephir_prop_3 = zend_string_init("totalNanoseconds", 16, 1);
	}
	ZEPHIR_METHOD_GLOBALS_PTR = pecalloc(1, sizeof(zephir_method_globals), 0);
	zephir_memory_grow_stack(ZEPHIR_METHOD_GLOBALS_PTR, __func__);

	zephir_read_property_cached(&_0, this_ptr, _zephir_prop_0, 649, PH_NOISY_CC | PH_READONLY);
	ZEPHIR_CPY_WRT(&activeProfile, &_0);
	ZEPHIR_CALL_FUNCTION(&_1, "hrtime", NULL, 0, &__$true);
	zephir_check_call_status();
	ZEPHIR_CALL_METHOD(NULL, &activeProfile, "setfinaltime", NULL, 0, &_1);
	zephir_check_call_status();
	zephir_read_property_cached(&_0, this_ptr, _zephir_prop_1, 648, PH_NOISY_CC | PH_READONLY);
	_2 = ZEPHIR_GT_LONG(&_0, 0);
	if (_2) {
		zephir_read_property_cached(&_3, this_ptr, _zephir_prop_2, 647, PH_NOISY_CC | PH_READONLY);
		zephir_read_property_cached(&_4, this_ptr, _zephir_prop_1, 648, PH_NOISY_CC | PH_READONLY);
		_2 = ZEPHIR_LE_LONG(&_4, zephir_fast_count_int(&_3));
	}
	if (_2) {
		zephir_read_property_cached(&_5$$3, this_ptr, _zephir_prop_2, 647, PH_NOISY_CC | PH_READONLY);
		ZEPHIR_CALL_FUNCTION(&firstKey, "array_key_first", NULL, 18, &_5$$3);
		zephir_check_call_status();
		if (Z_TYPE_P(&firstKey) != IS_NULL) {
			zephir_unset_property_array(this_ptr, ZEND_STRL("allProfiles"), &firstKey);
			zephir_read_property_cached(&_6$$4, this_ptr, _zephir_prop_2, 647, PH_NOISY_CC | PH_READONLY);
			zephir_array_unset(&_6$$4, &firstKey, PH_SEPARATE);
		}
	}
	zephir_read_property_cached(&_7, this_ptr, _zephir_prop_3, 650, PH_NOISY_CC | PH_READONLY);
	ZEPHIR_CALL_METHOD(&_8, &activeProfile, "gettotalelapsednanoseconds", NULL, 0);
	zephir_check_call_status();
	ZEPHIR_INIT_VAR(&_9);
	zephir_add_function(&_9, &_7, &_8);
	zephir_update_property_zval_cached(this_ptr, _zephir_prop_3, 650, &_9);
	zephir_update_property_array_append(this_ptr, SL("allProfiles"), &activeProfile);
	if ((zephir_method_exists_ex(this_ptr, ZEND_STRL("afterendprofile")) == SUCCESS)) {
		ZEPHIR_CALL_METHOD(NULL, this_ptr, "afterendprofile", NULL, 0, &activeProfile);
		zephir_check_call_status();
	}
	RETURN_THIS();
}

/**
 * Returns the total time in milliseconds spent by the profiles
 */
PHP_METHOD(Phalcon_Db_Profiler, getTotalElapsedMilliseconds)
{
	zval _0;
	zephir_method_globals *ZEPHIR_METHOD_GLOBALS_PTR = NULL;
	zend_long ZEPHIR_LAST_CALL_STATUS;
	zval *this_ptr = getThis();

	ZVAL_UNDEF(&_0);
	ZEPHIR_METHOD_GLOBALS_PTR = pecalloc(1, sizeof(zephir_method_globals), 0);
	zephir_memory_grow_stack(ZEPHIR_METHOD_GLOBALS_PTR, __func__);

	ZEPHIR_CALL_METHOD(&_0, this_ptr, "gettotalelapsednanoseconds", NULL, 0);
	zephir_check_call_status();
	RETURN_MM_DOUBLE(zephir_safe_div_zval_long(&_0, 1000000));
}

/**
 * Returns the total time in seconds spent by the profiles
 */
PHP_METHOD(Phalcon_Db_Profiler, getTotalElapsedSeconds)
{
	zval _0;
	zephir_method_globals *ZEPHIR_METHOD_GLOBALS_PTR = NULL;
	zend_long ZEPHIR_LAST_CALL_STATUS;
	zval *this_ptr = getThis();

	ZVAL_UNDEF(&_0);
	ZEPHIR_METHOD_GLOBALS_PTR = pecalloc(1, sizeof(zephir_method_globals), 0);
	zephir_memory_grow_stack(ZEPHIR_METHOD_GLOBALS_PTR, __func__);

	ZEPHIR_CALL_METHOD(&_0, this_ptr, "gettotalelapsedmilliseconds", NULL, 0);
	zephir_check_call_status();
	RETURN_MM_DOUBLE(zephir_safe_div_zval_long(&_0, 1000));
}

