/* Functions to deal with NULL-terminated string lists */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * This library is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU Lesser General Public License		*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU Lesser General Public License for more details: lgpl.txt or	*
 * http://www.fsf.org/copyleft/lesser.html									*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include <stdlib.h>     /* malloc and qsort */
#include <string.h>     /* strtok */
#include "genwrap.h"    /* stricmp */
#include "str_list.h"
#include "xpprintf.h"

str_list_t strListInit(void)
{
	str_list_t list;

	if ((list = (str_list_t)malloc(sizeof(char*))) == NULL)
		return NULL;

	list[0] = NULL;   /* terminated by default */
	return list;
}

size_t strListCount(const str_list_t list)
{
	size_t i;

	COUNT_LIST_ITEMS(list, i);

	return i;
}

bool strListIsEmpty(const str_list_t list)
{
	return (list == NULL) || (list[0] == NULL);
}

int strListIndexOf(const str_list_t list, const char* str)
{
	size_t i;

	if (list == NULL)
		return -1;

	for (i = 0; list[i] != NULL; i++) {
		if (list[i] == str)
			return i;
	}

	return -1;
}

int strListFind(const str_list_t list, const char* str, bool case_sensitive)
{
	size_t i;

	if (list == NULL || str == NULL)
		return -1;

	for (i = 0; list[i] != NULL; i++) {
		if (case_sensitive) {
			if (strcmp(list[i], str) == 0)
				return i;
		} else {
			if (stricmp(list[i], str) == 0)
				return i;
		}
	}

	return -1;
}

static char* str_list_append(str_list_t* list, char* str, size_t index)
{
	str_list_t lp;

	if ((lp = (str_list_t)realloc(*list, sizeof(char*) * (index + 2))) == NULL)
		return NULL;

	*list = lp;
	lp[index++] = str;
	lp[index] = NULL; /* terminate list */

	return str;
}

static char* str_list_insert(str_list_t* list, char* str, size_t index)
{
	size_t     i;
	size_t     count;
	str_list_t lp;

	if (*list == NULL)
		*list = strListInit();
	count = strListCount(*list);
	if (index > count)   /* invalid index, do nothing */
		return NULL;

	count++;
	if ((lp = (str_list_t)realloc(*list, sizeof(char*) * (count + 1))) == NULL)
		return NULL;

	*list = lp;
	for (i = count; i > index; i--)
		lp[i] = lp[i - 1];
	lp[index] = str;

	return str;
}

char* strListRemove(str_list_t* list, size_t index)
{
	char*      str;
	size_t     i;
	size_t     count;
	str_list_t lp;

	count = strListCount(*list);

	if (index == STR_LIST_LAST_INDEX && count)
		index = count - 1;

	if (index >= count)  /* invalid index, do nothing */
		return NULL;

	count--;
	if ((lp = (str_list_t)realloc(*list, sizeof(char*) * (count + 1))) == NULL)
		return NULL;

	*list = lp;
	str = lp[index];
	for (i = index; i < count; i++)
		lp[i] = lp[i + 1];
	lp[count] = NULL;

	return str;
}

// Remove without realloc
bool strListFastRemove(str_list_t list, size_t index, size_t count)
{
	size_t i;
	size_t total;

	total = strListCount(list);

	if (index == STR_LIST_LAST_INDEX && total)
		index = total - 1;

	if (index + count > total)   /* invalid index, do nothing */
		return false;

	for (i = index; i <= total - count; ++i)
		list[i] = list[i + count];

	return true;
}

bool strListDelete(str_list_t* list, size_t index)
{
	char* str;

	if ((str = strListRemove(list, index)) == NULL)
		return false;

	free(str);

	return true;
}

bool strListFastDelete(str_list_t list, size_t index, size_t count)
{
	size_t i;

	for (i = 0; i < count; ++i)
		if (list[index + i] == NULL)
			return false;

	for (i = 0; i < count; ++i)
		free(list[index + i]);

	return strListFastRemove(list, index, count);
}

void strListFastDeleteAll(str_list_t list)
{
	size_t i;

	for (i = 0; list[i] != NULL; ++i)
		free(list[i]);
	list[0] = NULL;
}

char* strListReplace(const str_list_t list, size_t index, const char* str)
{
	char*  buf;
	size_t count;

	if (str == NULL)
		return NULL;

	count = strListCount(list);

	if (index == STR_LIST_LAST_INDEX && count)
		index = count - 1;

	if (index >= count)  /* invalid index, do nothing */
		return NULL;

	if ((buf = (char*)realloc(list[index], strlen(str) + 1)) == NULL)
		return NULL;

	list[index] = buf;
	strcpy(buf, str);

	return buf;
}

size_t strListModifyEach(const str_list_t list, char*(modify(size_t, char*, void*)), void* cbdata)
{
	size_t i;
	if (list == NULL)
		return 0;
	for (i = 0; list[i] != NULL; i++) {
		char* str = modify(i, list[i], cbdata);
		if (str == NULL || str == list[i])   // Same old pointer (or NULL), no modification
			continue;
		str = strdup(str);
		if (str == NULL)
			break;
		free(list[i]);
		list[i] = str;
	}
	return i;
}

bool strListSwap(const str_list_t list, size_t index1, size_t index2)
{
	char*  tmp;
	size_t count;

	count = strListCount(list);

	if (index1 == STR_LIST_LAST_INDEX && count)
		index1 = count - 1;

	if (index2 == STR_LIST_LAST_INDEX && count)
		index2 = count - 1;

	if (index1 >= count || index2 >= count || index1 == index2)
		return false; /* invalid index, do nothing */

	tmp = list[index1];
	list[index1] = list[index2];
	list[index2] = tmp;

	return true;
}

char* strListAnnex(str_list_t* list, const char* str, size_t index)
{
	char *ret;

	if (str == NULL)
		return NULL;

	if (index == STR_LIST_LAST_INDEX)
		index = strListCount(*list);

	ret = str_list_append(list, (char*)str, index);
	return ret;
}

char* strListAppend(str_list_t* list, const char* str, size_t index)
{
	char* buf;
	char *ret;

	if (str == NULL)
		return NULL;

	if ((buf = strdup(str)) == NULL)
		return NULL;

	if (index == STR_LIST_LAST_INDEX)
		index = strListCount(*list);

	ret = str_list_append(list, buf, index);
	if (ret == NULL)
		free(buf);
	return ret;
}

size_t strListAppendList(str_list_t* list, const str_list_t add_list)
{
	size_t i;
	size_t count;

	count = strListCount(*list);
	for (i = 0; add_list != NULL && add_list[i] != NULL; i++)
		strListAppend(list, add_list[i], count++);

	return count;
}

#if !defined(__BORLANDC__) // Doesn't have asprintf() or va_copy()_vscprintf()
char* strListAppendFormat(str_list_t* list, const char* format, ...)
{
	char *  ret;
	char*   buf = NULL;
	int     len;
	va_list va;

	va_start(va, format);
	len = vasprintf(&buf, format, va);
	va_end(va);

	if (len == -1 || buf == NULL)
		return NULL;

	ret = str_list_append(list, buf, strListCount(*list));
	if (ret == NULL)
		free(buf);
	return ret;
}
#endif

char* strListInsert(str_list_t* list, const char* str, size_t index)
{
	char* buf;
	char* ret;

	if (str == NULL)
		return NULL;

	if ((buf = strdup(str)) == NULL)
		return NULL;

	ret = str_list_insert(list, buf, index);
	if (ret == NULL)
		free(buf);
	return ret;
}

size_t strListInsertList(str_list_t* list, const str_list_t add_list, size_t index)
{
	size_t i;

	if (add_list == NULL)
		return 0;

	for (i = 0; add_list[i] != NULL; i++)
		if (strListInsert(list, add_list[i], index++) == NULL)
			break;

	return i;
}

#if !defined(__BORLANDC__) // Doesn't have asprintf() or va_copy()_vscprintf()
char* strListInsertFormat(str_list_t* list, size_t index, const char* format, ...)
{
	char *  ret;
	char*   buf = NULL;
	int     len;
	va_list va;

	va_start(va, format);
	len = vasprintf(&buf, format, va);
	va_end(va);

	if (len == -1 || buf == NULL)
		return NULL;

	ret = str_list_insert(list, buf, index);
	if (ret == NULL)
		free(buf);
	return ret;
}
#endif

// Consecutive delimiters are treated as one (empty strings are skipped)
str_list_t strListSplit(str_list_t* lp, char* str, const char* delimit)
{
	size_t     count;
	char*      token;
	char*      tmp;
	str_list_t list;

	if (str == NULL || delimit == NULL)
		return NULL;

	if (lp == NULL) {
		if ((list = strListInit()) == NULL)
			return NULL;
		lp = &list;
		count = 0;
	} else
		count = strListCount(*lp);

	for (token = strtok_r(str, delimit, &tmp); token != NULL; token = strtok_r(NULL, delimit, &tmp))
		if (strListAppend(lp, token, count++) == NULL)
			break;

	return *lp;
}

str_list_t strListSplitNew(str_list_t* lp, char* str, const char* delimit)
{
	strListFreeStrings(*lp);
	return strListSplit(lp, str, delimit);
}

str_list_t strListSplitCopy(str_list_t* list, const char* str, const char* delimit)
{
	char*      buf;
	str_list_t new_list;

	if (str == NULL || delimit == NULL)
		return NULL;

	if ((buf = strdup(str)) == NULL)
		return NULL;

	new_list = strListSplit(list, buf, delimit);

	free(buf);

	if (list != NULL)
		*list = new_list;

	return new_list;
}

// Consecutive delimiters are treated as multiple (empty strings are supported)
str_list_t strListDivide(str_list_t* lp, char* str, const char* delimit)
{
	size_t     count;
	char*      p = str;
	char*      np;
	char*      end;
	str_list_t list;

	if (str == NULL || delimit == NULL)
		return NULL;

	if (lp == NULL) {
		if ((list = strListInit()) == NULL)
			return NULL;
		lp = &list;
		count = 0;
	} else
		count = strListCount(*lp);

	end = str + strlen(str);
	while (p < end) {
		np = p + strcspn(p, delimit);
		*np = '\0';
		strListAppend(lp, p, count++);
		p = np + 1;
	}

	return *lp;
}


size_t strListMerge(str_list_t* list, str_list_t add_list)
{
	size_t i;
	size_t count;

	if (add_list == NULL)
		return 0;

	count = strListCount(*list);
	for (i = 0; add_list[i] != NULL; i++)
		str_list_append(list, add_list[i], count++);

	return i;
}

char* strListCombine(str_list_t list, char* buf, size_t maxlen, const char* delimit)
{
	size_t i;
	char*  end;
	char*  ptr;

	if (maxlen < 1)
		return NULL;

	if (buf == NULL)
		if ((buf = (char*)malloc(maxlen)) == NULL)
			return NULL;

	memset(buf, 0, maxlen);
	if (list == NULL)
		return buf;

	end = buf + maxlen;
	for (i = 0, ptr = buf; list[i] != NULL && buf < end; i++)
		ptr += safe_snprintf(ptr, end - ptr, "%s%s", i ? delimit:"", list[i]);

	return buf;
}

#if defined(_WIN32)
	#define QSORT_CALLBACK_TYPE __cdecl
#else
	#define QSORT_CALLBACK_TYPE
#endif

static int QSORT_CALLBACK_TYPE strListCompareAlpha(const void *arg1, const void *arg2)
{
	return stricmp(*(char**)arg1, *(char**)arg2);
}

static int QSORT_CALLBACK_TYPE strListCompareAlphaReverse(const void *arg1, const void *arg2)
{
	return stricmp(*(char**)arg2, *(char**)arg1);
}

static int QSORT_CALLBACK_TYPE strListCompareAlphaCase(const void *arg1, const void *arg2)
{
	return strcmp(*(char**)arg1, *(char**)arg2);
}

static int QSORT_CALLBACK_TYPE strListCompareAlphaCaseReverse(const void *arg1, const void *arg2)
{
	return strcmp(*(char**)arg2, *(char**)arg1);
}

void strListSortAlpha(str_list_t list)
{
	if (list != NULL)
		qsort(list, strListCount(list), sizeof(char*), strListCompareAlpha);
}

void strListSortAlphaReverse(str_list_t list)
{
	if (list != NULL)
		qsort(list, strListCount(list), sizeof(char*), strListCompareAlphaReverse);
}

void strListSortAlphaCase(str_list_t list)
{
	if (list != NULL)
		qsort(list, strListCount(list), sizeof(char*), strListCompareAlphaCase);
}

void strListSortAlphaCaseReverse(str_list_t list)
{
	if (list != NULL)
		qsort(list, strListCount(list), sizeof(char*), strListCompareAlphaCaseReverse);
}

str_list_t strListDup(str_list_t list)
{
	str_list_t ret;
	size_t     count = 0;

	if (list == NULL)
		return NULL;
	ret = strListInit();
	for (; *list; list++)
		strListAppend(&ret, *list, count++);
	return ret;
}

int strListCmp(str_list_t list1, str_list_t list2)
{
	str_list_t l1 = strListDup(list1);
	str_list_t l2 = strListDup(list2);
	str_list_t ol1 = l1;
	str_list_t ol2 = l2;
	int        tmp;
	int        ret;

	if (*l1 == NULL && *l2 == NULL) {
		ret = 0;
		goto early_return;
	}
	if (*l1 == NULL) {
		ret = -1;
		goto early_return;
	}
	if (*l2 == NULL) {
		ret = 1;
		goto early_return;
	}

	strListSortAlphaCase(l1);
	strListSortAlphaCase(l2);

	for (; *l1; l1++, l2++) {
		if (*l2 == NULL) {
			ret = 1;
			goto early_return;
		}
		tmp = strcmp(*l1, *l2);
		if (tmp != 0) {
			ret = tmp;
			goto early_return;
		}
	}
	if (*l2 == NULL)
		ret = 0;
	else
		ret = -1;

early_return:
	strListFree(&ol1);
	strListFree(&ol2);
	return ret;
}

void strListFreeStrings(str_list_t list)
{
	size_t i;

	FREE_LIST_ITEMS(list, i);
}

void strListFree(str_list_t* list)
{
	if (list != NULL && *list != NULL) {
		strListFreeStrings(*list);
		FREE_AND_NULL(*list);
	}
}

// Read from the current file position (in stream 'fp') to the end of file
// adding each read line (up to max_line_len chars) to the specified string
// list (or allocating a new one, when 'lp' is NULL).
// Returns NULL on error (i.e. memory allocation or file read failure).
// Upon error, the allocated list will be freed, but only if 'lp' was NULL.
str_list_t strListReadFile(FILE* fp, str_list_t* lp, size_t max_line_len)
{
	char*      buf = NULL;
	size_t     count;
	str_list_t list = NULL;

	if (max_line_len < 1)
		max_line_len = 2048;

	if (lp == NULL) {
		if ((list = strListInit()) == NULL)
			return NULL;
		lp = &list;
	}

	if (fp != NULL) {
		count = strListCount(*lp);
		while (!feof(fp)) {
			if (buf == NULL && (buf = (char*)malloc(max_line_len + 1)) == NULL)
				break;

			if (fgets(buf, max_line_len + 1, fp) == NULL)
				break;
			if (strListAppend(lp, buf, count++) == NULL)
				break;
		}
		if (!feof(fp)) {
			strListFreeStrings(list);
			free(list);
			free(buf);
			return NULL;
		}
	}
	free(buf);

	return *lp;
}

size_t strListInsertFile(FILE* fp, str_list_t* lp, size_t index, size_t max_line_len)
{
	str_list_t list;
	size_t     count;

	if ((list = strListReadFile(fp, NULL, max_line_len)) == NULL)
		return 0;

	count = strListInsertList(lp, list, index);

	strListFree(&list);

	return count;
}

size_t strListWriteFile(FILE* fp, const str_list_t list, const char* separator)
{
	size_t i;

	if (list == NULL)
		return 0;

	for (i = 0; list[i] != NULL; i++) {
		if (fputs(list[i], fp) == EOF)
			break;
		if (separator != NULL && fputs(separator, fp) == EOF)
			break;
	}

	return i;
}

char* strListJoin(const str_list_t list, char* buf, size_t buflen, const char* separator)
{
	size_t i;

	if (buf == NULL || buflen < 1)
		return NULL;

	*buf = '\0';

	if (list == NULL)
		return buf;

	if (separator == NULL)
		separator = ", ";

	for (i = 0; list[i] != NULL; i++) {
		if (strlen(buf) + strlen(separator) + strlen(list[i]) >= buflen)
			break;
		if (i > 0)
			strcat(buf, separator);
		strcat(buf, list[i]);
	}
	return buf;
}

size_t strListBlockLength(char* block)
{
	char*  p = block;
	size_t str_len;
	size_t block_len = 0;

	if (block == NULL)
		return 0;

	/* calculate total block length */
	while ((str_len = strlen(p)) != 0) {
		block_len += (str_len + 1);
		p += (str_len + 1);
	}
	/* block must be double-NULL terminated */
	if (!block_len)
		block_len = 1;
	block_len++;

	return block_len;
}

char* strListCopyBlock(char* block)
{
	char*  p;
	size_t block_len;

	if ((block_len = strListBlockLength(block)) == 0)
		return NULL;

	if ((p = (char*)malloc(block_len)) == NULL)
		return NULL;
	memcpy(p, block, block_len);
	return p;
}

char* strListAppendBlock(char* block, str_list_t list)
{
	char*  p;
	size_t str_len;
	size_t block_len;
	size_t i;

	if ((block_len = strListBlockLength(block)) != 0)
		block_len--;    /* Over-write existing NULL terminator */

	for (i = 0; list != NULL && list[i] != NULL; i++) {
		str_len = strlen(list[i]);
		if (str_len == 0)
			continue;   /* can't include empty strings in block */
		if ((p = (char*)realloc(block, block_len + str_len + 1)) == NULL) {
			FREE_AND_NULL(block);
			return block;
		}
		block = p;
		strcpy(block + block_len, list[i]);
		block_len += (str_len + 1);
	}

	/* block must be double-NULL terminated */
	if (!block_len)
		block_len = 1;
	block_len++;
	if ((p = (char*)realloc(block, block_len)) == NULL) {
		FREE_AND_NULL(block);
		return block;
	}
	block = p;
	memset(block + (block_len - 2), 0, 2);

	return block;
}

char* strListCreateBlock(str_list_t list)
{
	return strListAppendBlock(NULL, list);
}

void strListFreeBlock(char* block)
{
	free(block);    /* this must be done here for Windows-DLL reasons */
}

int strListTruncateTrailingWhitespaces(str_list_t list)
{
	size_t i;

	if (list == NULL)
		return 0;

	for (i = 0; list[i] != NULL; i++) {
		truncsp(list[i]);
	}
	return i;
}

int strListTruncateTrailingLineEndings(str_list_t list)
{
	size_t i;

	if (list == NULL)
		return 0;

	for (i = 0; list[i] != NULL; i++) {
		truncnl(list[i]);
	}
	return i;
}

/* Truncate strings in list at first occurrence of any char in 'set' */
int strListTruncateStrings(str_list_t list, const char* set)
{
	size_t i;
	char*  p;

	if (list == NULL)
		return 0;

	for (i = 0; list[i] != NULL; i++) {
		p = strpbrk(list[i], set);
		if (p != NULL && *p != 0)
			*p = 0;
	}
	return i;
}

/* Strip chars in 'set' from strings in list */
int strListStripStrings(str_list_t list, const char* set)
{
	size_t i;
	char*  o;
	char*  p;

	if (list == NULL)
		return 0;

	for (i = 0; list[i] != NULL; i++) {
		for (o = p = list[i]; (*p != '\0'); p++) {
			if (strchr(set, *p) == NULL)
				*(o++) = *p;
		}
		*o = '\0';
	}
	return i;
}

/* Remove duplicate strings from list, return the new list length */
int strListDedupe(str_list_t* list, bool case_sensitive)
{
	size_t i, j;

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

	for (i = 0; (*list)[i] != NULL; i++) {
		for (j = i + 1; (*list)[j] != NULL; ) {
			if ((case_sensitive && strcmp((*list)[i], (*list)[j]) == 0)
			    || (!case_sensitive && stricmp((*list)[i], (*list)[j]) == 0))
				strListDelete(list, j);
			else
				j++;
		}
	}
	return i;
}

int strListDeleteBlanks(str_list_t* list)
{
	size_t i;

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

	for (i = 0; (*list)[i] != NULL; ) {
		if ((*list)[i][0] == '\0')
			strListDelete(list, i);
		else
			i++;
	}
	return i;
}

int strListFastDeleteBlanks(str_list_t list)
{
	size_t i;

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

	for (i = 0; list[i] != NULL; ) {
		if (list[i][0] == '\0')
			strListFastDelete(list, i, /* count: */ 1);
		else
			i++;
	}
	return i;
}
