#ifndef SIEVE_BINARY_H
#define SIEVE_BINARY_H

#include "lib.h"

#include "sieve-common.h"

/*
 * Config
 */

#define SIEVE_BINARY_VERSION_MAJOR     1
#define SIEVE_BINARY_VERSION_MINOR     4

/*
 * Binary object
 */

struct sieve_binary;

struct sieve_binary *sieve_binary_create_new(struct sieve_script *script);
void sieve_binary_ref(struct sieve_binary *sbin);
void sieve_binary_unref(struct sieve_binary **sbin);

/*
 * Accessors
 */

pool_t sieve_binary_pool(struct sieve_binary *sbin);
struct sieve_instance *sieve_binary_svinst(struct sieve_binary *sbin);
const char *sieve_binary_path(struct sieve_binary *sbin);
struct sieve_script *sieve_binary_script(struct sieve_binary *sbin);

time_t sieve_binary_mtime(struct sieve_binary *sbin);
const struct stat *sieve_binary_stat
(struct sieve_binary *sbin);

const char *sieve_binary_script_name(struct sieve_binary *sbin);
const char *sieve_binary_script_location(struct sieve_binary *sbin);

const char *sieve_binary_source(struct sieve_binary *sbin);
bool sieve_binary_loaded(struct sieve_binary *sbin);
bool sieve_binary_saved(struct sieve_binary *sbin);

/*
 * Utility
 */

const char *sieve_binfile_from_name(const char *name);

/*
 * Activation after code generation
 */

void sieve_binary_activate(struct sieve_binary *sbin);

/*
 * Saving the binary
 */

int sieve_binary_save
	(struct sieve_binary *sbin, const char *path, bool update, mode_t save_mode,
		enum sieve_error *error_r);

/*
 * Loading the binary
 */

struct sieve_binary *sieve_binary_open
	(struct sieve_instance *svinst, const char *path,
		struct sieve_script *script, enum sieve_error *error_r);
bool sieve_binary_up_to_date
	(struct sieve_binary *sbin, enum sieve_compile_flags cpflags);

/*
 * Block management
 */

enum sieve_binary_system_block {
	SBIN_SYSBLOCK_SCRIPT_DATA,
	SBIN_SYSBLOCK_EXTENSIONS,
	SBIN_SYSBLOCK_MAIN_PROGRAM,
	SBIN_SYSBLOCK_LAST
};

struct sieve_binary_block *sieve_binary_block_create
	(struct sieve_binary *sbin);

unsigned int sieve_binary_block_count
	(struct sieve_binary *sbin);

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

void sieve_binary_block_clear
	(struct sieve_binary_block *sblock);

size_t sieve_binary_block_get_size
	(const struct sieve_binary_block *sblock);

struct sieve_binary *sieve_binary_block_get_binary
	(const struct sieve_binary_block *sblock);

unsigned int sieve_binary_block_get_id
	(const struct sieve_binary_block *sblock);

/*
 * Extension support
 */

struct sieve_binary_extension {
	const struct sieve_extension_def *extension;

	bool (*binary_pre_save)
		(const struct sieve_extension *ext, struct sieve_binary *sbin,
			void *context, enum sieve_error *error_r);
	bool (*binary_post_save)
		(const struct sieve_extension *ext, struct sieve_binary *sbin,
			void *context, enum sieve_error *error_r);
	bool (*binary_open)
		(const struct sieve_extension *ext, struct sieve_binary *sbin,
			void *context);

	void (*binary_free)
		(const struct sieve_extension *ext, struct sieve_binary *sbin,
			void *context);

	bool (*binary_up_to_date)
		(const struct sieve_extension *ext, struct sieve_binary *sbin,
			void *context, enum sieve_compile_flags cpflags);
};

void sieve_binary_extension_set_context
	(struct sieve_binary *sbin, const struct sieve_extension *ext, void *context);
const void *sieve_binary_extension_get_context
	(struct sieve_binary *sbin, const struct sieve_extension *ext);

void sieve_binary_extension_set
	(struct sieve_binary *sbin, const struct sieve_extension *ext,
		const struct sieve_binary_extension *bext, void *context);

struct sieve_binary_block *sieve_binary_extension_create_block
	(struct sieve_binary *sbin, const struct sieve_extension *ext);
struct sieve_binary_block *sieve_binary_extension_get_block
(struct sieve_binary *sbin, const struct sieve_extension *ext);

int sieve_binary_extension_link
	(struct sieve_binary *sbin, const struct sieve_extension *ext);
const struct sieve_extension *sieve_binary_extension_get_by_index
	(struct sieve_binary *sbin, int index);
int sieve_binary_extension_get_index
	(struct sieve_binary *sbin, const struct sieve_extension *ext);
int sieve_binary_extensions_count(struct sieve_binary *sbin);


/*
 * Code emission
 */

/* Low-level emission functions */

sieve_size_t sieve_binary_emit_data
	(struct sieve_binary_block *sblock, const void *data, sieve_size_t size);
sieve_size_t sieve_binary_emit_byte
	(struct sieve_binary_block *sblock, uint8_t byte);
void sieve_binary_update_data
	(struct sieve_binary_block *sblock, sieve_size_t address, const void *data,
		sieve_size_t size);

/* Offset emission functions */

sieve_size_t sieve_binary_emit_offset
	(struct sieve_binary_block *sblock, sieve_offset_t offset);
void sieve_binary_resolve_offset
	(struct sieve_binary_block *sblock, sieve_size_t address);

/* Literal emission functions */

sieve_size_t sieve_binary_emit_integer
	(struct sieve_binary_block *sblock, sieve_number_t integer);
sieve_size_t sieve_binary_emit_string
	(struct sieve_binary_block *sblock, const string_t *str);
sieve_size_t sieve_binary_emit_cstring
	(struct sieve_binary_block *sblock, const char *str);

static inline sieve_size_t sieve_binary_emit_unsigned
	(struct sieve_binary_block *sblock, unsigned int count)
{
	return sieve_binary_emit_integer(sblock, count);
}

/* Extension emission functions */

sieve_size_t sieve_binary_emit_extension
	(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
		unsigned int offset);
void sieve_binary_emit_extension_object
	(struct sieve_binary_block *sblock,
		const struct sieve_extension_objects *objs, unsigned int code);

/*
 * Code retrieval
 */

/* Literals */

bool sieve_binary_read_byte
	(struct sieve_binary_block *sblock, sieve_size_t *address,
		unsigned int *byte_r) ATTR_NULL(3);
bool sieve_binary_read_code
	(struct sieve_binary_block *sblock, sieve_size_t *address,
		signed int *code_r) ATTR_NULL(3);
bool sieve_binary_read_offset
	(struct sieve_binary_block *sblock, sieve_size_t *address,
		sieve_offset_t *offset_r) ATTR_NULL(3);
bool sieve_binary_read_integer
  (struct sieve_binary_block *sblock, sieve_size_t *address,
		sieve_number_t *int_r) ATTR_NULL(3);
bool sieve_binary_read_string
  (struct sieve_binary_block *sblock, sieve_size_t *address,
		string_t **str_r) ATTR_NULL(3);

static inline bool sieve_binary_read_unsigned
(struct sieve_binary_block *sblock, sieve_size_t *address,
	unsigned int *count_r) ATTR_NULL(3)
{
	sieve_number_t integer = 0;

	if ( !sieve_binary_read_integer(sblock, address, &integer) )
		return FALSE;

	if ( count_r != NULL )
		*count_r = integer;

	return TRUE;
}

/* Extensions */

bool sieve_binary_read_extension
	(struct sieve_binary_block *sblock, sieve_size_t *address,
		unsigned int *offset_r, const struct sieve_extension **ext_r);
const void *sieve_binary_read_extension_object
	(struct sieve_binary_block *sblock, sieve_size_t *address,
    const struct sieve_extension_objects *objs);

/*
 * Debug info
 */

/* Writer */

struct sieve_binary_debug_writer;

struct sieve_binary_debug_writer *sieve_binary_debug_writer_init
	(struct sieve_binary_block *sblock);
void sieve_binary_debug_writer_deinit
	(struct sieve_binary_debug_writer **dwriter);

void sieve_binary_debug_emit
	(struct sieve_binary_debug_writer *dwriter, sieve_size_t code_address,
		unsigned int code_line, unsigned int code_column);

/* Reader */

struct sieve_binary_debug_reader *sieve_binary_debug_reader_init
	(struct sieve_binary_block *sblock);
void sieve_binary_debug_reader_deinit
	(struct sieve_binary_debug_reader **dreader);

void sieve_binary_debug_reader_reset
	(struct sieve_binary_debug_reader *dreader);

unsigned int sieve_binary_debug_read_line
	(struct sieve_binary_debug_reader *dreader, sieve_size_t code_address);

#endif
