#ifndef SIEVE_BINARY_PRIVATE_H
#define SIEVE_BINARY_PRIVATE_H

#include "sieve-common.h"
#include "sieve-binary.h"
#include "sieve-extensions.h"

#include <sys/stat.h>

/*
 * Binary file
 */

struct sieve_binary_file {
	pool_t pool;
	const char *path;
	struct sieve_instance *svinst;

	struct stat st;
	int fd;
	off_t offset;

	const void *(*load_data)
		(struct sieve_binary_file *file, off_t *offset, size_t size);
	buffer_t *(*load_buffer)
		(struct sieve_binary_file *file, off_t *offset, size_t size);
};

bool sieve_binary_file_open
	(struct sieve_binary_file *file, struct sieve_instance *svinst,
		const char *path, enum sieve_error *error_r);
void sieve_binary_file_close(struct sieve_binary_file **file);

/*
 * Internal structures
 */

/* Extension registration */

struct sieve_binary_extension_reg {
	/* The identifier of the extension within this binary */
	int index;

	/* Global extension object */
	const struct sieve_extension *extension;

	/* Extension to the binary; typically used to manage extension-specific blocks
	 * in the binary and as a means to get a binary_free notification to release
	 * references held by extensions.
	 */
	const struct sieve_binary_extension *binext;

	/* Context data associated to the binary by this extension */
	void *context;

	/* Main block for this extension */
	unsigned int block_id;
};

/* Block */

struct sieve_binary_block {
	struct sieve_binary *sbin;
	unsigned int id;
	int ext_index;

	buffer_t *data;

	uoff_t offset;
};

/*
 * Binary object
 */

struct sieve_binary {
	pool_t pool;
	int refcount;

	struct sieve_instance *svinst;

	struct sieve_script *script;

	struct sieve_binary_file *file;

	/* When the binary is loaded into memory or when it is being constructed by
	 * the generator, extensions can be associated to the binary. The extensions
	 * array is a sequential list of all linked extensions. The extension_index
	 * array is a mapping ext_id -> binary_extension. This is used to obtain the
	 * index code associated with an extension for this particular binary. The
	 * linked_extensions list all extensions linked to this binary object other
	 * than the preloaded language features implemented as 'extensions'.
	 *
	 * All arrays refer to the same extension registration objects. Upon loading
	 * a binary, the 'require'd extensions will sometimes need to associate
	 * context data to the binary object in memory. This is stored in these
	 * registration objects as well.
	 */
	ARRAY(struct sieve_binary_extension_reg *) extensions;
	ARRAY(struct sieve_binary_extension_reg *) extension_index;
	ARRAY(struct sieve_binary_extension_reg *) linked_extensions;

	/* Attributes of a loaded binary */
	const char *path;

	/* Blocks */
	ARRAY(struct sieve_binary_block *) blocks;
};

struct sieve_binary *sieve_binary_create
	(struct sieve_instance *svinst, struct sieve_script *script);

/* Blocks management */

static inline struct sieve_binary_block *sieve_binary_block_index
(struct sieve_binary *sbin, unsigned int id)
{
	struct sieve_binary_block * const *sblock;

	if  ( id >= array_count(&sbin->blocks) )
		return NULL;

	sblock = array_idx(&sbin->blocks, id);

	if ( *sblock == NULL ) {
		return NULL;
	}

	return *sblock;
}

static inline size_t _sieve_binary_block_get_size
(const struct sieve_binary_block *sblock)
{
	return buffer_get_used_size(sblock->data);
}

struct sieve_binary_block *sieve_binary_block_create_id
	(struct sieve_binary *sbin, unsigned int id);

buffer_t *sieve_binary_block_get_buffer
	(struct sieve_binary_block *sblock);

/* Extension registration */

static inline struct sieve_binary_extension_reg *
	sieve_binary_extension_create_reg
(struct sieve_binary *sbin, const struct sieve_extension *ext)
{
	int index = array_count(&sbin->extensions);
	struct sieve_binary_extension_reg *ereg;

	if ( ext->id < 0 ) return NULL;

	ereg = p_new(sbin->pool, struct sieve_binary_extension_reg, 1);
	ereg->index = index;
	ereg->extension = ext;

	array_idx_set(&sbin->extensions, (unsigned int) index, &ereg);
	array_idx_set(&sbin->extension_index, (unsigned int) ext->id, &ereg);

	return ereg;
}

static inline struct sieve_binary_extension_reg *sieve_binary_extension_get_reg
(struct sieve_binary *sbin, const struct sieve_extension *ext, bool create)
{
	struct sieve_binary_extension_reg *reg = NULL;

	if ( ext->id >= 0 && ext->id < (int) array_count(&sbin->extension_index) ) {
		struct sieve_binary_extension_reg * const *ereg =
			array_idx(&sbin->extension_index, (unsigned int) ext->id);

		reg = *ereg;
	}

	/* Register if not known */
	if ( reg == NULL && create )
		return sieve_binary_extension_create_reg(sbin, ext);

	return reg;
}

static inline int sieve_binary_extension_register
(struct sieve_binary *sbin, const struct sieve_extension *ext,
	struct sieve_binary_extension_reg **reg_r)
{
	struct sieve_binary_extension_reg *ereg;

	if ( (ereg=sieve_binary_extension_get_reg(sbin, ext, FALSE)) == NULL ) {
		ereg = sieve_binary_extension_create_reg(sbin, ext);

		if ( ereg == NULL ) return -1;

		array_append(&sbin->linked_extensions, &ereg, 1);
	}

	if ( reg_r != NULL ) *reg_r = ereg;
	return ereg->index;
}

/* Load/Save */

bool sieve_binary_load_block(struct sieve_binary_block *);

#endif
