#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>

/*
#include "player.hpp"
*/

#include <iostream>

struct player {
public:
	int bullets;
	int speed;

	player() : player(3, 100) {
	}

	player(int ammo) : player(ammo, 100) {
	}

	player(int ammo, int hitpoints)
	: bullets(ammo), hp(hitpoints) {
	}

	void boost() {
		speed += 10;
	}

	bool shoot() {
		if (bullets < 1)
			return false;
		--bullets;
		return true;
	}

	void set_hp(int value) {
		hp = value;
	}

	int get_hp() const {
		return hp;
	}

private:
	int hp;
};

int main() {
	std::cout << "=== usertype_advanced ===" << std::endl;
	sol::state lua;

	lua.open_libraries(sol::lib::base);

	// note that you can set a
	// userdata before you register a usertype,
	// and it will still carry
	// the right metatable if you register it later

	// set a variable "p2" of type "player" with 0 ammo
	lua["p2"] = player(0);

	// make usertype metatable
	sol::usertype<player> player_type
	     = lua.new_usertype<player>("player",
	          // 3 constructors
	          sol::constructors<player(),
	               player(int),
	               player(int, int)>());

	// typical member function that returns a variable
	player_type["shoot"] = &player::shoot;
	// typical member function
	player_type["boost"] = &player::boost;

	// gets or set the value using member variable syntax
	player_type["hp"]
	     = sol::property(&player::get_hp, &player::set_hp);

	// read and write variable
	player_type["speed"] = &player::speed;
	// can only read from, not write to
	// .set(foo, bar) is the same as [foo] = bar;
	player_type.set("bullets", sol::readonly(&player::bullets));

	// You can also add members to the code, defined in Lua!
	// This lets you have a high degree of flexibility in the
	// code
	std::string prelude_script = R"(
function player:brake ()
	self.speed = 0
	print("we hit the brakes!")
end
)";

	std::string player_script = R"(
-- call single argument integer constructor
p1 = player.new(2)

-- p2 is still here from being 
-- set with lua["p2"] = player(0); below
local p2shoots = p2:shoot()
assert(not p2shoots)
-- had 0 ammo
	
-- set variable property setter
p1.hp = 545
-- get variable through property unqualified_getter
print(p1.hp)
assert(p1.hp == 545)

local did_shoot_1 = p1:shoot()
print(did_shoot_1)
print(p1.bullets)
local did_shoot_2 = p1:shoot()
print(did_shoot_2)
print(p1.bullets)
local did_shoot_3 = p1:shoot()
print(did_shoot_3)
	
-- can read
print(p1.bullets)
-- would error: is a readonly variable, cannot write
-- p1.bullets = 20

p1:boost()
-- call the function we define at runtime from a Lua script
p1:brake()
)";

	// Uncomment and use the file to try that out, too!
	// Make sure it's in the local directory of the executable
	// after you build, or adjust the filename path Or whatever
	// else you like!
	//
	/*
	lua.script_file("prelude_script.lua");
	lua.script_file("player_script.lua");
	*/
	lua.script(prelude_script);
	lua.script(player_script);

	std::cout << std::endl;

	return 0;
}
