#include "actor_base.h"
#include "inputmanager.h"

#include <SDL3/SDL.h>

REGISTER_ENTITY(ActorBase);

ActorBase::ActorBase() :
	m_noclip(false)
{
}

ActorBase::~ActorBase()
{
}

void ActorBase::Update(float dt)
{
	UpdateBodyDirty();
	
	Entity::Update(dt);
	
	AfterEngineStep();
}

void ActorBase::AfterEngineStep()
{
	//btTransform xform = m_rigidBody->getWorldTransform();
	//m_position = btVectorToGlm(xform.getOrigin());

	m_position = btVectorToGlm(m_ph_motion_state.m_transform.getOrigin());

	glm::vec3 cameraPos = m_position;

	if (m_luaObject.IsTable())
	{
		LuaPlus::LuaObject m_camera_offset_y = m_luaObject.GetByName("m_camera_offset_y");
		if (m_camera_offset_y.IsNumber())
			cameraPos.y += (float)m_camera_offset_y.ToNumber();
	}

	m_camera.SetPosition(cameraPos);
}

void ActorBase::UpdateCameraMovement(float dt)
{
	// calculate player movement
	float speed = 12.0f * dt;

	uint32_t movementDir = GenMovementDir();

	if (movementDir & EMovementDir_Forward)
		m_position += speed * m_camera.GetFront();
	if (movementDir & EMovementDir_Backward)
		m_position -= speed * m_camera.GetFront();
	if (movementDir & EMovementDir_Left)
		m_position -= glm::normalize(glm::cross(m_camera.GetFront(), m_camera.GetUp())) * speed;
	if (movementDir & EMovementDir_Right)
		m_position += glm::normalize(glm::cross(m_camera.GetFront(), m_camera.GetUp())) * speed;

	// set position back to camera for calculation view matrix
	m_camera.SetPosition(m_position);
}

void ActorBase::UpdateBodyMovement(float dt)
{

	glm::vec3 dir = glm::vec3(0.0f);

	glm::vec3 camFront = m_camera.GetFront();
	camFront.y = 0.0f;
	camFront = glm::normalize(camFront);

	// calculate player movement
	float speed = 4.f;

	uint32_t movementDir = GenMovementDir();

	if (movementDir & EMovementDir_Forward)
		dir += camFront;
	if (movementDir & EMovementDir_Backward)
		dir -= camFront;
	if (movementDir & EMovementDir_Left)
		dir -= glm::normalize(glm::cross(camFront, m_camera.GetUp()));
	if (movementDir & EMovementDir_Right)
		dir += glm::normalize(glm::cross(camFront, m_camera.GetUp()));

	btVector3 currentvel = m_rigidBody->getLinearVelocity();
	glm::vec3 velocity = dir * speed;

	if (OnGround()) {
		m_rigidBody->setLinearVelocity(btVector3(velocity.x, currentvel.y(), velocity.z));
		if (movementDir & EMovementDir_Jump) {
			m_rigidBody->setLinearVelocity(btVector3(currentvel.x(), PLAYER_PHYS_JUMPSPEEDY, currentvel.z()));
		}
	}
	else {
		float airControl = 0.2f;
		btVector3 airvel = currentvel + glmVectorToBt(dir * speed * airControl * dt);
		m_rigidBody->setLinearVelocity(btVector3(airvel.x(), currentvel.y(), airvel.z()));
	}

	if (m_noclip && movementDir == EMouseButton_None) {
		m_rigidBody->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
	}

	/*if (glm::length(velocity) > 0.1f && OnGround() && !(movementDir & EMovementDir_Jump))
		m_rigidBody->setLinearVelocity(glmVectorToBt(velocity));

	if ((movementDir & EMovementDir_Jump) && OnGround()) {
		m_rigidBody->applyCentralImpulse(btVector3(0.0f, PLAYER_PHYS_JUMPSPEEDY, 0.0f));
		movementDir &= ~EMovementDir_Jump;
	}*/
}

void ActorBase::UpdateCameraLook()
{
	if (!g_inputManager.GetRelativeMouseMode())
		return;

	glm::ivec2 mousePos = g_inputManager.GetMousePos();

	// calculate yaw and pitch
	static float yaw = 0.0f, pitch = 0.0f;

	int deltaX = mousePos.x;
	int deltaY = mousePos.y;

	float sensitivity = 0.15f;

	yaw += deltaX * sensitivity;
	pitch -= deltaY * sensitivity;

	if (pitch > 89.0f) pitch = 89.0f;
	if (pitch < -89.0f) pitch = -89.0f;

	m_camera.SetYawPitch(yaw, pitch);
}

void ActorBase::ActivateCamera()
{
	g_inputManager.SetRelativeMouseMode(true);

	g_cameraManager.SetActiveCamera(&m_camera);
}

void ActorBase::CreatePlayerBody(float radius, float height, float mass, float friction, float damping)
{

	m_shape = new btCapsuleShape(radius, height);

	m_mass = mass;
	btVector3 local_inertia(0.0f, 0.0f, 0.0f);
	if (m_mass > 0.f) {
		m_shape->calculateLocalInertia(m_mass, local_inertia);
	}

	btRigidBody::btRigidBodyConstructionInfo rigid_body_ci(m_mass, nullptr, m_shape, local_inertia);

	m_rigidBody = new btRigidBody(rigid_body_ci);
	m_rigidBody->setUserPointer(this);
	m_rigidBody->setMotionState(&m_ph_motion_state);
	m_ph_motion_state.setBody(m_rigidBody);

	// I'm sure that position is valid
	btTransform xform;
	xform.setIdentity();
	xform.setOrigin(glmVectorToBt(m_position));
	m_rigidBody->setWorldTransform(xform);

	// ACTOR STUFF
	m_rigidBody->setAngularFactor(btVector3(0.0f, 0.0f, 0.0f));

	m_rigidBody->setFriction(friction);
	m_rigidBody->setAnisotropicFriction(btVector3(0.0f, 0.0f, 0.0f));
	m_rigidBody->setDamping(damping, 0.0f);

	m_rigidBody->setActivationState(DISABLE_DEACTIVATION);

	// #TODO: body filter and mask
	g_PhysicsWorld->GetWorld()->addRigidBody(m_rigidBody);

	m_bodyDirty = true;

}

void ActorBase::CreatePlayerBodyBox(float x, float y, float z, float mass, float friction, float damping)
{
	m_shape = new btBoxShape(btVector3(x, y, z));

	m_mass = mass;
	btVector3 local_inertia(0.0f, 0.0f, 0.0f);
	if (m_mass > 0.f) {
		m_shape->calculateLocalInertia(m_mass, local_inertia);
	}

	btRigidBody::btRigidBodyConstructionInfo rigid_body_ci(m_mass, nullptr, m_shape, local_inertia);

	m_rigidBody = new btRigidBody(rigid_body_ci);
	m_rigidBody->setUserPointer(this);
	m_rigidBody->setMotionState(&m_ph_motion_state);
	m_ph_motion_state.setBody(m_rigidBody);

	// I'm sure that position is valid
	btTransform xform;
	xform.setIdentity();
	xform.setOrigin(glmVectorToBt(m_position));
	m_rigidBody->setWorldTransform(xform);

	// ACTOR STUFF
	m_rigidBody->setAngularFactor(btVector3(0.0f, 0.0f, 0.0f));

	m_rigidBody->setFriction(friction);
	m_rigidBody->setAnisotropicFriction(btVector3(0.0f, 0.0f, 0.0f));
	m_rigidBody->setDamping(damping, 0.0f);

	m_rigidBody->setActivationState(DISABLE_DEACTIVATION);

	// #TODO: body filter and mask
	g_PhysicsWorld->GetWorld()->addRigidBody(m_rigidBody);

	m_bodyDirty = true;

}

void ActorBase::CreatePlayerBody_Old()
{

	m_shape = new btCapsuleShape(PLAYER_PHYS_RADIUS, PLAYER_PHYS_HEIGHT - PLAYER_PHYS_RADIUS * 2.0);

	m_mass = 80.0f;
	btVector3 local_inertia(0.0f, 0.0f, 0.0f);
	if (m_mass > 0.f) {
		m_shape->calculateLocalInertia(m_mass, local_inertia);
	}

	btRigidBody::btRigidBodyConstructionInfo rigid_body_ci(m_mass, nullptr, m_shape, local_inertia);

	m_rigidBody = new btRigidBody(rigid_body_ci);
	m_rigidBody->setUserPointer(this);
	m_rigidBody->setMotionState(&m_ph_motion_state);
	m_ph_motion_state.setBody(m_rigidBody);

	// I'm sure that position is valid
	btTransform xform;
	xform.setIdentity();
	xform.setOrigin(glmVectorToBt(m_position));
	m_rigidBody->setWorldTransform(xform);

	// ACTOR STUFF
	m_rigidBody->setAngularFactor(btVector3(0.0f, 0.0f, 0.0f));


	//m_rigidBody->setFriction(2.5f);

	m_rigidBody->setFriction(0.0f);
	m_rigidBody->setAnisotropicFriction(btVector3(0.0f, 0.0f, 0.0f));
	m_rigidBody->setDamping(0.0f, 0.0f);

	m_rigidBody->setActivationState(DISABLE_DEACTIVATION);

	// #TODO: body filter and mask
	g_PhysicsWorld->GetWorld()->addRigidBody(m_rigidBody);

	m_bodyDirty = true;
}

bool ActorBase::OnGround()
{
	float rayLength = (PLAYER_PHYS_HEIGHT / 2.0f) + 0.1f;

	btTransform xform = m_rigidBody->getWorldTransform();

	btVector3 rayStart = xform.getOrigin();
	btVector3 rayEnd = rayStart - btVector3(0.0f, rayLength, 0.0f);

	ClosestRayResultCallback RayResultCallback(rayStart, rayEnd, this);
	g_PhysicsWorld->GetWorld()->rayTest(rayStart, rayEnd, RayResultCallback);
	if (RayResultCallback.hasHit()) {
		btVector3 hitNormal = RayResultCallback.m_hitNormalWorld;
		if (hitNormal.y() > 0.7f) {
			return true;
		}
	}

	return false;
}

void ActorBase::SetNoclip(bool noclip)
{
	static int mask1 = -1, mask2 = -1;

	m_noclip = noclip;

	if (noclip)
	{
		if (mask1 == -1)
		{
			mask1 = m_rigidBody->getBroadphaseHandle()->m_collisionFilterGroup;
			mask2 = m_rigidBody->getBroadphaseHandle()->m_collisionFilterMask;
		}

		m_rigidBody->getBroadphaseHandle()->m_collisionFilterGroup = 0;
		m_rigidBody->getBroadphaseHandle()->m_collisionFilterMask = 0;
			
		g_PhysicsWorld->GetWorld()->getPairCache()->cleanProxyFromPairs(m_rigidBody->getBroadphaseHandle(), g_PhysicsWorld->GetWorld()->getDispatcher());

		m_rigidBody->setCollisionFlags(m_rigidBody->getCollisionFlags() |/* btCollisionObject::CF_KINEMATIC_OBJECT |*/ btCollisionObject::CF_NO_CONTACT_RESPONSE);
		m_rigidBody->setGravity(btVector3(0.0f, 0.f, 0.f));
	}
	else
	{

		m_rigidBody->getBroadphaseHandle()->m_collisionFilterGroup = mask1;
		m_rigidBody->getBroadphaseHandle()->m_collisionFilterMask = mask2;

	//	m_rigidBody->setCollisionFlags(m_rigidBody->getCollisionFlags() & ~btCollisionObject::CF_KINEMATIC_OBJECT);
		m_rigidBody->setCollisionFlags(m_rigidBody->getCollisionFlags() & ~btCollisionObject::CF_NO_CONTACT_RESPONSE);
		m_rigidBody->setGravity(g_PhysicsWorld->GetWorld()->getGravity());
	}
}

void ActorBase::RegisterFunctions()
{
	m_luaObject.Register("activate_camera", *this, &ActorBase::Lua_ActivateCamera);
	m_luaObject.Register("update_camera_look", *this, &ActorBase::Lua_UpdateCameraLook);
	m_luaObject.Register("update_camera_movement", *this, &ActorBase::Lua_UpdateCameraMovement);
	m_luaObject.Register("create_player_body", *this, &ActorBase::Lua_CreatePlayerBody);
	m_luaObject.Register("create_player_body_old", *this, &ActorBase::Lua_CreatePlayerBodyOld);
	m_luaObject.Register("create_player_body_box", *this, &ActorBase::Lua_CreatePlayerBodyBox);
	m_luaObject.Register("update_body_movement", *this, &ActorBase::Lua_UpdateBodyMovement);
	m_luaObject.Register("get_action", *this, &ActorBase::Lua_GetAction);
	m_luaObject.Register("get_movement", *this, &ActorBase::Lua_GetMovement);
	m_luaObject.Register("on_ground", *this, &ActorBase::Lua_OnGround);
	m_luaObject.Register("is_noclip", *this, &ActorBase::Lua_IsNoclip);

	//m_luaObject.RegisterDirect("activate_camera", &ActorBase_ActivateCamera);
	//m_luaObject.RegisterDirect("update_camera_look", &ActorBase_UpdateCameraLook);
	//m_luaObject.RegisterDirect("update_camera_movement", &ActorBase_UpdateCameraMovement);
}

int ActorBase::Lua_UpdateCameraMovement(LuaPlus::LuaState* state)
{
	LuaPlus::LuaStack stack(state);

	UpdateCameraMovement(stack[2].GetFloat());

	return 0;
}

int ActorBase::Lua_UpdateBodyMovement(LuaPlus::LuaState* state)
{
	LuaPlus::LuaStack stack(state);

	UpdateBodyMovement(stack[2].GetFloat());

	return 0;
}

int ActorBase::Lua_UpdateCameraLook(LuaPlus::LuaState* state)
{
	UpdateCameraLook();
	return 0;
}

int ActorBase::Lua_ActivateCamera(LuaPlus::LuaState* state)
{
	ActivateCamera();
	return 0;
}

int ActorBase::Lua_CreatePlayerBody(LuaPlus::LuaState* state)
{
	LuaPlus::LuaStack stack(state);

	float radius = (float)stack[2].GetNumber();
	float height = (float)stack[3].GetNumber();
	float mass = (float)stack[4].GetNumber();
	float friction = (float)stack[5].GetNumber();
	float damping = (float)stack[6].GetNumber();

	CreatePlayerBody(radius, height, mass, friction, damping);

	return 0;
}

int ActorBase::Lua_CreatePlayerBodyOld(LuaPlus::LuaState* state)
{
	CreatePlayerBody_Old();
	return 0;
}

int ActorBase::Lua_CreatePlayerBodyBox(LuaPlus::LuaState* state)
{
	LuaPlus::LuaStack stack(state);

	float x = (float)stack[2].GetNumber();
	float y = (float)stack[3].GetNumber();
	float z = (float)stack[4].GetNumber();
	float mass = (float)stack[5].GetNumber();
	float friction = (float)stack[6].GetNumber();
	float damping = (float)stack[7].GetNumber();

	CreatePlayerBodyBox(x, y, z, mass, friction, damping);
	return 0;
}

int ActorBase::Lua_GetAction(LuaPlus::LuaState* state)
{
	int action = GenAction();
	state->PushInteger(action);

	return 1;
}

int ActorBase::Lua_GetMovement(LuaPlus::LuaState* state)
{
	uint32_t movement = GenMovementDir();
	state->PushInteger(movement);

	return 1;
}

int ActorBase::Lua_OnGround(LuaPlus::LuaState* state)
{
	state->PushBoolean(OnGround());
	return 1;
}

int ActorBase::Lua_IsNoclip(LuaPlus::LuaState* state)
{
	state->PushBoolean(m_noclip);
	return 1;
}

int ActorBase::GenAction()
{
	int action = -1;

	if (!g_inputManager.GetRelativeMouseMode())
		return action;

	if (g_inputManager.GetMouse().IsKeyDown(EMouseButton_Left))
		action = 0;
	else if (g_inputManager.GetMouse().IsKeyDown(EMouseButton_Right))
		action = 1;
	else if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_R))
		action = 2;
	else if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_E))
		action = 3;

	return action;
}

uint32_t ActorBase::GenMovementDir()
{
	uint32_t movementDir = EMovementDir_None;

	if (!g_inputManager.GetRelativeMouseMode())
		return movementDir;

	if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_W)) {
		movementDir |= EMovementDir_Forward;
	}

	if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_S)) {
		movementDir |= EMovementDir_Backward;
	}

	if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_A)) {
		movementDir |= EMovementDir_Left;
	}

	if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_D)) {
		movementDir |= EMovementDir_Right;
	}

	if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_SPACE)) {
		movementDir |= EMovementDir_Jump;
	}

	return movementDir;
}
