/* Copyright (c) Pigeonhole authors, see top-level COPYING file */

/* Extension comparator-i;ascii-numeric
 * ------------------------------------
 *
 * Author: Stephan Bosch
 * Specification: RFC 2244
 * Implementation: full
 * Status: testing
 *
 */

#include "sieve-common.h"

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

#include <ctype.h>

/*
 * Forward declarations
 */

static const struct sieve_operand_def my_comparator_operand;

const struct sieve_comparator_def i_ascii_numeric_comparator;

/*
 * Extension
 */

static bool
ext_cmp_i_ascii_numeric_validator_load(const struct sieve_extension *ext,
				       struct sieve_validator *validator);

const struct sieve_extension_def comparator_i_ascii_numeric_extension = {
	.name = "comparator-i;ascii-numeric",
	.validator_load = ext_cmp_i_ascii_numeric_validator_load,
	SIEVE_EXT_DEFINE_OPERAND(my_comparator_operand)
};

static bool
ext_cmp_i_ascii_numeric_validator_load(const struct sieve_extension *ext,
				       struct sieve_validator *validator)
{
	sieve_comparator_register(validator, ext, &i_ascii_numeric_comparator);
	return TRUE;
}

/*
 * Operand
 */

static const struct sieve_extension_objects ext_comparators =
	SIEVE_EXT_DEFINE_COMPARATOR(i_ascii_numeric_comparator);

static const struct sieve_operand_def my_comparator_operand = {
	.name = "comparator-i;ascii-numeric",
	.ext_def = &comparator_i_ascii_numeric_extension,
	.class = &sieve_comparator_operand_class,
	.interface = &ext_comparators,
};

/*
 * Comparator
 */

/* Forward declarations */

static int
cmp_i_ascii_numeric_compare(const struct sieve_comparator *cmp,
			    const char *val1, size_t val1_size,
			    const char *val2, size_t val2_size);

/* Comparator object */

const struct sieve_comparator_def i_ascii_numeric_comparator = {
	SIEVE_OBJECT("i;ascii-numeric", &my_comparator_operand, 0),
	.flags =
		SIEVE_COMPARATOR_FLAG_ORDERING |
		SIEVE_COMPARATOR_FLAG_EQUALITY,
	.compare = cmp_i_ascii_numeric_compare,
};

/* Comparator implementation */

static int
cmp_i_ascii_numeric_compare(const struct sieve_comparator *cmp ATTR_UNUSED,
			    const char *val, size_t val_size,
			    const char *key, size_t key_size)
{
	const char *vend = val + val_size;
	const char *kend = key + key_size;
	const char *vp = val;
	const char *kp = key;
	int digits, i;

	/* RFC 4790: All input is valid; strings that do not start with a digit
	   represent positive infinity.
	 */
	if (!i_isdigit(*vp)) {
		if (i_isdigit(*kp)) {
			/* Value is greater */
			return 1;
		}
	} else {
		if (!i_isdigit(*kp)) {
			/* Value is less */
			return -1;
		}
	}

	/* Ignore leading zeros */

	while (*vp == '0' && vp < vend)
		vp++;
	while (*kp == '0' && kp < kend)
		kp++;

	/* Check whether both numbers are equally long in terms of digits */

	digits = 0;
	while (vp < vend && kp < kend && i_isdigit(*vp) && i_isdigit(*kp)) {
		vp++;
		kp++;
		digits++;
	}

	if (vp == vend || !i_isdigit(*vp)) {
		if (kp != kend && i_isdigit(*kp)) {
			/* Value is less */
			return -1;
		}
	} else {
		/* Value is greater */
		return 1;
	}

	/* Equally long: compare digits */

	vp -= digits;
	kp -= digits;
	i = 0;
	while (i < digits) {
		if (*vp > *kp)
			return 1;
		else if (*vp < *kp)
			return -1;

		kp++;
		vp++;
		i++;
	}

	return 0;
}
