// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

// values.c -- DVX BASIC value system implementation
//
// Tagged union values with reference-counted strings. The string
// heap uses simple refcounting: assignment increments, scope exit
// decrements, zero frees. No garbage collector needed.

#include "values.h"
#include "../compiler/opcodes.h"

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

#define BAS_STRING_IMMORTAL_REFCOUNT 999999  // sentinel refCount; empty string is never freed (see basStringUnref)
#define BAS_NUM_FORMAT_BUF_LEN       64      // scratch for the widest %g rendering of a double
#define BAS_SINGLE_SIG_DIGITS        7       // significant digits shown for SINGLE (VB3)
#define BAS_DOUBLE_SIG_DIGITS        15      // significant digits shown for DOUBLE (VB3)
#define BAS_RADIX_HEX                16
#define BAS_RADIX_OCT                8

// ============================================================
// String system
// ============================================================

// Singleton empty string -- never freed, always available.
// Extra byte for the null terminator via the struct hack.
static struct {
    BasStringT hdr;
    char       nul;
} sEmptyStringStorage = { { .refCount = BAS_STRING_IMMORTAL_REFCOUNT, .len = 0, .cap = 1 }, '\0' };
BasStringT *basEmptyString = &sEmptyStringStorage.hdr;


// Function prototypes (alphabetical)
void basArrayFree(BasArrayT *arr);
int32_t basArrayIndex(BasArrayT *arr, int32_t *indices, int32_t ndims);
BasArrayT *basArrayNew(int32_t dims, int32_t *lbounds, int32_t *ubounds, uint8_t elementType);
BasArrayT *basArrayRef(BasArrayT *arr);
void basArrayUnref(BasArrayT *arr);
BasStringT *basStringAlloc(int32_t cap);
int32_t basStringCompare(const BasStringT *a, const BasStringT *b);
int32_t basStringCompareCI(const BasStringT *a, const BasStringT *b);
BasStringT *basStringConcat(const BasStringT *a, const BasStringT *b);
BasStringT *basStringNew(const char *text, int32_t len);
BasStringT *basStringRef(BasStringT *s);
BasStringT *basStringSub(const BasStringT *s, int32_t start, int32_t len);
void basStringUnref(BasStringT *s);
BasUdtT *basUdtClone(const BasUdtT *udt);
void basUdtFree(BasUdtT *udt);
BasUdtT *basUdtNew(int32_t typeId, int32_t fieldCount);
BasUdtT *basUdtRef(BasUdtT *udt);
void basUdtUnref(BasUdtT *udt);
BasValueT basValBool(bool v);
int32_t basValCompare(BasValueT a, BasValueT b);
int32_t basValCompareCI(BasValueT a, BasValueT b);
static int32_t basValCompareImpl(BasValueT a, BasValueT b, bool caseInsensitive);
BasValueT basValCopy(BasValueT v);
BasValueT basValDouble(double v);
BasStringT *basValFormatString(BasValueT v);
BasValueT basValInteger(int16_t v);
bool basValIsTruthy(BasValueT v);
BasValueT basValLong(int32_t v);
BasValueT basValObject(void *obj);
double basParseNumber(const char *s);
void basValRelease(BasValueT *v);
bool basValRoundToInt32(BasValueT v, int32_t lo, int32_t hi, int32_t *out);
BasValueT basValSingle(float v);
BasValueT basValString(BasStringT *s);
BasValueT basValStringFromC(const char *text);
BasValueT basValToBool(BasValueT v);
BasValueT basValToDouble(BasValueT v);
int32_t basValToInt32(BasValueT v);
double basValToNumber(BasValueT v);
BasValueT basValToString(BasValueT v);
static int32_t formatDouble(double n, int32_t sigDigits, char *buf, int32_t bufSize);

// ============================================================
// Array system
// ============================================================
void basArrayFree(BasArrayT *arr) {
    if (!arr) {
        return;
    }

    if (arr->elements) {
        for (int32_t i = 0; i < arr->totalElements; i++) {
            basValRelease(&arr->elements[i]);
        }

        free(arr->elements);
    }

    free(arr);
}


int32_t basArrayIndex(BasArrayT *arr, int32_t *indices, int32_t ndims) {
    if (!arr || ndims != arr->dims) {
        return -1;
    }

    int32_t flatIdx = 0;
    int32_t multiplier = 1;

    // Row-major order: last dimension varies fastest
    for (int32_t d = ndims - 1; d >= 0; d--) {
        int32_t idx = indices[d] - arr->lbound[d];
        int32_t dimSize = arr->ubound[d] - arr->lbound[d] + 1;

        if (idx < 0 || idx >= dimSize) {
            return -1;
        }

        flatIdx += idx * multiplier;
        multiplier *= dimSize;
    }

    return flatIdx;
}


BasArrayT *basArrayNew(int32_t dims, int32_t *lbounds, int32_t *ubounds, uint8_t elementType) {
    if (dims < 1 || dims > BAS_ARRAY_MAX_DIMS) {
        return NULL;
    }

    BasArrayT *arr = (BasArrayT *)calloc(1, sizeof(BasArrayT));

    if (!arr) {
        return NULL;
    }

    arr->refCount    = 1;
    arr->elementType = elementType;
    arr->dims        = dims;

    int64_t total = 1;

    for (int32_t d = 0; d < dims; d++) {
        arr->lbound[d] = lbounds[d];
        arr->ubound[d] = ubounds[d];
        int64_t dimSize = (int64_t)ubounds[d] - (int64_t)lbounds[d] + 1;

        if (dimSize < 1) {
            free(arr);
            return NULL;
        }

        total *= dimSize;

        if (total > INT32_MAX) {
            free(arr);
            return NULL;
        }
    }

    arr->totalElements = (int32_t)total;
    arr->elements = (BasValueT *)calloc(arr->totalElements, sizeof(BasValueT));

    if (!arr->elements) {
        free(arr);
        return NULL;
    }

    // Initialize all elements to the default for the element type.  calloc
    // already zeroed everything and BAS_TYPE_INTEGER == 0, so integer arrays
    // (the common case) need no init pass at all.  Other types need their type
    // tag set; STRING elements additionally need a real (empty) string --
    // calloc leaves strVal NULL and the VM's string ops dereference it
    // unguarded, so an uninitialized string element would crash.
    if (elementType != BAS_TYPE_INTEGER) {
        for (int32_t i = 0; i < arr->totalElements; i++) {
            arr->elements[i].type = elementType;

            if (elementType == BAS_TYPE_STRING) {
                arr->elements[i].strVal = basStringRef(basEmptyString);
            }
        }
    }

    return arr;
}


BasArrayT *basArrayRef(BasArrayT *arr) {
    if (arr) {
        arr->refCount++;
    }

    return arr;
}


void basArrayUnref(BasArrayT *arr) {
    if (!arr) {
        return;
    }

    arr->refCount--;

    if (arr->refCount <= 0) {
        basArrayFree(arr);
    }
}


BasStringT *basStringAlloc(int32_t cap) {
    if (cap < 1) {
        cap = 1;
    }

    BasStringT *s = (BasStringT *)malloc(sizeof(BasStringT) + cap);

    if (!s) {
        return basStringRef(basEmptyString);
    }

    s->refCount = 1;
    s->len      = 0;
    s->cap      = cap;
    s->data[0]  = '\0';
    return s;
}


int32_t basStringCompare(const BasStringT *a, const BasStringT *b) {
    int32_t minLen = a->len < b->len ? a->len : b->len;
    int32_t cmp    = memcmp(a->data, b->data, minLen);

    if (cmp != 0) {
        return cmp;
    }

    if (a->len < b->len) {
        return -1;
    }

    if (a->len > b->len) {
        return 1;
    }

    return 0;
}


int32_t basStringCompareCI(const BasStringT *a, const BasStringT *b) {
    int32_t minLen = a->len < b->len ? a->len : b->len;

    for (int32_t i = 0; i < minLen; i++) {
        int32_t ca = toupper((unsigned char)a->data[i]);
        int32_t cb = toupper((unsigned char)b->data[i]);

        if (ca != cb) {
            return ca - cb;
        }
    }

    if (a->len < b->len) {
        return -1;
    }

    if (a->len > b->len) {
        return 1;
    }

    return 0;
}


BasStringT *basStringConcat(const BasStringT *a, const BasStringT *b) {
    int64_t total = (int64_t)a->len + (int64_t)b->len;

    if (total > BAS_STRING_MAX_LEN) {
        total = BAS_STRING_MAX_LEN;
    }

    int32_t newLen = (int32_t)total;
    int32_t copyA  = a->len < newLen ? a->len : newLen;
    int32_t copyB  = newLen - copyA;
    BasStringT *s  = basStringAlloc(newLen + 1);

    if (s->cap >= newLen + 1) {
        memcpy(s->data, a->data, copyA);
        memcpy(s->data + copyA, b->data, copyB);
        s->data[newLen] = '\0';
        s->len = newLen;
    }

    return s;
}


BasStringT *basStringNew(const char *text, int32_t len) {
    if (!text || len <= 0) {
        return basStringRef(basEmptyString);
    }

    BasStringT *s = basStringAlloc(len + 1);
    if (s->cap >= len + 1) {
        memcpy(s->data, text, len);
        s->data[len] = '\0';
        s->len = len;
    }
    return s;
}


BasStringT *basStringRef(BasStringT *s) {
    if (s) {
        s->refCount++;
    }

    return s;
}


BasStringT *basStringSub(const BasStringT *s, int32_t start, int32_t len) {
    if (start < 0) {
        start = 0;
    }

    if (start >= s->len) {
        return basStringRef(basEmptyString);
    }

    if (len < 0 || len > s->len - start) {
        len = s->len - start;
    }

    return basStringNew(s->data + start, len);
}


void basStringUnref(BasStringT *s) {
    if (!s || s == basEmptyString) {
        return;
    }

    s->refCount--;

    if (s->refCount <= 0) {
        free(s);
    }
}


// ============================================================
// UDT system
// ============================================================
BasUdtT *basUdtClone(const BasUdtT *udt) {
    BasUdtT *copy = basUdtNew(udt->typeId, udt->fieldCount);

    if (!copy) {
        return NULL;
    }

    for (int32_t i = 0; i < udt->fieldCount; i++) {
        const BasValueT *f = &udt->fields[i];

        if (f->type == BAS_TYPE_UDT && f->udtVal) {
            BasUdtT *nested = basUdtClone(f->udtVal);

            if (!nested) {
                basUdtFree(copy);
                return NULL;
            }

            copy->fields[i].type   = BAS_TYPE_UDT;
            copy->fields[i].udtVal = nested;
        } else {
            copy->fields[i] = basValCopy(*f);
        }
    }

    return copy;
}


void basUdtFree(BasUdtT *udt) {
    if (!udt) {
        return;
    }

    if (udt->fields) {
        for (int32_t i = 0; i < udt->fieldCount; i++) {
            basValRelease(&udt->fields[i]);
        }

        free(udt->fields);
    }

    free(udt);
}


BasUdtT *basUdtNew(int32_t typeId, int32_t fieldCount) {
    BasUdtT *udt = (BasUdtT *)calloc(1, sizeof(BasUdtT));

    if (!udt) {
        return NULL;
    }

    udt->refCount   = 1;
    udt->typeId     = typeId;
    udt->fieldCount = fieldCount;
    udt->fields     = (BasValueT *)calloc(fieldCount, sizeof(BasValueT));

    if (!udt->fields) {
        free(udt);
        return NULL;
    }

    return udt;
}


BasUdtT *basUdtRef(BasUdtT *udt) {
    if (udt) {
        udt->refCount++;
    }

    return udt;
}


void basUdtUnref(BasUdtT *udt) {
    if (!udt) {
        return;
    }

    udt->refCount--;

    if (udt->refCount <= 0) {
        basUdtFree(udt);
    }
}


// ============================================================
// Value constructors / refcount helpers
// ============================================================
double basParseNumber(const char *s) {
    if (!s) {
        return 0.0;
    }

    while (*s == ' ' || *s == '\t') {
        s++;
    }

    // &H / &O prefixed integers (VAL("&HFF") = 255).
    if (s[0] == '&' && (s[1] == 'H' || s[1] == 'h' || s[1] == 'O' || s[1] == 'o')) {
        int32_t radix = (s[1] == 'H' || s[1] == 'h') ? BAS_RADIX_HEX : BAS_RADIX_OCT;
        return (double)(int32_t)strtoul(s + 2, NULL, radix);
    }

    // Decimal: hand-scan the accepted span so atof's extra syntax (hex
    // floats, inf, nan) is never reached, and 'D' exponents are accepted.
    char    buf[BAS_NUM_FORMAT_BUF_LEN];
    int32_t len = 0;
    const char *p = s;

    if (*p == '+' || *p == '-') {
        buf[len++] = *p++;
    }

    bool sawDigit = false;

    while (isdigit((unsigned char)*p) && len < BAS_NUM_FORMAT_BUF_LEN - 1) {
        buf[len++] = *p++;
        sawDigit   = true;
    }

    if (*p == '.' && len < BAS_NUM_FORMAT_BUF_LEN - 1) {
        buf[len++] = *p++;

        while (isdigit((unsigned char)*p) && len < BAS_NUM_FORMAT_BUF_LEN - 1) {
            buf[len++] = *p++;
            sawDigit   = true;
        }
    }

    if (!sawDigit) {
        return 0.0;
    }

    if ((*p == 'E' || *p == 'e' || *p == 'D' || *p == 'd') && len < BAS_NUM_FORMAT_BUF_LEN - 3) {
        const char *q = p + 1;
        char        expBuf[BAS_NUM_FORMAT_BUF_LEN];
        int32_t     expLen = 0;

        if (*q == '+' || *q == '-') {
            expBuf[expLen++] = *q++;
        }

        if (isdigit((unsigned char)*q)) {
            while (isdigit((unsigned char)*q) && expLen < BAS_NUM_FORMAT_BUF_LEN - 1) {
                expBuf[expLen++] = *q++;
            }

            if (len + 1 + expLen < BAS_NUM_FORMAT_BUF_LEN) {
                buf[len++] = 'e';
                memcpy(buf + len, expBuf, expLen);
                len += expLen;
            }
        }
    }

    buf[len] = '\0';
    return atof(buf);
}


BasValueT basValBool(bool v) {
    BasValueT val;
    val.type    = BAS_TYPE_BOOLEAN;
    val.boolVal = v ? -1 : 0;
    return val;
}


int32_t basValCompare(BasValueT a, BasValueT b) {
    return basValCompareImpl(a, b, false);
}


int32_t basValCompareCI(BasValueT a, BasValueT b) {
    return basValCompareImpl(a, b, true);
}


static int32_t basValCompareImpl(BasValueT a, BasValueT b, bool caseInsensitive) {
    // String comparison
    if (a.type == BAS_TYPE_STRING && b.type == BAS_TYPE_STRING) {
        BasStringT *sa = a.strVal ? a.strVal : basEmptyString;
        BasStringT *sb = b.strVal ? b.strVal : basEmptyString;
        return caseInsensitive ? basStringCompareCI(sa, sb) : basStringCompare(sa, sb);
    }

    // Numeric comparison
    double na = basValToNumber(a);
    double nb = basValToNumber(b);

    if (na < nb) {
        return -1;
    }

    if (na > nb) {
        return 1;
    }

    return 0;
}


BasValueT basValCopy(BasValueT v) {
    if (v.type == BAS_TYPE_STRING && v.strVal) {
        basStringRef(v.strVal);
    } else if (v.type == BAS_TYPE_ARRAY && v.arrVal) {
        basArrayRef(v.arrVal);
    } else if (v.type == BAS_TYPE_UDT && v.udtVal) {
        basUdtRef(v.udtVal);
    } else if (v.type == BAS_TYPE_ELEM_REF) {
        basArrayRef(v.elemRef.arr);
    }

    return v;
}


BasValueT basValDouble(double v) {
    BasValueT val;
    val.type   = BAS_TYPE_DOUBLE;
    val.dblVal = v;
    return val;
}


BasStringT *basValFormatString(BasValueT v) {
    char buf[BAS_NUM_FORMAT_BUF_LEN];

    switch (v.type) {
        case BAS_TYPE_INTEGER:
            snprintf(buf, sizeof(buf), "%d", (int)v.intVal);
            return basStringNew(buf, (int32_t)strlen(buf));

        case BAS_TYPE_LONG:
            snprintf(buf, sizeof(buf), "%ld", (long)v.longVal);
            return basStringNew(buf, (int32_t)strlen(buf));

        case BAS_TYPE_SINGLE:
            return basStringNew(buf, formatDouble((double)v.sngVal, BAS_SINGLE_SIG_DIGITS, buf, sizeof(buf)));

        case BAS_TYPE_DOUBLE:
            return basStringNew(buf, formatDouble(v.dblVal, BAS_DOUBLE_SIG_DIGITS, buf, sizeof(buf)));

        case BAS_TYPE_BOOLEAN:
            return basStringNew(v.boolVal ? "True" : "False", v.boolVal ? 4 : 5);

        case BAS_TYPE_STRING:
            return v.strVal ? basStringRef(v.strVal) : basStringRef(basEmptyString);

        default:
            return basStringRef(basEmptyString);
    }
}


BasValueT basValInteger(int16_t v) {
    BasValueT val;
    val.type   = BAS_TYPE_INTEGER;
    val.intVal = v;
    return val;
}


bool basValIsTruthy(BasValueT v) {
    switch (v.type) {
        case BAS_TYPE_INTEGER:
            return v.intVal != 0;

        case BAS_TYPE_LONG:
            return v.longVal != 0;

        case BAS_TYPE_SINGLE:
            return v.sngVal != 0.0f;

        case BAS_TYPE_DOUBLE:
            return v.dblVal != 0.0;

        case BAS_TYPE_BOOLEAN:
            return v.boolVal != 0;

        case BAS_TYPE_STRING:
            return v.strVal && v.strVal->len > 0;

        default:
            return false;
    }
}


BasValueT basValLong(int32_t v) {
    BasValueT val;
    val.type    = BAS_TYPE_LONG;
    val.longVal = v;
    return val;
}


BasValueT basValObject(void *obj) {
    BasValueT val;
    val.type   = BAS_TYPE_OBJECT;
    val.objVal = obj;
    return val;
}


void basValRelease(BasValueT *v) {
    if (v->type == BAS_TYPE_STRING) {
        basStringUnref(v->strVal);
        v->strVal = NULL;
    } else if (v->type == BAS_TYPE_ARRAY) {
        basArrayUnref(v->arrVal);
        v->arrVal = NULL;
    } else if (v->type == BAS_TYPE_UDT) {
        basUdtUnref(v->udtVal);
        v->udtVal = NULL;
    } else if (v->type == BAS_TYPE_ELEM_REF) {
        basArrayUnref(v->elemRef.arr);
        v->elemRef.arr = NULL;
    }
}


bool basValRoundToInt32(BasValueT v, int32_t lo, int32_t hi, int32_t *out) {
    double n = basValToNumber(v);

    if (isnan(n)) {
        return false;
    }

    // rint() rounds half to even under the default rounding mode, which is
    // exactly the VB CINT/CLNG banker's rule (2.5 -> 2, 3.5 -> 4).
    double r = rint(n);

    if (r < (double)lo || r > (double)hi) {
        return false;
    }

    *out = (int32_t)r;
    return true;
}


BasValueT basValSingle(float v) {
    BasValueT val;
    val.type   = BAS_TYPE_SINGLE;
    val.sngVal = v;
    return val;
}


BasValueT basValString(BasStringT *s) {
    BasValueT val;
    val.type   = BAS_TYPE_STRING;
    val.strVal = s ? basStringRef(s) : basStringRef(basEmptyString);
    return val;
}


BasValueT basValStringFromC(const char *text) {
    BasValueT val;
    val.type   = BAS_TYPE_STRING;
    val.strVal = basStringNew(text, text ? (int32_t)strlen(text) : 0);
    return val;
}


// ============================================================
// Type conversion
// ============================================================
BasValueT basValToBool(BasValueT v) {
    return basValBool(basValIsTruthy(v));
}


BasValueT basValToDouble(BasValueT v) {
    return basValDouble(basValToNumber(v));
}


int32_t basValToInt32(BasValueT v) {
    double n = basValToNumber(v);

    if (isnan(n)) {
        return 0;
    }

    if (n >= (double)INT32_MAX) {
        return INT32_MAX;
    }

    if (n <= (double)INT32_MIN) {
        return INT32_MIN;
    }

    return (int32_t)n;
}


double basValToNumber(BasValueT v) {
    switch (v.type) {
        case BAS_TYPE_INTEGER:
            return (double)v.intVal;

        case BAS_TYPE_LONG:
            return (double)v.longVal;

        case BAS_TYPE_SINGLE:
            return (double)v.sngVal;

        case BAS_TYPE_DOUBLE:
            return v.dblVal;

        case BAS_TYPE_BOOLEAN:
            return (double)v.boolVal;

        case BAS_TYPE_STRING:
            if (v.strVal && v.strVal->len > 0) {
                return basParseNumber(v.strVal->data);
            }

            return 0.0;

        default:
            return 0.0;
    }
}


BasValueT basValToString(BasValueT v) {
    if (v.type == BAS_TYPE_STRING) {
        // Normalize a NULL strVal to the empty string so callers can
        // dereference the result unconditionally (basValCopy would
        // otherwise propagate the NULL).
        if (!v.strVal) {
            return basValString(NULL);
        }

        return basValCopy(v);
    }

    BasStringT *s = basValFormatString(v);
    BasValueT result;
    result.type   = BAS_TYPE_STRING;
    result.strVal = s;
    return result;
}


// Renders n with sigDigits significant digits the way VB PRINT/STR$ do:
// fixed notation while it fits, otherwise E notation with an upper-case
// exponent marker.  Returns the length written.
static int32_t formatDouble(double n, int32_t sigDigits, char *buf, int32_t bufSize) {
    int32_t len = snprintf(buf, bufSize, "%.*g", (int)sigDigits, n);

    if (len >= bufSize) {
        len = bufSize - 1;
    }

    for (int32_t i = 0; i < len; i++) {
        if (buf[i] == 'e') {
            buf[i] = 'E';
        }
    }

    return len;
}
