/* Copyright (C) 2020-2025  Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#include <igloo/sp.h>
#include <igloo/error.h>
#include "private.h"

static const char empty_string[] = "";

static const char *constant_strings[] = {
    // Numbers:
    "-1",
    "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
    "10", "100", "1000", "10000", "100000", "1000000",
    "15", "16", "31", "32", "63", "64", "127", "128",
    "255", "256", "511", "512", "1023", "1024", "2047", "2048",
    "4095", "4096", "8191", "8192", "16383", "16384",
    "32767", "32768", "65535", "65536", "131071", "131072",
    "262143", "262144", "524287", "524288", "1048575", "1048576",
    "2097151", "2097152", "4194303", "4194304", "8388607", "8388608",
    "16777215", "16777216",
    "4294967295", "4294967296",
    "18446744073709551615", "18446744073709551616",
    "0x0", "0x00", "0xff", "0xFF",
    "00", "000",
    "zero", "one", "two", "three", "four",
    "hrair",
    // Truth values:
    "yes", "true", "ja", "wahr",
    "no", "false", "nein", "unwahr", "falsch",
    "enable", "enabled", "disable", "disabled",
    // Hash names:
    "SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512",
    // Protocols:
    "http", "https", "ssh", "icyx", "icyxs", "tls",
    // Media Types:
    "application", "text", "image", "audio", "video",
    "text/plain", "text/html", "text/xml", "text/css",
    "application/xhtml+xml", "application/xml", "application/json",
    "application/octet-stream",
    "application/ogg", "audio/ogg", "video/ogg",
    "audio/x-matroska", "video/x-matroska", "video/x-matroska-3d",
    "video/webm", "video/webm",
    "image/png", "image/jpeg",
    // Charsets:
    "charset", "utf-8", "UTF-8",
    // TLS:
    "auto", "auto_no_plain", "rfc2817", "rfc2818",
    // Storage:
    "key", "value", "name", "type",
    "description",
    // Access:
    "allow", "deny", "username", "password", "role",
    "acl", "rule", "access",
    "limit", "limits",
    // Signals:
    "bitrate", "samplerate", "quality",
    // Vorbis Comments:
    "TITLE", "VERSION", "ALBUM", "TRACKNUMBER", "ARTIST", "PERFORMER",
    "COPYRIGHT", "LICENSE", "ORGANIZATION", "DESCRIPTION", "GENRE",
    "DATE", "LOCATION", "CONTACT", "ISRC",
    // VCLT:
    "STREAMURL", "FILENAME", "FILEURL", "LENGTH", "HASH", "OFFSET",
    "SIGNALINFO", "AUDIOINFO",
    "codec", "rate", "bits", "channels", "x", "y", "z", "stereomode",
    // UUIDs (RFC 4122):
    "00000000-0000-0000-0000-000000000000", // Nil UUID
    "6ba7b810-9dad-11d1-80b4-00c04fd430c8", // NameSpace_DNS
    "6ba7b811-9dad-11d1-80b4-00c04fd430c8", // NameSpace_URL
    "6ba7b812-9dad-11d1-80b4-00c04fd430c8", // NameSpace_OID
    "6ba7b814-9dad-11d1-80b4-00c04fd430c8", // NameSpace_X500
    // stats:
    "stats", "listeners", "server_name", "listenurl", "clients", "connections",
    // Universal Tags:
    "uuid", "oid", "uri", "tagname", "wikidata-identifier", "musicbrainz-identifier",
    "small-identifier", "sirtx-numerical-identifier", "chat-0-word-identifier",
    "8be115d2-dc2f-4a98-91e1-a6e3075cbc31", // uuid
    "d08dc905-bbf6-4183-b219-67723c3c8374", // oid
    "a8d1637d-af19-49e9-9ef8-6bc1fbcf6439", // uri
    "bfae7574-3dae-425d-89b1-9c087c140c23", // tagname
    "ce7aae1e-a210-4214-926a-0ebca56d77e3", // wikidata-identifier
    "95bd826b-bd3e-4b40-b16a-aa20c9f673e4", // musicbrainz-identifier
    "f87a38cb-fd13-4e15-866c-e49901adbec5", // small-identifier
    "039e0bb7-5dd3-40ee-a98c-596ff6cce405", // sirtx-numerical-identifier
    "2c7e15ed-aa2f-4e2f-9a1d-64df0c85875a", // chat-0-word-identifier
    // common paths and devices:
    "/", "/dev", "/tmp", "/dev/null", "/dev/tty", "/dev/console", // POSIX
    "/dev/stdin", "/dev/stdout", "/dev/stderr",
    "/dev/random", "/dev/urandom",
    // Others:
    "Icecast", "icecast", "hackme", "test", "admin", "Earth", "igloo",
    "localhost", "127.0.0.1", "::1", "::",
    "source", "relay", "stream", "mount", "mountpoint",
    "access.log", "error.log",
    "panic", "error", "warning", "info", "information", "debug",
    "none", "any", "all", "own",
    "default", "normal", "virtual",
    "strict", "legacy", "obsolete", "compat",
    "master", "slave", "client", "server", "peer",
    "event", "action", "trigger", "mode",
    "uri", "url", "urn",
    "filename", "hostname", "location",
    "port",
    "implementation", "version", "config", "configuration",
    "icon", "thumbnail",
    "EOF"
};

static pthread_once_t constant_strings_sort = PTHREAD_ONCE_INIT;

static inline void __ignore_error(igloo_error_t error)
{
    (void)error;
}

static int const_compare(const void *a, const void *b)
{
    return strcmp(*(char * const *)a, *(char * const *)b);
}

static void hash(const char *str, uint32_t *res, size_t *len)
{
    uint32_t ret = 1;
    size_t l = 0;

    for (; *str; str++) {
        ret = (ret * 1103515245) + 12345 + (uint32_t)(unsigned char)*str;
        l++;
    }

    *res = ret;
    *len = l;
}

static inline bool partof(const char *str, size_t len, igloo_sp_area_t *area)
{
    return (area->objects && area->start <= str && area->end >= str && area->minlen <= len && area->maxlen >= len) ? true : false;
}

static inline bool overlaping(igloo_sp_area_t *a, igloo_sp_area_t *b)
{
    return ((a->start <= b->end && a->end >= b->end) || (b->start <= a->end && b->end >= a->end)) ? true : false;
}

static void learn(const char *str, size_t len, igloo_sp_state_t *state, igloo_sp_area_t *area)
{
    size_t i;

    if (!area->objects) {
        area->start = str;
        area->end = str;
        area->minlen = len;
        area->maxlen = len;
    } else {
        if (area->start > str)
            area->start = str;
        if (area->end < str)
            area->end = str;
        if (area->minlen > len)
            area->minlen = len;
        if (area->maxlen < len)
            area->maxlen = len;
    }

    area->objects++;

    for (i = 0; i < (sizeof(state->areas)/sizeof(*state->areas)); i++) {
        if (state->areas[i] == area)
            continue;

        if (overlaping(state->areas[i], area)) {
            state->areas[i]->clean = false;
            area->clean = false;
        }
    }
}

static void forget(const char *str, igloo_sp_state_t *state, igloo_sp_area_t *area)
{
    if (area && area->objects && area->start <= str && area->end >= str) {
        area->objects--;
        if (!area->objects) {
            size_t i, j;

            memset(area, 0, sizeof(*area));

            for (i = 0; i < (sizeof(state->areas)/sizeof(*state->areas)); i++) {
                state->areas[i]->clean = true;
            }

            for (i = 0; i < (sizeof(state->areas)/sizeof(*state->areas)); i++) {
                if (state->areas[i] == area)
                    continue;
                for (j = i + 1; j < (sizeof(state->areas)/sizeof(*state->areas)); j++) {
                    if (state->areas[j] == area)
                        continue;
                    if (overlaping(state->areas[i], state->areas[j])) {
                        state->areas[i]->clean = false;
                        state->areas[j]->clean = false;
                    }
                }
            }
        }
    }
}

static igloo_error_t const_ref(const char *str, const char **ref, igloo_ro_t instance, igloo_sp_state_t *state, uint32_t hash, size_t len)
{
    void *ret;

    (void)instance, (void)hash;

    if (len < state->const_area.minlen || len > state->const_area.maxlen)
        return igloo_ERROR_NOSYS;

    ret = bsearch(&str, constant_strings, sizeof(constant_strings)/sizeof(*constant_strings), sizeof(*constant_strings), const_compare);

    if (ret) {
        *ref = *(const char**)ret;
        return igloo_ERROR_NONE;
    }

    return igloo_ERROR_NOSYS;
}

static igloo_error_t const_unref(const char **ref, igloo_ro_t instance, igloo_sp_state_t *state)
{
    size_t i;

    (void)instance;

    if (*ref < state->const_area.start || *ref > state->const_area.end)
        return igloo_ERROR_TYPEMM;

    if (state->const_area.clean)
        return igloo_ERROR_NONE;

    for (i = 0; i < (sizeof(constant_strings)/sizeof(*constant_strings)); i++)
        if (*ref == constant_strings[i])
            return igloo_ERROR_NONE;

    return igloo_ERROR_TYPEMM;
}

static igloo_error_t small_ref(const char *str, const char **ref, igloo_ro_t instance, igloo_sp_state_t *state, uint32_t hash, size_t len)
{
    size_t idx = hash % (sizeof(state->small_strings)/sizeof(*state->small_strings));
    igloo_sp_state_buffer_small_t *selected;

    (void)instance;

    if (len >= sizeof(state->small_strings[0].str))
        return igloo_ERROR_NOMEM;

    selected = &(state->small_strings[idx]);
    if (selected->refc) {
        if (strcmp(selected->str, str) == 0) {
            *ref = selected->str;
            selected->refc++;
            return igloo_ERROR_NONE;
        } else {
            return igloo_ERROR_NOMEM;
        }
    } else {
        strncpy(selected->str, str, sizeof(selected->str));
        *ref = selected->str;
        selected->refc = 1;
        learn(*ref, len, state, &(state->small_area));
        return igloo_ERROR_NONE;
    }
}

static igloo_error_t small_unref(const char **ref, igloo_ro_t instance, igloo_sp_state_t *state)
{
    igloo_sp_state_buffer_small_t *selected = (void*)(*ref) - offsetof(igloo_sp_state_buffer_small_t, str);

    (void)instance;

    if (selected >= &(state->small_strings[0]) && ((void*)selected) < (sizeof(state->small_strings) + (void*)&(state->small_strings))) {
        selected->refc--;
        if (!selected->refc)
            forget(*ref, state, &(state->small_area));
        return igloo_ERROR_NONE;
    }

    return igloo_ERROR_TYPEMM;
}

static igloo_error_t fallback_ref(const char *str, const char **ref, igloo_ro_t instance, igloo_sp_state_t *state, uint32_t hash, size_t len)
{
    (void)instance, (void)state, (void)hash, (void)len;

    *ref = strdup(str);
    if (!*ref)
        return igloo_ERROR_NOMEM;

    learn(*ref, len, state, &(state->fallback_area));

    return igloo_ERROR_NONE;
}

static igloo_error_t fallback_unref(const char **ref, igloo_ro_t instance, igloo_sp_state_t *state)
{
    (void)instance, (void)state;

    free(*(void **)ref);
    forget(*ref, state, &(state->fallback_area));

    return igloo_ERROR_NONE;
}

static const struct {
    igloo_error_t (*ref)(const char *str, const char **ref, igloo_ro_t instance, igloo_sp_state_t *state, uint32_t hash, size_t len);
    igloo_error_t (*unref)(const char **ref, igloo_ro_t instance, igloo_sp_state_t *state);
} impls[] = {
    {const_ref, const_unref},
    {small_ref, small_unref},
    {fallback_ref, fallback_unref}
};

static igloo_error_t igloo_sp_ref__impl(const char *str, const char **ref, igloo_ro_t instance)
{
    igloo_sp_state_t *state = igloo_instance_get_stringpool_state(instance);
    igloo_error_t error = igloo_ERROR_NOSYS;
    uint32_t h;
    size_t l;
    size_t i;

    if (!str || !ref || !state)
        return igloo_ERROR_FAULT;

    hash(str, &h, &l);

    if (l == 0) {
        *ref = empty_string;
        return igloo_ERROR_NONE;
    }

    igloo_rwlock_wlock(&(state->lock));
    for (i = 0; i < (sizeof(impls)/sizeof(*impls)); i++) {
        error = impls[i].ref(str, ref, instance, state, h, l);
        if (error == igloo_ERROR_NONE) {
            igloo_rwlock_unlock(&(state->lock));
            return igloo_ERROR_NONE;
        }
    }
    igloo_rwlock_unlock(&(state->lock));

    return error;
}

igloo_error_t igloo_sp_ref(const char *str, const char **ref, igloo_ro_t instance)
{
    return igloo_sp_ref__impl(str, ref, instance);
}

igloo_error_t igloo_sp_replace(const char *str, const char **ref, igloo_ro_t instance)
{
    igloo_error_t error;
    const char *ret;

    if (!ref)
        return igloo_ERROR_FAULT;

    if (str == NULL) {
        ret = NULL;
    } else {
        error = igloo_sp_ref__impl(str, &ret, instance);
        if (error != igloo_ERROR_NONE)
            return error;
    }

    if (*ref) {
        error = igloo_sp_unref(ref, instance);
        if (error != igloo_ERROR_NONE) {
            __ignore_error(igloo_sp_unref(&ret, instance));
            return error;
        }
    }

    *ref = ret;

    return igloo_ERROR_NONE;
}

igloo_error_t igloo_sp_unref(const char **ref, igloo_ro_t instance)
{
    igloo_sp_state_t *state = igloo_instance_get_stringpool_state(instance);
    igloo_error_t error = igloo_ERROR_NOSYS;
    size_t i;

    if (!ref || !state)
        return igloo_ERROR_FAULT;

    if (!*ref || *ref == empty_string)
        return igloo_ERROR_NONE;

    igloo_rwlock_wlock(&(state->lock));
    for (i = 0; i < (sizeof(impls)/sizeof(*impls)); i++) {
        error = impls[i].unref(ref, instance, state);
        if (error == igloo_ERROR_NONE) {
            *ref = NULL;
            igloo_rwlock_unlock(&(state->lock));
            return igloo_ERROR_NONE;
        }
    }
    igloo_rwlock_unlock(&(state->lock));

    return error;
}

static void constant_strings_sort_func(void)
{
    qsort(constant_strings, sizeof(constant_strings)/sizeof(*constant_strings), sizeof(*constant_strings), const_compare);
}

igloo_error_t igloo_instance_sp_init(igloo_sp_state_t *state)
{
    igloo_sp_area_t *areas[] = {&(state->const_area), &(state->small_area), &(state->fallback_area)};
    size_t i;

    if (sizeof(state->areas) != sizeof(areas))
        abort();

    // init lock:
    igloo_rwlock_init(&(state->lock));

    // init areas:
    memcpy(state->areas, areas, sizeof(state->areas));
    for (i = 0; i < (sizeof(state->areas)/sizeof(*state->areas)); i++) {
        state->areas[i]->clean = true;
    }

    // init constant_strings:
    pthread_once(&constant_strings_sort, constant_strings_sort_func);

    for (i = 0; i < (sizeof(constant_strings)/sizeof(*constant_strings)); i++) {
        const char *str = constant_strings[i];
        learn(str, strlen(str), state, &(state->const_area));
    }

    return igloo_ERROR_NONE;
}

void igloo_instance_sp_destroy(igloo_sp_state_t *state)
{
    igloo_rwlock_destroy(&(state->lock));
}

static void append_printf(char **buf, size_t *len, const char *format, ...)
{
    int res;
    va_list ap;

    va_start(ap, format);
    res = vsnprintf(*buf, *len - 1, format, ap);
    va_end(ap);

    if (res < 1 || (size_t)res > *len)
        return;

    *buf += res;
    *len -= res;
}

static void dump_area(char **buf, size_t *len, const char *name, igloo_sp_area_t *area)
{
    if (area->objects) {
        append_printf(buf, len, "%s={", name);
        append_printf(buf, len, "start=%p, end=%p, ", area->start, area->end);
        append_printf(buf, len, "minlen=%zu, maxlen=%zu, ", area->minlen, area->maxlen);
        append_printf(buf, len, "objects=%zu, ", area->objects);
        append_printf(buf, len, "clean=%s", area->clean ? "true" : "false");
        append_printf(buf, len, "}");
    } else {
        append_printf(buf, len, "%s=<unused>", name);
    }
}

igloo_error_t   igloo_instance_sp_stringify(igloo_sp_state_t *state, char *buf, size_t len)
{
    if (len < 1)
        return igloo_ERROR_FAULT;
    *buf = 0;

    append_printf(&buf, &len, "{");
    dump_area(&buf, &len, "const_area", &(state->const_area));
    append_printf(&buf, &len, ", ");
    dump_area(&buf, &len, "small_area", &(state->small_area));
    append_printf(&buf, &len, ", ");
    dump_area(&buf, &len, "fallback_area", &(state->fallback_area));
    append_printf(&buf, &len, "}");

    return igloo_ERROR_NONE;
}
