/******************************************************************************
 * 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
 ******************************************************************************/
#ifdef __gnu_linux__
#define _GNU_SOURCE
#endif /* __gnu_linux__ */

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include "proxy.h"
#include "proxy_msg.h"
#include "args.h"
#include "list.h"
#include "compat.h"

#ifdef __linux__
extern int digittoint(int c);
#endif /* __linux__ */

#define ARG_SIZE	100

static char tohex[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

/*
 * Convert a message to a packet ready to send over the wire.
 * 
 * Note: packet length is added when the packet is transmitted.
 */
int
proxy_serialize_msg(proxy_msg *m, char **result)
{
	int		i;
	int		hdr_len;
	int		arg_len;
	int		len = 0;
	char *	packet;

	if (m == NULL)
		return -1;
		
	/*
	 * Compute packet length. 
	 * 
	 * Header length includes leading space and separators.
	 * Body length include encoded strings and separators.
	 */
	hdr_len = MSG_ID_SIZE + MSG_TRANS_ID_SIZE + MSG_NARGS_SIZE + 3;
	
	for (i = 0; i < m->num_args; i++) {
		len += strlen(m->args[i]) + MSG_ARG_LEN_SIZE + 2;
	}
	
	/*
	 * Allocate packet
	 */
	packet = (char *)malloc(hdr_len + len + 1);

	sprintf(packet, " %0*x:%0*x:%0*x", 
		MSG_ID_SIZE, m->msg_id & MSG_ID_MASK, 
		MSG_TRANS_ID_SIZE, m->trans_id & MSG_TRANS_ID_MASK, 
		MSG_NARGS_SIZE, m->num_args & MSG_NARGS_MASK);
	
	len = hdr_len;
	
	for (i = 0; i < m->num_args; i++) {
		arg_len = strlen(m->args[i]);
		sprintf(packet + len, " %0*x:%s", 
			MSG_ARG_LEN_SIZE, arg_len & MSG_ARG_LEN_MASK, m->args[i]);
		len += arg_len + MSG_ARG_LEN_SIZE + 2;
	}
	
	*result = packet;
		
	return 0;
}

void
proxy_get_data(char *str, char **data, int *len)
{
	int		data_len;
	char	ch;
	char *	p;
		
	data_len = strlen(str) / 2;

	*len = data_len;
	*data = p = (char *)malloc(sizeof(char) * data_len);
	
	for (; data_len > 0; data_len--) {
		ch = digittoint(*str++);
		ch <<= 4;
		ch |= digittoint(*str++);

		*p++ = ch;
	}
}

void
proxy_get_int(char *str, int *val)
{
	*val = (int)strtol(str, NULL, 10);
}

void
proxy_get_bitset(char *str, bitset **b)
{
	*b = str_to_bitset(str);
}

/*
 * Convert wire protocol to message. 
 * 
 * Packet length has already been removed.
 */
int
proxy_deserialize_msg(char *packet, int packet_len, proxy_msg **msg)
{
	int			i;
	int			msg_id;
	int			trans_id;
	int			num_args;
	proxy_msg *	m = NULL;
	char *		arg;
	char *		end;
		
	if (packet == NULL || *packet != ' ' || packet_len < MSG_ID_SIZE + MSG_TRANS_ID_SIZE + MSG_NARGS_SIZE + 3) {
		return -1;
	}
	
	/*
	 * message ID
	 */
	packet++; /* Skip space */
	end = packet + MSG_ID_SIZE;
	*end++ = '\0';
	msg_id = strtol(packet, NULL, 16);

	/*
	 * transaction ID
	 */
	packet = end;
	end = packet + MSG_TRANS_ID_SIZE;
	*end++ = '\0';
	trans_id = strtol(packet, NULL, 16);
	 
	/*
	 * number of args
	 */
	packet = end;
	end = packet + MSG_NARGS_SIZE;
	*end++ = '\0';
	num_args = strtol(packet, NULL, 16);

	m = new_proxy_msg(msg_id, trans_id);
	
	packet = end;
	
	for (i = 0; i < num_args; i++) {
		if (proxy_msg_decode_string(packet, &arg, &packet) < 0) {
			free_proxy_msg(m);
			return -1;
		}
		proxy_msg_add_string_nocopy(m, arg);
		packet++; /* skip space */
	}
	
	*msg = m;
	
	return 0;
}

/*
 * Decode string argument. Returns pointer to the character after
 * the end of the string in 'end'
 */
int
proxy_msg_decode_string(char *str, char **arg, char **end)
{
	int		arg_len;
	int 	str_len = strlen(str);
	char *	ep;
	char *	p;
	
	if (str_len < MSG_ARG_LEN_SIZE + 1) {
		return -1;
	}
	
	ep = str + MSG_ARG_LEN_SIZE;
	*ep++ = '\0';
	arg_len = strtol(str, NULL, 16);
	
	if (str_len < MSG_ARG_LEN_SIZE + arg_len + 1) {
		return -1;
	}
	
	p = (char *)malloc(arg_len + 1);
	memcpy(p, ep, arg_len);
	p[arg_len] = '\0';
	
	*arg = p;
	*end = ep + arg_len;
	
	return 0;
}

static void
add_arg(proxy_msg *m, char *arg, int free_arg)
{
	int 	size = m->arg_size;

	if (m->arg_size < m->num_args + 2) {
		m->arg_size += ARG_SIZE;
	}

	if (size == 0) {
		m->arg_size++; // extra space to null terminate arguments
		m->args = (char **)malloc(sizeof(char *) * m->arg_size);
		m->free_args = (int *)malloc(sizeof(int *) * m->arg_size);
	} else if (size < m->arg_size) {
		m->args = (char **)realloc(m->args, sizeof(char *) * m->arg_size);
		m->free_args = (int *)realloc(m->free_args, sizeof(int *) * m->arg_size);
	}
	
	m->args[m->num_args] = arg;
	m->free_args[m->num_args] = free_arg;
	m->num_args++;
	
	/*
	 * Make sure that args are always null terminated
	 */
	m->args[m->num_args] = NULL;
}

void
proxy_msg_add_data(proxy_msg *m, char *data, int len)
{
	int		i;
	char	ch;
	char *	arg;
	char *	p;
	
	if (data == NULL) {
		proxy_msg_add_string(m, "00");
		return;
	}
	
	p = arg = (char *)malloc((len * 2) + 8 + 2);
	
	/*
	 * Encode data 
	 */
	for (i = 0; i < len; i++) {
		ch = *data++;
		*p++ = tohex[(ch >> 4) & 0xf];
		*p++ = tohex[ch & 0xf];
	}
	
	*p = '\0';
	
	proxy_msg_add_string(m, arg);
}

void
proxy_msg_add_int(proxy_msg *m, int val)
{
	char *	str_val;
	
	asprintf(&str_val, "%d", val);
	add_arg(m, str_val, 1);
}

void
proxy_msg_add_string(proxy_msg *m, char *val)
{
	if (val == NULL) {
		val = "";
	}
	add_arg(m, strdup(val), 1);
}

void
proxy_msg_add_string_nocopy(proxy_msg *m, char *val)
{
	if (val == NULL) {
		val = "";
	}
	add_arg(m, val, 0);
}

void
proxy_msg_add_args(proxy_msg *m, int nargs, char **args)
{
	int i;
	
	if (nargs == 0)
		return;
	
	for (i = 0; i < nargs; i++) {
		add_arg(m, strdup(args[i]), 1);
	}
}

void
proxy_msg_add_args_nocopy(proxy_msg *m, int nargs, char **args)
{
	int i;
	
	if (nargs == 0)
		return;
	
	for (i = 0; i < nargs; i++) {
		add_arg(m, args[i], 0);
	}
}

void
proxy_msg_add_keyval_int(proxy_msg *m, char *key, int val)
{
	char *	kv;
	
	asprintf(&kv, "%s=%d", key, val);
	add_arg(m, kv, 1);
}

void
proxy_msg_add_keyval_string(proxy_msg *m, char *key, char *val)
{
	char *	kv;
	
	asprintf(&kv, "%s=%s", key, val);
	add_arg(m, kv, 1);
}

void
proxy_msg_add_bitset(proxy_msg *m, bitset *b)
{
	add_arg(m, bitset_to_str(b), 1);
}

void
proxy_msg_insert_bitset(proxy_msg *m, bitset *b, int idx)
{
	int 	i;
	int		tmp_free;
	char *	tmp_arg;

	if (idx < 0)
		idx = 0;

	/*
	 * First add bitset to end
	 */
	proxy_msg_add_bitset(m, b);

	/*
	 * Just return if the insert location is at or past end
	 */
	if (idx >= m->num_args) {
		return;
	}
	
	/*
	 * Otherwise rotate last argument into required position
	 */
	
	tmp_arg = m->args[m->num_args-1];
	tmp_free = m->free_args[m->num_args-1];
	
	for (i = m->num_args-1; i > idx; i--) {
		m->args[i] = m->args[i-1];
		m->free_args[i] = m->free_args[i-1];
	}
	
	m->args[idx] = tmp_arg;
	m->free_args[idx] = tmp_free;
}

proxy_msg *	
new_proxy_msg(int msg_id, int trans_id) 
{
	proxy_msg *	m = (proxy_msg *)malloc(sizeof(proxy_msg));
	
	m->msg_id = msg_id;
	m->trans_id = trans_id;
	m->arg_size = 0;
	m->num_args = 0;
	m->args = NULL;
	m->free_args = NULL;
	
	return m;
}

void	
free_proxy_msg(proxy_msg *m) 
{
	int i;
	
	for (i = 0; i < m->num_args; i++) {
		if (m->free_args[i]) {
			free(m->args[i]);
		}
	}
		
	free(m->args);
	free(m->free_args);
	free(m);
}

/*
 * Add message to list of messages waiting to be sent.
 */
void
proxy_queue_msg(List *ev_list, proxy_msg *m)
{
	AddToList(ev_list, (void *)m);
}

/*
 * Process any queued messages.
 */
void
proxy_process_msgs(List *msg_list, void (*callback)(proxy_msg *, void *), void *data)
{
	proxy_msg *	m;
	
	if (msg_list == NULL)
		return;
		
	while ((m = (proxy_msg *)RemoveFirst(msg_list)) != NULL) {
		callback(m, data);
		free_proxy_msg(m);
	}
}

