/*
 *  Debugging related API calls
 */

#include "duk_internal.h"

DUK_EXTERNAL void duk_push_context_dump(duk_context *ctx) {
	duk_idx_t idx;
	duk_idx_t top;

	DUK_ASSERT_CTX_VALID(ctx);

	/* We don't duk_require_stack() here now, but rely on the caller having
	 * enough space.
	 */

	top = duk_get_top(ctx);
	duk_push_array(ctx);
	for (idx = 0; idx < top; idx++) {
		duk_dup(ctx, idx);
		duk_put_prop_index(ctx, -2, idx);
	}

	/* XXX: conversion errors should not propagate outwards.
	 * Perhaps values need to be coerced individually?
	 */
	duk_bi_json_stringify_helper(ctx,
	                             duk_get_top_index(ctx),  /*idx_value*/
	                             DUK_INVALID_INDEX,  /*idx_replacer*/
	                             DUK_INVALID_INDEX,  /*idx_space*/
	                             DUK_JSON_FLAG_EXT_CUSTOM |
	                             DUK_JSON_FLAG_ASCII_ONLY |
	                             DUK_JSON_FLAG_AVOID_KEY_QUOTES /*flags*/);

	duk_push_sprintf(ctx, "ctx: top=%ld, stack=%s", (long) top, (const char *) duk_safe_to_string(ctx, -1));
	duk_replace(ctx, -3);  /* [ ... arr jsonx(arr) res ] -> [ ... res jsonx(arr) ] */
	duk_pop(ctx);
	DUK_ASSERT(duk_is_string(ctx, -1));
}

#if defined(DUK_USE_DEBUGGER_SUPPORT)

DUK_EXTERNAL void duk_debugger_attach_custom(duk_context *ctx,
                                             duk_debug_read_function read_cb,
                                             duk_debug_write_function write_cb,
                                             duk_debug_peek_function peek_cb,
                                             duk_debug_read_flush_function read_flush_cb,
                                             duk_debug_write_flush_function write_flush_cb,
                                             duk_debug_request_function request_cb,
                                             duk_debug_detached_function detached_cb,
                                             void *udata) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_heap *heap;
	const char *str;
	duk_size_t len;

	/* XXX: should there be an error or an automatic detach if
	 * already attached?
	 */

	DUK_D(DUK_DPRINT("application called duk_debugger_attach()"));

	DUK_ASSERT_CTX_VALID(ctx);
	DUK_ASSERT(read_cb != NULL);
	DUK_ASSERT(write_cb != NULL);
	/* Other callbacks are optional. */

	heap = thr->heap;
	heap->dbg_read_cb = read_cb;
	heap->dbg_write_cb = write_cb;
	heap->dbg_peek_cb = peek_cb;
	heap->dbg_read_flush_cb = read_flush_cb;
	heap->dbg_write_flush_cb = write_flush_cb;
	heap->dbg_request_cb = request_cb;
	heap->dbg_detached_cb = detached_cb;
	heap->dbg_udata = udata;
	heap->dbg_have_next_byte = 0;

	/* Start in paused state. */
	heap->dbg_processing = 0;
	heap->dbg_paused = 1;
	heap->dbg_state_dirty = 1;
	heap->dbg_force_restart = 0;
	heap->dbg_step_type = 0;
	heap->dbg_step_thread = NULL;
	heap->dbg_step_csindex = 0;
	heap->dbg_step_startline = 0;
	heap->dbg_exec_counter = 0;
	heap->dbg_last_counter = 0;
	heap->dbg_last_time = 0.0;

	/* Send version identification and flush right afterwards.  Note that
	 * we must write raw, unframed bytes here.
	 */
	duk_push_sprintf(ctx, "%ld %ld %s %s\n",
	                 (long) DUK_DEBUG_PROTOCOL_VERSION,
	                 (long) DUK_VERSION,
	                 (const char *) DUK_GIT_DESCRIBE,
	                 (const char *) DUK_USE_TARGET_INFO);
	str = duk_get_lstring(ctx, -1, &len);
	DUK_ASSERT(str != NULL);
	duk_debug_write_bytes(thr, (const duk_uint8_t *) str, len);
	duk_debug_write_flush(thr);
	duk_pop(ctx);
}

DUK_EXTERNAL void duk_debugger_detach(duk_context *ctx) {
	duk_hthread *thr;

	DUK_D(DUK_DPRINT("application called duk_debugger_detach()"));

	DUK_ASSERT_CTX_VALID(ctx);
	thr = (duk_hthread *) ctx;
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);

	/* Can be called multiple times with no harm. */
	duk_debug_do_detach(thr->heap);
}

DUK_EXTERNAL void duk_debugger_cooperate(duk_context *ctx) {
	duk_hthread *thr;
	duk_bool_t processed_messages;

	DUK_ASSERT_CTX_VALID(ctx);
	thr = (duk_hthread *) ctx;
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);

	if (!DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) {
		return;
	}
	if (thr->callstack_top > 0 || thr->heap->dbg_processing) {
		/* Calling duk_debugger_cooperate() while Duktape is being
		 * called into is not supported.  This is not a 100% check
		 * but prevents any damage in most cases.
		 */
		return;
	}

	processed_messages = duk_debug_process_messages(thr, 1 /*no_block*/);
	DUK_UNREF(processed_messages);
}

DUK_EXTERNAL duk_bool_t duk_debugger_notify(duk_context *ctx, duk_idx_t nvalues) {
	duk_hthread *thr;
	duk_idx_t top;
	duk_idx_t idx;
	duk_bool_t ret = 0;

	DUK_ASSERT_CTX_VALID(ctx);
	thr = (duk_hthread *) ctx;
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);

	DUK_D(DUK_DPRINT("application called duk_debugger_notify() with nvalues=%ld", (long) nvalues));

	top = duk_get_top(ctx);
	if (top < nvalues) {
		DUK_ERROR_API(thr, "not enough stack values for notify");
		return ret;  /* unreachable */
	}
	if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) {
		duk_debug_write_notify(thr, DUK_DBG_CMD_APPNOTIFY);
		for (idx = top - nvalues; idx < top; idx++) {
			duk_tval *tv = DUK_GET_TVAL_POSIDX(ctx, idx);
			duk_debug_write_tval(thr, tv);
		}
		duk_debug_write_eom(thr);

		/* Return non-zero (true) if we have a good reason to believe
		 * the notify was delivered; if we're still attached at least
		 * a transport error was not indicated by the transport write
		 * callback.  This is not a 100% guarantee of course.
		 */
		if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) {
			ret = 1;
		}
	}
	duk_pop_n(ctx, nvalues);
	return ret;
}

DUK_EXTERNAL void duk_debugger_pause(duk_context *ctx) {
	duk_hthread *thr;

	DUK_ASSERT_CTX_VALID(ctx);
	thr = (duk_hthread *) ctx;
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);

	DUK_D(DUK_DPRINT("application called duk_debugger_pause()"));

	/* Treat like a debugger statement: ignore when not attached. */
	if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) {
		DUK_HEAP_SET_PAUSED(thr->heap);

		/* Pause on the next opcode executed.  This is always safe to do even
		 * inside the debugger message loop: the interrupt counter will be reset
		 * to its proper value when the message loop exits.
		 */
		thr->interrupt_init = 1;
		thr->interrupt_counter = 0;
	}
}

#else  /* DUK_USE_DEBUGGER_SUPPORT */

DUK_EXTERNAL void duk_debugger_attach_custom(duk_context *ctx,
                                             duk_debug_read_function read_cb,
                                             duk_debug_write_function write_cb,
                                             duk_debug_peek_function peek_cb,
                                             duk_debug_read_flush_function read_flush_cb,
                                             duk_debug_write_flush_function write_flush_cb,
                                             duk_debug_request_function request_cb,
                                             duk_debug_detached_function detached_cb,
                                             void *udata) {
	DUK_ASSERT_CTX_VALID(ctx);
	DUK_UNREF(read_cb);
	DUK_UNREF(write_cb);
	DUK_UNREF(peek_cb);
	DUK_UNREF(read_flush_cb);
	DUK_UNREF(write_flush_cb);
	DUK_UNREF(request_cb);
	DUK_UNREF(detached_cb);
	DUK_UNREF(udata);
	DUK_ERROR_API((duk_hthread *) ctx, "no debugger support");
}

DUK_EXTERNAL void duk_debugger_detach(duk_context *ctx) {
	DUK_ASSERT_CTX_VALID(ctx);
	DUK_ERROR_API((duk_hthread *) ctx, "no debugger support");
}

DUK_EXTERNAL void duk_debugger_cooperate(duk_context *ctx) {
	/* nop */
	DUK_ASSERT_CTX_VALID(ctx);
	DUK_UNREF(ctx);
}

DUK_EXTERNAL duk_bool_t duk_debugger_notify(duk_context *ctx, duk_idx_t nvalues) {
	duk_idx_t top;

	DUK_ASSERT_CTX_VALID(ctx);

	top = duk_get_top(ctx);
	if (top < nvalues) {
		DUK_ERROR_API((duk_hthread *) ctx, "not enough stack values for notify");
		return 0;  /* unreachable */
	}

	/* No debugger support, just pop values. */
	duk_pop_n(ctx, nvalues);
	return 0;
}

DUK_EXTERNAL void duk_debugger_pause(duk_context *ctx) {
	/* Treat like debugger statement: nop */
	DUK_ASSERT_CTX_VALID(ctx);
	DUK_UNREF(ctx);
}

#endif  /* DUK_USE_DEBUGGER_SUPPORT */
