#include "utility/script.h"
#include "gameplay/gameactor.h"
#include "gameplay/physics.h"
#include "gameplay/ai.h"
#include "gameplay/weapons/weapon.h"
#include "utility/raycaster.h"

void Script::registerGlobalUserTypes()
{
	auto vec3_mult_overloads = sol::overload(
		[](const glm::vec3& v1, const glm::vec3& v2)	-> glm::vec3 {return v1 * v2; },
		[](const glm::vec3& v1, const float f)			-> glm::vec3 { return v1 * f; },
		[](const float f, const glm::vec3& v1)			-> glm::vec3 { return f * v1; }
	);

	auto vec2_mult_overloads = sol::overload(
		[](const glm::vec2& v1, const glm::vec2& v2)	-> glm::vec2 { return v1 * v2; },
		[](const glm::vec2& v1, const float f)			-> glm::vec2 { return v1 * f; },
		[](const float f, const glm::vec3& v1)			-> glm::vec2 { return f * v1; }
	);

	auto vec3_div_overloads = sol::overload(
		[](const glm::vec3& v1, const glm::vec3& v2)	-> glm::vec3 {return v1 / v2; },
		[](const glm::vec3& v1, const float f)			-> glm::vec3 { return v1 / f; },
		[](const float f, const glm::vec3& v1)			-> glm::vec3 { return f / v1; }
	);

	auto vec2_div_overloads = sol::overload(
		[](const glm::vec2& v1, const glm::vec2& v2)	-> glm::vec2 { return v1 / v2; },
		[](const glm::vec2& v1, const float f)			-> glm::vec2 { return v1 / f; },
		[](const float f, const glm::vec3& v1)			-> glm::vec2 { return f / v1; }
	);

	// These usertypes are constant for every script
	lua.new_usertype<glm::vec3>("vec3",
		sol::constructors<glm::vec3(), glm::vec3(float, float, float)>(),
		sol::meta_function::multiplication, vec3_mult_overloads,
		sol::meta_function::division, vec3_div_overloads,
		"x", &glm::vec3::x,
		"y", &glm::vec3::y,
		"z", &glm::vec3::z);
	lua.new_usertype<glm::vec2>("vec2",
		sol::constructors<glm::vec2(), glm::vec2(float, float)>(),
		sol::meta_function::multiplication, vec2_mult_overloads,
		sol::meta_function::division, vec2_div_overloads,
		"x", &glm::vec2::x,
		"y", &glm::vec2::y);
	lua.new_usertype<GameActor>("GameActor",
		// properties
		"position", sol::property(&GameActor::getCenter, &GameActor::setPosition),
		"rotation", sol::property(&GameActor::getRotation, &GameActor::setRotation),
		"physics",  sol::property(&GameActor::getPhysicsComponent),
		// methods
		"moveUp", &GameActor::moveUp,
		"moveDown", &GameActor::moveDown,
		"moveLeft", &GameActor::moveLeft,
		"moveRight", &GameActor::moveRight,
		"moveForward", &GameActor::moveForward,
		"shoot", &GameActor::fireWeapon);
	lua.new_usertype<PhysicsComponent>("PhysicsComponent",
		"rigidBody", &PhysicsComponent::rigidBody);
	lua.new_usertype<PhysicsComponent::RigidBody>("RigidBody",
		"position", &PhysicsComponent::RigidBody::position,
		"mass", &PhysicsComponent::RigidBody::mass,
		"veloctiy", &PhysicsComponent::RigidBody::velocity,
		"applyForce", &PhysicsComponent::RigidBody::applyForce);
}

void AIScript::registerUserTypes() 
{
	lua["AIState"] = lua.create_table_with(
		"Idle", AIState::Idle,
		"Patrol", AIState::Patrol,
		"Alert", AIState::Alert
	);
	lua.new_usertype<AI>("AI",
		"state", sol::property(&AI::getState, &AI::setState));
	lua.new_usertype<Raycaster>("Raycaster",
		"performRaycast", &Raycaster::performRaycast,
		"bresenhamRaycast", &Raycaster::bresenhamRaycast,
		"distFromWall", &Raycaster::getDistanceFromWall,
		"tileSize", &Raycaster::getTileSize);
}

void WeaponScript::registerUserTypes()
{
	lua.new_usertype<Weapon>("Weapon",
		"weaponSize", sol::property(&Weapon::getWeaponSize),
		"weaponOffset", sol::property(&Weapon::getWeaponOffset),
		"bulletSize", sol::property(&Weapon::getBulletSize),
		"bulletSpeed", sol::property(&Weapon::getBulletSpeed),
		"wielder", sol::property(&Weapon::getWielder),
		"genBulletData", &Weapon::genBulletData,
		"createBullet", &Weapon::createBullet);
	lua.new_usertype<Weapon::BulletData>("BulletData",
		"origin", &Weapon::BulletData::origin,
		"direction", &Weapon::BulletData::direction,
		"mass", &Weapon::BulletData::mass,
		"sizeMod", &Weapon::BulletData::sizeMod,
		"speedMod", &Weapon::BulletData::speedMod,
		"dropMod", &Weapon::BulletData::dropMod);
}

Script::Script(const std::string& path)
{
	lua.open_libraries(sol::lib::base, sol::lib::math);
	registerGlobalUserTypes();
	loadScript(path);

}

Script::~Script()
{
	lua.collect_garbage();
}

AIScript::~AIScript()
{
	lua.collect_garbage();
	lua = sol::state{};
}

WeaponScript::~WeaponScript()
{
	lua.collect_gc();
	lua = sol::state{};
}
