// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Andrea Mazzoleni

#include "portable.h"

#include "elem.h"
#include "parity.h"
#include "state.h"
#include "stream.h"
#include "locate.h"

struct snapraid_parity_entry {
	uint32_t block_size;
	block_off_t low; /**< Lower position in the parity */
	block_off_t high; /**< Higher position in the parity */
	block_off_t fragments; /**< Number of fragments in the parity */
	struct snapraid_file* file;
	struct snapraid_disk* disk;
	tommy_node node;
};

struct snapraid_locate_info {
	block_off_t block_max; /**< Number of blocks in the parity */
	data_off_t parity_size; /**< Size in bytes of the parity */
	block_off_t tail_block; /**< Number of blocks of the tail of the parity */
	block_off_t low_to_free_tail_block; /**< First block that has to be freed */
};

static int parity_entry_compare(const void* void_a, const void* void_b)
{
	const struct snapraid_parity_entry* entry_a = void_a;
	const struct snapraid_parity_entry* entry_b = void_b;

	if (entry_a->high < entry_b->high)
		return -1;
	if (entry_a->high > entry_b->high)
		return 1;

	if (entry_a->low < entry_b->low)
		return -1;
	if (entry_a->low > entry_b->low)
		return 1;

	return strcmp(entry_a->file->sub, entry_b->file->sub);
}

static void dump_entry(void* void_entry)
{
	struct snapraid_parity_entry* entry = void_entry;
	printf("%12" PRIu64 " ", entry->low * (uint64_t)entry->block_size);
	printf("%12" PRIu64 " ", (entry->high - entry->low + 1) * (uint64_t)entry->block_size);
	printf("%8u ", entry->fragments);
	printf("%s\n", fmt_term(entry->disk, entry->file->sub));
}

static void add_size_to_sum(void* arg, void* void_entry)
{
	struct snapraid_parity_entry* entry = void_entry;
	data_off_t* size_sum = arg;

	*size_sum += entry->file->size;
}

static void collect_parity_block_file(uint32_t block_size, struct snapraid_disk* disk, struct snapraid_file* file, tommy_list* file_list, block_off_t low_to_free_tail_block)
{
	block_off_t parity_low = 0; /* lower block */
	block_off_t parity_high = 0; /* higher block */
	block_off_t parity_fragments = 0; /* number of fragments */

	for (block_off_t i = 0; i < file->blockmax; ++i) {
		block_off_t parity_pos = fs_file2par_find(disk, file, i);

		/* check if a valid position is found */
		if (parity_pos == POS_NULL) {
			/* block not yet allocated */
			continue;
		}

		if (parity_fragments == 0) {
			parity_low = parity_pos;
			parity_high = parity_pos;
			parity_fragments = 1;
		} else {
			if (parity_pos != parity_high + 1)
				++parity_fragments;
			parity_high = parity_pos;
		}
	}

	if (parity_fragments == 0)
		return; /* not allocated */

	if (low_to_free_tail_block > 0 && parity_high < low_to_free_tail_block)
		return; /* entry not in the range to free */

	/* found a relevant block so add the corresponding file */
	struct snapraid_parity_entry* entry = malloc_nofail(sizeof(struct snapraid_parity_entry));
	entry->block_size = block_size;
	entry->file = file;
	entry->disk = disk;
	entry->low = parity_low;
	entry->high = parity_high;
	entry->fragments = parity_fragments;
	tommy_list_insert_tail(file_list, &entry->node, entry);
}

void state_locate_info(struct snapraid_state* state, uint64_t parity_tail, struct snapraid_locate_info* info)
{
	uint32_t block_size = state->block_size;

	info->block_max = parity_allocated_size(state);
	info->parity_size = info->block_max * (uint64_t)block_size;

	info->tail_block = (parity_tail + block_size - 1) / block_size;

	if (info->tail_block > info->block_max)
		info->low_to_free_tail_block = 0;
	else
		info->low_to_free_tail_block = info->block_max - info->tail_block;
}

void state_locate(struct snapraid_state* state, uint64_t parity_tail)
{
	char buf[64];
	uint32_t block_size = state->block_size;
	block_off_t low_to_free_tail_block;

	printf("SnapRAID locate report:\n");
	printf("\n");

	if (parity_tail == 0) {
		printf("Locate all files\n\n");
		low_to_free_tail_block = 0;
	} else {
		printf("Locate files within the tail of %sB of the parity\n\n", fmt_size(parity_tail, buf, sizeof(buf)));

		struct snapraid_locate_info info;
		state_locate_info(state, parity_tail, &info);

		printf("Current parity size is %sB\n", fmt_size(info.parity_size, buf, sizeof(buf)));
		if (info.tail_block > info.block_max) {
			printf("Specified tail greater than the parity size! Operate on the full parity\n");
		}

		low_to_free_tail_block = info.low_to_free_tail_block;
	}

	msg_progress("Collecting files with offset greater or equal to %" PRIu64 "\n", low_to_free_tail_block * (uint64_t)block_size);

	tommy_list files;
	tommy_list_init(&files);
	for (tommy_node* i = tommy_list_head(&state->disklist); i != 0; i = i->next) {
		struct snapraid_disk* disk = i->data;
		for (tommy_node* j = tommy_list_head(&disk->filelist); j != 0; j = j->next) {
			struct snapraid_file* file = j->data;
			collect_parity_block_file(block_size, disk, file, &files, low_to_free_tail_block);
		}
	}

	printf("\n");

	if (tommy_list_count(&files) == 0) {
		printf("No files located in the specified parity tail.\n");
	} else {
		tommy_list_sort(&files, parity_entry_compare);

		data_off_t total_size_located = 0;

		tommy_list_foreach_arg(&files, add_size_to_sum, &total_size_located);

		printf("Located data in this range: %sB\n", fmt_size(total_size_located, buf, sizeof(buf)));

		printf("\n");

		/*      |<##################################################################72>|####80>| */
		printf("       Offset         Span    Frags\n");

		tommy_list_foreach(&files, dump_entry);
	}

	tommy_list_foreach(&files, free);
}

void state_locate_mark_tail_blocks_for_resync(struct snapraid_state* state, uint64_t parity_tail)
{
	char buf[64];
	uint32_t block_size = state->block_size;
	block_off_t low_to_free_tail_block;

	if (parity_tail == 0) {
		printf("Forcing reallocation of all files\n\n");
		low_to_free_tail_block = 0;
	} else {
		msg_progress("Forcing reallocation of all files within the tail of %sB of the parity\n\n", fmt_size(parity_tail, buf, sizeof(buf)));

		struct snapraid_locate_info info;
		state_locate_info(state, parity_tail, &info);

		printf("Current parity size is %sB\n", fmt_size(info.parity_size, buf, sizeof(buf)));
		if (info.tail_block > info.block_max) {
			printf("Specified tail greater than the parity size! Operate on the full parity\n");
		}

		low_to_free_tail_block = info.low_to_free_tail_block;
	}

	msg_progress("Collecting files with offset greater or equal to %" PRIu64 "\n", low_to_free_tail_block * (uint64_t)block_size);

	tommy_list files;
	tommy_list_init(&files);
	for (tommy_node* i = tommy_list_head(&state->disklist); i != 0; i = i->next) {
		struct snapraid_disk* disk = i->data;
		for (tommy_node* j = tommy_list_head(&disk->filelist); j != 0; j = j->next) {
			struct snapraid_file* file = j->data;
			collect_parity_block_file(block_size, disk, file, &files, low_to_free_tail_block);
		}
	}

	printf("\n");

	if (tommy_list_count(&files) == 0) {
		printf("No files located in the specified parity tail.\n");
	} else {
		/* process all the files partiall or fully overlapping the free zone */
		for (tommy_node* j = tommy_list_head(&files); j != 0; j = j->next) {
			struct snapraid_parity_entry* entry = j->data;
			struct snapraid_file* file = entry->file;
			struct snapraid_disk* disk = entry->disk;

			/* reallocate the full file, not only the part of in the free zone */
			/* this is required because the files blocks have to be in order in */
			/* in the parity file. */
			/* not reallocating the file head will prevent the reallocation of the file tail */
			for (block_off_t f = 0; f < file->blockmax; ++f) {
				block_off_t parity_pos = fs_file2par_find(disk, file, f);

				if (parity_pos == POS_NULL) {
					/* block not yet allocated */
					continue;
				}

				struct snapraid_block* block = fs_file2block_get(file, f);
				if (block_state_get(block) == BLOCK_STATE_BLK) {
					/* convert from BLK to REP */
					block_state_set(block, BLOCK_STATE_REP);
				}
			}
		}
	}

	tommy_list_foreach(&files, free);
}

