/*
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Contributor(s): Joseph Eagar, Geoffrey Bantle, Levi Schooley.
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file blender/bmesh/intern/bmesh_walkers_impl.c
 *  \ingroup bmesh
 *
 * BMesh Walker Code.
 */

#include <string.h>

#include "BLI_utildefines.h"

#include "BKE_customdata.h"

#include "bmesh.h"
#include "intern/bmesh_private.h"
#include "intern/bmesh_walkers_private.h"

/* pop into stack memory (common operation) */
#define BMW_state_remove_r(walker, owalk) { \
	memcpy(owalk, BMW_current_state(walker), sizeof(*(owalk))); \
	BMW_state_remove(walker); \
} (void)0

/** \name Mask Flag Checks
 * \{ */

static bool bmw_mask_check_vert(BMWalker *walker, BMVert *v)
{
	if ((walker->flag & BMW_FLAG_TEST_HIDDEN) && BM_elem_flag_test(v, BM_ELEM_HIDDEN)) {
		return false;
	}
	else if (walker->mask_vert && !BMO_elem_flag_test(walker->bm, v, walker->mask_vert)) {
		return false;
	}
	else {
		return true;
	}
}

static bool bmw_mask_check_edge(BMWalker *walker, BMEdge *e)
{
	if ((walker->flag & BMW_FLAG_TEST_HIDDEN) && BM_elem_flag_test(e, BM_ELEM_HIDDEN)) {
		return false;
	}
	else if (walker->mask_edge && !BMO_elem_flag_test(walker->bm, e, walker->mask_edge)) {
		return false;
	}
	else {
		return true;
	}
}

static bool bmw_mask_check_face(BMWalker *walker, BMFace *f)
{
	if ((walker->flag & BMW_FLAG_TEST_HIDDEN) && BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
		return false;
	}
	else if (walker->mask_face && !BMO_elem_flag_test(walker->bm, f, walker->mask_face)) {
		return false;
	}
	else {
		return true;
	}
}

/** \} */


/** \name Shell Walker
 * \{
 *
 * Starts at a vertex on the mesh and walks over the 'shell' it belongs
 * to via visiting connected edges.
 *
 * takes an edge or vertex as an argument, and spits out edges,
 * restrict flag acts on the edges as well.
 *
 * \todo Add restriction flag/callback for wire edges.
 */
static void bmw_VertShellWalker_visitEdge(BMWalker *walker, BMEdge *e)
{
	BMwShellWalker *shellWalk = NULL;

	if (BLI_gset_haskey(walker->visit_set, e)) {
		return;
	}

	if (!bmw_mask_check_edge(walker, e)) {
		return;
	}

	shellWalk = BMW_state_add(walker);
	shellWalk->curedge = e;
	BLI_gset_insert(walker->visit_set, e);
}

static void bmw_VertShellWalker_begin(BMWalker *walker, void *data)
{
	BMIter eiter;
	BMHeader *h = data;
	BMEdge *e;
	BMVert *v;

	if (UNLIKELY(h == NULL)) {
		return;
	}

	switch (h->htype) {
		case BM_VERT:
		{
			/* starting the walk at a vert, add all the edges
			 * to the worklist */
			v = (BMVert *)h;
			BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
				bmw_VertShellWalker_visitEdge(walker, e);
			}
			break;
		}

		case BM_EDGE:
		{
			/* starting the walk at an edge, add the single edge
			 * to the worklist */
			e = (BMEdge *)h;
			bmw_VertShellWalker_visitEdge(walker, e);
			break;
		}
		default:
			BLI_assert(0);
	}
}

static void *bmw_VertShellWalker_yield(BMWalker *walker)
{
	BMwShellWalker *shellWalk = BMW_current_state(walker);
	return shellWalk->curedge;
}

static void *bmw_VertShellWalker_step(BMWalker *walker)
{
	BMwShellWalker *swalk, owalk;
	BMEdge *e, *e2;
	BMVert *v;
	BMIter iter;
	int i;

	BMW_state_remove_r(walker, &owalk);
	swalk = &owalk;

	e = swalk->curedge;

	for (i = 0; i < 2; i++) {
		v = i ? e->v2 : e->v1;
		BM_ITER_ELEM (e2, &iter, v, BM_EDGES_OF_VERT) {
			bmw_VertShellWalker_visitEdge(walker, e2);
		}
	}

	return e;
}

#if 0
static void *bmw_VertShellWalker_step(BMWalker *walker)
{
	BMEdge *curedge, *next = NULL;
	BMVert *v_old = NULL;
	bool restrictpass = true;
	BMwShellWalker shellWalk = *((BMwShellWalker *)BMW_current_state(walker));
	
	if (!BLI_gset_haskey(walker->visit_set, shellWalk.base)) {
		BLI_gset_insert(walker->visit_set, shellWalk.base);
	}

	BMW_state_remove(walker);


	/* find the next edge whose other vertex has not been visite */
	curedge = shellWalk.curedge;
	do {
		if (!BLI_gset_haskey(walker->visit_set, curedge)) {
			if (!walker->restrictflag ||
			    (walker->restrictflag && BMO_elem_flag_test(walker->bm, curedge, walker->restrictflag)))
			{
				BMwShellWalker *newstate;

				v_old = BM_edge_other_vert(curedge, shellWalk.base);
				
				/* push a new state onto the stac */
				newState = BMW_state_add(walker);
				BLI_gset_insert(walker->visit_set, curedge);
				
				/* populate the new stat */

				newState->base = v_old;
				newState->curedge = curedge;
			}
		}
	} while ((curedge = bmesh_disk_edge_next(curedge, shellWalk.base)) != shellWalk.curedge);
	
	return shellWalk.curedge;
}
#endif

/** \} */


/** \name FaceShell Walker
 * \{
 *
 * Starts at an edge on the mesh and walks over the 'shell' it belongs
 * to via visiting connected faces.
 */
static void bmw_FaceShellWalker_visitEdge(BMWalker *walker, BMEdge *e)
{
	BMwShellWalker *shellWalk = NULL;

	if (BLI_gset_haskey(walker->visit_set, e)) {
		return;
	}

	if (!bmw_mask_check_edge(walker, e)) {
		return;
	}

	shellWalk = BMW_state_add(walker);
	shellWalk->curedge = e;
	BLI_gset_insert(walker->visit_set, e);
}

static void bmw_FaceShellWalker_begin(BMWalker *walker, void *data)
{
	BMEdge *e = data;
	bmw_FaceShellWalker_visitEdge(walker, e);
}

static void *bmw_FaceShellWalker_yield(BMWalker *walker)
{
	BMwShellWalker *shellWalk = BMW_current_state(walker);
	return shellWalk->curedge;
}

static void *bmw_FaceShellWalker_step(BMWalker *walker)
{
	BMwShellWalker *swalk, owalk;
	BMEdge *e, *e2;
	BMIter iter;

	BMW_state_remove_r(walker, &owalk);
	swalk = &owalk;

	e = swalk->curedge;

	if (e->l) {
		BMLoop *l_iter, *l_first;

		l_iter = l_first = e->l;
		do {
			BM_ITER_ELEM (e2, &iter, l_iter->f, BM_EDGES_OF_FACE) {
				if (e2 != e) {
					bmw_FaceShellWalker_visitEdge(walker, e2);
				}
			}
		} while ((l_iter = l_iter->radial_next) != l_first);
	}

	return e;
}
/** \} */


/** \name Connected Vertex Walker
 * \{
 *
 * Similar to shell walker, but visits vertices instead of edges.
 *
 * Walk from a vertex to all connected vertices.
 *
 */
static void bmw_ConnectedVertexWalker_visitVertex(BMWalker *walker, BMVert *v)
{
	BMwConnectedVertexWalker *vwalk;

	if (BLI_gset_haskey(walker->visit_set, v)) {
		/* already visited */
		return;
	}

	if (!bmw_mask_check_vert(walker, v)) {
		/* not flagged for walk */
		return;
	}

	vwalk = BMW_state_add(walker);
	vwalk->curvert = v;
	BLI_gset_insert(walker->visit_set, v);
}

static void bmw_ConnectedVertexWalker_begin(BMWalker *walker, void *data)
{
	BMVert *v = data;
	bmw_ConnectedVertexWalker_visitVertex(walker, v);
}

static void *bmw_ConnectedVertexWalker_yield(BMWalker *walker)
{
	BMwConnectedVertexWalker *vwalk = BMW_current_state(walker);
	return vwalk->curvert;
}

static void *bmw_ConnectedVertexWalker_step(BMWalker *walker)
{
	BMwConnectedVertexWalker *vwalk, owalk;
	BMVert *v, *v2;
	BMEdge *e;
	BMIter iter;

	BMW_state_remove_r(walker, &owalk);
	vwalk = &owalk;

	v = vwalk->curvert;

	BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
		v2 = BM_edge_other_vert(e, v);
		if (!BLI_gset_haskey(walker->visit_set, v2)) {
			bmw_ConnectedVertexWalker_visitVertex(walker, v2);
		}
	}

	return v;
}

/** \} */


/** \name Island Boundary Walker
 * \{
 *
 * Starts at a edge on the mesh and walks over the boundary of an island it belongs to.
 *
 * \note that this doesn't work on non-manifold geometry.
 * it might be better to rewrite this to extract
 * boundary info from the island walker, rather then directly walking
 * over the boundary.  raises an error if it encounters nonmanifold geometry.
 *
 * \todo Add restriction flag/callback for wire edges.
 */
static void bmw_IslandboundWalker_begin(BMWalker *walker, void *data)
{
	BMLoop *l = data;
	BMwIslandboundWalker *iwalk = NULL;

	iwalk = BMW_state_add(walker);

	iwalk->base = iwalk->curloop = l;
	iwalk->lastv = l->v;

	BLI_gset_insert(walker->visit_set, data);

}

static void *bmw_IslandboundWalker_yield(BMWalker *walker)
{
	BMwIslandboundWalker *iwalk = BMW_current_state(walker);

	return iwalk->curloop;
}

static void *bmw_IslandboundWalker_step(BMWalker *walker)
{
	BMwIslandboundWalker *iwalk, owalk;
	BMVert *v;
	BMEdge *e;
	BMFace *f;
	BMLoop *l;
	/* int found = 0; */

	memcpy(&owalk, BMW_current_state(walker), sizeof(owalk));
	/* normally we'd remove here, but delay until after error checking */
	iwalk = &owalk;

	l = iwalk->curloop;
	e = l->e;

	v = BM_edge_other_vert(e, iwalk->lastv);

	if (!BM_vert_is_manifold(v)) {
		BMW_reset(walker);
		BMO_error_raise(walker->bm, NULL, BMERR_WALKER_FAILED,
		                "Non-manifold vert while searching region boundary");
		return NULL;
	}
	
	/* pop off current state */
	BMW_state_remove(walker);
	
	f = l->f;
	
	while (1) {
		l = BM_loop_other_edge_loop(l, v);
		if (l != l->radial_next) {
			l = l->radial_next;
			f = l->f;
			e = l->e;

			if (!bmw_mask_check_face(walker, f)) {
				l = l->radial_next;
				break;
			}
		}
		else {
			f = l->f;
			e = l->e;
			break;
		}
	}
	
	if (l == owalk.curloop) {
		return NULL;
	}
	else if (BLI_gset_haskey(walker->visit_set, l)) {
		return owalk.curloop;
	}

	BLI_gset_insert(walker->visit_set, l);
	iwalk = BMW_state_add(walker);
	iwalk->base = owalk.base;

	//if (!BMO_elem_flag_test(walker->bm, l->f, walker->restrictflag))
	//	iwalk->curloop = l->radial_next;
	iwalk->curloop = l; //else iwalk->curloop = l;
	iwalk->lastv = v;

	return owalk.curloop;
}


/** \name Island Walker
 * \{
 *
 * Starts at a tool flagged-face and walks over the face region
 *
 * \todo Add restriction flag/callback for wire edges.
 */
static void bmw_IslandWalker_begin(BMWalker *walker, void *data)
{
	BMwIslandWalker *iwalk = NULL;

	if (!bmw_mask_check_face(walker, data)) {
		return;
	}

	iwalk = BMW_state_add(walker);
	BLI_gset_insert(walker->visit_set, data);

	iwalk->cur = data;
}

static void *bmw_IslandWalker_yield(BMWalker *walker)
{
	BMwIslandWalker *iwalk = BMW_current_state(walker);

	return iwalk->cur;
}

static void *bmw_IslandWalker_step(BMWalker *walker)
{
	BMwIslandWalker *iwalk, owalk;
	BMIter iter, liter;
	BMFace *f;
	BMLoop *l;
	
	BMW_state_remove_r(walker, &owalk);
	iwalk = &owalk;

	l = BM_iter_new(&liter, walker->bm, BM_LOOPS_OF_FACE, iwalk->cur);
	for ( ; l; l = BM_iter_step(&liter)) {
		/* could skip loop here too, but don't add unless we need it */
		if (!bmw_mask_check_edge(walker, l->e)) {
			continue;
		}

		f = BM_iter_new(&iter, walker->bm, BM_FACES_OF_EDGE, l->e);
		for ( ; f; f = BM_iter_step(&iter)) {

			if (!bmw_mask_check_face(walker, f)) {
				continue;
			}

			/* saves checking BLI_gset_haskey below (manifold edges theres a 50% chance) */
			if (f == iwalk->cur) {
				continue;
			}

			if (BLI_gset_haskey(walker->visit_set, f)) {
				continue;
			}
			
			iwalk = BMW_state_add(walker);
			iwalk->cur = f;
			BLI_gset_insert(walker->visit_set, f);
			break;
		}
	}
	
	return owalk.cur;
}

/** \} */


/** \name Edge Loop Walker
 * \{
 *
 * Starts at a tool-flagged edge and walks over the edge loop
 *
 */

/* utility function to see if an edge is apart of an ngon boundary */
static bool bm_edge_is_single(BMEdge *e)
{
	return ((BM_edge_is_boundary(e)) &&
	        (e->l->f->len > 4) &&
	        (BM_edge_is_boundary(e->l->next->e) || BM_edge_is_boundary(e->l->prev->e)));
}

static void bmw_LoopWalker_begin(BMWalker *walker, void *data)
{
	BMwLoopWalker *lwalk = NULL, owalk, *owalk_pt;
	BMEdge *e = data;
	BMVert *v;
	const int vert_edge_count[2] = {
	    BM_vert_edge_count_nonwire(e->v1),
	    BM_vert_edge_count_nonwire(e->v2),
	};

	v = e->v1;

	lwalk = BMW_state_add(walker);
	BLI_gset_insert(walker->visit_set, e);

	lwalk->cur = lwalk->start = e;
	lwalk->lastv = lwalk->startv = v;
	lwalk->is_boundary = BM_edge_is_boundary(e);
	lwalk->is_single = (lwalk->is_boundary && bm_edge_is_single(e));

	/* could also check that vertex*/
	if ((lwalk->is_boundary == false) &&
	    (vert_edge_count[0] == 3 || vert_edge_count[1] == 3))
	{
		BMIter iter;
		BMFace *f_iter;
		BMFace *f_best = NULL;

		BM_ITER_ELEM (f_iter, &iter, e, BM_FACES_OF_EDGE) {
			if (f_best == NULL || f_best->len < f_iter->len) {
				f_best = f_iter;
			}
		}

		if (f_best) {
			/* only use hub selection for 5+ sides else this could
			 * conflict with normal edge loop selection. */
			lwalk->f_hub = f_best->len > 4 ? f_best : NULL;
		}
		else {
			/* edge doesn't have any faces connected to it */
			lwalk->f_hub = NULL;
		}
	}
	else {
		lwalk->f_hub = NULL;
	}

	/* rewind */
	while ((owalk_pt = BMW_current_state(walker))) {
		owalk = *((BMwLoopWalker *)owalk_pt);
		BMW_walk(walker);
	}

	lwalk = BMW_state_add(walker);
	*lwalk = owalk;

	lwalk->lastv = lwalk->startv = BM_edge_other_vert(owalk.cur, lwalk->lastv);

	BLI_gset_clear(walker->visit_set, NULL);
	BLI_gset_insert(walker->visit_set, owalk.cur);
}

static void *bmw_LoopWalker_yield(BMWalker *walker)
{
	BMwLoopWalker *lwalk = BMW_current_state(walker);

	return lwalk->cur;
}

static void *bmw_LoopWalker_step(BMWalker *walker)
{
	BMwLoopWalker *lwalk, owalk;
	BMEdge *e, *nexte = NULL;
	BMLoop *l;
	BMVert *v;
	int i = 0;

	BMW_state_remove_r(walker, &owalk);
	lwalk = &owalk;

	e = lwalk->cur;
	l = e->l;

	if (owalk.f_hub) { /* NGON EDGE */
		int vert_edge_tot;

		v = BM_edge_other_vert(e, lwalk->lastv);

		vert_edge_tot = BM_vert_edge_count_nonwire(v);

		if (vert_edge_tot == 3) {
			l = BM_face_other_vert_loop(owalk.f_hub, lwalk->lastv, v);
			nexte = BM_edge_exists(v, l->v);

			if (bmw_mask_check_edge(walker, nexte) &&
			    !BLI_gset_haskey(walker->visit_set, nexte) &&
			    /* never step onto a boundary edge, this gives odd-results */
			    (BM_edge_is_boundary(nexte) == false))
			{
				lwalk = BMW_state_add(walker);
				lwalk->cur = nexte;
				lwalk->lastv = v;

				lwalk->is_boundary = owalk.is_boundary;
				lwalk->is_single = owalk.is_single;
				lwalk->f_hub = owalk.f_hub;

				BLI_gset_insert(walker->visit_set, nexte);
			}
		}
	}
	else if (l == NULL) {  /* WIRE EDGE */
		BMIter eiter;

		/* match trunk: mark all connected wire edges */
		for (i = 0; i < 2; i++) {
			v = i ? e->v2 : e->v1;

			BM_ITER_ELEM (nexte, &eiter, v, BM_EDGES_OF_VERT) {
				if ((nexte->l == NULL) &&
				    bmw_mask_check_edge(walker, nexte) &&
				    !BLI_gset_haskey(walker->visit_set, nexte))
				{
					lwalk = BMW_state_add(walker);
					lwalk->cur = nexte;
					lwalk->lastv = v;

					lwalk->is_boundary = owalk.is_boundary;
					lwalk->is_single = owalk.is_single;
					lwalk->f_hub = owalk.f_hub;

					BLI_gset_insert(walker->visit_set, nexte);
				}
			}
		}
	}
	else if (owalk.is_boundary == false) {  /* NORMAL EDGE WITH FACES */
		int vert_edge_tot;

		v = BM_edge_other_vert(e, lwalk->lastv);

		vert_edge_tot = BM_vert_edge_count_nonwire(v);

		/* typical loopiong over edges in the middle of a mesh */
		/* however, why use 2 here at all? I guess for internal ngon loops it can be useful. Antony R. */
		if (vert_edge_tot == 4 || vert_edge_tot == 2) {
			int i_opposite = vert_edge_tot / 2;
			int i = 0;
			do {
				l = BM_loop_other_edge_loop(l, v);
				if (BM_edge_is_manifold(l->e)) {
					l = l->radial_next;
				}
				else {
					l = NULL;
					break;
				}
			} while ((++i != i_opposite));
		}
		else {
			l = NULL;
		}

		if (l != NULL) {
			if (l != e->l &&
			    bmw_mask_check_edge(walker, l->e) &&
			    !BLI_gset_haskey(walker->visit_set, l->e))
			{
				lwalk = BMW_state_add(walker);
				lwalk->cur = l->e;
				lwalk->lastv = v;

				lwalk->is_boundary = owalk.is_boundary;
				lwalk->is_single = owalk.is_single;
				lwalk->f_hub = owalk.f_hub;

				BLI_gset_insert(walker->visit_set, l->e);
			}
		}
	}
	else if (owalk.is_boundary == true) {  /* BOUNDARY EDGE WITH FACES */
		int vert_edge_tot;

		v = BM_edge_other_vert(e, lwalk->lastv);

		vert_edge_tot = BM_vert_edge_count_nonwire(v);

		/* check if we should step, this is fairly involved */
		if (
		    /* walk over boundary of faces but stop at corners */
		    (owalk.is_single == false && vert_edge_tot > 2) ||

		    /* initial edge was a boundary, so is this edge and vertex is only apart of this face
		     * this lets us walk over the boundary of an ngon which is handy */
		    (owalk.is_single == true && vert_edge_tot == 2 && BM_edge_is_boundary(e)))
		{
			/* find next boundary edge in the fan */
			do {
				l = BM_loop_other_edge_loop(l, v);
				if (BM_edge_is_manifold(l->e)) {
					l = l->radial_next;
				}
				else if (BM_edge_is_boundary(l->e)) {
					break;
				}
				else {
					l = NULL;
					break;
				}
			} while (true);
		}

		if (owalk.is_single == false && l && bm_edge_is_single(l->e)) {
			l = NULL;
		}

		if (l != NULL) {
			if (l != e->l &&
			    bmw_mask_check_edge(walker, l->e) &&
			    !BLI_gset_haskey(walker->visit_set, l->e))
			{
				lwalk = BMW_state_add(walker);
				lwalk->cur = l->e;
				lwalk->lastv = v;

				lwalk->is_boundary = owalk.is_boundary;
				lwalk->is_single = owalk.is_single;
				lwalk->f_hub = owalk.f_hub;

				BLI_gset_insert(walker->visit_set, l->e);
			}
		}
	}

	return owalk.cur;
}

/** \} */


/** \name Face Loop Walker
 * \{
 *
 * Starts at a tool-flagged face and walks over the face loop
 * Conditions for starting and stepping the face loop have been
 * tuned in an attempt to match the face loops built by EditMesh
 */

/* Check whether the face loop should includes the face specified
 * by the given BMLoop */
static bool bmw_FaceLoopWalker_include_face(BMWalker *walker, BMLoop *l)
{
	/* face must have degree 4 */
	if (l->f->len != 4) {
		return false;
	}

	if (!bmw_mask_check_face(walker, l->f)) {
		return false;
	}

	/* the face must not have been already visited */
	if (BLI_gset_haskey(walker->visit_set, l->f) && BLI_gset_haskey(walker->visit_set_alt, l->e)) {
		return false;
	}

	return true;
}

/* Check whether the face loop can start from the given edge */
static bool bmw_FaceLoopWalker_edge_begins_loop(BMWalker *walker, BMEdge *e)
{
	/* There is no face loop starting from a wire edge */
	if (BM_edge_is_wire(e)) {
		return false;
	}
	
	/* Don't start a loop from a boundary edge if it cannot
	 * be extended to cover any faces */
	if (BM_edge_is_boundary(e)) {
		if (!bmw_FaceLoopWalker_include_face(walker, e->l)) {
			return false;
		}
	}
	
	/* Don't start a face loop from non-manifold edges */
	if (!BM_edge_is_manifold(e)) {
		return false;
	}

	return true;
}

static void bmw_FaceLoopWalker_begin(BMWalker *walker, void *data)
{
	BMwFaceLoopWalker *lwalk, owalk, *owalk_pt;
	BMEdge *e = data;
	/* BMesh *bm = walker->bm; */ /* UNUSED */
	/* int fcount = BM_edge_face_count(e); */ /* UNUSED */

	if (!bmw_FaceLoopWalker_edge_begins_loop(walker, e))
		return;

	lwalk = BMW_state_add(walker);
	lwalk->l = e->l;
	lwalk->no_calc = false;
	BLI_gset_insert(walker->visit_set, lwalk->l->f);

	/* rewind */
	while ((owalk_pt = BMW_current_state(walker))) {
		owalk = *((BMwFaceLoopWalker *)owalk_pt);
		BMW_walk(walker);
	}

	lwalk = BMW_state_add(walker);
	*lwalk = owalk;
	lwalk->no_calc = false;

	BLI_gset_clear(walker->visit_set_alt, NULL);
	BLI_gset_insert(walker->visit_set_alt, lwalk->l->e);

	BLI_gset_clear(walker->visit_set, NULL);
	BLI_gset_insert(walker->visit_set, lwalk->l->f);
}

static void *bmw_FaceLoopWalker_yield(BMWalker *walker)
{
	BMwFaceLoopWalker *lwalk = BMW_current_state(walker);
	
	if (!lwalk) {
		return NULL;
	}

	return lwalk->l->f;
}

static void *bmw_FaceLoopWalker_step(BMWalker *walker)
{
	BMwFaceLoopWalker *lwalk, owalk;
	BMFace *f;
	BMLoop *l;

	BMW_state_remove_r(walker, &owalk);
	lwalk = &owalk;

	f = lwalk->l->f;
	l = lwalk->l->radial_next;
	
	if (lwalk->no_calc) {
		return f;
	}

	if (!bmw_FaceLoopWalker_include_face(walker, l)) {
		l = lwalk->l;
		l = l->next->next;
		if (!BM_edge_is_manifold(l->e)) {
			l = l->prev->prev;
		}
		l = l->radial_next;
	}

	if (bmw_FaceLoopWalker_include_face(walker, l)) {
		lwalk = BMW_state_add(walker);
		lwalk->l = l;

		if (l->f->len != 4) {
			lwalk->no_calc = true;
			lwalk->l = owalk.l;
		}
		else {
			lwalk->no_calc = false;
		}

		/* both may already exist */
		BLI_gset_add(walker->visit_set_alt, l->e);
		BLI_gset_add(walker->visit_set, l->f);
	}

	return f;
}

/** \} */


// #define BMW_EDGERING_NGON

/** \name Edge Ring Walker
 * \{
 *
 * Starts at a tool-flagged edge and walks over the edge ring
 * Conditions for starting and stepping the edge ring have been
 * tuned in an attempt to match the edge rings built by EditMesh
 */
static void bmw_EdgeringWalker_begin(BMWalker *walker, void *data)
{
	BMwEdgeringWalker *lwalk, owalk, *owalk_pt;
	BMEdge *e = data;

	lwalk = BMW_state_add(walker);
	lwalk->l = e->l;

	if (!lwalk->l) {
		lwalk->wireedge = e;
		return;
	}
	else {
		lwalk->wireedge = NULL;
	}

	BLI_gset_insert(walker->visit_set, lwalk->l->e);

	/* rewind */
	while ((owalk_pt = BMW_current_state(walker))) {
		owalk = *((BMwEdgeringWalker *)owalk_pt);
		BMW_walk(walker);
	}

	lwalk = BMW_state_add(walker);
	*lwalk = owalk;

#ifdef BMW_EDGERING_NGON
	if (lwalk->l->f->len % 2 != 0)
#else
	if (lwalk->l->f->len != 4)
#endif
	{
		lwalk->l = lwalk->l->radial_next;
	}

	BLI_gset_clear(walker->visit_set, NULL);
	BLI_gset_insert(walker->visit_set, lwalk->l->e);
}

static void *bmw_EdgeringWalker_yield(BMWalker *walker)
{
	BMwEdgeringWalker *lwalk = BMW_current_state(walker);
	
	if (!lwalk) {
		return NULL;
	}

	if (lwalk->l) {
		return lwalk->l->e;
	}
	else {
		return lwalk->wireedge;
	}
}

static void *bmw_EdgeringWalker_step(BMWalker *walker)
{
	BMwEdgeringWalker *lwalk, owalk;
	BMEdge *e;
	BMLoop *l;
#ifdef BMW_EDGERING_NGON
	int i, len;
#endif

#define EDGE_CHECK(e) (bmw_mask_check_edge(walker, e) && (BM_edge_is_boundary(e) || BM_edge_is_manifold(e)))

	BMW_state_remove_r(walker, &owalk);
	lwalk = &owalk;

	l = lwalk->l;
	if (!l)
		return lwalk->wireedge;

	e = l->e;
	if (!EDGE_CHECK(e)) {
		/* walker won't traverse to a non-manifold edge, but may
		 * be started on one, and should not traverse *away* from
		 * a non-manifold edge (non-manifold edges are never in an
		 * edge ring with manifold edges */
		return e;
	}

#ifdef BMW_EDGERING_NGON
	l = l->radial_next;

	i = len = l->f->len;
	while (i > 0) {
		l = l->next;
		i -= 2;
	}

	if ((len <= 0) || (len % 2 != 0) || !EDGE_CHECK(l->e) ||
	    !bmw_mask_check_face(walker, l->f))
	{
		l = owalk.l;
		i = len;
		while (i > 0) {
			l = l->next;
			i -= 2;
		}
	}
	/* only walk to manifold edge */
	if ((l->f->len % 2 == 0) && EDGE_CHECK(l->e) &&
	    !BLI_gset_haskey(walker->visit_set, l->e))

#else

	l = l->radial_next;
	l = l->next->next;
	
	if ((l->f->len != 4) || !EDGE_CHECK(l->e) || !bmw_mask_check_face(walker, l->f)) {
		l = owalk.l->next->next;
	}
	/* only walk to manifold edge */
	if ((l->f->len == 4) && EDGE_CHECK(l->e) &&
	    !BLI_gset_haskey(walker->visit_set, l->e))
#endif
	{
		lwalk = BMW_state_add(walker);
		lwalk->l = l;
		lwalk->wireedge = NULL;

		BLI_gset_insert(walker->visit_set, l->e);
	}

	return e;

#undef EDGE_CHECK
}

/** \} */


/** \name Boundary Edge Walker
 * \{ */

static void bmw_EdgeboundaryWalker_begin(BMWalker *walker, void *data)
{
	BMwEdgeboundaryWalker *lwalk;
	BMEdge *e = data;

	BLI_assert(BM_edge_is_boundary(e));

	if (BLI_gset_haskey(walker->visit_set, e))
		return;

	lwalk = BMW_state_add(walker);
	lwalk->e = e;
	BLI_gset_insert(walker->visit_set, e);
}

static void *bmw_EdgeboundaryWalker_yield(BMWalker *walker)
{
	BMwEdgeboundaryWalker *lwalk = BMW_current_state(walker);

	if (!lwalk) {
		return NULL;
	}

	return lwalk->e;
}

static void *bmw_EdgeboundaryWalker_step(BMWalker *walker)
{
	BMwEdgeboundaryWalker *lwalk, owalk;
	BMEdge *e, *e_other;
	BMVert *v;
	BMIter eiter;
	BMIter viter;

	BMW_state_remove_r(walker, &owalk);
	lwalk = &owalk;

	e = lwalk->e;

	if (!bmw_mask_check_edge(walker, e)) {
		return e;
	}

	BM_ITER_ELEM (v, &viter, e, BM_VERTS_OF_EDGE) {
		BM_ITER_ELEM (e_other, &eiter, v, BM_EDGES_OF_VERT) {
			if (e != e_other && BM_edge_is_boundary(e_other)) {
				if (BLI_gset_haskey(walker->visit_set, e_other)) {
					continue;
				}

				if (!bmw_mask_check_edge(walker, e_other)) {
					continue;
				}

				lwalk = BMW_state_add(walker);
				BLI_gset_insert(walker->visit_set, e_other);

				lwalk->e = e_other;
			}
		}
	}

	return e;
}

/** \} */


/** \name UV Edge Walker
 *
 * walk over uv islands; takes a loop as input.  restrict flag
 * restricts the walking to loops whose vert has restrict flag set as a
 * tool flag.
 *
 * the flag parameter to BMW_init maps to a loop customdata layer index.
 *
 * \{ */

static void bmw_UVEdgeWalker_begin(BMWalker *walker, void *data)
{
	BMwUVEdgeWalker *lwalk;
	BMLoop *l = data;

	if (BLI_gset_haskey(walker->visit_set, l))
		return;

	lwalk = BMW_state_add(walker);
	lwalk->l = l;
	BLI_gset_insert(walker->visit_set, l);
}

static void *bmw_UVEdgeWalker_yield(BMWalker *walker)
{
	BMwUVEdgeWalker *lwalk = BMW_current_state(walker);
	
	if (!lwalk) {
		return NULL;
	}

	return lwalk->l;
}

static void *bmw_UVEdgeWalker_step(BMWalker *walker)
{
	const int type = walker->bm->ldata.layers[walker->layer].type;
	BMwUVEdgeWalker *lwalk, owalk;
	BMLoop *l, *l2, *l3, *nl, *cl;
	BMIter liter;
	void *d1, *d2;
	int i, j, rlen;

	BMW_state_remove_r(walker, &owalk);
	lwalk = &owalk;

	l = lwalk->l;
	nl = l->next;

	if (!bmw_mask_check_edge(walker, l->e)) {
		return l;
	}

	/* go over loops around l->v and nl->v and see which ones share l and nl's
	 * mloopuv's coordinates. in addition, push on l->next if necessary */
	for (i = 0; i < 2; i++) {
		cl = i ? nl : l;
		BM_ITER_ELEM (l2, &liter, cl->v, BM_LOOPS_OF_VERT) {
			d1 = CustomData_bmesh_get_layer_n(&walker->bm->ldata,
			                                  cl->head.data, walker->layer);
			
			rlen = BM_edge_face_count(l2->e);
			for (j = 0; j < rlen; j++) {
				if (BLI_gset_haskey(walker->visit_set, l2)) {
					continue;
				}

				if (!bmw_mask_check_edge(walker, l2->e)) {
					if (l2->v != cl->v) {
						continue;
					}
				}

				l3 = l2->v != cl->v ? l2->next : l2;
				d2 = CustomData_bmesh_get_layer_n(&walker->bm->ldata,
				                                  l3->head.data, walker->layer);

				if (!CustomData_data_equals(type, d1, d2))
					continue;
				
				lwalk = BMW_state_add(walker);
				BLI_gset_insert(walker->visit_set, l2);

				lwalk->l = l2;

				l2 = l2->radial_next;
			}
		}
	}

	return l;
}

/** \} */


static BMWalker bmw_VertShellWalker_Type = {
	BM_VERT | BM_EDGE,
	bmw_VertShellWalker_begin,
	bmw_VertShellWalker_step,
	bmw_VertShellWalker_yield,
	sizeof(BMwShellWalker),
	BMW_BREADTH_FIRST,
	BM_EDGE, /* valid restrict masks */
};

static BMWalker bmw_FaceShellWalker_Type = {
	BM_EDGE,
	bmw_FaceShellWalker_begin,
	bmw_FaceShellWalker_step,
	bmw_FaceShellWalker_yield,
	sizeof(BMwShellWalker),
	BMW_BREADTH_FIRST,
	BM_EDGE, /* valid restrict masks */
};

static BMWalker bmw_IslandboundWalker_Type = {
	BM_LOOP,
	bmw_IslandboundWalker_begin,
	bmw_IslandboundWalker_step,
	bmw_IslandboundWalker_yield,
	sizeof(BMwIslandboundWalker),
	BMW_DEPTH_FIRST,
	BM_FACE, /* valid restrict masks */
};

static BMWalker bmw_IslandWalker_Type = {
	BM_FACE,
	bmw_IslandWalker_begin,
	bmw_IslandWalker_step,
	bmw_IslandWalker_yield,
	sizeof(BMwIslandWalker),
	BMW_BREADTH_FIRST,
	BM_EDGE | BM_FACE, /* valid restrict masks */
};

static BMWalker bmw_LoopWalker_Type = {
	BM_EDGE,
	bmw_LoopWalker_begin,
	bmw_LoopWalker_step,
	bmw_LoopWalker_yield,
	sizeof(BMwLoopWalker),
	BMW_DEPTH_FIRST,
	0, /* valid restrict masks */ /* could add flags here but so far none are used */
};

static BMWalker bmw_FaceLoopWalker_Type = {
	BM_EDGE,
	bmw_FaceLoopWalker_begin,
	bmw_FaceLoopWalker_step,
	bmw_FaceLoopWalker_yield,
	sizeof(BMwFaceLoopWalker),
	BMW_DEPTH_FIRST,
	0, /* valid restrict masks */ /* could add flags here but so far none are used */
};

static BMWalker bmw_EdgeringWalker_Type = {
	BM_EDGE,
	bmw_EdgeringWalker_begin,
	bmw_EdgeringWalker_step,
	bmw_EdgeringWalker_yield,
	sizeof(BMwEdgeringWalker),
	BMW_DEPTH_FIRST,
	BM_EDGE, /* valid restrict masks */
};

static BMWalker bmw_EdgeboundaryWalker_Type = {
	BM_EDGE,
	bmw_EdgeboundaryWalker_begin,
	bmw_EdgeboundaryWalker_step,
	bmw_EdgeboundaryWalker_yield,
	sizeof(BMwEdgeboundaryWalker),
	BMW_DEPTH_FIRST,
	0,
};

static BMWalker bmw_UVEdgeWalker_Type = {
	BM_LOOP,
	bmw_UVEdgeWalker_begin,
	bmw_UVEdgeWalker_step,
	bmw_UVEdgeWalker_yield,
	sizeof(BMwUVEdgeWalker),
	BMW_DEPTH_FIRST,
	BM_EDGE, /* valid restrict masks */
};

static BMWalker bmw_ConnectedVertexWalker_Type = {
	BM_VERT,
	bmw_ConnectedVertexWalker_begin,
	bmw_ConnectedVertexWalker_step,
	bmw_ConnectedVertexWalker_yield,
	sizeof(BMwConnectedVertexWalker),
	BMW_BREADTH_FIRST,
	BM_VERT, /* valid restrict masks */
};

BMWalker *bm_walker_types[] = {
	&bmw_VertShellWalker_Type,          /* BMW_VERT_SHELL */
	&bmw_FaceShellWalker_Type,          /* BMW_FACE_SHELL */
	&bmw_LoopWalker_Type,               /* BMW_LOOP */
	&bmw_FaceLoopWalker_Type,           /* BMW_FACELOOP */
	&bmw_EdgeringWalker_Type,           /* BMW_EDGERING */
	&bmw_EdgeboundaryWalker_Type,       /* BMW_EDGEBOUNDARY */
	&bmw_UVEdgeWalker_Type,             /* BMW_LOOPDATA_ISLAND */
	&bmw_IslandboundWalker_Type,        /* BMW_ISLANDBOUND */
	&bmw_IslandWalker_Type,             /* BMW_ISLAND */
	&bmw_ConnectedVertexWalker_Type,    /* BMW_CONNECTED_VERTEX */
};

const int bm_totwalkers = ARRAY_SIZE(bm_walker_types);
