/* Copyright (c) Dovecot authors, see top-level COPYING file */

#include "lib.h"
#include "array.h"
#include "istream.h"
#include "ostream.h"
#include "doveadm-print-private.h"

struct doveadm_print_header_context {
	const char *key;
	char *sticky_value;
	bool sticky;
};

struct doveadm_print_context {
	pool_t pool;
	ARRAY(struct doveadm_print_header_context) headers;
	const struct doveadm_print_vfuncs *v;

	unsigned int header_idx;
	bool print_stream_open;
};

bool doveadm_print_hide_titles = FALSE;
struct ostream *doveadm_print_ostream = NULL;

static struct doveadm_print_context *ctx;
static bool doveadm_print_init_disallowed = FALSE;
static bool doveadm_print_header_disallowed = FALSE;

bool doveadm_print_is_initialized(void)
{
	return ctx != NULL;
}

void doveadm_print_header(const char *key, const char *title,
			  enum doveadm_print_header_flags flags)
{
	struct doveadm_print_header hdr;
	struct doveadm_print_header_context *hdr_ctx;

	i_assert(title != NULL);

	if (doveadm_print_header_disallowed)
		i_panic("doveadm_print_header() is called in wrong place");

	i_zero(&hdr);
	hdr.key = key;
	hdr.title = title;
	hdr.flags = flags;
	ctx->v->header(&hdr);

	hdr_ctx = array_append_space(&ctx->headers);
	hdr_ctx->key = p_strdup(ctx->pool, key);
	hdr_ctx->sticky = (flags & DOVEADM_PRINT_HEADER_FLAG_STICKY) != 0;
}

void doveadm_print_header_simple(const char *key_title)
{
	doveadm_print_header(key_title, key_title, 0);
}

unsigned int doveadm_print_get_headers_count(void)
{
	return array_count(&ctx->headers);
}

static void doveadm_print_sticky_headers(void)
{
	const struct doveadm_print_header_context *headers;
	unsigned int count;

	headers = array_get(&ctx->headers, &count);
	i_assert(count > 0);
	for (;;) {
		if (ctx->header_idx == count)
			ctx->header_idx = 0;
		else if (headers[ctx->header_idx].sticky) {
			ctx->v->print(headers[ctx->header_idx].sticky_value);
			ctx->header_idx++;
		} else {
			break;
		}
	}
}

void doveadm_print(const char *value)
{
	i_assert(!ctx->print_stream_open);

	doveadm_print_sticky_headers();
	ctx->v->print(value);
	ctx->header_idx++;
}

void doveadm_print_empty(unsigned int count)
{
	for (unsigned int i = 0; i < count; i++)
		doveadm_print("");
}

void doveadm_print_num(uintmax_t value)
{
	T_BEGIN {
		doveadm_print(dec2str(value));
	} T_END;
}

void doveadm_print_stream(const void *value, size_t size)
{
	if (!ctx->print_stream_open) {
		doveadm_print_sticky_headers();
		ctx->print_stream_open = TRUE;
	}
	ctx->v->print_stream(value, size);
	if (size == 0) {
		ctx->header_idx++;
		ctx->print_stream_open = FALSE;
	}
}

int doveadm_print_istream(struct istream *input)
{
	const unsigned char *data;
	size_t size;
	ssize_t ret;

	while ((ret = i_stream_read_more(input, &data, &size)) > 0) {
		doveadm_print_stream(data, size);
		i_stream_skip(input, size);
	}
	i_assert(ret == -1);
	doveadm_print_stream("", 0);
	if (input->stream_errno != 0) {
		/* caller will log the error */
		return -1;
	}
	return 0;
}

void doveadm_print_sticky(const char *key, const char *value)
{
	struct doveadm_print_header_context *hdr;

	if (ctx == NULL) {
		/* command doesn't really print anything */
		return;
	}

	array_foreach_modifiable(&ctx->headers, hdr) {
		if (strcmp(hdr->key, key) == 0) {
			i_free(hdr->sticky_value);
			hdr->sticky_value = i_strdup(value);
			return;
		}
	}
	i_unreached();
}

void doveadm_print_flush(void)
{
	if (ctx != NULL && ctx->v->flush != NULL)
		ctx->v->flush();
	o_stream_uncork(doveadm_print_ostream);
	o_stream_cork(doveadm_print_ostream);
}

void doveadm_print_init(const char *name)
{
	pool_t pool;
	unsigned int i;

	if (ctx != NULL) {
		/* already forced the type */
		return;
	}
	if (doveadm_print_init_disallowed)
		i_panic("doveadm_print_init() is called in wrong place");

	pool = pool_alloconly_create("doveadm print", 1024);
	ctx = p_new(pool, struct doveadm_print_context, 1);
	ctx->pool = pool;
	p_array_init(&ctx->headers, pool, 16);

	for (i = 0; doveadm_print_vfuncs_all[i] != NULL; i++) {
		if (strcmp(doveadm_print_vfuncs_all[i]->name, name) == 0) {
			ctx->v = doveadm_print_vfuncs_all[i];
			break;
		}
	}
	if (ctx->v == NULL)
		i_fatal("Unknown print formatter: %s", name);
	if (ctx->v->init != NULL)
		ctx->v->init();
}

void doveadm_print_deinit(void)
{
	struct doveadm_print_header_context *hdr;

	if (ctx == NULL)
		return;

	if (ctx->v->flush != NULL && doveadm_print_ostream != NULL) {
		ctx->v->flush();
		o_stream_uncork(doveadm_print_ostream);
	}
	if (ctx->v->deinit != NULL)
		ctx->v->deinit();
	array_foreach_modifiable(&ctx->headers, hdr)
		i_free(hdr->sticky_value);
	pool_unref(&ctx->pool);
	ctx = NULL;

	doveadm_print_init_disallowed = FALSE;
	doveadm_print_header_disallowed = FALSE;
}

void doveadm_print_init_disallow(bool disallow)
{
	doveadm_print_init_disallowed = disallow;
}

void doveadm_print_header_disallow(bool disallow)
{
	doveadm_print_header_disallowed = disallow;
}
