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

#include "lib.h"
#include "str-sanitize.h"
#include "hash.h"
#include "array.h"

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

#include "sieve-comparators.h"

#include <string.h>
#include <stdio.h>

/*
 * Core comparators
 */

const struct sieve_comparator_def *sieve_core_comparators[] = {
	&i_octet_comparator, &i_ascii_casemap_comparator
};

const unsigned int sieve_core_comparators_count =
	N_ELEMENTS(sieve_core_comparators);

/*
 * Comparator 'extension'
 */

static bool cmp_validator_load
	(const struct sieve_extension *ext, struct sieve_validator *valdtr);

const struct sieve_extension_def comparator_extension = {
	.name = "@comparators",
	.validator_load = cmp_validator_load
};

/*
 * Validator context:
 *   name-based comparator registry.
 */

static struct sieve_validator_object_registry *_get_object_registry
(struct sieve_validator *valdtr)
{
	struct sieve_instance *svinst;
	const struct sieve_extension *mcht_ext;

	svinst = sieve_validator_svinst(valdtr);
	mcht_ext = sieve_get_comparator_extension(svinst);
	return sieve_validator_object_registry_get(valdtr, mcht_ext);
}

void sieve_comparator_register
(struct sieve_validator *valdtr, const struct sieve_extension *ext,
	const struct sieve_comparator_def *cmp)
{
	struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);

	sieve_validator_object_registry_add(regs, ext, &cmp->obj_def);
}

static struct sieve_comparator *sieve_comparator_create
(struct sieve_validator *valdtr, struct sieve_command *cmd,
	const char *identifier)
{
	struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
	struct sieve_object object;
	struct sieve_comparator *cmp;

	if ( !sieve_validator_object_registry_find(regs, identifier, &object) )
		return NULL;

	cmp = p_new(sieve_command_pool(cmd), struct sieve_comparator, 1);
	cmp->object = object;
	cmp->def = (const struct sieve_comparator_def *) object.def;

  return cmp;
}

bool cmp_validator_load
(const struct sieve_extension *ext, struct sieve_validator *valdtr)
{
	struct sieve_validator_object_registry *regs =
		sieve_validator_object_registry_init(valdtr, ext);
	unsigned int i;

	/* Register core comparators */
	for ( i = 0; i < sieve_core_comparators_count; i++ ) {
		sieve_validator_object_registry_add
			(regs, NULL, &(sieve_core_comparators[i]->obj_def));
	}

	return TRUE;
}

/*
 * Comparator tagged argument
 */

/* Forward declarations */

static bool tag_comparator_validate
	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
		struct sieve_command *cmd);
static bool tag_comparator_generate
	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
		struct sieve_command *cmd);

/* Argument object */

const struct sieve_argument_def comparator_tag = {
	.identifier = "comparator",
	.validate = tag_comparator_validate,
	.generate = tag_comparator_generate
};

/* Argument implementation */

static bool tag_comparator_validate
(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
	struct sieve_command *cmd)
{
	struct sieve_ast_argument *tag = *arg;
	const struct sieve_comparator *cmp;

	/* Skip tag */
	*arg = sieve_ast_argument_next(*arg);

	/* Check syntax:
	 *   ":comparator" <comparator-name: string>
	 */
	if ( !sieve_validate_tag_parameter
		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, FALSE) ) {
		return FALSE;
	}

	/* FIXME: We can currently only handle string literal argument, so
	 * variables are not allowed.
	 */
	if ( !sieve_argument_is_string_literal(*arg) ) {
		sieve_argument_validate_error(valdtr, *arg,
			"this Sieve implementation currently only supports "
			"a literal string argument for the :comparator tag");
		return FALSE;
	}

	/* Get comparator from registry */
	cmp = sieve_comparator_create(valdtr, cmd, sieve_ast_argument_strc(*arg));

	if ( cmp == NULL ) {
		sieve_argument_validate_error(valdtr, *arg,
			"unknown comparator '%s'",
			str_sanitize(sieve_ast_argument_strc(*arg),80));

		return FALSE;
	}

	/* String argument not needed during code generation, so detach it from
	 * argument list
	 */
	*arg = sieve_ast_arguments_detach(*arg, 1);

	/* Store comparator in context */
	tag->argument->data = (void *) cmp;

	return TRUE;
}

static bool tag_comparator_generate
(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
	struct sieve_command *cmd ATTR_UNUSED)
{
	const struct sieve_comparator *cmp =
		(const struct sieve_comparator *) arg->argument->data;

	sieve_opr_comparator_emit(cgenv->sblock, cmp);

	return TRUE;
}

/* Functions to enable and evaluate comparator tag for commands */

void sieve_comparators_link_tag
(struct sieve_validator *valdtr, struct sieve_command_registration *cmd_reg,
	int id_code)
{
	struct sieve_instance *svinst;
	const struct sieve_extension *mcht_ext;

	svinst = sieve_validator_svinst(valdtr);
	mcht_ext = sieve_get_comparator_extension(svinst);

	sieve_validator_register_tag
		(valdtr, cmd_reg, mcht_ext, &comparator_tag, id_code);
}

bool sieve_comparator_tag_is
(struct sieve_ast_argument *tag, const struct sieve_comparator_def *cmp_def)
{
	const struct sieve_comparator *cmp;

	if ( !sieve_argument_is(tag, comparator_tag) )
		return FALSE;

	cmp = (const struct sieve_comparator *) tag->argument->data;

	return ( cmp->def == cmp_def );
}

const struct sieve_comparator *sieve_comparator_tag_get
(struct sieve_ast_argument *tag)
{
	if ( !sieve_argument_is(tag, comparator_tag) )
		return NULL;


	return (const struct sieve_comparator *) tag->argument->data;
}

/*
 * Comparator coding
 */

const struct sieve_operand_class sieve_comparator_operand_class =
	{ "comparator" };

static const struct sieve_extension_objects core_comparators =
	SIEVE_EXT_DEFINE_COMPARATORS(sieve_core_comparators);

const struct sieve_operand_def comparator_operand = {
	.name = "comparator",
	.code = SIEVE_OPERAND_COMPARATOR,
	.class = &sieve_comparator_operand_class,
	.interface = &core_comparators
};

/*
 * Trivial/Common comparator method implementations
 */

bool sieve_comparator_octet_skip
	(const struct sieve_comparator *cmp ATTR_UNUSED,
		const char **val, const char *val_end)
{
	if ( *val < val_end ) {
		(*val)++;
		return TRUE;
	}

	return FALSE;
}
