// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 * Copyright (C) 2021-2024 Cyril Hrubis <metan@ucw.cz>
 */

#include <string.h>
#include <stdarg.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#include "ujson_utf.h"
#include "ujson_writer.h"

static inline int get_depth_bit(ujson_writer *self, char *mask)
{
	int depth = self->depth - 1;

	if (depth < 0)
		return -1;

	return !!(mask[depth/8] & (1<<(depth%8)));
}

static inline void set_depth_bit(ujson_writer *self, int val)
{
	if (val)
		self->depth_type[self->depth/8] |= (1<<(self->depth%8));
	else
		self->depth_type[self->depth/8] &= ~(1<<(self->depth%8));

	self->depth_first[self->depth/8] |= (1<<(self->depth%8));

	self->depth++;
}

static inline void clear_depth_bit(ujson_writer *self)
{
	self->depth--;
}

static inline int in_arr(ujson_writer *self)
{
	return !get_depth_bit(self, self->depth_type);
}

static inline int in_obj(ujson_writer *self)
{
	return get_depth_bit(self, self->depth_type);
}

static inline void clear_depth_first(ujson_writer *self)
{
	int depth = self->depth - 1;

	self->depth_first[depth/8] &= ~(1<<(depth%8));
}

static inline int is_first(ujson_writer *self)
{
	int ret = get_depth_bit(self, self->depth_first);

	if (ret == 1)
		clear_depth_first(self);

	return ret;
}

static inline void err(ujson_writer *buf, const char *fmt, ...)
{
	va_list va;

	va_start(va, fmt);
	vsnprintf(buf->err, UJSON_ERR_MAX, fmt, va);
	va_end(va);
}

static inline int is_err(ujson_writer *buf)
{
	return buf->err[0];
}

static inline int out(ujson_writer *self, const char *buf, size_t len)
{
	return self->out(self, buf, len);
}

static inline int out_str(ujson_writer *self, const char *str)
{
	return out(self, str, strlen(str));
}

static inline int out_ch(ujson_writer *self, char ch)
{
	return out(self, &ch, 1);
}

#define ESC_FLUSH(esc_char) do {\
	out(self, val, i); \
	val += i + 1; \
	i = 0; \
	out_str(self, esc_char); \
} while (0)

static inline int out_esc_str(ujson_writer *self, const char *val)
{
	if (out_ch(self, '"'))
		return 1;

	size_t i = 0;
	int8_t next_chsz;

	do {
		next_chsz = ujson_utf8_next_chsz(val, i);

		if (next_chsz == 1) {
			switch (val[i]) {
			case '\"':
				ESC_FLUSH("\\\"");
			break;
			case '\\':
				ESC_FLUSH("\\\\");
			break;
			case '/':
				ESC_FLUSH("\\/");
			break;
			case '\b':
				ESC_FLUSH("\\b");
			break;
			case '\f':
				ESC_FLUSH("\\f");
			break;
			case '\n':
				ESC_FLUSH("\\n");
			break;
			case '\r':
				ESC_FLUSH("\\r");
			break;
			case '\t':
				ESC_FLUSH("\\t");
			break;
			default:
				i += next_chsz;
			}
		} else {
			i += next_chsz;
		}
	} while (next_chsz);

	if (i) {
		if (out(self, val, i))
			return 1;
	}

	if (out_ch(self, '"'))
		return 1;

	return 0;
}

static int do_padd(ujson_writer *self)
{
	unsigned int i;

	for (i = 0; i < self->depth; i++) {
		if (out_ch(self, ' '))
			return 1;
	}

	return 0;
}

static int newline(ujson_writer *self)
{
	if (out_ch(self, '\n'))
		return 0;

	if (do_padd(self))
		return 1;

	return 0;
}

static int add_common(ujson_writer *self, const char *id)
{
	if (is_err(self))
		return 1;

	if (!self->depth) {
		err(self, "Object/Array has to be started first");
		return 1;
	}

	if (in_arr(self)) {
		if (id) {
			err(self, "Array entries can't have id");
			return 1;
		}
	} else {
		if (!id) {
			err(self, "Object entries must have id");
			return 1;
		}
	}

	if (!is_first(self) && out_ch(self, ','))
		return 1;

	if (self->depth && newline(self))
		return 1;

	if (id) {
		if (out_esc_str(self, id))
			return 1;

		if (out_str(self, ": "))
			return 1;
	}

	return 0;
}

int ujson_obj_start(ujson_writer *self, const char *id)
{
	if (self->depth >= UJSON_RECURSION_MAX)
		return 1;

	if (!self->depth && id) {
		err(self, "Top level object cannot have id");
		return 1;
	}

	if (self->depth && add_common(self, id))
		return 1;

	if (out_ch(self, '{'))
		return 1;

	set_depth_bit(self, 1);

	return 0;
}

int ujson_obj_finish(ujson_writer *self)
{
	if (is_err(self))
		return 1;

	if (!in_obj(self)) {
		err(self, "Not in object!");
		return 1;
	}

	int first = is_first(self);

	clear_depth_bit(self);

	if (!first)
		newline(self);

	return out_ch(self, '}');
}

int ujson_arr_start(ujson_writer *self, const char *id)
{
	if (self->depth >= UJSON_RECURSION_MAX) {
		err(self, "Recursion too deep");
		return 1;
	}

	if (!self->depth && id) {
		err(self, "Top level array cannot have id");
		return 1;
	}

	if (self->depth && add_common(self, id))
		return 1;

	if (out_ch(self, '['))
		return 1;

	set_depth_bit(self, 0);

	return 0;
}

int ujson_arr_finish(ujson_writer *self)
{
	if (is_err(self))
		return 1;

	if (!in_arr(self)) {
		err(self, "Not in array!");
		return 1;
	}

	int first = is_first(self);

	clear_depth_bit(self);

	if (!first)
		newline(self);

	return out_ch(self, ']');
}

int ujson_null_add(ujson_writer *self, const char *id)
{
	if (add_common(self, id))
		return 1;

	return out_str(self, "null");
}

int ujson_int_add(ujson_writer *self, const char *id, long val)
{
	char buf[64];

	if (add_common(self, id))
		return 1;

	snprintf(buf, sizeof(buf), "%li", val);

	return out_str(self, buf);
}

int ujson_bool_add(ujson_writer *self, const char *id, int val)
{
	if (add_common(self, id))
		return 1;

	if (val)
		return out_str(self, "true");
	else
		return out_str(self, "false");
}

int ujson_str_add(ujson_writer *self, const char *id, const char *val)
{
	if (add_common(self, id))
		return 1;

	if (out_esc_str(self, val))
		return 1;

	return 0;
}

int ujson_float_add(ujson_writer *self, const char *id, double val)
{
	char buf[64];

	if (add_common(self, id))
		return 1;

	snprintf(buf, sizeof(buf), "%lg", val);

	return out_str(self, buf);
}

int ujson_writer_finish(ujson_writer *self)
{
	if (is_err(self))
		goto err;

	if (self->depth) {
		err(self, "Objects and/or Arrays not finished");
		goto err;
	}

	if (newline(self))
		return 1;

	return 0;
err:
	if (self->err_print)
		self->err_print(self->err_print_priv, self->err);

	return 1;
}

struct json_writer_file {
	int fd;
	size_t buf_used;
	char buf[1024];
};

static int out_writer_file_write(ujson_writer *self, int fd, const char *buf, ssize_t buf_len)
{
	do {
		ssize_t ret = write(fd, buf, buf_len);
		if (ret <= 0) {
			err(self, "Failed to write to a file");
			return 1;
		}

		if (ret > buf_len) {
			err(self, "Wrote more bytes than requested?!");
			return 1;
		}

		buf_len -= ret;
	} while (buf_len);

	return 0;
}

static int out_writer_file(ujson_writer *self, const char *buf, size_t buf_len)
{
	struct json_writer_file *writer_file = self->out_priv;
	size_t buf_size = sizeof(writer_file->buf);
	size_t buf_avail = buf_size - writer_file->buf_used;

	if (buf_len > buf_size/4)
		return out_writer_file_write(self, writer_file->fd, buf, buf_len);

	if (buf_len >= buf_avail) {
		if (out_writer_file_write(self, writer_file->fd,
		                          writer_file->buf, writer_file->buf_used))
			return 1;

		memcpy(writer_file->buf, buf, buf_len);
		writer_file->buf_used = buf_len;
		return 0;
	}

	memcpy(writer_file->buf + writer_file->buf_used, buf, buf_len);
	writer_file->buf_used += buf_len;

	return 0;
}

int ujson_writer_file_close(ujson_writer *self)
{
	struct json_writer_file *writer_file = self->out_priv;
	int saved_errno = 0;

	if (writer_file->buf_used) {
		if (out_writer_file_write(self, writer_file->fd,
		                          writer_file->buf, writer_file->buf_used))

			saved_errno = errno;
	}

	if (close(writer_file->fd)) {
		if (!saved_errno)
			saved_errno = errno;
	}

	free(self);

	if (saved_errno) {
		errno = saved_errno;
		return 1;
	}

	return 0;
}

ujson_writer *ujson_writer_file_open(const char *path)
{
	ujson_writer *ret;
	struct json_writer_file *writer_file;

	ret = malloc(sizeof(ujson_writer) + sizeof(struct json_writer_file));
	if (!ret)
		return NULL;

	writer_file = (void*)ret + sizeof(ujson_writer);

	writer_file->fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0664);
	if (!writer_file->fd) {
		free(ret);
		return NULL;
	}

	writer_file->buf_used = 0;

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

	ret->err_print = UJSON_ERR_PRINT;
	ret->err_print_priv = UJSON_ERR_PRINT_PRIV;
	ret->out = out_writer_file;
	ret->out_priv = writer_file;

	return ret;
}