/*
  C bindings for uni-json library

  Copyright (C) 2025 Rainer Weikusat, rweikusat@talktalk.net
  Adapted for VDev example usage

  MIT-licensed.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include "uni_json_p_binding.h"
#include "uni_json_types.h"

/* Forward declarations for the structures used in the example */
struct uni_json_value {
    unsigned type;
    union {
        struct {
            struct uni_json_object_entry *first;
        } obj;
        struct {
            struct uni_json_array_entry *first;
        } ary;
        struct {
            uint8_t *s;
            size_t len;
        } s;
        double num;
        int b;
    } u;
};

struct uni_json_object_entry {
    struct uni_json_object_entry *next;
    uint8_t *key;
    struct uni_json_value *value;
};

struct uni_json_array_entry {
    struct uni_json_array_entry *next;
    struct uni_json_value *value;
};

/* Object functions */
static void *make_object(void)
{
    struct uni_json_value *obj = malloc(sizeof(struct uni_json_value));
    if (obj) {
        obj->type = UJ_T_OBJ;
        obj->u.obj.first = NULL;
    }
    return obj;
}

static void free_object(void *obj)
{
    struct uni_json_value *v = obj;
    if (!v) return;
    
    if (v->type == UJ_T_OBJ) {
        struct uni_json_object_entry *entry = v->u.obj.first;
        while (entry) {
            struct uni_json_object_entry *next = entry->next;
            free(entry->key);
            free_object(entry->value);
            free(entry);
            entry = next;
        }
    }
    free(v);
}

static int add_2_object(void *key, void *value, void *obj)
{
    struct uni_json_value *v = obj;
    struct uni_json_object_entry *entry;
    
    if (!v || v->type != UJ_T_OBJ) {
        return -1;
    }
    
    entry = malloc(sizeof(struct uni_json_object_entry));
    if (!entry) {
        return -1;
    }
    
    entry->key = key;
    entry->value = value;
    entry->next = v->u.obj.first;
    v->u.obj.first = entry;
    
    return 0;
}

/* Array functions */
static void *make_array(void)
{
    struct uni_json_value *arr = malloc(sizeof(struct uni_json_value));
    if (arr) {
        arr->type = UJ_T_ARY;
        arr->u.ary.first = NULL;
    }
    return arr;
}

static void free_array(void *ary)
{
    struct uni_json_value *v = ary;
    if (!v) return;
    
    if (v->type == UJ_T_ARY) {
        struct uni_json_array_entry *entry = v->u.ary.first;
        while (entry) {
            struct uni_json_array_entry *next = entry->next;
            free_object(entry->value);
            free(entry);
            entry = next;
        }
    }
    free(v);
}

static int add_2_array(void *value, void *ary)
{
    struct uni_json_value *v = ary;
    struct uni_json_array_entry *entry;
    
    if (!v || v->type != UJ_T_ARY) return -1;
    
    entry = malloc(sizeof(struct uni_json_array_entry));
    if (!entry) return -1;
    
    entry->value = value;
    entry->next = v->u.ary.first;
    v->u.ary.first = entry;
    
    return 0;
}

/* String functions */
static void *make_work_string(void)
{
    struct uni_json_value *ptr = malloc(sizeof(struct uni_json_value));
    if (ptr) {
        /* Initialize the structure */
        memset(ptr, 0, sizeof(struct uni_json_value));
    }
    return ptr;
}

static void free_work_string(void *str)
{
    free(str);
}

static int add_2_string(uint8_t *data, size_t len, void *str)
{
    struct uni_json_value *v = str;
    if (!v) return -1;
    
    v->u.s.s = malloc(len + 1);
    if (!v->u.s.s) {
        return -1;
    }
    
    memcpy(v->u.s.s, data, len);
    v->u.s.s[len] = '\0';
    v->u.s.len = len;
    
    return 0;
}

static void *finalize_string(void *str, uint8_t *data, size_t len)
{
    struct uni_json_value *v;
    
    /* If str is NULL, we need to create a new string object */
    if (!str) {
        v = malloc(sizeof(struct uni_json_value));
        if (!v) {
            return NULL;
        }
        memset(v, 0, sizeof(struct uni_json_value));
    } else {
        v = str;
    }
    
    /* Allocate and copy the string data */
    v->u.s.s = malloc(len + 1);
    if (!v->u.s.s) {
        if (!str) free(v);
        return NULL;
    }
    
    memcpy(v->u.s.s, data, len);
    v->u.s.s[len] = '\0';
    v->u.s.len = len;
    v->type = UJ_T_STR;
    
    return v;
}

static void free_string(void *str)
{
    struct uni_json_value *v = str;
    if (!v) return;
    
    if (v->type == UJ_T_STR) {
        free(v->u.s.s);
    }
    free(v);
}

/* Simple type functions */
static void *make_null(void)
{
    struct uni_json_value *v = malloc(sizeof(struct uni_json_value));
    if (v) {
        v->type = UJ_T_NULL;
    }
    return v;
}

static void free_null(void *null)
{
    free(null);
}

static void *make_bool(int true_false)
{
    struct uni_json_value *v = malloc(sizeof(struct uni_json_value));
    if (v) {
        v->type = UJ_T_BOOL;
        v->u.b = true_false;
    }
    return v;
}

static void free_bool(void *boolean)
{
    free(boolean);
}

static void *make_number(uint8_t *data, size_t len, unsigned flags)
{
    (void)flags; /* Suppress unused parameter warning */
    struct uni_json_value *v = malloc(sizeof(struct uni_json_value));
    if (!v) return NULL;
    
    v->type = UJ_T_NUM;
    
    /* Convert string to double */
    char *num_str = malloc(len + 1);
    if (!num_str) {
        free(v);
        return NULL;
    }
    
    memcpy(num_str, data, len);
    num_str[len] = '\0';
    
    v->u.num = atof(num_str);
    free(num_str);
    
    return v;
}

static void free_number(void *num)
{
    free(num);
}

/* Error handler */
static void on_error(unsigned code, size_t pos, void *p)
{
    (void)p; /* Suppress unused parameter warning */
    fprintf(stderr, "JSON parse error %u at position %zu\n", code, pos);
}

/* C bindings structure */
struct uni_json_p_binding uni_json_c_bindings = {
    .on_error = on_error,
    
    .make_object = make_object,
    .free_object = free_object,
    .add_2_object = add_2_object,
    
    .make_array = make_array,
    .free_array = free_array,
    .add_2_array = add_2_array,
    
    .make_work_string = make_work_string,
    .free_work_string = free_work_string,
    .add_2_string = add_2_string,
    .finalize_string = finalize_string,
    .free_string = free_string,
    
    .make_null = make_null,
    .free_null = free_null,
    
    .make_bool = make_bool,
    .free_bool = free_bool,
    
    .make_number = make_number,
    .free_number = free_number
};
