/*
 * This file is part of libplacebo.
 *
 * libplacebo 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.1 of the License, or (at your option) any later version.
 *
 * libplacebo 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include "common.h"

PL_API_BEGIN

typedef struct pl_str {
    uint8_t *buf;
    size_t len;
} pl_str;

// For formatting with "%.*s"
#define PL_STR_FMT(str) (int)((str).len), ((str).buf ? (char *)((str).buf) : "")

static inline pl_str pl_str0(const char *str)
{
    return (pl_str) {
        .buf = (uint8_t *) str,
        .len = str ? strlen(str) : 0,
    };
}

// Macro version of pl_str0, for constants
#define PL_STR0(str) ((pl_str) { (uint8_t *) (str), (str) ? strlen(str) : 0 })

static inline pl_str pl_strdup(void *alloc, pl_str str)
{
    return (pl_str) {
        .buf = (uint8_t *) (str.len ? pl_memdup(alloc, str.buf, str.len) : NULL),
        .len = str.len,
    };
}

// Always returns a valid string
static inline char *pl_strdup0(void *alloc, pl_str str)
{
    return pl_strndup0(alloc, str.len ? (char *) str.buf : "", str.len);
}

// Adds a trailing \0 for convenience, even if `append` is an empty string
void pl_str_append(void *alloc, pl_str *str, pl_str append);

// Like `pl_str_append` but for raw memory, omits trailing \0
void pl_str_append_raw(void *alloc, pl_str *str, const void *ptr, size_t size);

// Locale-sensitive string functions
char *pl_asprintf(void *parent, const char *fmt, ...)
    PL_PRINTF(2, 3);
char *pl_vasprintf(void *parent, const char *fmt, va_list ap)
    PL_PRINTF(2, 0);
void pl_str_append_asprintf(void *alloc, pl_str *str, const char *fmt, ...)
    PL_PRINTF(3, 4);
void pl_str_append_vasprintf(void *alloc, pl_str *str, const char *fmt, va_list va)
    PL_PRINTF(3, 0);
int pl_str_sscanf(pl_str str, const char *fmt, ...);

// Locale-invariant versions of append_(v)asprintf
//
// NOTE: These only support a small handful of modifiers. Check `format.c`
// for a list. Calling them on an invalid string will abort!
void pl_str_append_asprintf_c(void *alloc, pl_str *str, const char *fmt, ...)
    PL_PRINTF(3, 4);
void pl_str_append_vasprintf_c(void *alloc, pl_str *str, const char *fmt, va_list va)
    PL_PRINTF(3, 0);

// Variant of the above which takes arguments directly from a pointer in memory,
// reading them incrementally (tightly packed). Returns the amount of bytes
// read from `args`, as determined by the following table:
//
// %c: sizeof(char)
// %d, %u: sizeof(int)
// %f: sizeof(double)
// %lld, %llu: sizeof(long long int)
// %zu: sizeof(size_t)
// %s: \0 terminated string
// %.*s: sizeof(int) + that many bytes (no \0 terminator)
size_t pl_str_append_memprintf_c(void *alloc, pl_str *str, const char *fmt,
                                 const void *args)
    PL_PRINTF(3, 0);

// Locale-invariant number printing
int pl_str_print_hex(char *buf, size_t len, unsigned short n);
int pl_str_print_int(char *buf, size_t len, int n);
int pl_str_print_uint(char *buf, size_t len, unsigned int n);
int pl_str_print_int64(char *buf, size_t len, int64_t n);
int pl_str_print_uint64(char *buf, size_t len, uint64_t n);
int pl_str_print_float(char *buf, size_t len, float n);
int pl_str_print_double(char *buf, size_t len, double n);

// Locale-invariant number parsing
bool pl_str_parse_hex(pl_str str, unsigned short *out);
bool pl_str_parse_int(pl_str str, int *out);
bool pl_str_parse_uint(pl_str str, unsigned int *out);
bool pl_str_parse_int64(pl_str str, int64_t *out);
bool pl_str_parse_uint64(pl_str str, uint64_t *out);
bool pl_str_parse_float(pl_str str, float *out);
bool pl_str_parse_double(pl_str str, double *out);

// Variants of string.h functions
int pl_strchr(pl_str str, int c);
size_t pl_strspn(pl_str str, const char *accept);
size_t pl_strcspn(pl_str str, const char *reject);

// Strip leading/trailing whitespace
pl_str pl_str_strip(pl_str str);

// Generic functions for cutting up strings
static inline pl_str pl_str_take(pl_str str, size_t len)
{
    if (len < str.len)
        str.len = len;
    return str;
}

static inline pl_str pl_str_drop(pl_str str, size_t len)
{
    if (len >= str.len)
        return (pl_str) { .buf = NULL, .len = 0 };

    str.buf += len;
    str.len -= len;
    return str;
}

// Find a substring in another string, and return its index (or -1)
int pl_str_find(pl_str haystack, pl_str needle);

// String splitting functions. These return the part of the string before
// the separator, and optionally the rest (in `out_rest`).
//
// Note that the separator is not included as part of either string.
pl_str pl_str_split_char(pl_str str, char sep, pl_str *out_rest);
pl_str pl_str_split_str(pl_str str, pl_str sep, pl_str *out_rest);

// Like `pl_str_split_char`, but splits on any char in `seps`
pl_str pl_str_split_chars(pl_str str, const char *seps, pl_str *out_rest);

static inline pl_str pl_str_getline(pl_str str, pl_str *out_rest)
{
    return pl_str_split_char(str, '\n', out_rest);
}

// Decode a string containing hexadecimal data. All whitespace will be silently
// ignored. When successful, this allocates a new array to store the output.
bool pl_str_decode_hex(void *alloc, pl_str hex, pl_str *out);

static inline bool pl_str_equals(pl_str str1, pl_str str2)
{
    if (str1.len != str2.len)
        return false;
    if (str1.buf == str2.buf || !str1.len)
        return true;
    return memcmp(str1.buf, str2.buf, str1.len) == 0;
}

static inline bool pl_str_startswith(pl_str str, pl_str prefix)
{
    if (!prefix.len)
        return true;
    if (str.len < prefix.len)
        return false;
    return memcmp(str.buf, prefix.buf, prefix.len) == 0;
}

static inline bool pl_str_endswith(pl_str str, pl_str suffix)
{
    if (!suffix.len)
        return true;
    if (str.len < suffix.len)
        return false;
    return memcmp(str.buf + str.len - suffix.len, suffix.buf, suffix.len) == 0;
}

static inline bool pl_str_eatstart(pl_str *str, pl_str prefix)
{
    if (!pl_str_startswith(*str, prefix))
        return false;

    str->buf += prefix.len;
    str->len -= prefix.len;
    return true;
}

static inline bool pl_str_eatend(pl_str *str, pl_str suffix)
{
    if (!pl_str_endswith(*str, suffix))
        return false;

    str->len -= suffix.len;
    return true;
}

// Convenience wrappers for the above which save the use of a pl_str0
static inline pl_str pl_str_split_str0(pl_str str, const char *sep, pl_str *out_rest)
{
    return pl_str_split_str(str, pl_str0(sep), out_rest);
}

static inline bool pl_str_startswith0(pl_str str, const char *prefix)
{
    return pl_str_startswith(str, pl_str0(prefix));
}

static inline bool pl_str_endswith0(pl_str str, const char *suffix)
{
    return pl_str_endswith(str, pl_str0(suffix));
}

static inline bool pl_str_equals0(pl_str str1, const char *str2)
{
    return pl_str_equals(str1, pl_str0(str2));
}

static inline bool pl_str_eatstart0(pl_str *str, const char *prefix)
{
    return pl_str_eatstart(str, pl_str0(prefix));
}

static inline bool pl_str_eatend0(pl_str *str, const char *prefix)
{
    return pl_str_eatend(str, pl_str0(prefix));
}

// String building helpers, used to lazily construct a string by appending a
// series of string templates which can be executed on-demand into a final
// output buffer.
typedef struct pl_str_builder_t *pl_str_builder;

// Returns the number of bytes consumed from `args`. Be warned that the pointer
// given will not necessarily be aligned to the type you need it as, so make
// sure to use `memcpy` or some other method of safely loading arbitrary data
// from memory.
typedef size_t (*pl_str_template)(void *alloc, pl_str *buf, const uint8_t *args);

pl_str_builder pl_str_builder_alloc(void *alloc);
void pl_str_builder_free(pl_str_builder *builder);

// Resets string builder without destroying buffer
void pl_str_builder_reset(pl_str_builder builder);

// Returns a representative hash of the string builder's output, without
// actually executing it. Note that this is *not* the same as a pl_str_hash of
// the string builder's output.
//
// Note also that the output of this may not survive a process restart because
// of position-independent code and address randomization moving around the
// locatons of template functions, so special care must be taken not to
// compare such hashes across process invocations.
uint64_t pl_str_builder_hash(const pl_str_builder builder);

// Executes a string builder, dispatching all templates. The resulting string
// is guaranteed to be \0-terminated, as a minor convenience.
//
// Calling any other `pl_str_builder_*` function on this builder causes the
// contents of the returned string to become undefined.
pl_str pl_str_builder_exec(pl_str_builder builder);

// Append a template and its arguments to a string builder
void pl_str_builder_append(pl_str_builder builder, pl_str_template tmpl,
                           const void *args, size_t args_size);

// Append an entire other `pl_str_builder` onto `builder`
void pl_str_builder_concat(pl_str_builder builder, const pl_str_builder append);

// Append a constant string. This will only record &str into the buffer, which
// may have a number of unwanted consequences if the memory pointed at by
// `str` mutates at any point in time in the future, or if `str` is not
// at a stable location in memory.
//
// This is intended for strings which are compile-time constants.
void pl_str_builder_const_str(pl_str_builder builder, const char *str);

// Append a string. This will make a full copy of `str`
void pl_str_builder_str(pl_str_builder builder, const pl_str str);
#define pl_str_builder_str0(b, str) pl_str_builder_str(b, pl_str0(str))

// Append a string printf-style. This will preprocess `fmt` to determine the
// number and type of arguments. Supports the same format conversion characters
// as `pl_str_append_asprintf_c`.
void pl_str_builder_printf_c(pl_str_builder builder, const char *fmt, ...)
    PL_PRINTF(2, 3);

void pl_str_builder_vprintf_c(pl_str_builder builder, const char *fmt, va_list ap)
    PL_PRINTF(2, 0);

// Helper macro to specialize `pl_str_builder_printf_c` to
// `pl_str_builder_const_str` if it contains no format characters.
#define pl_str_builder_addf(builder, ...) do                                    \
{                                                                               \
    if (_contains_fmt_chars(__VA_ARGS__)) {                                     \
        pl_str_builder_printf_c(builder, __VA_ARGS__);                          \
    } else {                                                                    \
        pl_str_builder_const_str(builder, _get_fmt(__VA_ARGS__));               \
    }                                                                           \
} while (0)

// Helper macros to deal with the non-portability of __VA_OPT__(,)
#define _contains_fmt_chars(fmt, ...)   (strchr(fmt, '%'))
#define _get_fmt(fmt, ...)              fmt

PL_API_END
