/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "str.h"
#include "seq-set-builder.h"

struct seqset_builder {
	string_t *str;
	uint32_t last_seq;
	size_t last_seq_pos;
	size_t prefix_length;
};

struct seqset_builder *seqset_builder_init(string_t *str)
{
	struct seqset_builder *builder;
	builder = i_new(struct seqset_builder, 1);
	builder->str = str;
	builder->last_seq = 0;
	builder->prefix_length = str_len(str);
	builder->last_seq_pos = 0;
	return builder;
}

static void
seqset_builder_append_one(struct seqset_builder *builder, uint32_t seq)
{
	builder->last_seq_pos = str_len(builder->str)+1;
	str_printfa(builder->str, "%u,", seq);
}

static void
seqset_builder_create_or_merge_range(struct seqset_builder *builder,
				     uint32_t seq)
{
	char delimiter = '\0';

	i_assert(builder->last_seq_pos > builder->prefix_length);

	str_truncate(builder->str, builder->last_seq_pos-1);

	/* Get the delimiter from the builder string */
	if (str_len(builder->str) > 0 &&
	    str_len(builder->str) - 1 > builder->prefix_length)
		delimiter = str_data(builder->str)[str_len(builder->str) - 1];

	if (delimiter == ':') {
		seqset_builder_append_one(builder, seq);
	} else if (delimiter == ',' || delimiter == '\0') {
		str_printfa(builder->str, "%u:", builder->last_seq);
		builder->last_seq_pos = str_len(builder->str) + 1;
		str_printfa(builder->str, "%u,", seq);
	} else
		i_unreached();
	return;
}

void seqset_builder_add(struct seqset_builder *builder, uint32_t seq)
{
	if (builder->last_seq == 0) {
		/* No seq was yet appended so just append this one */
		seqset_builder_append_one(builder, seq);
	} else if (builder->last_seq + 1 == seq) {
		/* This seq is following directly on the previous one
		   try to create a range of seqs */
		seqset_builder_create_or_merge_range(builder, seq);
	} else {
		/* Append this seq without creating a range */
		seqset_builder_append_one(builder, seq);
	}
	builder->last_seq = seq;
}

bool seqset_builder_try_add(struct seqset_builder *builder, size_t max_len,
			    uint32_t seq)
{
	/* Length of this sequence to be appended */
	unsigned int seq_len = 0;
	/* Buffer to use when calculating seq length as string */
	char seq_str[MAX_INT_STRLEN];
	/* Current length of the seq string */
	unsigned int builder_str_len = str_len(builder->str);

	if (builder->last_seq + 1 == seq && builder_str_len + 1 <= max_len) {
		/* Following sequence: This seq can't grow the overall length
		   by more than one. */
		seqset_builder_add(builder, seq);
		return TRUE;
	}

	if (builder_str_len + MAX_INT_STRLEN + 1 <= max_len) {
		/* Appending the maximum length of a sequence number and ','
		   still fits into max_len. There is no need to check the
		   actual length. */
		seqset_builder_add(builder, seq);
		return TRUE;
	}

	seq_len = strlen(dec2str_buf(seq_str, seq)) + 1;
	if (seq_len + builder_str_len > max_len)
		return FALSE;

	seqset_builder_add(builder, seq);
	return TRUE;
}

void seqset_builder_deinit(struct seqset_builder **builder)
{
	/* If anything was appended to the string remove the trailing ',' */
	if ((*builder)->last_seq != 0)
		str_truncate((*builder)->str, str_len((*builder)->str) - 1);
	i_free(*builder);
}
