#include "rg_system.h"
#include "rg_utils.h"

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

char *rg_strtolower(char *str)
{
    if (!str)
        return NULL;

    for (char *c = str; *c; c++)
        if (*c >= 'A' && *c <= 'Z')
            *c += 32;

    return str;
}

char *rg_strtoupper(char *str)
{
    if (!str)
        return NULL;

    for (char *c = str; *c; c++)
        if (*c >= 'a' && *c <= 'z')
            *c -= 32;

    return str;
}

char *rg_json_fixup(char *json)
{
    // Strip trailing commas, eg [,1,2,3] {"a":1,}
    for (char *ptr = json, *prev = ptr; ptr && *ptr; ++ptr)
    {
        if ((*ptr == '}' || *ptr == ']' || *ptr == '{' || *ptr == '[' || *ptr == ',') && *prev == ',')
        {
            RG_LOGW("Found trailing comma at pos %d", (int)(ptr - json));
            *prev = ' ';
        }
        if (*ptr != '\t' && *ptr != '\n'  && *ptr != '\r' && *ptr != ' ')
            prev = ptr;
    }

    // TODO: We should also strip C-style comments!

    return json;
}

int rg_utf8_decode(const char **ptr)
{
    // This implementation is based solely on https://en.wikipedia.org/wiki/UTF-8#Description
    // It's probably wrong in many ways but I'm sure it'll be good enough for us :)
    if (!ptr)
        return -1;

    int first_byte = **ptr;
    int codepoint = 0;
    size_t extra_bytes = 0;

    *ptr += 1; // Always consume the first byte

    if ((first_byte & 0x80) == 0x00)
    {
        return first_byte;
    }
    else if ((first_byte & 0xE0) == 0xC0)
    {
        codepoint = first_byte & 0x1F;
        extra_bytes = 1;
    }
    else if ((first_byte & 0xF0) == 0xE0)
    {
        codepoint = first_byte & 0x0F;
        extra_bytes = 2;
    }
    else if ((first_byte & 0xF8) == 0xF0)
    {
        codepoint = first_byte & 0x07;
        extra_bytes = 3;
    }
    else // Invalid prefix
    {
        RG_LOGD("Invalid utf-8 prefix");
        return -1; // first_byte
    }

    for (size_t i = 0; i < extra_bytes; ++i)
    {
        int next_byte = *(*ptr + i);
        if ((next_byte & 0xC0) != 0x80)
        {
            RG_LOGD("Invalid multi-byte codepoint");
            return -1; // first_byte
        }
        codepoint <<= 6;
        codepoint += next_byte & 0x3F;
    }

    *ptr += extra_bytes;
    return codepoint;
}

size_t rg_utf8_encode(char *output, int codepoint)
{
    if (codepoint <= 0x7F) // 1 byte
    {
        output[0] = codepoint & 0xFF;
        return 1;
    }
    else if (codepoint <= 0x7FF) // 2 bytes
    {
        output[0] = 0xC0 | ((codepoint >> 6) & 0x1F);
        output[1] = 0x80 | (codepoint & 0x3F);
        return 2;
    }
    else if (codepoint <= 0xFFFF) // 3 bytes
    {
        output[0] = 0xE0 | ((codepoint >> 12) & 0x0F);
        output[1] = 0x80 | ((codepoint >> 6) & 0x3F);
        output[2] = 0x80 | (codepoint & 0x3F);
        return 3;
    }
    else if (codepoint <= 0x10FFFF) // 4 bytes
    {
        output[0] = 0xF0 | ((codepoint >> 18) & 0x07);
        output[1] = 0x80 | ((codepoint >> 12) & 0x3F);
        output[2] = 0x80 | ((codepoint >> 6) & 0x3F);
        output[3] = 0x80 | (codepoint & 0x3F);
        return 4;
    }
    RG_LOGD("Out of range codepoint 0x%X", codepoint);
    return 0;
}

size_t rg_utf8_strlen(const char *str)
{
    if (!str)
        return 0;

    size_t length = 0;
    while (*str)
    {
        if (rg_utf8_decode(&str) > 0)
            length++;
    }
    return length;
}

const char *rg_dirname(const char *path)
{
    static char buffer[100];
    const char *basename = strrchr(path, '/');
    ptrdiff_t length = basename - path;

    if (!path || !basename)
        return ".";

    if (path[0] == '/' && path[1] == 0)
        return "/";

    RG_ASSERT(length < 100, "to do: use heap");

    strncpy(buffer, path, length);
    buffer[length] = 0;

    return buffer;
}

const char *rg_basename(const char *path)
{
    if (!path)
        return ".";

    const char *name = strrchr(path, '/');
    return name ? name + 1 : path;
}

const char *rg_extension(const char *filename)
{
    if (!filename)
        return NULL;
    const char *ptr = filename + strlen(filename) - 1;
    while (ptr > filename && *ptr != '/')
    {
        if (*ptr == '.')
            return ptr + 1;
        ptr--;
    }
    return NULL;
}

bool rg_extension_match(const char *filename, const char *extensions)
{
    const char *ext = rg_extension(filename);
    if (!ext || !extensions)
        return false;

    const char *haystack = extensions;
    while (*haystack)
    {
        while (*haystack == ' ')
            haystack++;

        const char *needle = ext;
        while (*needle && *haystack && tolower((unsigned char)*needle) == tolower((unsigned char)*haystack))
        {
            needle++;
            haystack++;
        }
        if (*needle == 0 && (*haystack == 0 || *haystack == ' '))
            return true;

        // Fast forward to next extension
        while (*haystack && *haystack != ' ')
            haystack++;
    }

    return false;
}

const char *rg_relpath(const char *path)
{
    if (!path)
        return NULL;

    if (strncmp(path, RG_STORAGE_ROOT, strlen(RG_STORAGE_ROOT)) == 0)
    {
        const char *relpath = path + strlen(RG_STORAGE_ROOT);
        if (relpath[0] == '/' || relpath[0] == 0)
            path = relpath;
    }
    return path;
}

uint32_t rg_crc32(uint32_t crc, const uint8_t *buf, size_t len)
{
#ifdef ESP_PLATFORM
    // This is part of the ROM but finding the correct header is annoying as it differs per SOC...
    extern uint32_t crc32_le(uint32_t crc, const uint8_t *buf, uint32_t len);
    return crc32_le(crc, buf, len);
#else
    // Derived from: http://www.hackersdelight.org/hdcodetxt/crc.c.txt
    crc = ~crc;
    for (size_t i = 0; i < len; ++i)
    {
        crc = crc ^ buf[i];
        for (int j = 7; j >= 0; j--) // Do eight times.
        {
            uint32_t mask = -(crc & 1);
            crc = (crc >> 1) ^ (0xEDB88320 & mask);
        }
    }
    return ~crc;
#endif
}

/**
 * This function is the SuperFastHash from:
 *  http://www.azillionmonkeys.com/qed/hash.html
*/
IRAM_ATTR uint32_t rg_hash(const char *data, size_t len)
{
    #define get16bits(d) (*((const uint16_t *)(d)))

    if (len <= 0 || data == NULL)
        return 0;

    uint32_t hash = len, tmp;
    int rem = len & 3;
    len >>= 2;

    /* Main loop */
    for (; len > 0; len--)
    {
        hash += get16bits(data);
        tmp = (get16bits(data + 2) << 11) ^ hash;
        hash = (hash << 16) ^ tmp;
        data += 2 * sizeof(uint16_t);
        hash += hash >> 11;
    }

    /* Handle end cases */
    switch (rem)
    {
    case 3:
        hash += get16bits(data);
        hash ^= hash << 16;
        hash ^= ((signed char)data[sizeof(uint16_t)]) << 18;
        hash += hash >> 11;
        break;
    case 2:
        hash += get16bits(data);
        hash ^= hash << 11;
        hash += hash >> 17;
        break;
    case 1:
        hash += (signed char)*data;
        hash ^= hash << 10;
        hash += hash >> 1;
    }

    /* Force "avalanching" of final 127 bits */
    hash ^= hash << 3;
    hash += hash >> 5;
    hash ^= hash << 4;
    hash += hash >> 17;
    hash ^= hash << 25;
    hash += hash >> 6;

    #undef get16bits
    return hash;
}

typedef struct
{
    uint16_t length;
    uint16_t unused;
    char data[];
} unique_string_t;

const char *rg_unique_string(const char *str)
{
    static const unique_string_t **strings = NULL;
    static size_t strings_count = 0;

    if (!str)
        return NULL;

    size_t len = strlen(str);

    for (int i = 0; i < strings_count; i++)
    {
        if (strings[i]->length != len)
            continue;
        if (memcmp(strings[i]->data, str, len + 1) == 0)
            return strings[i]->data;
    }

    unique_string_t *obj = malloc(sizeof(unique_string_t) + len + 1);

    strings = realloc(strings, (strings_count + 1) * sizeof(unique_string_t *));
    RG_ASSERT(strings && obj, "alloc failed");

    memcpy(obj->data, str, len + 1);
    obj->length = len;

    strings[strings_count++] = obj;

    return obj->data;
}

typedef struct rg_bucket_s
{
    size_t capacity;
    size_t cursor;
    rg_bucket_t *prev;
    rg_bucket_t *next;
    uint8_t data[];
} rg_bucket_t;

rg_bucket_t *rg_bucket_create(size_t capacity_bytes)
{
    rg_bucket_t *bucket = calloc(1, sizeof(rg_bucket_t) + capacity_bytes);
    if (!bucket)
        return NULL;
    bucket->capacity = capacity_bytes;
    return bucket;
}

void *rg_bucket_insert(rg_bucket_t *bucket, const void *item, size_t item_bytes)
{
    if (!bucket || bucket->capacity < item_bytes)
    {
        RG_LOGW("Item size exceeds bucket capacity!");
        return NULL;
    }
    while (bucket->cursor + item_bytes > bucket->capacity)
    {
        if (!bucket->next) // End of the list, must allocate
        {
            rg_bucket_t *new_bucket = rg_bucket_create(bucket->capacity);
            if (!new_bucket)
                return NULL;
            new_bucket->prev = bucket;
            bucket->next = new_bucket;
            bucket = new_bucket;
            break;
        }
        bucket = bucket->next;
    }
    void *ptr = bucket->data + bucket->cursor;
    if (item)
        memcpy(ptr, item, item_bytes);
    bucket->cursor += ((item_bytes + (sizeof(int) - 1)) / sizeof(int)) * sizeof(int);
    // bucket->cursor += item_bytes;
    return ptr;
}

void rg_bucket_free(rg_bucket_t *bucket)
{
    while (bucket)
    {
        rg_bucket_t *next = bucket->next;
        free(bucket);
        bucket = next;
    }
}

void *rg_alloc(size_t size, uint32_t caps)
{
    char caps_list[36] = "";
    size_t available = 0;
    void *ptr;

    if (caps & MEM_SLOW)
        strcat(caps_list, "SPIRAM|");
    if (caps & MEM_FAST)
        strcat(caps_list, "INTERNAL|");
    if (caps & MEM_DMA)
        strcat(caps_list, "DMA|");
    if (caps & MEM_EXEC)
        strcat(caps_list, "IRAM|");
    strcat(caps_list, (caps & MEM_32BIT) ? "32BIT" : "8BIT");

#ifdef ESP_PLATFORM
    uint32_t esp_caps = 0;
    esp_caps |= (caps & MEM_SLOW ? MALLOC_CAP_SPIRAM : (caps & MEM_FAST ? MALLOC_CAP_INTERNAL : 0));
    esp_caps |= (caps & MEM_DMA ? MALLOC_CAP_DMA : 0);
    esp_caps |= (caps & MEM_EXEC ? MALLOC_CAP_EXEC : 0);
    esp_caps |= (caps & MEM_32BIT ? MALLOC_CAP_32BIT : MALLOC_CAP_8BIT);

    if (!(ptr = heap_caps_calloc(1, size, esp_caps)))
    {
        available = heap_caps_get_largest_free_block(esp_caps);
        // Loosen the caps and try again
        if ((ptr = heap_caps_calloc(1, size, esp_caps & ~(MALLOC_CAP_SPIRAM | MALLOC_CAP_INTERNAL))))
        {
            RG_LOGW("SIZE=%d, CAPS=%s, PTR=%p << CAPS not fully met! (available: %d)\n",
                    (int)size, caps_list, ptr, (int)available);
            return ptr;
        }
    }
#else
    ptr = calloc(1, size);
#endif

    if (!ptr)
    {
        RG_LOGE("SIZE=%d, CAPS=%s << FAILED! (available: %d)\n", (int)size, caps_list, (int)available);
        if (caps & MEM_NOPANIC)
            return NULL;
        RG_PANIC("Memory allocation failed!");
    }

    RG_LOGI("SIZE=%d, CAPS=%s, PTR=%p\n", (int)size, caps_list, ptr);
    return ptr;
}

void rg_usleep(uint32_t us)
{
    int64_t goal = rg_system_timer() + us;
    // Only yield if it's for more than one tick duration, otherwise the delay will overshoot
    if (us >= 1000000 / RG_TICK_RATE)
        rg_task_delay(us / 1000);
    // Then we busy wait, which is fine as it's a short delay
    while (rg_system_timer() < goal)
        continue;
}
