/*  Copyright (C) CZ.NIC, z.s.p.o. and contributors
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  For more information, see <https://www.knot-dns.cz/>
 */

#include <assert.h>
#include <stdlib.h>

#include "knot/journal/journal_metadata.h"
#include "knot/zone/zonedb.h"
#include "libknot/packet/wire.h"
#include "contrib/mempattern.h"
#include "contrib/ucw/mempool.h"

/*! \brief Discard zone in zone database. */
static void discard_zone(zone_t *zone, bool abort_txn)
{
	// Don't flush if removed zone (no previous configuration available).
	if (conf_rawid_exists(conf(), C_ZONE, zone->name, knot_dname_size(zone->name)) ||
	    catalog_has_member(conf()->catalog, zone->name)) {

		// Flush if bootstrapped or if the journal doesn't exist.
		if (!zone->zonefile.exists ||
		    !zone_journal_same_serial(zone, zone_contents_serial(zone->contents))) {
			zone_flush_journal(conf(), zone, false);
		}
	}

	if (abort_txn) {
		zone_control_clear(zone);
	}
	zone_free(&zone);
}

knot_zonedb_t *knot_zonedb_new(void)
{
	knot_zonedb_t *db = calloc(1, sizeof(knot_zonedb_t));
	if (db == NULL) {
		return NULL;
	}

	db->trie = trie_create(NULL);
	if (db->trie == NULL) {
		free(db);
		return NULL;
	}

	return db;
}

knot_zonedb_t *knot_zonedb_cow(knot_zonedb_t *from)
{
	knot_zonedb_t *to = calloc(1, sizeof(*to));
	if (to == NULL) {
		return to;
	}
	from->cow = trie_cow(from->trie, NULL, NULL);
	to->cow = from->cow;
	to->trie = trie_cow_new(to->cow);
	if (to->trie == NULL) {
		free(to);
		to = NULL;
	}
	return to;
}

int knot_zonedb_insert(knot_zonedb_t *db, zone_t *zone)
{
	if (db == NULL || zone == NULL) {
		return KNOT_EINVAL;
	}

	assert(zone->name);
	knot_dname_storage_t lf_storage;
	uint8_t *lf = knot_dname_lf(zone->name, lf_storage);
	assert(lf);

	trie_val_t *rval;
	if (db->cow != NULL) {
		rval = trie_get_cow(db->cow, lf + 1, *lf);
	} else {
		rval = trie_get_ins(db->trie, lf + 1, *lf);
	}
	if (rval == NULL) {
		return KNOT_ENOMEM;
	}
	*rval = zone;

	return KNOT_EOK;
}

int knot_zonedb_del(knot_zonedb_t *db, const knot_dname_t *zone_name)
{
	if (db == NULL || zone_name == NULL) {
		return KNOT_EINVAL;
	}

	knot_dname_storage_t lf_storage;
	uint8_t *lf = knot_dname_lf(zone_name, lf_storage);
	assert(lf);

	trie_val_t *rval = trie_get_try(db->trie, lf + 1, *lf);
	if (rval == NULL) {
		return KNOT_ENOENT;
	}

	if (db->cow != NULL) {
		return trie_del_cow(db->cow, lf + 1, *lf, NULL);
	} else {
		return trie_del(db->trie, lf + 1, *lf, NULL);
	}
}

zone_t *knot_zonedb_find(knot_zonedb_t *db, const knot_dname_t *zone_name)
{
	if (db == NULL) {
		return NULL;
	}

	knot_dname_storage_t lf_storage;
	uint8_t *lf = knot_dname_lf(zone_name, lf_storage);
	assert(lf);

	trie_val_t *val = trie_get_try(db->trie, lf + 1, *lf);
	if (val == NULL) {
		return NULL;
	}

	return *val;
}

zone_t **knot_zonedb_find_ptr(knot_zonedb_t *db, const knot_dname_t *zone_name)
{
	if (db == NULL) {
		return NULL;
	}

	knot_dname_storage_t lf_storage;
	uint8_t *lf = knot_dname_lf(zone_name, lf_storage);
	assert(lf);

	trie_val_t *val = trie_get_try(db->trie, lf + 1, *lf);
	if (val == NULL) {
		return NULL;
	}

	return (zone_t **)val;
}

zone_t *knot_zonedb_find_suffix(knot_zonedb_t *db, const knot_dname_t *zone_name)
{
	if (db == NULL || zone_name == NULL) {
		return NULL;
	}

	while (true) {
		knot_dname_storage_t lf_storage;
		uint8_t *lf = knot_dname_lf(zone_name, lf_storage);
		assert(lf);

		trie_val_t *val = trie_get_try(db->trie, lf + 1, *lf);
		if (val != NULL) {
			return *val;
		} else if (zone_name[0] == 0) {
			return NULL;
		}

		zone_name = knot_dname_next_label(zone_name);
	}
}

size_t knot_zonedb_size(const knot_zonedb_t *db)
{
	if (db == NULL) {
		return 0;
	}

	return trie_weight(db->trie);
}

void knot_zonedb_free(knot_zonedb_t **db)
{
	if (db == NULL || *db == NULL) {
		return;
	}

	trie_free((*db)->trie);
	free(*db);
	*db = NULL;
}

void knot_zonedb_deep_free(knot_zonedb_t **db, bool abort_txn)
{
	if (db == NULL || *db == NULL) {
		return;
	}

	knot_zonedb_foreach(*db, discard_zone, abort_txn);
	knot_zonedb_free(db);
}
