/*
 * Embedded Linux library
 * Copyright (C) 2011-2014  Intel Corporation
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _GNU_SOURCE
#include <string.h>

#include "strv.h"
#include "private.h"
#include "useful.h"

/**
 * SECTION:strv
 * @short_description: String array functions
 *
 * String array functions
 */

/**
 * l_strfreev:
 * @strlist: String list to free
 *
 * Frees a list of strings
 **/
LIB_EXPORT void l_strfreev(char **strlist)
{
	l_strv_free(strlist);
}

/**
 * l_strsplit:
 * @str: String to split
 * @sep: The delimiter character
 *
 * Splits a string into pieces which do not contain the delimiter character.
 * As a special case, an empty string is returned as an empty array, e.g.
 * an array with just the NULL element.
 *
 * Note that this function only works with ASCII delimiters.
 *
 * Returns: A newly allocated %NULL terminated string array.  This array
 * should be freed using l_strfreev().
 **/
LIB_EXPORT char **l_strsplit(const char *str, const char sep)
{
	int len;
	int i;
	const char *p;
	char **ret;

	if (unlikely(!str))
		return NULL;

	if (str[0] == '\0')
		return l_new(char *, 1);

	for (p = str, len = 1; *p; p++)
		if (*p == sep)
			len += 1;

	ret = l_new(char *, len + 1);

	i = 0;
	p = str;
	len = 0;

	while (p[len]) {
		if (p[len] != sep) {
			len += 1;
			continue;
		}

		ret[i++] = l_strndup(p, len);
		p += len + 1;
		len = 0;
	}

	ret[i++] = l_strndup(p, len);

	return ret;
}

/**
 * l_strsplit_set:
 * @str: String to split
 * @separators: A set of delimiters
 *
 * Splits a string into pieces which do not contain the delimiter characters
 * that can be found in @separators.
 * As a special case, an empty string is returned as an empty array, e.g.
 * an array with just the NULL element.
 *
 * Note that this function only works with ASCII delimiters.
 *
 * Returns: A newly allocated %NULL terminated string array.  This array
 * should be freed using l_strfreev().
 **/
LIB_EXPORT char **l_strsplit_set(const char *str, const char *separators)
{
	int len;
	int i;
	const char *p;
	char **ret;
	bool sep_table[256];

	if (unlikely(!str))
		return NULL;

	if (str[0] == '\0')
		return l_new(char *, 1);

	memset(sep_table, 0, sizeof(sep_table));

	for (p = separators; *p; p++)
		sep_table[(unsigned char) *p] = true;

	for (p = str, len = 1; *p; p++)
		if (sep_table[(unsigned char) *p] == true)
			len += 1;

	ret = l_new(char *, len + 1);

	i = 0;
	p = str;
	len = 0;

	while (p[len]) {
		if (sep_table[(unsigned char) p[len]] != true) {
			len += 1;
			continue;
		}

		ret[i++] = l_strndup(p, len);
		p += len + 1;
		len = 0;
	}

	ret[i++] = l_strndup(p, len);

	return ret;
}

/**
 * l_strjoinv:
 * @str_array: a %NULL terminated array of strings to join
 * @delim: Delimiting character
 *
 * Joins strings contanied in the @str_array into one long string delimited
 * by @delim.
 *
 * Returns: A newly allocated string that should be freed using l_free()
 */
LIB_EXPORT char *l_strjoinv(char **str_array, const char delim)
{
	size_t len = 0;
	unsigned int i;
	char *ret;
	char *p;

	if (unlikely(!str_array))
		return NULL;

	if (!str_array[0])
		return l_strdup("");

	for (i = 0; str_array[i]; i++)
		len += strlen(str_array[i]);

	len += 1 + i - 1;

	ret = l_malloc(len);

	p = stpcpy(ret, str_array[0]);

	for (i = 1; str_array[i]; i++) {
		*p++ = delim;
		p = stpcpy(p, str_array[i]);
	}

	return ret;
}

/**
 * l_strv_new:
 *
 * Returns: new empty string array
 **/
LIB_EXPORT char **l_strv_new(void)
{
	return l_new(char *, 1);
}

/**
 * l_strv_free:
 * @str_array: a %NULL terminated array of strings
 *
 * Frees strings in @str_array and @str_array itself
 **/
LIB_EXPORT void l_strv_free(char **str_array)
{
	if (likely(str_array)) {
		int i;

		for (i = 0; str_array[i]; i++)
			l_free(str_array[i]);

		l_free(str_array);
	}
}

/**
 * l_strv_length:
 * @str_array: a %NULL terminated array of strings
 *
 * Returns: the number of strings in @str_array
 */
LIB_EXPORT unsigned int l_strv_length(char **str_array)
{
	unsigned int i = 0;

	if (unlikely(!str_array))
		return 0;

	while (str_array[i])
		i += 1;

	return i;
}

/**
 * l_strv_contains:
 * @str_array: a %NULL terminated array of strings
 * @item: An item to search for, must be not %NULL
 *
 * Returns: #true if @str_array contains item
 */
LIB_EXPORT bool l_strv_contains(char **str_array, const char *item)
{
	unsigned int i = 0;

	if (unlikely(!str_array || !item))
		return false;

	while (str_array[i]) {
		if (!strcmp(str_array[i], item))
			return true;

		i += 1;
	}

	return false;
}

/**
 * l_strv_append:
 * @str_array: a %NULL terminated array of strings or %NULL
 * @str: A string to be appended at the end of @str_array
 *
 * Returns: New %NULL terminated array of strings with @str added
 */
LIB_EXPORT char **l_strv_append(char **str_array, const char *str)
{
	char **ret;
	unsigned int i, len;

	if (unlikely(!str))
		return str_array;

	len = l_strv_length(str_array);
	ret = l_new(char *, len + 2);

	for (i = 0; i < len; i++)
		ret[i] = str_array[i];

	ret[i] = l_strdup(str);

	l_free(str_array);

	return ret;
}

LIB_EXPORT char **l_strv_append_printf(char **str_array,
						const char *format, ...)
{
	va_list args;
	char **ret;

	va_start(args, format);
	ret = l_strv_append_vprintf(str_array, format, args);
	va_end(args);

	return ret;
}

LIB_EXPORT char **l_strv_append_vprintf(char **str_array,
					const char *format, va_list args)
{
	char **ret;
	unsigned int i, len;

	if (unlikely(!format))
		return str_array;

	len = l_strv_length(str_array);
	ret = l_new(char *, len + 2);

	for (i = 0; i < len; i++)
		ret[i] = str_array[i];

	ret[i] = l_strdup_vprintf(format, args);

	l_free(str_array);

	return ret;
}

/**
 * l_strv_copy:
 * @str_array: a %NULL terminated array of strings or %NULL
 *
 * Returns: An independent copy of @str_array.
 */
LIB_EXPORT char **l_strv_copy(char **str_array)
{
	int i, len;
	char **copy;

	if (unlikely(!str_array))
		return NULL;

	for (len = 0; str_array[len]; len++);

	copy = l_malloc(sizeof(char *) * (len + 1));

	for (i = len; i >= 0; i--)
		copy[i] = l_strdup(str_array[i]);

	return copy;
}

/**
 * l_strv_eq:
 * @a: a %NULL terminated array of strings or %NULL
 * @b: another %NULL terminated array of strings or %NULL
 *
 * Returns: Whether @a and @b's contents are identical, including the
 * order, or @a and @b are both %NULL.
 */
LIB_EXPORT bool l_strv_eq(char **a, char **b)
{
	if (!a || !b)
		return a == b;

	for (; *a; a++, b++)
		if (!*b || strcmp(*a, *b))
			return false;

	return !*b;
}
