#include <stdio.h>

#include "lib.h"
#include "compat.h"
#include "mempool.h"
#include "hash.h"
#include "array.h"
#include "message-address.h"

#include "sieve-extensions.h"
#include "sieve-code.h"
#include "sieve-binary.h"
#include "sieve-validator.h"
#include "sieve-generator.h"
#include "sieve-interpreter.h"

#include "sieve-address-parts.h"

#include <string.h>

/* 
 * Predeclarations 
 */
 
static void opr_address_part_emit
	(struct sieve_binary *sbin, unsigned int code);
static void opr_address_part_emit_ext
	(struct sieve_binary *sbin, int ext_id);

/* 
 * Comparator 'extension' 
 */

static int ext_my_id = -1;

static bool addrp_extension_load(int ext_id);
static bool addrp_validator_load(struct sieve_validator *validator);
static bool addrp_interpreter_load(struct sieve_interpreter *interp);

const struct sieve_extension address_part_extension = {
	"@address-part",
	addrp_extension_load,
	addrp_validator_load,
	NULL,
	NULL, //addrp_interpreter_load,
	NULL,
	NULL
};
	
static bool addrp_extension_load(int ext_id) 
{
	ext_my_id = ext_id;
	return TRUE;
}

/* 
 * Validator context:
 *   name-based comparator registry. 
 */
 
struct addrp_validator_context {
	struct hash_table *address_parts;
};

static inline struct addrp_validator_context *
	get_validator_context(struct sieve_validator *validator)
{
	return (struct addrp_validator_context *) 
		sieve_validator_extension_get_context(validator, ext_my_id);
}
 
void sieve_address_part_register
	(struct sieve_validator *validator, const struct sieve_address_part *addrp) 
{
	struct addrp_validator_context *ctx = get_validator_context(validator);
	
	hash_insert(ctx->address_parts, (void *) addrp->identifier, (void *) addrp);
}

const struct sieve_address_part *sieve_address_part_find
		(struct sieve_validator *validator, const char *addrp_name) 
{
	struct addrp_validator_context *ctx = get_validator_context(validator);

  return 	(const struct sieve_address_part *) 
  	hash_lookup(ctx->address_parts, addrp_name);
}

static bool addrp_validator_load(struct sieve_validator *validator)
{
	unsigned int i;
	pool_t pool = sieve_validator_pool(validator);
	
	struct addrp_validator_context *ctx = 
		p_new(pool, struct addrp_validator_context, 1);
	
	/* Setup comparator registry */
	ctx->address_parts = hash_create
		(pool, pool, 0, str_hash, (hash_cmp_callback_t *)strcmp);

	/* Register core comparators */
	for ( i = 0; i < sieve_core_address_parts_count; i++ ) {
		const struct sieve_address_part *addrp = sieve_core_address_parts[i];
		
		hash_insert(ctx->address_parts, (void *) addrp->identifier, (void *) addrp);
	}

	sieve_validator_extension_set_context(validator, ext_my_id, ctx);

	return TRUE;
}

void sieve_address_parts_link_tags
	(struct sieve_validator *validator, 
		struct sieve_command_registration *cmd_reg,
		unsigned int id_code) 
{
	struct addrp_validator_context *ctx = get_validator_context(validator);
	struct hash_iterate_context *itx = hash_iterate_init(ctx->address_parts);
	void *key; 
	void *addrp;
	
	while ( hash_iterate(itx, &key, &addrp) ) {
		sieve_validator_register_tag
			(validator, cmd_reg, ((struct sieve_address_part *) addrp)->tag, id_code); 	
	}

	hash_iterate_deinit(&itx); 	
}

/*
 * Address-part operand
 */
 
struct sieve_operand_class address_part_class = 
	{ "address-part", NULL };
struct sieve_operand address_part_operand = 
	{ "address-part", &address_part_class, FALSE };

/* 
 * Address-part tag 
 */
 
static bool tag_address_part_validate
	(struct sieve_validator *validator ATTR_UNUSED, 
	struct sieve_ast_argument **arg, 
	struct sieve_command_context *cmd ATTR_UNUSED)
{
	const struct sieve_address_part *addrp;

	/* Syntax:   
	 *   ":localpart" / ":domain" / ":all"
   */
	
	/* Get address_part from registry */
	addrp = sieve_address_part_find(validator, sieve_ast_argument_tag(*arg));
	
	if ( addrp == NULL ) {
		sieve_command_validate_error(validator, cmd, 
			"unknown address-part modifier '%s' "
			"(this error should not occur and is probably a bug)", 
			sieve_ast_argument_strc(*arg));

		return FALSE;
	}

	/* Store comparator in context */
	(*arg)->context = (void *) addrp;
	
	/* Skip tag */
	*arg = sieve_ast_argument_next(*arg);

	return TRUE;
}

/* Code generation */

static void opr_address_part_emit
	(struct sieve_binary *sbin, unsigned int code)
{ 
	(void) sieve_operand_emit_code(sbin, SIEVE_OPERAND_ADDRESS_PART);
	(void) sieve_binary_emit_byte(sbin, code);
}

static void opr_address_part_emit_ext
	(struct sieve_binary *sbin, int ext_id)
{ 
	unsigned char cmp_code = SIEVE_ADDRESS_PART_CUSTOM + 
		sieve_binary_extension_get_index(sbin, ext_id);
	
	(void) sieve_operand_emit_code(sbin, SIEVE_OPERAND_ADDRESS_PART);	
	(void) sieve_binary_emit_byte(sbin, cmp_code);
}

const struct sieve_address_part *sieve_opr_address_part_read
  (struct sieve_binary *sbin, sieve_size_t *address)
{
	unsigned int addrp_code;
	const struct sieve_operand *operand = sieve_operand_read(sbin, address);
	
	if ( operand == NULL || operand->class != &address_part_class ) 
		return NULL;
	
	if ( sieve_binary_read_byte(sbin, address, &addrp_code) ) {
		if ( addrp_code < SIEVE_ADDRESS_PART_CUSTOM ) {
			if ( addrp_code < sieve_core_address_parts_count )
				return sieve_core_address_parts[addrp_code];
			else
				return NULL;
		} else {
		  /*const struct sieve_extension *ext = 
		  	sieve_binary_get_extension(sbin, cmp_code - SIEVE_ADDRESS_PART_CUSTOM);
		  
		  if ( ext != NULL )
		  	return (const struct sieve_address_part *) 
		  		sieve_interpreter_registry_get(addrp_registry, ext);	
		  else*/
		  	return NULL;
		}
	}		
		
	return NULL;
}

bool sieve_opr_address_part_dump(struct sieve_binary *sbin, sieve_size_t *address)
{
	sieve_size_t pc = *address;
	const struct sieve_address_part *addrp = 
		sieve_opr_address_part_read(sbin, address);
	
	if ( addrp == NULL )
		return FALSE;
		
	printf("%08x:   ADDRP: %s\n", pc, addrp->identifier);
	
	return TRUE;
}

static bool tag_address_part_generate
	(struct sieve_generator *generator, struct sieve_ast_argument **arg, 
	struct sieve_command_context *cmd ATTR_UNUSED)
{
	struct sieve_binary *sbin = sieve_generator_get_binary(generator);
	struct sieve_address_part *addrp = 
		(struct sieve_address_part *) (*arg)->context;
	
	if ( addrp->extension == NULL ) {
		if ( addrp->code < SIEVE_ADDRESS_PART_CUSTOM )
			opr_address_part_emit(sbin, addrp->code);
		else
			return FALSE;
	} else {
		//opr_comparator_emit_ext(sbin, cmp->extension);
	} 
		
	*arg = sieve_ast_argument_next(*arg);
	
	return TRUE;
}

/*
 * Address Matching
 */
 
bool sieve_address_stringlist_match
	(struct sieve_address_part *addrp, struct sieve_coded_stringlist *key_list,
		struct sieve_comparator *cmp,	const char *data)
{
	bool matched = FALSE;
	const struct message_address *addr;
	
	t_push();
	
	addr = message_address_parse
		(unsafe_data_stack_pool, (const unsigned char *) data, 
			strlen(data), 256, FALSE);
	
	while (!matched && addr != NULL) {
		if (addr->domain != NULL) {
			/* mailbox@domain */
			const char *part;
			
			i_assert(addr->mailbox != NULL);
			
			part = addrp->extract_from(addr);
			
			if ( sieve_stringlist_match(key_list, part, cmp) )
				matched = TRUE;				
		} 

		addr = addr->next;
	}
	
	t_pop();
	
	return matched;
}

/* 
 * Core address-part modifiers
 */
 
const struct sieve_argument address_localpart_tag = 
	{ "localpart", tag_address_part_validate, tag_address_part_generate };
const struct sieve_argument address_domain_tag = 
	{ "domain", tag_address_part_validate, tag_address_part_generate };
const struct sieve_argument address_all_tag = 
	{ "all", tag_address_part_validate, tag_address_part_generate };

static const char *addrp_all_extract_from
	(const struct message_address *address)
{
	return t_strconcat(address->mailbox, "@", address->domain, NULL);
}

static const char *addrp_domain_extract_from
	(const struct message_address *address)
{
	return address->domain;
}

static const char *addrp_localpart_extract_from
	(const struct message_address *address)
{
	return address->mailbox;
}

const struct sieve_address_part all_address_part = {
	"all",
	&address_all_tag,
	SIEVE_ADDRESS_PART_ALL,
	NULL,
	addrp_all_extract_from
};

const struct sieve_address_part local_address_part = {
	"localpart",
	&address_localpart_tag,
	SIEVE_ADDRESS_PART_LOCAL,
	NULL,
	addrp_localpart_extract_from
};

const struct sieve_address_part domain_address_part = {
	"domain",
	&address_domain_tag,
	SIEVE_ADDRESS_PART_DOMAIN,
	NULL,
	addrp_domain_extract_from
};

const struct sieve_address_part *sieve_core_address_parts[] = {
	&all_address_part, &local_address_part, &domain_address_part
};

const unsigned int sieve_core_address_parts_count = N_ELEMENTS(sieve_core_address_parts);