#include "smartcolsP.h"

static int walk_line(struct libscols_table *tb,
		     struct libscols_line *ln,
		     struct libscols_column *cl,
		     int (*callback)(struct libscols_table *,
			            struct libscols_line *,
				    struct libscols_column *,
				    void *),
		    void *data)
{
	int rc = 0;

	DBG(LINE, ul_debugobj(ln, " wall line"));

	/* we list group children in __scols_print_tree() after tree root node */
	if (is_group_member(ln) && is_last_group_member(ln) && has_group_children(ln))
		tb->ngrpchlds_pending++;

	if (has_groups(tb))
		rc = scols_groups_update_grpset(tb, ln);
	if (rc == 0)
		rc = callback(tb, ln, cl, data);

	/* children */
	if (rc == 0 && has_children(ln)) {
		struct list_head *p;

		DBG(LINE, ul_debugobj(ln, " children walk"));

		list_for_each(p, &ln->ln_branch) {
			struct libscols_line *chld = list_entry(p,
					struct libscols_line, ln_children);

			rc = walk_line(tb, chld, cl, callback, data);
			if (rc)
				break;
		}
	}

	DBG(LINE, ul_debugobj(ln, "<- walk line done [rc=%d]", rc));
	return rc;
}

/* last line in the tree? */
int scols_walk_is_last(struct libscols_table *tb, struct libscols_line *ln)
{
	if (tb->walk_last_done == 0)
		return 0;
	if (tb->ngrpchlds_pending > 0)
		return 0;
	if (has_children(ln))
		return 0;
	if (is_tree_root(ln) && !is_last_tree_root(tb, ln))
		return 0;
	if (is_group_member(ln) && (!is_last_group_member(ln) || has_group_children(ln)))
		return 0;
	if (is_child(ln)) {
		struct libscols_line *parent = ln->parent;

		if (!is_last_child(ln))
			return 0;
		while (parent) {
			if (is_child(parent) && !is_last_child(parent))
				return 0;
			if (!parent->parent)
				break;
			parent = parent->parent;
		}
		if (is_tree_root(parent) && !is_last_tree_root(tb, parent))
			return 0;
	}
	if (is_group_child(ln) && !is_last_group_child(ln))
		return 0;

	DBG(LINE, ul_debugobj(ln, "last in table"));
	return 1;
}

int scols_walk_tree(struct libscols_table *tb,
		    struct libscols_column *cl,
		    int (*callback)(struct libscols_table *,
			            struct libscols_line *,
				    struct libscols_column *,
				    void *),
		    void *data)
{
	int rc = 0;
	struct libscols_line *ln;
	struct libscols_iter itr;

	assert(tb);
	DBG(TAB, ul_debugobj(tb, ">> walk start"));

	/* init */
	tb->ngrpchlds_pending = 0;
	tb->walk_last_tree_root = NULL;
	tb->walk_last_done = 0;

	if (has_groups(tb))
		scols_groups_reset_state(tb);

	/* set pointer to last tree root */
	scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
	while (scols_table_next_line(tb, &itr, &ln) == 0) {
		if (!tb->walk_last_tree_root)
			tb->walk_last_tree_root = ln;
		if (is_child(ln) || is_group_child(ln))
			continue;
		tb->walk_last_tree_root = ln;
	}

	/* walk */
	scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
	while (rc == 0 && scols_table_next_line(tb, &itr, &ln) == 0) {
		if (ln->parent || ln->parent_group)
			continue;

		if (tb->walk_last_tree_root == ln)
			tb->walk_last_done = 1;
		rc = walk_line(tb, ln, cl, callback, data);

		/* walk group's children */
		while (rc == 0 && tb->ngrpchlds_pending) {
			struct libscols_group *gr = scols_grpset_get_printable_children(tb);
			struct list_head *p;

			DBG(LINE, ul_debugobj(ln, " walk group children [pending=%zu]", tb->ngrpchlds_pending));
			if (!gr) {
				DBG(LINE, ul_debugobj(ln, " *** ngrpchlds_pending counter invalid"));
				tb->ngrpchlds_pending = 0;
				break;
			}

			tb->ngrpchlds_pending--;

			list_for_each(p, &gr->gr_children) {
				struct libscols_line *chld =
					list_entry(p, struct libscols_line, ln_children);

				rc = walk_line(tb, chld, cl, callback, data);
				if (rc)
					break;
			}
		}
	}

	tb->ngrpchlds_pending = 0;
	tb->walk_last_done = 0;
	DBG(TAB, ul_debugobj(tb, "<< walk end [rc=%d]", rc));
	return rc;
}
