#ifndef PROTOTYPE1_SRC_L1_CORE_VECU8_AS_STR_H
#define PROTOTYPE1_SRC_L1_CORE_VECU8_AS_STR_H

#include <stdarg.h>

#ifdef PROTOTYPE1_L1_CODEGEN_BOOTSTRAP_USE_CHICKEN_VECU8
#include "chicken_VecU8.h"
#else
#include "../../../gen/l1/VecAndSpan_U8.h"
#endif

VecU8 VecU8_from_cstr(const char* dc) {
    size_t n = strlen(dc);
    VecU8 res = (VecU8){ .buf = safe_calloc(n, 1), .len = n, .capacity = n };
    memcpy(res.buf, dc, n);
    return res;
}

SpanU8 SpanU8_from_cstr(const char* dc) {
    return (SpanU8){.data = (const U8*)dc, .len = strlen(dc)};
}

#define vcstr(dc) (VecU8_from_cstr(dc))
#define cstr(dc) ((SpanU8){(const U8*)dc, sizeof(dc) - 1})

/* Not thread safe (for stdout) !*/
void SpanU8_print(SpanU8 str) {
    for (size_t i = 0; i < str.len; i++)
        putc((int)*SpanU8_at(str, i), stdout);
}

/* Not thread safe (for `stream`) ! */
void SpanU8_fprint(SpanU8 str, FILE* stream) {
    for (size_t i = 0; i < str.len; i++)
        putc((int)*SpanU8_at(str, i), stream);
}

NODISCARD VecU8 VecU8_format(const char *fmt, ...) {
    assert(fmt);

    /*  first pass: figure out required length  */
    va_list ap;
    va_start(ap, fmt);
    va_list ap_copy;
    va_copy(ap_copy, ap);

    /* bytes *without* NUL */
    const int needed = vsnprintf(NULL, 0, fmt, ap);
    va_end(ap);
    if (needed < 0)
        abortf("Formatting error\n");
    const size_t len = (size_t)needed;
    /*  allocate buffer (+1 so vsnprintf can add its NUL)  */
    U8 *buf = safe_malloc(len + 1);
    /*  second pass: actually format  */
    vsnprintf((char *)buf, len + 1, fmt, ap_copy);
    va_end(ap_copy);

    return (VecU8){ .buf = buf, .len = len, .capacity = len + 1 };
}

size_t S64_stringification_get_length(S64 x){
    if (x == -9223372036854775807L-1)
        return 20;
    int i = 0;
    if (x <= 0) {
        x = -x;
        i++;
    }
    while (x > 0) {
        i++;
        x /= 10;
    }
    return i;
}

void S64_stringification_into_buf(S64 x, VecU8* targ){
    if (x == 0) {
        VecU8_append(targ, '0');
    } else if (x == -9223372036854775807L-1) {
        VecU8_append_span(targ, cstr("-9223372036854775808"));
    } else {
        if (x < 0) {
            VecU8_append(targ, '-');
            x = -x;
        }
        int digits = 0;
        while (x > 0) {
            VecU8_append(targ, '0' + (x % 10));
            x /= 10;
            digits++;
        }
        U8* left = targ->buf + targ->len - digits;
        U8* right = targ->buf + targ->len - 1;
        while (left < right) {
            U8 t = *right;
            *right = *left;
            *left = t;
            left++;
            right--;
        }
    }
}

size_t U64_stringification_get_length(U64 x){
    if (x == 0)
        return 1;
    int i = 0;
    while (x > 0) {
        i++;
        x /= 10;
    }
    return i;
}

void U64_stringification_into_buf(U64 x, VecU8* targ){
    if (x == 0) {
        VecU8_append(targ, '0');
    } else {
        int digits = 0;
        while (x > 0) {
            VecU8_append(targ, '0' + (x % 10));
            x /= 10;
            digits++;
        }
        U8* left = targ->buf + targ->len - digits;
        U8* right = targ->buf + targ->len - 1;
        while (left < right) {
            U8 t = *right;
            *right = *left;
            *left = t;
            left++;
            right--;
        }
    }
}

/* %s - SpanU8
 * %v - VecU8
 * %u - U64
 * %c - int (one byte character)
 * %i - S64
 */
NODISCARD VecU8 VecU8_fmt(const char* fmt, ...) {
    assert(fmt);
    size_t k = 0;
    va_list args;
    va_start(args, fmt);
    for (const char *ch = fmt; *ch; ) {
        if (*ch == '%') {
            ch++;
            if (*ch == '%') {
                k++;
            } else if (*ch == 's') {
                SpanU8 s = va_arg(args, SpanU8);
                k += s.len;
            } else if (*ch == 'c') {
                va_arg(args, int);
                k++;
            } else if (*ch == 'v') {
                /* We had not taken ownership of YET (will take it the next iteration) */
                VecU8 vs = va_arg(args, VecU8);
                k += vs.len;
            } else if (*ch == 'i') {
                S64 x = va_arg(args, S64);
                k += S64_stringification_get_length(x);
            } else if (*ch == 'u') {
                U64 x = va_arg(args, U64);
                k += U64_stringification_get_length(x);
            } else
                abortf("Format syntax error at pos %lu! Watch out, be careful", (size_t)(ch - fmt));
        } else {
            k++;
        }
        ch++;
    }
    va_end(args);
    VecU8 res = VecU8_new_reserved(k);
    va_start(args, fmt);
    for (const char *ch = fmt; *ch;) {
        if (*ch == '%') {
            ch++;
            if (*ch == '%') {
                VecU8_append(&res, '%');
            } else if (*ch == 's') {
                SpanU8 s = va_arg(args, SpanU8);
                VecU8_append_span(&res, s);
            } else if (*ch == 'c') {
                int byte = va_arg(args, int);
                VecU8_append(&res, (U8)byte);
            } else if (*ch == 'v') {
                VecU8 vs = va_arg(args, VecU8);
                VecU8_append_vec(&res, vs); /* Moved ownership of vs argument */
            } else if (*ch == 'i') {
                S64 x = va_arg(args, S64);
                S64_stringification_into_buf(x, &res);
            } else if (*ch == 'u') {
                U64 x = va_arg(args, U64);
                U64_stringification_into_buf(x, &res);
            } else
                assert(false);
        } else {
            VecU8_append(&res, *ch);
        }
        ch++;
    }
    va_end(args);
    return res;
}

// todo: generate a special span method to check equality of contents
bool strings_in_spans_equal(SpanU8 a, SpanU8 b) {
    if (a.len != b.len)
        return false;
    for (size_t i = 0; i < a.len; i++) {
        if (*SpanU8_at(a, i) != *SpanU8_at(b, i))
            return false;
    }
    return true;
}

/* 0 means error */
U32 SpanU8_decode_as_utf8(SpanU8* rem){
    assert(rem->len > 0);
    U8 first = rem->data[0];
    rem->data++;
    rem->len--;
    if (!(first & 0b10000000))
        return first;
    uint8_t a = 0b11000000;
    uint8_t b = 0b00100000;
    for (int sz = 1; sz <= 3; sz++){
        if ((first & (a | b)) == a) {
            /* sz is the character size in bytes */
            if (rem->len < (size_t)sz)
                return 0;
            U32 res = first & (b - 1);
            for (int i = 1; i < sz; i++) {
                U8 th = rem->data[0];
                if ((th & 0b11000000) != 0b10000000)
                    return 0;
                res <<= 6;
                res |= (th & 0b00111111);
                rem->data++;
            }
            rem->len -= sz;
            return res;
        }
        a |= b;
        b >>= 1;
    }
    return 0;
}

#endif
