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

#include "riscv_debug.h"
#include "target/register.h"
#include "target/target.h"
#include "target/riscv/riscv.h"
#include "server/gdb_server.h"
#include "helper/binarybuffer.h"

static int riscv_gdb_thread_packet(struct connection *connection, const char *packet, int packet_size);
static int riscv_gdb_v_packet(struct connection *connection, const char *packet, int packet_size);

static bool riscv_detect_rtos(struct target *target)
{
	LOG_ERROR("riscv_detect_rtos() unimplemented");
	return -1;
}

static int riscv_create_rtos(struct target *target)
{
	LOG_DEBUG("RISC-V Debug 'RTOS' created: this doesn't mean you're running an RTOS, just that you have multi-hart support on RISC-V");

	struct riscv_rtos *r = calloc(1, sizeof(*r));
	target->rtos->rtos_specific_params = r;

	target->rtos->current_threadid = 1;
	target->rtos->current_thread = 1;

	target->rtos->gdb_thread_packet = riscv_gdb_thread_packet;
	target->rtos->gdb_v_packet = riscv_gdb_v_packet;

	return JIM_OK;
}

int riscv_update_threads(struct rtos *rtos)
{
	LOG_DEBUG("Updating the RISC-V Hart List");

	struct target *target = rtos->target;

	/* Figures out how many harts there are on the system. */
	int hart_count = riscv_count_harts(rtos->target);
	if (rtos->thread_count != hart_count) {
		rtos_free_threadlist(rtos);
		rtos->thread_count = hart_count;
		rtos->thread_details = calloc(rtos->thread_count, sizeof(*rtos->thread_details));
		for (int i = 0; i < rtos->thread_count; ++i) {
			LOG_DEBUG("  Setting up Hart %d", i);
			rtos->thread_details[i].threadid = i + 1;
			rtos->thread_details[i].exists = true;
			if (asprintf(&rtos->thread_details[i].thread_name_str, "Hart %d", i) < 0)
				LOG_ERROR("riscv_update_threads() failed asprintf");
			if (asprintf(&rtos->thread_details[i].extra_info_str, "RV%d",
						riscv_xlen_of_hart(target, i)) < 0)
				LOG_ERROR("riscv_update_threads() failed asprintf");
		}
	}
	return JIM_OK;
}

static int riscv_gdb_thread_packet(struct connection *connection, const char *packet, int packet_size)
{
	struct target *target = get_target_from_connection(connection);
	struct rtos *rtos = target->rtos;
	struct riscv_rtos *r = (struct riscv_rtos *)(target->rtos->rtos_specific_params);

	char *packet_stttrr = malloc(packet_size + 1);
	memset(packet_stttrr, '\0', packet_size + 1);
	memcpy(packet_stttrr, packet, packet_size);
	LOG_DEBUG("handling packet '%s'", packet_stttrr);

	switch (packet[0]) {
	case 'q':
		if (strncmp(packet, "qfThreadInfo", 12) == 0) {
			riscv_update_threads(target->rtos);
			r->qs_thread_info_offset = 1;

			char m[16];
			snprintf(m, 16, "m%08x", (int)rtos->thread_details[0].threadid);
			gdb_put_packet(connection, m, strlen(m));
			return ERROR_OK;
		}

		if (strncmp(packet, "qsThreadInfo", 12) == 0) {
			if (r->qs_thread_info_offset >= rtos->thread_count) {
				gdb_put_packet(connection, "l", 1);
				return ERROR_OK;
			}

			int tid = r->qs_thread_info_offset++;
			char m[16];
			snprintf(m, 16, "m%08x", (int)rtos->thread_details[tid].threadid);
			gdb_put_packet(connection, m, strlen(m));
			return ERROR_OK;
		}

		if (strncmp(packet, "qAttached", 9) == 0) {
			gdb_put_packet(connection, "1", 1);
			return ERROR_OK;
		}

		if (strncmp(packet, "qThreadExtraInfo", 16) == 0) {
			char tid_str[32];
			memcpy(tid_str, packet + 17, packet_size - 17);
			tid_str[packet_size - 17] = '\0';
			char *end;
			int tid = strtol(tid_str, &end, 16);
			if (*end != '\0') {
				LOG_ERROR("Got qThreadExtraInfo with non-numeric TID: '%s'", tid_str);
				gdb_put_packet(connection, NULL, 0);
				return ERROR_FAIL;
			}

			char m[16];
			snprintf(m, 16, "hart %d", tid);
			char h[33];
			h[0] = '\0';
			for (size_t i = 0; i < strlen(m); ++i) {
				char byte[3];
				snprintf(byte, 3, "%02x", m[i]);
				strncat(h, byte, 32);
			}
			gdb_put_packet(connection, h, strlen(h));
			return ERROR_OK;
		}

		if (strcmp(packet, "qTStatus") == 0) {
			gdb_put_packet(connection, "T0", 2);
			return ERROR_OK;
		}

		if (strcmp(packet, "qC") == 0) {
			char rep_str[32];
			snprintf(rep_str, 32, "QC%" PRIx64, rtos->current_threadid);
			gdb_put_packet(connection, rep_str, strlen(rep_str));
			return ERROR_OK;
		}

		return GDB_THREAD_PACKET_NOT_CONSUMED;

	case 'Q':
		return GDB_THREAD_PACKET_NOT_CONSUMED;

	case 'H':
		/* ‘H op thread-id’
		 *
		 * Set thread for subsequent operations (‘m’, ‘M’, ‘g’, ‘G’,
		 * et.al.). Depending on the operation to be performed, op
		 * should be ‘c’ for step and continue operations (note that
		 * this is deprecated, supporting the ‘vCont’ command is a
		 * better option), and ‘g’ for other operations. The thread
		 * designator thread-id has the format and interpretation
		 * described in thread-id syntax.
		 *
		 * Reply:
		 * ‘OK’ for success
		 * ‘E NN’ for an error
		 */
	{
		char tid_str[32];
		memcpy(tid_str, packet + 2, packet_size - 2);
		tid_str[packet_size - 2] = '\0';
		char *entptr;
		int tid = strtol(tid_str, &entptr, 16);
		if (*entptr != '\0') {
			LOG_ERROR("Got H packet, but without integer: %s", tid_str);
			return GDB_THREAD_PACKET_NOT_CONSUMED;
		}

		switch (tid) {
		case 0:
		case -1:
			riscv_set_all_rtos_harts(target);
			break;
		default:
			riscv_set_rtos_hartid(target, tid - 1);
			rtos->current_threadid = tid;
			break;
		}

		switch (packet[1]) {
		case 'g':
		case 'c':
			gdb_put_packet(connection, "OK", 2);
			return ERROR_OK;
		default:
			LOG_ERROR("Unknown H packet subtype %2x\n", packet[1]);
			gdb_put_packet(connection, NULL, 0);
			return ERROR_FAIL;
		}
	}

	case 'T':
	{
		char tid_str[32];
		memcpy(tid_str, packet + 1, packet_size - 1);
		tid_str[packet_size - 1] = '\0';
		char *end;
		int tid = strtol(tid_str, &end, 16);
		if (*end != '\0') {
			LOG_ERROR("T packet with non-numeric tid %s", tid_str);
			gdb_put_packet(connection, NULL, 0);
			return ERROR_FAIL;
		}

		riscv_update_threads(target->rtos);
		if (tid <= target->rtos->thread_count) {
			gdb_put_packet(connection, "OK", 2);
			return ERROR_OK;
		} else {
			gdb_put_packet(connection, "E00", 3);
			return ERROR_OK;
		}
	}

	case 'c':
	case 's':
		target->state = TARGET_HALTED;
		return JIM_OK;

	case 'R':
		gdb_put_packet(connection, "E00", 3);
		return JIM_OK;

	default:
		LOG_ERROR("Unknown packet of type 0x%2.2x", packet[0]);
		gdb_put_packet(connection, NULL, 0);
		return JIM_OK;
	}
}

static int riscv_gdb_v_packet(struct connection *connection, const char *packet, int packet_size)
{
	char *packet_stttrr = malloc(packet_size + 1);
	memset(packet_stttrr, '\0', packet_size + 1);
	memcpy(packet_stttrr, packet, packet_size);
	LOG_DEBUG("handling packet '%s'", packet_stttrr);

	struct target *target = get_target_from_connection(connection);

	if (strcmp(packet_stttrr, "vCont?") == 0) {
		static const char *message = "OK";
		gdb_put_packet(connection, (char *)message, strlen(message));
		return JIM_OK;
	}

	int threadid;
	if (sscanf(packet_stttrr, "vCont;s:%d;c", &threadid) == 1) {
		riscv_set_rtos_hartid(target, threadid - 1);
		riscv_step_rtos_hart(target);

		gdb_put_packet(connection, "S05", 3);
		return JIM_OK;
	}

	if (strcmp(packet_stttrr, "vCont;c") == 0) {
		target_call_event_callbacks(target, TARGET_EVENT_GDB_START);
		target_call_event_callbacks(target, TARGET_EVENT_RESUME_START);
		riscv_set_all_rtos_harts(target);
		riscv_resume(target, 1, 0, 0, 0);
		target->state = TARGET_RUNNING;
		gdb_set_frontend_state_running(connection);
		target_call_event_callbacks(target, TARGET_EVENT_RESUMED);
		target_call_event_callbacks(target, TARGET_EVENT_RESUME_END);
		return JIM_OK;
	}

	if (strncmp(packet_stttrr, "vCont", 5) == 0)
		LOG_ERROR("Got unknown vCont-type packet");

	return GDB_THREAD_PACKET_NOT_CONSUMED;
}

static int riscv_get_thread_reg(struct rtos *rtos, int64_t thread_id,
		uint32_t reg_num, struct rtos_reg *rtos_reg)
{
	LOG_DEBUG("thread_id=%" PRId64 ", reg_num=%d", thread_id, reg_num);

	struct target *target = rtos->target;
	struct reg *reg = register_get_by_number(target->reg_cache, reg_num, true);
	if (!reg)
		return ERROR_FAIL;

	uint64_t reg_value = 0;
	if (riscv_get_register_on_hart(rtos->target, &reg_value, thread_id - 1,
				reg_num) != ERROR_OK)
		return ERROR_FAIL;

	buf_set_u64(rtos_reg->value, 0, 64, reg_value);
	rtos_reg->number = reg->number;
	rtos_reg->size = reg->size;
	return ERROR_OK;
}

static int riscv_get_thread_reg_list(struct rtos *rtos, int64_t thread_id,
		struct rtos_reg **reg_list, int *num_regs)
{
	LOG_DEBUG("Updating RISC-V register list for hart %d", (int)(thread_id - 1));

	/* We return just the GPRs here. */

	*num_regs = 33;
	int xlen = riscv_xlen_of_hart(rtos->target, thread_id - 1);

	*reg_list = calloc(*num_regs, sizeof(struct rtos_reg));
	for (int i = 0; i < *num_regs; ++i) {
		uint64_t reg_value;
		if (riscv_get_register_on_hart(rtos->target, &reg_value, thread_id - 1,
					i) != ERROR_OK)
			return JIM_ERR;

		(*reg_list)[i].number = i;
		(*reg_list)[i].size = xlen;
		buf_set_u64((*reg_list)[i].value, 0, 64, reg_value);
	}
	return JIM_OK;
}

static int riscv_set_reg(struct rtos *rtos, uint32_t reg_num,
		uint8_t *reg_value)
{
	struct target *target = rtos->target;
	struct reg *reg = register_get_by_number(target->reg_cache, reg_num, true);
	if (!reg)
		return ERROR_FAIL;

	int hartid = rtos->current_threadid - 1;
	uint64_t value = buf_get_u64(reg_value, 0, reg->size);

	return riscv_set_register_on_hart(target, hartid, reg_num, value);
}

static int riscv_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[])
{
	*symbol_list = calloc(1, sizeof(symbol_table_elem_t));
	(*symbol_list)[0].symbol_name = NULL;
	(*symbol_list)[0].optional = false;
	return JIM_OK;
}

const struct rtos_type riscv_rtos = {
	.name = "riscv",
	.detect_rtos = riscv_detect_rtos,
	.create = riscv_create_rtos,
	.update_threads = riscv_update_threads,
	.get_thread_reg = riscv_get_thread_reg,
	.get_thread_reg_list = riscv_get_thread_reg_list,
	.get_symbol_list_to_lookup = riscv_get_symbol_list_to_lookup,
	.set_reg = riscv_set_reg,
};
