/**
 * $Id: action.c,v 1.12 2004/01/27 15:18:04 hos Exp $
 *
 * ***** BEGIN GPL/BL DUAL 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. The Blender
 * Foundation also sells licenses for use in proprietary software under
 * the Blender License.  See http://www.blender.org/BL/ for information
 * about this.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, 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/BL DUAL LICENSE BLOCK *****
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <math.h>
#include <stdlib.h>	/* for NULL */

#include "MEM_guardedalloc.h"
#include "BLI_arithb.h"
#include "BLI_blenlib.h"

#include "BKE_action.h"
#include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_utildefines.h"

#include "DNA_object_types.h"
#include "DNA_ipo_types.h"
#include "DNA_curve_types.h"
#include "DNA_scene_types.h"
#include "DNA_action_types.h"
#include "DNA_nla_types.h"

#include "BKE_blender.h"
#include "BKE_ipo.h"
#include "BKE_object.h"
#include "BKE_library.h"
#include "BKE_anim.h"
#include "BKE_armature.h"

#include "nla.h"

#include "BKE_constraint.h"
#include "DNA_constraint_types.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/* Local function prototypes */
static void do_pose_constraint_channels(bPose *pose, bAction *act, 
										float ctime);
static void get_constraint_influence_from_pose(bPose *dst, bPose *src);
static void blend_constraints(ListBase *dst, const ListBase *src,
							  float srcweight, short mode);
static void rest_pose(bPose *pose, int clearflag);

/* Implementation */

bPoseChannel *get_pose_channel(const bPose *pose, const char *name)
{
	bPoseChannel *chan;

	for (chan=pose->chanbase.first; chan; chan=chan->next){
		if (!strcmp (chan->name, name))
			return chan;
	}

	return NULL;
}

static void rest_pose(bPose *pose, int clearflag)
{
	bPoseChannel *chan;
	int i;

	if (!pose)
		return;

	for (chan=pose->chanbase.first; chan; chan=chan->next){
		for (i=0; i<3; i++){
			chan->loc[i]=0.0;
			chan->quat[i+1]=0.0;
			chan->size[i]=1.0;
		}
		chan->quat[0]=1.0;
		if (clearflag)
			chan->flag =0;
	}
}

static void blend_constraints(ListBase *dst, const ListBase *src,
							  float srcweight, short mode)
{
	bConstraint *dcon;
	const bConstraint *scon;
	float dstweight = 0;

	switch (mode){
	case POSE_BLEND:
		dstweight = 1.0F - srcweight;
		break;
	case POSE_ADD:
		dstweight = 1.0F;
		break;
	}

	/* Blend constraints */
	for (dcon=dst->first; dcon; dcon=dcon->next){
		for (scon = src->first; scon; scon=scon->next){
			if (!strcmp(scon->name, dcon->name))
				break;
		}
		
		if (scon){
			dcon->enforce = (dcon->enforce*dstweight) + (scon->enforce*srcweight);
			if (mode == POSE_BLEND)
				dcon->enforce/=2.0;
			
			if (dcon->enforce>1.0)
				dcon->enforce=1.0;
			if (dcon->enforce<0.0)
				dcon->enforce=0.0;

		}
	}
}

void blend_poses(bPose *dst, const bPose *src, float srcweight, short mode)
{
	bPoseChannel *dchan;
	const bPoseChannel *schan;
	float	dquat[4], squat[4], mat[3][3];
	float dstweight;
	int i;
	
	switch (mode){
	case POSE_BLEND:
		dstweight = 1.0F - srcweight;
		break;
	case POSE_ADD:
		dstweight = 1.0F;
		break;
	default :
		dstweight = 1.0F;
	}
	
	for (dchan = dst->chanbase.first; dchan; dchan=dchan->next){
		schan = get_pose_channel(src, dchan->name);
		if (schan){
			if (schan->flag & (POSE_ROT|POSE_LOC|POSE_SIZE)){
				
				/* Convert both quats to matrices and then back again. 
				 * This prevents interpolation problems
				 * This sucks because it is slow and stupid
				 */
				
				QuatToMat3(dchan->quat, mat);
				Mat3ToQuat(mat, dquat);
				QuatToMat3(schan->quat, mat);
				Mat3ToQuat(mat, squat);
				
				/* Do the transformation blend */
				for (i=0; i<3; i++){
					if (schan->flag & POSE_LOC)
						dchan->loc[i] = (dchan->loc[i]*dstweight) + (schan->loc[i]*srcweight);
					if (schan->flag & POSE_SIZE)
						dchan->size[i] = 1.0f + ((dchan->size[i]-1.0f)*dstweight) + ((schan->size[i]-1.0f)*srcweight);
					if (schan->flag & POSE_ROT)
						dchan->quat[i+1] = (dquat[i+1]*dstweight) + (squat[i+1]*srcweight);
				}
				
				/* Do one more iteration for the quaternions only and normalize the quaternion if needed */
				if (schan->flag & POSE_ROT)
					dchan->quat[0] = 1.0f + ((dquat[0]-1.0f)*dstweight) + ((squat[0]-1.0f)*srcweight);
				if (mode==POSE_BLEND)
					NormalQuat (dchan->quat);
				dchan->flag |= schan->flag;
			}
		}
	}
}

void clear_pose_constraint_status(Object *ob)
{
	bPoseChannel *chan;

	if (!ob)
		return;
	if (!ob->pose)
		return;

	for (chan = ob->pose->chanbase.first; chan; chan=chan->next){
		chan->flag &= ~PCHAN_DONE;
	}
}

float calc_action_start(const bAction *act)
{
	const bActionChannel *chan;
	const IpoCurve	*icu;
	float size=999999999.0f;
	int	i;
	int	foundvert=0;
	const bConstraintChannel *conchan;


	if (!act)
		return 0;

	for (chan=act->chanbase.first; chan; chan=chan->next){
		for (icu=chan->ipo->curve.first; icu; icu=icu->next)
			for (i=0; i<icu->totvert; i++){
				size = MIN2 (size, icu->bezt[i].vec[1][0]);
				foundvert=1;
				
			}
			for (conchan=chan->constraintChannels.first; conchan; conchan=conchan->next){
				for (icu=conchan->ipo->curve.first; icu; icu=icu->next)
					for (i=0; i<icu->totvert; i++){
						size = MIN2 (size, icu->bezt[i].vec[1][0]);
						foundvert=1;
					}
			}
	}
	
	if (!foundvert)
		return 0;
	else
		return size;
}

float calc_action_end(const bAction *act)
{
	const bActionChannel	*chan;
	const bConstraintChannel *conchan;
	const IpoCurve		*icu;
	float size=0;
	int	i;

	if (!act)
		return 0;

	for (chan=act->chanbase.first; chan; chan=chan->next){
		for (icu=chan->ipo->curve.first; icu; icu=icu->next)
			for (i=0; i<icu->totvert; i++)
				size = MAX2 (size, icu->bezt[i].vec[1][0]);
			
			for (conchan=chan->constraintChannels.first; conchan; conchan=conchan->next){
				for (icu=conchan->ipo->curve.first; icu; icu=icu->next)
					for (i=0; i<icu->totvert; i++)
						size = MAX2 (size, icu->bezt[i].vec[1][0]);
			}
	}
	return size;
}

bPoseChannel *verify_pose_channel(bPose* pose, const char* name)
{
	bPoseChannel *chan;

	if (!pose){
		return NULL;
	}

	/*      See if this channel exists */
	for (chan=pose->chanbase.first; chan; chan=chan->next){
		if (!strcmp (name, chan->name))
			return chan;
	}

	/* If not, create it and add it */
	chan = MEM_callocN(sizeof(bPoseChannel), "verifyPoseChannel");

	strcpy (chan->name, name);
	chan->loc[0] = chan->loc[1] = chan->loc[2] = 0.0F;
	chan->quat[1] = chan->quat[2] = chan->quat[3] = 0.0F;
	chan->quat[0] = 1.0F;
	chan->size[0] = chan->size[1] = chan->size[2] = 1.0F;

	chan->flag |= POSE_ROT|POSE_SIZE|POSE_LOC;

	BLI_addtail (&pose->chanbase, chan);

	return chan;
}

void get_pose_from_pose(bPose **pose, const bPose *src)
{
	const bPoseChannel	*pchan;
	bPoseChannel *newchan;

	if (!src)
		return;
	if (!pose)
		return;

	/* If there is no pose, create one */
	if (!*pose){
		*pose=MEM_callocN (sizeof(bPose), "pose");
	}

	/* Copy the data from the action into the pose */
	for (pchan=src->chanbase.first; pchan; pchan=pchan->next){
		newchan = copy_pose_channel(pchan);
		verify_pose_channel(*pose, pchan->name);
		set_pose_channel(*pose, newchan);
	}
}

static void get_constraint_influence_from_pose(bPose *dst, bPose *src)
{
	bConstraint *dcon, *scon;

	if (!src || !dst)
		return;

	for (dcon = dst->chanbase.first; dcon; dcon=dcon->next){
		for (scon=src->chanbase.first; scon; scon=scon->next){
			if (!strcmp(scon->name, dcon->name))
				break;
		}
		if (scon){
			dcon->enforce = scon->enforce;
		}
	}
}

/* If the pose does not exist, a new one is created */

void get_pose_from_action(bPose **pose, bAction *act, float ctime) 
{
	bActionChannel *achan;
	bPoseChannel	*pchan;
	Ipo				*ipo;
	IpoCurve		*curve;
	

	if (!act)
		return;
	if (!pose)
		return;

	/* If there is no pose, create one */
	if (!*pose){
		*pose=MEM_callocN (sizeof(bPose), "pose");
	}

	/* Copy the data from the action into the pose */
	for (achan=act->chanbase.first; achan; achan=achan->next){
		act->achan= achan;

		ipo = achan->ipo;
		if (ipo){
			pchan=MEM_callocN (sizeof(bPoseChannel), "gpfa_poseChannel");
			strcpy (pchan->name, achan->name);

			act->pchan=pchan;
			/* Evaluates and sets the internal ipo value */
			calc_ipo(ipo, ctime);

			/* Set the pchan flags */
			for (curve = achan->ipo->curve.first; curve; curve=curve->next){
				/*	Skip empty curves */
				if (!curve->totvert)
					continue;

				switch (curve->adrcode){
				case AC_QUAT_X:
				case AC_QUAT_Y:
				case AC_QUAT_Z:
				case AC_QUAT_W:
					pchan->flag |= POSE_ROT;
					break;
				case AC_LOC_X:
				case AC_LOC_Y:
				case AC_LOC_Z:
					pchan->flag |= POSE_LOC;
					break;
				case AC_SIZE_X:
				case AC_SIZE_Y:
				case AC_SIZE_Z:
					pchan->flag |= POSE_SIZE;
					break;
				}
			}

			execute_ipo((ID*)act, achan->ipo);
		
			set_pose_channel(*pose, pchan);
		}
	}
}

void do_all_actions()
{
	Base *base;
	bPose *apose=NULL;
	bPose *tpose=NULL;
	Object *ob;
	bActionStrip *strip;
	int	doit;
	float striptime, frametime, length, actlength;
	float blendfac, stripframe;

	int set;

	/* NEW: current scene ob ipo's */
	base= G.scene->base.first;
	set= 0;

	while(base) {
		
		ob = base->object;

		/* Retrieve data from the NLA */
		if(ob->type==OB_ARMATURE){

			doit=0;

			/* Clear pose */
			if (apose){
				clear_pose(apose);
				MEM_freeN(apose);
			}
			/* Clear pose */
			if (tpose){
				clear_pose(tpose);
				MEM_freeN(tpose);
			}

			copy_pose(&apose, ob->pose, 1);
			copy_pose(&tpose, ob->pose, 1);
			rest_pose(apose, 1);
 
			if (base->object->nlastrips.first){
				rest_pose(base->object->pose, 0);
			}

			for (strip=base->object->nlastrips.first; strip; strip=strip->next){
				doit = 0;
				if (strip->act){
			
					/* Determine if the current frame is within the strip's range */
					length = strip->end-strip->start;
					actlength = strip->actend-strip->actstart;
					striptime = (G.scene->r.cfra-(strip->start)) / length;
					stripframe = (G.scene->r.cfra-(strip->start)) ;


					if (striptime>=0.0){
						
						rest_pose(tpose, 1);

						/* Handle path */
						if (strip->flag & ACTSTRIP_USESTRIDE){
							if (ob->parent && ob->parent->type==OB_CURVE){
								Curve *cu = ob->parent->data;
								float ctime, pdist;

								if (cu->flag & CU_PATH){
									/* Ensure we have a valid path */
									if(cu->path==0 || cu->path->data==0) calc_curvepath(ob->parent);

									/* Find the position on the path */
									ctime= bsystem_time(ob, ob->parent, (float)G.scene->r.cfra, 0.0);
									
									if(calc_ipo_spec(cu->ipo, CU_SPEED, &ctime)==0) {
										ctime /= cu->pathlen;
										CLAMP(ctime, 0.0, 1.0);
									}
									pdist = ctime*cu->path->totdist;
									
									if (strip->stridelen)
										striptime = pdist / strip->stridelen;
									else
										striptime = 0;
									
									striptime = (float)fmod (striptime, 1.0);
									
									frametime = (striptime * actlength) + strip->actstart;
									get_pose_from_action (&tpose, strip->act, bsystem_time(ob, 0, frametime, 0.0));
#ifdef __NLA_BLENDCON
									do_pose_constraint_channels(tpose, strip->act, bsystem_time(ob, 0, frametime, 0.0));
#endif
									doit=1;
								}
							}
						}

						/* Handle repeat */
		
						else if (striptime < 1.0){
							/* Mod to repeat */
							striptime*=strip->repeat;
							striptime = (float)fmod (striptime, 1.0);
							
							frametime = (striptime * actlength) + strip->actstart;
							get_pose_from_action (&tpose, strip->act, bsystem_time(ob, 0, frametime, 0.0));
#ifdef __NLA_BLENDCON
							do_pose_constraint_channels(tpose, strip->act, bsystem_time(ob, 0, frametime, 0.0));
#endif
							doit=1;
						}
						/* Handle extend */
						else{
							if (strip->flag & ACTSTRIP_HOLDLASTFRAME){
								striptime = 1.0;
								frametime = (striptime * actlength) + strip->actstart;
								get_pose_from_action (&tpose, strip->act, bsystem_time(ob, 0, frametime, 0.0));
#ifdef __NLA_BLENDCON
								do_pose_constraint_channels(tpose, strip->act, bsystem_time(ob, 0, frametime, 0.0));
#endif
								doit=1;
							}
						}

						/* Handle blendin & blendout */
						if (doit){
							/* Handle blendin */

							if (strip->blendin>0.0 && stripframe<=strip->blendin && G.scene->r.cfra>=strip->start){
								blendfac = stripframe/strip->blendin;
							}
							else if (strip->blendout>0.0 && stripframe>=(length-strip->blendout) && G.scene->r.cfra<=strip->end){
								blendfac = (length-stripframe)/(strip->blendout);
							}
							else
								blendfac = 1;

							/* Blend this pose with the accumulated pose */
							blend_poses (apose, tpose, blendfac, strip->mode);
#ifdef __NLA_BLENDCON
							blend_constraints(&apose->chanbase, &tpose->chanbase, blendfac, strip->mode);
#endif
						}
					}					
					if (apose){
						get_pose_from_pose(&ob->pose, apose);
#ifdef __NLA_BLENDCON
						get_constraint_influence_from_pose(ob->pose, apose);
#endif
					}
				}
				
			}

			/* Do local action (always overrides the nla actions) */
			/*	At the moment, only constraint ipos on the local action have any effect */
			if(base->object->action) {
				get_pose_from_action (&ob->pose, ob->action, bsystem_time(ob, 0, (float) G.scene->r.cfra, 0.0));
				do_pose_constraint_channels(ob->pose, ob->action, bsystem_time(ob, 0, (float) G.scene->r.cfra, 0.0));
				doit = 1;
			} 
			
			if (doit)
				apply_pose_armature(get_armature(ob), ob->pose, 1);

		}
		base= base->next;
		if(base==0 && set==0 && G.scene->set) {
			set= 1;
			base= G.scene->set->base.first; 
		}
		
	}

	if (apose){
		clear_pose(apose);
		MEM_freeN(apose);
		apose = NULL;
	}	
	if (tpose){
		clear_pose(tpose);
		MEM_freeN(tpose);
		apose = NULL;
	}

}

static void do_pose_constraint_channels(bPose *pose, bAction *act, float ctime)
{
	bPoseChannel *pchan;
	bActionChannel *achan;

	if (!pose || !act)
		return;
	
	for (pchan=pose->chanbase.first; pchan; pchan=pchan->next){
		achan=get_named_actionchannel(act, pchan->name);
		if (achan)
			do_constraint_channels(&pchan->constraints, &achan->constraintChannels, ctime);
	}
}

bActionChannel *get_named_actionchannel(bAction *act, const char *name)
{
	bActionChannel *chan;

	if (!act)
		return NULL;

	for (chan = act->chanbase.first; chan; chan=chan->next){
		if (!strcmp (chan->name, name))
			return chan;
	}

	return NULL;
}

void clear_pose(bPose *pose) 
{
	bPoseChannel *chan;
	
	if (pose->chanbase.first){
		for (chan = pose->chanbase.first; chan; chan=chan->next){
			free_constraints(&chan->constraints);
		}
		BLI_freelistN (&pose->chanbase);
	}
}

void make_local_action(bAction *act)
{
	Object *ob;
	bAction *actn;
	int local=0, lib=0;

	if(act->id.lib==0) return;
	if(act->id.us==1) {
		act->id.lib= 0;
		act->id.flag= LIB_LOCAL;
		new_id(0, (ID *)act, 0);
		return;
	}
	
	ob= G.main->object.first;
	while(ob) {
		if(ob->action==act) {
			if(ob->id.lib) lib= 1;
			else local= 1;
		}
		ob= ob->id.next;
	}
	
	if(local && lib==0) {
		act->id.lib= 0;
		act->id.flag= LIB_LOCAL;
		new_id(0, (ID *)act, 0);
	}
	else if(local && lib) {
		actn= copy_action(act);
		actn->id.us= 0;
		
		ob= G.main->object.first;
		while(ob) {
			if(ob->action==act) {
				
				if(ob->id.lib==0) {
					ob->action = actn;
					ob->activecon = NULL;
					actn->id.us++;
					act->id.us--;
				}
			}
			ob= ob->id.next;
		}
	}
}


void free_action(bAction *act)
{
	bActionChannel *chan;

	/* Free channels */
	for (chan=act->chanbase.first; chan; chan=chan->next){
		if (chan->ipo)
			chan->ipo->id.us--;
		free_constraint_channels(&chan->constraintChannels);
	}

	if (act->chanbase.first)
		BLI_freelistN (&act->chanbase);
}

bAction* copy_action(const bAction *src)
{
	bAction *dst = NULL;
	bActionChannel *dchan, *schan;
	
	if(!src) return NULL;
	
	dst= copy_libblock(src);
	duplicatelist(&(dst->chanbase), &(src->chanbase));

	for (dchan=dst->chanbase.first, schan=src->chanbase.first; dchan; dchan=dchan->next, schan=schan->next){
		dchan->ipo = copy_ipo(dchan->ipo);
		copy_constraint_channels(&dchan->constraintChannels, &schan->constraintChannels);
	}
	dst->id.flag |= LIB_FAKEUSER;
	dst->id.us++;
	return dst;
}

bPoseChannel *copy_pose_channel(const bPoseChannel* src)
{
	bPoseChannel *dst;

	if (!src)
		return NULL;

	dst = MEM_callocN (sizeof(bPoseChannel), "copyPoseChannel");
	memcpy (dst, src, sizeof(bPoseChannel));
	dst->prev=dst->next=NULL;

	return dst;
}

void copy_pose(bPose **dst, const bPose *src, int copycon)
{
	bPose *outPose;
	const bPose * inPose;
	bPoseChannel	*newChan;
	const bPoseChannel *curChan;

	inPose = src;
	
	if (!inPose){
		*dst=NULL;
		return;
	}

	outPose=MEM_callocN(sizeof(bPose), "pose");

	for (curChan=inPose->chanbase.first; curChan; curChan=curChan->next){
		newChan=MEM_callocN(sizeof(bPoseChannel), "copyposePoseChannel");

		strcpy (newChan->name, curChan->name);
		newChan->flag=curChan->flag;

		memcpy (newChan->loc, curChan->loc, sizeof (curChan->loc));
		memcpy (newChan->size, curChan->size, sizeof (curChan->size));
		memcpy (newChan->quat, curChan->quat, sizeof (curChan->quat));
		Mat4CpyMat4 (newChan->obmat, curChan->obmat);

		BLI_addtail (&outPose->chanbase, newChan);
		if (copycon){
			copy_constraints(&newChan->constraints, &curChan->constraints);
		}
	}
	
	*dst=outPose;
}

bPoseChannel *set_pose_channel(bPose *pose, bPoseChannel *chan)
{
	/*	chan is no longer valid for the calling function.
		and should not be used by that function after calling
		this one
	*/
	bPoseChannel	*curChan;

	/*	Determine if an equivalent channel exists already */
	for (curChan=pose->chanbase.first; curChan; curChan=curChan->next){
		if (!strcmp (curChan->name, chan->name)){
			if (chan->flag & POSE_ROT)
				memcpy (curChan->quat, chan->quat, sizeof(chan->quat)); 
			if (chan->flag & POSE_SIZE) 
				memcpy (curChan->size, chan->size, sizeof(chan->size));
			if (chan->flag & POSE_LOC)
				memcpy (curChan->loc, chan->loc, sizeof(chan->loc));
			if (chan->flag & PCHAN_DONE)
				Mat4CpyMat4 (curChan->obmat, chan->obmat);

			curChan->flag |= chan->flag;
			MEM_freeN (chan);
			return curChan;
		}
	}

	MEM_freeN (chan);
	return NULL;
	/* If an equivalent channel doesn't exist, then don't bother setting it. */
}


