/*
 * 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_git_blame_h__
#define INCLUDE_git_blame_h__

#include "common.h"
#include "oid.h"

/**
 * @file git2/blame.h
 * @brief Specify a file's most recent changes per-line
 * @defgroup git_blame Git blame routines
 * @ingroup Git
 *
 * Producing a "blame" (or "annotated history") decorates individual
 * lines in a file with the commit that introduced that particular line
 * of changes. This can be useful to indicate when and why a particular
 * change was made.
 * @{
 */
GIT_BEGIN_DECL

struct git_signature;
struct git_repository;

/**
 * Flags for indicating option behavior for git_blame APIs.
 */
typedef enum {
	/** Normal blame, the default */
	GIT_BLAME_NORMAL = 0,

	/**
	 * Track lines that have moved within a file (like `git blame -M`).
	 *
	 * This is not yet implemented and reserved for future use.
	 */
	GIT_BLAME_TRACK_COPIES_SAME_FILE = (1<<0),

	/**
	 * Track lines that have moved across files in the same commit
	 * (like `git blame -C`).
	 *
	 * This is not yet implemented and reserved for future use.
	 */
	GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES = (1<<1),

	/**
	 * Track lines that have been copied from another file that exists
	 * in the same commit (like `git blame -CC`).  Implies SAME_FILE.
	 *
	 * This is not yet implemented and reserved for future use.
	 */
	GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES = (1<<2),

	/**
	 * Track lines that have been copied from another file that exists in
	 * *any* commit (like `git blame -CCC`).  Implies SAME_COMMIT_COPIES.
	 *
	 * This is not yet implemented and reserved for future use.
	 */
	GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES = (1<<3),

	/**
	 * Restrict the search of commits to those reachable following only
	 * the first parents.
	 */
	GIT_BLAME_FIRST_PARENT = (1<<4),

	/**
	 * Use mailmap file to map author and committer names and email
	 * addresses to canonical real names and email addresses. The
	 * mailmap will be read from the working directory, or HEAD in a
	 * bare repository.
	 */
	GIT_BLAME_USE_MAILMAP = (1<<5),

	/** Ignore whitespace differences */
	GIT_BLAME_IGNORE_WHITESPACE = (1<<6)
} git_blame_flag_t;

/**
 * Blame progress callback.
 *
 * Called for each blame suspect.
 *
 * - 'suspect' is the id of the suspect commit.
 *
 * Returning a non-zero value will abort the blame.
 */
typedef int (*git_blame_progress_cb)(
	const git_oid *suspect,
	void *payload);

/**
 * Blame options structure
 *
 * Initialize with `GIT_BLAME_OPTIONS_INIT`. Alternatively, you can
 * use `git_blame_options_init`.
 *
 */
typedef struct git_blame_options {
	unsigned int version;

	/** A combination of `git_blame_flag_t` */
	unsigned int flags;

	/**
	 * The lower bound on the number of alphanumeric characters that
	 * must be detected as moving/copying within a file for it to
	 * associate those lines with the parent commit. The default value
	 * is 20.
	 *
	 * This value only takes effect if any of the `GIT_BLAME_TRACK_COPIES_*`
	 * flags are specified.
	 */
	uint16_t min_match_characters;

	/** The id of the newest commit to consider. The default is HEAD. */
	git_oid newest_commit;

	/**
	 * The id of the oldest commit to consider.
	 * The default is the first commit encountered with a NULL parent.
	 */
	git_oid oldest_commit;

	/**
	 * The first line in the file to blame.
	 * The default is 1 (line numbers start with 1).
	 */
	size_t min_line;

	/**
	 * The last line in the file to blame.
	 * The default is the last line of the file.
	 */
	size_t max_line;

	git_blame_progress_cb progress_cb;
	void *payload;
} git_blame_options;

/** Current version for the `git_blame_options` structure */
#define GIT_BLAME_OPTIONS_VERSION 1

/** Static constructor for `git_blame_options` */
#define GIT_BLAME_OPTIONS_INIT {GIT_BLAME_OPTIONS_VERSION}

/**
 * Initialize git_blame_options structure
 *
 * Initializes a `git_blame_options` with default values. Equivalent to creating
 * an instance with GIT_BLAME_OPTIONS_INIT.
 *
 * @param opts The `git_blame_options` struct to initialize.
 * @param version The struct version; pass `GIT_BLAME_OPTIONS_VERSION`.
 * @return Zero on success; -1 on failure.
 */
GIT_EXTERN(int) git_blame_options_init(
	git_blame_options *opts,
	unsigned int version);

/**
 * Structure that represents a blame hunk.
 */
typedef struct git_blame_hunk {
	/**
	 * The number of lines in this hunk.
	 */
	size_t lines_in_hunk;

	/**
	 * The OID of the commit where this line was last changed.
	 */
	git_oid final_commit_id;

	/**
	 * The 1-based line number where this hunk begins, in the final version
	 * of the file.
	 */
	size_t final_start_line_number;

	/**
	 * The author of `final_commit_id`. If `GIT_BLAME_USE_MAILMAP` has been
	 * specified, it will contain the canonical real name and email address.
	 */
	git_signature *final_signature;

	/**
	 * The committer of `final_commit_id`. If `GIT_BLAME_USE_MAILMAP` has
	 * been specified, it will contain the canonical real name and email
	 * address.
	 */
	git_signature *final_committer;

	/**
	 * The OID of the commit where this hunk was found.
	 * This will usually be the same as `final_commit_id`, except when
	 * `GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES` has been specified.
	 */
	git_oid orig_commit_id;

	/**
	 * The path to the file where this hunk originated, as of the commit
	 * specified by `orig_commit_id`.
	 */
	const char *orig_path;

	/**
	 * The 1-based line number where this hunk begins in the file named by
	 * `orig_path` in the commit specified by `orig_commit_id`.
	 */
	size_t orig_start_line_number;

	/**
	 * The author of `orig_commit_id`. If `GIT_BLAME_USE_MAILMAP` has been
	 * specified, it will contain the canonical real name and email address.
	 */
	git_signature *orig_signature;

	/**
	 * The committer of `orig_commit_id`. If `GIT_BLAME_USE_MAILMAP` has
	 * been specified, it will contain the canonical real name and email
	 * address.
	 */
	git_signature *orig_committer;

	/*
	 * The summary of the commit.
	 */
	const char *summary;

	/**
	 * The 1 iff the hunk has been tracked to a boundary commit (the root,
	 * or the commit specified in git_blame_options.oldest_commit)
	 */
	char boundary;
} git_blame_hunk;

/**
 * Structure that represents a line in a blamed file.
 */
typedef struct git_blame_line {
	const char *ptr;
	size_t len;
} git_blame_line;

/** Opaque structure to hold blame results */
typedef struct git_blame git_blame;

/**
 * Gets the number of lines that exist in the blame structure.
 *
 * @param blame The blame structure to query.
 * @return The number of line.
 */
GIT_EXTERN(size_t) git_blame_linecount(git_blame *blame);

/**
 * Gets the number of hunks that exist in the blame structure.
 *
 * @param blame The blame structure to query.
 * @return The number of hunks.
 */
GIT_EXTERN(size_t) git_blame_hunkcount(git_blame *blame);

/**
 * Gets the blame hunk at the given index.
 *
 * @param blame the blame structure to query
 * @param index index of the hunk to retrieve
 * @return the hunk at the given index, or NULL on error
 */
GIT_EXTERN(const git_blame_hunk *) git_blame_hunk_byindex(
	git_blame *blame,
	size_t index);

/**
 * Gets the hunk that relates to the given line number in the newest
 * commit.
 *
 * @param blame the blame structure to query
 * @param lineno the (1-based) line number to find a hunk for
 * @return the hunk that contains the given line, or NULL on error
 */
GIT_EXTERN(const git_blame_hunk *) git_blame_hunk_byline(
	git_blame *blame,
	size_t lineno);

/**
 * Gets the information about the line in the blame.
 *
 * @param blame the blame structure to query
 * @param idx the (1-based) line number
 * @return the blamed line, or NULL on error
 */
GIT_EXTERN(const git_blame_line *) git_blame_line_byindex(
	git_blame *blame,
	size_t idx);

#ifndef GIT_DEPRECATE_HARD
/**
 * Gets the number of hunks that exist in the blame structure.
 *
 * @param blame The blame structure to query.
 * @return The number of hunks.
 */

GIT_EXTERN(uint32_t) git_blame_get_hunk_count(git_blame *blame);

/**
 * Gets the blame hunk at the given index.
 *
 * @param blame the blame structure to query
 * @param index index of the hunk to retrieve
 * @return the hunk at the given index, or NULL on error
 */
GIT_EXTERN(const git_blame_hunk *) git_blame_get_hunk_byindex(
	git_blame *blame,
	uint32_t index);

/**
 * Gets the hunk that relates to the given line number in the newest commit.
 *
 * @param blame the blame structure to query
 * @param lineno the (1-based) line number to find a hunk for
 * @return the hunk that contains the given line, or NULL on error
 */
GIT_EXTERN(const git_blame_hunk *) git_blame_get_hunk_byline(
	git_blame *blame,
	size_t lineno);
#endif

/**
 * Get the blame for a single file in the repository.
 *
 * @param out pointer that will receive the blame object
 * @param repo repository whose history is to be walked
 * @param path path to file to consider
 * @param options options for the blame operation or NULL
 * @return 0 on success, or an error code
 */
GIT_EXTERN(int) git_blame_file(
	git_blame **out,
	git_repository *repo,
	const char *path,
	git_blame_options *options);

/**
 * Get the blame for a single file in the repository, using the specified
 * buffer contents as the uncommitted changes of the file (the working
 * directory contents).
 *
 * @param out pointer that will receive the blame object
 * @param repo repository whose history is to be walked
 * @param path path to file to consider
 * @param contents the uncommitted changes
 * @param contents_len the length of the changes buffer
 * @param options options for the blame operation or NULL
 * @return 0 on success, or an error code
 */
GIT_EXTERN(int) git_blame_file_from_buffer(
	git_blame **out,
	git_repository *repo,
	const char *path,
	const char *contents,
	size_t contents_len,
	git_blame_options *options);

/**
 * Get blame data for a file that has been modified in memory. The `blame`
 * parameter is a pre-calculated blame for the in-odb history of the file.
 * This means that once a file blame is completed (which can be expensive),
 * updating the buffer blame is very fast.
 *
 * Lines that differ between the buffer and the committed version are
 * marked as having a zero OID for their final_commit_id.
 *
 * @param out pointer that will receive the resulting blame data
 * @param base cached blame from the history of the file (usually the output
 *                  from git_blame_file)
 * @param buffer the (possibly) modified contents of the file
 * @param buffer_len number of valid bytes in the buffer
 * @return 0 on success, or an error code. (use git_error_last for information
 *         about the error)
 */
GIT_EXTERN(int) git_blame_buffer(
	git_blame **out,
	git_blame *base,
	const char *buffer,
	size_t buffer_len);

/**
 * Free memory allocated by git_blame_file or git_blame_buffer.
 *
 * @param blame the blame structure to free
 */
GIT_EXTERN(void) git_blame_free(git_blame *blame);

/** @} */
GIT_END_DECL
#endif

