#include "inputmanager.h"
#include "debugrender.h"
#include "game_object.h"
#include <SDL3/SDL.h>

#define PLAYER_PHYS_MASS           80.0
#define PLAYER_PHYS_RADIUS         0.40
#define PLAYER_PHYS_HEIGHT         1.79
#define PLAYER_PHYS_JUMPDIST       PLAYER_PHYS_RADIUS
#define PLAYER_PHYS_JUMPHEIGHT     2.0
#define PLAYER_PHYS_JUMPSPEEDY     5.0
#define PLAYER_PHYS_WALK_SPEED     ( 5.5 )
#define PLAYER_PHYS_RUN_SPEED_MUL  1.4
#define PLAYER_PHYS_MOVE_SPEED_EXP 1.0
#define PLAYER_PHYS_FLY_SPEED_EXP  4.0

// Lua wrappers
void Entity_LoadModel(LuaPlus::LuaState* state)
{
	LuaPlus::LuaStack stack(state);

	Entity* entity = (Entity*)stack[1].GetByName("__object").GetLightUserdata();
	entity->LoadModel(stack[2].GetString());
}

void Entity_SetVisible(LuaPlus::LuaState* state)
{
	LuaPlus::LuaStack stack(state);

	Entity* entity = (Entity*)stack[1].GetByName("__object").GetLightUserdata();
	entity->SetVisible(stack[2].GetBoolean());
}

bool Entity_GetVisible(LuaPlus::LuaState* state)
{
	LuaPlus::LuaStack stack(state);

	Entity* entity = (Entity*)stack[1].GetByName("__object").GetLightUserdata();
	return entity->GetVisible();
}

void ActorBase_UpdateCameraMovement(LuaPlus::LuaState* state)
{
	LuaPlus::LuaStack stack(state);

	ActorBase* entity = (ActorBase*)stack[1].GetByName("__object").GetLightUserdata();
	entity->UpdateCameraMovement(stack[2].GetFloat());
}

void ActorBase_UpdateCameraLook(LuaPlus::LuaState* state)
{
	LuaPlus::LuaStack stack(state);

	ActorBase* entity = (ActorBase*)stack[1].GetByName("__object").GetLightUserdata();
	entity->UpdateCameraLook();
}

void ActorBase_ActivateCamera(LuaPlus::LuaState* state)
{
	LuaPlus::LuaStack stack(state);

	ActorBase* entity = (ActorBase*)stack[1].GetByName("__object").GetLightUserdata();
	entity->ActivateCamera();
}

REGISTER_ENTITY(Entity);

Entity::Entity() : 
	m_model(nullptr),
	m_shape(nullptr),
	m_rigidBody(nullptr),
	m_bodyDirty(false)
{
}

Entity::~Entity()
{
	if (m_onShutdownFunction.IsFunction())
	{
		LuaPlus::LuaFunctionVoid function = m_onShutdownFunction;
		function(m_luaObject);
	}

	if (m_luaObject.IsTable())
	{
		LuaPlus::LuaObject name = m_luaObject["m_name"];
		if (name.IsString())
		{
			LuaPlus::LuaObject factory = GetLuaState().GetGlobal("g_factory");
			factory.SetNil(name.GetString());
		}

		LuaPlus::LuaObject lookup = GetLuaState().GetGlobal("g_entities");
		lookup.SetNil(GetID());

		m_luaObject.AssignNil();
	}
}

void Entity::Update(float dt)
{
	UpdateBody();

	if ( m_onUpdateFunction.IsFunction())
	{
		LuaPlus::LuaFunctionVoid function = m_onUpdateFunction;
		function(m_luaObject, static_cast<lua_Number>(dt));
	}
}

void Entity::Render()
{
	if (m_model)
	{
		m_model->Draw(GetWorldTransform());
	
		BoundingBox bbox = m_model->GetBoundingBox();
		bbox.TransformAABB(GetWorldTransform());

		g_debugRender->DrawBoundingBox(bbox, glm::vec3(1.0f, 0.0f, 0.0f));
	}
}

void Entity::OnCollide(IEntityBase* other)
{
	if (!m_onCollideFunction.IsFunction())
		return;

	// find entity
	LuaPlus::LuaObject lookup = GetLuaState().GetGlobal("g_entities");
	LuaPlus::LuaObject otherTable = lookup.GetByIndex(other->GetID());
	if (!otherTable.IsNil())
	{
		LuaPlus::LuaFunctionVoid function = m_onCollideFunction;
		function(m_luaObject, otherTable);
	}
}

const std::string& Entity::GetName()
{
	return m_name;
}

void Entity::SetName(const std::string& name)
{
	m_name = name;
}

void Entity::LoadModel(const char* filename)
{
	m_model = g_modelSystem->LoadModel(filename);
}

void Entity::SetVisible(bool visible)
{
}

bool Entity::GetVisible()
{
	return true;
}

void Entity::InitFromTable(LuaPlus::LuaObject& _object)
{
	SDL_assert_always(!_object.IsNil() && "Invalid object passed");

	// catch table instance
	m_luaObject = _object;

	// find functions
	m_onInitFunction = m_luaObject.GetByName("on_init");
	m_onShutdownFunction = m_luaObject.GetByName("on_shutdown");
	m_onUpdateFunction = m_luaObject.GetByName("on_update");
	m_onCollideFunction = m_luaObject.GetByName("on_collide");

	// check
	//SDL_assert_always(m_onInitFunction.IsFunction() || m_onShutdownFunction.IsFunction() || m_onUpdateFunction.IsFunction());

	// register base entity functions
	RegisterBaseFunctions();

	// register entity functions
	RegisterFunctions();

	// assign C++ object
	m_luaObject.SetLightUserdata("__object", this);

	// call init
	if (m_onInitFunction.IsFunction())
	{
		LuaPlus::LuaFunctionVoid onInitFunction = m_onInitFunction;
		onInitFunction(m_luaObject);
	}
}

void Entity::RegisterBaseFunctions()
{
	m_luaObject.Register("load_model", *this, &Entity::Lua_LoadModel);
	m_luaObject.Register("translate", *this, &Entity::Lua_Translate);
	m_luaObject.Register("set_position", *this, &Entity::Lua_SetPosition);
	m_luaObject.Register("get_classname", *this, &Entity::Lua_GetClassname);

//	m_luaObject.RegisterDirect("load_model", &Entity_LoadModel);
	//m_luaObject.RegisterDirect("set_visible", &Entity_SetVisible);
	//m_luaObject.RegisterDirect("get_visible", &Entity_GetVisible);
}

void Entity::RegisterFunctions()
{
}

void Entity::Help_Translate(float x, float y, float z)
{
	m_position.x += x;
	m_position.y += y;
	m_position.z += z;
}

void Entity::Help_SetPosition(float x, float y, float z)
{
	m_position.x = x;
	m_position.y = y;
	m_position.z = z;
}

int Entity::Lua_LoadModel(LuaPlus::LuaState* state)
{
	LuaPlus::LuaStack stack(state);
	
	if (stack.Count() > 1)
	{
		LoadModel(stack[2].GetString());
	}
	else
	{
		LoadModel(stack[1].GetString());
	}
	
	return 0;
}

int Entity::Lua_Translate(LuaPlus::LuaState* state)
{
	LuaPlus::LuaStack stack(state);

	float x = stack[2].GetNumber();
	float y = stack[3].GetNumber();
	float z = stack[4].GetNumber();

	Help_Translate(x, y, z);

	return 0;
}

int Entity::Lua_SetPosition(LuaPlus::LuaState* state)
{
	LuaPlus::LuaStack stack(state);

	float x = stack[2].GetNumber();
	float y = stack[3].GetNumber();
	float z = stack[4].GetNumber();

	Help_SetPosition(x, y, z);

	return 0;
}

int Entity::Lua_GetClassname(LuaPlus::LuaState* state)
{
	state->PushString(GetClassname());
	return 1;
}

const LuaPlus::LuaObject& Entity::GetLuaObject()
{
	return m_luaObject;
}

void Entity::CreateTestBody()
{
	m_shape = new btBoxShape(btVector3(0.2f, 0.2f, 0.2f));

	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);

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

	m_bodyDirty = true;
}

void Entity::UpdateBody()
{
	m_position = btVectorToGlm(m_ph_motion_state.m_transform.getOrigin());
	m_ph_motion_state.m_transform.getRotation().getEulerZYX(m_rotation.z, m_rotation.y, m_rotation.x);
	m_rotation = glm::degrees(m_rotation);
}

void Entity::UpdateBodyDirty()
{
	if (m_bodyDirty)
	{
		btTransform xform;
		xform.setIdentity();
		xform.setOrigin(glmVectorToBt(m_position));
		m_rigidBody->setWorldTransform(xform);

		m_bodyDirty = false;
	}
}

REGISTER_ENTITY(ActorBase);

ActorBase::ActorBase()
{
}

ActorBase::~ActorBase()
{
	if (m_rigidBody) {
		g_PhysicsWorld->GetWorld()->removeRigidBody(m_rigidBody);

		delete m_rigidBody;
		m_rigidBody = nullptr;

		m_ph_motion_state.setBody(nullptr);
	}

	if (m_shape) {
		delete m_shape;
		m_shape = nullptr;
	}
}

void ActorBase::Update(float dt)
{
	static bool s_test = true;

	if (s_test) {
		UpdateBodyDirty();
		Entity::Update(dt);
		AfterEngineStep();
	} else {
		ActivateCamera();

		UpdateCameraLook();

		UpdateCameraMovement(dt);

	}

		return;
}

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

	m_position = btVectorToGlm(m_ph_motion_state.m_transform.getOrigin());
	m_camera.SetPosition(m_position);
}

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 (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()
{
	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()
{

	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.1f, 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::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_body", *this, &ActorBase::Lua_CreateBody);
	m_luaObject.Register("update_body_movement", *this, &ActorBase::Lua_UpdateBodyMovement);

	//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_CreateBody(LuaPlus::LuaState* state)
{
	CreatePlayerBody();
	return 0;
}

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

	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;
}

REGISTER_ENTITY(WeaponBase);

WeaponBase::WeaponBase()
{
}

WeaponBase::~WeaponBase()
{
}

void WeaponBase::Update(float dt)
{
}

void WeaponBase::Fire(const glm::vec3& direction, float damage)
{
}
