/*
 * ***** 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 gameengine/Converter/BL_ArmatureObject.cpp
 *  \ingroup bgeconv
 */


#include "BL_ArmatureObject.h"
#include "BL_ActionActuator.h"
#include "BL_Action.h"
#include "KX_BlenderSceneConverter.h"
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "BLI_ghash.h"
#include "BIK_api.h"
#include "BKE_action.h"
#include "BKE_armature.h"
#include "BKE_object.h"
#include "BKE_library.h"
#include "BKE_global.h"

extern "C" {
#include "BKE_animsys.h"
}

#include "BKE_constraint.h"
#include "CTR_Map.h"
#include "CTR_HashedPtr.h"
#include "MEM_guardedalloc.h"
#include "DNA_action_types.h"
#include "DNA_armature_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_constraint_types.h"
#include "RNA_access.h"
#include "KX_PythonInit.h"
#include "KX_KetsjiEngine.h"

#include "EXP_ListWrapper.h"

#include "MT_Matrix4x4.h"

/** 
 * Move here pose function for game engine so that we can mix with GE objects
 * Principle is as follow:
 * Use Blender structures so that BKE_pose_where_is can be used unchanged
 * Copy the constraint so that they can be enabled/disabled/added/removed at runtime
 * Don't copy the constraints for the pose used by the Action actuator, it does not need them.
 * Scan the constraint structures so that the KX equivalent of target objects are identified and 
 * stored in separate list.
 * When it is about to evaluate the pose, set the KX object position in the obmat of the corresponding
 * Blender objects and restore after the evaluation.
 */
static void game_copy_pose(bPose **dst, bPose *src, int copy_constraint)
{
	bPose *out;
	bPoseChannel *pchan, *outpchan;
	GHash *ghash;
	
	/* the game engine copies the current armature pose and then swaps
	 * the object pose pointer. this makes it possible to change poses
	 * without affecting the original blender data. */

	if (!src) {
		*dst=NULL;
		return;
	}
	else if (*dst==src) {
		printf("game_copy_pose source and target are the same\n");
		*dst=NULL;
		return;
	}
	
	out= (bPose*)MEM_dupallocN(src);
	out->chanhash = NULL;
	out->agroups.first= out->agroups.last= NULL;
	out->ikdata = NULL;
	out->ikparam = MEM_dupallocN(src->ikparam);
	out->flag |= POSE_GAME_ENGINE;
	BLI_duplicatelist(&out->chanbase, &src->chanbase);

	/* remap pointers */
	ghash= BLI_ghash_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "game_copy_pose gh");

	pchan= (bPoseChannel *)src->chanbase.first;
	outpchan= (bPoseChannel *)out->chanbase.first;
	for (; pchan; pchan=pchan->next, outpchan=outpchan->next)
		BLI_ghash_insert(ghash, pchan, outpchan);

	for (pchan = (bPoseChannel *)out->chanbase.first; pchan; pchan = pchan->next) {
		pchan->parent= (bPoseChannel *)BLI_ghash_lookup(ghash, pchan->parent);
		pchan->child= (bPoseChannel *)BLI_ghash_lookup(ghash, pchan->child);

		if (copy_constraint) {
			ListBase listb;
			// copy all constraint for backward compatibility
			// BKE_constraints_copy NULLs listb, no need to make extern for this operation.
			BKE_constraints_copy(&listb, &pchan->constraints, false);
			pchan->constraints= listb;
		}
		else {
			BLI_listbase_clear(&pchan->constraints);
		}

		if (pchan->custom) {
			id_us_plus(&pchan->custom->id);
		}

		// fails to link, props are not used in the BGE yet.
#if 0
		if (pchan->prop)
			pchan->prop= IDP_CopyProperty(pchan->prop);
#endif
		pchan->prop= NULL;
	}

	BLI_ghash_free(ghash, NULL, NULL);
	// set acceleration structure for channel lookup
	BKE_pose_channels_hash_make(out);
	*dst=out;
}



/* Only allowed for Poses with identical channels */
static void game_blend_poses(bPose *dst, bPose *src, float srcweight, short mode)
{
	bPoseChannel *dchan;
	const bPoseChannel *schan;
	bConstraint *dcon, *scon;
	float dstweight;
	int i;

	if (mode == BL_Action::ACT_BLEND_BLEND)
	{
		dstweight = 1.0f - srcweight;
	} else if (mode == BL_Action::ACT_BLEND_ADD)
	{
		dstweight = 1.0f;
	} else {
		dstweight = 1.0f;
	}
	
	schan= (bPoseChannel *)src->chanbase.first;
	for (dchan = (bPoseChannel *)dst->chanbase.first; dchan; dchan=(bPoseChannel *)dchan->next, schan= (bPoseChannel *)schan->next) {
		// always blend on all channels since we don't know which one has been set
		/* quat interpolation done separate */
		if (schan->rotmode == ROT_MODE_QUAT) {
			float dquat[4], squat[4];
			
			copy_qt_qt(dquat, dchan->quat);
			copy_qt_qt(squat, schan->quat);
			// Normalize quaternions so that interpolation/multiplication result is correct.
			normalize_qt(dquat);
			normalize_qt(squat);

			if (mode==BL_Action::ACT_BLEND_BLEND)
				interp_qt_qtqt(dchan->quat, dquat, squat, srcweight);
			else {
				mul_fac_qt_fl(squat, srcweight);
				mul_qt_qtqt(dchan->quat, dquat, squat);
			}
			
			normalize_qt(dchan->quat);
		}

		for (i=0; i<3; i++) {
			/* blending for loc and scale are pretty self-explanatory... */
			dchan->loc[i] = (dchan->loc[i]*dstweight) + (schan->loc[i]*srcweight);
			dchan->size[i] = 1.0f + ((dchan->size[i]-1.0f)*dstweight) + ((schan->size[i]-1.0f)*srcweight);
			
			/* euler-rotation interpolation done here instead... */
			// FIXME: are these results decent?
			if (schan->rotmode)
				dchan->eul[i] = (dchan->eul[i]*dstweight) + (schan->eul[i]*srcweight);
		}
		for (dcon= (bConstraint *)dchan->constraints.first, scon= (bConstraint *)schan->constraints.first;
		     dcon && scon;
		     dcon = dcon->next, scon = scon->next)
		{
			/* no 'add' option for constraint blending */
			dcon->enforce= dcon->enforce*(1.0f-srcweight) + scon->enforce*srcweight;
		}
	}
	
	/* this pose is now in src time */
	dst->ctime= src->ctime;
}

BL_ArmatureObject::BL_ArmatureObject(
				void* sgReplicationInfo, 
				SG_Callbacks callbacks, 
				Object *armature,
				Scene *scene,
				int vert_deform_type)

:	KX_GameObject(sgReplicationInfo,callbacks),
	m_controlledConstraints(),
	m_poseChannels(),
	m_scene(scene), // maybe remove later. needed for BKE_pose_where_is
	m_lastframe(0.0),
	m_timestep(0.040),
	m_vert_deform_type(vert_deform_type),
	m_constraintNumber(0),
	m_channelNumber(0),
	m_lastapplyframe(0.0)
{
	m_origObjArma = armature; // Keep a copy of the original armature so we can fix drivers later
	m_objArma = BKE_object_copy(G.main, armature);
	m_objArma->data = BKE_armature_copy(G.main, (bArmature *)armature->data);
	// During object replication ob->data is increase, we decrease it now because we get a copy.
	id_us_min(&((bArmature *)m_origObjArma->data)->id);
	m_pose = m_objArma->pose;
	// need this to get iTaSC working ok in the BGE
	m_pose->flag |= POSE_GAME_ENGINE;
	memcpy(m_obmat, m_objArma->obmat, sizeof(m_obmat));

	// The side-effect of this method registers this object as "animatable" with the KX_Scene.
	GetActionManager();
}

BL_ArmatureObject::~BL_ArmatureObject()
{
	BL_ArmatureConstraint* constraint;
	while ((constraint = m_controlledConstraints.Remove()) != NULL) {
		delete constraint;
	}
	BL_ArmatureChannel* channel;
	while ((channel = static_cast<BL_ArmatureChannel*>(m_poseChannels.Remove())) != NULL) {
		delete channel;
	}

	if (m_objArma) {
		BKE_libblock_free(G.main, m_objArma->data);
		BKE_libblock_free(G.main, m_objArma);
	}
}


void BL_ArmatureObject::LoadConstraints(KX_BlenderSceneConverter* converter)
{
	// first delete any existing constraint (should not have any)
	while (!m_controlledConstraints.Empty()) {
		BL_ArmatureConstraint* constraint = m_controlledConstraints.Remove();
		delete constraint;
	}
	m_constraintNumber = 0;

	// list all the constraint and convert them to BL_ArmatureConstraint
	// get the persistent pose structure
	bPoseChannel* pchan;
	bConstraint* pcon;
	const bConstraintTypeInfo* cti;
	Object* blendtarget;
	KX_GameObject* gametarget;
	KX_GameObject* gamesubtarget;

	// and locate the constraint
	for (pchan = (bPoseChannel *)m_pose->chanbase.first; pchan; pchan = pchan->next) {
		for (pcon = (bConstraint *)pchan->constraints.first; pcon; pcon = pcon->next) {
			if (pcon->flag & CONSTRAINT_DISABLE)
				continue;
			// which constraint should we support?
			switch (pcon->type) {
			case CONSTRAINT_TYPE_TRACKTO:
			case CONSTRAINT_TYPE_DAMPTRACK:
			case CONSTRAINT_TYPE_KINEMATIC:
			case CONSTRAINT_TYPE_ROTLIKE:
			case CONSTRAINT_TYPE_LOCLIKE:
			case CONSTRAINT_TYPE_MINMAX:
			case CONSTRAINT_TYPE_SIZELIKE:
			case CONSTRAINT_TYPE_LOCKTRACK:
			case CONSTRAINT_TYPE_STRETCHTO:
			case CONSTRAINT_TYPE_CLAMPTO:
			case CONSTRAINT_TYPE_TRANSFORM:
			case CONSTRAINT_TYPE_DISTLIMIT:
			case CONSTRAINT_TYPE_TRANSLIKE:
				cti = BKE_constraint_typeinfo_get(pcon);
				gametarget = gamesubtarget = NULL;
				if (cti && cti->get_constraint_targets) {
					ListBase listb = { NULL, NULL };
					cti->get_constraint_targets(pcon, &listb);
					if (listb.first) {
						bConstraintTarget* target = (bConstraintTarget*)listb.first;
						if (target->tar && target->tar != m_objArma) {
							// only remember external objects, self target is handled automatically
							blendtarget = target->tar;
							gametarget = converter->FindGameObject(blendtarget);
						}
						if (target->next != NULL) {
							// secondary target
							target = target->next;
							if (target->tar && target->tar != m_objArma) {
								// only track external object
								blendtarget = target->tar;
								gamesubtarget = converter->FindGameObject(blendtarget);
							}
						}
					}
					if (cti->flush_constraint_targets)
						cti->flush_constraint_targets(pcon, &listb, 1);
				}
				BL_ArmatureConstraint* constraint = new BL_ArmatureConstraint(this, pchan, pcon, gametarget, gamesubtarget);
				m_controlledConstraints.AddBack(constraint);
				m_constraintNumber++;
			}
		}
	}

	// If we have constraints, make sure we get treated as an "animated" object
	if (m_constraintNumber > 0)
		GetActionManager();
}

BL_ArmatureConstraint* BL_ArmatureObject::GetConstraint(const char* posechannel, const char* constraintname)
{
	SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
	for (cit.begin(); !cit.end(); ++cit) {
		BL_ArmatureConstraint* constraint = *cit;
		if (constraint->Match(posechannel, constraintname))
			return constraint;
	}
	return NULL;
}

BL_ArmatureConstraint* BL_ArmatureObject::GetConstraint(const char* posechannelconstraint)
{
	// performance: use hash string instead of plain string compare
	SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
	for (cit.begin(); !cit.end(); ++cit) {
		BL_ArmatureConstraint* constraint = *cit;
		if (!strcmp(constraint->GetName(), posechannelconstraint))
			return constraint;
	}
	return NULL;
}

BL_ArmatureConstraint* BL_ArmatureObject::GetConstraint(int index)
{
	SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
	for (cit.begin(); !cit.end() && index; ++cit, --index);
	return (cit.end()) ? NULL : *cit;
}

/* this function is called to populate the m_poseChannels list */
void BL_ArmatureObject::LoadChannels()
{
	if (m_poseChannels.Empty()) {
		bPoseChannel* pchan;
		BL_ArmatureChannel* proxy;
	
		m_channelNumber = 0;
		for (pchan = (bPoseChannel *)m_pose->chanbase.first; pchan; pchan=(bPoseChannel *)pchan->next) {
			proxy = new BL_ArmatureChannel(this, pchan);
			m_poseChannels.AddBack(proxy);
			m_channelNumber++;
		}
	}
}

BL_ArmatureChannel* BL_ArmatureObject::GetChannel(bPoseChannel* pchan)
{
	LoadChannels();
	SG_DList::iterator<BL_ArmatureChannel> cit(m_poseChannels);
	for (cit.begin(); !cit.end(); ++cit) 
	{
		BL_ArmatureChannel* channel = *cit;
		if (channel->m_posechannel == pchan)
			return channel;
	}
	return NULL;
}

BL_ArmatureChannel* BL_ArmatureObject::GetChannel(const char* str)
{
	LoadChannels();
	SG_DList::iterator<BL_ArmatureChannel> cit(m_poseChannels);
	for (cit.begin(); !cit.end(); ++cit) 
	{
		BL_ArmatureChannel* channel = *cit;
		if (!strcmp(channel->m_posechannel->name, str))
			return channel;
	}
	return NULL;
}

BL_ArmatureChannel* BL_ArmatureObject::GetChannel(int index)
{
	LoadChannels();
	if (index < 0 || index >= m_channelNumber)
		return NULL;
	SG_DList::iterator<BL_ArmatureChannel> cit(m_poseChannels);
	for (cit.begin(); !cit.end() && index; ++cit, --index);
	return (cit.end()) ? NULL : *cit;
}

CValue* BL_ArmatureObject::GetReplica()
{
	BL_ArmatureObject* replica = new BL_ArmatureObject(*this);
	replica->ProcessReplica();
	return replica;
}

void BL_ArmatureObject::ProcessReplica()
{
	KX_GameObject::ProcessReplica();

	bArmature* tmp = (bArmature*)m_objArma->data;
	m_objArma = BKE_object_copy(G.main, m_objArma);
	m_objArma->data = BKE_armature_copy(G.main, tmp);
	m_pose = m_objArma->pose;
}

void BL_ArmatureObject::ReParentLogic()
{
	SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
	for (cit.begin(); !cit.end(); ++cit) {
		(*cit)->ReParent(this);
	}
	KX_GameObject::ReParentLogic();
}

void BL_ArmatureObject::Relink(CTR_Map<CTR_HashedPtr, void*> *obj_map)
{
	SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
	for (cit.begin(); !cit.end(); ++cit) {
		(*cit)->Relink(obj_map);
	}
	KX_GameObject::Relink(obj_map);
}

bool BL_ArmatureObject::UnlinkObject(SCA_IObject* clientobj)
{
	// clientobj is being deleted, make sure we don't hold any reference to it
	bool res = false;
	SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
	for (cit.begin(); !cit.end(); ++cit) {
		res |= (*cit)->UnlinkObject(clientobj);
	}
	return res;
}

void BL_ArmatureObject::ApplyPose()
{
	m_armpose = m_objArma->pose;
	m_objArma->pose = m_pose;
	// in the GE, we use ctime to store the timestep
	m_pose->ctime = (float)m_timestep;
	//m_scene->r.cfra++;
	if (m_lastapplyframe != m_lastframe) {
		// update the constraint if any, first put them all off so that only the active ones will be updated
		SG_DList::iterator<BL_ArmatureConstraint> cit(m_controlledConstraints);
		for (cit.begin(); !cit.end(); ++cit) {
			(*cit)->UpdateTarget();
		}
		// update ourself
		UpdateBlenderObjectMatrix(m_objArma);
		BKE_pose_where_is(m_scene, m_objArma); // XXX
		// restore ourself
		memcpy(m_objArma->obmat, m_obmat, sizeof(m_obmat));
		// restore active targets
		for (cit.begin(); !cit.end(); ++cit) {
			(*cit)->RestoreTarget();
		}
		m_lastapplyframe = m_lastframe;
	}
}

void BL_ArmatureObject::RestorePose()
{
	m_objArma->pose = m_armpose;
	m_armpose = NULL;
}

void BL_ArmatureObject::SetPose(bPose *pose)
{
	extract_pose_from_pose(m_pose, pose);
	m_lastapplyframe = -1.0;
}

void BL_ArmatureObject::SetPoseByAction(bAction *action, float localtime)
{
	Object *arm = GetArmatureObject();

	PointerRNA ptrrna;
	RNA_id_pointer_create(&arm->id, &ptrrna);

	animsys_evaluate_action(&ptrrna, action, NULL, localtime);
}

void BL_ArmatureObject::BlendInPose(bPose *blend_pose, float weight, short mode)
{
	game_blend_poses(m_pose, blend_pose, weight, mode);
}

bool BL_ArmatureObject::UpdateTimestep(double curtime)
{
	if (curtime != m_lastframe) {
		// compute the timestep for the underlying IK algorithm
		m_timestep = curtime-m_lastframe;
		m_lastframe= curtime;
	}

	return false;
}

void BL_ArmatureObject::GetPose(bPose **pose)
{
	/* If the caller supplies a null pose, create a new one. */
	/* Otherwise, copy the armature's pose channels into the caller-supplied pose */
		
	if (!*pose) {
		/* probably not to good of an idea to
		 * duplicate everything, but it clears up 
		 * a crash and memory leakage when 
		 * &BL_ActionActuator::m_pose is freed
		 */
		game_copy_pose(pose, m_pose, 0);
	}
	else {
		if (*pose == m_pose)
			// no need to copy if the pointers are the same
			return;

		extract_pose_from_pose(*pose, m_pose);
	}
}

double BL_ArmatureObject::GetLastFrame()
{
	return m_lastframe;
}

bool BL_ArmatureObject::GetBoneMatrix(Bone* bone, MT_Matrix4x4& matrix)
{
	bPoseChannel *pchan;

	ApplyPose();
	pchan = BKE_pose_channel_find_name(m_objArma->pose, bone->name);
	if (pchan)
		matrix.setValue(&pchan->pose_mat[0][0]);
	RestorePose();

	return (pchan != NULL);
}

float BL_ArmatureObject::GetBoneLength(Bone* bone) const
{
	return (float)(MT_Point3(bone->head) - MT_Point3(bone->tail)).length();
}

#ifdef WITH_PYTHON

// PYTHON

PyTypeObject BL_ArmatureObject::Type = {
	PyVarObject_HEAD_INIT(NULL, 0)
	"BL_ArmatureObject",
	sizeof(PyObjectPlus_Proxy),
	0,
	py_base_dealloc,
	0,
	0,
	0,
	0,
	py_base_repr,
	0,
	&KX_GameObject::Sequence,
	&KX_GameObject::Mapping,
	0,0,0,
	NULL,
	NULL,
	0,
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
	0,0,0,0,0,0,0,
	Methods,
	0,
	0,
	&KX_GameObject::Type,
	0,0,0,0,0,0,
	py_base_new
};

PyMethodDef BL_ArmatureObject::Methods[] = {
	KX_PYMETHODTABLE_NOARGS(BL_ArmatureObject, update),
	{NULL,NULL} //Sentinel
};

PyAttributeDef BL_ArmatureObject::Attributes[] = {

	KX_PYATTRIBUTE_RO_FUNCTION("constraints",		BL_ArmatureObject, pyattr_get_constraints),
	KX_PYATTRIBUTE_RO_FUNCTION("channels",		BL_ArmatureObject, pyattr_get_channels),
	{NULL} //Sentinel
};

static int bl_armature_object_get_constraints_size_cb(void *self_v)
{
	return ((BL_ArmatureObject *)self_v)->GetConstraintNumber();
}

static PyObject *bl_armature_object_get_constraints_item_cb(void *self_v, int index)
{
	return ((BL_ArmatureObject *)self_v)->GetConstraint(index)->GetProxy();
}

static const char *bl_armature_object_get_constraints_item_name_cb(void *self_v, int index)
{
	return ((BL_ArmatureObject *)self_v)->GetConstraint(index)->GetName();
}

PyObject *BL_ArmatureObject::pyattr_get_constraints(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
{
	return (new CListWrapper(self_v,
							 ((BL_ArmatureObject *)self_v)->GetProxy(),
							 NULL,
							 bl_armature_object_get_constraints_size_cb,
							 bl_armature_object_get_constraints_item_cb,
							 bl_armature_object_get_constraints_item_name_cb,
							 NULL))->NewProxy(true);
}

static int bl_armature_object_get_channels_size_cb(void *self_v)
{
	return ((BL_ArmatureObject *)self_v)->GetChannelNumber();
}

static PyObject *bl_armature_object_get_channels_item_cb(void *self_v, int index)
{
	return ((BL_ArmatureObject *)self_v)->GetChannel(index)->GetProxy();
}

static const char *bl_armature_object_get_channels_item_name_cb(void *self_v, int index)
{
	return ((BL_ArmatureObject *)self_v)->GetChannel(index)->GetName();
}

PyObject *BL_ArmatureObject::pyattr_get_channels(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
{
	BL_ArmatureObject *self = static_cast<BL_ArmatureObject *>(self_v);
	self->LoadChannels(); // make sure we have the channels
	return (new CListWrapper(self_v,
							 self->GetProxy(),
							 NULL,
							 bl_armature_object_get_channels_size_cb,
							 bl_armature_object_get_channels_item_cb,
							 bl_armature_object_get_channels_item_name_cb,
							 NULL))->NewProxy(true);
}

KX_PYMETHODDEF_DOC_NOARGS(BL_ArmatureObject, update, 
						  "update()\n"
						  "Make sure that the armature will be updated on next graphic frame.\n"
						  "This is automatically done if a KX_ArmatureActuator with mode run is active\n"
						  "or if an action is playing. This function is useful in other cases.\n")
{
	UpdateTimestep(KX_GetActiveEngine()->GetFrameTime());
	Py_RETURN_NONE;
}

#endif // WITH_PYTHON
