/*
 * Copyright (c) 2008 Jiri Svoboda
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup debug
 * @{
 */
/** @file
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syscall.h>
#include <ipc/ipc.h>
#include <fibril.h>
#include <errno.h>
#include <udebug.h>
#include <async.h>
#include <string.h>

#include "cmd.h"
#include "cons.h"
#include "dthread.h"
#include "breakpoint.h"
#include "include/arch.h"
#include "main.h"

void thread_debug_start(unsigned thread_hash);

#define IN_BUF_SIZE 64
static char in_buf[IN_BUF_SIZE];

#define MAX_ARGC 10
int cmd_argc;
char *cmd_argv[MAX_ARGC + 1];	/* need one spare field for cmd_split() */


int next_thread_id;

int app_phone;
volatile bool abort_debug;

volatile int paused;

static void command_split(char *cmd_str)
{
	char *p = cmd_str;

	if (*p == '\0') {
		cmd_argc = 0;
		return;
	}

	cmd_argc = 1;
	cmd_argv[0] = p;

	while (*p != '\0') {
		if (*p == ' ') {
			cmd_argv[cmd_argc++] = p + 1;
			*p = '\0';
		}
		++p;
	}
}

static void command_run(void)
{
	int i;
	int cmp_len;
	int len;

	int idx_found;
	int num_found;

	len = strlen(cmd_argv[0]);
	cmp_len = 1;

	/* Silence warnings */
	num_found = 0;
	idx_found = 0;

	while (cmp_len <= len + 1) {

		num_found = 0;
		i = 0;
		while (cmd_table[i].name != NULL) {
			if (strncmp(cmd_table[i].name, cmd_argv[0], cmp_len) == 0) {
				idx_found = i;
				++num_found;
			}
			++i;
		}

		if (num_found < 2) break;

		++cmp_len;
	}

	if (num_found == 0) {
		cons_printf("Unknown command. Try one of:\n");
		cmd_help(0, NULL);
		return;
	}

	if (cmd_argc - 1 != cmd_table[idx_found].argc) {
		cons_printf("Command '%s' expects %d arguments\n",
		cmd_table[idx_found].name, cmd_table[idx_found].argc);
		return;
	}

	(*cmd_table[idx_found].proc)(cmd_argc, cmd_argv);
}

static void thread_stop(void)
{
	dthread_t *dt;
	uintptr_t pc;

	dt = dthread_get();
	pc = dthread_get_pc(dt);
	cons_printf("[thread %d] stopped at 0x%lx\n", dt->id, pc);
	dthread_stop_me();
	cons_printf("[thread %d] go\n", dt->id);
}

/*
 * Called by a fibril (from arch code) when a breakpoint is hit.
 */
void breakpoint_hit(breakpoint_t *b)
{
	dthread_t *dt;

	dt = dthread_get();
	cons_printf("Thread %d hit breakpoint %d at 0x%lx\n",
	    dt->id, b->id, b->addr);
	thread_stop();
}

/*
 * Called by a fibril (from arch code) when a single instruction
 * in singlestep is executed
 */
void singlestep_hit(void)
{
	cons_printf("singlestep hit\n");
	thread_stop();
}

static int task_connect(int taskid)
{
	int rc;
	unsigned evmask;

	cons_printf("ipc_connect_kbox(%d)... ", taskid);
	rc = ipc_connect_kbox(taskid);
	cons_printf("-> %d\n", rc);
	app_phone = rc;
	if (rc < 0) return rc;

	cons_printf("udebug_begin()... ");
	rc = udebug_begin(app_phone);
	cons_printf("-> %d\n", rc);
	if (rc < 0) return rc;

	evmask = UDEBUG_EM_ALL & ~(UDEBUG_EM_SYSCALL_B | UDEBUG_EM_SYSCALL_E);
	cons_printf("udebug_set_evmask(0x%x)... ", evmask);
	rc = udebug_set_evmask(app_phone, evmask);
	cons_printf("-> %d\n", rc);
	if (rc < 0) return rc;

	return 0;
}

#define THASH_BUF_INIT_LENGTH 32

static int get_thread_list(thash_t **thash_buf_ptr, int *n)
{
	int rc;
	size_t tb_copied;
	size_t tb_needed;
	int i;
	size_t tb_size;
	thash_t *thash_buf;

	tb_size = THASH_BUF_INIT_LENGTH * sizeof(thash_t);
	thash_buf = malloc(tb_size);

	rc = udebug_thread_read(app_phone, thash_buf,
		tb_size, &tb_copied, &tb_needed);
	if (rc < 0) return rc;

	if (tb_needed > tb_size) {
		/* Larger buffer needed  */

		free(thash_buf);

		tb_size = tb_needed;
		thash_buf = malloc(tb_size);

		if (!thash_buf) {
			printf("malloc failed\n");
			exit(1);
		}

		/* Try again */
		
		rc = udebug_thread_read(app_phone, thash_buf,
			tb_size, &tb_copied, &tb_needed);

		if (rc < 0) return rc;
	}

	*n = tb_copied / sizeof(thash_t);

	cons_printf("thread hashes:");

	for (i = 0; i < *n; ++i) {
		cons_printf("0x%x\n", thash_buf[i]);
	}

	cons_printf("Total of %u threads\n", *n);

	*thash_buf_ptr = thash_buf;

	return 0;
}

static void event_thread_b(unsigned hash)
{
	async_serialize_start();
	cons_printf("new thread, hash 0x%x\n", hash);
	async_serialize_end();

	thread_debug_start(hash);
}

static void debug_event(thash_t thash, udebug_event_t ev_type, sysarg_t val0)
{
	switch (ev_type) {
	case UDEBUG_EVENT_STOP:
		cons_printf("stop event\n");
		thread_stop();
		break;
	case UDEBUG_EVENT_THREAD_B:
		event_thread_b(val0);
		break;
	case UDEBUG_EVENT_THREAD_E:
		cons_printf("thread 0x%x exited\n", val0);
		abort_debug = true;
		break;
	case UDEBUG_EVENT_BREAKPOINT:
		arch_event_breakpoint(thash);
		break;
	case UDEBUG_EVENT_TRAP:
		arch_event_trap(dthread_get());
		break;
	default:
		cons_printf("unknown event type %d\n", ev_type);
		break;
	}
}

static int debug_loop(void *dt_arg)
{
	int rc;
	udebug_event_t ev_type;
	unsigned val0, val1;
	dthread_t *dt;

	dt = (dthread_t *)dt_arg;

	cons_printf("debug_loop(%d)\n", dt->id);

	while (!abort_debug) {

		/* Run thread until an event occurs */
		rc = udebug_go(app_phone, dt->hash, &ev_type, &val0, &val1);

		if (ev_type == UDEBUG_EVENT_FINISHED) {
			cons_printf("thread %u debugging finished\n", dt->id);
			break;
		}
		if (rc >= 0) debug_event(dt->hash, ev_type, val0);
	}

	cons_printf("debug_loop(%d) exiting\n", dt->id);
	return 0;
}

void thread_debug_start(unsigned thash)
{
	fid_t fid;
	dthread_t *dt;

	dt = dthread_new(thash);

	fid = fibril_create(debug_loop, (void *)dt);
	if (fid == 0) {
		cons_printf("Warning: Failed creating fibril\n");
	}
	dt->fid = fid;

	fibril_add_ready(fid);
}

static void debug_active_task(void)
{
	int taskid;
	int i;
	int rc;

	thash_t *thash_buffer;
	int n_threads;

	cons_printf("Breakpoint Debugger\n");
	cons_printf("Press 'c' to connect\n");
	while ((i = getchar()) != 'c')
		putchar(i);

	taskid = 14;
	rc = task_connect(taskid);
	if (rc < 0) {
		cons_printf("Failed to connect to task %d\n", taskid);
		return;
	}

	cons_printf("Connected to task %d\n", taskid);

	rc = get_thread_list(&thash_buffer, &n_threads);
	if (rc < 0) {
		cons_printf("Failed to get thread list\n", rc);
		return;
	}

	abort_debug = false;

	for (i = 0; i < n_threads; i++) {
		thread_debug_start(thash_buffer[i]);
	}

	while (!quit) {
		cons_read_line(in_buf, IN_BUF_SIZE);
		command_split(in_buf);
		if (cmd_argc == 0) continue;

		command_run();
	}

	cons_printf("terminate debugging session...\n");
	abort_debug = true;
	udebug_end(app_phone);
	ipc_hangup(app_phone);

	cons_printf("done\n");
	return;
}

static void main_init(void)
{
	next_thread_id = 1;
	paused = 0;
	
	list_initialize(&dthreads);
	breakpoint_init();
	cwt = NULL;
}

int main(void)
{
	main_init();

	while (1) {
		debug_active_task();
	}
}

/** @}
 */
