/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 * This file is based on WME.
 * http://dead-code.org/redir.php?target=wme
 * Copyright (c) 2003-2013 Jan Nedoma and contributors
 */

#include "engines/wintermute/base/base_game.h"
#include "engines/wintermute/base/base_engine.h"
#include "engines/wintermute/base/gfx/3dutils.h"
#include "engines/wintermute/base/gfx/xanimation.h"
#include "engines/wintermute/base/gfx/xanimation_set.h"
#include "engines/wintermute/base/gfx/xframe_node.h"
#include "engines/wintermute/base/gfx/xmodel.h"
#include "engines/wintermute/base/gfx/xfile_loader.h"
#include "engines/wintermute/dcgf.h"

namespace Wintermute {

//////////////////////////////////////////////////////////////////////////
Animation::Animation(BaseGame *inGame) : BaseClass(inGame) {
	_targetFrame = nullptr;
}

//////////////////////////////////////////////////////////////////////////
Animation::~Animation() {
	for (int32 i = 0; i < _posKeys.getSize(); i++) {
		delete _posKeys[i];
	}
	_posKeys.removeAll();

	for (int32 i = 0; i < _rotKeys.getSize(); i++) {
		delete _rotKeys[i];
	}
	_rotKeys.removeAll();

	for (int32 i = 0; i < _scaleKeys.getSize(); i++) {
		delete _scaleKeys[i];
	}
	_scaleKeys.removeAll();
}

//////////////////////////////////////////////////////////////////////////
bool Animation::findBone(FrameNode *rootFrame) {
	if (!_targetName.empty()) {
		_targetFrame = rootFrame->findFrame(_targetName.c_str());
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
bool Animation::load(XFileData *xobj, AnimationSet *parentAnimSet) {
	bool result;
	XClassType objectType;

	// Query the child for it's FileDataReference
	if (xobj->isReference()) {
		// The original data is found
		result = xobj->getType(objectType);
		if (!result) {
			BaseEngine::LOG(0, "Couldn't retrieve object type while loading animation");
			return result;
		}

		// The object must be a frame
		if (objectType == kXClassFrame) {
			// The frame is found, get its name
			// The name will be used later by the findBone function to get
			// a pointer to the target frame
			if (_targetFrame) {
				BaseEngine::LOG(0, "Animation frame name reference duplicated");
				return false;
			}

			// get name
			result = XModel::loadName(_targetName, xobj);
			if (!result) {
				BaseEngine::LOG(0, "Error retrieving frame name while loading animation");
				return false;
			}
		}
	} else {
		// a data object is found, get its type
		result = xobj->getType(objectType);
		if (!result)
			return false;

		if (objectType == kXClassAnimationKey) {
			// an animation key is found, load the data
			XAnimationKeyObject *animationKey = xobj->getXAnimationKeyObject();
			if (!animationKey)
				return false;
			result = loadAnimationKeyData(animationKey);
			if (!result)
				return false;
		} else if (objectType == kXClassAnimationOptions) {
			XAnimationOptionsObject *animationOptions = xobj->getXAnimationOptionsObject();
			if (!animationOptions)
				return false;
			result = loadAnimationOptionData(animationOptions, parentAnimSet);
			if (!result)
				return false;
		}
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
bool Animation::loadAnimationOptionData(XAnimationOptionsObject *animationOptionData, AnimationSet *parentAnimSet) {
	if (animationOptionData->_openclosed && parentAnimSet)
		parentAnimSet->_looping = true;

	return true;
}

//////////////////////////////////////////////////////////////////////////
bool Animation::loadAnimationKeyData(XAnimationKeyObject *animationKey) {
	// get the type and count of the key
	uint32 keyType = animationKey->_keyType;
	uint32 numKeys = animationKey->_numKeys;

	if (keyType == 0) { // rotation key
		if (_rotKeys.getSize() != 0) {
			BaseEngine::LOG(0, "Rotation key duplicated");
			return false;
		}

		for (uint32 key = 0; key < numKeys; key++) {
			const XTimedFloatKeys *fileRotKey = &animationKey->_keys[key];
			assert(fileRotKey->_numTfkeys == 4);

			BoneRotationKey *rotKey = new BoneRotationKey;
			rotKey->_time        = fileRotKey->_time;
			// NOTE x files are w x y z and QUATERNIONS are x y z w
			rotKey->_rotation._w = fileRotKey->_tfkeys[0];
			rotKey->_rotation._x = fileRotKey->_tfkeys[1];
			rotKey->_rotation._y = fileRotKey->_tfkeys[2];
			rotKey->_rotation._z = fileRotKey->_tfkeys[3];

			_rotKeys.add(rotKey);
		}
	} else if (keyType == 1) { // scale key
		if (_scaleKeys.getSize() != 0) {
			BaseEngine::LOG(0, "Scale key duplicated");
			return false;
		}

		for (uint32 key = 0; key < numKeys; key++) {
			const XTimedFloatKeys *fileScaleKey = &animationKey->_keys[key];
			assert(fileScaleKey->_numTfkeys == 3);

			BoneScaleKey *scaleKey = new BoneScaleKey;
			scaleKey->_time  = fileScaleKey->_time;
			scaleKey->_scale._x = fileScaleKey->_tfkeys[0];
			scaleKey->_scale._y = fileScaleKey->_tfkeys[1];
			scaleKey->_scale._z = fileScaleKey->_tfkeys[2];

			_scaleKeys.add(scaleKey);
		}
	} else if (keyType == 2) { // position key
		if (_posKeys.getSize() != 0) {
			BaseEngine::LOG(0, "Position key duplicated");
			return false;
		}

		for (uint32 key = 0; key < numKeys; key++) {
			const XTimedFloatKeys *filePosKey = &animationKey->_keys[key];
			assert(filePosKey->_numTfkeys == 3);

			BonePositionKey *posKey = new BonePositionKey;
			posKey->_time = filePosKey->_time;
			posKey->_pos._x = filePosKey->_tfkeys[0];
			posKey->_pos._y = filePosKey->_tfkeys[1];
			posKey->_pos._z = filePosKey->_tfkeys[2];

			_posKeys.add(posKey);
		}
	} else if (keyType == 4) { // matrix key
		if (_rotKeys.getSize() != 0 || _scaleKeys.getSize() != 0 || _posKeys.getSize() != 0) {
			BaseEngine::LOG(0, "Matrix key duplicated");
			return false;
		}

		DXQuaternion qRot;
		DXVector3 transVec;
		DXVector3 scaleVec;

		for (uint32 key = 0; key < numKeys; key++) {
			const XTimedFloatKeys *fileMatrixKey = &animationKey->_keys[key];
			uint32 time = fileMatrixKey->_time;
			assert(fileMatrixKey->_numTfkeys == 16);

			DXMatrix keyData;
			for (uint32 i = 0; i < 16; ++i) {
				keyData._m4x4[i] = fileMatrixKey->_tfkeys[i];
			}

			// we always convert matrix keys to T-R-S
			C3DUtils::decomposeMatrixSimple(&keyData, &transVec, &scaleVec, &qRot);

			BonePositionKey *positionKey = new BonePositionKey;
			positionKey->_time = time;
			positionKey->_pos = transVec;
			_posKeys.add(positionKey);

			BoneScaleKey *scaleKey = new BoneScaleKey;
			scaleKey->_time = time;
			scaleKey->_scale = scaleVec;
			_scaleKeys.add(scaleKey);

			BoneRotationKey *rotationKey = new BoneRotationKey;
			rotationKey->_time = time;
			rotationKey->_rotation = qRot;

			rotationKey->_rotation._x = -rotationKey->_rotation._x;
			rotationKey->_rotation._y = -rotationKey->_rotation._y;
			rotationKey->_rotation._z = -rotationKey->_rotation._z;
			_rotKeys.add(rotationKey);
		}
	} else {
		// the type is unknown, report the error
		BaseEngine::LOG(0, "Unexpected animation key type (%d)", keyType);
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
bool Animation::update(int slot, uint32 localTime, float animLerpValue) {
	// no target frame = no animation keys
	if (!_targetFrame) {
		return true;
	}

	DXVector3 resultPos(0.0f, 0.0f, 0.0f);
	DXVector3 resultScale(1.0f, 1.0f, 1.0f);
	DXQuaternion resultRot(0.0f, 0.0f, 0.0f, 1.0f);

	int keyIndex1, keyIndex2;
	uint32 time1, time2;
	float lerpValue;

	bool animate = false;

	// scale keys
	if (_scaleKeys.getSize() > 0) {
		keyIndex1 = keyIndex2 = 0;

		// get the two keys between which the time is currently in
		for (int32 key = 0; key < _scaleKeys.getSize(); key++) {
			if (_scaleKeys[key]->_time > localTime) {
				keyIndex2 = key;

				if (key > 0) {
					keyIndex1 = key - 1;
				} else { // when ikey == 0, then dwp2 == 0
					keyIndex1 = key;
				}

				break;
			}
		}

		time1 = _scaleKeys[keyIndex1]->_time;
		time2 = _scaleKeys[keyIndex2]->_time;

		// get the lerp value
		if ((time2 - time1) == 0) {
			lerpValue = 0;
		} else {
			lerpValue = float(localTime - time1) / float(time2 - time1);
		}

		// apply the lerp function on the scale vector
		DXVec3Lerp(&resultScale, &_scaleKeys[keyIndex1]->_scale, &_scaleKeys[keyIndex2]->_scale, lerpValue);

		animate = true;
	}

	// rotation keys
	if (_rotKeys.getSize() > 0) {
		keyIndex1 = keyIndex2 = 0;

		// get the two keys surrounding the current time value
		for (int32 key = 0; key < _rotKeys.getSize(); key++) {
			if (_rotKeys[key]->_time > localTime) {
				keyIndex2 = key;
				if (key > 0) {
					keyIndex1 = key - 1;
				} else { // when ikey == 0, then dwp2 == 0
					keyIndex1 = key;
				}

				break;
			}
		}
		time1 = _rotKeys[keyIndex1]->_time;
		time2 = _rotKeys[keyIndex2]->_time;

		// get the lerp value
		if ((time2 - time1) == 0) {
			lerpValue = 0;
		} else {
			lerpValue = float(localTime - time1) / float(time2 - time1);
		}

		// apply spherical lerp function
		DXQuaternion q1, q2;

		q1._x = -_rotKeys[keyIndex1]->_rotation._x;
		q1._y = -_rotKeys[keyIndex1]->_rotation._y;
		q1._z = -_rotKeys[keyIndex1]->_rotation._z;
		q1._w =  _rotKeys[keyIndex1]->_rotation._w;

		q2._x = -_rotKeys[keyIndex2]->_rotation._x;
		q2._y = -_rotKeys[keyIndex2]->_rotation._y;
		q2._z = -_rotKeys[keyIndex2]->_rotation._z;
		q2._w =  _rotKeys[keyIndex2]->_rotation._w;

		DXQuaternionSlerp(&resultRot, &q1, &q2, lerpValue);

		animate = true;
	}

	// position keys
	if (_posKeys.getSize() > 0) {
		keyIndex1 = keyIndex2 = 0;

		// get the two keys surrounding the time value
		for (int32 key = 0; key < _posKeys.getSize(); key++) {
			if (_posKeys[key]->_time > localTime) {
				keyIndex2 = key;
				if (key > 0) {
					keyIndex1 = key - 1;
				} else { // when ikey == 0, then dwp2 == 0
					keyIndex1 = key;
				}

				break;
			}
		}
		time1 = _posKeys[keyIndex1]->_time;
		time2 = _posKeys[keyIndex2]->_time;

		// get the lerp value
		if (time2 - time1 == 0)
			lerpValue = 0;
		else
			lerpValue = float(localTime - time1) / float(time2 - time1);

		// apply the lerp function
		DXVec3Lerp(&resultPos, &_posKeys[keyIndex1]->_pos, &_posKeys[keyIndex2]->_pos, lerpValue);

		animate = true;
	}

	if (animate) {
		_targetFrame->setTransformation(slot, resultPos, resultScale, resultRot, animLerpValue);
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
int Animation::getFrameTime() {
	uint32 frameTime = 0;
	uint32 prevTime;

	// get the shortest frame time
	prevTime = 0;
	for (int32 i = 0; i < _rotKeys.getSize(); i++) {
		if (frameTime == 0 || _rotKeys[i]->_time - prevTime < frameTime)
			frameTime = _rotKeys[i]->_time - prevTime;

		prevTime = _rotKeys[i]->_time;
	}

	prevTime = 0;
	for (int32 i = 0; i < _posKeys.getSize(); i++) {
		if (frameTime == 0 || _posKeys[i]->_time - prevTime < frameTime)
			frameTime = _posKeys[i]->_time - prevTime;

		prevTime = _posKeys[i]->_time;
	}

	prevTime = 0;
	for (int32 i = 0; i < _scaleKeys.getSize(); i++) {
		if (frameTime == 0 || _scaleKeys[i]->_time - prevTime < frameTime)
			frameTime = _scaleKeys[i]->_time - prevTime;

		prevTime = _scaleKeys[i]->_time;
	}

	return frameTime;
}

//////////////////////////////////////////////////////////////////////////
uint32 Animation::getTotalTime() {
	uint32 totalTime = 0;
	if (_rotKeys.getSize() > 0) {
		totalTime = MAX(totalTime, _rotKeys[_rotKeys.getSize() - 1]->_time);
	}

	if (_posKeys.getSize() > 0) {
		totalTime = MAX(totalTime, _posKeys[_posKeys.getSize() - 1]->_time);
	}

	if (_scaleKeys.getSize() > 0) {
		totalTime = MAX(totalTime, _scaleKeys[_scaleKeys.getSize() - 1]->_time);
	}

	return totalTime;
}

} // namespace Wintermute
