/******************************************************************************
 * Copyright (c) 2005 The Regents of the University of California.
 * This material was produced under U.S. Government contract W-7405-ENG-36
 * for Los Alamos National Laboratory, which is operated by the University
 * of California for the U.S. Department of Energy. The U.S. Government has
 * rights to use, reproduce, and distribute this software. NEITHER THE
 * GOVERNMENT NOR THE UNIVERSITY MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR
 * ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is modified
 * to produce derivative works, such modified software should be clearly  
 * marked, so as not to confuse it with the version available from LANL.
 *
 * Additionally, this program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * LA-CC 04-115
 ******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include "sdm.h"
#include "dbg.h"
#include "dbg_proxy.h"
#include "session.h"
#include "proxy.h"
#include "proxy_event.h"
#include "bitset.h"
#include "handler.h"

/*
 * The TIMEOUT determines how often the debugger polls progress routines.
 * Too often and it uses too much CPU, too few and things will slow down...
 */
#define CLIENT_TIMEOUT	50000
#define SERVER_TIMEOUT	50000

static sdm_idset		tmp_idset;
static bitset *			tmp_bitset;

sdm_idset
bitset_to_idset(bitset *b)
{
	int bit;

	sdm_set_clear(tmp_idset);

	for (bit = 0; bit < bitset_size(b); bit++) {
		if (bitset_test(b, bit)) {
			sdm_set_add_element(tmp_idset, bit);
		}
	}

	return tmp_idset;
}

bitset *
idset_to_bitset(sdm_idset ids)
{
	sdm_id		id;

	bitset_clear(tmp_bitset);

	for (id = sdm_set_first(ids); !sdm_set_done(ids); id = sdm_set_next(ids)) {
		bitset_set(tmp_bitset, id);
	}

	return tmp_bitset;
}

static int
session_event_handler(sdm_message msg, void *data)
{
	int				len;
	unsigned char *	buf;
	dbg_event *		ev;
	proxy_msg *		pmsg;
	session *		s = (session *)data;

	sdm_message_get_payload(msg, &buf, &len);

	if (proxy_deserialize_msg(buf, len, &pmsg) < 0) {
 		fprintf(stderr, "could not deserialize message");
 	 	sdm_message_free(msg);
 	 	return -1;
	}

	if (DbgDeserializeEvent(pmsg->msg_id, pmsg->num_args, pmsg->args, &ev) < 0) {
 		fprintf(stderr, "bad conversion to debug event");
 	 	sdm_message_free(msg);
 	 	return -1;
	}

	ev->procs = idset_to_bitset(sdm_message_get_source(msg));

	if (s->sess_event_handler != NULL) {
 		s->sess_event_handler(ev, s->sess_event_data);
	}

 	sdm_message_free(msg);

 	return 0;
}

static int
send_command(bitset *procs, int timeout, unsigned char *cmd, int len, void *cbdata)
{
	sdm_message 	msg;
	sdm_idset		dest = bitset_to_idset(procs);

	msg = sdm_message_new(cmd, len);
	sdm_aggregate_set_value(sdm_message_get_aggregate(msg), SDM_AGGREGATE_TIMEOUT, timeout);
	sdm_set_union(sdm_message_get_destination(msg), dest);

	sdm_aggregate_message(msg, SDM_AGGREGATE_DOWNSTREAM);

	sdm_message_set_send_callback(msg, sdm_message_free);
	sdm_message_send(msg);

	return 0;
}

/*
 * Session initialization
 */
int
DbgInit(session **sess, int argc, char *argv[])
{
	session *		s;
	
	*sess = s = (session *)malloc(sizeof(session));
	
	if (sdm_init(argc, argv) < 0) {
		return -1;
	}

	s->sess_procs = sdm_route_get_size() - 1;

	tmp_idset = sdm_set_new();
	tmp_bitset = bitset_new(s->sess_procs);

	sdm_aggregate_set_completion_callback(session_event_handler, (void *)s);

	return 0;
}

int
DbgConnect(session *s)
{
	return proxy_clnt_connect(s->sess_proxy);
}

int
DbgCreate(session *s)
{
	return proxy_clnt_create(s->sess_proxy);
}

int
DbgStartSession(session *s, char *exe, char *path, char *args)
{
	int				res;
	int				len;
	unsigned char *	buf;
	bitset *		procs = bitset_new(s->sess_procs);
	proxy_msg *		msg = new_proxy_msg(DBG_STARTSESSION_CMD, 0);

	proxy_msg_add_int(msg, SERVER_TIMEOUT);
	proxy_msg_add_string(msg, exe);
	proxy_msg_add_string(msg, path);
	proxy_msg_add_string(msg, args);
	proxy_serialize_msg(msg, &buf, &len);

	/*
	 * Create a bitset containing all processes
	 */
	bitset_invert(procs);

	res = send_command(procs, DBG_EV_WAITALL, buf, len, NULL);

	free_proxy_msg(msg);
	bitset_free(procs);
	
	return res;
}

/*
 * Breakpoint operations
 */
int 
DbgSetLineBreakpoint(session *s, bitset *set, int bpid, char *file, int line)
{
	int				res;
	int				len;
	unsigned char *	buf;
	proxy_msg *		msg = new_proxy_msg(DBG_SETLINEBREAKPOINT_CMD, 0);

	proxy_msg_add_int(msg, bpid);
	proxy_msg_add_string(msg, file);
	proxy_msg_add_int(msg, line);

	proxy_serialize_msg(msg, &buf, &len);

	res = send_command(set, DBG_EV_WAITALL, buf, len, NULL);

	free_proxy_msg(msg);
	
	return res;
}

int 
DbgSetFuncBreakpoint(session *s, bitset *set, int bpid, char *file, char *func)
{
	proxy_msg *	msg = new_proxy_msg(DBG_SETFUNCBREAKPOINT_CMD, 0);

	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_int(msg, bpid);
	proxy_msg_add_string(msg, file);
	proxy_msg_add_string(msg, func);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int 
DbgDeleteBreakpoint(session *s, bitset *set, int bpid)
{
	proxy_msg *	msg = new_proxy_msg(DBG_DELETEBREAKPOINT_CMD, 0);
	
	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_int(msg, bpid);
	
	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int 
DbgEnableBreakpoint(session *s, bitset *set, int bpid)
{
	proxy_msg *	msg = new_proxy_msg(DBG_ENABLEBREAKPOINT_CMD, 0);
	
	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_int(msg, bpid);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int 
DbgDisableBreakpoint(session *s, bitset *set, int bpid)
{
	proxy_msg *	msg = new_proxy_msg(DBG_DISABLEBREAKPOINT_CMD, 0);
	
	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_int(msg, bpid);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int 
DbgConditionBreakpoint(session *s, bitset *set, int bpid, char *expr)
{
	proxy_msg *	msg = new_proxy_msg(DBG_CONDITIONBREAKPOINT_CMD, 0);
	
	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_int(msg, bpid);
	proxy_msg_add_string(msg, expr);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int 
DbgBreakpointAfter(session *s, bitset *set, int bpid, int icount)
{
	proxy_msg *	msg = new_proxy_msg(DBG_BREAKPOINTAFTER_CMD, 0);
	
	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_int(msg, bpid);
	proxy_msg_add_int(msg, icount);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int 
DbgSetWatchpoint(session *s, bitset *set, int bpid, char *expr, int access, int read, char *condition, int icount)
{
	proxy_msg *	msg = new_proxy_msg(DBG_SETWATCHPOINT_CMD, 0);

	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_int(msg, bpid);
	proxy_msg_add_string(msg, expr);
	proxy_msg_add_int(msg, access);
	proxy_msg_add_int(msg, read);
	proxy_msg_add_string(msg, condition);
	proxy_msg_add_int(msg, icount);
	
	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}
/*
 * Process control operations
 */
int 
DbgGo(session *s, bitset *set)
{
	int				res;
	int				len;
	unsigned char *	buf;
	proxy_msg *		msg = new_proxy_msg(DBG_GO_CMD, 0);

	proxy_serialize_msg(msg, &buf, &len);

	res = send_command(set, DBG_EV_WAITSOME, buf, len, NULL);

	free_proxy_msg(msg);
	
	return res;
}

int 
DbgStep(session *s, bitset *set, int count, int type)
{
	int				res;
	int				len;
	unsigned char *	buf;
	proxy_msg *		msg = new_proxy_msg(DBG_STEP_CMD, 0);
	
	proxy_msg_add_int(msg, count);
	proxy_msg_add_int(msg, type);
	proxy_serialize_msg(msg, &buf, &len);

	res = send_command(set, DBG_EV_WAITSOME, buf, len, NULL);
	
	free_proxy_msg(msg);
	
	return res;
}

int 
DbgTerminate(session *s, bitset *set)
{
	proxy_msg *	msg = new_proxy_msg(DBG_TERMINATE_CMD, 0);

	proxy_msg_add_bitset(msg, set);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int 
DbgSuspend(session *s, bitset *set)
{
	proxy_msg *	msg = new_proxy_msg(DBG_SUSPEND_CMD, 0);

	proxy_msg_add_bitset(msg, set);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}
/*
 * Stack frame operations
 */
int 
DbgListStackframes(session *s, bitset *set, int low, int depth)
{
	int				res;
	int				len;
	unsigned char *	buf;
	proxy_msg *		msg = new_proxy_msg(DBG_LISTSTACKFRAMES_CMD, 0);
	
	proxy_msg_add_int(msg, low);
	proxy_msg_add_int(msg, depth);
	proxy_serialize_msg(msg, &buf, &len);

	res = send_command(set, DBG_EV_WAITALL, buf, len, NULL);
	
	return 0;
}

int 
DbgSetCurrentStackframe(session *s, bitset *set, int level)
{
	proxy_msg *	msg = new_proxy_msg(DBG_SETCURRENTSTACKFRAME_CMD, 0);
	
	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_int(msg, level);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

/*
 * Expression/variable operations
 */
int 
DbgEvaluateExpression(session *s, bitset *set, char *exp)
{
	int				res;
	int				len;
	unsigned char *	buf;
	proxy_msg *		msg = new_proxy_msg(DBG_EVALUATEEXPRESSION_CMD, 0);

	proxy_msg_add_string(msg, exp);
	proxy_serialize_msg(msg, &buf, &len);

	res = send_command(set, DBG_EV_WAITSOME, buf, len, NULL);
	
	free_proxy_msg(msg);
	
	return res;
}

int 
DbgGetType(session *s, bitset *set, char *exp)
{
	proxy_msg *	msg = new_proxy_msg(DBG_GETTYPE_CMD, 0);

	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_string(msg, exp);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int 
DbgListLocalVariables(session *s, bitset *set)
{
	proxy_msg *	msg = new_proxy_msg(DBG_LISTLOCALVARIABLES_CMD, 0);

	proxy_msg_add_bitset(msg, set);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int 
DbgListArguments(session *s, bitset *set, int low, int high)
{
	proxy_msg *	msg = new_proxy_msg(DBG_LISTARGUMENTS_CMD, 0);

	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_int(msg, low);
	proxy_msg_add_int(msg, high);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int 
DbgListGlobalVariables(session *s, bitset *set)
{
	proxy_msg *	msg = new_proxy_msg(DBG_LISTGLOBALVARIABLES_CMD, 0);

	proxy_msg_add_bitset(msg, set);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int
DbgListInfoThreads(session *s, bitset *set) 
{
	proxy_msg *	msg = new_proxy_msg(DBG_LISTINFOTHREADS_CMD, 0);

	proxy_msg_add_bitset(msg, set);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int 
DbgSetThreadSelect(session *s, bitset *set, int threadNum) 
{
	proxy_msg *	msg = new_proxy_msg(DBG_SETTHREADSELECT_CMD, 0);

	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_int(msg, threadNum);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int 
DbgStackInfoDepth(session *s, bitset *set) 
{
	proxy_msg *	msg = new_proxy_msg(DBG_STACKINFODEPTH_CMD, 0);

	proxy_msg_add_bitset(msg, set);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int 
DbgDataReadMemory(session *s, bitset *set, long offset, char *address, char *format, int wordSize, int rows, int cols, char *asChar) 
{
	proxy_msg *	msg = new_proxy_msg(DBG_DATAREADMEMORY_CMD, 0);

	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_int(msg, offset);
	proxy_msg_add_string(msg, address);
	proxy_msg_add_string(msg, format);
	proxy_msg_add_int(msg, wordSize);
	proxy_msg_add_int(msg, rows);
	proxy_msg_add_int(msg, cols);
	proxy_msg_add_string(msg, asChar);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int 
DbgDataWriteMemory(session *s, bitset *set, long offset, char *address, char *format, int wordSize, char *value) 
{
	proxy_msg *	msg = new_proxy_msg(DBG_DATAWRITEMEMORY_CMD, 0);

	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_int(msg, offset);
	proxy_msg_add_string(msg, address);
	proxy_msg_add_string(msg, format);
	proxy_msg_add_int(msg, wordSize);
	proxy_msg_add_string(msg, value);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int
DbgListSignals(session *s, bitset *set, char *name)
{
	proxy_msg *	msg = new_proxy_msg(DBG_LISTSIGNALS_CMD, 0);

	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_string(msg, name);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int
DbgCLIHandle(session *s, bitset *set, char *arg)
{
	proxy_msg *	msg = new_proxy_msg(DBG_CLIHANDLE_CMD, 0);

	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_string(msg, arg);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int
DbgQuit(session *s)
{
	int				res;
	int				len;
	unsigned char *	buf;
	bitset *		procs = bitset_new(s->sess_procs);
	proxy_msg *		msg = new_proxy_msg(DBG_QUIT_CMD, 0);

	proxy_serialize_msg(msg, &buf, &len);

	bitset_invert(procs);

	res = send_command(procs, DBG_EV_WAITALL, buf, len, NULL);

	free_proxy_msg(msg);
	bitset_free(procs);

	return res;
}

int
DbgEvaluatePartialExpression(session *s, bitset *set, char *name, char *key, int listChildren, int express)
{
	proxy_msg *	msg = new_proxy_msg(DBG_EVALUATEPARTIALEXPRESSION_CMD, 0);

	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_string(msg, name);
	proxy_msg_add_string(msg, key);
	proxy_msg_add_int(msg, listChildren);
	proxy_msg_add_int(msg, express);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

int
DbgDeletePartialExpression(session *s, bitset *set, char *arg)
{
	proxy_msg *	msg = new_proxy_msg(DBG_DELETEPARTIALEXPRESSION_CMD, 0);

	proxy_msg_add_bitset(msg, set);
	proxy_msg_add_string(msg, arg);

	proxy_clnt_queue_msg(s->sess_proxy, msg);
	
	free_proxy_msg(msg);
	
	return 0;
}

/*
 * Event handling
 */
int
DbgProgress(session *s)
{
	return sdm_progress();
}

void
DbgRegisterEventHandler(session *s, void (*event_callback)(dbg_event *, void *), void *data)
{
	s->sess_event_handler = event_callback;
	s->sess_event_data = data;
}

/**
 * Unregister file descriptor handler
 */
void
DbgUnregisterEventHandler(session *s, void (*event_callback)(dbg_event *, void *))
{
	s->sess_event_handler = NULL;
}

/**
 * Register a handler for file descriptor events.
 */
void
DbgRegisterReadFileHandler(session *s, int fd, int (*file_handler)(int, void *), void *data)
{
	RegisterFileHandler(fd, READ_FILE_HANDLER, file_handler, data);
}

void
DbgRegisterWriteFileHandler(session *s, int fd, int (*file_handler)(int, void *), void *data)
{
	RegisterFileHandler(fd, WRITE_FILE_HANDLER, file_handler, data);
}

void
DbgRegisterExceptFileHandler(session *s, int fd, int (*file_handler)(int, void *), void *data)
{
	RegisterFileHandler(fd, EXCEPT_FILE_HANDLER, file_handler, data);
}

/**
 * Unregister file descriptor handler
 */
void
DbgUnregisterFileHandler(session *s, int fd)
{
	UnregisterFileHandler(fd);
}
