/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */
#ifndef INCLUDE_sys_git_refdb_backend_h__
#define INCLUDE_sys_git_refdb_backend_h__

#include "git2/common.h"
#include "git2/types.h"
#include "git2/oid.h"

/**
 * @file git2/sys/refdb_backend.h
 * @brief Custom reference database backends for refs storage
 * @defgroup git_refdb_backend Custom reference database backends for refs storage
 * @ingroup Git
 * @{
 */
GIT_BEGIN_DECL


/**
 * Every backend's iterator must have a pointer to itself as the first
 * element, so the API can talk to it. You'd define your iterator as
 *
 *     struct my_iterator {
 *             git_reference_iterator parent;
 *             ...
 *     }
 *
 * and assign `iter->parent.backend` to your `git_refdb_backend`.
 */
struct git_reference_iterator {
	git_refdb *db;

	/**
	 * Return the current reference and advance the iterator.
	 */
	int GIT_CALLBACK(next)(
		git_reference **ref,
		git_reference_iterator *iter);

	/**
	 * Return the name of the current reference and advance the iterator
	 */
	int GIT_CALLBACK(next_name)(
		const char **ref_name,
		git_reference_iterator *iter);

	/**
	 * Free the iterator
	 */
	void GIT_CALLBACK(free)(
		git_reference_iterator *iter);
};

/** An instance for a custom backend */
struct git_refdb_backend {
	unsigned int version; /**< The backend API version */

	/**
	 * Queries the refdb backend for the existence of a reference.
	 *
	 * A refdb implementation must provide this function.
	 *
	 * @param exists The implementation shall set this to `0` if a ref does
	 *             not exist, otherwise to `1`.
	 * @param ref_name The reference's name that should be checked for
	 *               existence.
	 * @return `0` on success, a negative error value code.
	 */
	int GIT_CALLBACK(exists)(
		int *exists,
		git_refdb_backend *backend,
		const char *ref_name);

	/**
	 * Queries the refdb backend for a given reference.
	 *
	 * A refdb implementation must provide this function.
	 *
	 * @param out The implementation shall set this to the allocated
	 *          reference, if it could be found, otherwise to `NULL`.
	 * @param ref_name The reference's name that should be checked for
	 *               existence.
	 * @return `0` on success, `GIT_ENOTFOUND` if the reference does
	 *         exist, otherwise a negative error code.
	 */
	int GIT_CALLBACK(lookup)(
		git_reference **out,
		git_refdb_backend *backend,
		const char *ref_name);

	/**
	 * Allocate an iterator object for the backend.
	 *
	 * A refdb implementation must provide this function.
	 *
	 * @param out The implementation shall set this to the allocated
	 *          reference iterator. A custom structure may be used with an
	 *          embedded `git_reference_iterator` structure. Both `next`
	 *          and `next_name` functions of `git_reference_iterator` need
	 *          to be populated.
	 * @param glob A pattern to filter references by. If given, the iterator
	 *           shall only return references that match the glob when
	 *           passed to `wildmatch`.
	 * @return `0` on success, otherwise a negative error code.
	 */
	int GIT_CALLBACK(iterator)(
		git_reference_iterator **iter,
		struct git_refdb_backend *backend,
		const char *glob);

	/**
	 * Writes the given reference to the refdb.
	 *
	 * A refdb implementation must provide this function.
	 *
	 * @param ref The reference to persist. May either be a symbolic or
	 *          direct reference.
	 * @param force Whether to write the reference if a reference with the
	 *            same name already exists.
	 * @param who The person updating the reference. Shall be used to create
	 *          a reflog entry.
	 * @param message The message detailing what kind of reference update is
	 *              performed. Shall be used to create a reflog entry.
	 * @param old If not `NULL` and `force` is not set, then the
	 *          implementation needs to ensure that the reference is currently at
	 *          the given OID before writing the new value. If both `old`
	 *          and `old_target` are `NULL`, then the reference should not
	 *          exist at the point of writing.
	 * @param old_target If not `NULL` and `force` is not set, then the
	 *                 implementation needs to ensure that the symbolic
	 *                 reference is currently at the given target before
	 *                 writing the new value. If both `old` and
	 *                 `old_target` are `NULL`, then the reference should
	 *                 not exist at the point of writing.
	 * @return `0` on success, otherwise a negative error code.
	 */
	int GIT_CALLBACK(write)(git_refdb_backend *backend,
		     const git_reference *ref, int force,
		     const git_signature *who, const char *message,
		     const git_oid *old, const char *old_target);

	/**
	 * Rename a reference in the refdb.
	 *
	 * A refdb implementation must provide this function.
	 *
	 * @param out The implementation shall set this to the newly created
	 *          reference or `NULL` on error.
	 * @param old_name The current name of the reference that is to be renamed.
	 * @param new_name The new name that the old reference shall be renamed to.
	 * @param force Whether to write the reference if a reference with the
	 *            target name already exists.
	 * @param who The person updating the reference. Shall be used to create
	 *          a reflog entry.
	 * @param message The message detailing what kind of reference update is
	 *              performed. Shall be used to create a reflog entry.
	 * @return `0` on success, otherwise a negative error code.
	 */
	int GIT_CALLBACK(rename)(
		git_reference **out, git_refdb_backend *backend,
		const char *old_name, const char *new_name, int force,
		const git_signature *who, const char *message);

	/**
	 * Deletes the given reference from the refdb.
	 *
	 * If it exists, its reflog should be deleted as well.
	 *
	 * A refdb implementation must provide this function.
	 *
	 * @param ref_name The name of the reference name that shall be deleted.
	 * @param old_id If not `NULL` and `force` is not set, then the
	 *             implementation needs to ensure that the reference is currently at
	 *             the given OID before writing the new value.
	 * @param old_target If not `NULL` and `force` is not set, then the
	 *                 implementation needs to ensure that the symbolic
	 *                 reference is currently at the given target before
	 *                 writing the new value.
	 * @return `0` on success, otherwise a negative error code.
	 */
	int GIT_CALLBACK(del)(git_refdb_backend *backend, const char *ref_name, const git_oid *old_id, const char *old_target);

	/**
	 * Suggests that the given refdb compress or optimize its references.
	 *
	 * This mechanism is implementation specific. For on-disk reference
	 * databases, this may pack all loose references.
	 *
	 * A refdb implementation may provide this function; if it is not
	 * provided, nothing will be done.
	 *
	 * @return `0` on success a negative error code otherwise
	 */
	int GIT_CALLBACK(compress)(git_refdb_backend *backend);

	/**
	 * Query whether a particular reference has a log (may be empty)
	 *
	 * Shall return 1 if it has a reflog, 0 it it doesn't and negative in
	 * case an error occurred.
	 *
	 * A refdb implementation must provide this function.
	 *
	 * @return `0` on success, `1` if the reflog for the given reference
	 *         exists, a negative error code otherwise
	 */
	int GIT_CALLBACK(has_log)(git_refdb_backend *backend, const char *refname);

	/**
	 * Make sure a particular reference will have a reflog which
	 * will be appended to on writes.
	 *
	 * A refdb implementation must provide this function.
	 *
	 * @return `0` on success, a negative error code otherwise
	 */
	int GIT_CALLBACK(ensure_log)(git_refdb_backend *backend, const char *refname);

	/**
	 * Frees any resources held by the refdb (including the `git_refdb_backend`
	 * itself).
	 *
	 * A refdb backend implementation must provide this function.
	 */
	void GIT_CALLBACK(free)(git_refdb_backend *backend);

	/**
	 * Read the reflog for the given reference name.
	 *
	 * A refdb implementation must provide this function.
	 *
	 * @return `0` on success, a negative error code otherwise
	 */
	int GIT_CALLBACK(reflog_read)(git_reflog **out, git_refdb_backend *backend, const char *name);

	/**
	 * Write a reflog to disk.
	 *
	 * A refdb implementation must provide this function.
	 *
	 * @param reflog The complete reference log for a given reference. Note
	 *             that this may contain entries that have already been
	 *             written to disk.
	 * @return `0` on success, a negative error code otherwise
	 */
	int GIT_CALLBACK(reflog_write)(git_refdb_backend *backend, git_reflog *reflog);

	/**
	 * Rename a reflog.
	 *
	 * A refdb implementation must provide this function.
	 *
	 * @param old_name The name of old reference whose reflog shall be renamed from.
	 * @param new_name The name of new reference whose reflog shall be renamed to.
	 * @return `0` on success, a negative error code otherwise
	 */
	int GIT_CALLBACK(reflog_rename)(git_refdb_backend *_backend, const char *old_name, const char *new_name);

	/**
	 * Remove a reflog.
	 *
	 * A refdb implementation must provide this function.
	 *
	 * @param name The name of the reference whose reflog shall be deleted.
	 * @return `0` on success, a negative error code otherwise
	 */
	int GIT_CALLBACK(reflog_delete)(git_refdb_backend *backend, const char *name);

	/**
	 * Lock a reference.
	 *
	 * A refdb implementation may provide this function; if it is not
	 * provided, the transaction API will fail to work.
	 *
	 * @param payload_out Opaque parameter that will be passed verbosely to
	 *                  `unlock`.
	 * @param refname Reference that shall be locked.
	 * @return `0` on success, a negative error code otherwise
	 */
	int GIT_CALLBACK(lock)(void **payload_out, git_refdb_backend *backend, const char *refname);

	/**
	 * Unlock a reference.
	 *
	 * Only one of target or symbolic_target will be set.
	 * `success` will be true if the reference should be update, false if
	 * the lock must be discarded.
	 *
	 * A refdb implementation must provide this function if a `lock`
	 * implementation is provided.
	 *
	 * @param payload The payload returned by `lock`.
	 * @param success `1` if a reference should be updated, `2` if
	 *              a reference should be deleted, `0` if the lock must be
	 *              discarded.
	 * @param update_reflog `1` in case the reflog should be updated, `0`
	 *                    otherwise.
	 * @param ref The reference which should be unlocked.
	 * @param who The person updating the reference. Shall be used to create
	 *          a reflog entry in case `update_reflog` is set.
	 * @param message The message detailing what kind of reference update is
	 *              performed. Shall be used to create a reflog entry in
	 *              case `update_reflog` is set.
	 * @return `0` on success, a negative error code otherwise
	 */
	int GIT_CALLBACK(unlock)(git_refdb_backend *backend, void *payload, int success, int update_reflog,
		      const git_reference *ref, const git_signature *sig, const char *message);
};

/** Current version for the `git_refdb_backend_options` structure */
#define GIT_REFDB_BACKEND_VERSION 1

/** Static constructor for `git_refdb_backend_options` */
#define GIT_REFDB_BACKEND_INIT {GIT_REFDB_BACKEND_VERSION}

/**
 * Initializes a `git_refdb_backend` with default values. Equivalent to
 * creating an instance with GIT_REFDB_BACKEND_INIT.
 *
 * @param backend the `git_refdb_backend` struct to initialize
 * @param version Version of struct; pass `GIT_REFDB_BACKEND_VERSION`
 * @return Zero on success; -1 on failure.
 */
GIT_EXTERN(int) git_refdb_init_backend(
	git_refdb_backend *backend,
	unsigned int version);

/**
 * Constructors for default filesystem-based refdb backend
 *
 * Under normal usage, this is called for you when the repository is
 * opened / created, but you can use this to explicitly construct a
 * filesystem refdb backend for a repository.
 *
 * @param backend_out Output pointer to the git_refdb_backend object
 * @param repo Git repository to access
 * @return 0 on success, <0 error code on failure
 */
GIT_EXTERN(int) git_refdb_backend_fs(
	git_refdb_backend **backend_out,
	git_repository *repo);

/**
 * Sets the custom backend to an existing reference DB
 *
 * The `git_refdb` will take ownership of the `git_refdb_backend` so you
 * should NOT free it after calling this function.
 *
 * @param refdb database to add the backend to
 * @param backend pointer to a git_refdb_backend instance
 * @return 0 on success; error code otherwise
 */
GIT_EXTERN(int) git_refdb_set_backend(
	git_refdb *refdb,
	git_refdb_backend *backend);

/** @} */
GIT_END_DECL

#endif
