/*
 * ***** 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.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file blender/editors/util/ed_transverts.c
 *  \ingroup edutil
 */

#include "MEM_guardedalloc.h"

#include "DNA_armature_types.h"
#include "DNA_curve_types.h"
#include "DNA_lattice_types.h"
#include "DNA_meta_types.h"
#include "DNA_scene_types.h"
#include "DNA_object_types.h"

#include "BLI_blenlib.h"
#include "BLI_utildefines.h"
#include "BLI_math.h"

#include "BKE_curve.h"
#include "BKE_depsgraph.h"
#include "BKE_lattice.h"
#include "BKE_editmesh.h"
#include "BKE_DerivedMesh.h"
#include "BKE_context.h"

#include "ED_armature.h"

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


/* copied from editobject.c, now uses (almost) proper depgraph */
void ED_transverts_update_obedit(TransVertStore *tvs, Object *obedit)
{
	const int mode = tvs->mode;
	BLI_assert(ED_transverts_check_obedit(obedit) == true);

	DAG_id_tag_update(obedit->data, 0);

	if (obedit->type == OB_MESH) {
		BMEditMesh *em = BKE_editmesh_from_object(obedit);
		BM_mesh_normals_update(em->bm);
	}
	else if (ELEM(obedit->type, OB_CURVE, OB_SURF)) {
		Curve *cu = obedit->data;
		ListBase *nurbs = BKE_curve_editNurbs_get(cu);
		Nurb *nu = nurbs->first;

		while (nu) {
			/* keep handles' vectors unchanged */
			if (nu->bezt && (mode & TM_SKIP_HANDLES)) {
				int a = nu->pntsu;
				TransVert *tv = tvs->transverts;
				BezTriple *bezt = nu->bezt;

				while (a--) {
					if (bezt->hide == 0) {
						bool skip_handle = false;
						if (bezt->f2 & SELECT)
							skip_handle = (mode & TM_SKIP_HANDLES) != 0;

						if ((bezt->f1 & SELECT) && !skip_handle) {
							BLI_assert(tv->loc == bezt->vec[0]);
							tv++;
						}

						if (bezt->f2 & SELECT) {
							float v[3];

							if (((bezt->f1 & SELECT) && !skip_handle) == 0) {
								sub_v3_v3v3(v, tv->loc, tv->oldloc);
								add_v3_v3(bezt->vec[0], v);
							}

							if (((bezt->f3 & SELECT) && !skip_handle) == 0) {
								sub_v3_v3v3(v, tv->loc, tv->oldloc);
								add_v3_v3(bezt->vec[2], v);
							}

							BLI_assert(tv->loc == bezt->vec[1]);
							tv++;
						}

						if ((bezt->f3 & SELECT) && !skip_handle) {
							BLI_assert(tv->loc == bezt->vec[2]);
							tv++;
						}
					}

					bezt++;
				}
			}

			BKE_nurb_test2D(nu);
			BKE_nurb_handles_test(nu, true); /* test for bezier too */
			nu = nu->next;
		}
	}
	else if (obedit->type == OB_ARMATURE) {
		bArmature *arm = obedit->data;
		EditBone *ebo;
		TransVert *tv = tvs->transverts;
		int a = 0;

		/* Ensure all bone tails are correctly adjusted */
		for (ebo = arm->edbo->first; ebo; ebo = ebo->next) {
			/* adjust tip if both ends selected */
			if ((ebo->flag & BONE_ROOTSEL) && (ebo->flag & BONE_TIPSEL)) {
				if (tv) {
					float diffvec[3];

					sub_v3_v3v3(diffvec, tv->loc, tv->oldloc);
					add_v3_v3(ebo->tail, diffvec);

					a++;
					if (a < tvs->transverts_tot) tv++;
				}
			}
		}

		/* Ensure all bones are correctly adjusted */
		for (ebo = arm->edbo->first; ebo; ebo = ebo->next) {
			if ((ebo->flag & BONE_CONNECTED) && ebo->parent) {
				/* If this bone has a parent tip that has been moved */
				if (ebo->parent->flag & BONE_TIPSEL) {
					copy_v3_v3(ebo->head, ebo->parent->tail);
				}
				/* If this bone has a parent tip that has NOT been moved */
				else {
					copy_v3_v3(ebo->parent->tail, ebo->head);
				}
			}
		}
		if (arm->flag & ARM_MIRROR_EDIT)
			transform_armature_mirror_update(obedit);
	}
	else if (obedit->type == OB_LATTICE) {
		Lattice *lt = obedit->data;

		if (lt->editlatt->latt->flag & LT_OUTSIDE)
			outside_lattice(lt->editlatt->latt);
	}
}

static void set_mapped_co(void *vuserdata, int index, const float co[3],
                          const float UNUSED(no[3]), const short UNUSED(no_s[3]))
{
	void **userdata = vuserdata;
	BMEditMesh *em = userdata[0];
	TransVert *tv = userdata[1];
	BMVert *eve = BM_vert_at_index(em->bm, index);

	if (BM_elem_index_get(eve) != TM_INDEX_SKIP) {
		tv = &tv[BM_elem_index_get(eve)];

		/* be clever, get the closest vertex to the original,
		 * behaves most logically when the mirror modifier is used for eg [#33051]*/
		if ((tv->flag & TX_VERT_USE_MAPLOC) == 0) {
			/* first time */
			copy_v3_v3(tv->maploc, co);
			tv->flag |= TX_VERT_USE_MAPLOC;
		}
		else {
			/* find best location to use */
			if (len_squared_v3v3(eve->co, co) < len_squared_v3v3(eve->co, tv->maploc)) {
				copy_v3_v3(tv->maploc, co);
			}
		}
	}
}

bool ED_transverts_check_obedit(Object *obedit)
{
	return (ELEM(obedit->type, OB_ARMATURE, OB_LATTICE, OB_MESH, OB_SURF, OB_CURVE, OB_MBALL));
}

void ED_transverts_create_from_obedit(TransVertStore *tvs, Object *obedit, const int mode)
{
	Nurb *nu;
	BezTriple *bezt;
	BPoint *bp;
	TransVert *tv = NULL;
	MetaElem *ml;
	BMVert *eve;
	EditBone *ebo;
	int a;

	tvs->transverts_tot = 0;

	if (obedit->type == OB_MESH) {
		BMEditMesh *em = BKE_editmesh_from_object(obedit);
		BMesh *bm = em->bm;
		BMIter iter;
		void *userdata[2] = {em, NULL};
		/*int proptrans = 0; */ /*UNUSED*/

		/* abuses vertex index all over, set, just set dirty here,
		 * perhaps this could use its own array instead? - campbell */

		/* transform now requires awareness for select mode, so we tag the f1 flags in verts */
		tvs->transverts_tot = 0;
		if (em->selectmode & SCE_SELECT_VERTEX) {
			BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
				if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN) && BM_elem_flag_test(eve, BM_ELEM_SELECT)) {
					BM_elem_index_set(eve, TM_INDEX_ON); /* set_dirty! */
					tvs->transverts_tot++;
				}
				else {
					BM_elem_index_set(eve, TM_INDEX_OFF);  /* set_dirty! */
				}
			}
		}
		else if (em->selectmode & SCE_SELECT_EDGE) {
			BMEdge *eed;

			BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
				BM_elem_index_set(eve, TM_INDEX_OFF);  /* set_dirty! */
			}

			BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) {
				if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN) && BM_elem_flag_test(eed, BM_ELEM_SELECT)) {
					BM_elem_index_set(eed->v1, TM_INDEX_ON);  /* set_dirty! */
					BM_elem_index_set(eed->v2, TM_INDEX_ON);  /* set_dirty! */
				}
			}

			BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
				if (BM_elem_index_get(eve) == TM_INDEX_ON) tvs->transverts_tot++;
			}
		}
		else {
			BMFace *efa;

			BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
				BM_elem_index_set(eve, TM_INDEX_OFF);  /* set_dirty! */
			}

			BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
				if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN) && BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
					BMIter liter;
					BMLoop *l;

					BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
						BM_elem_index_set(l->v, TM_INDEX_ON); /* set_dirty! */
					}
				}
			}

			BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
				if (BM_elem_index_get(eve) == TM_INDEX_ON) tvs->transverts_tot++;
			}
		}
		/* for any of the 3 loops above which all dirty the indices */
		bm->elem_index_dirty |= BM_VERT;

		/* and now make transverts */
		if (tvs->transverts_tot) {
			tv = tvs->transverts = MEM_callocN(tvs->transverts_tot * sizeof(TransVert), __func__);

			a = 0;
			BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
				if (BM_elem_index_get(eve)) {
					BM_elem_index_set(eve, a);  /* set_dirty! */
					copy_v3_v3(tv->oldloc, eve->co);
					tv->loc = eve->co;
					tv->flag = (BM_elem_index_get(eve) == TM_INDEX_ON) ? SELECT : 0;

					if (mode & TM_CALC_NORMALS) {
						tv->flag |= TX_VERT_USE_NORMAL;
						copy_v3_v3(tv->normal, eve->no);
					}

					tv++;
					a++;
				}
				else {
					BM_elem_index_set(eve, TM_INDEX_SKIP);  /* set_dirty! */
				}
			}
			/* set dirty already, above */

			userdata[1] = tvs->transverts;
		}

		if (tvs->transverts && em->derivedCage) {
			BM_mesh_elem_table_ensure(bm, BM_VERT);
			em->derivedCage->foreachMappedVert(em->derivedCage, set_mapped_co, userdata, DM_FOREACH_NOP);
		}
	}
	else if (obedit->type == OB_ARMATURE) {
		bArmature *arm = obedit->data;
		int totmalloc = BLI_listbase_count(arm->edbo);

		totmalloc *= 2;  /* probably overkill but bones can have 2 trans verts each */

		tv = tvs->transverts = MEM_callocN(totmalloc * sizeof(TransVert), __func__);

		for (ebo = arm->edbo->first; ebo; ebo = ebo->next) {
			if (ebo->layer & arm->layer) {
				const bool tipsel = (ebo->flag & BONE_TIPSEL) != 0;
				const bool rootsel = (ebo->flag & BONE_ROOTSEL) != 0;
				const bool rootok = (!(ebo->parent && (ebo->flag & BONE_CONNECTED) && (ebo->parent->flag & BONE_TIPSEL)));

				if ((tipsel && rootsel) || (rootsel)) {
					/* Don't add the tip (unless mode & TM_ALL_JOINTS, for getting all joints),
					 * otherwise we get zero-length bones as tips will snap to the same
					 * location as heads.
					 */
					if (rootok) {
						copy_v3_v3(tv->oldloc, ebo->head);
						tv->loc = ebo->head;
						tv->flag = SELECT;
						tv++;
						tvs->transverts_tot++;
					}

					if ((mode & TM_ALL_JOINTS) && (tipsel)) {
						copy_v3_v3(tv->oldloc, ebo->tail);
						tv->loc = ebo->tail;
						tv->flag = SELECT;
						tv++;
						tvs->transverts_tot++;
					}
				}
				else if (tipsel) {
					copy_v3_v3(tv->oldloc, ebo->tail);
					tv->loc = ebo->tail;
					tv->flag = SELECT;
					tv++;
					tvs->transverts_tot++;
				}
			}
		}
	}
	else if (ELEM(obedit->type, OB_CURVE, OB_SURF)) {
		Curve *cu = obedit->data;
		int totmalloc = 0;
		ListBase *nurbs = BKE_curve_editNurbs_get(cu);

		for (nu = nurbs->first; nu; nu = nu->next) {
			if (nu->type == CU_BEZIER)
				totmalloc += 3 * nu->pntsu;
			else
				totmalloc += nu->pntsu * nu->pntsv;
		}
		tv = tvs->transverts = MEM_callocN(totmalloc * sizeof(TransVert), __func__);

		nu = nurbs->first;
		while (nu) {
			if (nu->type == CU_BEZIER) {
				a = nu->pntsu;
				bezt = nu->bezt;
				while (a--) {
					if (bezt->hide == 0) {
						bool skip_handle = false;
						if (bezt->f2 & SELECT)
							skip_handle = (mode & TM_SKIP_HANDLES) != 0;

						if ((bezt->f1 & SELECT) && !skip_handle) {
							copy_v3_v3(tv->oldloc, bezt->vec[0]);
							tv->loc = bezt->vec[0];
							tv->flag = bezt->f1 & SELECT;

							if (mode & TM_CALC_NORMALS) {
								tv->flag |= TX_VERT_USE_NORMAL;
								BKE_nurb_bezt_calc_plane(nu, bezt, tv->normal);
							}

							tv++;
							tvs->transverts_tot++;
						}
						if (bezt->f2 & SELECT) {
							copy_v3_v3(tv->oldloc, bezt->vec[1]);
							tv->loc = bezt->vec[1];
							tv->flag = bezt->f2 & SELECT;

							if (mode & TM_CALC_NORMALS) {
								tv->flag |= TX_VERT_USE_NORMAL;
								BKE_nurb_bezt_calc_plane(nu, bezt, tv->normal);
							}

							tv++;
							tvs->transverts_tot++;
						}
						if ((bezt->f3 & SELECT) && !skip_handle) {
							copy_v3_v3(tv->oldloc, bezt->vec[2]);
							tv->loc = bezt->vec[2];
							tv->flag = bezt->f3 & SELECT;

							if (mode & TM_CALC_NORMALS) {
								tv->flag |= TX_VERT_USE_NORMAL;
								BKE_nurb_bezt_calc_plane(nu, bezt, tv->normal);
							}

							tv++;
							tvs->transverts_tot++;
						}
					}
					bezt++;
				}
			}
			else {
				a = nu->pntsu * nu->pntsv;
				bp = nu->bp;
				while (a--) {
					if (bp->hide == 0) {
						if (bp->f1 & SELECT) {
							copy_v3_v3(tv->oldloc, bp->vec);
							tv->loc = bp->vec;
							tv->flag = bp->f1 & SELECT;
							tv++;
							tvs->transverts_tot++;
						}
					}
					bp++;
				}
			}
			nu = nu->next;
		}
	}
	else if (obedit->type == OB_MBALL) {
		MetaBall *mb = obedit->data;
		int totmalloc = BLI_listbase_count(mb->editelems);

		tv = tvs->transverts = MEM_callocN(totmalloc * sizeof(TransVert), __func__);

		ml = mb->editelems->first;
		while (ml) {
			if (ml->flag & SELECT) {
				tv->loc = &ml->x;
				copy_v3_v3(tv->oldloc, tv->loc);
				tv->flag = SELECT;
				tv++;
				tvs->transverts_tot++;
			}
			ml = ml->next;
		}
	}
	else if (obedit->type == OB_LATTICE) {
		Lattice *lt = obedit->data;

		bp = lt->editlatt->latt->def;

		a = lt->editlatt->latt->pntsu * lt->editlatt->latt->pntsv * lt->editlatt->latt->pntsw;

		tv = tvs->transverts = MEM_callocN(a * sizeof(TransVert), __func__);

		while (a--) {
			if (bp->f1 & SELECT) {
				if (bp->hide == 0) {
					copy_v3_v3(tv->oldloc, bp->vec);
					tv->loc = bp->vec;
					tv->flag = bp->f1 & SELECT;
					tv++;
					tvs->transverts_tot++;
				}
			}
			bp++;
		}
	}

	if (!tvs->transverts_tot && tvs->transverts) {
		/* prevent memory leak. happens for curves/latticies due to */
		/* difficult condition of adding points to trans data */
		MEM_freeN(tvs->transverts);
		tvs->transverts = NULL;
	}

	tvs->mode = mode;
}

void ED_transverts_free(TransVertStore *tvs)
{
	MEM_SAFE_FREE(tvs->transverts);
	tvs->transverts_tot = 0;
}

int ED_transverts_poll(bContext *C)
{
	Object *obedit = CTX_data_edit_object(C);
	if (obedit) {
		if (ED_transverts_check_obedit(obedit)) {
			return true;
		}
	}
	return false;
}
