/*
 misc.c : irssi

    Copyright (C) 1999 Timo Sirainen

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "module.h"
#include "misc.h"
#include "commands.h"

typedef struct {
	int condition;
	GInputFunction function;
        void *data;
} IRSSI_INPUT_REC;

static int irssi_io_invoke(GIOChannel *source, GIOCondition condition,
			   void *data)
{
	IRSSI_INPUT_REC *rec = data;
	int icond = 0;

	if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
		/* error, we have to call the function.. */
		if (rec->condition & G_IO_IN)
			icond |= G_INPUT_READ;
		else
			icond |= G_INPUT_WRITE;
	}

	if (condition & (G_IO_IN | G_IO_PRI))
		icond |= G_INPUT_READ;
	if (condition & G_IO_OUT)
		icond |= G_INPUT_WRITE;

	if (rec->condition & icond)
		rec->function(rec->data, source, icond);

	return TRUE;
}

int g_input_add_full(GIOChannel *source, int priority, int condition,
		     GInputFunction function, void *data)
{
        IRSSI_INPUT_REC *rec;
	unsigned int result;
	GIOCondition cond;

	rec = g_new(IRSSI_INPUT_REC, 1);
	rec->condition = condition;
	rec->function = function;
	rec->data = data;

	cond = (GIOCondition) (G_IO_ERR|G_IO_HUP|G_IO_NVAL);
	if (condition & G_INPUT_READ)
		cond |= G_IO_IN|G_IO_PRI;
	if (condition & G_INPUT_WRITE)
		cond |= G_IO_OUT;

	result = g_io_add_watch_full(source, priority, cond,
				     irssi_io_invoke, rec, g_free);

	return result;
}

int g_input_add(GIOChannel *source, int condition,
		GInputFunction function, void *data)
{
	return g_input_add_full(source, G_PRIORITY_DEFAULT, condition,
				function, data);
}

/* easy way to bypass glib polling of io channel internal buffer */
int g_input_add_poll(int fd, int priority, int condition,
		     GInputFunction function, void *data)
{
	GIOChannel *source = g_io_channel_unix_new(fd);
	int ret = g_input_add_full(source, priority, condition, function, data);
	g_io_channel_unref(source);
	return ret;
}

int g_timeval_cmp(const GTimeVal *tv1, const GTimeVal *tv2)
{
	if (tv1->tv_sec < tv2->tv_sec)
		return -1;
	if (tv1->tv_sec > tv2->tv_sec)
		return 1;

	return tv1->tv_usec < tv2->tv_usec ? -1 :
		tv1->tv_usec > tv2->tv_usec ? 1 : 0;
}

long get_timeval_diff(const GTimeVal *tv1, const GTimeVal *tv2)
{
	long secs, usecs;

	secs = tv1->tv_sec - tv2->tv_sec;
	usecs = tv1->tv_usec - tv2->tv_usec;
	if (usecs < 0) {
		usecs += 1000000;
		secs--;
	}
	usecs = usecs/1000 + secs * 1000;

	return usecs;
}

int find_substr(const char *list, const char *item)
{
	const char *ptr;

	g_return_val_if_fail(list != NULL, FALSE);
	g_return_val_if_fail(item != NULL, FALSE);

	if (*item == '\0')
		return FALSE;

	for (;;) {
		while (i_isspace(*list)) list++;
		if (*list == '\0') break;

		ptr = strchr(list, ' ');
		if (ptr == NULL) ptr = list+strlen(list);

		if (g_ascii_strncasecmp(list, item, ptr-list) == 0 &&
		    item[ptr-list] == '\0')
			return TRUE;

		list = ptr;
	}

	return FALSE;
}

int strarray_find(char **array, const char *item)
{
	char **tmp;
	int index;

	g_return_val_if_fail(array != NULL, -1);
	g_return_val_if_fail(item != NULL, -1);

	index = 0;
	for (tmp = array; *tmp != NULL; tmp++, index++) {
		if (g_ascii_strcasecmp(*tmp, item) == 0)
			return index;
	}

	return -1;
}

GSList *gslist_find_string(GSList *list, const char *key)
{
	for (; list != NULL; list = list->next)
		if (g_strcmp0(list->data, key) == 0) return list;

	return NULL;
}

GSList *gslist_find_icase_string(GSList *list, const char *key)
{
	for (; list != NULL; list = list->next)
		if (g_ascii_strcasecmp(list->data, key) == 0) return list;

	return NULL;
}

void *gslist_foreach_find(GSList *list, FOREACH_FIND_FUNC func, const void *data)
{
	void *ret;

	while (list != NULL) {
		ret = func(list->data, (void *) data);
                if (ret != NULL) return ret;

		list = list->next;
	}

	return NULL;
}

void gslist_free_full (GSList *list, GDestroyNotify free_func)
{
	GSList *tmp;

	if (list == NULL)
		return;

	for (tmp = list; tmp != NULL; tmp = tmp->next)
		free_func(tmp->data);

	g_slist_free(list);
}

GSList *gslist_remove_string (GSList *list, const char *str)
{
	GSList *l;

	l = g_slist_find_custom(list, str, (GCompareFunc) g_strcmp0);
	if (l != NULL)
		return g_slist_remove_link(list, l);

	return list;
}

GSList *gslist_delete_string (GSList *list, const char *str, GDestroyNotify free_func)
{
	GSList *l;

	l = g_slist_find_custom(list, str, (GCompareFunc) g_strcmp0);
	if (l != NULL) {
		free_func(l->data);
		return g_slist_delete_link(list, l);
	}

	return list;
}

/* `list' contains pointer to structure with a char* to string. */
char *gslistptr_to_string(GSList *list, int offset, const char *delimiter)
{
	GString *str;
	char **data, *ret;

	str = g_string_new(NULL);
	while (list != NULL) {
		data = G_STRUCT_MEMBER_P(list->data, offset);

		if (str->len != 0) g_string_append(str, delimiter);
		g_string_append(str, *data);
		list = list->next;
	}

        ret = str->str;
	g_string_free(str, FALSE);
	return ret;
}

/* `list' contains char* */
char *gslist_to_string(GSList *list, const char *delimiter)
{
	GString *str;
	char *ret;

	str = g_string_new(NULL);
	while (list != NULL) {
		if (str->len != 0) g_string_append(str, delimiter);
		g_string_append(str, list->data);

		list = list->next;
	}

        ret = str->str;
	g_string_free(str, FALSE);
	return ret;
}

void hash_save_key(char *key, void *value, GSList **list)
{
        *list = g_slist_append(*list, key);
}

/* remove all the options from the optlist hash table that are valid for the
 * command cmd */
GList *optlist_remove_known(const char *cmd, GHashTable *optlist)
{
	GList *list, *tmp, *next;

	list = g_hash_table_get_keys(optlist);
	if (cmd != NULL && list != NULL) {
		for (tmp = list; tmp != NULL; tmp = next) {
			char *option = tmp->data;
			next = tmp->next;

			if (command_have_option(cmd, option))
				list = g_list_remove(list, option);
		}
	}

	return list;
}

GList *glist_find_string(GList *list, const char *key)
{
	for (; list != NULL; list = list->next)
		if (g_strcmp0(list->data, key) == 0) return list;

	return NULL;
}

GList *glist_find_icase_string(GList *list, const char *key)
{
	for (; list != NULL; list = list->next)
		if (g_ascii_strcasecmp(list->data, key) == 0) return list;

	return NULL;
}

char *stristr(const char *data, const char *key)
{
	const char *max;
	int keylen, datalen, pos;

	keylen = strlen(key);
	datalen = strlen(data);

	if (keylen > datalen)
		return NULL;
	if (keylen == 0)
		return (char *) data;

	max = data+datalen-keylen;
	pos = 0;
	while (data <= max) {
		if (key[pos] == '\0')
                        return (char *) data;

		if (i_toupper(data[pos]) == i_toupper(key[pos]))
			pos++;
		else {
			data++;
                        pos = 0;
		}
	}

	return NULL;
}

#define isbound(c) \
	((unsigned char) (c) < 128 && \
	(i_isspace(c) || i_ispunct(c)))

static char *strstr_full_case(const char *data, const char *key, int icase)
{
	const char *start, *max;
	int keylen, datalen, pos, match;

	keylen = strlen(key);
	datalen = strlen(data);

	if (keylen > datalen)
		return NULL;
	if (keylen == 0)
		return (char *) data;

	max = data+datalen-keylen;
	start = data; pos = 0;
	while (data <= max) {
		if (key[pos] == '\0') {
			if (data[pos] != '\0' && !isbound(data[pos])) {
				data++;
				pos = 0;
                                continue;
			}
			return (char *) data;
		}

		match = icase ? (i_toupper(data[pos]) == i_toupper(key[pos])) :
				 data[pos] == key[pos];

		if (match && (pos != 0 || data == start || isbound(data[-1])))
			pos++;
		else {
			data++;
                        pos = 0;
		}
	}

	return NULL;
}

char *strstr_full(const char *data, const char *key)
{
        return strstr_full_case(data, key, FALSE);
}

char *stristr_full(const char *data, const char *key)
{
        return strstr_full_case(data, key, TRUE);
}

/* convert ~/ to $HOME */
char *convert_home(const char *path)
{
	const char *home;

	if (*path == '~' && (*(path+1) == '/' || *(path+1) == '\0')) {
		home = g_get_home_dir();
		if (home == NULL)
			home = ".";

		return g_strconcat(home, path+1, NULL);
	} else {
		return g_strdup(path);
	}
}

int g_istr_equal(gconstpointer v, gconstpointer v2)
{
	return g_ascii_strcasecmp((const char *) v, (const char *) v2) == 0;
}

int g_istr_cmp(gconstpointer v, gconstpointer v2)
{
	return g_ascii_strcasecmp((const char *) v, (const char *) v2);
}

guint g_istr_hash(gconstpointer v)
{
	const signed char *p;
	guint32 h = 5381;

	for (p = v; *p != '\0'; p++)
		h = (h << 5) + h + g_ascii_toupper(*p);

	return h;
}

/* Find `mask' from `data', you can use * and ? wildcards. */
int match_wildcards(const char *cmask, const char *data)
{
	char *mask, *newmask, *p1, *p2;
	int ret;

	newmask = mask = g_strdup(cmask);
	for (; *mask != '\0' && *data != '\0'; mask++) {
		if (*mask != '*') {
			if (*mask != '?' && i_toupper(*mask) != i_toupper(*data))
				break;

			data++;
			continue;
		}

		while (*mask == '?' || *mask == '*') mask++;
		if (*mask == '\0') {
			data += strlen(data);
			break;
		}

		p1 = strchr(mask, '*');
		p2 = strchr(mask, '?');
		if (p1 == NULL || (p2 < p1 && p2 != NULL)) p1 = p2;

		if (p1 != NULL) *p1 = '\0';

		data = stristr(data, mask);
		if (data == NULL) break;

		data += strlen(mask);
		mask += strlen(mask)-1;

		if (p1 != NULL) *p1 = p1 == p2 ? '?' : '*';
	}

	while (*mask == '*') mask++;

	ret = data != NULL && *data == '\0' && *mask == '\0';
	g_free(newmask);

	return ret;
}

/* Return TRUE if all characters in `str' are numbers.
   Stop when `end_char' is found from string. */
int is_numeric(const char *str, char end_char)
{
	g_return_val_if_fail(str != NULL, FALSE);

	if (*str == '\0' || *str == end_char)
		return FALSE;

	while (*str != '\0' && *str != end_char) {
		if (!i_isdigit(*str)) return FALSE;
		str++;
	}

	return TRUE;
}

/* replace all `from' chars in string to `to' chars. returns `str' */
char *replace_chars(char *str, char from, char to)
{
	char *p;

	for (p = str; *p != '\0'; p++) {
		if (*p == from) *p = to;
	}
	return str;
}

int octal2dec(int octal)
{
	int dec, n;

	dec = 0; n = 1;
	while (octal != 0) {
		dec += n*(octal%10);
		octal /= 10; n *= 8;
	}

	return dec;
}

int dec2octal(int decimal)
{
	int octal, pos;

	octal = 0; pos = 0;
	while (decimal > 0) {
		octal += (decimal & 7)*(pos == 0 ? 1 : pos);
		decimal /= 8;
		pos += 10;
	}

	return octal;
}

/* string -> uoff_t */
uoff_t str_to_uofft(const char *str)
{
#ifdef UOFF_T_LONG_LONG
	return (uoff_t)strtoull(str, NULL, 10);
#else
	return (uoff_t)strtoul(str, NULL, 10);
#endif
}

/* convert all low-ascii (<32) to ^<A..> combinations */
char *show_lowascii(const char *str)
{
	char *ret, *p;

	ret = p = g_malloc(strlen(str)*2+1);
	while (*str != '\0') {
		if ((unsigned char) *str >= 32)
			*p++ = *str;
		else {
			*p++ = '^';
			*p++ = *str + 'A'-1;
		}
		str++;
	}
	*p = '\0';

	return ret;
}

/* Get time in human readable form with localtime() + asctime() */
char *my_asctime(time_t t)
{
	struct tm *tm;
	char *str;
        int len;

	tm = localtime(&t);
	if (tm == NULL)
	    return g_strdup("???");

	str = g_strdup(asctime(tm));

	len = strlen(str);
	if (len > 0) str[len-1] = '\0';
        return str;
}

/* Returns number of columns needed to print items.
   save_column_widths is filled with length of each column. */
int get_max_column_count(GSList *items, COLUMN_LEN_FUNC len_func,
			 int max_width, int max_columns,
			 int item_extra, int item_min_size,
			 int **save_column_widths, int *rows)
{
        GSList *tmp;
	int **columns, *columns_width, *columns_rows;
	int item_pos, items_count;
	int ret, len, max_len, n, col;

	items_count = g_slist_length(items);
	if (items_count == 0) {
		*save_column_widths = NULL;
                *rows = 0;
		return 0;
	}

	len = max_width/(item_extra+item_min_size);
        if (len <= 0) len = 1;
	if (max_columns <= 0 || len < max_columns)
                max_columns = len;

	columns = g_new0(int *, max_columns);
	columns_width = g_new0(int, max_columns);
	columns_rows = g_new0(int, max_columns);

	for (n = 1; n < max_columns; n++) {
		columns[n] = g_new0(int, n+1);
		columns_rows[n] = items_count <= n+1 ? 1 :
                        (items_count+n)/(n+1);
	}

	/* for each possible column count, save the column widths and
	   find the biggest column count that fits to screen. */
        item_pos = 0; max_len = 0;
	for (tmp = items; tmp != NULL; tmp = tmp->next) {
		len = item_extra+len_func(tmp->data);
		if (max_len < len)
			max_len = len;

		for (n = 1; n < max_columns; n++) {
			if (columns_width[n] > max_width)
				continue; /* too wide */

			col = item_pos/columns_rows[n];
			if (columns[n][col] < len) {
				columns_width[n] += len-columns[n][col];
                                columns[n][col] = len;
			}
		}

                item_pos++;
	}

	for (n = max_columns-1; n >= 1; n--) {
		if (columns_width[n] <= max_width &&
		    columns[n][n] > 0)
                        break;
	}
        ret = n+1;

	*save_column_widths = g_new(int, ret);
	if (ret == 1) {
                **save_column_widths = max_len;
                *rows = 1;
	} else {
		memcpy(*save_column_widths, columns[ret-1], sizeof(int)*ret);
		*rows = columns_rows[ret-1];
	}

	for (n = 1; n < max_columns; n++)
                g_free(columns[n]);
	g_free(columns_width);
	g_free(columns_rows);
	g_free(columns);

        return ret;
}

/* Return a column sorted copy of a list. */
GSList *columns_sort_list(GSList *list, int rows)
{
        GSList *tmp, *sorted;
	int row, skip;

	if (list == NULL || rows == 0)
                return list;

	sorted = NULL;

	for (row = 0; row < rows; row++) {
                tmp = g_slist_nth(list, row);
                skip = 1;
		for (; tmp != NULL; tmp = tmp->next) {
			if (--skip == 0) {
                                skip = rows;
				sorted = g_slist_append(sorted, tmp->data);
			}
		}
	}

	g_return_val_if_fail(g_slist_length(sorted) ==
			     g_slist_length(list), sorted);
        return sorted;
}

/* Expand escape string, the first character in data should be the
   one after '\'. Returns the expanded character or -1 if error. */
int expand_escape(const char **data)
{
        char digit[4];

	switch (**data) {
	case 't':
		return '\t';
	case 'r':
		return '\r';
	case 'n':
		return '\n';
	case 'e':
		return 27; /* ESC */
	case '\\':
		return '\\';

	case 'x':
                /* hex digit */
		if (!i_isxdigit((*data)[1]) || !i_isxdigit((*data)[2]))
			return -1;

		digit[0] = (*data)[1];
		digit[1] = (*data)[2];
                digit[2] = '\0';
		*data += 2;
		return strtol(digit, NULL, 16);
	case 'c':
		/* check for end of string */
		if ((*data)[1] == '\0')
			return 0;
		/* control character (\cA = ^A) */
		(*data)++;
		return i_toupper(**data) - 64;
	case '0': case '1': case '2': case '3':
	case '4': case '5': case '6': case '7':
                /* octal */
		digit[1] = digit[2] = digit[3] = '\0';
                digit[0] = (*data)[0];
		if ((*data)[1] >= '0' && (*data)[1] <= '7') {
			++*data;
			digit[1] = **data;
			if ((*data)[1] >= '0' && (*data)[1] <= '7') {
				++*data;
				digit[2] = **data;
			}
		}
		return strtol(digit, NULL, 8);
	default:
		return -1;
	}
}

/* Escape all '"', "'" and '\' chars with '\' */
char *escape_string(const char *str)
{
	char *ret, *p;

	p = ret = g_malloc(strlen(str)*2+1);
	while (*str != '\0') {
		if (*str == '"' || *str == '\'' || *str == '\\')
			*p++ = '\\';
		*p++ = *str++;
	}
	*p = '\0';

	return ret;
}

/* Escape all '\' chars with '\' */
char *escape_string_backslashes(const char *str)
{
	char *ret, *p;

	p = ret = g_malloc(strlen(str)*2+1);
	while (*str != '\0') {
		if (*str == '\\')
			*p++ = '\\';
		*p++ = *str++;
	}
	*p = '\0';

	return ret;
}

int nearest_power(int num)
{
	int n = 1;

	while (n < num) n <<= 1;
	return n;
}

/* Parses unsigned integers from strings with decent error checking.
 * Returns true on success, false otherwise (overflow, no valid number, etc)
 * There's a 31 bit limit so the output can be assigned to signed positive ints */
int parse_uint(const char *nptr, char **endptr, int base, guint *number)
{
	char *endptr_;
	gulong parsed;

	/* strtoul accepts whitespace and plus/minus signs, for some reason */
	if (!i_isdigit(*nptr)) {
		return FALSE;
	}

	errno = 0;
	parsed = strtoul(nptr, &endptr_, base);

	if (errno || endptr_ == nptr || parsed >= (1U << 31)) {
		return FALSE;
	}

	if (endptr) {
		*endptr = endptr_;
	}

	if (number) {
		*number = (guint) parsed;
	}

	return TRUE;
}

static int parse_number_sign(const char *input, char **endptr, int *sign)
{
	int sign_ = 1;

	while (i_isspace(*input))
		input++;

	if (*input == '-') {
		sign_ = -sign_;
		input++;
	}

	*sign = sign_;
	*endptr = (char *) input;
	return TRUE;
}

static int parse_time_interval_uint(const char *time, guint *msecs)
{
	const char *desc;
	guint number;
	int len, ret, digits;

	*msecs = 0;

	/* max. return value is around 24 days */
	number = 0; ret = TRUE; digits = FALSE;
	while (i_isspace(*time))
		time++;
	for (;;) {
		if (i_isdigit(*time)) {
			char *endptr;
			if (!parse_uint(time, &endptr, 10, &number)) {
				return FALSE;
			}
			time = endptr;
			digits = TRUE;
			continue;
		}

		if (!digits)
			return FALSE;

		/* skip punctuation */
		while (*time != '\0' && i_ispunct(*time) && *time != '-')
			time++;

		/* get description */
		for (len = 0, desc = time; i_isalpha(*time); time++)
			len++;

		while (i_isspace(*time))
			time++;

		if (len == 0) {
			if (*time != '\0')
				return FALSE;
			*msecs += number * 1000; /* assume seconds */
			return TRUE;
		}

		if (g_ascii_strncasecmp(desc, "days", len) == 0) {
			if (number > 24) {
				/* would overflow */
				return FALSE;
			}
			*msecs += number * 1000*3600*24;
		} else if (g_ascii_strncasecmp(desc, "hours", len) == 0)
			*msecs += number * 1000*3600;
		else if (g_ascii_strncasecmp(desc, "minutes", len) == 0 ||
			 g_ascii_strncasecmp(desc, "mins", len) == 0)
			*msecs += number * 1000*60;
		else if (g_ascii_strncasecmp(desc, "seconds", len) == 0 ||
			 g_ascii_strncasecmp(desc, "secs", len) == 0)
			*msecs += number * 1000;
		else if (g_ascii_strncasecmp(desc, "milliseconds", len) == 0 ||
			 g_ascii_strncasecmp(desc, "millisecs", len) == 0 ||
			 g_ascii_strncasecmp(desc, "mseconds", len) == 0 ||
			 g_ascii_strncasecmp(desc, "msecs", len) == 0)
			*msecs += number;
		else {
			ret = FALSE;
		}

		/* skip punctuation */
		while (*time != '\0' && i_ispunct(*time) && *time != '-')
			time++;

		if (*time == '\0')
			break;

		number = 0;
		digits = FALSE;
	}

	return ret;
}

static int parse_size_uint(const char *size, guint *bytes)
{
	const char *desc;
	guint number, multiplier, limit;
	int len;

	*bytes = 0;

	/* max. return value is about 1.6 years */
	number = 0;
	while (*size != '\0') {
		if (i_isdigit(*size)) {
			char *endptr;
			if (!parse_uint(size, &endptr, 10, &number)) {
				return FALSE;
			}
			size = endptr;
			continue;
		}

		/* skip punctuation */
		while (*size != '\0' && i_ispunct(*size))
			size++;

		/* get description */
		for (len = 0, desc = size; i_isalpha(*size); size++)
			len++;

		if (len == 0) {
			if (number == 0) {
				/* "0" - allow it */
				return TRUE;
			}

			*bytes += number*1024; /* assume kilobytes */
			return FALSE;
		}

		multiplier = 0;
		limit = 0;

		if (g_ascii_strncasecmp(desc, "gbytes", len) == 0) {
			multiplier = 1U << 30;
			limit = 2U << 0;
		}
		if (g_ascii_strncasecmp(desc, "mbytes", len) == 0) {
			multiplier = 1U << 20;
			limit = 2U << 10;
		}
		if (g_ascii_strncasecmp(desc, "kbytes", len) == 0) {
			multiplier = 1U << 10;
			limit = 2U << 20;
		}
		if (g_ascii_strncasecmp(desc, "bytes", len) == 0) {
			multiplier = 1;
			limit = 2U << 30;
		}

		if (limit && number > limit) {
			return FALSE;
		}

		*bytes += number * multiplier;

		/* skip punctuation */
		while (*size != '\0' && i_ispunct(*size))
			size++;
	}

	return TRUE;
}

int parse_size(const char *size, int *bytes)
{
	guint bytes_;
	int ret;

	ret = parse_size_uint(size, &bytes_);

	if (bytes_ > (1U << 31)) {
		return FALSE;
	}

	*bytes = bytes_;
	return ret;
}

int parse_time_interval(const char *time, int *msecs)
{
	guint msecs_;
	char *number;
	int ret, sign;

	parse_number_sign(time, &number, &sign);

	ret = parse_time_interval_uint(number, &msecs_);

	if (msecs_ > (1U << 31)) {
		return FALSE;
	}

	*msecs = msecs_ * sign;
	return ret;
}


char *ascii_strup(char *str)
{
	char *s;

	for (s = str; *s; s++)
		*s = g_ascii_toupper (*s);
	return str;
}

char *ascii_strdown(char *str)
{
	char *s;

	for (s = str; *s; s++)
		*s = g_ascii_tolower (*s);
	return str;
}

char **strsplit_len(const char *str, int len, gboolean onspace)
{
	char **ret = g_new(char *, 1);
	int n;
	int offset;

	for (n = 0; *str != '\0'; n++, str += offset) {
		offset = MIN(len, strlen(str));
		if (onspace && strlen(str) > len) {
			/*
			 * Try to find a space to split on and leave
			 * the space on the previous line.
			 */
			int i;
			for (i = len - 1; i > 0; i--) {
				if (str[i] == ' ') {
					offset = i;
					break;
				}
			}
		}
		ret[n] = g_strndup(str, offset);
		ret = g_renew(char *, ret, n + 2);
	}
	ret[n] = NULL;

	return ret;
}

char *binary_to_hex(unsigned char *buffer, size_t size)
{
	static const char hex[] = "0123456789ABCDEF";
	char *result = NULL;
	int i;

	if (buffer == NULL || size == 0)
		return NULL;

	result = g_malloc(3 * size);

	for (i = 0; i < size; i++) {
		result[i * 3 + 0] = hex[(buffer[i] >> 4) & 0xf];
		result[i * 3 + 1] = hex[(buffer[i] >> 0) & 0xf];
		result[i * 3 + 2] = i == size - 1 ? '\0' : ':';
	}

	return result;
}
