/*
  +----------------------------------------------------------------------+
  | PHP Version 5                                                        |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2013 The PHP Group                                |
  +----------------------------------------------------------------------+
  | http://www.opensource.org/licenses/mit-license.php  MIT License      |
  +----------------------------------------------------------------------+
  | Author: Jani Taskinen <jani.taskinen@iki.fi>                         |
  | Author: Patrick Reilly <preilly@php.net>                             |
  +----------------------------------------------------------------------+
*/

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

extern "C" {
#include "php.h"
#include "ext/date/php_date.h"
#include "ext/standard/php_string.h"
#include "zend_interfaces.h"
#include "zend_closures.h"
#include "ext/spl/spl_exceptions.h"
#include "zend_exceptions.h"
}

#include "php_v8js_macros.h"
#include "v8js_v8.h"
#include "v8js_exceptions.h"

static void v8js_timer_interrupt_handler(v8::Isolate *isolate, void *data) { /* {{{ */
#ifdef ZTS
	TSRMLS_D = (void ***) data;
#endif

	if (!V8JSG(timer_stack).size()) {
		return;
	}

	v8::Locker locker(isolate);
	v8::HeapStatistics hs;
	bool send_notification = false;
	bool has_sent_notification = false;

	do {
		if (send_notification) {
#if PHP_V8_API_VERSION >= 3028036
			isolate->LowMemoryNotification();
#else
			v8::V8::LowMemoryNotification();
#endif
			has_sent_notification = true;
		}

		isolate->GetHeapStatistics(&hs);

		V8JSG(timer_mutex).lock();

		for (std::deque< v8js_timer_ctx* >::iterator it = V8JSG(timer_stack).begin();
			 it != V8JSG(timer_stack).end(); it ++) {
			v8js_timer_ctx *timer_ctx = *it;
			v8js_ctx *c = timer_ctx->ctx;

			if(c->isolate != isolate || timer_ctx->killed) {
				continue;
			}

			if (timer_ctx->memory_limit > 0 && hs.used_heap_size() > timer_ctx->memory_limit) {
				if (has_sent_notification) {
					timer_ctx->killed = true;
					v8::V8::TerminateExecution(c->isolate);
					c->memory_limit_hit = true;
				} else {
					// force garbage collection, then check again
					send_notification = true;
					break;
				}
			}
		}

		V8JSG(timer_mutex).unlock();
	} while(send_notification != has_sent_notification);
}
/* }}} */

void v8js_timer_thread(TSRMLS_D) /* {{{ */
{
	while (!V8JSG(timer_stop)) {

		V8JSG(timer_mutex).lock();
		if (V8JSG(timer_stack).size()) {
			v8js_timer_ctx *timer_ctx = V8JSG(timer_stack).front();
			v8js_ctx *c = timer_ctx->ctx;
			std::chrono::time_point<std::chrono::high_resolution_clock> now = std::chrono::high_resolution_clock::now();

			if(timer_ctx->killed) {
				/* execution already terminated, nothing to check anymore,
				 * but wait for caller to pop this timer context. */
			}
			else if(timer_ctx->time_limit > 0 && now > timer_ctx->time_point) {
				timer_ctx->killed = true;
				v8::V8::TerminateExecution(c->isolate);
				c->time_limit_hit = true;
			}
			else if (timer_ctx->memory_limit > 0) {
				/* If a memory_limit is set, we need to interrupt execution
				 * and check heap size within the callback.  We must *not*
				 * directly call GetHeapStatistics here, since we don't have
				 * a v8::Locker on the isolate, but are expected to hold one,
				 * and cannot aquire it as v8 is executing the script ... */
				void *data = NULL;
#ifdef ZTS
				data = (void *) TSRMLS_C;
#endif
				c->isolate->RequestInterrupt(v8js_timer_interrupt_handler, data);
			}
		}
		V8JSG(timer_mutex).unlock();

		// Sleep for 10ms
#ifdef _WIN32
		concurrency::wait(10);
#else
		std::chrono::milliseconds duration(10);
		std::this_thread::sleep_for(duration);
#endif
	}
}
/* }}} */


void v8js_timer_push(long time_limit, long memory_limit, v8js_ctx *c TSRMLS_DC) /* {{{ */
{
	V8JSG(timer_mutex).lock();

	// Create context for this timer
	v8js_timer_ctx *timer_ctx = (v8js_timer_ctx *)emalloc(sizeof(v8js_timer_ctx));

	// Calculate the time point when the time limit is exceeded
	std::chrono::milliseconds duration(time_limit);
	std::chrono::time_point<std::chrono::high_resolution_clock> from = std::chrono::high_resolution_clock::now();

	// Push the timer context
	timer_ctx->time_limit = time_limit;
	timer_ctx->memory_limit = memory_limit;
	timer_ctx->time_point = from + duration;
	timer_ctx->ctx = c;
	timer_ctx->killed = false;
	V8JSG(timer_stack).push_front(timer_ctx);

	V8JSG(timer_mutex).unlock();
}
/* }}} */

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */