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

struct StringMeta {
    MemoryManager *mm;
    usize length;
};

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

static inline void*
S_start(MemoryManager *mm, usize length) {
    if (NULL == mm)
        return NULL;

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

    void* start = NULL;
    if (R_Ok != mm->alloc(*mm, &start, sizeof(sm) + sm.length + 1) ||
        NULL == start)
    {
        return NULL;
    }

    memcpy(start, &sm, sizeof(sm));
    return start + sizeof(sm);
}

String
S_new(MemoryManager *mm, const char* str) {
    if (NULL == str)
        return NULL;

    usize strlength = strlen(str);

    void* start = S_start(mm, strlength);

    if (NULL != start)
        memcpy(start, str, strlength + 1);

    return start;
}

String
S_newlen(MemoryManager *mm, usize length) {
    void* start = S_start(mm, length);

    if (NULL != start)
        memset(start, '\0', length + 1);

    return start;
}

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

String
S_dup(MemoryManager *mm, String s) {
    if (NULL == mm || NULL == s)
        return NULL;

    usize allocated = S_allocated(s);

    void* start = NULL;
    if (R_Ok != mm->alloc(*mm, &start, allocated) ||
        NULL == start)
    {
        return NULL;
    }

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

String
S_vconcat(MemoryManager *mm, va_list args) {
    if (NULL == mm)
        return NULL;

    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(mm, length);

    if (NULL == result)
        return NULL;

    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(MemoryManager *mm, ...) {
    va_list args;
    usize length = 0;

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

    return result;
}

String
S_vprintf(MemoryManager *mm, const char* fmt, va_list args) {
    if (NULL == mm)
        return NULL;

    va_list temp;
    va_copy(temp, args);
    usize length = vsnprintf(NULL, 0, fmt, temp);
    va_end(temp);

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

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

String
S_printf(MemoryManager *mm, const char* fmt, ...) {
    va_list args;

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

    return result;
}

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

static inline Result
S_resize(String *s, usize length) {
    if (NULL == s || NULL == *s)
        return R_Uninitialized;

    struct StringMeta *sm = S_metadata(*s);

    void *start = sm;
    RETURN_NOTOK(
        sm->mm->realloc(*sm->mm, &start, S_allocated(*s) + length));
    *s = start + sizeof(*sm);

    return R_Ok;
}

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

    usize strlength = strlen(str);

    RETURN_NOTOK(S_resize(s, strlength));

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

    return R_Ok;
}

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

    usize strlength = strlen(str);

    RETURN_NOTOK(S_resize(s, strlength));

    struct StringMeta *sm = S_metadata(*s);

    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;
}
