// Kyler Olsen
// YREA SLS
// String Helpers
// November 2025

#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <math.h>
#include <stdlib.h>

#include "sls/string.h"
#include "sls/lexer.h"

int sls_isascii(unsigned char c) {
    return c < 128;
}

size_t sls_str_nlen(const char *s, size_t maxlen) {
    size_t i;
    for (i = 0; i < maxlen; i++)
        if (s[i] == '\0') break;
    return i;
}

SlsStr sls_str_malloc(const char *s, size_t maxlen) {
    size_t length = sls_str_nlen(s, maxlen);
    char *new_str = (char *)malloc(sizeof(char) * (length + 1));
    if (new_str == NULL) return SLS_STR_NULL;
    memcpy(new_str, s, length);
    new_str[length] = '\0';
    return (SlsStr){length, new_str, TRUE};
}

SlsStr sls_str_new(size_t length) {
    char *new_str = (char *)calloc(length + 1, sizeof(char));
    if (new_str == NULL) return SLS_STR_NULL;
    return (SlsStr){length, new_str, TRUE};
}

SlsStr sls_str_cpy(const SlsStr s) {
    return sls_str_malloc(s.str, s.len);
}

int32_t sls_str_cmp(const SlsStr a, const SlsStr b) {
    int cmp = strncmp(a.str, b.str, (a.len < b.len) ? a.len : b.len);
    if (cmp != 0) return cmp;
    return (a.len > b.len) - (a.len < b.len);
}

void sls_str_free(SlsStr *s) {
    if (s->allocated) {
        free((void *)s->str);
        s->len = 0;
        s->str = NULL;
        s->allocated = FALSE;
    }
}

typedef enum {
    FORMAT_PERCENT_ESCAPE,
    FORMAT_C_STRINGS,
    FORMAT_CHARACTER,
    FORMAT_INTEGER_32,
    FORMAT_INTEGER_64,
    FORMAT_UNSIGNED_INTEGER_64,
    FORMAT_SIZE_INTEGER,
    FORMAT_FLOAT,
    FORMAT_SLS_STR,
    FORMAT_SLS_TOKEN_TYPE,
    FORMAT_SLS_ARRAY_TYPE,
    FORMAT_SLS_BUILTIN_INTEGER,
    FORMAT_SLS_ERROR,
    FORMAT_SLS_BOOLEAN,
} FormatStringTypes;

typedef struct {
    FormatStringTypes type;
    union {
        const char *c_string;
        char character;
        int32_t integer_32;
        int64_t integer_64;
        uint64_t unsigned_integer_64;
        size_t size_integer;
        double ffloat;
        SlsStr sls_str;
        TokenType token_type;
        ArrayType array_type;
        IntegerBuiltInType builtin_integer;
        SlsError error;
        Boolean boolean;
    };
    ptrdiff_t str_index;
    size_t self_length;
} FormatStringItem;

static size_t number_length(int64_t i) {
    if (i == 0) return 1;
    size_t len = (i < 0 ? 1 : 0);
    while (i) { len++; i /= 10; }
    return len;
}

static size_t unsigned_number_length(uint64_t i) {
    if (i == 0) return 1;
    size_t len = 0;
    while (i) { len++; i /= 10; }
    return len;
}

SlsStr sls_format(const SlsStr s, ...) {
    va_list args;
    va_start(args, s);
    size_t count = 0;
    const char *current = strchr(s.str, '%');
    do {
        if (!current) break;
        if (!current[1]) break;
        switch (current[1]) {
        case '%':
        case 'y':
        case 'c':
        case 'd':
        case 'l':
        case 'u':
        case 'z':
        case 'f':
        case 's':
        case 't':
        case 'a':
        case 'i':
        case 'e':
        case 'b':
            count++;
            break;
        }
        current = strchr(current + 2, '%');
    } while (current);

    FormatStringItem *items = (FormatStringItem *)malloc(sizeof(FormatStringItem) * count);
    if (items == NULL) return SLS_STR_NULL;
    size_t i = 0;
    const char *last_index = s.str;
    size_t length = s.len;
    current = strchr(s.str, '%');
    do {
        switch (current[1]) {
        case '%':
            items[i].type = FORMAT_PERCENT_ESCAPE;
            length += items[i].self_length = 1;
            length -= 2;
            break;
        case 'y':
            items[i].type = FORMAT_C_STRINGS;
            items[i].c_string = va_arg(args, const char *);
            length += items[i].self_length = strlen(items[i].c_string);
            length -= 2;
            break;
        case 'c':
            items[i].type = FORMAT_CHARACTER;
            items[i].character = va_arg(args, int);
            length += items[i].self_length = 1;
            length -= 2;
            break;
        case 'd':
            items[i].type = FORMAT_INTEGER_32;
            items[i].integer_32 = va_arg(args, int32_t);
            length += items[i].self_length = number_length(items[i].integer_32);
            length -= 2;
            break;
        case 'l':
            items[i].type = FORMAT_INTEGER_64;
            items[i].integer_64 = va_arg(args, int64_t);
            length += items[i].self_length = number_length(items[i].integer_64);
            length -= 2;
            break;
        case 'u':
            items[i].type = FORMAT_UNSIGNED_INTEGER_64;
            items[i].unsigned_integer_64 = va_arg(args, uint64_t);
            length += items[i].self_length = unsigned_number_length(items[i].unsigned_integer_64);
            length -= 2;
            break;
        case 'z':
            items[i].type = FORMAT_SIZE_INTEGER;
            items[i].size_integer = va_arg(args, size_t);
            length += items[i].self_length = unsigned_number_length(items[i].size_integer);
            length -= 2;
            break;
        case 'f':
            items[i].type = FORMAT_FLOAT;
            items[i].ffloat = va_arg(args, double);
            length += items[i].self_length = snprintf(NULL, 0, "%.2f", items[i].ffloat);
            length -= 2;
            break;
        case 's':
            items[i].type = FORMAT_SLS_STR;
            items[i].sls_str = va_arg(args, SlsStr);
            length += items[i].self_length = items[i].sls_str.len;
            length -= 2;
            break;
        case 't':
            items[i].type = FORMAT_SLS_TOKEN_TYPE;
            items[i].token_type = va_arg(args, TokenType);
            if (items[i].token_type >= TOKEN_TYPE_COUNT) {
                free(items);
                return SLS_STR_NULL;
            }
            length += items[i].self_length = sls_str_nlen(TOKEN_TYPES_NAMES[items[i].token_type], TYPE_NAMES_SAFE_LENGTH);
            length -= 2;
            break;
        case 'a':
            items[i].type = FORMAT_SLS_ARRAY_TYPE;
            items[i].array_type = va_arg(args, ArrayType);
            if (items[i].array_type >= ARRAY_TYPE_COUNT) {
                free(items);
                return SLS_STR_NULL;
            }
            length += items[i].self_length = sls_str_nlen(ARRAY_TYPES_NAMES[items[i].array_type], TYPE_NAMES_SAFE_LENGTH);
            length -= 2;
            break;
        case 'i':
            items[i].type = FORMAT_SLS_BUILTIN_INTEGER;
            items[i].builtin_integer = va_arg(args, IntegerBuiltInType);
            if (items[i].builtin_integer >= INTEGER_TYPE_COUNT) {
                free(items);
                return SLS_STR_NULL;
            }
            length += items[i].self_length = sls_str_nlen(INTEGER_TYPES_NAMES[items[i].builtin_integer], TYPE_NAMES_SAFE_LENGTH);
            length -= 2;
            break;
        case 'e':
            items[i].type = FORMAT_SLS_ERROR;
            items[i].error = va_arg(args, SlsError);
            length += items[i].self_length = items[i].error.message.len;
            length -= 2;
            break;
        case 'b':
            items[i].type = FORMAT_SLS_BOOLEAN;
            items[i].boolean = va_arg(args, Boolean);
            length += items[i].self_length = (items[i].boolean ? 4 : 5);
            length -= 2;
            break;
        }
        items[i].str_index = (ptrdiff_t)(current - last_index);
        last_index = current + 2;
        i++;
        current = strchr(current + 2, '%');
    } while (current);
    va_end(args);

    char *temp = (char *)malloc(sizeof(char) * length);
    if (temp == NULL) {
        free(items);
        return SLS_STR_NULL;
    }
    SlsStr str_new = sls_str_new(length);
    if (str_new.str == NULL) {
        free(items);
        free(temp);
        return SLS_STR_NULL;
    }
    char *str = (char *)str_new.str;
    size_t item_i = 0;
    ptrdiff_t target_i = 0;
    ptrdiff_t source_i = 0;

    while (item_i < count) {
        memcpy(str + target_i, s.str + source_i, items[item_i].str_index);
        target_i += items[item_i].str_index;
        source_i += items[item_i].str_index + 2;

        switch (items[item_i].type) {
        case FORMAT_PERCENT_ESCAPE:
            memcpy(temp, "%", items[item_i].self_length + 1);
            break;
        case FORMAT_C_STRINGS:
            snprintf(temp, items[item_i].self_length + 1, "%s", items[item_i].c_string);
            break;
        case FORMAT_CHARACTER:
            snprintf(temp, items[item_i].self_length + 1, "%c", items[item_i].character);
            break;
        case FORMAT_INTEGER_32:
            snprintf(temp, items[item_i].self_length + 1, "%d", items[item_i].integer_32);
            break;
        case FORMAT_INTEGER_64:
            snprintf(temp, items[item_i].self_length + 1, "%ld", items[item_i].integer_64);
            break;
        case FORMAT_UNSIGNED_INTEGER_64:
            snprintf(temp, items[item_i].self_length + 1, "%lu", items[item_i].unsigned_integer_64);
            break;
        case FORMAT_SIZE_INTEGER:
            snprintf(temp, items[item_i].self_length + 1, "%zu", items[item_i].size_integer);
            break;
        case FORMAT_FLOAT:
            snprintf(temp, items[item_i].self_length + 1, "%.2f", items[item_i].ffloat); // Fixed-point decimal display
            break;
        case FORMAT_SLS_STR:
            snprintf(temp, items[item_i].self_length + 1, "%s", items[item_i].sls_str.str);
            break;
        case FORMAT_SLS_TOKEN_TYPE:
            snprintf(temp, items[item_i].self_length + 1, "%s", TOKEN_TYPES_NAMES[items[item_i].token_type]);
            break;
        case FORMAT_SLS_ARRAY_TYPE:
            snprintf(temp, items[item_i].self_length + 1, "%s", ARRAY_TYPES_NAMES[items[item_i].array_type]);
            break;
        case FORMAT_SLS_BUILTIN_INTEGER:
            snprintf(temp, items[item_i].self_length + 1, "%s", INTEGER_TYPES_NAMES[items[item_i].builtin_integer]);
            break;
        case FORMAT_SLS_ERROR:
            snprintf(temp, items[item_i].self_length + 1, "%s", items[item_i].error.message.str);
            break;
        case FORMAT_SLS_BOOLEAN:
            memcpy(temp, (items[item_i].boolean ? "TRUE" : "FALSE"), items[item_i].self_length + 1);
            break;
        }

        memcpy(str + target_i, temp, items[item_i].self_length);
        target_i += items[item_i].self_length;
        item_i++;
    }
    if (s.len > (size_t)source_i)
        memcpy(str + target_i, s.str + source_i, s.len - source_i);
    str[str_new.len] = '\0';

    free(items);
    free(temp);
    return str_new;
}
