/*  deform.c   June 2001
 *  
 *  support for deformation groups
 * 
 *	Reevan McKay
 *
 * $Id$
 *
 * ***** 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.
 *
 * The Original Code is: all of this file.
 *
 * Contributor(s): none yet.
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file blender/blenkernel/intern/deform.c
 *  \ingroup bke
 */


#include <string.h>
#include <math.h>
#include "ctype.h"

#include "MEM_guardedalloc.h"

#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"

#include "BKE_deform.h"

#include "BLI_blenlib.h"


void defgroup_copy_list (ListBase *outbase, ListBase *inbase)
{
	bDeformGroup *defgroup, *defgroupn;

	outbase->first= outbase->last= NULL;

	for (defgroup = inbase->first; defgroup; defgroup=defgroup->next){
		defgroupn= defgroup_duplicate(defgroup);
		BLI_addtail(outbase, defgroupn);
	}
}

bDeformGroup *defgroup_duplicate (bDeformGroup *ingroup)
{
	bDeformGroup *outgroup;

	if (!ingroup)
		return NULL;

	outgroup=MEM_callocN(sizeof(bDeformGroup), "copy deformGroup");
	
	/* For now, just copy everything over. */
	memcpy (outgroup, ingroup, sizeof(bDeformGroup));

	outgroup->next=outgroup->prev=NULL;

	return outgroup;
}

/* copy & overwrite weights */
void defvert_copy (MDeformVert *dvert_r, const MDeformVert *dvert)
{
	if(dvert_r->totweight == dvert->totweight) {
		if(dvert->totweight)
			memcpy(dvert_r->dw, dvert->dw, dvert->totweight * sizeof(MDeformWeight));
	}
	else {
		if(dvert_r->dw)
			MEM_freeN(dvert_r->dw);

		if(dvert->totweight)
			dvert_r->dw= MEM_dupallocN(dvert->dw);
		else
			dvert_r->dw= NULL;

		dvert_r->totweight = dvert->totweight;
	}
}

/* only sync over matching weights, don't add or remove groups
 * warning, loop within loop.
 */
void defvert_sync (MDeformVert *dvert_r, const MDeformVert *dvert, int use_verify)
{
	if(dvert->totweight && dvert_r->totweight) {
		int i;
		MDeformWeight *dw;
		for(i=0, dw=dvert->dw; i < dvert->totweight; i++, dw++) {
			MDeformWeight *dw_r;
			if(use_verify)	dw_r= defvert_find_index(dvert_r, dw->def_nr);
			else			dw_r= defvert_verify_index(dvert_r, dw->def_nr);

			if(dw_r) {
				dw_r->weight= dw->weight;
			}
		}
	}
}

/* be sure all flip_map values are valid */
void defvert_sync_mapped (MDeformVert *dvert_r, const MDeformVert *dvert, const int *flip_map, int use_verify)
{
	if(dvert->totweight && dvert_r->totweight) {
		int i;
		MDeformWeight *dw;
		for(i=0, dw=dvert->dw; i < dvert->totweight; i++, dw++) {
			MDeformWeight *dw_r;
			if(use_verify)	dw_r= defvert_find_index(dvert_r, flip_map[dw->def_nr]);
			else			dw_r= defvert_verify_index(dvert_r, flip_map[dw->def_nr]);

			if(dw_r) {
				dw_r->weight= dw->weight;
			}
		}
	}
}

/* be sure all flip_map values are valid */
void defvert_remap (MDeformVert *dvert, int *map)
{
	MDeformWeight *dw;
	int i;
	for(i=0, dw=dvert->dw; i<dvert->totweight; i++, dw++) {
		dw->def_nr= map[dw->def_nr];
	}
}

void defvert_normalize (MDeformVert *dvert)
{
	if(dvert->totweight<=0) {
		/* nothing */
	}
	else if (dvert->totweight==1) {
		dvert->dw[0].weight= 1.0f;
	}
	else {
		int i;
		float tot= 0.0f;
		MDeformWeight *dw;
		for(i=0, dw=dvert->dw; i < dvert->totweight; i++, dw++)
			tot += dw->weight;

		if(tot > 0.0f) {
			for(i=0, dw=dvert->dw; i < dvert->totweight; i++, dw++)
				dw->weight /= tot;
		}
	}
}

void defvert_flip (MDeformVert *dvert, const int *flip_map)
{
	MDeformWeight *dw;
	int i;

	for(dw= dvert->dw, i=0; i<dvert->totweight; dw++, i++)
		if(flip_map[dw->def_nr] >= 0)
			dw->def_nr= flip_map[dw->def_nr];
}


bDeformGroup *defgroup_find_name (Object *ob, const char *name)
{
	/* return a pointer to the deform group with this name
	 * or return NULL otherwise.
	 */
	bDeformGroup *curdef;

	for (curdef = ob->defbase.first; curdef; curdef=curdef->next) {
		if (!strcmp(curdef->name, name)) {
			return curdef;
		}
	}
	return NULL;
}

int defgroup_name_index (Object *ob, const char *name)
{
	/* Return the location of the named deform group within the list of
	 * deform groups. This function is a combination of defgroup_find_index and
	 * defgroup_find_name. The other two could be called instead, but that
	 * require looping over the vertexgroups twice.
	 */
	bDeformGroup *curdef;
	int def_nr;
	
	if(name && name[0] != '\0') {
		for (curdef=ob->defbase.first, def_nr=0; curdef; curdef=curdef->next, def_nr++) {
			if (!strcmp(curdef->name, name))
				return def_nr;
		}
	}

	return -1;
}

int defgroup_find_index (Object *ob, bDeformGroup *dg)
{
	/* Fetch the location of this deform group
	 * within the linked list of deform groups.
	 * (this number is stored in the deform
	 * weights of the deform verts to link them
	 * to this deform group).
	 *
	 * note: this is zero based, ob->actdef starts at 1.
	 */

	bDeformGroup *eg;
	int def_nr;

	eg = ob->defbase.first;
	def_nr = 0;

	/* loop through all deform groups */
	while (eg != NULL) {

		/* if the current deform group is
		 * the one we are after, return
		 * def_nr
		 */
		if (eg == dg) {
			break;
		}
		++def_nr;
		eg = eg->next;
	}

	/* if there was no deform group found then
	 * return -1 (should set up a nice symbolic
	 * constant for this)
	 */
	if (eg == NULL) return -1;
	
	return def_nr;
}

/* note, must be freed */
int *defgroup_flip_map(Object *ob, int use_default)
{
	bDeformGroup *dg;
	int totdg= BLI_countlist(&ob->defbase);

	if(totdg==0) {
		return NULL;
	}
	else {
		char name[sizeof(dg->name)];
		int i, flip_num, *map= MEM_mallocN(totdg * sizeof(int), "get_defgroup_flip_map");

		memset(map, -1, totdg * sizeof(int));

		for (dg=ob->defbase.first, i=0; dg; dg=dg->next, i++) {
			if(map[i] == -1) { /* may be calculated previously */

				/* incase no valid value is found, use this */
				if(use_default)
					map[i]= i;

				flip_side_name(name, dg->name, 0);
				if(strcmp(name, dg->name)) {
					flip_num= defgroup_name_index(ob, name);
					if(flip_num >= 0) {
						map[i]= flip_num;
						map[flip_num]= i; /* save an extra lookup */
					}
				}
			}
		}
		return map;
	}
}

int defgroup_flip_index(Object *ob, int index, int use_default)
{
	bDeformGroup *dg= BLI_findlink(&ob->defbase, index);
	int flip_index = -1;

	if(dg) {
		char name[sizeof(dg->name)];
		flip_side_name(name, dg->name, 0);

		if(strcmp(name, dg->name))
			flip_index= defgroup_name_index(ob, name);
	}

	return (flip_index==-1 && use_default) ? index : flip_index;
}

static int defgroup_find_name_dupe(const char *name, bDeformGroup *dg, Object *ob)
{
	bDeformGroup *curdef;
	
	for (curdef = ob->defbase.first; curdef; curdef=curdef->next) {
		if (dg!=curdef) {
			if (!strcmp(curdef->name, name)) {
				return 1;
			}
		}
	}

	return 0;
}

static int defgroup_unique_check(void *arg, const char *name)
{
	struct {Object *ob; void *dg;} *data= arg;
	return defgroup_find_name_dupe(name, data->dg, data->ob);
}

void defgroup_unique_name (bDeformGroup *dg, Object *ob)
{
	struct {Object *ob; void *dg;} data;
	data.ob= ob;
	data.dg= dg;

	BLI_uniquename_cb(defgroup_unique_check, &data, "Group", '.', dg->name, sizeof(dg->name));
}

/* finds the best possible flipped name. For renaming; check for unique names afterwards */
/* if strip_number: removes number extensions
 * note: dont use sizeof() for 'name' or 'from_name' */
void flip_side_name (char name[MAX_VGROUP_NAME], const char from_name[MAX_VGROUP_NAME], int strip_number)
{
	int     len;
	char    prefix[MAX_VGROUP_NAME]=  "";   /* The part before the facing */
	char    suffix[MAX_VGROUP_NAME]=  "";   /* The part after the facing */
	char    replace[MAX_VGROUP_NAME]= "";   /* The replacement string */
	char    number[MAX_VGROUP_NAME]=  "";   /* The number extension string */
	char    *index=NULL;

	len= BLI_strnlen(from_name, MAX_VGROUP_NAME);
	if(len<3) return; // we don't do names like .R or .L

	BLI_strncpy(name, from_name, MAX_VGROUP_NAME);

	/* We first check the case with a .### extension, let's find the last period */
	if(isdigit(name[len-1])) {
		index= strrchr(name, '.'); // last occurrence
		if (index && isdigit(index[1]) ) { // doesnt handle case bone.1abc2 correct..., whatever!
			if(strip_number==0)
				BLI_strncpy(number, index, sizeof(number));
			*index= 0;
			len= BLI_strnlen(name, MAX_VGROUP_NAME);
		}
	}

	BLI_strncpy(prefix, name, sizeof(prefix));

#define IS_SEPARATOR(a) ((a)=='.' || (a)==' ' || (a)=='-' || (a)=='_')

	/* first case; separator . - _ with extensions r R l L  */
	if( IS_SEPARATOR(name[len-2]) ) {
		switch(name[len-1]) {
			case 'l':
				prefix[len-1]= 0;
				strcpy(replace, "r");
				break;
			case 'r':
				prefix[len-1]= 0;
				strcpy(replace, "l");
				break;
			case 'L':
				prefix[len-1]= 0;
				strcpy(replace, "R");
				break;
			case 'R':
				prefix[len-1]= 0;
				strcpy(replace, "L");
				break;
		}
	}
	/* case; beginning with r R l L , with separator after it */
	else if( IS_SEPARATOR(name[1]) ) {
		switch(name[0]) {
			case 'l':
				strcpy(replace, "r");
				strcpy(suffix, name+1);
				prefix[0]= 0;
				break;
			case 'r':
				strcpy(replace, "l");
				strcpy(suffix, name+1);
				prefix[0]= 0;
				break;
			case 'L':
				strcpy(replace, "R");
				strcpy(suffix, name+1);
				prefix[0]= 0;
				break;
			case 'R':
				strcpy(replace, "L");
				strcpy(suffix, name+1);
				prefix[0]= 0;
				break;
		}
	}
	else if(len > 5) {
		/* hrms, why test for a separator? lets do the rule 'ultimate left or right' */
		index = BLI_strcasestr(prefix, "right");
		if (index==prefix || index==prefix+len-5) {
			if(index[0]=='r')
				strcpy (replace, "left");
			else {
				if(index[1]=='I')
					strcpy (replace, "LEFT");
				else
					strcpy (replace, "Left");
			}
			*index= 0;
			strcpy (suffix, index+5);
		}
		else {
			index = BLI_strcasestr(prefix, "left");
			if (index==prefix || index==prefix+len-4) {
				if(index[0]=='l')
					strcpy (replace, "right");
				else {
					if(index[1]=='E')
						strcpy (replace, "RIGHT");
					else
						strcpy (replace, "Right");
				}
				*index= 0;
				strcpy (suffix, index+4);
			}
		}
	}

#undef IS_SEPARATOR

	BLI_snprintf (name, MAX_VGROUP_NAME, "%s%s%s%s", prefix, replace, suffix, number);
}

float defvert_find_weight(const struct MDeformVert *dvert, const int group_num)
{
	MDeformWeight *dw= defvert_find_index(dvert, group_num);
	return dw ? dw->weight : 0.0f;
}

float defvert_array_find_weight_safe(const struct MDeformVert *dvert, int index, int group_num)
{
	if(group_num == -1 || dvert == NULL)
		return 1.0f;

	return defvert_find_weight(dvert+index, group_num);
}


MDeformWeight *defvert_find_index(const MDeformVert *dvert, const int defgroup)
{
	if(dvert && defgroup >= 0) {
		MDeformWeight *dw = dvert->dw;
		int i;

		for(i=dvert->totweight; i>0; i--, dw++)
			if(dw->def_nr == defgroup)
				return dw;
	}

	return NULL;
}

/* Ensures that mv has a deform weight entry for the specified defweight group */
/* Note this function is mirrored in editmesh_tools.c, for use for editvertices */
MDeformWeight *defvert_verify_index(MDeformVert *dv, const int defgroup)
{
	MDeformWeight *newdw;

	/* do this check always, this function is used to check for it */
	if(!dv || defgroup<0)
		return NULL;

	newdw = defvert_find_index(dv, defgroup);
	if(newdw)
		return newdw;

	newdw = MEM_callocN(sizeof(MDeformWeight)*(dv->totweight+1), "deformWeight");
	if(dv->dw) {
		memcpy(newdw, dv->dw, sizeof(MDeformWeight)*dv->totweight);
		MEM_freeN(dv->dw);
	}
	dv->dw=newdw;

	dv->dw[dv->totweight].weight=0.0f;
	dv->dw[dv->totweight].def_nr=defgroup;
	/* Group index */

	dv->totweight++;

	return dv->dw+(dv->totweight-1);
}
