/* radare - LGPL - Copyright 2009-2026 - pancake */

#define R_LOG_ORIGIN "cmdapi"
#include <r_core.h>
#include <r_core_priv.h>
#include <sdb/ht_pp.h>

#define NCMDS (sizeof (cmd->cmds) / sizeof (*cmd->cmds))

typedef struct {
	RCmdCtxCb callback;
	void *user;
	size_t active_calls;
	RCmdArgFlags flags;
} RCmdHandler;

typedef struct r_cmd_handler_frame_t {
	RCmdHandler *handler;
	struct r_cmd_handler_frame_t *parent;
} RCmdHandlerFrame;

static R_TH_LOCAL RCmdHandlerFrame *current_handler_frame;

static bool cmd_handler_is_current(const RCmdHandler *handler) {
	RCmdHandlerFrame *frame;
	for (frame = current_handler_frame; frame; frame = frame->parent) {
		if (frame->handler == handler) {
			return true;
		}
	}
	return false;
}

static void cmd_handler_call_end(RCmd *cmd, RCmdHandler *handler) {
	r_th_lock_enter (cmd->handlers_lock);
	handler->active_calls--;
	if (!handler->active_calls) {
		r_th_cond_signal_all (cmd->handlers_idle);
	}
	r_th_lock_leave (cmd->handlers_lock);
}

static void alias_freefn(HtPPKv *kv) {
	if (kv) {
		char *k = kv->key;
		RCmdAliasVal *v = kv->value;
		free (v->data);
		free (k);
		free (v);
	}
}

static void *alias_dupkey(const void *k) {
	return strdup ((const char *)k);
}

static void *alias_dupvalue(const void *v_void) {
	RCmdAliasVal *v = (RCmdAliasVal *)v_void;
	ut8 *data = malloc (v->sz);
	if (!data) {
		return NULL;
	}
	RCmdAliasVal *vcopy = R_NEW (RCmdAliasVal);
	vcopy->is_data = v->is_data;
	vcopy->is_str = v->is_str;
	vcopy->sz = v->sz;
	vcopy->data = data;
	memcpy (vcopy->data, v->data, v->sz);
	return vcopy;
}

static ut32 alias_calcsizeK(const void *k) {
	return strlen ((const char *)k);
}

static ut32 alias_calcsizeV(const void *v) {
	return ((RCmdAliasVal *)v)->sz;
}

static int alias_cmp(const void *k1, const void *k2) {
	return strcmp ((const char *)k1, (const char *)k2);
}

static inline ut32 alias_hash(const char *key, size_t len) {
	if (len > 32) {
		return (ut32)r_str_hash64_seed (key, len, 0);
	}
	ut32 h = 5381;
	size_t i;
	for (i = 0; i < len; i++) {
		h = h * 33 + (ut8)key[i];
	}
	return h;
}

static ut32 alias_hashfn(const void *key) {
	// Short callback keys need only one scan, without a separate strlen.
	const char *name = key;
	ut32 h = 5381;
	size_t i = 0;
	while (i < 32 && name[i]) {
		h = h * 33 + (ut8)name[i++];
	}
	if (i < 32 || !name[i]) {
		return h;
	}
	return (ut32)r_str_hash64_seed (name, i + strlen (name + i), 0);
}

R_API void r_cmd_alias_init(RCmd *cmd) {
	HtPPOptions opt = {0};
	opt.cmp = alias_cmp;
	opt.hashfn = alias_hashfn;
	opt.dupkey = alias_dupkey;
	opt.dupvalue = alias_dupvalue;
	opt.calcsizeK = alias_calcsizeK;
	opt.calcsizeV = alias_calcsizeV;
	opt.freefn = alias_freefn;
	opt.elem_size = sizeof (HtPPKv);
	cmd->aliases = ht_pp_new_opt (&opt);
}

R_API RCmd *r_cmd_new(void *data) {
	int i;
	RCmd *cmd = R_NEW0 (RCmd);
	cmd->data = data;
	for (i = 0; i < NCMDS; i++) {
		cmd->cmds[i] = NULL;
	}
	cmd->nullcallback = NULL;
	cmd->handlers = r_trie_new (free);
	cmd->handlers_lock = r_th_lock_new (false);
	cmd->handlers_idle = r_th_cond_new ();
	if (!cmd->handlers || !cmd->handlers_lock || !cmd->handlers_idle) {
		r_trie_free (cmd->handlers);
		r_th_lock_free (cmd->handlers_lock);
		r_th_cond_free (cmd->handlers_idle);
		free (cmd);
		return NULL;
	}
	// cmd->root_cmd_desc = create_cmd_desc (cmd, NULL, R_CMD_DESC_TYPE_ARGV, "", &root_help, true);
	r_cmd_macro_init (&cmd->macro);
	r_cmd_alias_init (cmd);
	return cmd;
}

R_API void r_cmd_free(RCmd *cmd) {
	int i;
	if (!cmd) {
		return;
	}
	ht_up_free (cmd->ts_symbols_ht);
	r_cmd_alias_free (cmd);
	r_cmd_macro_fini (&cmd->macro);
	r_core_plugins_fini (cmd);
	r_trie_free (cmd->handlers);
	r_th_lock_free (cmd->handlers_lock);
	r_th_cond_free (cmd->handlers_idle);
	for (i = 0; i < NCMDS; i++) {
		if (cmd->cmds[i]) {
			R_FREE (cmd->cmds[i]);
		}
	}
	// cmd_desc_free (cmd->root_cmd_desc);
	free (cmd);
}

R_API void r_cmd_alias_free(RCmd *cmd) {
	R_RETURN_IF_FAIL (cmd);
	ht_pp_free (cmd->aliases);
	cmd->aliases = NULL;
}

R_API bool r_cmd_alias_del(RCmd *cmd, const char *k) {
	R_RETURN_VAL_IF_FAIL (cmd && k, false);
	return ht_pp_delete (cmd->aliases, k);
}

R_API bool r_cmd_alias_set_cmd(RCmd *cmd, const char *k, const char *v) {
	R_RETURN_VAL_IF_FAIL (cmd && k && v, false);
	RCmdAliasVal val;
	val.data = (ut8 *)v;
	val.sz = strlen (v) + 1;
	val.is_str = true;
	val.is_data = false;

	return ht_pp_update (cmd->aliases, k, &val);
}

R_API bool r_cmd_alias_set_raw(RCmd *cmd, const char *k, const ut8 *v, int sz, bool append) {
	R_RETURN_VAL_IF_FAIL (cmd && k && sz >= 0 && (v || !sz), false);
	v = v? v: (const ut8 *)"";
	ut8 *owned = NULL;
	RCmdAliasVal *old = append? r_cmd_alias_get (cmd, k): NULL;
	if (old) {
		if (!old->is_data) {
			return false;
		}
		const int old_sz = old->sz - old->is_str;
		if (old_sz < 0 || sz > ST32_MAX - old_sz) {
			return false;
		}
		const int new_sz = old_sz + sz;
		owned = malloc ((size_t)new_sz + 1);
		if (!owned) {
			return false;
		}
		memcpy (owned, old->data, old_sz);
		memcpy (owned + old_sz, v, sz);
		v = owned;
		sz = new_sz;
	}

	int i;
	bool is_str = true;
	int len = sz;
	for (i = 0; i < sz; i++) {
		if (!v[i]) {
			is_str = i == sz - 1;
			len = i;
			break;
		}
		if (!IS_PRINTABLE (v[i]) && !IS_WHITECHAR (v[i])) {
			is_str = false;
			break;
		}
	}

	if (is_str) {
		while (len > 0 && (v[len - 1] == '\r' || v[len - 1] == '\n')) {
			len--;
		}
		if (len == ST32_MAX) {
			free (owned);
			return false;
		}
		if (!owned) {
			owned = (ut8 *)(len? r_str_newlen ((const char *)v, len): strdup (""));
		}
		if (!owned) {
			return false;
		}
		owned[len] = 0;
	}

	RCmdAliasVal val = {
		.data = owned? owned: (ut8 *)v,
		.sz = is_str? len + 1: sz,
		.is_str = is_str,
		.is_data = true
	};
	bool ret = ht_pp_update (cmd->aliases, k, &val);
	free (owned);
	return ret;
}

R_API RCmdAliasVal *r_cmd_alias_get(RCmd *cmd, const char *k) {
	R_RETURN_VAL_IF_FAIL (cmd && cmd->aliases && k, NULL);
	const size_t len = strlen (k);
	HtPP *aliases = cmd->aliases;
	HtPPBucket *bucket = &aliases->table[alias_hash (k, len) % aliases->size];
	ut32 i;
	for (i = 0; i < bucket->count; i++) {
		HtPPKv *kv = &bucket->arr[i];
		if (kv->key_len == len && (kv->key == k || !memcmp (kv->key, k, len))) {
			return kv->value;
		}
	}
	return NULL;
}

/* Returns a new copy of v->data. If !v->is_str, hex escaped */
R_API char *r_cmd_alias_val_strdup(RCmdAliasVal *v) {
	if (v->is_str) {
		return strdup ((char *)v->data);
	}
	return r_str_escape_raw (v->data, v->sz);
}

/* Returns a new copy of v->data. If !v->is_str, b64 encoded. */
R_API char *r_cmd_alias_val_strdup_b64(RCmdAliasVal *v) {
	if (v->is_str) {
		return strdup ((char *)v->data);
	}
	return r_base64_encode_dyn ((const ut8*)v->data, v->sz);
}

R_API void r_cmd_set_data(RCmd *cmd, void *data) {
	cmd->data = data;
}

R_API bool r_cmd_register(RCmd *cmd, const char *name, RCmdCtxCb callback, void *handler_user) {
	R_RETURN_VAL_IF_FAIL (cmd && name && callback, false);
	return r_cmd_register_args (cmd, name, callback, handler_user, R_CMD_ARGS_DEFAULT);
}

R_IPI bool r_cmd_register_args(RCmd *cmd, const char *name, RCmdCtxCb callback, void *handler_user, RCmdArgFlags flags) {
	R_RETURN_VAL_IF_FAIL (cmd && name && callback, false);
	RStrs key = r_strs_from (name);
	r_th_lock_enter (cmd->handlers_lock);
	if (r_strs_empty (key) || r_trie_find (cmd->handlers, key)) {
		r_th_lock_leave (cmd->handlers_lock);
		return false;
	}
	RCmdHandler *handler = R_NEW (RCmdHandler);
	handler->callback = callback;
	handler->user = handler_user;
	handler->active_calls = 0;
	handler->flags = flags;
	if (!r_trie_insert (cmd->handlers, key, handler)) {
		r_th_lock_leave (cmd->handlers_lock);
		free (handler);
		return false;
	}
	r_th_lock_leave (cmd->handlers_lock);
	return true;
}

R_API bool r_cmd_unregister(RCmd *cmd, const char *name) {
	R_RETURN_VAL_IF_FAIL (cmd && name, false);
	r_th_lock_enter (cmd->handlers_lock);
	const RStrs key = r_strs_from (name);
	RCmdHandler *handler = r_trie_find (cmd->handlers, key);
	if (!handler || cmd_handler_is_current (handler)) {
		r_th_lock_leave (cmd->handlers_lock);
		return false;
	}
	handler = r_trie_take (cmd->handlers, key);
	while (handler->active_calls) {
		r_th_cond_wait (cmd->handlers_idle, cmd->handlers_lock, 0);
	}
	r_th_lock_leave (cmd->handlers_lock);
	free (handler);
	return true;
}

typedef struct {
	RList *keys;
	bool valid;
} RCmdHandlerKeys;

static bool cmd_handler_collect_key(RStrs name, void *value, void *user) {
	RCmdHandlerKeys *result = user;
	if (cmd_handler_is_current (value)) {
		result->valid = false;
		return false;
	}
	const size_t length = r_strs_len (name);
	if (length > ST32_MAX) {
		result->valid = false;
		return false;
	}
	char *key = r_str_ndup (name.a, (int)length);
	if (!key || !r_list_append (result->keys, key)) {
		free (key);
		result->valid = false;
		return false;
	}
	return true;
}

R_API size_t r_cmd_unregister_prefix(RCmd *cmd, const char *prefix) {
	R_RETURN_VAL_IF_FAIL (cmd && prefix, 0);
	RCmdHandlerKeys result = {
		.keys = r_list_newf (free),
		.valid = true,
	};
	if (!result.keys) {
		return 0;
	}
	r_th_lock_enter (cmd->handlers_lock);
	const bool collected = r_trie_foreach_prefix (cmd->handlers,
		r_strs_from (prefix), cmd_handler_collect_key, &result);
	if (!collected || !result.valid) {
		r_th_lock_leave (cmd->handlers_lock);
		r_list_free (result.keys);
		return 0;
	}
	RList *handlers = r_list_newf (free);
	if (!handlers) {
		r_th_lock_leave (cmd->handlers_lock);
		r_list_free (result.keys);
		return 0;
	}
	RListIter *iter;
	char *key;
	r_list_foreach (result.keys, iter, key) {
		RCmdHandler *handler = r_trie_take (cmd->handlers, r_strs_from (key));
		if (handler) {
			r_list_append (handlers, handler);
		}
	}
	RCmdHandler *handler;
	r_list_foreach (handlers, iter, handler) {
		while (handler->active_calls) {
			r_th_cond_wait (cmd->handlers_idle, cmd->handlers_lock, 0);
		}
	}
	r_th_lock_leave (cmd->handlers_lock);
	const size_t count = r_list_length (handlers);
	r_list_free (result.keys);
	r_list_free (handlers);
	return count;
}

typedef struct {
	RCmdForeachCb callback;
	void *user;
} RCmdForeachContext;

static bool cmd_foreach_handler(RStrs name, void *value, void *user) {
	RCmdForeachContext *context = user;
	(void)value;
	return context->callback (name, context->user);
}

R_API bool r_cmd_foreach_prefix(const RCmd *cmd, const char *prefix, RCmdForeachCb callback, void *user) {
	R_RETURN_VAL_IF_FAIL (cmd && prefix && callback, false);
	r_th_lock_enter (cmd->handlers_lock);
	RCmdForeachContext context = {
		.callback = callback,
		.user = user
	};
	const bool res = r_trie_foreach_prefix (cmd->handlers, r_strs_from (prefix), cmd_foreach_handler, &context);
	r_th_lock_leave (cmd->handlers_lock);
	return res;
}

R_API bool r_cmd_add(RCmd *cmd, const char *name, RCmdCb cb) {
	const ut8 idx = name[0];
	RCmdItem *item = cmd->cmds[idx];
	if (!item) {
		item = R_NEW0 (RCmdItem);
		cmd->cmds[idx] = item;
	}
	strncpy (item->cmd, name, sizeof (item->cmd)-1);
	item->callback = cb;
	return true;
}

R_API void r_cmd_del(RCmd *cmd, const char *command) {
	const ut8 idx = command[0];
	R_FREE (cmd->cmds[idx]);
}

static RCmdResult cmd_result(RCmdAction action, st64 status) {
	return (RCmdResult) {
		.action = action,
		.status = status
	};
}

static RCmdResult cmd_result_from_legacy(int status) {
	return cmd_result (status == -2? R_CMD_ACTION_QUIT: R_CMD_ACTION_CONTINUE, status);
}

static int cmd_result_to_legacy(RCmdResult result) {
	return result.action == R_CMD_ACTION_CONTINUE
		? (int)R_CLAMP (result.status, (st64)ST32_MIN, (st64)ST32_MAX)
		: result.action == R_CMD_ACTION_QUIT? -2: -1;
}

static const char *cmd_decode_escape(const char *src, const char *end, char **dst) {
	if (src >= end) {
		*(*dst)++ = '\\';
		return src;
	}
	ut8 ch = *src++;
	switch (ch) {
	case 'a': ch = '\a'; break;
	case 'b': ch = '\b'; break;
	case 'e': ch = 0x1b; break;
	case 'f': ch = '\f'; break;
	case 'n': ch = '\n'; break;
	case 'r': ch = '\r'; break;
	case 's': ch = ' '; break;
	case 't': ch = '\t'; break;
	case 'v': ch = '\v'; break;
	case 'x': {
		ut8 value = 0;
		if (end - src >= 2 && r_hex_to_byte (&value, src[0]) && r_hex_to_byte (&value, src[1])) {
			*(*dst)++ = value;
			return src + 2;
		}
		return src;
	}
	default:
		if (IS_OCTAL (ch)) {
			ut8 value = ch - '0';
			int i;
			for (i = 0; i < 2 && src < end && IS_OCTAL (*src); i++, src++) {
				value = (value * 8) + (*src - '0');
			}
			ch = value;
		}
		break;
	}
	*(*dst)++ = ch;
	return src;
}

/* Rebuilds context arguments. Raw calls only split on whitespace, leaving
 * quoting, escapes and the untouched command tail available to the handler. */
static bool cmd_context_parse_args(RCmdContext *context, RStrs rest, bool raw) {
	RVecRStrs_fini (&context->args);
	free (context->args_storage);
	context->args_storage = NULL;
	if (r_strs_empty (rest)) {
		return true;
	}
	char *storage = malloc (r_strs_len (rest) + 1);
	if (!storage) {
		return false;
	}
	context->args_storage = storage;
	*storage = 0;
	const char *src = rest.a;
	char *dst = storage;
	while (src < rest.b) {
		while (src < rest.b && isspace ((ut8)*src)) {
			src++;
		}
		if (src == rest.b) {
			break;
		}
		char quote = 0;
		char *begin = dst;
		while (src < rest.b) {
			char ch = *src;
			if (!quote && isspace ((ut8)ch)) {
				break;
			}
			src++;
			if (!raw) {
				if (ch == '\\') {
					src = cmd_decode_escape (src, rest.b, &dst);
					continue;
				}
				if (ch == '\'' || ch == '"') {
					if (!quote) {
						quote = ch;
						continue;
					}
					if (quote == ch) {
						quote = 0;
						continue;
					}
				}
			}
			*dst++ = ch;
		}
		RStrs *arg = RVecRStrs_emplace_back (&context->args);
		if (!arg) {
			return false;
		}
		*arg = r_strs_new (begin, dst);
		*dst++ = 0;
	}
	return true;
}

static void cmd_context_fini(RCmdContext *context) {
	RVecRStrs_fini (&context->args);
	free (context->args_storage);
}

static RCons *cmd_legacy_capture_begin(RCmd *cmd, RCmdContext *parent) {
	RCons *cons = parent
		? parent->cons
		: cmd->get_cons? cmd->get_cons (cmd->data): cmd->cons;
	if (!cmd->cons || !cons || cons == cmd->cons) {
		return NULL;
	}
	r_cons_push (cmd->cons);
	cmd->cons->context->cmd_str_depth++;
	if (parent) {
		parent->cons = cmd->cons;
	}
	return cons;
}

static void cmd_legacy_capture_end(RCmd *cmd, RCmdContext *parent, RCons *cons) {
	if (parent) {
		parent->cons = cons;
	}
	cmd->cons->context->cmd_str_depth--;
	r_cons_filter (cmd->cons);
	r_cons_merge_output (cons, cmd->cons);
	r_cons_pop (cmd->cons);
	r_cons_echo (cmd->cons, NULL);
}

static RCmdResult cmd_call_registered(RCmd *cmd, RCmdContext *parent, RStrs input, bool raw) {
	RStrs lookup = input;
	RCmdContext storage = {
		.parent = parent,
		.cmd = cmd,
		.cons = parent? parent->cons: cmd->get_cons? cmd->get_cons (cmd->data): cmd->cons,
		.user = cmd->data,
		.remaining_depth = parent? parent->remaining_depth: 0,
		.blocksize = parent? parent->blocksize: cmd->get_blocksize? cmd->get_blocksize (cmd->data): 0,
	};
	RCmdContext *context = &storage;
	const char *parsed_from = NULL;
	RCmdArgFlags parsed_flags = R_CMD_ARGS_DEFAULT;
	while (!r_strs_empty (lookup)) {
		size_t matched = 0;
		RCmdCtxCb callback = NULL;
		void *handler_user = NULL;
		RCmdArgFlags flags = R_CMD_ARGS_DEFAULT;
		r_th_lock_enter (cmd->handlers_lock);
		RCmdHandler *handler = r_trie_find_longest_prefix (cmd->handlers, lookup, &matched);
		if (handler && matched) {
			callback = handler->callback;
			handler_user = handler->user;
			flags = handler->flags;
			handler->active_calls++;
		}
		r_th_lock_leave (cmd->handlers_lock);
		if (!callback) {
			break;
		}
		const char *sub_end = input.a + matched;
		if (!(flags & R_CMD_ARGS_ATTACHED)) {
			while (sub_end < input.b && !isspace ((ut8)*sub_end)) {
				sub_end++;
			}
		} else if (sub_end < input.b && *sub_end == '?' &&
				(sub_end + 1 == input.b || isspace ((ut8)sub_end[1]))) {
			sub_end++;
		}
		context->raw = raw || (flags & R_CMD_ARGS_VERBATIM);
		if (parsed_from != sub_end || parsed_flags != flags) {
			RStrs args = r_strs_new (sub_end, input.b);
			if (flags & R_CMD_ARGS_VERBATIM) {
				args.a = args.b;
			}
			if (!cmd_context_parse_args (context, args, raw)) {
				cmd_handler_call_end (cmd, handler);
				cmd_context_fini (context);
				return cmd_result (R_CMD_ACTION_ABORT, 2);
			}
			parsed_from = sub_end;
			parsed_flags = flags;
		}
		context->subcmd = r_strs_new (input.a + matched, sub_end);
		context->handler_user = handler_user;
		RCmdHandlerFrame frame = {
			.handler = handler,
			.parent = current_handler_frame,
		};
		current_handler_frame = &frame;
		RCmdResult result = callback (context);
		current_handler_frame = frame.parent;
		cmd_handler_call_end (cmd, handler);
		if (result.action != R_CMD_ACTION_UNHANDLED) {
			cmd_context_fini (context);
			return result;
		}
		lookup.b = lookup.a + matched - 1;
	}
	cmd_context_fini (context);
	return cmd_result (R_CMD_ACTION_UNHANDLED, 127);
}

R_IPI RCmdResult r_cmd_call_result(RCmd *cmd, RCmdContext *parent, const char *input, bool raw) {
	RCore *core = cmd->data;
	RCons *capture = NULL;
	RCmdResult result;
	// Skipping redundant single quotes on command calls
	while (*input == '\'') {
		input++;
	}
	if (!*input) {
		if (!cmd->nullcallback) {
			return cmd_result (R_CMD_ACTION_UNHANDLED, 127);
		}
		capture = cmd_legacy_capture_begin (cmd, parent);
		result = cmd_result_from_legacy (cmd->nullcallback (cmd->data));
		goto beach;
	}
	RCmdAliasVal *v = r_cmd_alias_get (cmd, input);
	if (v && v->is_data) {
		char *v_str = r_cmd_alias_val_strdup (v);
		RCons *cons = parent
			? parent->cons
			: cmd->get_cons? cmd->get_cons (cmd->data): cmd->cons;
		r_cons_print (cons, v_str);
		free (v_str);
		return cmd_result_from_legacy (true);
	}
	result = cmd_call_registered (cmd, parent, r_strs_from (input), raw);
	if (result.action != R_CMD_ACTION_UNHANDLED) {
		return result;
	}
	capture = cmd_legacy_capture_begin (cmd, parent);
	RListIter *iter;
	if (cmd->libstore) {
		RCorePluginSession *cps;
		r_list_foreach (cmd->libstore->plugins, iter, cps) {
			RCorePlugin *plugin = cps->plugin;
			if (plugin->call && plugin->call (cps, input)) {
				result = cmd_result_from_legacy (true);
				goto beach;
			}
		}
	}
	const ut8 idx = input[0];
	RCmdItem *item = cmd->cmds[idx];
	if (item && item->callback) {
		result = cmd_result_from_legacy (item->callback (cmd->data, input + 1));
		goto beach;
	}
	if (core && core->sdb) {
		const char *suggestion = sdb_const_get (core->sdb, input, NULL);
		if (suggestion) {
			R_LOG_INFO ("%s", suggestion);
		}
	}
beach:
	if (capture) {
		cmd_legacy_capture_end (cmd, parent, capture);
	}
	return result;
}

R_API int r_cmd_call(RCmd *cmd, const char *input) {
	R_RETURN_VAL_IF_FAIL (cmd && input, -1);
	return cmd_result_to_legacy (r_cmd_call_result (cmd, NULL, input, false));
}

R_IPI int r_cmd_call_context(RCmd *cmd, RCmdContext *parent, const char *input, bool raw) {
	return cmd_result_to_legacy (r_cmd_call_result (cmd, parent, input, raw));
}

/** macro.c **/

R_API RCmdMacroItem *r_cmd_macro_item_new(void) {
	return R_NEW0 (RCmdMacroItem);
}

R_API void r_cmd_macro_item_free(RCmdMacroItem *item) {
	if (item) {
		free (item->name);
		free (item->args);
		free (item->code);
		free (item);
	}
}

R_API void r_cmd_macro_init(RCmdMacro *mac) {
	R_RETURN_IF_FAIL (mac);
	mac->counter = 0;
	mac->_brk_value = 0;
	mac->brk_value = &mac->_brk_value;
	mac->num = NULL;
	mac->user = NULL;
	mac->cmd = NULL;
	mac->macros = r_list_newf ((RListFree)r_cmd_macro_item_free);
}

R_API void r_cmd_macro_fini(RCmdMacro *mac) {
	r_list_free (mac->macros);
	mac->macros = NULL;
}

// XXX add support single line function definitions
// XXX add support for single name multiple nargs macros
R_API bool r_cmd_macro_add(RCmdMacro *mac, const char *oname) {
	struct r_cmd_macro_item_t *macro;
	char *name, *args = NULL;
	//char buf[R_CMD_MAXLEN];
	RCmdMacroItem *m;
	bool macro_update = false;
	RListIter *iter;
	char *pbody;
	// char *bufp;
	char *ptr;
	int lidx;

	if (!*oname) {
		char *list = r_cmd_macro_list (mac, 0);
		eprintf ("%s\n", list); // TODO: return char *?
		free (list);
		return false;
	}

	name = strdup (oname);
	if (!name) {
		return false;
	}

	pbody = strchr (name, ';');
	if (!pbody) {
		R_LOG_ERROR ("Invalid macro body in '%s'", name);
		free (name);
		return false;
	}
	*pbody = '\0';
	pbody++;

	if (*name && name[1] && name[strlen (name) - 1] == ')') {
		R_LOG_ERROR ("missing macro body?");
		free (name);
		return false;
	}

	macro = NULL;
	ptr = strchr (name, ' ');
	if (ptr) {
		*ptr = '\0';
		args = ptr + 1;
	}
	macro_update = false;
	r_list_foreach (mac->macros, iter, m) {
		if (!strcmp (name, m->name)) {
			macro = m;
			// keep macro->name
			free (macro->code);
			free (macro->args);
			macro_update = true;
			break;
		}
	}
	if (ptr) {
		*ptr = ' ';
	}
	if (!macro) {
		macro = r_cmd_macro_item_new ();
		if (!macro) {
			free (name);
			return false;
		}
		macro->name = strdup (name);
	}

	macro->codelen = (pbody[0])? strlen (pbody) + 2 : 4096;
	macro->code = (char *)malloc (macro->codelen);
	*macro->code = '\0';
	macro->nargs = 0;
	if (!args) {
		args = "";
	}
	macro->args = strdup (args);
	ptr = strchr (macro->name, ' ');
	if (ptr) {
		*ptr = '\0';
		macro->nargs = r_str_word_set0 (ptr + 1);
	}

	for (lidx = 0; pbody[lidx]; lidx++) {
		if (pbody[lidx] == ';') {
			pbody[lidx] = '\n';
		} else if (pbody[lidx] == ')' && pbody[lidx - 1] == '\n') {
			pbody[lidx] = '\0';
		}
	}
	strncpy (macro->code, pbody, macro->codelen);
	macro->code[macro->codelen - 1] = 0;
	if (macro_update == false) {
		r_list_append (mac->macros, macro);
	}
	free (name);
	return true;
}

R_API bool r_cmd_macro_rm(RCmdMacro *mac, const char *_name) {
	R_RETURN_VAL_IF_FAIL (mac && _name, false);
	RListIter *iter;
	RCmdMacroItem *m;
	char *name = strdup (_name);
	if (!name) {
		return false;
	}
	char *ptr = strchr (name, ')');
	if (ptr) {
		*ptr = '\0';
	}
	bool ret = false;
	r_list_foreach (mac->macros, iter, m) {
		if (!strcmp (m->name, name)) {
			r_list_delete (mac->macros, iter);
			R_LOG_DEBUG ("Macro '%s' removed", name);
			ret = true;
			break;
		}
	}
	free (name);
	return ret;
}

static char *macro_meta(RCmdMacro *mac) {
	RCmdMacroItem *m;
	int j;
	RListIter *iter;
	RStrBuf *sb = r_strbuf_new ("");
	r_list_foreach (mac->macros, iter, m) {
		r_strbuf_appendf (sb, "\"(%s %s; ", m->name, m->args);
		for (j = 0; m->code[j]; j++) {
			if (m->code[j] == '\n') {
				r_strbuf_append (sb, ";");
			} else {
				r_strbuf_appendf (sb, "%c", m->code[j]);
			}
		}
		r_strbuf_append (sb, ")\"\n");
	}
	return r_strbuf_drain (sb);
}

R_API char *r_cmd_macro_list(RCmdMacro *mac, int mode) {
	RCmdMacroItem *m;
	int j, idx = 0;
	RListIter *iter;
	if (mode == '*') {
		return macro_meta (mac);
	}
	if (mode == 'j') {
		PJ *pj = pj_new ();
		pj_o (pj);
		pj_ks (pj, "cmd", "(j");
		pj_ka (pj, "macros");
		r_list_foreach (mac->macros, iter, m) {
			pj_o (pj);
			pj_ks (pj, "name", m->name);
			pj_ks (pj, "args", m->args);
			pj_ks (pj, "cmds", r_str_trim_head_ro (m->code));
			pj_end (pj);
			idx++;
		}
		pj_end (pj);
		pj_end (pj);
		return pj_drain (pj);
	}

	RStrBuf *sb = r_strbuf_new ("");
	r_list_foreach (mac->macros, iter, m) {
		r_strbuf_appendf (sb, "%d (%s %s; ", idx, m->name, m->args);
		for (j = 0; m->code[j]; j++) {
			if (m->code[j] == '\n') {
				r_strbuf_append (sb, "; ");
			} else {
				r_strbuf_appendf (sb, "%c", m->code[j]);
			}
		}
		r_strbuf_append (sb, ")\n");
		idx++;
	}
	return r_strbuf_drain (sb);
}
R_API int r_cmd_macro_cmd_args(RCmdMacro *mac, const char *ptr, const char *args, int nargs) {
	int i, j;
	char *pcmd, cmd[R_CMD_MAXLEN];
	const char *arg = args;

	for (*cmd = i = j = 0; j < R_CMD_MAXLEN && ptr[j]; i++,j++) {
		if (ptr[j] == '$') {
			if (ptr[j+1]>='0' && ptr[j+1]<='9') {
				int wordlen;
				int w = ptr[j+1]-'0';
				const char *word = r_str_word_get0 (arg, w);
				if (word && *word) {
					wordlen = strlen (word);
					if ((i + wordlen + 1) >= sizeof (cmd)) {
						return -1;
					}
					memcpy (cmd+i, word, wordlen+1);
					i += wordlen-1;
					j++;
				} else {
					R_LOG_ERROR ("Undefined argument %d", w);
				}
			} else if (ptr[j + 1] == '@') {
				char off[32];
				int offlen;
				offlen = snprintf (off, sizeof (off), "%d",
					mac->counter);
				if ((i + offlen + 1) >= sizeof (cmd)) {
					return -1;
				}
				memcpy (cmd + i, off, offlen + 1);
				i += offlen-1;
				j++;
			} else {
				cmd[i] = ptr[j];
				cmd[i + 1] = '\0';
			}
		} else {
			cmd[i] = ptr[j];
			cmd[i + 1] = '\0';
		}
	}
	for (pcmd = cmd; *pcmd && (*pcmd == ' ' || *pcmd == '\t'); pcmd++) {
		;
	}
	//eprintf ("-pre %d\n", (int)mac->num->value);
	int xx = (*pcmd == ')')? 0: mac->cmd (mac->user, pcmd);
	//eprintf ("-pos %p %d\n", mac->num, (int)mac->num->value);
	return xx;
}

R_API char *r_cmd_macro_label_process(RCmdMacro *mac, RCmdMacroLabel *labels, int *labels_n, char *ptr) {
	int i;
	for (; *ptr == ' '; ptr++) {
		;
	}
	if (ptr[strlen (ptr) - 1] == ':' && !strchr (ptr, ' ')) {
		/* label detected */
		if (ptr[0] == '.') {
		//	eprintf("---> GOTO '%s'\n", ptr+1);
			/* goto */
			for (i = 0; i < *labels_n; i++) {
			//	eprintf("---| chk '%s'\n", labels[i].name);
				if (!strcmp (ptr + 1, labels[i].name)) {
					return labels[i].ptr;
				}
			}
			return NULL;
			/* conditional goto */
		} else if (ptr[0] == '?' && ptr[1] == '!' && ptr[2] != '?') {
			if (mac->num && mac->num->value != 0) {
				char *label = ptr + 3;
				for (; *label == ' ' || *label == '.'; label++) {
					;
				}
				// eprintf("===> GOTO %s\n", label);
				/* goto label ptr+3 */
				for (i = 0; i < *labels_n; i++) {
					if (!strcmp (label, labels[i].name)) {
						return labels[i].ptr;
					}
				}
				return NULL;
			}
			/* conditional goto */
		} else if (ptr[0] == '?' && ptr[1] == '?' && ptr[2] != '?') {
			if (mac->num->value == 0) {
				char *label = ptr + 3;
				for (; label[0] == ' ' || label[0] == '.'; label++) {
					;
				}
				//		eprintf("===> GOTO %s\n", label);
				/* goto label ptr+3 */
				for (i = 0; i < *labels_n; i++) {
					if (!strcmp (label, labels[i].name)) {
						return labels[i].ptr;
					}
				}
				return NULL;
			}
		} else {
			bool known = false;
			for (i = 0; i < *labels_n; i++) {
		//	eprintf("---| chk '%s'\n", labels[i].name);
				if (!strcmp (ptr, labels[i].name)) {
					known = true;
					break;
				}
			}
			/* Add label */
		//	eprintf("===> ADD LABEL(%s)\n", ptr);
			if (!known) {
				if (*labels_n >= MACRO_LABELS) {
					R_LOG_ERROR ("Too many labels in macro (max %d)", MACRO_LABELS);
					return NULL;
				}
				if (strlen (ptr) >= sizeof (labels[0].name)) {
					R_LOG_ERROR ("Label name '%s' is too long", ptr);
					return NULL;
				}
				r_str_ncpy (labels[*labels_n].name, ptr, sizeof (labels[0].name));
				labels[*labels_n].ptr = ptr + strlen (ptr) + 1;
				*labels_n = *labels_n + 1;
			}
		}
		ptr += strlen (ptr) + 1;
	}
	return ptr;
}

R_API int r_cmd_macro_call(RCmdMacro *mac, const char *name) {
	RCore *core = (RCore *)mac->user;
	char *ptr2;
	RListIter *iter;
	RCmdMacroItem *m;

	/* labels */
	int labels_n = 0;
	struct r_cmd_macro_label_t labels[MACRO_LABELS];

	char *str = strdup (name);
	if (!str) {
		r_sys_perror ("strdup");
		return false;
	}
	char *ptr = strchr (str, ')');
	if (!ptr) {
		R_LOG_ERROR ("Missing end ')' parenthesis in '%s'", str);
		free (str);
		return false;
	} else {
		*ptr = '\0';
	}

	int nargs = 0;
	char *args = strchr (str, ' ');
	if (args) {
		*args = '\0';
		args++;
		nargs = r_str_word_set0 (args);
	}

	mac->macro_level++;
	if (mac->macro_level > MACRO_LIMIT) {
		R_LOG_ERROR ("Maximum macro recursivity reached");
		mac->macro_level--;
		free (str);
		return 0;
	}
	ptr = strchr (str, ';');
	if (ptr) {
		*ptr = 0;
	}

	int Gvalue = 0;
	r_cons_break_push (core->cons, NULL, NULL);
	r_list_foreach (mac->macros, iter, m) {
		if (!strcmp (str, m->name)) {
			if (m->nargs != 0 && nargs != m->nargs) {
				R_LOG_ERROR ("Macro '%s' expects %d args, not %d", m->name, m->nargs, nargs);
				mac->macro_level--;
				free (str);
				r_cons_break_pop (core->cons);
				return false;
			}
			// the body can remove or redefine this same macro, so walk a private copy of its code
			char *code = strdup (m->code);
			if (!code) {
				mac->macro_level--;
				free (str);
				r_cons_break_pop (core->cons);
				return false;
			}
			char *ptr = code;
			char *end = strchr (ptr, '\n');
			mac->brk = 0;
			do {
				if (end) {
					*end = '\0';
				}
				if (r_cons_is_breaked (core->cons)) {
					R_LOG_INFO ("Interrupted at (%s)", ptr);
					free (code);
					free (str);
					r_cons_break_pop (core->cons);
					return false;
				}
				r_cons_flush (core->cons);
				/* Label handling */
				ptr2 = r_cmd_macro_label_process (mac, &(labels[0]), &labels_n, ptr);
				if (!ptr2) {
					R_LOG_ERROR ("Oops. invalid label name");
					mac->macro_level--;
					free (code);
					free (str);
					goto out_clean;
				} else if (ptr != ptr2) {
					ptr = ptr2;
					if (end) {
						*end = '\n';
					}
					end = strchr (ptr, '\n');
					continue;
				}
				/* Command execution */
				if (*ptr) {
					mac->num->value = Gvalue;
					int r = r_cmd_macro_cmd_args (mac, ptr, args, nargs);
					// TODO: handle quit? r == 0??
					// quit, exits the macro. like a break
					Gvalue = mac->num->value;
					if (r < 0) {
						free (code);
						free (str);
						r_cons_break_pop (core->cons);
						return r;
					}
				}
				if (end) {
					*end = '\n';
					ptr = end + 1;
				} else {
					mac->macro_level--;
					free (code);
					free (str);
					goto out_clean;
				}

				/* Fetch next command */
				end = strchr (ptr, '\n');
			} while (!mac->brk);
			free (code);
			if (mac->brk) {
				mac->macro_level--;
				free (str);
				goto out_clean;
			}
			// the list node may have been deleted by the body, do not iterate from it
			break;
		}
	}
	R_LOG_ERROR ("No macro named '%s'", str);
	mac->macro_level--;
	free (str);
out_clean:
	r_cons_break_pop (core->cons);
	return true;
}

R_API int r_cmd_macro_break(RCmdMacro *mac, const char *value) {
	mac->brk = 1;
	mac->brk_value = NULL;
	mac->_brk_value = (ut64)r_num_math (mac->num, value);
	if (value && *value) {
		mac->brk_value = &mac->_brk_value;
	}
	return 0;
}
