/*
 * 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_iterator_h__
#define INCLUDE_iterator_h__

#include "common.h"
#include "git2/index.h"
#include "vector.h"
#include "buffer.h"

#define ITERATOR_PREFIXCMP(ITER, STR, PREFIX)	\
	(((ITER).flags & GIT_ITERATOR_IGNORE_CASE) != 0 ? \
	git__prefixcmp_icase((STR), (PREFIX)) : \
	git__prefixcmp((STR), (PREFIX)))

typedef struct git_iterator git_iterator;

typedef enum {
	GIT_ITERATOR_TYPE_EMPTY = 0,
	GIT_ITERATOR_TYPE_TREE = 1,
	GIT_ITERATOR_TYPE_INDEX = 2,
	GIT_ITERATOR_TYPE_WORKDIR = 3,
	GIT_ITERATOR_TYPE_SPOOLANDSORT = 4
} git_iterator_type_t;

typedef enum {
	GIT_ITERATOR_IGNORE_CASE = (1 << 0), /* ignore_case */
	GIT_ITERATOR_DONT_IGNORE_CASE = (1 << 1), /* force ignore_case off */
} git_iterator_flag_t;

typedef struct {
	int (*current)(git_iterator *, const git_index_entry **);
	int (*at_end)(git_iterator *);
	int (*advance)(git_iterator *, const git_index_entry **);
	int (*seek)(git_iterator *, const char *prefix);
	int (*reset)(git_iterator *, const char *start, const char *end);
	void (*free)(git_iterator *);
} git_iterator_callbacks;

struct git_iterator {
	git_iterator_type_t type;
	git_iterator_callbacks *cb;
	git_repository *repo;
	char *start;
	char *end;
	int (*prefixcomp)(const char *str, const char *prefix);
	unsigned int flags;
};

extern int git_iterator_for_nothing(
	git_iterator **out, git_iterator_flag_t flags);

/* tree iterators will match the ignore_case value from the index of the
 * repository, unless you override with a non-zero flag value
 */
extern int git_iterator_for_tree_range(
	git_iterator **out,
	git_tree *tree,
	git_iterator_flag_t flags,
	const char *start,
	const char *end);

GIT_INLINE(int) git_iterator_for_tree(git_iterator **out, git_tree *tree)
{
	return git_iterator_for_tree_range(out, tree, 0, NULL, NULL);
}

/* index iterators will take the ignore_case value from the index; the
 * ignore_case flags are not used
 */
extern int git_iterator_for_index_range(
	git_iterator **out,
	git_index *index,
	git_iterator_flag_t flags,
	const char *start,
	const char *end);

GIT_INLINE(int) git_iterator_for_index(git_iterator **out, git_index *index)
{
	return git_iterator_for_index_range(out, index, 0, NULL, NULL);
}

/* workdir iterators will match the ignore_case value from the index of the
 * repository, unless you override with a non-zero flag value
 */
extern int git_iterator_for_workdir_range(
	git_iterator **out,
	git_repository *repo,
	git_iterator_flag_t flags,
	const char *start,
	const char *end);

GIT_INLINE(int) git_iterator_for_workdir(git_iterator **out, git_repository *repo)
{
	return git_iterator_for_workdir_range(out, repo, 0, NULL, NULL);
}

extern void git_iterator_free(git_iterator *iter);

/* Spool all iterator values, resort with alternative ignore_case value
 * and replace callbacks with spoolandsort alternates.
 */
extern int git_iterator_spoolandsort_push(git_iterator *iter, bool ignore_case);

/* Restore original callbacks - not required in most circumstances */
extern void git_iterator_spoolandsort_pop(git_iterator *iter);

/* Entry is not guaranteed to be fully populated.  For a tree iterator,
 * we will only populate the mode, oid and path, for example.  For a workdir
 * iterator, we will not populate the oid.
 *
 * You do not need to free the entry.  It is still "owned" by the iterator.
 * Once you call `git_iterator_advance`, then content of the old entry is
 * no longer guaranteed to be valid.
 */
GIT_INLINE(int) git_iterator_current(
	git_iterator *iter, const git_index_entry **entry)
{
	return iter->cb->current(iter, entry);
}

GIT_INLINE(int) git_iterator_at_end(git_iterator *iter)
{
	return iter->cb->at_end(iter);
}

GIT_INLINE(int) git_iterator_advance(
	git_iterator *iter, const git_index_entry **entry)
{
	return iter->cb->advance(iter, entry);
}

GIT_INLINE(int) git_iterator_seek(
	git_iterator *iter, const char *prefix)
{
	return iter->cb->seek(iter, prefix);
}

GIT_INLINE(int) git_iterator_reset(
	git_iterator *iter, const char *start, const char *end)
{
	return iter->cb->reset(iter, start, end);
}

GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter)
{
	return iter->type;
}

GIT_INLINE(git_repository *) git_iterator_owner(git_iterator *iter)
{
	return iter->repo;
}

GIT_INLINE(git_iterator_flag_t) git_iterator_flags(git_iterator *iter)
{
	return iter->flags;
}

GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter)
{
	return ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0);
}

extern int git_iterator_current_tree_entry(
	git_iterator *iter, const git_tree_entry **tree_entry);

extern int git_iterator_current_parent_tree(
	git_iterator *iter, const char *parent_path, const git_tree **tree_ptr);

extern int git_iterator_current_is_ignored(git_iterator *iter);

/**
 * Iterate into a workdir directory.
 *
 * Workdir iterators do not automatically descend into directories (so that
 * when comparing two iterator entries you can detect a newly created
 * directory in the workdir).  As a result, you may get S_ISDIR items from
 * a workdir iterator.  If you wish to iterate over the contents of the
 * directories you encounter, then call this function when you encounter
 * a directory.
 *
 * If there are no files in the directory, this will end up acting like a
 * regular advance and will skip past the directory, so you should be
 * prepared for that case.
 *
 * On non-workdir iterators or if not pointing at a directory, this is a
 * no-op and will not advance the iterator.
 */
extern int git_iterator_advance_into_directory(
	git_iterator *iter, const git_index_entry **entry);

extern int git_iterator_cmp(
	git_iterator *iter, const char *path_prefix);

/**
 * Get the full path of the current item from a workdir iterator.
 * This will return NULL for a non-workdir iterator.
 */
extern int git_iterator_current_workdir_path(
	git_iterator *iter, git_buf **path);


extern git_index *git_iterator_index_get_index(git_iterator *iter);

extern git_iterator_type_t git_iterator_inner_type(git_iterator *iter);

#endif