// Notes and useful links:
// This conversation (https://www.sourceware.org/ml/gdb/2009-02/msg00100.html) suggests GDB clients usually ignore error codes
// Useful, but not to be blindly trusted - http://www.embecosm.com/appnotes/ean4/embecosm-howto-rsp-server-ean4-issue-2.html
// https://github.com/llvm-mirror/lldb/blob/master/docs/lldb-gdb-remote.txt
// http://www.cygwin.com/ml/gdb/2008-05/msg00166.html

#include "gdbserver/core.h"
#include "gdbr_common.h"
#include "libgdbr.h"
#include "packet.h"
#include "utils.h"
#include <r_util.h>

static int _server_handle_qSupported(libgdbr_t *g) {
	int ret;
	char buf[128];
	snprintf (buf, sizeof (buf), "PacketSize=%x;QStartNoAckMode+;qXfer:exec-file:read+", (ut32) (g->read_max - 1));
	if ((ret = handle_qSupported (g)) < 0) {
		return -1;
	}
	return send_msg (g, buf);
}

static int _server_handle_qTStatus(libgdbr_t *g) {
	int ret;
	// TODO Handle proper reporting of trace status
	const char *message = "";
	if ((ret = send_ack (g)) < 0) {
		return -1;
	}
	return send_msg (g, message);
}

static int _server_handle_qOffsets(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	char buf[64], *ptr;
	ptr = buf + snprintf (buf, sizeof (buf), "TextSeg=");
	if (send_ack (g) < 0) {
		return -1;
	}
	if (cmd_cb (g, core_ptr, "dm", ptr, sizeof (buf) - (ptr - buf) - 1) < 0) {
		send_msg (g, "");
		return -1;
	}
	return send_msg (g, buf);
}

static int _server_handle_exec_file_read(libgdbr_t *g, gdbr_server_cmd_cb cb,
					 void *core_ptr) {
	char *buf, *ptr, cmd[64] = {0};
	size_t buflen = 512;
	int ret;
	if (send_ack (g) < 0) {
		return -1;
	}
	ptr = g->data + strlen ("qXfer:exec-file:read:");
	if (*ptr != ':') {
		int pid;
		if ((pid = (int) strtol (ptr, NULL, 16)) <= 0 || pid != g->pid) {
			return send_msg (g, "E00");
		}
	}
	if (!(ptr = strchr (ptr, ':'))) {
		return send_msg (g, "E00");
	}
	ptr++;
	snprintf (cmd, sizeof (cmd) - 1, "if%s", ptr);
	if (!(buf = malloc (buflen))) {
		send_msg (g, "E01");
		return -1;
	}
	if (cb (g, core_ptr, cmd, buf, buflen) < 0) {
		free (buf);
		return send_msg (g, "E01");
	}
	ret = send_msg (g, buf);
	free (buf);
	return ret;
}

static int _server_handle_M(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	if (send_ack (g) < 0) {
		return -1;
	}
	char *memstr, *buf;
	ut64 memlen, memlen2, addr;
	g->data[g->data_len] = '\0';
	if (sscanf (g->data + 1, "%"PFMT64x",%"PFMT64x, &addr, &memlen) != 2) {
		return send_msg (g, "E01");
	}
	memlen2 = memlen * 2;
	if (!(memstr = strchr (g->data, ':')) || !*(++memstr)) {
		return send_msg (g, "E01");
	}
	if (memlen2 != strlen (memstr)) {
		return send_msg (g, "E01");
	}
	if (!(buf = malloc (memlen2 + 64))) {
		return send_msg (g, "E01");
	}
	snprintf (buf, memlen2 + 63, "wx 0x%s @ 0x%"PFMT64x, memstr, addr);
	buf[memlen2 + 63] = '\0';
	R_LOG_DEBUG ("%s: buf: %s", __func__, buf);
	if (cmd_cb (g, core_ptr, buf, NULL, 0) < 0) {
		free (buf);
		return send_msg (g, "E01");
	}
	free (buf);
	return send_msg (g, "OK");
}

static int _server_handle_s(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	char message[64];
	if (send_ack (g) < 0) {
		return -1;
	}
	if (g->data_len > 1) {
		// We don't handle s[addr] packet
		return send_msg (g, "E01");
	}
	if (cmd_cb (g, core_ptr, "ds", NULL, 0) < 0) {
		send_msg (g, "E01");
		return -1;
	}
	if (cmd_cb (g, core_ptr, "?", message, sizeof (message)) < 0) {
		send_msg (g, "");
		return -1;
	}
	return send_msg (g, message);
}

static int _server_handle_c(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	char message[64];
	if (send_ack (g) < 0) {
		return -1;
	}
	if (g->data_len > 1) {
		// We don't handle s[addr] packet
		return send_msg (g, "E01");
	}
	if (cmd_cb (g, core_ptr, "dc", NULL, 0) < 0) {
		send_msg (g, "E01");
		return -1;
	}
	if (cmd_cb (g, core_ptr, "?", message, sizeof (message)) < 0) {
		send_msg (g, "");
		return -1;
	}
	return send_msg (g, message);
}

static int _server_handle_ques(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	char message[64] = {0};
	if (send_ack (g) < 0) {
		return -1;
	}
	if (cmd_cb (g, core_ptr, "?", message, sizeof (message)) < 0) {
		send_msg (g, "");
		return -1;
	}
	return send_msg (g, message);
}

static int _server_handle_qC(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	char *buf;
	int ret;
	size_t buf_len = 80;
	if ((ret = send_ack (g)) < 0) {
		return -1;
	}
	if (!(buf = malloc (buf_len))) {
		return -1;
	}
	if ((ret = cmd_cb (g, core_ptr, "dp", buf, buf_len)) < 0) {
		free (buf);
		return -1;
	}
	ret = send_msg (g, buf);
	free (buf);
	return ret;
}

static int _server_handle_k(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	send_ack (g);
	return -1;
}

static int _server_handle_vKill(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	if (send_ack (g) < 0) {
		return -1;
	}
	// TODO handle killing of pid
	send_msg (g, "OK");
	return -1;
}

static int _server_handle_z(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	if (send_ack (g) < 0) {
		return -1;
	}
	char set; // Z = set, z = remove
	int type;
	ut64 addr;
	char cmd[64];
	if (sscanf (g->data, "%c%d,%"PFMT64x, &set, &type, &addr) != 3) {
		return send_msg (g, "E01");
	}
	if (type != 0) {
		// TODO handle hw breakpoints and watchpoints
		return send_msg (g, "E01");
	}
	switch (set) {
	case 'Z':
		// Set
		snprintf (cmd, sizeof (cmd) - 1, "db 0x%"PFMT64x, addr);
		break;
	case 'z':
		// Remove
		snprintf (cmd, sizeof (cmd) - 1, "db- 0x%"PFMT64x, addr);
		break;
	default:
		return send_msg (g, "E01");
	}
	if (cmd_cb (g, core_ptr, cmd, NULL, 0) < 0) {
		send_msg (g, "E01");
		return -1;
	}
	return send_msg (g, "OK");
}

static int _server_handle_vCont(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	char *action = NULL;
	char *save_ptr = NULL;
	if (send_ack (g) < 0) {
		return -1;
	}
	g->data[g->data_len] = '\0';
	if (g->data[5] == '?') {
		// Query about everything we support
		return send_msg (g, "vCont;c;s");
	}
	if (!(action = r_str_tok_r (g->data, ";", &save_ptr))) {
		return send_msg (g, "E01");
	}
	while ((action = r_str_tok_r (NULL, ";", &save_ptr))) {
		R_LOG_DEBUG ("%s: action: %s", __func__, action);
		switch (action[0]) {
		case 's':
			// TODO handle thread selections
			if (cmd_cb (g, core_ptr, "ds", NULL, 0) < 0) {
				send_msg (g, "E01");
				return -1;
			}
			return send_msg (g, "OK");
		case 'c':
			// TODO handle thread selections
			if (cmd_cb (g, core_ptr, "dc", NULL, 0) < 0) {
				send_msg (g, "E01");
				return -1;
			}
			return send_msg (g, "OK");
		default:
			// TODO support others
			return send_msg (g, "E01");
		}
	}
	return -1;
}

static int _server_handle_qAttached(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	if (send_ack (g) < 0) {
		return -1;
	}
	// TODO check if process was attached or created
	// Right now, says that process was created
	return send_msg (g, "0");
}

// TODO, proper handling of Hg and Hc (right now handled identically)

// Set thread for all operations other than "step" and "continue"
static int _server_handle_Hg(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	// We don't yet support multiprocess. Client is not supposed to send Hgp. If we receive it anyway,
	// send error
	char cmd[32];
	unsigned int tid;
	if (send_ack (g) < 0) {
		return -1;
	}
	if (g->data_len <= 2 || isalpha ((ut8)g->data[2])) {
		return send_msg (g, "E01");
	}
	// Hg-1 = "all threads", Hg0 = "pick any thread"
	if (g->data[2] == '0' || !strncmp (g->data + 2, "-1", 2)) {
		return send_msg (g, "OK");
	}
	if (sscanf (g->data + 2, "%x", &tid) != 1) {
		return send_msg (g, "E01");
	}
	snprintf (cmd, sizeof (cmd) - 1, "dpt=%u", tid);
	// Set thread for future operations
	if (cmd_cb (g, core_ptr, cmd, NULL, 0) < 0) {
		send_msg (g, "E01");
		return -1;
	}
	return send_msg (g, "OK");
}

// Set thread for "step" and "continue"
static int _server_handle_Hc(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	// Usually this is only sent with Hc-1. Still. Set the threads for next operations
	char cmd[32];
	unsigned int tid;
	if (send_ack (g) < 0) {
		return -1;
	}
	if (g->data_len <= 2 || isalpha ((ut8)g->data[2])) {
		return send_msg (g, "E01");
	}
	// Hc-1 = "all threads", Hc0 = "pick any thread"
	if (g->data[2] == '0' || !strncmp (g->data + 2, "-1", 2)) {
		return send_msg (g, "OK");
	}
	if (sscanf (g->data + 2, "%x", &tid) != 1) {
		return send_msg (g, "E01");
	}
	snprintf (cmd, sizeof (cmd) - 1, "dpt=%u", tid);
	// Set thread for future operations
	if (cmd_cb (g, core_ptr, cmd, NULL, 0) < 0) {
		send_msg (g, "E01");
		return -1;
	}
	return send_msg (g, "OK");
}

static int _server_handle_qfThreadInfo(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	char *buf;
	int ret;
	size_t buf_len = g->stub_features.pkt_sz;
	if ((ret = send_ack (g)) < 0) {
		return -1;
	}
	if (!(buf = malloc (buf_len))) {
		return -1;
	}
	if ((ret = cmd_cb (g, core_ptr, "dpt", buf, buf_len)) < 0) {
		free (buf);
		return -1;
	}
	ret = send_msg (g, buf);
	free (buf);
	return ret;
}

static int _server_handle_qsThreadInfo(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	// TODO handle overflow from qfThreadInfo. Otherwise this won't work with programs with many threads
	if (send_ack (g) < 0 || send_msg (g, "l") < 0) {
		return -1;
	}
	return 0;
}

static int _server_handle_g(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	char *buf;
	// To be very safe
	int buf_len = 4096;
	int ret;
	if (send_ack (g) < 0) {
		return -1;
	}
	if (!(buf = malloc (buf_len))) {
		send_msg (g, "E01");
		return -1;
	}
	memset (buf, 0, buf_len);
	if ((buf_len = cmd_cb (g, core_ptr, "dr", buf, buf_len)) < 0) {
		free (buf);
		send_msg (g, "E01");
		return -1;
	}
	ret = send_msg (g, buf);
	free (buf);
	return ret;
}

static int _server_handle_m(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	int length;
	if (send_ack (g) < 0) {
		return -1;
	}
	g->data[g->data_len] = 0;
	char *cmd = strdup (g->data);
	char *buf = malloc (g->data_max / 2);
	if (!buf) {
		free (cmd);
		send_msg (g, "E01");
		return -1;
	}
	if ((length = cmd_cb (g, core_ptr, cmd, buf, (g->data_max / 2) - 1)) < 0
	    || length >= g->data_max / 2) {
		free (cmd);
		free (buf);
		return send_msg (g, "E01");
	}
	pack_hex (buf, length, g->data);
	free (cmd);
	free (buf);
	g->data[length * 2] = '\0';
	return send_msg (g, g->data);
}

// Read register number
static int _server_handle_p(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	char message[128] = {0}, cmd[128] = { 0 };
	int regnum, i;
	if (send_ack (g) < 0) {
		return -1;
	}
	if (!isxdigit ((ut8)g->data[1]) || !g->registers) {
		return send_msg (g, "E01");
	}
	regnum = strtol (g->data + 1, NULL, 16);
	if (regnum < 0) {
		return send_msg (g, "E01");
	}
	// We need to do this because length of register set is not known
	for (i = 0; i < regnum; i++) {
		if (!*g->registers[i].name) {
			return send_msg (g, "E01");
		}
	}
	if (snprintf (cmd, sizeof (cmd) - 1, "dr %s", g->registers[regnum].name) < 0) {
		send_msg (g, "E01");
		return -1;
	}
	if (cmd_cb (g, core_ptr, cmd, message, sizeof (message)) < 0) {
		send_msg (g, "E01");
		return -1;
	}
	return send_msg (g, message);
}

// Write register number
static int _server_handle_P(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	char *ptr, *end, *cmd;
	int regnum, i;
	if (send_ack (g) < 0) {
		return -1;
	}
	if (g->data_len < 4 || !g->registers || !isxdigit ((ut8)g->data[1])) {
		return send_msg (g, "E01");
	}
	ptr = memchr (g->data + 2, '=', g->data_len - 2);
	if (!ptr) {
		return send_msg (g, "E01");
	}
	long parsed_regnum = strtol (g->data + 1, &end, 16);
	if (parsed_regnum < 0 || parsed_regnum > INT_MAX || end != ptr) {
		return send_msg (g, "E01");
	}
	regnum = (int)parsed_regnum;
	ptr++;
	size_t value_len = g->data_len - (ptr - g->data);
	if (!value_len || value_len > ST32_MAX) {
		return send_msg (g, "E01");
	}
	size_t value_pos;
	for (value_pos = 0; value_pos < value_len; value_pos++) {
		if (!isxdigit ((ut8)ptr[value_pos])) {
			return send_msg (g, "E01");
		}
	}
	// We need to do this because length of register set is not known
	for (i = 0; i < regnum; i++) {
		if (!*g->registers[i].name) {
			return send_msg (g, "E01");
		}
	}
	cmd = r_str_newf ("dr %s=0x%.*s", g->registers[regnum].name, (int)value_len, ptr);
	if (!cmd) {
		return send_msg (g, "E01");
	}
	if (cmd_cb (g, core_ptr, cmd, NULL, 0) < 0) {
		free (cmd);
		send_msg (g, "E01");
		return -1;
	}
	free (cmd);
	return send_msg (g, "OK");
}

static int _server_handle_vMustReplyEmpty(libgdbr_t *g) {
	if (send_ack (g) < 0) {
		return -1;
	}
	return send_msg (g, "");
}

static int _server_handle_qTfV(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	// TODO
	if (send_ack (g) < 0) {
		return -1;
	}
	return send_msg (g, "");
}

int gdbr_server_serve(libgdbr_t *g, gdbr_server_cmd_cb cmd_cb, void *core_ptr) {
	R_RETURN_VAL_IF_FAIL (g, -1);
	int ret = -1;
	for (;;) {
		if (read_packet (g, false) < 0) {
			break;
			// continue;
		}
		if (g->data_len == 0) {
			continue;
		}
		if (r_str_startswith (g->data, "k")) {
			return _server_handle_k (g, cmd_cb, core_ptr);
		}
		if (r_str_startswith (g->data, "vKill")) {
			return _server_handle_vKill (g, cmd_cb, core_ptr);
		}
		if (r_str_startswith (g->data, "qSupported")) {
			if ((ret = _server_handle_qSupported (g)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "qTStatus")) {
			if ((ret = _server_handle_qTStatus (g)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "qC") && g->data_len == 2) {
			if ((ret = _server_handle_qC (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "qAttached")) {
			if ((ret = _server_handle_qAttached (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "vMustReplyEmpty")) {
			if ((ret = _server_handle_vMustReplyEmpty (g)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "qTfV")) {
			if ((ret = _server_handle_qTfV (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "qfThreadInfo")) {
			if ((ret = _server_handle_qfThreadInfo (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "qsThreadInfo")) {
			if ((ret = _server_handle_qsThreadInfo (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "Hg")) {
			if ((ret = _server_handle_Hg (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "Hc")) {
			if ((ret = _server_handle_Hc (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "?")) {
			if ((ret = _server_handle_ques (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "g") && g->data_len == 1) {
			if ((ret = _server_handle_g (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "vCont")) {
			if ((ret = _server_handle_vCont (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "qOffsets")) {
			if ((ret = _server_handle_qOffsets (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (g->data[0] == 'z' || g->data[0] == 'Z') {
			if ((ret = _server_handle_z (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (g->data[0] == 's') {
			if ((ret = _server_handle_s (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (g->data[0] == 'c') {
			if ((ret = _server_handle_c (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "m")) {
			if ((ret = _server_handle_m (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "M")) {
			if ((ret = _server_handle_M (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "P")) {
			if ((ret = _server_handle_P (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "p")) {
			if ((ret = _server_handle_p (g, cmd_cb, core_ptr)) < 0) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "qXfer:exec-file:read:")) {
			if ((ret = _server_handle_exec_file_read (g, cmd_cb, core_ptr)) < 0 ) {
				return ret;
			}
			continue;
		}
		if (r_str_startswith (g->data, "QStartNoAckMode")) {
			if (send_ack (g) < 0) {
				return -1;
			}
			g->no_ack = true;
			R_LOG_DEBUG ("[noack mode enabled]");
			if (send_msg (g, "OK") < 0) {
				return -1;
			}
			continue;
		}
		// Unrecognized packet
		if (send_ack (g) < 0 || send_msg (g, "") < 0) {
			g->data[g->data_len] = '\0';
			R_LOG_ERROR ("%s: Unknown packet: %s", __func__, g->data);
			return -1;
		}
	};
	return ret;
}
