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

/** \file blender/editors/mesh/editmesh_intersect.c
 *  \ingroup edmesh
 */

#include "MEM_guardedalloc.h"

#include "DNA_object_types.h"

#include "BLI_math.h"
#include "BLI_array.h"
#include "BLI_linklist_stack.h"


#include "BKE_context.h"
#include "BKE_report.h"
#include "BKE_editmesh.h"

#include "RNA_access.h"
#include "RNA_define.h"

#include "WM_api.h"
#include "WM_types.h"

#include "ED_mesh.h"
#include "ED_screen.h"

#include "intern/bmesh_private.h"

#include "mesh_intern.h"  /* own include */

#include "tools/bmesh_intersect.h"


/* -------------------------------------------------------------------- */
/* Cut intersections into geometry */

/**
 * Compare selected with its self.
 */
static int bm_face_isect_self(BMFace *f, void *UNUSED(user_data))
{
	if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
		return 0;
	}
	else {
		return -1;
	}
}

/**
 * Compare selected/unselected.
 */
static int bm_face_isect_pair(BMFace *f, void *UNUSED(user_data))
{
	if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
		return -1;
	}
	else if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
		return 0;
	}
	else {
		return 1;
	}
}

enum {
	ISECT_SEL           = 0,
	ISECT_SEL_UNSEL     = 1,
};

static EnumPropertyItem isect_mode_items[] = {
	{ISECT_SEL, "SELECT", 0, "Self Intersect",
	 "Self intersect selected faces"},
	{ISECT_SEL_UNSEL, "SELECT_UNSELECT", 0, "Selected/Unselected",
	 "Intersect selected with unselected faces"},
	{0, NULL, 0, NULL, NULL}
};

static int edbm_intersect_exec(bContext *C, wmOperator *op)
{
	Object *obedit = CTX_data_edit_object(C);
	BMEditMesh *em = BKE_editmesh_from_object(obedit);
	BMesh *bm = em->bm;
	const int mode = RNA_enum_get(op->ptr, "mode");
	int (*test_fn)(BMFace *, void *);
	bool use_separate = RNA_boolean_get(op->ptr, "use_separate");
	const float eps = RNA_float_get(op->ptr, "threshold");
	bool use_self;
	bool has_isect;

	switch (mode) {
		case ISECT_SEL:
			test_fn = bm_face_isect_self;
			use_self = true;
			break;
		default:  /* ISECT_SEL_UNSEL */
			test_fn = bm_face_isect_pair;
			use_self = false;
			break;
	}


	has_isect = BM_mesh_intersect(
	        bm,
	        em->looptris, em->tottri,
	        test_fn, NULL,
	        use_self, use_separate,
	        eps);


	if (has_isect) {
		BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false);

		if (em->bm->selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) {
			BMIter iter;
			BMEdge *e;

			BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
				if (BM_elem_flag_test(e, BM_ELEM_TAG)) {
					BM_edge_select_set(bm, e, true);
				}
			}
		}

		EDBM_mesh_normals_update(em);
		EDBM_update_generic(em, true, true);
	}
	else {
		BKE_report(op->reports, RPT_WARNING, "No intersections found");
	}

	return OPERATOR_FINISHED;
}

void MESH_OT_intersect(struct wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Intersect";
	ot->description = "Cut an intersection into faces";
	ot->idname = "MESH_OT_intersect";

	/* api callbacks */
	ot->exec = edbm_intersect_exec;
	ot->poll = ED_operator_editmesh;

	/* props */
	RNA_def_enum(ot->srna, "mode", isect_mode_items, ISECT_SEL_UNSEL, "Source", "");
	RNA_def_boolean(ot->srna, "use_separate", true, "Separate", "");
	RNA_def_float(ot->srna, "threshold", 0.000001f, 0.0, 0.01, "Merge threshold", "", 0.0, 0.001);

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}


/* -------------------------------------------------------------------- */
/* Face Split by Edges */


/** \name Face/Edge Split
 * \{ */

static void bm_face_split_by_edges(BMesh *bm, BMFace *f, const char hflag)
{
	BMEdge **edge_net = NULL;
	BLI_array_declare(edge_net);

	const int f_index = BM_elem_index_get(f);

	BMLoop *l_iter;
	BMLoop *l_first;
	BMVert *v;

	BMFace **face_arr;
	int face_arr_len;

	/* likely this will stay very small
	 * all verts pushed into this stack _must_ have their previous edges set! */
	BLI_SMALLSTACK_DECLARE(vert_stack, BMVert *);
	BLI_SMALLSTACK_DECLARE(vert_stack_next, BMVert *);


	/* collect all edges */
	l_iter = l_first = BM_FACE_FIRST_LOOP(f);
	do {
		BMIter iter;
		BMEdge *e;

		BM_ITER_ELEM (e, &iter, l_iter->v, BM_EDGES_OF_VERT) {
			if (BM_elem_flag_test(e, hflag) &&
			    (BM_elem_index_get(e) == f_index))
			{
				v = BM_edge_other_vert(e, l_iter->v);
				v->e = e;

				BLI_SMALLSTACK_PUSH(vert_stack, v);
				BLI_array_append(edge_net, e);
			}
		}
	} while ((l_iter = l_iter->next) != l_first);



	/* now assign all */
	/* pop free values into the next stack */
	while ((v = BLI_SMALLSTACK_POP_EX(vert_stack, vert_stack_next))) {
		BMIter eiter;
		BMEdge *e_next;

		BM_ITER_ELEM (e_next, &eiter, v, BM_EDGES_OF_VERT) {
			if (BM_elem_flag_test(e_next, hflag) &&
			    (BM_elem_index_get(e_next) == -1))
			{
				BMVert *v_next;
				v_next = BM_edge_other_vert(e_next, v);
				BM_elem_index_set(e_next, f_index);
				BLI_SMALLSTACK_PUSH(vert_stack_next, v_next);
				BLI_array_append(edge_net, e_next);
			}
		}

		if (BLI_SMALLSTACK_IS_EMPTY(vert_stack)) {
			BLI_SMALLSTACK_SWAP(vert_stack, vert_stack_next);
		}
	}

	BM_face_split_edgenet(bm, f, edge_net, BLI_array_count(edge_net), &face_arr, &face_arr_len);
	BLI_array_free(edge_net);

	if (face_arr_len) {
		int i;
		for (i = 0; i < face_arr_len; i++) {
			BM_face_select_set(bm, face_arr[i], true);
			BM_elem_flag_disable(face_arr[i], hflag);
		}
	}

	if (face_arr) {
		MEM_freeN(face_arr);
	}
}

static int edbm_face_split_by_edges_exec(bContext *C, wmOperator *UNUSED(op))
{
	Object *obedit = CTX_data_edit_object(C);
	BMEditMesh *em = BKE_editmesh_from_object(obedit);
	BMesh *bm = em->bm;
	const char hflag = BM_ELEM_TAG;

	BMVert *v;
	BMEdge *e;
	BMFace *f;
	BMIter iter;

	BLI_SMALLSTACK_DECLARE(loop_stack, BMLoop *);

	BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
		BM_elem_flag_disable(v, hflag);
	}

	/* edge index is set to -1 then used to assosiate them with faces */
	BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
		if (BM_elem_flag_test(e, BM_ELEM_SELECT) && BM_edge_is_wire(e)) {
			BM_elem_flag_enable(e, hflag);

			BM_elem_flag_enable(e->v1, hflag);
			BM_elem_flag_enable(e->v2, hflag);

		}
		else {
			BM_elem_flag_disable(e, hflag);
		}
		BM_elem_index_set(e, -1);  /* set_dirty */
	}

	BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
		if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
			BM_elem_flag_enable(f, hflag);
		}
		else {
			BM_elem_flag_disable(f, hflag);
		}
	}

	BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
		if (BM_elem_flag_test(e, hflag)) {
			BMIter viter;
			BM_ITER_ELEM (v, &viter, e, BM_VERTS_OF_EDGE) {
				BMIter liter;
				BMLoop *l;

				unsigned int loop_stack_len;
				BMLoop *l_best = NULL;

				BLI_assert(BLI_SMALLSTACK_IS_EMPTY(loop_stack));
				loop_stack_len = 0;

				BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) {
					if (BM_elem_flag_test(l->f, hflag)) {
						BLI_SMALLSTACK_PUSH(loop_stack, l);
						loop_stack_len++;
					}
				}

				if (loop_stack_len == 0) {
					/* pass */
				}
				else if (loop_stack_len == 1) {
					l_best = BLI_SMALLSTACK_POP(loop_stack);
				}
				else {
					/* complicated case, match the edge with a face-loop */

					BMVert *v_other = BM_edge_other_vert(e, v);
					float e_dir[3];

					/* we wan't closest to zero */
					float dot_best = FLT_MAX;

					sub_v3_v3v3(e_dir, v_other->co, v->co);
					normalize_v3(e_dir);

					while ((l = BLI_SMALLSTACK_POP(loop_stack))) {
						float dot_test;

						/* Check dot first to save on expensive angle-comparison.
						 * ideal case is 90d difference == 0.0 dot */
						dot_test = fabsf(dot_v3v3(e_dir, l->f->no));
						if (dot_test < dot_best) {

							/* check we're in the correct corner (works with convex loops too) */
							if (angle_signed_on_axis_v3v3v3_v3(l->prev->v->co, l->v->co, v_other->co,    l->f->no) <
							    angle_signed_on_axis_v3v3v3_v3(l->prev->v->co, l->v->co, l->next->v->co, l->f->no))
							{
								dot_best = dot_test;
								l_best = l;
							}
						}
					}
				}

				if (l_best) {
					BM_elem_index_set(e, BM_elem_index_get(l_best->f));  /* set_dirty */
				}
			}
		}
	}

	bm->elem_index_dirty |= BM_EDGE;


	BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
		if (BM_elem_flag_test(f, hflag)) {
			bm_face_split_by_edges(bm, f, hflag);
		}
	}

	EDBM_mesh_normals_update(em);
	EDBM_update_generic(em, true, true);

	return OPERATOR_FINISHED;
}


void MESH_OT_face_split_by_edges(struct wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Split by Edges";
	ot->description = "Split faces by loose edges";
	ot->idname = "MESH_OT_face_split_by_edges";

	/* api callbacks */
	ot->exec = edbm_face_split_by_edges_exec;
	ot->poll = ED_operator_editmesh;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/** \} */
