#include "String.h"
#include "GlobalArena.h"
#include "Types.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

struct StringMeta {
    BumpArena b;
    usize length;
};

static inline struct StringMeta*
S_metadata(String s) {
    return (struct StringMeta*)(s - sizeof(struct StringMeta));
}

String
S_new(BumpArena b, const char* str) {
    if (NULL == b || NULL == str)
        return NULL;

    struct StringMeta sm = {
        .b = b,
        .length = strlen(str),
    };

    void* start = B_alloc(b, sizeof(sm) + sm.length + 1);
    if (NULL == start)
        return NULL;

    memcpy(start, &sm, sizeof(sm));
    start += sizeof(sm);
    memcpy(start, str, sm.length + 1);

    return start;
}

String
S_newlen(BumpArena b, usize length) {
    if (NULL == b)
        return NULL;

    struct StringMeta sm = {
        .b = b,
        .length = length,
    };

    void* start = B_alloc(b, sizeof(sm) + sm.length + 1);
    if (NULL == start)
        return NULL;

    memcpy(start, &sm, sizeof(sm));
    start += sizeof(sm);
    memset(start, '\0', sm.length + 1);

    return start;
}

static inline usize
S_allocated(String s) {
    return sizeof(struct StringMeta) + S_length(s) + 1;
}

String
S_dup(BumpArena b, String s) {
    if (NULL == b || NULL == s)
        return NULL;

    usize allocated = S_allocated(s);

    void* start = B_alloc(b, allocated);
    if (NULL == start)
        return NULL;

    memcpy(start, s - sizeof(struct StringMeta), allocated);

    return start + sizeof(struct StringMeta);
}

usize
S_length(String s) {
    if (NULL == s)
        return 0;
    return S_metadata(s)->length;
}

Result
S_append(String s, const char* str) {
    if (NULL == s || NULL == str)
        return R_Uninitialized;

    struct StringMeta* sm = S_metadata(s);

    if (!B_resizeable(sm->b, s))
        return R_OutOfBounds;

    usize strlength = strlen(str);
    RETURN_NOTOK(B_resize(sm->b, sizeof(*sm) + (sm->length + strlength) + 1));

    strcat(s, str);
    sm->length += strlength;

    return R_Ok;
}

Result
S_prepend(String s, const char* str) {
    if (NULL == s)
        return R_Uninitialized;

    struct StringMeta* sm = S_metadata(s);

    if (!B_resizeable(sm->b, s))
        return R_OutOfBounds;

    usize strlength = strlen(str);
    RETURN_NOTOK(B_resize(sm->b, sizeof(*sm) + (sm->length + strlength) + 1));

    for (usize i = sm->length; i > 0; --i)
        s[i - 1 + strlength] = s[i - 1];
    for (usize i = 0; i < strlength; ++i)
        s[i] = str[i];

    sm->length += strlength;

    return R_Ok;
}

usize
S_find(String s, char c) {
    if (NULL == s)
        return -1;

    unsigned index = 0;
    while (s[index] != '\0' && s[index] != c)
        ++index;

    return index;
}

static String
S_vconcat(BumpArena b, va_list args) {
    usize length = 0;

    va_list temp;
    va_copy(temp, args);
    for (const char* str = va_arg(temp, const char*); str != NULL; str = va_arg(temp, const char*)) {
        length += strlen(str);
    }
    va_end(temp);

    String result = S_newlen(b, length);

    if (NULL == result)
        return result;

    if (0 < length) {
        for (const char* str = va_arg(args, const char*); str != NULL; str = va_arg(args, const char*))
            strncat(result, str, length);
    }
    return result;
}

String
S_concat(BumpArena b, ...) {
    va_list args;
    usize length = 0;

    va_start(args, b);
    String result = S_vconcat(b, args);
    va_end(args);

    return result;
}

static String
S_vprintf(BumpArena b, const char* fmt, va_list args) {
    va_list temp;
    va_copy(temp, args);
    usize length = vsnprintf(NULL, 0, fmt, temp);
    va_end(temp);

    String result = S_newlen(b, length);
    if (NULL == result)
        return NULL;

    vsnprintf(result, length + 1, fmt, args);
    return result;
}

String
S_printf(BumpArena b, const char* fmt, ...) {
    va_list args;

    va_start(args, fmt);
    String result = S_vprintf(b, fmt, args);
    va_end(args);

    return result;
}

String
SG_new(const char* str) {
    return S_new(g_arena, str);
}

String
SG_newlen(usize length) {
    return S_newlen(g_arena, length);
}

String
SG_dup(String s) {
    return S_dup(g_arena, s);
}

String
SG_concat(const char* str, ...) {
    if (str == NULL)
        return NULL;

    va_list args;

    va_start(args, str);
    String result = S_vconcat(g_arena, args);
    va_end(args);

    S_prepend(result, str);

    return result;
}

String
SG_printf(const char* fmt, ...) {
    va_list args;

    va_start(args, fmt);
    String result = S_vprintf(g_arena, fmt, args);
    va_end(args);

    return result;
}
