/*
 * ***** 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_utils.c
 *  \ingroup bmesh
 *
 * utility bmesh operators, e.g. transform,
 * translate, rotate, scale, etc.
 */

#include "MEM_guardedalloc.h"

#include "DNA_meshdata_types.h"

#include "BLI_math.h"
#include "BLI_alloca.h"

#include "BKE_customdata.h"

#include "bmesh.h"

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

#define ELE_NEW 1

void bmo_create_vert_exec(BMesh *bm, BMOperator *op)
{
	float vec[3];

	BMO_slot_vec_get(op->slots_in, "co", vec);

	BMO_vert_flag_enable(bm, BM_vert_create(bm, vec, NULL, BM_CREATE_NOP), ELE_NEW);
	BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "vert.out", BM_VERT, ELE_NEW);
}

void bmo_transform_exec(BMesh *UNUSED(bm), BMOperator *op)
{
	BMOIter iter;
	BMVert *v;
	float mat[4][4], mat_space[4][4], imat_space[4][4];

	BMO_slot_mat4_get(op->slots_in, "matrix", mat);
	BMO_slot_mat4_get(op->slots_in, "space", mat_space);

	if (!is_zero_m4(mat_space)) {
		invert_m4_m4(imat_space, mat_space);
		mul_m4_series(mat, imat_space, mat, mat_space);
	}

	BMO_ITER (v, &iter, op->slots_in, "verts", BM_VERT) {
		mul_m4_v3(mat, v->co);
	}
}

void bmo_translate_exec(BMesh *bm, BMOperator *op)
{
	float mat[4][4], vec[3];

	BMO_slot_vec_get(op->slots_in, "vec", vec);

	unit_m4(mat);
	copy_v3_v3(mat[3], vec);

	BMO_op_callf(bm, op->flag, "transform matrix=%m4 space=%s verts=%s", mat, op, "space", op, "verts");
}

void bmo_scale_exec(BMesh *bm, BMOperator *op)
{
	float mat[3][3], vec[3];

	BMO_slot_vec_get(op->slots_in, "vec", vec);

	unit_m3(mat);
	mat[0][0] = vec[0];
	mat[1][1] = vec[1];
	mat[2][2] = vec[2];

	BMO_op_callf(bm, op->flag, "transform matrix=%m3 space=%s verts=%s", mat, op, "space", op, "verts");
}

void bmo_rotate_exec(BMesh *bm, BMOperator *op)
{
	float center[3];
	float mat[4][4];

	BMO_slot_vec_get(op->slots_in, "cent", center);
	BMO_slot_mat4_get(op->slots_in, "matrix", mat);
	transform_pivot_set_m4(mat, center);

	BMO_op_callf(bm, op->flag, "transform matrix=%m4 space=%s verts=%s", mat, op, "space", op, "verts");
}

void bmo_reverse_faces_exec(BMesh *bm, BMOperator *op)
{
	const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS);
	const bool use_loop_mdisp_flip = BMO_slot_bool_get(op->slots_in, "flip_multires");
	BMOIter siter;
	BMFace *f;

	BMO_ITER (f, &siter, op->slots_in, "faces", BM_FACE) {
		BM_face_normal_flip_ex(bm, f, cd_loop_mdisp_offset, use_loop_mdisp_flip);
	}
}

void bmo_rotate_edges_exec(BMesh *bm, BMOperator *op)
{
	BMOIter siter;
	BMEdge *e, *e2;
	const bool use_ccw   = BMO_slot_bool_get(op->slots_in, "use_ccw");
	const bool is_single = BMO_slot_buffer_count(op->slots_in, "edges") == 1;
	short check_flag = is_single ?
	            BM_EDGEROT_CHECK_EXISTS :
	            BM_EDGEROT_CHECK_EXISTS | BM_EDGEROT_CHECK_DEGENERATE;

#define EDGE_OUT   1
#define FACE_TAINT 1

	BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
		/**
		 * this ends up being called twice, could add option to not to call check in
		 * #BM_edge_rotate to get some extra speed */
		if (BM_edge_rotate_check(e)) {
			BMFace *fa, *fb;
			if (BM_edge_face_pair(e, &fa, &fb)) {

				/* check we're untouched */
				if (BMO_face_flag_test(bm, fa, FACE_TAINT) == false &&
				    BMO_face_flag_test(bm, fb, FACE_TAINT) == false)
				{

					/* don't touch again (faces will be freed so run before rotating the edge) */
					BMO_face_flag_enable(bm, fa, FACE_TAINT);
					BMO_face_flag_enable(bm, fb, FACE_TAINT);

					if (!(e2 = BM_edge_rotate(bm, e, use_ccw, check_flag))) {

						BMO_face_flag_disable(bm, fa, FACE_TAINT);
						BMO_face_flag_disable(bm, fb, FACE_TAINT);
#if 0
						BMO_error_raise(bm, op, BMERR_INVALID_SELECTION, "Could not rotate edge");
						return;
#endif

						continue;
					}

					BMO_edge_flag_enable(bm, e2, EDGE_OUT);
				}
			}
		}
	}

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

#undef EDGE_OUT
#undef FACE_TAINT

}

#define SEL_FLAG	1
#define SEL_ORIG	2

static void bmo_face_flag_set_flush(BMesh *bm, BMFace *f, const short oflag, const bool value)
{
	BMLoop *l_iter;
	BMLoop *l_first;

	BMO_face_flag_set(bm, f, oflag, value);
	l_iter = l_first = BM_FACE_FIRST_LOOP(f);
	do {
		BMO_edge_flag_set(bm, l_iter->e, oflag, value);
		BMO_vert_flag_set(bm, l_iter->v, oflag, value);
	} while ((l_iter = l_iter->next) != l_first);
}


static void bmo_region_extend_expand(
        BMesh *bm, BMOperator *op,
        const bool use_faces, const bool use_faces_step)
{
	BMOIter siter;

	if (!use_faces) {
		BMVert *v;

		BMO_ITER (v, &siter, op->slots_in, "geom", BM_VERT) {
			bool found = false;

			{
				BMIter eiter;
				BMEdge *e;

				BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
					if (!BMO_edge_flag_test(bm, e, SEL_ORIG) &&
					    !BM_elem_flag_test(e, BM_ELEM_HIDDEN))
					{
						found = true;
						break;
					}
				}
			}

			if (found) {
				if (!use_faces_step) {
					BMIter eiter;
					BMEdge *e;

					BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
						if (!BMO_edge_flag_test(bm, e, SEL_FLAG) &&
						    !BM_elem_flag_test(e, BM_ELEM_HIDDEN))
						{
							BMO_edge_flag_enable(bm, e, SEL_FLAG);
							BMO_vert_flag_enable(bm, BM_edge_other_vert(e, v), SEL_FLAG);
						}
					}
				}
				else {
					BMIter fiter;
					BMFace *f;

					BM_ITER_ELEM (f, &fiter, v, BM_FACES_OF_VERT) {
						if (!BMO_face_flag_test(bm, f, SEL_FLAG) &&
						    !BM_elem_flag_test(f, BM_ELEM_HIDDEN))
						{
							bmo_face_flag_set_flush(bm, f, SEL_FLAG, true);
						}
					}

					/* handle wire edges (when stepping over faces) */
					{
						BMIter eiter;
						BMEdge *e;
						BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
							if (BM_edge_is_wire(e)) {
								if (!BMO_edge_flag_test(bm, e, SEL_FLAG) &&
								    !BM_elem_flag_test(e, BM_ELEM_HIDDEN))
								{
									BMO_edge_flag_enable(bm, e, SEL_FLAG);
									BMO_vert_flag_enable(bm, BM_edge_other_vert(e, v), SEL_FLAG);
								}
							}
						}
					}
				}
			}
		}
	}
	else {
		BMFace *f;

		BMO_ITER (f, &siter, op->slots_in, "geom", BM_FACE) {
			BMIter liter;
			BMLoop *l;

			BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
				if (!use_faces_step) {
					BMIter fiter;
					BMFace *f_other;

					BM_ITER_ELEM (f_other, &fiter, l->e, BM_FACES_OF_EDGE) {
						if (!BMO_face_flag_test(bm, f_other, SEL_ORIG | SEL_FLAG) &&
						    !BM_elem_flag_test(f_other, BM_ELEM_HIDDEN))
						{
							BMO_face_flag_enable(bm, f_other, SEL_FLAG);
						}
					}
				}
				else {
					BMIter fiter;
					BMFace *f_other;

					BM_ITER_ELEM (f_other, &fiter, l->v, BM_FACES_OF_VERT) {
						if (!BMO_face_flag_test(bm, f_other, SEL_ORIG | SEL_FLAG) &&
						    !BM_elem_flag_test(f_other, BM_ELEM_HIDDEN))
						{
							BMO_face_flag_enable(bm, f_other, SEL_FLAG);
						}
					}
				}
			}
		}
	}
}

static void bmo_region_extend_contract(
        BMesh *bm, BMOperator *op,
        const bool use_faces, const bool use_faces_step)
{
	BMOIter siter;

	if (!use_faces) {
		BMVert *v;

		BMO_ITER (v, &siter, op->slots_in, "geom", BM_VERT) {
			bool found = false;

			if (!use_faces_step) {
				BMIter eiter;
				BMEdge *e;

				BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
					if (!BMO_edge_flag_test(bm, e, SEL_ORIG)) {
						found = true;
						break;
					}
				}
			}
			else {
				BMIter fiter;
				BMFace *f;

				BM_ITER_ELEM (f, &fiter, v, BM_FACES_OF_VERT) {
					if (!BMO_face_flag_test(bm, f, SEL_ORIG)) {
						found = true;
						break;
					}
				}

				/* handle wire edges (when stepping over faces) */
				if (!found) {
					BMIter eiter;
					BMEdge *e;

					BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
						if (BM_edge_is_wire(e)) {
							if (!BMO_edge_flag_test(bm, e, SEL_ORIG)) {
								found = true;
								break;
							}
						}
					}
				}
			}

			if (found) {
				BMIter eiter;
				BMEdge *e;

				BMO_vert_flag_enable(bm, v, SEL_FLAG);

				BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
					BMO_edge_flag_enable(bm, e, SEL_FLAG);
				}
			}
		}
	}
	else {
		BMFace *f;

		BMO_ITER (f, &siter, op->slots_in, "geom", BM_FACE) {
			BMIter liter;
			BMLoop *l;

			BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {

				if (!use_faces_step) {
					BMIter fiter;
					BMFace *f_other;

					BM_ITER_ELEM (f_other, &fiter, l->e, BM_FACES_OF_EDGE) {
						if (!BMO_face_flag_test(bm, f_other, SEL_ORIG)) {
							BMO_face_flag_enable(bm, f, SEL_FLAG);
							break;
						}
					}
				}
				else {
					BMIter fiter;
					BMFace *f_other;

					BM_ITER_ELEM (f_other, &fiter, l->v, BM_FACES_OF_VERT) {
						if (!BMO_face_flag_test(bm, f_other, SEL_ORIG)) {
							BMO_face_flag_enable(bm, f, SEL_FLAG);
							break;
						}
					}
				}
			}
		}
	}
}

void bmo_region_extend_exec(BMesh *bm, BMOperator *op)
{
	const bool use_faces = BMO_slot_bool_get(op->slots_in, "use_faces");
	const bool use_face_step = BMO_slot_bool_get(op->slots_in, "use_face_step");
	const bool constrict = BMO_slot_bool_get(op->slots_in, "use_contract");

	BMO_slot_buffer_flag_enable(bm, op->slots_in, "geom", BM_ALL_NOLOOP, SEL_ORIG);

	if (constrict) {
		bmo_region_extend_contract(bm, op, use_faces, use_face_step);
	}
	else {
		bmo_region_extend_expand(bm, op, use_faces, use_face_step);
	}

	BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "geom.out", BM_ALL_NOLOOP, SEL_FLAG);
}

void bmo_smooth_vert_exec(BMesh *UNUSED(bm), BMOperator *op)
{
	BMOIter siter;
	BMIter iter;
	BMVert *v;
	BMEdge *e;
	float (*cos)[3] = MEM_mallocN(sizeof(*cos) * BMO_slot_buffer_count(op->slots_in, "verts"), __func__);
	float *co, *co2, clip_dist = BMO_slot_float_get(op->slots_in, "clip_dist");
	const float fac = BMO_slot_float_get(op->slots_in, "factor");
	int i, j, clipx, clipy, clipz;
	int xaxis, yaxis, zaxis;

	clipx = BMO_slot_bool_get(op->slots_in, "mirror_clip_x");
	clipy = BMO_slot_bool_get(op->slots_in, "mirror_clip_y");
	clipz = BMO_slot_bool_get(op->slots_in, "mirror_clip_z");

	xaxis = BMO_slot_bool_get(op->slots_in, "use_axis_x");
	yaxis = BMO_slot_bool_get(op->slots_in, "use_axis_y");
	zaxis = BMO_slot_bool_get(op->slots_in, "use_axis_z");

	i = 0;
	BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {

		co = cos[i];
		zero_v3(co);

		j = 0;
		BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
			co2 = BM_edge_other_vert(e, v)->co;
			add_v3_v3v3(co, co, co2);
			j += 1;
		}

		if (!j) {
			copy_v3_v3(co, v->co);
			i++;
			continue;
		}

		mul_v3_fl(co, 1.0f / (float)j);
		interp_v3_v3v3(co, v->co, co, fac);

		if (clipx && fabsf(v->co[0]) <= clip_dist)
			co[0] = 0.0f;
		if (clipy && fabsf(v->co[1]) <= clip_dist)
			co[1] = 0.0f;
		if (clipz && fabsf(v->co[2]) <= clip_dist)
			co[2] = 0.0f;

		i++;
	}

	i = 0;
	BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
		if (xaxis)
			v->co[0] = cos[i][0];
		if (yaxis)
			v->co[1] = cos[i][1];
		if (zaxis)
			v->co[2] = cos[i][2];

		i++;
	}

	MEM_freeN(cos);
}

/**************************************************************************** *
 * Cycle UVs for a face
 **************************************************************************** */

void bmo_rotate_uvs_exec(BMesh *bm, BMOperator *op)
{
	BMOIter fs_iter;  /* selected faces iterator */
	BMFace *fs;       /* current face */
	BMIter l_iter;    /* iteration loop */

	const bool use_ccw = BMO_slot_bool_get(op->slots_in, "use_ccw");
	const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV);

	if (cd_loop_uv_offset != -1) {
		BMO_ITER (fs, &fs_iter, op->slots_in, "faces", BM_FACE) {
			if (use_ccw == false) {  /* same loops direction */
				BMLoop *lf;	/* current face loops */
				MLoopUV *f_luv; /* first face loop uv */
				float p_uv[2];	/* previous uvs */
				float t_uv[2];	/* tmp uvs */

				int n = 0;
				BM_ITER_ELEM (lf, &l_iter, fs, BM_LOOPS_OF_FACE) {
					/* current loop uv is the previous loop uv */
					MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(lf, cd_loop_uv_offset);
					if (n == 0) {
						f_luv = luv;
						copy_v2_v2(p_uv, luv->uv);
					}
					else {
						copy_v2_v2(t_uv, luv->uv);
						copy_v2_v2(luv->uv, p_uv);
						copy_v2_v2(p_uv, t_uv);
					}
					n++;
				}

				copy_v2_v2(f_luv->uv, p_uv);
			}
			else { /* counter loop direction */
				BMLoop *lf;	/* current face loops */
				MLoopUV *p_luv; /* previous loop uv */
				MLoopUV *luv;
				float t_uv[2];	/* current uvs */

				int n = 0;
				BM_ITER_ELEM (lf, &l_iter, fs, BM_LOOPS_OF_FACE) {
					/* previous loop uv is the current loop uv */
					luv = BM_ELEM_CD_GET_VOID_P(lf, cd_loop_uv_offset);
					if (n == 0) {
						p_luv = luv;
						copy_v2_v2(t_uv, luv->uv);
					}
					else {
						copy_v2_v2(p_luv->uv, luv->uv);
						p_luv = luv;
					}
					n++;
				}

				copy_v2_v2(luv->uv, t_uv);
			}
		}
	}
}

/**************************************************************************** *
 * Reverse UVs for a face
 **************************************************************************** */

static void bm_face_reverse_uvs(BMFace *f, const int cd_loop_uv_offset)
{
	BMIter iter;
	BMLoop *l;
	int i;

	float (*uvs)[2] = BLI_array_alloca(uvs, f->len);

	BM_ITER_ELEM_INDEX (l, &iter, f, BM_LOOPS_OF_FACE, i) {
		MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
		copy_v2_v2(uvs[i], luv->uv);
	}

	/* now that we have the uvs in the array, reverse! */
	BM_ITER_ELEM_INDEX (l, &iter, f, BM_LOOPS_OF_FACE, i) {
		/* current loop uv is the previous loop uv */
		MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
		copy_v2_v2(luv->uv, uvs[(f->len - i - 1)]);
	}
}
void bmo_reverse_uvs_exec(BMesh *bm, BMOperator *op)
{
	BMOIter iter;
	BMFace *f;
	const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV);

	if (cd_loop_uv_offset != -1) {
		BMO_ITER (f, &iter, op->slots_in, "faces", BM_FACE) {
			bm_face_reverse_uvs(f, cd_loop_uv_offset);
		}
	}
}

/**************************************************************************** *
 * Cycle colors for a face
 **************************************************************************** */

void bmo_rotate_colors_exec(BMesh *bm, BMOperator *op)
{
	BMOIter fs_iter;  /* selected faces iterator */
	BMFace *fs;       /* current face */
	BMIter l_iter;    /* iteration loop */

	const bool use_ccw = BMO_slot_bool_get(op->slots_in, "use_ccw");
	const int cd_loop_color_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPCOL);

	if (cd_loop_color_offset != -1) {
		BMO_ITER (fs, &fs_iter, op->slots_in, "faces", BM_FACE) {
			if (use_ccw == false) {  /* same loops direction */
				BMLoop *lf;	/* current face loops */
				MLoopCol *f_lcol; /* first face loop color */
				MLoopCol p_col;	/* previous color */
				MLoopCol t_col;	/* tmp color */

				int n = 0;
				BM_ITER_ELEM (lf, &l_iter, fs, BM_LOOPS_OF_FACE) {
					/* current loop color is the previous loop color */
					MLoopCol *lcol = BM_ELEM_CD_GET_VOID_P(lf, cd_loop_color_offset);
					if (n == 0) {
						f_lcol = lcol;
						p_col = *lcol;
					}
					else {
						t_col = *lcol;
						*lcol = p_col;
						p_col = t_col;
					}
					n++;
				}

				*f_lcol = p_col;
			}
			else {  /* counter loop direction */
				BMLoop *lf;	/* current face loops */
				MLoopCol *p_lcol; /* previous loop color */
				MLoopCol *lcol;
				MLoopCol t_col;	/* current color */

				int n = 0;
				BM_ITER_ELEM (lf, &l_iter, fs, BM_LOOPS_OF_FACE) {
					/* previous loop color is the current loop color */
					lcol = BM_ELEM_CD_GET_VOID_P(lf, cd_loop_color_offset);
					if (n == 0) {
						p_lcol = lcol;
						t_col = *lcol;
					}
					else {
						*p_lcol = *lcol;
						p_lcol = lcol;
					}
					n++;
				}

				*lcol = t_col;
			}
		}
	}
}

/*************************************************************************** *
 * Reverse colors for a face
 *************************************************************************** */
static void bm_face_reverse_colors(BMFace *f, const int cd_loop_color_offset)
{
	BMIter iter;
	BMLoop *l;
	int i;

	MLoopCol *cols = BLI_array_alloca(cols, f->len);

	BM_ITER_ELEM_INDEX (l, &iter, f, BM_LOOPS_OF_FACE, i) {
		MLoopCol *lcol = BM_ELEM_CD_GET_VOID_P(l, cd_loop_color_offset);
		cols[i] = *lcol;
	}

	/* now that we have the uvs in the array, reverse! */
	BM_ITER_ELEM_INDEX (l, &iter, f, BM_LOOPS_OF_FACE, i) {
		/* current loop uv is the previous loop color */
		MLoopCol *lcol = BM_ELEM_CD_GET_VOID_P(l, cd_loop_color_offset);
		*lcol = cols[(f->len - i - 1)];
	}
}
void bmo_reverse_colors_exec(BMesh *bm, BMOperator *op)
{
	BMOIter iter;
	BMFace *f;
	const int cd_loop_color_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPCOL);

	if (cd_loop_color_offset != -1) {
		BMO_ITER (f, &iter, op->slots_in, "faces", BM_FACE) {
			bm_face_reverse_colors(f, cd_loop_color_offset);
		}
	}
}
