/*
 * 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

#include <stdio.h>

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

/**
 * SECTION:string
 * @short_description: Growable string buffer
 *
 * Growable string buffer support
 */

/**
 * l_string:
 *
 * Opaque object representing the string buffer.
 */
struct l_string {
	size_t max;
	size_t len;
	char *str;
};

static inline size_t next_power(size_t len)
{
	size_t n = 1;

	if (len > SIZE_MAX / 2)
		return SIZE_MAX;

	while (n < len)
		n = n << 1;

	return n;
}

static void grow_string(struct l_string *str, size_t extra)
{
	if (str->len + extra < str->max)
		return;

	str->max = next_power(str->len + extra + 1);
	str->str = l_realloc(str->str, str->max);
}

/**
 * l_string_new:
 * @initial_length: Initial length of the groable string
 *
 * Create new growable string.  If the @initial_length is 0, then a safe
 * default is chosen.
 *
 * Returns: a newly allocated #l_string object.
 **/
LIB_EXPORT struct l_string *l_string_new(size_t initial_length)
{
	static const size_t DEFAULT_INITIAL_LENGTH = 127;
	struct l_string *ret;

	ret = l_new(struct l_string, 1);

	if (initial_length == 0)
		initial_length = DEFAULT_INITIAL_LENGTH;

	grow_string(ret, initial_length);
	ret->str[0] = '\0';

	return ret;
}

/**
 * l_string_free:
 * @string: growable string object
 *
 * Free the growable string object and all associated data
 **/
LIB_EXPORT void l_string_free(struct l_string *string)
{
	if (unlikely(!string))
		return;

	l_free(string->str);
	l_free(string);
}

/**
 * l_string_unwrap:
 * @string: growable string object
 *
 * Free the growable string object and return the internal string data.
 * The caller is responsible for freeing the string data using l_free(),
 * and the string object is no longer usable.
 *
 * Returns: @string's internal buffer
 **/
LIB_EXPORT char *l_string_unwrap(struct l_string *string)
{
	char *result;

	if (unlikely(!string))
		return NULL;

	result = string->str;

	l_free(string);

	return result;
}

/**
 * l_string_append:
 * @dest: growable string object
 * @src: C-style string to copy
 *
 * Appends the contents of @src to @dest.  The internal buffer of @dest is
 * grown if necessary.
 *
 * Returns: @dest
 **/
LIB_EXPORT struct l_string *l_string_append(struct l_string *dest,
						const char *src)
{
	size_t size;

	if (unlikely(!dest || !src))
		return NULL;

	size = strlen(src);

	grow_string(dest, size);

	memcpy(dest->str + dest->len, src, size);
	dest->len += size;
	dest->str[dest->len] = '\0';

	return dest;
}

/**
 * l_string_append_c:
 * @dest: growable string object
 * @c: Character
 *
 * Appends character given by @c to @dest.  The internal buffer of @dest is
 * grown if necessary.
 *
 * Returns: @dest
 **/
LIB_EXPORT struct l_string *l_string_append_c(struct l_string *dest,
						const char c)
{
	if (unlikely(!dest))
		return NULL;

	grow_string(dest, 1);
	dest->str[dest->len++] = c;
	dest->str[dest->len] = '\0';

	return dest;
}

/**
 * l_string_append_fixed:
 * @dest: growable string object
 * @src: Character array to copy from
 * @max: Maximum number of characters to copy
 *
 * Appends the contents of a fixed size string array @src to @dest.
 * The internal buffer of @dest is grown if necessary.  Up to a maximum of
 * @max characters are copied.  If a null is encountered in the first @max
 * characters, the string is copied only up to the NULL character.
 *
 * Returns: @dest
 **/
LIB_EXPORT struct l_string *l_string_append_fixed(struct l_string *dest,
							const char *src,
							size_t max)
{
	const char *nul;

	if (unlikely(!dest || !src || !max))
		return NULL;

	nul = memchr(src, 0, max);
	if (nul)
		max = nul - src;

	grow_string(dest, max);

	memcpy(dest->str + dest->len, src, max);
	dest->len += max;
	dest->str[dest->len] = '\0';

	return dest;
}

/**
 * l_string_append_vprintf:
 * @dest: growable string object
 * @format: the string format.  See the sprintf() documentation
 * @args: the parameters to insert
 *
 * Appends a formatted string to the growable string buffer.  This function
 * is equivalent to l_string_append_printf except that the arguments are
 * passed as a va_list.
 **/
LIB_EXPORT void l_string_append_vprintf(struct l_string *dest,
					const char *format, va_list args)
{
	size_t len;
	size_t have_space;
	va_list args_copy;

	if (unlikely(!dest))
		return;

#if __STDC_VERSION__ > 199409L
	va_copy(args_copy, args);
#else
	__va_copy(args_copy, args);
#endif

	have_space = dest->max - dest->len;
	len = vsnprintf(dest->str + dest->len, have_space, format, args);

	if (len >= have_space) {
		grow_string(dest, len);
		len = vsprintf(dest->str + dest->len, format, args_copy);
	}

	dest->len += len;

	va_end(args_copy);
}

/**
 * l_string_append_printf:
 * @dest: growable string object
 * @format: the string format.  See the sprintf() documentation
 * @...: the parameters to insert
 *
 * Appends a formatted string to the growable string buffer, growing it as
 * necessary.
 **/
LIB_EXPORT void l_string_append_printf(struct l_string *dest,
					const char *format, ...)
{
	va_list args;

	if (unlikely(!dest))
		return;

	va_start(args, format);
	l_string_append_vprintf(dest, format, args);
	va_end(args);
}

/**
 * l_string_length:
 * @string: growable string object
 *
 * Returns: bytes used in the string.
 **/
LIB_EXPORT unsigned int l_string_length(struct l_string *string)
{
	if (unlikely(!string))
		return 0;

	return string->len;
}

LIB_EXPORT struct l_string *l_string_truncate(struct l_string *string,
							size_t new_size)
{
	if (unlikely(!string))
		return NULL;

	if (new_size >= string->len)
		return string;

	string->len = new_size;
	string->str[new_size] = '\0';

	return string;
}

struct arg {
	size_t max_len;
	size_t cur_len;
	char *chars;
};

static inline void arg_init(struct arg *arg)
{
	arg->max_len = 0;
	arg->cur_len = 0;
	arg->chars = NULL;
}

static void arg_putchar(struct arg *arg, char ch)
{
	if (arg->cur_len == arg->max_len) {
		arg->max_len += 32; /* Grow by at least 32 bytes */
		arg->chars = l_realloc(arg->chars, 1 + arg->max_len);
	}

	arg->chars[arg->cur_len++] = ch;
	arg->chars[arg->cur_len] = '\0';
}

static void arg_putmem(struct arg *arg, const void *mem, size_t len)
{
	if (len == 0)
		return;

	if (arg->cur_len + len > arg->max_len) {
		size_t growby = len * 2;

		if (growby < 32)
			growby = 32;

		arg->max_len += growby;
		arg->chars = l_realloc(arg->chars, 1 + arg->max_len);
	}

	memcpy(arg->chars + arg->cur_len, mem, len);
	arg->cur_len += len;
	arg->chars[arg->cur_len] = '\0';
}

static bool parse_backslash(struct arg *arg, const char *args, size_t *pos)
{
	/* We're at the backslash, not within double quotes */
	char c = args[*pos + 1];

	switch (c) {
	case 0:
		return false;
	case '\n':
		break;
	default:
		arg_putchar(arg, c);
		break;
	}

	*pos += 1;
	return true;
}

static bool parse_quoted_backslash(struct arg *arg,
						const char *args, size_t *pos)
{
	/* We're at the backslash, within double quotes */
	char c = args[*pos + 1];

	switch (c) {
	case 0:
		return false;
	case '\n':
		break;
	case '"':
	case '\\':
		arg_putchar(arg, c);
		break;
	default:
		arg_putchar(arg, '\\');
		arg_putchar(arg, c);
		break;
	}

	*pos += 1;
	return true;
}

static bool parse_single_quote(struct arg *arg, const char *args, size_t *pos)
{
	/* We're just past the single quote */
	size_t start = *pos;

	for (; args[*pos]; *pos += 1) {
		if (args[*pos] != '\'')
			continue;

		arg_putmem(arg, args + start, *pos - start);
		return true;
	}

	/* Unterminated ' */
	return false;
}

static bool parse_double_quote(struct arg *arg, const char *args, size_t *pos)
{
	/* We're just past the double quote */
	for (; args[*pos]; *pos += 1) {
		char c = args[*pos];

		switch (c) {
		case '"':
			return true;
		case '\\':
			if (!parse_quoted_backslash(arg, args, pos))
				return false;

			break;
		default:
			arg_putchar(arg, c);
			break;
		}
	}

	/* Unterminated */
	return false;
}

static void add_arg(char ***args, char *arg, int *n_args)
{
	*args = l_realloc(*args, sizeof(char *) * (2 + *n_args));
	(*args)[*n_args] = arg;
	(*args)[*n_args + 1] = NULL;

	*n_args += 1;
}

LIB_EXPORT char **l_parse_args(const char *args, int *out_n_args)
{
	size_t i;
	struct arg arg;
	char **ret = l_realloc(NULL, sizeof(char *));
	int n_args = 0;

	ret[0] = NULL;
	arg_init(&arg);

	for (i = 0; args[i]; i++) {
		switch (args[i]) {
		case '\\':
			if (!parse_backslash(&arg, args, &i))
				goto error;
			break;
		case '"':
			i += 1;
			if (!parse_double_quote(&arg, args, &i))
				goto error;

			/* Add an empty string */
			if (!arg.cur_len)
				add_arg(&ret, l_strdup(""), &n_args);

			break;
		case '\'':
			i += 1;
			if (!parse_single_quote(&arg, args, &i))
				goto error;

			/* Add an empty string */
			if (!arg.cur_len)
				add_arg(&ret, l_strdup(""), &n_args);

			break;
		default:
			if (!strchr(" \t", args[i])) {
				if (args[i] == '\n')
					goto error;

				arg_putchar(&arg, args[i]);
				continue;
			}

			if (arg.cur_len)
				add_arg(&ret, arg.chars, &n_args);

			arg_init(&arg);
			break;
		}
	}

	if (arg.cur_len)
		add_arg(&ret, arg.chars, &n_args);

	if (out_n_args)
		*out_n_args = n_args;

	return ret;

error:
	l_free(arg.chars);
	l_strfreev(ret);
	return NULL;
}
