// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include "CSceneNodeAnimatorCameraFPS.h"
#include "IVideoDriver.h"
#include "ISceneManager.h"
#include "Keycodes.h"
#include "ICursorControl.h"
#include "ICameraSceneNode.h"
#include "ISceneNodeAnimatorCollisionResponse.h"

namespace irr
{
namespace scene
{

//! constructor
CSceneNodeAnimatorCameraFPS::CSceneNodeAnimatorCameraFPS(gui::ICursorControl* cursorControl,
		f32 rotateSpeed, f32 moveSpeed, f32 jumpSpeed,
		SKeyMap* keyMapArray, u32 keyMapSize, bool noVerticalMovement, bool invertY, float rotateSpeedKeyboard)
: CursorControl(cursorControl),
// On X11 we get events even when mouse is not inside the Irrlicht window, on Windows we don't.
// It might be possible to add grabbing on Windows as well in which case this has to be somewhat changed.
// TODO: I don't know about OSX, but in theory it should be like old Irrlicht 1.8 behavior whatever that was there.
#ifdef _IRR_COMPILE_WITH_X11_DEVICE_
	GrabMouse(false),
#else
	GrabMouse(true),
#endif
	MaxVerticalAngle(88.0f), NoVerticalMovement(noVerticalMovement),
	MoveSpeed(moveSpeed),
	RotateSpeedKeyboard(rotateSpeedKeyboard), RotateSpeed(rotateSpeed),
	JumpSpeed(jumpSpeed),
	MouseYDirection(invertY ? -1.0f : 1.0f),
	LastAnimationTime(0), HadMouseEvent(false), firstUpdate(true), firstInput(true)
{
	#ifdef _DEBUG
	setDebugName("CCameraSceneNodeAnimatorFPS");
	#endif

	if (CursorControl)
		CursorControl->grab();

	allKeysUp();

	// create key map
	if (!keyMapArray || !keyMapSize)
	{
		// create default key map
		KeyMap.push_back(SKeyMap(EKA_MOVE_FORWARD, irr::KEY_UP));
		KeyMap.push_back(SKeyMap(EKA_MOVE_BACKWARD, irr::KEY_DOWN));
		KeyMap.push_back(SKeyMap(EKA_STRAFE_LEFT, irr::KEY_LEFT));
		KeyMap.push_back(SKeyMap(EKA_STRAFE_RIGHT, irr::KEY_RIGHT));
		KeyMap.push_back(SKeyMap(EKA_JUMP_UP, irr::KEY_KEY_J));
	}
	else
	{
		// create custom key map
		setKeyMap(keyMapArray, keyMapSize);
	}
}


//! destructor
CSceneNodeAnimatorCameraFPS::~CSceneNodeAnimatorCameraFPS()
{
	if (CursorControl)
		CursorControl->drop();
}


//! It is possible to send mouse and key events to the camera. Most cameras
//! may ignore this input, but camera scene nodes which are created for
//! example with scene::ISceneManager::addMayaCameraSceneNode or
//! scene::ISceneManager::addFPSCameraSceneNode, may want to get this input
//! for changing their position, look at target or whatever.
bool CSceneNodeAnimatorCameraFPS::OnEvent(const SEvent& evt)
{
	switch(evt.EventType)
	{
	case EET_KEY_INPUT_EVENT:
		for (u32 i=0; i<KeyMap.size(); ++i)
		{
			if (KeyMap[i].KeyCode == evt.KeyInput.Key)
			{
				CursorKeys[KeyMap[i].Action] = evt.KeyInput.PressedDown;
				return true;
			}
		}
		break;

	case EET_MOUSE_INPUT_EVENT:
		HadMouseEvent = true;
		if ( evt.MouseInput.Event == EMIE_MOUSE_ENTER_CANVAS && CursorControl)
		{
			CursorControl->setPosition(0.5f, 0.5f);
			CenterCursor = CursorControl->getRelativePosition(false);
			CursorPos = CenterCursor;
		}
		break;

	default:
		break;
	}

	return false;
}


void CSceneNodeAnimatorCameraFPS::animateNode(ISceneNode* node, u32 timeMs)
{
	if (!node || node->getType() != ESNT_CAMERA)
		return;

	ICameraSceneNode* camera = static_cast<ICameraSceneNode*>(node);

	if (firstUpdate)
	{
		camera->updateAbsolutePosition();
		if (CursorControl )
		{
			CursorControl->setPosition(0.5f, 0.5f);
			CursorPos = CenterCursor = CursorControl->getRelativePosition(false);
		}

		LastAnimationTime = timeMs;

		firstUpdate = false;
	}

	// If the camera isn't the active camera, and receiving input, then don't process it.
	if(!camera->isInputReceiverEnabled())
	{
		firstInput = true;
		return;
	}

	if ( firstInput )
	{
		allKeysUp();
		firstInput = false;
	}

	scene::ISceneManager * smgr = camera->getSceneManager();
	if(smgr && smgr->getActiveCamera() != camera)
		return;

	// get time
	f32 timeDiff = (f32) ( timeMs - LastAnimationTime );
	LastAnimationTime = timeMs;

	// Update rotation
	core::vector3df target = (camera->getTarget() - camera->getAbsolutePosition());
	core::vector3df relativeRotation = target.getHorizontalAngle();

	if (CursorControl)
	{
		bool reset = false;

		if ( HadMouseEvent || GrabMouse)
			CursorPos = CursorControl->getRelativePosition();

		if (CursorPos != CenterCursor)
		{
			relativeRotation.Y -= (CenterCursor.X - CursorPos.X) * RotateSpeed;
			relativeRotation.X -= (CenterCursor.Y - CursorPos.Y) * RotateSpeed * MouseYDirection;

			reset = true;
		}

		if ( GrabMouse && !reset)
		{
			// Special case, mouse is whipped outside of window before it can update.
			video::IVideoDriver* driver = smgr->getVideoDriver();
			core::vector2d<u32> mousepos(u32(CursorPos.X), u32(CursorPos.Y));
			core::rect<u32> screenRect(0, 0, driver->getScreenSize().Width, driver->getScreenSize().Height);

			// Only if we are moving outside quickly.
			reset = !screenRect.isPointInside(mousepos);
		}

		if(reset)
		{
			CursorControl->setPosition(0.5f, 0.5f);
			CenterCursor = CursorControl->getRelativePosition(false);	// often no longer 0.5 due to int/float conversions
			CursorPos = CenterCursor;
 		}
	}
	HadMouseEvent = false;

	// keyboard rotation
	if (CursorKeys[EKA_ROTATE_LEFT])
		relativeRotation.Y -= timeDiff * RotateSpeedKeyboard;

	if (CursorKeys[EKA_ROTATE_RIGHT])
		relativeRotation.Y += timeDiff * RotateSpeedKeyboard;

	// X < MaxVerticalAngle or X > 360-MaxVerticalAngle

	if (relativeRotation.X > MaxVerticalAngle*2 &&
		relativeRotation.X < 360.0f-MaxVerticalAngle)
	{
		relativeRotation.X = 360.0f-MaxVerticalAngle;
	}
	else
	if (relativeRotation.X > MaxVerticalAngle &&
		relativeRotation.X < 360.0f-MaxVerticalAngle)
	{
		relativeRotation.X = MaxVerticalAngle;
	}

	// set target
	core::vector3df pos = camera->getPosition();
	target.set(0,0, core::max_(1.f, pos.getLength()));	// better float precision than (0,0,1) in target-pos calculation in camera
	core::vector3df movedir(target);

	core::matrix4 mat;
	mat.setRotationDegrees(core::vector3df(relativeRotation.X, relativeRotation.Y, 0));
	mat.transformVect(target);

	if (NoVerticalMovement)
	{
		mat.setRotationDegrees(core::vector3df(0, relativeRotation.Y, 0));
		mat.transformVect(movedir);
	}
	else
	{
		movedir = target;
	}

	movedir.normalize();

	if (CursorKeys[EKA_MOVE_FORWARD])
		pos += movedir * timeDiff * MoveSpeed;

	if (CursorKeys[EKA_MOVE_BACKWARD])
		pos -= movedir * timeDiff * MoveSpeed;

	// strafing

	core::vector3df strafevect(target);
	strafevect = strafevect.crossProduct(camera->getUpVector());

	if (NoVerticalMovement)
		strafevect.Y = 0.0f;

	strafevect.normalize();

	if (CursorKeys[EKA_STRAFE_LEFT])
		pos += strafevect * timeDiff * MoveSpeed;

	if (CursorKeys[EKA_STRAFE_RIGHT])
		pos -= strafevect * timeDiff * MoveSpeed;

	// For jumping, we find the collision response animator attached to our camera
	// and if it's not falling, we tell it to jump.
	if (CursorKeys[EKA_JUMP_UP])
	{
		const ISceneNodeAnimatorList& animators = camera->getAnimators();
		ISceneNodeAnimatorList::ConstIterator it = animators.begin();
		while(it != animators.end())
		{
			if(ESNAT_COLLISION_RESPONSE == (*it)->getType())
			{
				ISceneNodeAnimatorCollisionResponse * collisionResponse =
					static_cast<ISceneNodeAnimatorCollisionResponse *>(*it);

				if(!collisionResponse->isFalling())
					collisionResponse->jump(JumpSpeed);
			}

			it++;
		}
	}

	// write translation
	camera->setPosition(pos);

	// write right target
	target += pos;
	camera->setTarget(target);
}

void CSceneNodeAnimatorCameraFPS::allKeysUp()
{
	for (u32 i=0; i<EKA_COUNT; ++i)
		CursorKeys[i] = false;
}


//! Sets the rotation speed
void CSceneNodeAnimatorCameraFPS::setRotateSpeed(f32 speed)
{
	RotateSpeed = speed;
}


//! Sets the movement speed
void CSceneNodeAnimatorCameraFPS::setMoveSpeed(f32 speed)
{
	MoveSpeed = speed;
}


//! Gets the rotation speed
f32 CSceneNodeAnimatorCameraFPS::getRotateSpeed() const
{
	return RotateSpeed;
}


// Gets the movement speed
f32 CSceneNodeAnimatorCameraFPS::getMoveSpeed() const
{
	return MoveSpeed;
}

//! Sets the keyboard mapping for this animator
void CSceneNodeAnimatorCameraFPS::setKeyMap(SKeyMap *map, u32 count)
{
	// clear the keymap
	KeyMap.clear();

	// add actions
	for (u32 i=0; i<count; ++i)
	{
		KeyMap.push_back(map[i]);
	}
}

void CSceneNodeAnimatorCameraFPS::setKeyMap(const core::array<SKeyMap>& keymap)
{
	KeyMap=keymap;
}

const core::array<SKeyMap>& CSceneNodeAnimatorCameraFPS::getKeyMap() const
{
	return KeyMap;
}


//! Sets whether vertical movement should be allowed.
void CSceneNodeAnimatorCameraFPS::setVerticalMovement(bool allow)
{
	NoVerticalMovement = !allow;
}


//! Sets whether the Y axis of the mouse should be inverted.
void CSceneNodeAnimatorCameraFPS::setInvertMouse(bool invert)
{
	if (invert)
		MouseYDirection = -1.0f;
	else
		MouseYDirection = 1.0f;
}


ISceneNodeAnimator* CSceneNodeAnimatorCameraFPS::createClone(ISceneNode* node, ISceneManager* newManager)
{
	CSceneNodeAnimatorCameraFPS * newAnimator =
		new CSceneNodeAnimatorCameraFPS(CursorControl,	RotateSpeed, MoveSpeed, JumpSpeed,
											0, 0, NoVerticalMovement);
	newAnimator->cloneMembers(this);
	newAnimator->setKeyMap(KeyMap);
	return newAnimator;
}

void CSceneNodeAnimatorCameraFPS::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const
{
	ISceneNodeAnimator::serializeAttributes(out, options);

	out->addFloat("MaxVerticalAngle", MaxVerticalAngle);
	out->addBool("NoVerticalMovement", NoVerticalMovement);
	out->addFloat("MoveSpeed", MoveSpeed);
	out->addFloat("RotateSpeedKeyboard", RotateSpeedKeyboard);
	out->addFloat("RotateSpeed", RotateSpeed);
	out->addFloat("JumpSpeed", JumpSpeed);
	out->addFloat("MouseYDirection", MouseYDirection);

	out->addInt("KeyMapSize", (s32)KeyMap.size());
	for ( u32 i=0; i < KeyMap.size(); ++i )
	{
		core::stringc name("Action");
		name += core::stringc(i);
		out->addInt(name.c_str(), (int)KeyMap[i].Action);
		name = core::stringc("KeyCode") + core::stringc(i);
		out->addInt(name.c_str(), (int)KeyMap[i].KeyCode);
	}
}

void CSceneNodeAnimatorCameraFPS::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options)
{
	ISceneNodeAnimator::deserializeAttributes(in, options);

	MaxVerticalAngle = in->getAttributeAsFloat("MaxVerticalAngle", MaxVerticalAngle);
	NoVerticalMovement = in->getAttributeAsBool("NoVerticalMovement", NoVerticalMovement);
	MoveSpeed = in->getAttributeAsFloat("MoveSpeed", MoveSpeed);
	RotateSpeedKeyboard = in->getAttributeAsFloat("RotateSpeedKeyboard", RotateSpeedKeyboard);
	RotateSpeed = in->getAttributeAsFloat("RotateSpeed", RotateSpeed);
	JumpSpeed = in->getAttributeAsFloat("JumpSpeed", JumpSpeed);
	MouseYDirection = in->getAttributeAsFloat("MouseYDirection", MouseYDirection);

	if ( in->findAttribute("KeyMapSize") )
	{
		KeyMap.clear();
		s32 keyMapSize = in->getAttributeAsInt("KeyMapSize");
		for ( u32 i=0; i < (u32)keyMapSize; ++i )
		{
			SKeyMap keyMapEntry;
			core::stringc name("Action");
			name += core::stringc(i);
			keyMapEntry.Action = static_cast<EKEY_ACTION>(in->getAttributeAsInt(name.c_str()));
			name = core::stringc("KeyCode") + core::stringc(i);
			keyMapEntry.KeyCode = static_cast<EKEY_CODE>(in->getAttributeAsInt(name.c_str()));
			KeyMap.push_back(keyMapEntry);
		}
	}
}


} // namespace scene
} // namespace irr