
#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/memory.h"
#include "kernel/object.h"
#include "kernel/fcall.h"
#include "kernel/exception.h"
#include "kernel/operators.h"
#include "kernel/array.h"
#include "kernel/hash.h"

ZEPHIR_INIT_CLASS(Accelleratii_Component_Stopwatch_StopwatchEvent) {

	ZEPHIR_REGISTER_CLASS(Accelleratii\\Component\\Stopwatch, StopwatchEvent, accelleratii, component_stopwatch_stopwatchevent, accelleratii_component_stopwatch_stopwatchevent_method_entry, 0);

	/**
	 * @var StopwatchPeriod[]
	 */
	zend_declare_property_null(accelleratii_component_stopwatch_stopwatchevent_ce, SL("periods"), ZEND_ACC_PRIVATE TSRMLS_CC);

	/**
	 * @var float
	 */
	zend_declare_property_null(accelleratii_component_stopwatch_stopwatchevent_ce, SL("origin"), ZEND_ACC_PRIVATE TSRMLS_CC);

	/**
	 * @var string
	 */
	zend_declare_property_null(accelleratii_component_stopwatch_stopwatchevent_ce, SL("category"), ZEND_ACC_PRIVATE TSRMLS_CC);

	/**
	 * @var float[]
	 */
	zend_declare_property_null(accelleratii_component_stopwatch_stopwatchevent_ce, SL("started"), ZEND_ACC_PRIVATE TSRMLS_CC);

	return SUCCESS;

}

/**
 * Constructor.
 *
 * @param float       origin   The origin time in milliseconds
 * @param string|null category The event category or null to use the default
 *
 * @throws \InvalidArgumentException When the raw time is not valid
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, __construct) {

	zephir_nts_static zephir_fcall_cache_entry *_3 = NULL;
	int ZEPHIR_LAST_CALL_STATUS;
	zval *origin, *category = NULL, *_0, *_1, *_2 = NULL, *_4 = NULL;

	ZEPHIR_MM_GROW();
	zephir_fetch_params(1, 1, 1, &origin, &category);

	if (!category) {
		category = ZEPHIR_GLOBAL(global_null);
	}


	ZEPHIR_INIT_VAR(_0);
	array_init(_0);
	zephir_update_property_this(this_ptr, SL("started"), _0 TSRMLS_CC);
	ZEPHIR_INIT_VAR(_1);
	array_init(_1);
	zephir_update_property_this(this_ptr, SL("periods"), _1 TSRMLS_CC);
	ZEPHIR_CALL_METHOD(&_2, this_ptr, "formattime", &_3, origin);
	zephir_check_call_status();
	zephir_update_property_this(this_ptr, SL("origin"), _2 TSRMLS_CC);
	ZEPHIR_INIT_VAR(_4);
	if (Z_TYPE_P(category) == IS_STRING) {
		ZEPHIR_CPY_WRT(_4, category);
	} else {
		ZEPHIR_INIT_NVAR(_4);
		ZVAL_STRING(_4, "default", 1);
	}
	zephir_update_property_this(this_ptr, SL("category"), _4 TSRMLS_CC);
	ZEPHIR_MM_RESTORE();

}

/**
 * Gets the category.
 *
 * @return string The category
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, getCategory) {


	RETURN_MEMBER(this_ptr, "category");

}

/**
 * Gets the origin.
 *
 * @return float The origin in milliseconds
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, getOrigin) {


	RETURN_MEMBER(this_ptr, "origin");

}

/**
 * Starts a new event period.
 *
 * @return StopwatchEvent The event
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, start) {

	int ZEPHIR_LAST_CALL_STATUS;
	zval *_0 = NULL;

	ZEPHIR_MM_GROW();

	ZEPHIR_CALL_METHOD(&_0, this_ptr, "getnow",  NULL);
	zephir_check_call_status();
	zephir_update_property_array_append(this_ptr, SL("started"), _0 TSRMLS_CC);
	RETURN_THIS();

}

/**
 * Stops the last started event period.
 *
 * @throws \LogicException When start wasn't called before stopping
 *
 * @return StopwatchEvent The event
 *
 * @throws \LogicException When stop() is called without a matching call to start()
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, stop) {

	int ZEPHIR_LAST_CALL_STATUS;
	zephir_nts_static zephir_fcall_cache_entry *_4 = NULL;
	zval *_0, *_1, *_2, *_3 = NULL, *_5 = NULL;

	ZEPHIR_MM_GROW();

	_0 = zephir_fetch_nproperty_this(this_ptr, SL("started"), PH_NOISY_CC);
	if (!zephir_fast_count_int(_0 TSRMLS_CC)) {
		ZEPHIR_THROW_EXCEPTION_DEBUG_STR(spl_ce_LogicException, "stop() called but start() has not been called before.", "accelleratii/component/stopwatch/stopwatchevent.zep", 104);
		return;
	}
	ZEPHIR_INIT_VAR(_1);
	object_init_ex(_1, accelleratii_component_stopwatch_stopwatchperiod_ce);
	_2 = zephir_fetch_nproperty_this(this_ptr, SL("started"), PH_NOISY_CC);
	Z_SET_ISREF_P(_2);
	ZEPHIR_CALL_FUNCTION(&_3, "array_pop", &_4, _2);
	Z_UNSET_ISREF_P(_2);
	zephir_check_call_status();
	ZEPHIR_CALL_METHOD(&_5, this_ptr, "getnow",  NULL);
	zephir_check_call_status();
	ZEPHIR_CALL_METHOD(NULL, _1, "__construct", NULL, _3, _5);
	zephir_check_call_status();
	zephir_update_property_array_append(this_ptr, SL("periods"), _1 TSRMLS_CC);
	RETURN_THIS();

}

/**
 * Checks if the event was started
 *
 * @return bool
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, isStarted) {

	zval *_0;

	ZEPHIR_MM_GROW();

	ZEPHIR_OBS_VAR(_0);
	zephir_read_property_this(&_0, this_ptr, SL("started"), PH_NOISY_CC);
	RETURN_MM_BOOL(!ZEPHIR_IS_EMPTY(_0));

}

/**
 * Stops the current period and then starts a new one.
 *
 * @return StopwatchEvent The event
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, lap) {

	int ZEPHIR_LAST_CALL_STATUS;
	zval *_0 = NULL;

	ZEPHIR_MM_GROW();

	ZEPHIR_CALL_METHOD(&_0, this_ptr, "stop",  NULL);
	zephir_check_call_status();
	ZEPHIR_RETURN_CALL_METHOD(_0, "start", NULL);
	zephir_check_call_status();
	RETURN_MM();

}

/**
 * Stops all non already stopped periods.
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, ensureStopped) {

	zephir_fcall_cache_entry *_1 = NULL;
	int ZEPHIR_LAST_CALL_STATUS;
	zval *_0;

	ZEPHIR_MM_GROW();

	while (1) {
		_0 = zephir_fetch_nproperty_this(this_ptr, SL("started"), PH_NOISY_CC);
		if (!(zephir_fast_count_int(_0 TSRMLS_CC))) {
			break;
		}
		ZEPHIR_CALL_METHOD(NULL, this_ptr, "stop", &_1);
		zephir_check_call_status();
	}
	ZEPHIR_MM_RESTORE();

}

/**
 * Gets all event periods.
 *
 * @return StopwatchPeriod[] An array of StopwatchPeriod instances
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, getPeriods) {


	RETURN_MEMBER(this_ptr, "periods");

}

/**
 * Gets the relative time of the start of the first period.
 *
 * @return int     The time (in milliseconds)
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, getStartTime) {

	int ZEPHIR_LAST_CALL_STATUS;
	zval *_0 = NULL, *_1, *_2, *_3;

	ZEPHIR_MM_GROW();

	ZEPHIR_INIT_VAR(_0);
	_1 = zephir_fetch_nproperty_this(this_ptr, SL("periods"), PH_NOISY_CC);
	if (zephir_array_isset_long(_1, 0)) {
		_2 = zephir_fetch_nproperty_this(this_ptr, SL("periods"), PH_NOISY_CC);
		zephir_array_fetch_long(&_3, _2, 0, PH_NOISY | PH_READONLY, "accelleratii/component/stopwatch/stopwatchevent.zep", 159 TSRMLS_CC);
		ZEPHIR_CALL_METHOD(&_0, _3, "getstarttime",  NULL);
		zephir_check_call_status();
	} else {
		ZVAL_LONG(_0, 0);
	}
	RETURN_CCTOR(_0);

}

/**
 * Gets the relative time of the end of the last period.
 *
 * @return int     The time (in milliseconds)
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, getEndTime) {

	zval *_0, *_1 = NULL, *_2, *_3;
	int count, ZEPHIR_LAST_CALL_STATUS;

	ZEPHIR_MM_GROW();

	_0 = zephir_fetch_nproperty_this(this_ptr, SL("periods"), PH_NOISY_CC);
	count = zephir_fast_count_int(_0 TSRMLS_CC);
	ZEPHIR_INIT_VAR(_1);
	if (count) {
		_2 = zephir_fetch_nproperty_this(this_ptr, SL("periods"), PH_NOISY_CC);
		zephir_array_fetch_long(&_3, _2, (count - 1), PH_NOISY | PH_READONLY, "accelleratii/component/stopwatch/stopwatchevent.zep", 173 TSRMLS_CC);
		ZEPHIR_CALL_METHOD(&_1, _3, "getendtime",  NULL);
		zephir_check_call_status();
	} else {
		ZVAL_LONG(_1, 0);
	}
	RETURN_CCTOR(_1);

}

/**
 * Gets the duration of the events (including all periods).
 *
 * @return int     The duration (in milliseconds)
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, getDuration) {

	HashTable *_11;
	HashPosition _10;
	zephir_fcall_cache_entry *_8 = NULL, *_9 = NULL;
	zend_bool _1;
	int stopped, left, total = 0, index, i, _2, _3, ZEPHIR_LAST_CALL_STATUS;
	zval *periods = NULL, *period = NULL, *_0, *_4 = NULL, *_5, *_6, *_7 = NULL, **_12;

	ZEPHIR_MM_GROW();

	_0 = zephir_fetch_nproperty_this(this_ptr, SL("periods"), PH_NOISY_CC);
	ZEPHIR_CPY_WRT(periods, _0);
	stopped = zephir_fast_count_int(periods TSRMLS_CC);
	_0 = zephir_fetch_nproperty_this(this_ptr, SL("started"), PH_NOISY_CC);
	left = ((zephir_fast_count_int(_0 TSRMLS_CC) - stopped) - 1);
	_3 = left;
	_2 = 0;
	_1 = 0;
	if (_2 <= _3) {
		while (1) {
			if (_1) {
				_2++;
				if (!(_2 <= _3)) {
					break;
				}
			} else {
				_1 = 1;
			}
			i = _2;
			index = (stopped + i);
			ZEPHIR_INIT_LNVAR(_4);
			object_init_ex(_4, accelleratii_component_stopwatch_stopwatchperiod_ce);
			_5 = zephir_fetch_nproperty_this(this_ptr, SL("started"), PH_NOISY_CC);
			zephir_array_fetch_long(&_6, _5, index, PH_NOISY | PH_READONLY, "accelleratii/component/stopwatch/stopwatchevent.zep", 194 TSRMLS_CC);
			ZEPHIR_CALL_METHOD(&_7, this_ptr, "getnow",  &_8);
			zephir_check_call_status();
			ZEPHIR_CALL_METHOD(NULL, _4, "__construct", &_9, _6, _7);
			zephir_check_call_status();
			zephir_array_append(&periods, _4, PH_SEPARATE, "accelleratii/component/stopwatch/stopwatchevent.zep", 194);
		}
	}
	zephir_is_iterable(periods, &_11, &_10, 0, 0, "accelleratii/component/stopwatch/stopwatchevent.zep", 201);
	for (
	  ; zephir_hash_get_current_data_ex(_11, (void**) &_12, &_10) == SUCCESS
	  ; zephir_hash_move_forward_ex(_11, &_10)
	) {
		ZEPHIR_GET_HVALUE(period, _12);
		ZEPHIR_CALL_METHOD(&_7, period, "getduration",  NULL);
		zephir_check_call_status();
		total += zephir_get_numberval(_7);
	}
	RETURN_MM_LONG(total);

}

/**
 * Gets the max memory usage of all periods.
 *
 * @return int     The memory usage (in bytes)
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, getMemory) {

	int ZEPHIR_LAST_CALL_STATUS;
	HashTable *_2;
	HashPosition _1;
	zval *period = NULL, *memory = NULL, *_0, **_3, *_4 = NULL;

	ZEPHIR_MM_GROW();
	ZEPHIR_INIT_VAR(memory);
	ZVAL_LONG(memory, 0);

	_0 = zephir_fetch_nproperty_this(this_ptr, SL("periods"), PH_NOISY_CC);
	zephir_is_iterable(_0, &_2, &_1, 0, 0, "accelleratii/component/stopwatch/stopwatchevent.zep", 219);
	for (
	  ; zephir_hash_get_current_data_ex(_2, (void**) &_3, &_1) == SUCCESS
	  ; zephir_hash_move_forward_ex(_2, &_1)
	) {
		ZEPHIR_GET_HVALUE(period, _3);
		ZEPHIR_CALL_METHOD(&_4, period, "getmemory",  NULL);
		zephir_check_call_status();
		if (ZEPHIR_GT(_4, memory)) {
			ZEPHIR_CALL_METHOD(&memory, period, "getmemory",  NULL);
			zephir_check_call_status();
		}
	}
	RETURN_CCTOR(memory);

}

/**
 * Return the current time relative to origin.
 *
 * @return float Time in ms
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, getNow) {

	int ZEPHIR_LAST_CALL_STATUS;
	zephir_nts_static zephir_fcall_cache_entry *_1 = NULL, *_4 = NULL;
	zval *_0 = NULL, *_2, *_3;

	ZEPHIR_MM_GROW();

	ZEPHIR_CALL_FUNCTION(&_0, "microtime", &_1, ZEPHIR_GLOBAL(global_true));
	zephir_check_call_status();
	_2 = zephir_fetch_nproperty_this(this_ptr, SL("origin"), PH_NOISY_CC);
	ZEPHIR_INIT_VAR(_3);
	ZVAL_LONG(_3, ((zephir_get_numberval(_0) * 1000) - zephir_get_numberval(_2)));
	ZEPHIR_RETURN_CALL_METHOD(this_ptr, "formattime", &_4, _3);
	zephir_check_call_status();
	RETURN_MM();

}

/**
 * Formats a time.
 *
 * @param int|float     time A raw time
 *
 * @return float The formatted time
 *
 * @throws \InvalidArgumentException When the raw time is not valid
 */
PHP_METHOD(Accelleratii_Component_Stopwatch_StopwatchEvent, formatTime) {

	int ZEPHIR_LAST_CALL_STATUS;
	zephir_nts_static zephir_fcall_cache_entry *_1 = NULL;
	zval *time, _0;

	ZEPHIR_MM_GROW();
	zephir_fetch_params(1, 1, 0, &time);



	if (!zephir_is_numeric(time)) {
		ZEPHIR_THROW_EXCEPTION_DEBUG_STR(spl_ce_InvalidArgumentException, "The time must be a numerical value", "accelleratii/component/stopwatch/stopwatchevent.zep", 244);
		return;
	}
	ZEPHIR_SINIT_VAR(_0);
	ZVAL_LONG(&_0, 1);
	ZEPHIR_RETURN_CALL_FUNCTION("round", &_1, time, &_0);
	zephir_check_call_status();
	RETURN_MM();

}

