// SPDX-License-Identifier: GPL-2.0+ OR MIT
#include "erofs/dedupe.h"
#include "liberofs_xxhash.h"
#include <stdlib.h>

struct z_erofs_dedupe_ext_item {
	struct list_head list;
	struct z_erofs_dedupe_ext_item *revoke;
	struct z_erofs_inmem_extent e;
	u64		xxh64;
};

static struct list_head dupl_ext[65536];
static struct z_erofs_dedupe_ext_item *revoke_list;

int z_erofs_dedupe_ext_insert(struct z_erofs_inmem_extent *e,
			      u64 hash)
{
	struct z_erofs_dedupe_ext_item *item;
	struct list_head *p;

	item = malloc(sizeof(struct z_erofs_dedupe_ext_item));
	if (!item)
		return -ENOMEM;
	item->e = *e;
	item->xxh64 = hash;

	p = &dupl_ext[hash & (ARRAY_SIZE(dupl_ext) - 1)];
	list_add_tail(&item->list, p);
	item->revoke = revoke_list;
	revoke_list = item;
	return 0;
}

erofs_off_t z_erofs_dedupe_ext_match(struct erofs_sb_info *sbi,
				     u8 *encoded, unsigned int len,
				     bool raw, u64 *hash)
{
	struct z_erofs_dedupe_ext_item *item;
	struct list_head *p;
	u64 _xxh64;
	char *memb = NULL;
	int ret;

	*hash = _xxh64 = xxh64(encoded, len, 0);
	p = &dupl_ext[_xxh64 & (ARRAY_SIZE(dupl_ext) - 1)];
	list_for_each_entry(item, p, list) {
		if (item->xxh64 == _xxh64 && item->e.plen == len &&
		    item->e.raw == raw) {
			if (!memb) {
				memb = malloc(len);
				if (!memb)
					break;
			}
			ret = erofs_dev_read(sbi, 0, memb, item->e.pstart, len);
			if (ret < 0 || memcmp(memb, encoded, len))
				continue;
			free(memb);
			return item->e.pstart;
		}
	}
	free(memb);
	return 0;
}

void z_erofs_dedupe_ext_commit(bool drop)
{
	if (drop) {
		struct z_erofs_dedupe_ext_item *item, *n;

		for (item = revoke_list; item; item = n) {
			n = item->revoke;
			list_del(&item->list);
			free(item);
		}
	}
	revoke_list = NULL;
}

int z_erofs_dedupe_ext_init(void)
{
	struct list_head *p;

	for (p = dupl_ext; p < dupl_ext + ARRAY_SIZE(dupl_ext); ++p)
		init_list_head(p);
	return 0;
}

void z_erofs_dedupe_ext_exit(void)
{
	struct z_erofs_dedupe_ext_item *item, *n;
	struct list_head *p;

	if (!dupl_ext[0].next)
		return;
	z_erofs_dedupe_commit(true);
	for (p = dupl_ext; p < dupl_ext + ARRAY_SIZE(dupl_ext); ++p) {
		list_for_each_entry_safe(item, n, p, list) {
			list_del(&item->list);
			free(item);
		}
	}
}
