#include "PlayableCamera.h"

#include <math.h>

#include "Mix/HID.h"
#include "Mix/Scene.h"

namespace Utility{

const Mix::Vector3 PlayableCamera::UP = Mix::Vector3( 0.0f, 1.0f, 0.0f );
const Float32 PlayableCamera::DEF_DISTANCE = 5.0f;
const Float32 PlayableCamera::MIN_DISTANCE = 1.0f;
const Float32 PlayableCamera::MAX_DISTANCE = 10.0f;
const Float32 PlayableCamera::STEP_DISTANCE = 3.0f;

PlayableCamera::PlayableCamera( Mix::Scene::IUniversalCamera* pCamera,
								Mix::HID::IGamepad* pGamepad,
								Mix::Scene::IActorModel* pModel ) :
m_pCamera( NULL ),
m_pGamepad( NULL ),
m_pNode( NULL ),
m_AtToEye( 0.0f, 0.5f, 1.0f ),
m_Height( 0.5f ),
m_Distance( PlayableCamera::DEF_DISTANCE )
{
	MIX_ASSERT( pCamera != NULL );
	MIX_ASSERT( pModel != NULL );
	MIX_ASSERT( pGamepad != NULL );

	MIX_ADD_REF( pCamera );
	m_pCamera = pCamera;

	MIX_ADD_REF( pGamepad );
	m_pGamepad = pGamepad;

	pModel->GetNodeByName( L"Body_C0", &m_pNode );
	MIX_ASSERT( m_pNode != NULL );

	m_AtToEye.Normalize();
}

PlayableCamera::~PlayableCamera( void )
{
	Dispose();
}

void PlayableCamera::Dispose( void )
{
	MIX_RELEASE( m_pCamera );
	MIX_RELEASE( m_pGamepad );
	MIX_RELEASE( m_pNode );
}

void PlayableCamera::OnUpdate( Float32 dt )
{
	const Mix::Vector2& rightStick = m_pGamepad->GetStickState( Mix::HID::GAMEPAD_RIGHT );

	if( fabs( rightStick.x ) > 0.1f )
	{
		Mix::Quaternion rot;

		rot.SetRotationAxis( PlayableCamera::UP, MIX_TO_RAD( rightStick.x ) );
		m_AtToEye = Mix::Matrix4x4( rot ) * m_AtToEye;
	}

	if( fabs( rightStick.y ) > 0.1f )
	{
		m_Height += rightStick.y * -0.1f;
		m_Height = MIX_CLAMP( m_Height, 0.0f, 0.5f );
	}

	if(	m_pGamepad->GetButtonState( Mix::HID::GAMEPAD_POV_RIGHT ) & Mix::HID::DOWN )
	{
		m_Distance = max( PlayableCamera::MIN_DISTANCE, m_Distance - dt * PlayableCamera::STEP_DISTANCE );
	}
	else if( m_pGamepad->GetButtonState( Mix::HID::GAMEPAD_POV_LEFT ) & Mix::HID::DOWN )
	{
		m_Distance = min( PlayableCamera::MAX_DISTANCE, m_Distance + dt * PlayableCamera::STEP_DISTANCE );
	}
}

Boolean PlayableCamera::OnRefresh( void )
{
	Mix::Vector3 at;
	Mix::Vector3 eye;

	at = ComputeAt();
	eye = ComputeEye();

	m_pCamera->SetView( at + eye, at, PlayableCamera::UP );
	m_pCamera->Update();

	return True;
}

Mix::Vector3 PlayableCamera::ComputeAt( void ) const
{
	return ( m_pNode != NULL )? m_pNode->GetWorldMatrix().GetRow( 3 ) : Mix::Vector3::Zero();
}

Mix::Vector3 PlayableCamera::ComputeEye( void ) const
{
	Mix::Vector3 eye;

	eye = m_AtToEye;
	eye.y = m_Height;
	eye = eye.ToNormalize() * m_Distance;

	return eye;
}

}
