/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
 */

#include "lib.h"
#include "str.h"

#include "sieve-common.h"
#include "sieve-extensions.h"
#include "sieve-error.h"
#include "sieve-script.h"
#include "sieve-binary.h"
#include "sieve-generator.h"
#include "sieve-interpreter.h"
#include "sieve-dump.h"

#include "ext-ihave-common.h"
#include "ext-ihave-binary.h"

/*
 * Forward declarations
 */

static bool
ext_ihave_binary_pre_save(const struct sieve_extension *ext,
			  struct sieve_binary *sbin, void *context,
			  enum sieve_error *error_code_r);
static bool
ext_ihave_binary_open(const struct sieve_extension *ext,
		      struct sieve_binary *sbin, void *context);
static bool
ext_ihave_binary_up_to_date(const struct sieve_extension *ext,
			    struct sieve_binary *sbin, void *context,
			    enum sieve_compile_flags cpflags);

/*
 * Binary include extension
 */

const struct sieve_binary_extension ihave_binary_ext = {
	.extension = &ihave_extension,
	.binary_pre_save = ext_ihave_binary_pre_save,
	.binary_open = ext_ihave_binary_open,
	.binary_up_to_date = ext_ihave_binary_up_to_date,
};

/*
 * Binary context management
 */

struct ext_ihave_binary_context {
	struct sieve_binary *binary;
	struct sieve_binary_block *block;

	ARRAY(const char *) missing_extensions;
};

static struct ext_ihave_binary_context *
ext_ihave_binary_create_context(const struct sieve_extension *this_ext,
				struct sieve_binary *sbin)
{
	pool_t pool = sieve_binary_pool(sbin);
	struct ext_ihave_binary_context *ctx =
		p_new(pool, struct ext_ihave_binary_context, 1);

	ctx->binary = sbin;
	p_array_init(&ctx->missing_extensions, pool, 64);

	sieve_binary_extension_set(sbin, this_ext, &ihave_binary_ext, ctx);
	return ctx;
}

struct ext_ihave_binary_context *
ext_ihave_binary_get_context(const struct sieve_extension *this_ext,
			     struct sieve_binary *sbin)
{
	struct ext_ihave_binary_context *ctx =
		(struct ext_ihave_binary_context *)
			sieve_binary_extension_get_context(sbin, this_ext);

	if (ctx == NULL)
		ctx = ext_ihave_binary_create_context(this_ext, sbin);
	return ctx;
}

struct ext_ihave_binary_context *
ext_ihave_binary_init(const struct sieve_extension *this_ext,
		      struct sieve_binary *sbin, struct sieve_ast *ast)
{
	struct ext_ihave_ast_context *ast_ctx =
		ext_ihave_get_ast_context(this_ext, ast);
	struct ext_ihave_binary_context *binctx;
	const char *const *exts;
	unsigned int i, count;

	binctx = ext_ihave_binary_get_context(this_ext, sbin);

	exts = array_get(&ast_ctx->missing_extensions, &count);
	if (count > 0) {
		pool_t pool = sieve_binary_pool(sbin);

		if (binctx->block == NULL) {
			binctx->block = sieve_binary_extension_create_block(
				sbin, this_ext);
		}

		for (i = 0; i < count; i++) {
			const char *ext_name = p_strdup(pool, exts[i]);

			array_append(&binctx->missing_extensions, &ext_name, 1);
		}
	}

	return binctx;
}

/*
 * Binary extension
 */

static bool
ext_ihave_binary_pre_save(const struct sieve_extension *ext,
			  struct sieve_binary *sbin, void *context,
			  enum sieve_error *error_code_r ATTR_UNUSED)
{
	struct ext_ihave_binary_context *binctx =
		(struct ext_ihave_binary_context *)context;
	const char *const *exts;
	unsigned int count, i;

	exts = array_get(&binctx->missing_extensions, &count);

	if (binctx->block != NULL)
		sieve_binary_block_clear(binctx->block);

	if (count > 0) {
		if (binctx->block == NULL) {
			binctx->block =
				sieve_binary_extension_create_block(sbin, ext);
		}

		sieve_binary_emit_unsigned(binctx->block, count);

		for (i = 0; i < count; i++)
			sieve_binary_emit_cstring(binctx->block, exts[i]);
	}
	return TRUE;
}

static bool
ext_ihave_binary_open(const struct sieve_extension *ext,
		      struct sieve_binary *sbin, void *context)
{
	struct sieve_instance *svinst = ext->svinst;
	struct ext_ihave_binary_context *binctx =
		(struct ext_ihave_binary_context *)context;
	struct sieve_binary_block *sblock;
	unsigned int i, count, block_id;
	sieve_size_t offset;

	sblock = sieve_binary_extension_get_block(sbin, ext);

	if (sblock != NULL) {
		binctx->block = sblock;
		block_id = sieve_binary_block_get_id(sblock);

		offset = 0;

		/* Read number of missing extensions to read subsequently */
		if (!sieve_binary_read_unsigned(sblock, &offset, &count)) {
			e_error(svinst->event, "ihave: "
				"failed to read missing extension count "
				"from block %d of binary %s",
				block_id, sieve_binary_path(sbin));
			return FALSE;
		}

		/* Read dependencies */
		for (i = 0; i < count; i++) {
			string_t *ext_name;
			const char *name;

			if (!sieve_binary_read_string(sblock, &offset,
						      &ext_name)) {
				/* Binary is corrupt, recompile */
				e_error(svinst->event, "ihave: "
					"failed to read missing extension name "
					"from block %d of binary %s",
					block_id, sieve_binary_path(sbin));
				return FALSE;
			}

			name = str_c(ext_name);
			array_append(&binctx->missing_extensions, &name, 1);
		}
	}

	return TRUE;
}

static bool
ext_ihave_binary_up_to_date(const struct sieve_extension *ext,
			    struct sieve_binary *sbin ATTR_UNUSED,
			    void *context, enum sieve_compile_flags cpflags)
{
	struct ext_ihave_binary_context *binctx =
		(struct ext_ihave_binary_context *)context;
	const struct sieve_extension *mext;
	const char *const *mexts;
	unsigned int count, i;

	mexts = array_get(&binctx->missing_extensions, &count);
	for (i = 0; i < count; i++) {
		mext = sieve_extension_get_by_name(ext->svinst, mexts[i]);
		if (mext != NULL &&
		    ((cpflags & SIEVE_COMPILE_FLAG_NOGLOBAL) == 0 ||
		     !mext->global))
			return FALSE;
	}
	return TRUE;
}

/*
 * Main extension interface
 */

bool ext_ihave_binary_load(const struct sieve_extension *ext,
			   struct sieve_binary *sbin)
{
	(void)ext_ihave_binary_get_context(ext, sbin);

	return TRUE;
}

bool ext_ihave_binary_dump(const struct sieve_extension *ext,
			   struct sieve_dumptime_env *denv)
{
	struct sieve_binary *sbin = denv->sbin;
	struct ext_ihave_binary_context *binctx =
		ext_ihave_binary_get_context(ext, sbin);
	const char *const *exts;
	unsigned int count, i;

	exts = array_get(&binctx->missing_extensions, &count);

	if (count > 0) {
		sieve_binary_dump_sectionf(
			denv, "Extensions missing at compile (block: %d)",
			sieve_binary_block_get_id(binctx->block));

		for (i = 0; i < count; i++)
			sieve_binary_dumpf(denv, "  -  %s\n", exts[i]);
	}

	return TRUE;
}
