/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "seq-range-array.h"
#include "mail-index-private.h"
#include "mail-index-transaction-private.h"


struct uid_map {
	uint32_t idx;
	uint32_t uid;
};

static int uid_map_cmp(const void *p1, const void *p2)
{
	const struct uid_map *m1 = p1, *m2 = p2;

	return m1->uid < m2->uid ? -1 :
		(m1->uid > m2->uid ? 1 : 0);
}

static void
mail_index_transaction_sort_appends_ext(ARRAY_TYPE(seq_array_array) *updates,
					uint32_t first_new_seq,
					const uint32_t *old_to_newseq_map)
{
	ARRAY_TYPE(seq_array) *ext_rec_arrays;
	ARRAY_TYPE(seq_array) *old_array;
	ARRAY_TYPE(seq_array) new_array;
	unsigned int ext_count;
	const uint32_t *ext_rec;
	uint32_t seq;
	unsigned int i, j, count;

	if (!array_is_created(updates))
		return;

	ext_rec_arrays = array_get_modifiable(updates, &count);
	for (j = 0; j < count; j++) {
		old_array = &ext_rec_arrays[j];
		if (!array_is_created(old_array))
			continue;

		ext_count = array_count(old_array);
		array_create(&new_array, default_pool,
			     old_array->arr.element_size, ext_count);
		for (i = 0; i < ext_count; i++) {
			ext_rec = array_idx(old_array, i);

			seq = *ext_rec < first_new_seq ? *ext_rec :
				old_to_newseq_map[*ext_rec - first_new_seq];
			(void)mail_index_seq_array_add(&new_array, seq, ext_rec+1,
						       old_array->arr.element_size -
						       sizeof(*ext_rec), NULL);
		}
		array_free(old_array);
		ext_rec_arrays[j] = new_array;
	}
}

static void
sort_appends_seq_range(ARRAY_TYPE(seq_range) *array, uint32_t first_new_seq,
		       const uint32_t *old_to_newseq_map)
{
	struct seq_range *range, temp_range;
	ARRAY_TYPE(seq_range) old_seqs;
	uint32_t idx, idx1, idx2;
	unsigned int i, count;

	range = array_get_modifiable(array, &count);
	for (i = 0; i < count; i++) {
		if (range[i].seq2 >= first_new_seq)
			break;
	}
	if (i == count) {
		/* nothing to do */
		return;
	}

	i_array_init(&old_seqs, count - i);
	if (range[i].seq1 < first_new_seq) {
		temp_range.seq1 = first_new_seq;
		temp_range.seq2 = range[i].seq2;
		array_push_back(&old_seqs, &temp_range);
		range[i].seq2 = first_new_seq - 1;
		i++;
	}
	array_append(&old_seqs, &range[i], count - i);
	array_delete(array, i, count - i);

	range = array_get_modifiable(&old_seqs, &count);
	for (i = 0; i < count; i++) {
		idx1 = range[i].seq1 - first_new_seq;
		idx2 = range[i].seq2 - first_new_seq;
		for (idx = idx1; idx <= idx2; idx++)
			seq_range_array_add(array, old_to_newseq_map[idx]);
	}
	array_free(&old_seqs);
}

static void
mail_index_transaction_sort_appends_keywords(struct mail_index_transaction *t,
					     const uint32_t *old_to_newseq_map)
{
	struct mail_index_transaction_keyword_update *update;

	if (!array_is_created(&t->keyword_updates))
		return;

	array_foreach_modifiable(&t->keyword_updates, update) {
		if (array_is_created(&update->add_seq)) {
			sort_appends_seq_range(&update->add_seq,
					       t->first_new_seq,
					       old_to_newseq_map);
		}
		if (array_is_created(&update->remove_seq)) {
			sort_appends_seq_range(&update->remove_seq,
					       t->first_new_seq,
					       old_to_newseq_map);
		}
	}
}

void mail_index_transaction_sort_appends(struct mail_index_transaction *t)
{
	struct mail_index_record *recs, *sorted_recs;
	struct uid_map *new_uid_map;
	uint32_t *old_to_newseq_map;
	unsigned int i, count;

	if (!array_is_created(&t->appends))
		return;
	recs = array_get_modifiable(&t->appends, &count);
	i_assert(count > 0);

	if (!t->appends_nonsorted) {
		i_assert(recs[0].uid != 0);
#ifdef DEBUG
		for (i = 1; i < count; i++)
			i_assert(recs[i-1].uid < recs[i].uid);
#endif
		return;
	}

	/* first make a copy of the UIDs and map them to sequences */
	new_uid_map = i_new(struct uid_map, count);
	for (i = 0; i < count; i++) {
		i_assert(recs[i].uid != 0);
		new_uid_map[i].idx = i;
		new_uid_map[i].uid = recs[i].uid;
	}

	/* now sort the UID map */
	qsort(new_uid_map, count, sizeof(*new_uid_map), uid_map_cmp);

	/* sort mail records */
	sorted_recs = i_new(struct mail_index_record, count);
	sorted_recs[0] = recs[new_uid_map[0].idx];
	for (i = 1; i < count; i++) {
		sorted_recs[i] = recs[new_uid_map[i].idx];
		if (sorted_recs[i].uid == sorted_recs[i-1].uid)
			i_panic("Duplicate UIDs added in transaction");
	}
	buffer_write(t->appends.arr.buffer, 0, sorted_recs,
		     sizeof(*sorted_recs) * count);
	i_free(sorted_recs);

	old_to_newseq_map = i_new(uint32_t, count);
	for (i = 0; i < count; i++)
		old_to_newseq_map[new_uid_map[i].idx] = i + t->first_new_seq;
	i_free(new_uid_map);

	mail_index_transaction_sort_appends_ext(&t->ext_rec_updates,
						t->first_new_seq,
						old_to_newseq_map);
	mail_index_transaction_sort_appends_ext(&t->ext_rec_atomics,
						t->first_new_seq,
						old_to_newseq_map);
	mail_index_transaction_sort_appends_keywords(t, old_to_newseq_map);
	i_free(old_to_newseq_map);

	t->appends_nonsorted = FALSE;
}
