// SPDX-FileCopyrightText: 2009-2020 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only

#include "rz_types.h"
#include "rz_config.h"
#include "rz_cons.h"
#include "rz_core.h"
#include "rz_debug.h"
#include "rz_io.h"

static void printPadded(RzCore *core, int pad) {
	if (pad < 1) {
		pad = 8;
	}
	char *fmt = rz_str_newf("0x%%0%d" PFMT64x, pad);
	char *off = rz_str_newf(fmt, core->offset);
	rz_cons_printf("%s\n", off);
	free(off);
	free(fmt);
}

RZ_IPI bool rz_core_seek_to_register(RzCore *core, const char *regname, bool is_silent) {
	ut64 off = rz_core_reg_getv_by_role_or_name(core, regname);
	return rz_core_seek_opt(core, off, true, !is_silent);
}

RZ_IPI int rz_core_seek_opcode_backward(RzCore *core, int numinstr, bool silent) {
	int i, val = 0;
	// N previous instructions
	ut64 addr = core->offset;
	int ret = 0;
	if (rz_core_prevop_addr(core, core->offset, numinstr, &addr)) {
		ret = core->offset - addr;
	} else {
#if 0
		// core_asm_bwdis_len is really buggy and we should remove it. seems like prevop_addr
		// works as expected, because is the one used from visual
		ret = rz_core_asm_bwdis_len (core, &instr_len, &addr, numinstr);
#endif
		addr = core->offset;
		const int mininstrsize = rz_analysis_archinfo(core->analysis, RZ_ANALYSIS_ARCHINFO_MIN_OP_SIZE);
		for (i = 0; i < numinstr; i++) {
			ut64 prev_addr = rz_core_prevop_addr_force(core, addr, 1);
			if (prev_addr == UT64_MAX) {
				prev_addr = addr - mininstrsize;
			}
			if (prev_addr == UT64_MAX || prev_addr >= core->offset) {
				break;
			}
			RzAsmOp op = { 0 };
			rz_core_seek(core, prev_addr, true);
			rz_asm_disassemble(core->rasm, &op, core->block, 32);
			if (op.size < mininstrsize) {
				op.size = mininstrsize;
			}
			val += op.size;
			addr = prev_addr;
		}
	}
	rz_core_seek_opt(core, addr, true, !silent);
	val += ret;
	return val;
}

RZ_IPI int rz_core_seek_opcode_forward(RzCore *core, int n, bool silent) {
	// N forward instructions
	int i, ret, val = 0;
	if (!silent) {
		rz_core_seek_mark(core);
	}
	for (val = i = 0; i < n; i++) {
		RzAnalysisOp op;
		ret = rz_analysis_op(core->analysis, &op, core->offset, core->block,
			core->blocksize, RZ_ANALYSIS_OP_MASK_BASIC);
		if (ret < 1) {
			ret = 1;
		}
		rz_core_seek_delta(core, ret, false);
		rz_analysis_op_fini(&op);
		val += ret;
	}
	rz_core_seek_save(core);
	return val;
}

RZ_IPI int rz_core_seek_opcode(RzCore *core, int n, bool silent) {
	int val = (n < 0)
		? rz_core_seek_opcode_backward(core, -n, silent)
		: rz_core_seek_opcode_forward(core, n, silent);
	core->num->value = val;
	return val;
}

static void cmd_seek_opcode(RzCore *core, const char *input, bool silent) {
	if (input[0] == '?') {
		RZ_LOG_ERROR("core: Usage: so [-][n]\n");
		return;
	}
	if (!strcmp(input, "-")) {
		input = "-1";
	}
	int n = rz_num_math(core->num, input);
	if (n == 0) {
		n = 1;
	}
	rz_core_seek_opcode(core, n, silent);
}

RZ_IPI int rz_seek_search(void *data, const char *input) {
	RzCore *core = (RzCore *)data;
	const char *pfx = rz_config_get(core->config, "search.prefix");
	const ut64 saved_from = rz_config_get_i(core->config, "search.from");
	const ut64 saved_maxhits = rz_config_get_i(core->config, "search.maxhits");
	int kwidx = core->search->n_kws; // (int)rz_config_get_i (core->config, "search.kwidx")-1;
	if (kwidx < 0) {
		kwidx = 0;
	}
	switch (input[0]) {
	case ' ':
	case 'v':
	case 'V':
	case 'w':
	case 'W':
	case 'z':
	case 'm':
	case 'c':
	case 'A':
	case 'e':
	case 'E':
	case 'i':
	case 'R':
	case 'r':
	case '/':
	case 'x':
		rz_config_set_i(core->config, "search.from", core->offset + 1);
		rz_config_set_i(core->config, "search.maxhits", 1);
		rz_core_cmdf(core, "sd 1@e:cfg.seek.silent=true; /%s; sd -1@e:cfg.seek.silent=true; s %s%d_0; f- %s%d_0",
			input, pfx, kwidx, pfx, kwidx);
		rz_config_set_i(core->config, "search.from", saved_from);
		rz_config_set_i(core->config, "search.maxhits", saved_maxhits);
		break;
	case '?':
		RZ_LOG_ERROR("core: Usage: s/.. arg.\n");
		rz_cons_printf("/?\n");
		break;
	default:
		RZ_LOG_ERROR("core: unknown search method\n");
		break;
	}
	return 0;
}

static RzCmdStatus bool2cmdstatus(bool res) {
	return res ? RZ_CMD_STATUS_OK : RZ_CMD_STATUS_ERROR;
}

RZ_IPI RzCmdStatus rz_seek_handler(RzCore *core, int argc, const char **argv) {
	if (argc == 1) {
		rz_cons_printf("0x%" PFMT64x "\n", core->offset);
		return RZ_CMD_STATUS_OK;
	}

	rz_core_seek_mark(core);

	// NOTE: hack to make it work with local function labels
	char *ptr;
	if ((ptr = strstr(argv[1], "+.")) != NULL) {
		char *dup = strdup(argv[1]);
		dup[ptr - argv[1]] = '\x00';
		core->offset = rz_num_math(core->num, dup);
		free(dup);
	}

	ut64 addr = rz_num_math(core->num, argv[1]);
	if (core->num->nc.errors) {
		RZ_LOG_ERROR("Cannot seek to unknown address '%s'\n", core->num->nc.calc_buf);
		return RZ_CMD_STATUS_ERROR;
	}
	return bool2cmdstatus(rz_core_seek_and_save(core, addr, true));
}

RZ_IPI RzCmdStatus rz_seek_delta_handler(RzCore *core, int argc, const char **argv) {
	st64 delta = strtoll(argv[1], NULL, 0);
	return bool2cmdstatus(rz_core_seek_delta(core, delta, true));
}

RZ_IPI RzCmdStatus rz_seek_padded_handler(RzCore *core, int argc, const char **argv) {
	int n = argc > 1 ? atoi(argv[1]) : 0;
	printPadded(core, n);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_seek_base_handler(RzCore *core, int argc, const char **argv) {
	return bool2cmdstatus(rz_core_seek_base(core, argv[1], true));
}

RZ_IPI RzCmdStatus rz_seek_blocksize_backward_handler(RzCore *core, int argc, const char **argv) {
	int n = argc == 2 ? rz_num_math(core->num, argv[1]) : 1;
	if (n < 1) {
		RZ_LOG_ERROR("invalid argument: number is negative or zero\n");
		return RZ_CMD_STATUS_ERROR;
	}
	int delta = -core->blocksize / n;
	return bool2cmdstatus(rz_core_seek_delta(core, delta, true));
}

RZ_IPI RzCmdStatus rz_seek_blocksize_forward_handler(RzCore *core, int argc, const char **argv) {
	int n = argc == 2 ? rz_num_math(core->num, argv[1]) : 1;
	if (n < 1) {
		RZ_LOG_ERROR("invalid argument: number is negative or zero\n");
		return RZ_CMD_STATUS_ERROR;
	}
	int delta = core->blocksize / n;
	return bool2cmdstatus(rz_core_seek_delta(core, delta, true));
}

RZ_IPI RzCmdStatus rz_seek_redo_handler(RzCore *core, int argc, const char **argv) {
	return bool2cmdstatus(rz_core_seek_redo(core));
}

RZ_IPI RzCmdStatus rz_seek_undo_handler(RzCore *core, int argc, const char **argv) {
	return bool2cmdstatus(rz_core_seek_undo(core));
}

RZ_IPI RzCmdStatus rz_seek_undo_reset_handler(RzCore *core, int argc, const char **argv) {
	rz_core_seek_reset(core);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_seek_history_list_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	RzList *list = rz_core_seek_list(core);
	RzListIter *iter;
	RzCoreSeekItem *undo;
	PJ *pj = state->d.pj;
	rz_cmd_state_output_array_start(state);
	bool current_met = false;
	rz_list_foreach (list, iter, undo) {
		RzFlagItem *f = rz_flag_get_at(core->flags, undo->offset, true);
		const char *comment;
		char *name = NULL;
		if (f) {
			if (f->offset != undo->offset) {
				name = rz_str_newf("%s+%" PFMT64d, f->name, undo->offset - f->offset);
			} else {
				name = strdup(f->name);
			}
		}
		current_met |= undo->is_current;
		switch (state->mode) {
		case RZ_OUTPUT_MODE_JSON:
			pj_o(pj);
			pj_kn(pj, "offset", undo->offset);
			pj_kn(pj, "cursor", undo->cursor);
			if (name) {
				pj_ks(pj, "name", name);
			}
			pj_kb(pj, "current", undo->is_current);
			pj_end(pj);
			break;
		case RZ_OUTPUT_MODE_STANDARD:
			comment = "";
			if (undo->is_current) {
				comment = " # current seek";
			} else if (current_met) {
				comment = " # redo";
			}
			rz_cons_printf("0x%" PFMT64x " %s%s\n", undo->offset, name ? name : "", comment);
			break;
		case RZ_OUTPUT_MODE_RIZIN:
			if (undo->is_current) {
				rz_cons_printf("# Current seek @ 0x%" PFMT64x "\n", undo->offset);
			} else if (current_met) {
				rz_cons_printf("f redo_%d @ 0x%" PFMT64x "\n", RZ_ABS(undo->idx - 1), undo->offset);
			} else {
				rz_cons_printf("f undo_%d @ 0x%" PFMT64x "\n", RZ_ABS(undo->idx + 1), undo->offset);
			}
			break;
		default:
			rz_warn_if_reached();
			break;
		}
		free(name);
	}
	rz_cmd_state_output_array_end(state);
	rz_list_free(list);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_seek_asz_handler(RzCore *core, int argc, const char **argv) {
	ut64 align = rz_num_math(NULL, argv[1]);
	ut64 addr = core->offset;
	rz_core_seek_mark(core);
	if (argc > 2) {
		addr = rz_num_math(core->num, argv[2]);
		rz_core_seek(core, addr, false);
	}
	return bool2cmdstatus(rz_core_seek_align(core, align, true));
}

RZ_IPI RzCmdStatus rz_seek_basicblock_handler(RzCore *core, int argc, const char **argv) {
	return bool2cmdstatus(rz_core_seek_analysis_bb(core, core->offset, true));
}

RZ_IPI RzCmdStatus rz_seek_function_handler(RzCore *core, int argc, const char **argv) {
	RzAnalysisFunction *fcn = NULL;
	ut64 addr;
	if (argc == 1) {
		fcn = rz_analysis_get_fcn_in(core->analysis, core->offset, 0);
		if (!fcn) {
			return RZ_CMD_STATUS_ERROR;
		}
		addr = rz_analysis_function_max_addr(fcn);
	} else {
		fcn = rz_analysis_get_function_byname(core->analysis, argv[1]);
		if (!fcn) {
			return RZ_CMD_STATUS_ERROR;
		}
		addr = fcn->addr;
	}
	return bool2cmdstatus(rz_core_seek_and_save(core, addr, true));
}

RZ_IPI RzCmdStatus rz_seek_function_current_handler(RzCore *core, int argc, const char **argv) {
	RzAnalysisFunction *fcn = rz_analysis_get_fcn_in(core->analysis, core->offset, 0);
	if (!fcn) {
		return RZ_CMD_STATUS_ERROR;
	}
	return bool2cmdstatus(rz_core_seek_and_save(core, fcn->addr, true));
}

RZ_IPI RzCmdStatus rz_seek_begin_handler(RzCore *core, int argc, const char **argv) {
	RzIOMap *map = rz_io_map_get(core->io, core->offset);
	ut64 addr = map ? map->itv.addr : 0;
	return bool2cmdstatus(rz_core_seek_and_save(core, addr, true));
}

RZ_IPI RzCmdStatus rz_seek_end_handler(RzCore *core, int argc, const char **argv) {
	RzIOMap *map = rz_io_map_get(core->io, core->offset);
	ut64 addr;
	if (map) {
		// XXX: this +2 is a hack. must fix gap between sections
		addr = map->itv.addr + map->itv.size + 2;
	} else {
		if (!core->file) {
			return RZ_CMD_STATUS_ERROR;
		}
		addr = rz_io_fd_size(core->io, core->file->fd);
	}
	return bool2cmdstatus(rz_core_seek_and_save(core, addr, true));
}

RZ_IPI RzCmdStatus rz_seek_next_handler(RzCore *core, int argc, const char **argv) {
	const char *nkey;
	if (argc == 1) {
		nkey = rz_config_get(core->config, "scr.nkey");
	} else {
		nkey = argv[1];
	}
	return bool2cmdstatus(rz_core_seek_next(core, nkey, true));
}

RZ_IPI RzCmdStatus rz_seek_prev_handler(RzCore *core, int argc, const char **argv) {
	const char *nkey;
	if (argc == 1) {
		nkey = rz_config_get(core->config, "scr.nkey");
	} else {
		nkey = argv[1];
	}
	return bool2cmdstatus(rz_core_seek_prev(core, nkey, true));
}

RZ_IPI RzCmdStatus rz_seek_opcode_handler(RzCore *core, int argc, const char **argv) {
	cmd_seek_opcode(core, argc > 1 ? argv[1] : "", false);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_seek_register_handler(RzCore *core, int argc, const char **argv) {
	return bool2cmdstatus(rz_core_seek_to_register(core, argv[1], false));
}
