/**
 * $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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * The Original Code is Copyright (C) 2009 Blender Foundation, Joshua Leung
 * All rights reserved.
 *
 * The Original Code is: all of this file.
 *
 * Contributor(s): Joshua Leung (full recode)
 *
 * ***** END GPL LICENSE BLOCK *****
 */
 

#include <math.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <float.h>

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

#include "MEM_guardedalloc.h"

#include "DNA_anim_types.h"

#include "BLI_blenlib.h"
#include "BLI_arithb.h"
#include "BLI_noise.h"

#include "BKE_fcurve.h"
#include "BKE_curve.h" 
#include "BKE_global.h"
#include "BKE_idprop.h"
#include "BKE_utildefines.h"

#include "RNA_access.h"
#include "RNA_types.h"

#ifndef DISABLE_PYTHON
#include "BPY_extern.h" 
#endif

#define SMALL -1.0e-10
#define SELECT 1

/* ************************** Data-Level Functions ************************* */

/* ---------------------- Freeing --------------------------- */

/* Frees the F-Curve itself too, so make sure BLI_remlink is called before calling this... */
void free_fcurve (FCurve *fcu) 
{
	if (fcu == NULL) 
		return;
	
	/* free curve data */
	if (fcu) {
		if (fcu->bezt) MEM_freeN(fcu->bezt);
		if (fcu->fpt) MEM_freeN(fcu->fpt);
	}
	
	/* free RNA-path, as this were allocated when getting the path string */
	if (fcu->rna_path)
		MEM_freeN(fcu->rna_path);
	
	/* free extra data - i.e. modifiers, and driver */
	fcurve_free_driver(fcu);
	free_fmodifiers(&fcu->modifiers);
	
	/* free f-curve itself */
	MEM_freeN(fcu);
}

/* Frees a list of F-Curves */
void free_fcurves (ListBase *list)
{
	FCurve *fcu, *fcn;
	
	/* sanity check */
	if (list == NULL)
		return;
		
	/* free data - no need to call remlink before freeing each curve, 
	 * as we store reference to next, and freeing only touches the curve
	 * it's given
	 */
	for (fcu= list->first; fcu; fcu= fcn) {
		fcn= fcu->next;
		free_fcurve(fcu);
	}
	
	/* clear pointers just in case */
	list->first= list->last= NULL;
}	

/* ---------------------- Copy --------------------------- */

/* duplicate an F-Curve */
FCurve *copy_fcurve (FCurve *fcu)
{
	FCurve *fcu_d;
	
	/* sanity check */
	if (fcu == NULL)
		return NULL;
		
	/* make a copy */
	fcu_d= MEM_dupallocN(fcu);
	
	fcu_d->next= fcu_d->prev= NULL;
	fcu_d->grp= NULL;
	
	/* copy curve data */
	fcu_d->bezt= MEM_dupallocN(fcu_d->bezt);
	fcu_d->fpt= MEM_dupallocN(fcu_d->fpt);
	
	/* copy rna-path */
	fcu_d->rna_path= MEM_dupallocN(fcu_d->rna_path);
	
	/* copy driver */
	fcu_d->driver= fcurve_copy_driver(fcu_d->driver);
	
	/* copy modifiers */
	copy_fmodifiers(&fcu_d->modifiers, &fcu->modifiers);
	
	/* return new data */
	return fcu_d;
}

/* duplicate a list of F-Curves */
void copy_fcurves (ListBase *dst, ListBase *src)
{
	FCurve *dfcu, *sfcu;
	
	/* sanity checks */
	if ELEM(NULL, dst, src)
		return;
	
	/* clear destination list first */
	dst->first= dst->last= NULL;
	
	/* copy one-by-one */
	for (sfcu= src->first; sfcu; sfcu= sfcu->next) {
		dfcu= copy_fcurve(sfcu);
		BLI_addtail(dst, dfcu);
	}
}

/* ---------------------- Relink --------------------------- */

#if 0
/* uses id->newid to match pointers with other copied data 
 * 	- called after single-user or other such
 */
			if (icu->driver)
				ID_NEW(icu->driver->ob);
#endif

/* --------------------- Finding -------------------------- */

/* Find the F-Curve affecting the given RNA-access path + index, in the list of F-Curves provided */
FCurve *list_find_fcurve (ListBase *list, const char rna_path[], const int array_index)
{
	FCurve *fcu;
	
	/* sanity checks */
	if ( ELEM(NULL, list, rna_path) || (array_index < 0) )
		return NULL;
	
	/* check paths of curves, then array indices... */
	for (fcu= list->first; fcu; fcu= fcu->next) {
		/* simple string-compare (this assumes that they have the same root...) */
		if (fcu->rna_path && !strcmp(fcu->rna_path, rna_path)) {
			/* now check indicies */
			if (fcu->array_index == array_index)
				return fcu;
		}
	}
	
	/* return */
	return NULL;
}

/* Calculate the extents of F-Curve's data */
void calc_fcurve_bounds (FCurve *fcu, float *xmin, float *xmax, float *ymin, float *ymax)
{
	float xminv=999999999.0f, xmaxv=-999999999.0f;
	float yminv=999999999.0f, ymaxv=-999999999.0f;
	short foundvert=0;
	unsigned int i;
	
	if (fcu->totvert) {
		if (fcu->bezt) {
			/* frame range can be directly calculated from end verts */
			if (xmin || xmax) {
				xminv= MIN2(xminv, fcu->bezt[0].vec[1][0]);
				xmaxv= MAX2(xmaxv, fcu->bezt[fcu->totvert-1].vec[1][0]);
			}
			
			/* only loop over keyframes to find extents for values if needed */
			if (ymin || ymax) {
				BezTriple *bezt;
				
				for (bezt=fcu->bezt, i=0; i < fcu->totvert; bezt++, i++) {
					if (bezt->vec[1][1] < yminv)
						yminv= bezt->vec[1][1];
					if (bezt->vec[1][1] > ymaxv)
						ymaxv= bezt->vec[1][1];
				}
			}
		}
		else if (fcu->fpt) {
			/* frame range can be directly calculated from end verts */
			if (xmin || xmax) {
				xminv= MIN2(xminv, fcu->fpt[0].vec[0]);
				xmaxv= MAX2(xmaxv, fcu->fpt[fcu->totvert-1].vec[0]);
			}
			
			/* only loop over keyframes to find extents for values if needed */
			if (ymin || ymax) {
				FPoint *fpt;
				
				for (fpt=fcu->fpt, i=0; i < fcu->totvert; fpt++, i++) {
					if (fpt->vec[1] < yminv)
						yminv= fpt->vec[1];
					if (fpt->vec[1] > ymaxv)
						ymaxv= fpt->vec[1];
				}
			}
		}
		
		foundvert=1;
	}
	
	/* minimum sizes are 1.0f */
	if (foundvert) {
		if (xminv == xmaxv) xmaxv += 1.0f;
		if (yminv == ymaxv) ymaxv += 1.0f;
		
		if (xmin) *xmin= xminv;
		if (xmax) *xmax= xmaxv;
		
		if (ymin) *ymin= yminv;
		if (ymax) *ymax= ymaxv;
	}
	else {
		if (xmin) *xmin= 0.0f;
		if (xmax) *xmax= 0.0f;
		
		if (ymin) *ymin= 1.0f;
		if (ymax) *ymax= 1.0f;
	}
}

/* Calculate the extents of F-Curve's keyframes */
void calc_fcurve_range (FCurve *fcu, float *start, float *end)
{
	float min=999999999.0f, max=-999999999.0f;
	short foundvert=0;

	if (fcu->totvert) {
		if (fcu->bezt) {
			min= MIN2(min, fcu->bezt[0].vec[1][0]);
			max= MAX2(max, fcu->bezt[fcu->totvert-1].vec[1][0]);
		}
		else if (fcu->fpt) {
			min= MIN2(min, fcu->fpt[0].vec[0]);
			max= MAX2(max, fcu->fpt[fcu->totvert-1].vec[0]);
		}
		
		foundvert=1;
	}
	
	/* minimum length is 1 frame */
	if (foundvert) {
		if (min == max) max += 1.0f;
		*start= min;
		*end= max;
	}
	else {
		*start= 0.0f;
		*end= 1.0f;
	}
}

/* ***************************** Keyframe Column Tools ********************************* */

/* add a BezTriple to a column */
void bezt_add_to_cfra_elem (ListBase *lb, BezTriple *bezt)
{
	CfraElem *ce, *cen;
	
	for (ce= lb->first; ce; ce= ce->next) {
		/* double key? */
		if (ce->cfra == bezt->vec[1][0]) {
			if (bezt->f2 & SELECT) ce->sel= bezt->f2;
			return;
		}
		/* should key be inserted before this column? */
		else if (ce->cfra > bezt->vec[1][0]) break;
	}
	
	/* create a new column */
	cen= MEM_callocN(sizeof(CfraElem), "add_to_cfra_elem");	
	if (ce) BLI_insertlinkbefore(lb, ce, cen);
	else BLI_addtail(lb, cen);

	cen->cfra= bezt->vec[1][0];
	cen->sel= bezt->f2;
}

/* ***************************** Samples Utilities ******************************* */
/* Some utilities for working with FPoints (i.e. 'sampled' animation curve data, such as
 * data imported from BVH/Mocap files), which are specialised for use with high density datasets,
 * which BezTriples/Keyframe data are ill equipped to do.
 */
 
 
/* Basic sampling callback which acts as a wrapper for evaluate_fcurve() 
 *	'data' arg here is unneeded here...
 */
float fcurve_samplingcb_evalcurve (FCurve *fcu, void *data, float evaltime)
{
	/* assume any interference from drivers on the curve is intended... */
	return evaluate_fcurve(fcu, evaltime);
} 

 
/* Main API function for creating a set of sampled curve data, given some callback function 
 * used to retrieve the values to store.
 */
void fcurve_store_samples (FCurve *fcu, void *data, int start, int end, FcuSampleFunc sample_cb)
{
	FPoint *fpt, *new_fpt;
	int cfra;
	
	/* sanity checks */
	// TODO: make these tests report errors using reports not printf's
	if ELEM(NULL, fcu, sample_cb) {
		printf("Error: No F-Curve with F-Curve Modifiers to Bake\n");
		return;
	}
	if (start >= end) {
		printf("Error: Frame range for Sampled F-Curve creation is inappropriate \n");
		return;
	}
	
	/* set up sample data */
	fpt= new_fpt= MEM_callocN(sizeof(FPoint)*(end-start+1), "FPoint Samples");
	
	/* use the sampling callback at 1-frame intervals from start to end frames */
	for (cfra= start; cfra <= end; cfra++, fpt++) {
		fpt->vec[0]= (float)cfra;
		fpt->vec[1]= sample_cb(fcu, data, (float)cfra);
	}
	
	/* free any existing sample/keyframe data on curve  */
	if (fcu->bezt) MEM_freeN(fcu->bezt);
	if (fcu->fpt) MEM_freeN(fcu->fpt);
	
	/* store the samples */
	fcu->bezt= NULL;
	fcu->fpt= new_fpt;
	fcu->totvert= end - start + 1;
}

/* ***************************** F-Curve Sanity ********************************* */
/* The functions here are used in various parts of Blender, usually after some editing
 * of keyframe data has occurred. They ensure that keyframe data is properly ordered and
 * that the handles are correctly 
 */

/* This function recalculates the handles of an F-Curve 
 * If the BezTriples have been rearranged, sort them first before using this.
 */
void calchandles_fcurve (FCurve *fcu)
{
	BezTriple *bezt, *prev, *next;
	int a= fcu->totvert;

	/* Error checking:
	 *	- need at least two points
	 *	- need bezier keys
	 *	- only bezier-interpolation has handles (for now)
	 */
	if (ELEM(NULL, fcu, fcu->bezt) || (a < 2) /*|| ELEM(fcu->ipo, BEZT_IPO_CONST, BEZT_IPO_LIN)*/) 
		return;
	
	/* get initial pointers */
	bezt= fcu->bezt;
	prev= NULL;
	next= (bezt + 1);
	
	/* loop over all beztriples, adjusting handles */
	while (a--) {
		/* clamp timing of handles to be on either side of beztriple */
		if (bezt->vec[0][0] > bezt->vec[1][0]) bezt->vec[0][0]= bezt->vec[1][0];
		if (bezt->vec[2][0] < bezt->vec[1][0]) bezt->vec[2][0]= bezt->vec[1][0];
		
		/* calculate auto-handles */
		if (fcu->flag & FCURVE_AUTO_HANDLES) 
			calchandleNurb(bezt, prev, next, 2);	/* 2==special autohandle && keep extrema horizontal */
		else
			calchandleNurb(bezt, prev, next, 1);	/* 1==special autohandle */
		
		/* for automatic ease in and out */
		if ((bezt->h1==HD_AUTO) && (bezt->h2==HD_AUTO)) {
			/* only do this on first or last beztriple */
			if ((a == 0) || (a == fcu->totvert-1)) {
				/* set both handles to have same horizontal value as keyframe */
				if (fcu->extend == FCURVE_EXTRAPOLATE_CONSTANT) {
					bezt->vec[0][1]= bezt->vec[2][1]= bezt->vec[1][1];
				}
			}
		}
		
		/* advance pointers for next iteration */
		prev= bezt;
		if (a == 1) next= NULL;
		else next++;
		bezt++;
	}
}

/* Use when F-Curve with handles has changed
 * It treats all BezTriples with the following rules:
 *  - PHASE 1: do types have to be altered?
 * 		-> Auto handles: become aligned when selection status is NOT(000 || 111)
 * 		-> Vector handles: become 'nothing' when (one half selected AND other not)
 *  - PHASE 2: recalculate handles
*/
void testhandles_fcurve (FCurve *fcu)
{
	BezTriple *bezt;
	unsigned int a;

	/* only beztriples have handles (bpoints don't though) */
	if ELEM(NULL, fcu, fcu->bezt)
		return;
	
	/* loop over beztriples */
	for (a=0, bezt=fcu->bezt; a < fcu->totvert; a++, bezt++) {
		short flag= 0;
		
		/* flag is initialised as selection status
		 * of beztriple control-points (labelled 0,1,2)
		 */
		if (bezt->f1 & SELECT) flag |= (1<<0); // == 1
		if (bezt->f2 & SELECT) flag |= (1<<1); // == 2
		if (bezt->f3 & SELECT) flag |= (1<<2); // == 4
		
		/* one or two handles selected only */
		if (ELEM(flag, 0, 7)==0) {
			/* auto handles become aligned */
			if (bezt->h1==HD_AUTO)
				bezt->h1= HD_ALIGN;
			if (bezt->h2==HD_AUTO)
				bezt->h2= HD_ALIGN;
			
			/* vector handles become 'free' when only one half selected */
			if (bezt->h1==HD_VECT) {
				/* only left half (1 or 2 or 1+2) */
				if (flag < 4) 
					bezt->h1= 0;
			}
			if (bezt->h2==HD_VECT) {
				/* only right half (4 or 2+4) */
				if (flag > 3) 
					bezt->h2= 0;
			}
		}
	}

	/* recalculate handles */
	calchandles_fcurve(fcu);
}

/* This function sorts BezTriples so that they are arranged in chronological order,
 * as tools working on F-Curves expect that the BezTriples are in order.
 */
void sort_time_fcurve (FCurve *fcu)
{
	short ok= 1;
	
	/* keep adjusting order of beztriples until nothing moves (bubble-sort) */
	while (ok) {
		ok= 0;
		
		/* currently, will only be needed when there are beztriples */
		if (fcu->bezt) {
			BezTriple *bezt;
			unsigned int a;
			
			/* loop over ALL points to adjust position in array and recalculate handles */
			for (a=0, bezt=fcu->bezt; a < fcu->totvert; a++, bezt++) {
				/* check if thee's a next beztriple which we could try to swap with current */
				if (a < (fcu->totvert-1)) {
					/* swap if one is after the other (and indicate that order has changed) */
					if (bezt->vec[1][0] > (bezt+1)->vec[1][0]) {
						SWAP(BezTriple, *bezt, *(bezt+1));
						ok= 1;
					}
					
					/* if either one of both of the points exceeds crosses over the keyframe time... */
					if ( (bezt->vec[0][0] > bezt->vec[1][0]) && (bezt->vec[2][0] < bezt->vec[1][0]) ) {
						/* swap handles if they have switched sides for some reason */
						SWAP(float, bezt->vec[0][0], bezt->vec[2][0]);
						SWAP(float, bezt->vec[0][1], bezt->vec[2][1]);
					}
					else {
						/* clamp handles */
						if (bezt->vec[0][0] > bezt->vec[1][0]) 
							bezt->vec[0][0]= bezt->vec[1][0];
						if (bezt->vec[2][0] < bezt->vec[1][0]) 
							bezt->vec[2][0]= bezt->vec[1][0];
					}
				}
			}
		}
	}
}

/* This function tests if any BezTriples are out of order, thus requiring a sort */
short test_time_fcurve (FCurve *fcu)
{
	unsigned int a;
	
	/* sanity checks */
	if (fcu == NULL)
		return 0;
	
	/* currently, only need to test beztriples */
	if (fcu->bezt) {
		BezTriple *bezt;
		
		/* loop through all BezTriples, stopping when one exceeds the one after it */
		for (a=0, bezt= fcu->bezt; a < (fcu->totvert - 1); a++, bezt++) {
			if (bezt->vec[1][0] > (bezt+1)->vec[1][0])
				return 1;
		}
	}
	else if (fcu->fpt) {
		FPoint *fpt;
		
		/* loop through all FPoints, stopping when one exceeds the one after it */
		for (a=0, fpt= fcu->fpt; a < (fcu->totvert - 1); a++, fpt++) {
			if (fpt->vec[0] > (fpt+1)->vec[0])
				return 1;
		}
	}
	
	/* none need any swapping */
	return 0;
}

/* ***************************** Drivers ********************************* */

/* Driver API --------------------------------- */

/* This frees the driver target itself */
void driver_free_target (ChannelDriver *driver, DriverTarget *dtar)
{
	/* sanity checks */
	if (dtar == NULL)
		return;
		
	/* free target vars */
	if (dtar->rna_path)
		MEM_freeN(dtar->rna_path);
	
	/* remove the target from the driver */
	if (driver)
		BLI_freelinkN(&driver->targets, dtar);
	else
		MEM_freeN(dtar);
}

/* Add a new driver target variable */
DriverTarget *driver_add_new_target (ChannelDriver *driver)
{
	DriverTarget *dtar;
	
	/* sanity checks */
	if (driver == NULL)
		return NULL;
		
	/* make a new target */
	dtar= MEM_callocN(sizeof(DriverTarget), "DriverTarget");
	BLI_addtail(&driver->targets, dtar);
	
	/* give the target a 'unique' name */
	strcpy(dtar->name, "var");
	BLI_uniquename(&driver->targets, dtar, "var", '_', offsetof(DriverTarget, name), 64);
	
	/* return the target */
	return dtar;
}

/* This frees the driver itself */
void fcurve_free_driver(FCurve *fcu)
{
	ChannelDriver *driver;
	DriverTarget *dtar, *dtarn;
	
	/* sanity checks */
	if ELEM(NULL, fcu, fcu->driver)
		return;
	driver= fcu->driver;
	
	/* free driver targets */
	for (dtar= driver->targets.first; dtar; dtar= dtarn) {
		dtarn= dtar->next;
		driver_free_target(driver, dtar);
	}
	
	/* free driver itself, then set F-Curve's point to this to NULL (as the curve may still be used) */
	MEM_freeN(driver);
	fcu->driver= NULL;
}

/* This makes a copy of the given driver */
ChannelDriver *fcurve_copy_driver (ChannelDriver *driver)
{
	ChannelDriver *ndriver;
	DriverTarget *dtar;
	
	/* sanity checks */
	if (driver == NULL)
		return NULL;
		
	/* copy all data */
	ndriver= MEM_dupallocN(driver);
	
	/* copy targets */
	ndriver->targets.first= ndriver->targets.last= NULL;
	BLI_duplicatelist(&ndriver->targets, &driver->targets);
	
	for (dtar= ndriver->targets.first; dtar; dtar= dtar->next) {
		/* make a copy of target's rna path if available */
		if (dtar->rna_path)
			dtar->rna_path = MEM_dupallocN(dtar->rna_path);
	}
	
	/* return the new driver */
	return ndriver;
}

/* Driver Evaluation -------------------------- */

/* Helper function to obtain a value using RNA from the specified source (for evaluating drivers) */
float driver_get_target_value (ChannelDriver *driver, DriverTarget *dtar)
{
	PointerRNA id_ptr, ptr;
	PropertyRNA *prop;
	ID *id;
	char *path;
	int index;
	float value= 0.0f;
	
	/* sanity check */
	if ELEM(NULL, driver, dtar)
		return 0.0f;
	
	/* get RNA-pointer for the ID-block given in target */
	RNA_id_pointer_create(dtar->id, &id_ptr);
	id= dtar->id;
	path= dtar->rna_path;
	index= dtar->array_index;
	
	/* error check for missing pointer... */
	if (id == NULL) {
		printf("Error: driver doesn't have any valid target to use \n");
		if (G.f & G_DEBUG) printf("\tpath = %s [%d] \n", path, index);
		driver->flag |= DRIVER_FLAG_INVALID;
		return 0.0f;
	}
	
	/* get property to read from, and get value as appropriate */
	if (RNA_path_resolve(&id_ptr, path, &ptr, &prop)) {
		switch (RNA_property_type(prop)) {
			case PROP_BOOLEAN:
				if (RNA_property_array_length(&ptr, prop))
					value= (float)RNA_property_boolean_get_index(&ptr, prop, index);
				else
					value= (float)RNA_property_boolean_get(&ptr, prop);
				break;
			case PROP_INT:
				if (RNA_property_array_length(&ptr, prop))
					value= (float)RNA_property_int_get_index(&ptr, prop, index);
				else
					value= (float)RNA_property_int_get(&ptr, prop);
				break;
			case PROP_FLOAT:
				if (RNA_property_array_length(&ptr, prop))
					value= RNA_property_float_get_index(&ptr, prop, index);
				else
					value= RNA_property_float_get(&ptr, prop);
				break;
			case PROP_ENUM:
				value= (float)RNA_property_enum_get(&ptr, prop);
				break;
			default:
				break;
		}
	}
	else if (G.f & G_DEBUG)
		printf("Driver Evaluation Error: cannot resolve target for %s -> %s \n", id->name, path);
	
	return value;
}

/* Get two PoseChannels from the targets of the given Driver */
static void driver_get_target_pchans2 (ChannelDriver *driver, bPoseChannel **pchan1, bPoseChannel **pchan2)
{
	DriverTarget *dtar;
	short i = 0;
	
	/* before doing anything */
	*pchan1= NULL;
	*pchan2= NULL;
	
	/* only take the first two targets */
	for (dtar= driver->targets.first; (dtar) && (i < 2); dtar=dtar->next, i++) {
		PointerRNA id_ptr, ptr;
		PropertyRNA *prop;
		
		/* get RNA-pointer for the ID-block given in target */
		if (dtar->id)
			RNA_id_pointer_create(dtar->id, &id_ptr);
		else
			continue;
		
		/* resolve path so that we have pointer to the right posechannel */
		if (RNA_path_resolve(&id_ptr, dtar->rna_path, &ptr, &prop)) {
			/* is pointer valid (i.e. pointing to an actual posechannel */
			if ((ptr.type == &RNA_PoseChannel) && (ptr.data)) {
				/* first or second target? */
				if (i)
					*pchan1= ptr.data;
				else
					*pchan2= ptr.data;
			}
		}
	}
}

/* Evaluate an Channel-Driver to get a 'time' value to use instead of "evaltime"
 *	- "evaltime" is the frame at which F-Curve is being evaluated
 * 	- has to return a float value 
 */
static float evaluate_driver (ChannelDriver *driver, float evaltime)
{
	DriverTarget *dtar;
	
	/* check if driver can be evaluated */
	if (driver->flag & DRIVER_FLAG_INVALID)
		return 0.0f;
	
	// TODO: the flags for individual targets need to be used too for more fine-grained support...
	switch (driver->type) {
		case DRIVER_TYPE_AVERAGE: /* average values of driver targets */
		{
			/* check how many targets there are first (i.e. just one?) */
			if (driver->targets.first == driver->targets.last) {
				/* just one target, so just use that */
				dtar= driver->targets.first;
				return driver_get_target_value(driver, dtar);
			}
			else {
				/* more than one target, so average the values of the targets */
				int tot = 0;
				float value = 0.0f;
				
				/* loop through targets, adding (hopefully we don't get any overflow!) */
				for (dtar= driver->targets.first; dtar; dtar=dtar->next) {
					value += driver_get_target_value(driver, dtar); 
					tot++;
				}
				
				/* return the average of these */
				return (value / (float)tot);
			}
		}
			break;
		
		case DRIVER_TYPE_PYTHON: /* expression */
		{
#ifndef DISABLE_PYTHON
			/* check for empty or invalid expression */
			if ( (driver->expression[0] == '\0') ||
				 (driver->flag & DRIVER_FLAG_INVALID) )
			{
				return 0.0f;
			}
			
			/* this evaluates the expression using Python,and returns its result:
			 * 	- on errors it reports, then returns 0.0f
			 */
			return BPY_pydriver_eval(driver);
#endif /* DISABLE_PYTHON*/
		}
			break;

		
		case DRIVER_TYPE_ROTDIFF: /* difference of rotations of 2 bones (should ideally be in same armature) */
		{
			bPoseChannel *pchan, *pchan2;
			float q1[4], q2[4], quat[4], angle;
			
			/* get pose channels, and check if we've got two */
			driver_get_target_pchans2(driver, &pchan, &pchan2);
			if (ELEM(NULL, pchan, pchan2)) {
				/* disable this driver, since it doesn't work correctly... */
				driver->flag |= DRIVER_FLAG_INVALID;
				
				/* check what the error was */
				if ((pchan == NULL) && (pchan2 == NULL))
					printf("Driver Evaluation Error: Rotational difference failed - first 2 targets invalid \n");
				else if (pchan == NULL)
					printf("Driver Evaluation Error: Rotational difference failed - first target not valid PoseChannel \n");
				else if (pchan2 == NULL)
					printf("Driver Evaluation Error: Rotational difference failed - second target not valid PoseChannel \n");
					
				/* stop here... */
				return 0.0f;
			}			
			
			/* use the final posed locations */
			Mat4ToQuat(pchan->pose_mat, q1);
			Mat4ToQuat(pchan2->pose_mat, q2);
			
			QuatInv(q1);
			QuatMul(quat, q1, q2);
			angle = 2.0f * (saacos(quat[0]));
			angle= ABS(angle);
			
			return (angle > M_PI) ? (float)((2.0f * M_PI) - angle) : (float)(angle);
		}
			break;
		
		default:
		{
			/* special 'hack' - just use stored value 
			 *	This is currently used as the mechanism which allows animated settings to be able
			 * 	to be changed via the UI.
			 */
			return driver->curval;
		}
	}
	
	/* return 0.0f, as couldn't find relevant data to use */
	return 0.0f;
}

/* ***************************** Curve Calculations ********************************* */

/* The total length of the handles is not allowed to be more
 * than the horizontal distance between (v1-v4).
 * This is to prevent curve loops.
*/
void correct_bezpart (float *v1, float *v2, float *v3, float *v4)
{
	float h1[2], h2[2], len1, len2, len, fac;
	
	/* calculate handle deltas */
	h1[0]= v1[0] - v2[0];
	h1[1]= v1[1] - v2[1];
	
	h2[0]= v4[0] - v3[0];
	h2[1]= v4[1] - v3[1];
	
	/* calculate distances: 
	 * 	- len	= span of time between keyframes 
	 *	- len1	= length of handle of start key
	 *	- len2 	= length of handle of end key
	 */
	len= v4[0]- v1[0];
	len1= (float)fabs(h1[0]);
	len2= (float)fabs(h2[0]);
	
	/* if the handles have no length, no need to do any corrections */
	if ((len1+len2) == 0.0f) 
		return;
		
	/* the two handles cross over each other, so force them
	 * apart using the proportion they overlap 
	 */
	if ((len1+len2) > len) {
		fac= len / (len1+len2);
		
		v2[0]= (v1[0] - fac*h1[0]);
		v2[1]= (v1[1] - fac*h1[1]);
		
		v3[0]= (v4[0] - fac*h2[0]);
		v3[1]= (v4[1] - fac*h2[1]);
	}
}

/* find root ('zero') */
int findzero (float x, float q0, float q1, float q2, float q3, float *o)
{
	double c0, c1, c2, c3, a, b, c, p, q, d, t, phi;
	int nr= 0;

	c0= q0 - x;
	c1= 3.0 * (q1 - q0);
	c2= 3.0 * (q0 - 2.0*q1 + q2);
	c3= q3 - q0 + 3.0 * (q1 - q2);
	
	if (c3 != 0.0) {
		a= c2/c3;
		b= c1/c3;
		c= c0/c3;
		a= a/3;
		
		p= b/3 - a*a;
		q= (2*a*a*a - a*b + c) / 2;
		d= q*q + p*p*p;
		
		if (d > 0.0) {
			t= sqrt(d);
			o[0]= (float)(Sqrt3d(-q+t) + Sqrt3d(-q-t) - a);
			
			if ((o[0] >= SMALL) && (o[0] <= 1.000001)) return 1;
			else return 0;
		}
		else if (d == 0.0) {
			t= Sqrt3d(-q);
			o[0]= (float)(2*t - a);
			
			if ((o[0] >= SMALL) && (o[0] <= 1.000001)) nr++;
			o[nr]= (float)(-t-a);
			
			if ((o[nr] >= SMALL) && (o[nr] <= 1.000001)) return nr+1;
			else return nr;
		}
		else {
			phi= acos(-q / sqrt(-(p*p*p)));
			t= sqrt(-p);
			p= cos(phi/3);
			q= sqrt(3 - 3*p*p);
			o[0]= (float)(2*t*p - a);
			
			if ((o[0] >= SMALL) && (o[0] <= 1.000001)) nr++;
			o[nr]= (float)(-t * (p + q) - a);
			
			if ((o[nr] >= SMALL) && (o[nr] <= 1.000001)) nr++;
			o[nr]= (float)(-t * (p - q) - a);
			
			if ((o[nr] >= SMALL) && (o[nr] <= 1.000001)) return nr+1;
			else return nr;
		}
	}
	else {
		a=c2;
		b=c1;
		c=c0;
		
		if (a != 0.0) {
			// discriminant
			p= b*b - 4*a*c;
			
			if (p > 0) {
				p= sqrt(p);
				o[0]= (float)((-b-p) / (2 * a));
				
				if ((o[0] >= SMALL) && (o[0] <= 1.000001)) nr++;
				o[nr]= (float)((-b+p)/(2*a));
				
				if ((o[nr] >= SMALL) && (o[nr] <= 1.000001)) return nr+1;
				else return nr;
			}
			else if (p == 0) {
				o[0]= (float)(-b / (2 * a));
				if ((o[0] >= SMALL) && (o[0] <= 1.000001)) return 1;
				else return 0;
			}
		}
		else if (b != 0.0) {
			o[0]= (float)(-c/b);
			
			if ((o[0] >= SMALL) && (o[0] <= 1.000001)) return 1;
			else return 0;
		}
		else if (c == 0.0) {
			o[0]= 0.0;
			return 1;
		}
		
		return 0;	
	}
}

void berekeny (float f1, float f2, float f3, float f4, float *o, int b)
{
	float t, c0, c1, c2, c3;
	int a;

	c0= f1;
	c1= 3.0f * (f2 - f1);
	c2= 3.0f * (f1 - 2.0f*f2 + f3);
	c3= f4 - f1 + 3.0f * (f2 - f3);
	
	for (a=0; a < b; a++) {
		t= o[a];
		o[a]= c0 + t*c1 + t*t*c2 + t*t*t*c3;
	}
}

void berekenx (float *f, float *o, int b)
{
	float t, c0, c1, c2, c3;
	int a;

	c0= f[0];
	c1= 3.0f * (f[3] - f[0]);
	c2= 3.0f * (f[0] - 2.0f*f[3] + f[6]);
	c3= f[9] - f[0] + 3.0f * (f[3] - f[6]);
	
	for (a=0; a < b; a++) {
		t= o[a];
		o[a]= c0 + t*c1 + t*t*c2 + t*t*t*c3;
	}
}


/* -------------------------- */

/* Calculate F-Curve value for 'evaltime' using BezTriple keyframes */
static float fcurve_eval_keyframes (FCurve *fcu, BezTriple *bezts, float evaltime)
{
	BezTriple *bezt, *prevbezt, *lastbezt;
	float v1[2], v2[2], v3[2], v4[2], opl[32], dx, fac;
	unsigned int a;
	int b;
	float cvalue = 0.0f;
	
	/* get pointers */
	a= fcu->totvert-1;
	prevbezt= bezts;
	bezt= prevbezt+1;
	lastbezt= prevbezt + a;
	
	/* evaluation time at or past endpoints? */
	if (prevbezt->vec[1][0] >= evaltime) 
	{
		/* before or on first keyframe */
		if ( (fcu->extend == FCURVE_EXTRAPOLATE_LINEAR) && (prevbezt->ipo != BEZT_IPO_CONST) &&
			!(fcu->flag & FCURVE_DISCRETE_VALUES) ) 
		{
			/* linear or bezier interpolation */
			if (prevbezt->ipo==BEZT_IPO_LIN) 
			{
				/* Use the next center point instead of our own handle for
				 * linear interpolated extrapolate 
				 */
				if (fcu->totvert == 1) 
					cvalue= prevbezt->vec[1][1];
				else 
				{
					bezt = prevbezt+1;
					dx= prevbezt->vec[1][0] - evaltime;
					fac= bezt->vec[1][0] - prevbezt->vec[1][0];
					
					/* prevent division by zero */
					if (fac) {
						fac= (bezt->vec[1][1] - prevbezt->vec[1][1]) / fac;
						cvalue= prevbezt->vec[1][1] - (fac * dx);
					}
					else 
						cvalue= prevbezt->vec[1][1];
				}
			} 
			else 
			{
				/* Use the first handle (earlier) of first BezTriple to calculate the
				 * gradient and thus the value of the curve at evaltime
				 */
				dx= prevbezt->vec[1][0] - evaltime;
				fac= prevbezt->vec[1][0] - prevbezt->vec[0][0];
				
				/* prevent division by zero */
				if (fac) {
					fac= (prevbezt->vec[1][1] - prevbezt->vec[0][1]) / fac;
					cvalue= prevbezt->vec[1][1] - (fac * dx);
				}
				else 
					cvalue= prevbezt->vec[1][1];
			}
		}
		else 
		{
			/* constant (BEZT_IPO_HORIZ) extrapolation or constant interpolation, 
			 * so just extend first keyframe's value 
			 */
			cvalue= prevbezt->vec[1][1];
		}
	}
	else if (lastbezt->vec[1][0] <= evaltime) 
	{
		/* after or on last keyframe */
		if ( (fcu->extend == FCURVE_EXTRAPOLATE_LINEAR) && (lastbezt->ipo != BEZT_IPO_CONST) &&
			!(fcu->flag & FCURVE_DISCRETE_VALUES) ) 
		{
			/* linear or bezier interpolation */
			if (lastbezt->ipo==BEZT_IPO_LIN) 
			{
				/* Use the next center point instead of our own handle for
				 * linear interpolated extrapolate 
				 */
				if (fcu->totvert == 1) 
					cvalue= lastbezt->vec[1][1];
				else 
				{
					prevbezt = lastbezt - 1;
					dx= evaltime - lastbezt->vec[1][0];
					fac= lastbezt->vec[1][0] - prevbezt->vec[1][0];
					
					/* prevent division by zero */
					if (fac) {
						fac= (lastbezt->vec[1][1] - prevbezt->vec[1][1]) / fac;
						cvalue= lastbezt->vec[1][1] + (fac * dx);
					}
					else 
						cvalue= lastbezt->vec[1][1];
				}
			} 
			else 
			{
				/* Use the gradient of the second handle (later) of last BezTriple to calculate the
				 * gradient and thus the value of the curve at evaltime
				 */
				dx= evaltime - lastbezt->vec[1][0];
				fac= lastbezt->vec[2][0] - lastbezt->vec[1][0];
				
				/* prevent division by zero */
				if (fac) {
					fac= (lastbezt->vec[2][1] - lastbezt->vec[1][1]) / fac;
					cvalue= lastbezt->vec[1][1] + (fac * dx);
				}
				else 
					cvalue= lastbezt->vec[1][1];
			}
		}
		else 
		{
			/* constant (BEZT_IPO_HORIZ) extrapolation or constant interpolation, 
			 * so just extend last keyframe's value 
			 */
			cvalue= lastbezt->vec[1][1];
		}
	}
	else 
	{
		/* evaltime occurs somewhere in the middle of the curve */
		for (a=0; prevbezt && bezt && (a < fcu->totvert-1); a++, prevbezt=bezt, bezt++) 
		{  
			/* evaltime occurs within the interval defined by these two keyframes */
			if ((prevbezt->vec[1][0] <= evaltime) && (bezt->vec[1][0] >= evaltime)) 
			{
				/* value depends on interpolation mode */
				if ((prevbezt->ipo == BEZT_IPO_CONST) || (fcu->flag & FCURVE_DISCRETE_VALUES))
				{
					/* constant (evaltime not relevant, so no interpolation needed) */
					cvalue= prevbezt->vec[1][1];
				}
				else if (prevbezt->ipo == BEZT_IPO_LIN) 
				{
					/* linear - interpolate between values of the two keyframes */
					fac= bezt->vec[1][0] - prevbezt->vec[1][0];
					
					/* prevent division by zero */
					if (fac) {
						fac= (evaltime - prevbezt->vec[1][0]) / fac;
						cvalue= prevbezt->vec[1][1] + (fac * (bezt->vec[1][1] - prevbezt->vec[1][1]));
					}
					else
						cvalue= prevbezt->vec[1][1];
				}
				else 
				{
					/* bezier interpolation */
						/* v1,v2 are the first keyframe and its 2nd handle */
					v1[0]= prevbezt->vec[1][0];
					v1[1]= prevbezt->vec[1][1];
					v2[0]= prevbezt->vec[2][0];
					v2[1]= prevbezt->vec[2][1];
						/* v3,v4 are the last keyframe's 1st handle + the last keyframe */
					v3[0]= bezt->vec[0][0];
					v3[1]= bezt->vec[0][1];
					v4[0]= bezt->vec[1][0];
					v4[1]= bezt->vec[1][1];
					
					/* adjust handles so that they don't overlap (forming a loop) */
					correct_bezpart(v1, v2, v3, v4);
					
					/* try to get a value for this position - if failure, try another set of points */
					b= findzero(evaltime, v1[0], v2[0], v3[0], v4[0], opl);
					if (b) {
						berekeny(v1[1], v2[1], v3[1], v4[1], opl, 1);
						cvalue= opl[0];
						break;
					}
				}
			}
		}
	}
	
	/* return value */
	return cvalue;
}

/* Calculate F-Curve value for 'evaltime' using FPoint samples */
static float fcurve_eval_samples (FCurve *fcu, FPoint *fpts, float evaltime)
{
	FPoint *prevfpt, *lastfpt, *fpt;
	float cvalue= 0.0f;
	
	/* get pointers */
	prevfpt= fpts;
	lastfpt= prevfpt + fcu->totvert-1;
	
	/* evaluation time at or past endpoints? */
	if (prevfpt->vec[0] >= evaltime) {
		/* before or on first sample, so just extend value */
		cvalue= prevfpt->vec[1];
	}
	else if (lastfpt->vec[0] <= evaltime) {
		/* after or on last sample, so just extend value */
		cvalue= lastfpt->vec[1];
	}
	else {
		/* find the one on the right frame (assume that these are spaced on 1-frame intervals) */
		fpt= prevfpt + (int)(evaltime - prevfpt->vec[0]);
		cvalue= fpt->vec[1];
	}
	
	/* return value */
	return cvalue;
}

/* ***************************** F-Curve - Evaluation ********************************* */

/* Evaluate and return the value of the given F-Curve at the specified frame ("evaltime") 
 * Note: this is also used for drivers
 */
float evaluate_fcurve (FCurve *fcu, float evaltime) 
{
	float cvalue= 0.0f;
	float devaltime;
	
	/* if there is a driver (only if this F-Curve is acting as 'driver'), evaluate it to find value to use as "evaltime" 
	 * since drivers essentially act as alternative input (i.e. in place of 'time') for F-Curves
	 *	- this value will also be returned as the value of the 'curve', if there are no keyframes
	 */
	if (fcu->driver) {
		/* evaltime now serves as input for the curve */
		evaltime= cvalue= evaluate_driver(fcu->driver, evaltime);
	}
	
	/* evaluate modifiers which modify time to evaluate the base curve at */
	devaltime= evaluate_time_fmodifiers(&fcu->modifiers, fcu, cvalue, evaltime);
	
	/* evaluate curve-data 
	 *	- 'devaltime' instead of 'evaltime', as this is the time that the last time-modifying 
	 *	  F-Curve modifier on the stack requested the curve to be evaluated at
	 */
	if (fcu->bezt)
		cvalue= fcurve_eval_keyframes(fcu, fcu->bezt, devaltime);
	else if (fcu->fpt)
		cvalue= fcurve_eval_samples(fcu, fcu->fpt, devaltime);
	
	/* evaluate modifiers */
	evaluate_value_fmodifiers(&fcu->modifiers, fcu, &cvalue, evaltime);
	
	/* if curve can only have integral values, perform truncation (i.e. drop the decimal part)
	 * here so that the curve can be sampled correctly
	 */
	if (fcu->flag & FCURVE_INT_VALUES)
		cvalue= (float)((int)cvalue);
	
	/* return evaluated value */
	return cvalue;
}

/* Calculate the value of the given F-Curve at the given frame, and set its curval */
void calculate_fcurve (FCurve *fcu, float ctime)
{
	/* only calculate + set curval (overriding the existing value) if curve has 
	 * any data which warrants this...
	 */
	if ( (fcu->totvert) || (fcu->driver && !(fcu->driver->flag & DRIVER_FLAG_INVALID)) ||
		 list_has_suitable_fmodifier(&fcu->modifiers, 0, FMI_TYPE_GENERATE_CURVE) )
	{
		/* calculate and set curval (evaluates driver too if necessary) */
		fcu->curval= evaluate_fcurve(fcu, ctime);
	}
}

