/*  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/>
 */

#pragma once

#include "knot/journal/journal_basic.h"

typedef struct {
	uint32_t first_serial;
	uint32_t serial_to;
	uint32_t flushed_upto;
	uint32_t merged_serial;
	uint32_t changeset_count;
	uint32_t flags; // a bitmap of flags, see enum below
	bool _new_zone; // private: if there were no metadata at all previously
} journal_metadata_t;

enum journal_metadata_flags {
	JOURNAL_LAST_FLUSHED_VALID   = (1 << 0), // deprecated
	JOURNAL_SERIAL_TO_VALID      = (1 << 1),
	JOURNAL_MERGED_SERIAL_VALID  = (1 << 2),
};

typedef int (*journals_walk_cb_t)(const knot_dname_t *zone, void *ctx);

/*!
 * \brief Update the computation of DB resources used by each zone.
 *
 * Because the amount of used space is bigger than sum of changesets' serialized_sizes,
 * journal uses a complicated way to compute each zone's used space: there is a metadata
 * showing always the previously-inserting zone. Before the next insert, it is computed
 * how the total usage of the DB changed during the previous insert (or delete), and the
 * usage increase (or decrease) is accounted on the bill of the previous inserter.
 *
 * \param txn            Journal DB transaction.
 * \param new_inserter   Name of the zone that is going to insert now. Might be NULL if no insert nor delete will be done.
 */
void update_last_inserter(knot_lmdb_txn_t *txn, const knot_dname_t *new_inserter);

/* \brief Return the journal database usage by given zone. */
uint64_t journal_get_occupied(knot_lmdb_txn_t *txn, const knot_dname_t *zone);

/*!
 * \brief Load the metadata from DB into structure.
 *
 * \param txn    Journal DB transaction.
 * \param zone   Zone name.
 * \param md     Output: metadata structure.
 */
void journal_load_metadata(knot_lmdb_txn_t *txn, const knot_dname_t *zone, journal_metadata_t *md);

/*!
 * \brief Store the metadata from structure into DB.
 *
 * \param txn    Journal DB transaction.
 * \param zone   Zone name.
 * \param md     Metadata structure.
 */
void journal_store_metadata(knot_lmdb_txn_t *txn, const knot_dname_t *zone, const journal_metadata_t *md);

/*!
 * \brief Update metadata according to what was deleted.
 *
 * \param md              Metadata structure to be updated.
 * \param deleted_upto    Serial-to of the last deleted changeset.
 * \param deleted_count   Number of deleted changesets.
 */
void journal_metadata_after_delete(journal_metadata_t *md, uint32_t deleted_upto,
                                   size_t deleted_count);

/*!
 * \brief Update metadata according to what was merged.
 *
 * \param md                   Metadata structure to be updated.
 * \param merged_zij           True if it was a merge into zone-in-journal.
 * \param merged_serial        Serial-from of the merged changeset (ignored if 'merged_zij').
 * \param merged_serial_to     Serial-to of the merged changeset.
 * \param original_serial_to   Previous serial-to of the merged changeset before the merge.
 */
void journal_metadata_after_merge(journal_metadata_t *md, bool merged_zij, uint32_t merged_serial,
                                  uint32_t merged_serial_to, uint32_t original_serial_to);

/*!
 * \brief Update metadata according to what was inserted.
 *
 * \param md          Metadata structure to be updated.
 * \param serial      Serial-from of the inserted changeset.
 * \param serial_to   Serial-to of the inserted changeset.
 */
void journal_metadata_after_insert(journal_metadata_t *md, uint32_t serial, uint32_t serial_to);

/*!
 * \brief Update metadata according to inserted extra changeset.
 *
 * \param md          Metadata structure to be updated.
 * \param serial      Serial-from of the inserted changeset.
 * \param serial_to   Serial-to of the inserted changeset.
 */
void journal_metadata_after_extra(journal_metadata_t *md, uint32_t serial, uint32_t serial_to);

/*!
 * \brief Delete all zone records in a txn that will later write to the same zone.
 *
 * \note The difference against journal_del_zone(), which purges even metadata, incl "occupied".
 * \note This preserves keeping track of space occupied/freed by this zone.
 */
void journal_del_zone_txn(knot_lmdb_txn_t *txn, const knot_dname_t *zone);

/*!
 * \brief Completely delete all journal records belonging to this zone, including metadata.
 *
 * \param j   Journal to be scraped.
 * \param check_existence   Don't operate if the journal seems not to exist.
 *
 * \return KNOT_E*
 */
int journal_scrape_with_md(zone_journal_t j, bool check_existence);

/*!
 * \brief Copy all records related to this zone from one journal DB to another.
 *
 * \param from   DB to copy from.
 * \param to     DB to copy to.
 * \param zone   Journal zone.
 *
 * \return KNOT_E*
 */
int journal_copy_with_md(knot_lmdb_db_t *from, knot_lmdb_db_t *to, const knot_dname_t *zone);

/*!
 * \brief Update the metadata stored in journal DB after a zone flush.
 *
 * \param j   Journal to be notified about flush.
 *
 * \return KNOT_E*
 */
int journal_set_flushed(zone_journal_t j);

/*!
 * \brief Obtain information about the zone's journal from the DB (mostly metadata).
 *
 * \param j                Zone journal.
 * \param exists           Output: bool if the zone exists in the journal.
 * \param first_serial     Optional output: serial-from of the first changeset in journal.
 * \param has_zij          Optional output: bool if there is zone-in-journal.
 * \param serial_to        Optional output: serial.to of the last changeset in journal.
 * \param has_merged       Optional output: bool if there is a special (non zone-in-journal) merged changeset.
 * \param merged_serial    Optional output: serial-from of the merged changeset.
 * \param occupied         Optional output: DB space occupied by this zones.
 * \param occupied_total   Optional output: DB space occupied in total by all zones.
 *
 * \return KNOT_E*
 */
int journal_info(zone_journal_t j, bool *exists, uint32_t *first_serial, bool *has_zij,
                 uint32_t *serial_to, bool *has_merged, uint32_t *merged_serial,
                 uint64_t *occupied, uint64_t *occupied_total);

/*! \brief Return true if this zone exists in journal DB. */
inline static bool journal_is_existing(zone_journal_t j) {
	bool ex = false;
	(void)journal_info(j, &ex, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
	return ex;
}

/*!
 * \brief Call a function for each zone being in the journal DB.
 *
 * \param db    Journal database.
 * \param cb    Callback to be called for each zone-name found.
 * \param ctx   Arbitrary context to be passed to the callback.
 *
 * \return An error code from either journal operations or from the callback.
 */
int journals_walk(knot_lmdb_db_t *db, journals_walk_cb_t cb, void *ctx);
