// SPDX-License-Identifier: Apache-2.0

#include "array.h"
#include "util.h"

#include <string.h>

struct VarlinkArray {
        unsigned long refcount;
        VarlinkValueKind element_kind;

        VarlinkValue *elements;
        unsigned long n_elements;
        unsigned long n_allocated_elements;

        bool writable;
};

static long array_append(VarlinkArray *array, VarlinkValue **valuep) {
        if (array->n_elements == array->n_allocated_elements) {
                unsigned long prev_n_allocated_elements = array->n_allocated_elements;
                array->n_allocated_elements = MAX(array->n_allocated_elements * 2, 16);
                array->elements = realloc(array->elements, array->n_allocated_elements * sizeof(VarlinkValue));
                if (!array->elements)
                        return -VARLINK_ERROR_PANIC;
                memset(array->elements + prev_n_allocated_elements,
                       0,
                       (array->n_allocated_elements - prev_n_allocated_elements) * sizeof(VarlinkValue));
        }

        *valuep = &array->elements[array->n_elements];
        array->n_elements += 1;

        return 0;
}

VarlinkValueKind varlink_array_get_element_kind(VarlinkArray *array) {
        return array->element_kind;
}

_public_ long varlink_array_new(VarlinkArray **arrayp) {
        _cleanup_(varlink_array_unrefp) VarlinkArray *array = NULL;

        array = calloc(1, sizeof(VarlinkArray));
        if (!array)
                return -VARLINK_ERROR_PANIC;

        array->refcount = 1;
        array->writable = true;

        *arrayp = array;
        array = NULL;

        return 0;
}

long varlink_array_new_from_scanner(VarlinkArray **arrayp, Scanner *scanner, locale_t locale, unsigned long depth_cnt) {
        _cleanup_(varlink_array_unrefp) VarlinkArray *array = NULL;
        bool first = true;
        long r;

        r = varlink_array_new(&array);
        if (r < 0)
                return r;

        if (scanner_expect_operator(scanner, "[") < 0)
                return -VARLINK_ERROR_INVALID_JSON;

        while (scanner_peek(scanner) != ']') {
                VarlinkValue *value;

                if (!first) {
                        if (scanner_expect_operator(scanner, ",") < 0)
                                return -VARLINK_ERROR_INVALID_JSON;
                }

                r = array_append(array, &value);
                if (r < 0)
                        return r;

                if (!varlink_value_read_from_scanner(value, scanner, locale, depth_cnt))
                        return -VARLINK_ERROR_INVALID_JSON;

                /* Accept `null` value for any element kind */
                if (value->kind != VARLINK_VALUE_NULL) {
                        if (array->element_kind == VARLINK_VALUE_UNDEFINED)
                                array->element_kind = value->kind;
                        else if (array->element_kind != value->kind)
                                return -VARLINK_ERROR_INVALID_JSON;
                }

                first = false;
        }

        if (scanner_expect_operator(scanner, "]") < 0)
                return -VARLINK_ERROR_INVALID_JSON;

        *arrayp = array;
        array = NULL;

        return 0;
}

_public_ VarlinkArray *varlink_array_ref(VarlinkArray *array) {
        array->refcount += 1;
        return array;
}

_public_ VarlinkArray *varlink_array_unref(VarlinkArray *array) {
        array->refcount -= 1;

        if (array->refcount == 0) {
                for (unsigned long i = 0; i < array->n_elements; i += 1)
                        varlink_value_clear(&array->elements[i]);

                free(array->elements);
                free(array);
        }

        return NULL;
}

_public_ void varlink_array_unrefp(VarlinkArray **arrayp) {
        if (*arrayp)
                varlink_array_unref(*arrayp);
}

_public_ unsigned long varlink_array_get_n_elements(VarlinkArray *array) {
        return array->n_elements;
}

_public_ long varlink_array_get_bool(VarlinkArray *array, unsigned long index, bool *bp) {
        if (index >= array->n_elements)
                return -VARLINK_ERROR_INVALID_INDEX;

        if (array->elements[index].kind != VARLINK_VALUE_BOOL)
                return -VARLINK_ERROR_INVALID_TYPE;

        *bp = array->elements[index].b;

        return 0;
}

_public_ long varlink_array_get_int(VarlinkArray *array, unsigned long index, int64_t *ip) {
        if (index >= array->n_elements)
                return -VARLINK_ERROR_INVALID_INDEX;

        if (array->elements[index].kind != VARLINK_VALUE_INT)
                return -VARLINK_ERROR_INVALID_TYPE;

        *ip = array->elements[index].i;

        return 0;
}

_public_ long varlink_array_get_float(VarlinkArray *array, unsigned long index, double *fp) {
        if (index >= array->n_elements)
                return -VARLINK_ERROR_INVALID_INDEX;

        if (array->elements[index].kind != VARLINK_VALUE_FLOAT)
                return -VARLINK_ERROR_INVALID_TYPE;

        *fp = array->elements[index].f;

        return 0;
}

_public_ long varlink_array_get_string(VarlinkArray *array, unsigned long index, const char **stringp) {
        if (index >= array->n_elements)
                return -VARLINK_ERROR_INVALID_INDEX;

        if (array->elements[index].kind != VARLINK_VALUE_STRING)
                return -VARLINK_ERROR_INVALID_TYPE;

        *stringp = array->elements[index].s;

        return 0;
}

_public_ long varlink_array_get_array(VarlinkArray *array, unsigned long index, VarlinkArray **elementp) {
        if (index >= array->n_elements)
                return -VARLINK_ERROR_INVALID_INDEX;

        if (array->elements[index].kind != VARLINK_VALUE_ARRAY)
                return -VARLINK_ERROR_INVALID_TYPE;

        *elementp = array->elements[index].array;

        return 0;
}

_public_ long varlink_array_get_object(VarlinkArray *array, unsigned long index, VarlinkObject **objectp) {
        if (index >= array->n_elements)
                return -VARLINK_ERROR_INVALID_INDEX;

        if (array->elements[index].kind != VARLINK_VALUE_OBJECT)
                return -VARLINK_ERROR_INVALID_TYPE;

        *objectp = array->elements[index].object;

        return 0;
}

long varlink_array_get_value(VarlinkArray *array, unsigned long index, VarlinkValue **valuep) {
        if (index >= array->n_elements)
                return -VARLINK_ERROR_INVALID_INDEX;

        *valuep = &array->elements[index];

        return 0;
}

_public_ long varlink_array_append_null(VarlinkArray *array) {
        VarlinkValue *v;
        long r;

        if (!array->writable)
                return -VARLINK_ERROR_READ_ONLY;

        r = array_append(array, &v);
        if (r < 0)
                return r;

        v->kind = VARLINK_VALUE_NULL;

        return 0;
}

_public_ long varlink_array_append_bool(VarlinkArray *array, bool b) {
        VarlinkValue *v;
        long r;

        if (!array->writable)
                return -VARLINK_ERROR_READ_ONLY;

        if (array->element_kind == VARLINK_VALUE_UNDEFINED)
                array->element_kind = VARLINK_VALUE_BOOL;
        else if (array->element_kind != VARLINK_VALUE_BOOL)
                return -VARLINK_ERROR_INVALID_TYPE;

        r = array_append(array, &v);
        if (r < 0)
                return r;

        v->kind = VARLINK_VALUE_BOOL;
        v->b = b;

        return 0;
}

_public_ long varlink_array_append_int(VarlinkArray *array, int64_t i) {
        VarlinkValue *v;
        long r;

        if (!array->writable)
                return -VARLINK_ERROR_READ_ONLY;

        if (array->element_kind == VARLINK_VALUE_UNDEFINED)
                array->element_kind = VARLINK_VALUE_INT;
        else if (array->element_kind != VARLINK_VALUE_INT)
                return -VARLINK_ERROR_INVALID_TYPE;

        r = array_append(array, &v);
        if (r < 0)
                return r;

        v->kind = VARLINK_VALUE_INT;
        v->i = i;

        return 0;
}

_public_ long varlink_array_append_float(VarlinkArray *array, double f) {
        VarlinkValue *v;
        long r;

        if (!array->writable)
                return -VARLINK_ERROR_READ_ONLY;

        if (array->element_kind == VARLINK_VALUE_UNDEFINED)
                array->element_kind = VARLINK_VALUE_FLOAT;
        else if (array->element_kind != VARLINK_VALUE_FLOAT)
                return -VARLINK_ERROR_INVALID_TYPE;

        r = array_append(array, &v);
        if (r < 0)
                return r;

        v->kind = VARLINK_VALUE_FLOAT;
        v->f = f;

        return 0;
}

_public_ long varlink_array_append_string(VarlinkArray *array, const char *string) {
        VarlinkValue *v;
        long r;

        if (!array->writable)
                return -VARLINK_ERROR_READ_ONLY;

        if (array->element_kind == VARLINK_VALUE_UNDEFINED)
                array->element_kind = VARLINK_VALUE_STRING;
        else if (array->element_kind != VARLINK_VALUE_STRING)
                return -VARLINK_ERROR_INVALID_TYPE;

        r = array_append(array, &v);
        if (r < 0)
                return r;

        v->kind = VARLINK_VALUE_STRING;
        v->s = strdup(string);
        if (!v->s)
                return -VARLINK_ERROR_PANIC;

        return 0;
}

_public_ long varlink_array_append_array(VarlinkArray *array, VarlinkArray *element) {
        VarlinkValue *v;
        long r;

        if (!array->writable)
                return -VARLINK_ERROR_READ_ONLY;

        if (array->element_kind == VARLINK_VALUE_UNDEFINED)
                array->element_kind = VARLINK_VALUE_ARRAY;
        else if (array->element_kind != VARLINK_VALUE_ARRAY)
                return -VARLINK_ERROR_INVALID_TYPE;

        r = array_append(array, &v);
        if (r < 0)
                return r;

        v->kind = VARLINK_VALUE_ARRAY;
        v->array = varlink_array_ref(element);

        return 0;
}

_public_ long varlink_array_append_object(VarlinkArray *array, VarlinkObject *object) {
        VarlinkValue *v;
        long r;

        if (!array->writable)
                return -VARLINK_ERROR_READ_ONLY;

        if (array->element_kind == VARLINK_VALUE_UNDEFINED)
                array->element_kind = VARLINK_VALUE_OBJECT;
        else if (array->element_kind != VARLINK_VALUE_OBJECT)
                return -VARLINK_ERROR_INVALID_TYPE;

        r = array_append(array, &v);
        if (r < 0)
                return r;

        v->kind = VARLINK_VALUE_OBJECT;
        v->object = varlink_object_ref(object);

        return 0;
}

long varlink_array_write_json(VarlinkArray *array,
                              FILE *stream,
                              long indent,
                              const char *key_pre, const char *key_post,
                              const char *value_pre, const char *value_post) {
        long r;

        if (array->n_elements == 0) {
                if (fprintf(stream, "[]") < 0)
                        return -VARLINK_ERROR_PANIC;

                return 0;
        }

        if (fprintf(stream, "[") < 0)
                return -VARLINK_ERROR_PANIC;

        if (indent >= 0)
                if (fprintf(stream, "\n") < 0)
                        return -VARLINK_ERROR_PANIC;

        for (unsigned long i = 0; i < array->n_elements; i += 1) {
                if (i > 0) {
                        if (fprintf(stream, ",") < 0)
                                return -VARLINK_ERROR_PANIC;

                        if (indent >= 0)
                                if (fprintf(stream, "\n") < 0)
                                        return -VARLINK_ERROR_PANIC;
                }

                if (indent >= 0)
                        if (fprintf(stream, "%*s", (int)(indent + 1) * 2, " ") < 0)
                                return -VARLINK_ERROR_PANIC;

                r = varlink_value_write_json(&array->elements[i], stream,
                                             indent >= 0 ? indent + 1 : -1,
                                             key_pre, key_post,
                                             value_pre, value_post);
                if (r < 0)
                        return r;
        }

        if (indent >= 0)
                if (fprintf(stream, "\n%*s", (int)indent * 2, " ") < 0)
                        return -VARLINK_ERROR_PANIC;

        if (fprintf(stream, "]") < 0)
                return -VARLINK_ERROR_PANIC;

        return 0;
}
