/*
 * ***** 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.
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file blender/bmesh/operators/bmo_connect.c
 *  \ingroup bmesh
 */

#include "MEM_guardedalloc.h"

#include "BLI_math.h"
#include "BLI_array.h"
#include "BLI_utildefines.h"

#include "bmesh.h"

#include "intern/bmesh_operators_private.h" /* own include */

#define VERT_INPUT	1
#define EDGE_OUT	1
#define FACE_NEW	2
#define EDGE_MARK	4
#define EDGE_DONE	8
#define FACE_OUT	16

void bmo_connect_verts_exec(BMesh *bm, BMOperator *op)
{
	BMIter iter, liter;
	BMFace *f, *nf;
	BMLoop *(*loops_split)[2] = NULL;
	BLI_array_declare(loops_split);
	BMLoop *l, *nl, *lastl = NULL;
	BMVert *(*verts_pair)[2] = NULL;
	BLI_array_declare(verts_pair);
	int i;
	
	BMO_slot_buffer_flag_enable(bm, op->slots_in, "verts", BM_VERT, VERT_INPUT);

	/* BMESH_TODO, loop over vert faces:
	 * faster then looping over all faces, then searching each for flagged verts*/
	for (f = BM_iter_new(&iter, bm, BM_FACES_OF_MESH, NULL); f; f = BM_iter_step(&iter)) {
		BLI_array_empty(loops_split);
		BLI_array_empty(verts_pair);
		
		if (BMO_elem_flag_test(bm, f, FACE_NEW)) {
			continue;
		}

		l = BM_iter_new(&liter, bm, BM_LOOPS_OF_FACE, f);
		lastl = NULL;
		for ( ; l; l = BM_iter_step(&liter)) {
			if (BMO_elem_flag_test(bm, l->v, VERT_INPUT)) {
				if (!lastl) {
					lastl = l;
					continue;
				}

				if (lastl != l->prev && lastl != l->next) {
					BLI_array_grow_one(loops_split);
					loops_split[BLI_array_count(loops_split) - 1][0] = lastl;
					loops_split[BLI_array_count(loops_split) - 1][1] = l;

				}
				lastl = l;
			}
		}

		if (BLI_array_count(loops_split) == 0) {
			continue;
		}
		
		if (BLI_array_count(loops_split) > 1) {
			BLI_array_grow_one(loops_split);
			loops_split[BLI_array_count(loops_split) - 1][0] = loops_split[BLI_array_count(loops_split) - 2][1];
			loops_split[BLI_array_count(loops_split) - 1][1] = loops_split[0][0];
		}

		BM_face_legal_splits(bm, f, loops_split, BLI_array_count(loops_split));
		
		for (i = 0; i < BLI_array_count(loops_split); i++) {
			if (loops_split[i][0] == NULL) {
				continue;
			}

			BLI_array_grow_one(verts_pair);
			verts_pair[BLI_array_count(verts_pair) - 1][0] = loops_split[i][0]->v;
			verts_pair[BLI_array_count(verts_pair) - 1][1] = loops_split[i][1]->v;
		}

		for (i = 0; i < BLI_array_count(verts_pair); i++) {
			nf = BM_face_split(bm, f, verts_pair[i][0], verts_pair[i][1], &nl, NULL, false);
			f = nf;
			
			if (!nl || !nf) {
				BMO_error_raise(bm, op, BMERR_CONNECTVERT_FAILED, NULL);
				BLI_array_free(loops_split);
				return;
			}
			BMO_elem_flag_enable(bm, nf, FACE_NEW);
			BMO_elem_flag_enable(bm, nl->e, EDGE_OUT);
		}
	}

	BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, EDGE_OUT);

	BLI_array_free(loops_split);
	BLI_array_free(verts_pair);
}

static BMVert *get_outer_vert(BMesh *bm, BMEdge *e)
{
	BMIter iter;
	BMEdge *e2;
	int i;

	i = 0;
	BM_ITER_ELEM (e2, &iter, e->v1, BM_EDGES_OF_VERT) {
		if (BMO_elem_flag_test(bm, e2, EDGE_MARK)) {
			i++;
		}
	}

	return (i == 2) ? e->v2 : e->v1;
}

/* Clamp x to the interval {0..len-1}, with wrap-around */
static int clamp_index(const int x, const int len)
{
	if (x >= 0) {
		return x % len;
	}
	else {
		int r = len - (-x % len);
		if (r == len)
			return len - 1;
		else
			return r;
	}
}

/* There probably is a better way to swap BLI_arrays, or if there
 * isn't there should be... */
#define ARRAY_SWAP(elemtype, arr1, arr2)                                      \
	{                                                                         \
		int i;                                                                \
		elemtype *arr_tmp = NULL;                                             \
		BLI_array_declare(arr_tmp);                                           \
		for (i = 0; i < BLI_array_count(arr1); i++) {                         \
			BLI_array_append(arr_tmp, arr1[i]);                               \
		}                                                                     \
		BLI_array_empty(arr1);                                                \
		for (i = 0; i < BLI_array_count(arr2); i++) {                         \
			BLI_array_append(arr1, arr2[i]);                                  \
		}                                                                     \
		BLI_array_empty(arr2);                                                \
		for (i = 0; i < BLI_array_count(arr_tmp); i++) {                      \
			BLI_array_append(arr2, arr_tmp[i]);                               \
		}                                                                     \
		BLI_array_free(arr_tmp);                                              \
	} (void)0

/* get the 2 loops matching 2 verts.
 * first attempt to get the face corners that use the edge defined by v1 & v2,
 * if that fails just get any loop thats on the vert (the first one) */
static void bm_vert_loop_pair(BMesh *bm, BMVert *v1, BMVert *v2, BMLoop **l1, BMLoop **l2)
{
	BMIter liter;
	BMLoop *l;

	if ((v1->e && v1->e->l) &&
	    (v2->e && v2->e->l))
	{
		BM_ITER_ELEM (l, &liter, v1, BM_LOOPS_OF_VERT) {
			if (l->prev->v == v2) {
				*l1 = l;
				*l2 = l->prev;
				return;
			}
			else if (l->next->v == v2) {
				*l1 = l;
				*l2 = l->next;
				return;
			}
		}
	}

	/* fallback to _any_ loop */
	*l1 = BM_iter_at_index(bm, BM_LOOPS_OF_VERT, v1, 0);
	*l2 = BM_iter_at_index(bm, BM_LOOPS_OF_VERT, v2, 0);
}

void bmo_bridge_loops_exec(BMesh *bm, BMOperator *op)
{
	BMEdge **ee1 = NULL, **ee2 = NULL;
	BMVert **vv1 = NULL, **vv2 = NULL;
	BLI_array_declare(ee1);
	BLI_array_declare(ee2);
	BLI_array_declare(vv1);
	BLI_array_declare(vv2);
	BMOIter siter;
	BMIter iter;
	BMEdge *e, *nexte;
	int c = 0, cl1 = 0, cl2 = 0;

	/* merge-bridge support */
	const bool  use_merge    = BMO_slot_bool_get(op->slots_in,  "use_merge");
	const float merge_factor = BMO_slot_float_get(op->slots_in, "merge_factor");

	BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK);

	BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
		if (!BMO_elem_flag_test(bm, e, EDGE_DONE)) {
			BMVert *v, *ov;
			/* BMEdge *e2, *e3, *oe = e; */ /* UNUSED */
			BMEdge *e2, *e3;
			
			if (c > 2) {
				BMO_error_raise(bm, op, BMERR_INVALID_SELECTION, "Select only two edge loops");
				goto cleanup;
			}
			
			e2 = e;
			v = e->v1;
			do {
				v = BM_edge_other_vert(e2, v);
				nexte = NULL;
				BM_ITER_ELEM (e3, &iter, v, BM_EDGES_OF_VERT) {
					if (e3 != e2 && BMO_elem_flag_test(bm, e3, EDGE_MARK)) {
						if (nexte == NULL) {
							nexte = e3;
						}
						else {
							/* edges do not form a loop: there is a disk
							 * with more than two marked edges. */
							BMO_error_raise(bm, op, BMERR_INVALID_SELECTION,
							                "Selection must only contain edges from two edge loops");
							goto cleanup;
						}
					}
				}
				
				if (nexte)
					e2 = nexte;
			} while (nexte && e2 != e);
			
			if (!e2)
				e2 = e;

			e = e2;
			ov = v;
			do {
				if (c == 0) {
					BLI_array_append(ee1, e2);
					BLI_array_append(vv1, v);
				}
				else {
					BLI_array_append(ee2, e2);
					BLI_array_append(vv2, v);
				}
				
				BMO_elem_flag_enable(bm, e2, EDGE_DONE);
				
				v = BM_edge_other_vert(e2, v);
				BM_ITER_ELEM (e3, &iter, v, BM_EDGES_OF_VERT) {
					if (e3 != e2 && BMO_elem_flag_test(bm, e3, EDGE_MARK) && !BMO_elem_flag_test(bm, e3, EDGE_DONE)) {
						break;
					}
				}
				if (e3)
					e2 = e3;
			} while (e3 && e2 != e);
			
			if (v && !e3) {
				if (c == 0) {
					if (BLI_array_count(vv1) && v == vv1[BLI_array_count(vv1) - 1]) {
						printf("%s: internal state waning *TODO DESCRIPTION!*\n", __func__);
					}
					BLI_array_append(vv1, v);
				}
				else {
					BLI_array_append(vv2, v);
				}
			}
			
			/* test for connected loops, and set cl1 or cl2 if so */
			if (v == ov) {
				if (c == 0) {
					cl1 = 1;
				}
				else {
					cl2 = 1;
				}
			}
			
			c++;
		}
	}

	if (ee1 && ee2) {
		int i, j;
		BMVert *v1, *v2, *v3, *v4;
		int starti = 0, dir1 = 1, wdir = 0, lenv1, lenv2;

		/* Simplify code below by avoiding the (!cl1 && cl2) case */
		if (!cl1 && cl2) {
			SWAP(int, cl1, cl2);
			ARRAY_SWAP(BMVert *, vv1, vv2);
			ARRAY_SWAP(BMEdge *, ee1, ee2);
		}

		lenv1 = lenv2 = BLI_array_count(vv1);

		/* Below code assumes vv1/vv2 each have at least two verts. should always be
		 * a safe assumption, since ee1/ee2 are non-empty and an edge has two verts. */
		BLI_assert((lenv1 > 1) && (lenv2 > 1));

		/* BMESH_TODO: Would be nice to handle cases where the edge loops
		 * have different edge counts by generating triangles & quads for
		 * the bridge instead of quads only. */
		if (BLI_array_count(ee1) != BLI_array_count(ee2)) {
			BMO_error_raise(bm, op, BMERR_INVALID_SELECTION,
			                "Selected loops must have equal edge counts");
			goto cleanup;
		}

		if (vv1[0] == vv1[lenv1 - 1]) {
			lenv1--;
		}
		if (vv2[0] == vv2[lenv2 - 1]) {
			lenv2--;
		}

		/* Find starting point and winding direction for two unclosed loops */
		if (!cl1 && !cl2) {
			/* First point of loop 1 */
			v1 = get_outer_vert(bm, ee1[0]);
			/* Last point of loop 1 */
			v2 = get_outer_vert(bm, ee1[clamp_index(-1, BLI_array_count(ee1))]);
			/* First point of loop 2 */
			v3 = get_outer_vert(bm, ee2[0]);
			/* Last point of loop 2 */
			v4 = get_outer_vert(bm, ee2[clamp_index(-1, BLI_array_count(ee2))]);

			/* ugh, happens when bridging single edges, user could just make a face
			 * but better support it for sake of completeness */
			if (v1 == v2) {
				BLI_assert(BLI_array_count(ee1) == 1);
				v2 = (vv1[0] == v2) ? vv1[1] : vv1[0];
			}
			if (v3 == v4) {
				BLI_assert(BLI_array_count(ee2) == 1);
				v4 = (vv2[0] == v4) ? vv2[1] : vv2[0];
			}

			/* If v1 is a better match for v4 than v3, AND v2 is a better match
			 * for v3 than v4, the loops are in opposite directions, so reverse
			 * the order of reads from vv1. We can avoid sqrt for comparison */
			if (len_squared_v3v3(v1->co, v3->co) > len_squared_v3v3(v1->co, v4->co) &&
			    len_squared_v3v3(v2->co, v4->co) > len_squared_v3v3(v2->co, v3->co))
			{
				dir1 = -1;
				starti = clamp_index(-1, lenv1);
			}
		}

		/* Find the smallest sum of distances from verts in vv1 to verts in vv2,
		 * finding a starting point in the first loop, to start with vv2[0] in the
		 * second loop. This is a simplistic attempt to get a better edge-to-edge
		 * match between two loops. */
		if (cl1) {
			float min = 1e32;

			for (i = 0; i < lenv1; i++) {
				float len;

				/* compute summed length between vertices in forward direction */
				len = 0.0f;
				for (j = 0; (j < lenv2) && (len < min); j++) {
					len += len_v3v3(vv1[clamp_index(i + j, lenv1)]->co, vv2[j]->co);
				}

				if (len < min) {
					min = len;
					starti = i;
					dir1 = 1;
				}

				/* compute summed length between vertices in backward direction */
				len = 0.0f;
				for (j = 0; (j < lenv2) && (len < min); j++) {
					len += len_v3v3(vv1[clamp_index(i - j, lenv1)]->co, vv2[j]->co);
				}

				if (len < min) {
					min = len;
					starti = i;
					dir1 = -1;
				}
			}
		}

		/* Vert rough attempt to determine proper winding for the bridge quads:
		 * just uses the first loop it finds for any of the edges of ee2 or ee1 */
		if (wdir == 0) {
			for (i = 0; i < BLI_array_count(ee2); i++) {
				if (ee2[i]->l) {
					wdir = (ee2[i]->l->v == vv2[i]) ? (-1) : (1);
					break;
				}
			}
		}
		if (wdir == 0) {
			for (i = 0; i < BLI_array_count(ee1); i++) {
				j = clamp_index((i * dir1) + starti, BLI_array_count(ee1));
				if (ee1[j]->l && ee2[j]->l) {
					wdir = (ee2[j]->l->v == vv2[j]) ? (1) : (-1);
					break;
				}
			}
		}
		
		/* merge loops of bridge faces */
		if (use_merge) {
			const int vert_len = min_ii(BLI_array_count(vv1), BLI_array_count(vv2)) - ((cl1 || cl2) ? 1 : 0);
			const int edge_len = min_ii(BLI_array_count(ee1), BLI_array_count(ee2));

			if (merge_factor <= 0.0f) {
				/* 2 --> 1 */
				for (i = 0; i < vert_len; i++) {
					BM_vert_splice(bm, vv2[i], vv1[i]);
				}
				for (i = 0; i < edge_len; i++) {
					BM_edge_splice(bm, ee2[i], ee1[i]);
				}
			}
			else if (merge_factor >= 1.0f) {
				/* 1 --> 2 */
				for (i = 0; i < vert_len; i++) {
					BM_vert_splice(bm, vv1[i], vv2[i]);
				}
				for (i = 0; i < edge_len; i++) {
					BM_edge_splice(bm, ee1[i], ee2[i]);
				}
			}
			else {
				/* mid factor, be tricky */
				/* 1 --> 2 */
				for (i = 0; i < vert_len; i++) {
					BM_data_interp_from_verts(bm, vv1[i], vv2[i], vv2[i], merge_factor);
					interp_v3_v3v3(vv2[i]->co, vv1[i]->co, vv2[i]->co, merge_factor);
					BM_elem_flag_merge(vv1[i], vv2[i]);
					BM_vert_splice(bm, vv1[i], vv2[i]);
				}
				for (i = 0; i < edge_len; i++) {
					BM_data_interp_from_edges(bm, ee1[i], ee2[i], ee2[i], merge_factor);
					BM_elem_flag_merge(ee1[i], ee2[i]);
					BM_edge_splice(bm, ee1[i], ee2[i]);
				}
			}
		}
		else {
			/* Generate the bridge quads */
			for (i = 0; i < BLI_array_count(ee1) && i < BLI_array_count(ee2); i++) {
				BMFace *f;

				BMLoop *l_1 = NULL;
				BMLoop *l_2 = NULL;
				BMLoop *l_1_next = NULL;
				BMLoop *l_2_next = NULL;
				BMLoop *l_iter;
				BMFace *f_example;

				int i1, i1next, i2, i2next;

				i1 = clamp_index(i * dir1 + starti, lenv1);
				i1next = clamp_index((i + 1) * dir1 + starti, lenv1);
				i2 = i;
				i2next = clamp_index(i + 1, lenv2);

				if (vv1[i1] == vv1[i1next]) {
					continue;
				}

				if (wdir < 0) {
					SWAP(int, i1, i1next);
					SWAP(int, i2, i2next);
				}

				/* get loop data - before making the face */
				bm_vert_loop_pair(bm, vv1[i1], vv2[i2], &l_1, &l_2);
				bm_vert_loop_pair(bm, vv1[i1next], vv2[i2next], &l_1_next, &l_2_next);
				/* copy if loop data if its is missing on one ring */
				if (l_1 && l_1_next == NULL) l_1_next = l_1;
				if (l_1_next && l_1 == NULL) l_1 = l_1_next;
				if (l_2 && l_2_next == NULL) l_2_next = l_2;
				if (l_2_next && l_2 == NULL) l_2 = l_2_next;
				f_example = l_1 ? l_1->f : (l_2 ? l_2->f : NULL);

				f = BM_face_create_quad_tri(bm,
				                            vv1[i1],
				                            vv2[i2],
				                            vv2[i2next],
				                            vv1[i1next],
				                            f_example, true);
				if (UNLIKELY((f == NULL) || (f->len != 4))) {
					fprintf(stderr, "%s: in bridge! (bmesh internal error)\n", __func__);
				}
				else {
					BMO_elem_flag_enable(bm, f, FACE_OUT);

					l_iter = BM_FACE_FIRST_LOOP(f);

					if (l_1)      BM_elem_attrs_copy(bm, bm, l_1,      l_iter); l_iter = l_iter->next;
					if (l_2)      BM_elem_attrs_copy(bm, bm, l_2,      l_iter); l_iter = l_iter->next;
					if (l_2_next) BM_elem_attrs_copy(bm, bm, l_2_next, l_iter); l_iter = l_iter->next;
					if (l_1_next) BM_elem_attrs_copy(bm, bm, l_1_next, l_iter);
				}
			}
		}
	}

	BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, FACE_OUT);

cleanup:
	BLI_array_free(ee1);
	BLI_array_free(ee2);
	BLI_array_free(vv1);
	BLI_array_free(vv2);
}
