/*
   game/game.cc
   This file is part of the Osirion project and is distributed under
   the terms of the GNU General Public License version 2
*/

#include <vector>

#include "auxiliary/functions.h"
#include "core/gameserver.h"
#include "filesystem/filesystem.h"
#include "filesystem/inifile.h"
#include "game/game.h"
#include "game/planet.h"
#include "game/ship.h"
#include "game/star.h"
#include "math/mathlib.h"
#include "sys/sys.h"

namespace game
{



ShipModel		*default_shipmodel = 0;
core::Zone		*default_zone = 0;

/*----- engine game functions ------------------------------------- */

/// list the ship model registry
void func_list_ship(std::string const &args)
{
	ShipModel::list();
}

/// a player joins the game
void func_join(core::Player *player, std::string const &args)
{
	if (player->control())
		return;

	player->set_zone(default_zone);
	Ship *ship = new Ship(player, default_shipmodel);
	ship->set_zone(default_zone);
	player->set_control(ship);

	core::server()->send_sound(player, "game/buy-ship");
	
	std::string message("^B");
	message.append(player->name());
	message.append("^B joins the game.");
	core::server()->broadcast(message);

	player->player_dirty = true;
}

/// a player joins the spectators
void func_spectate(core::Player *player, std::string const &args)
{
	if (!player->control())
		return;

	std::string message("^B");
	message.append(player->name());
	message.append("^B spectates.");
	core::server()->broadcast(message);

	if (player->control()) {
		player->remove_asset(player->control());
	}
}

/// a player buys a ship
void func_buy(core::Player *player, std::string const &args)
{
	
	std::string shipname;
	std::string helpstr;
	std::istringstream is(args);
	is >> shipname;
	aux::to_lowercase(shipname);

	ShipModel *shipmodel = 0;
	for (ShipModel::iterator smit = ShipModel::registry.begin(); smit != ShipModel::registry.end(); smit++) {
		if (shipname == (*smit).first) {
			shipmodel = (*smit).second;
			break;
		}

		if (helpstr.size())
			helpstr.append("^N|^B");
		helpstr.append((*smit).second->label());
	}

	if (shipmodel) {
		// player has only ship for now
		if (player->control()) {
			player->remove_asset(player->control());
		}

		Ship * ship = new Ship(player, shipmodel);
		if (player->zone()) {
			ship->set_zone(player->zone());
		} else {
			ship->set_zone(default_zone);
		}
		player->set_control(ship);
		
		core::server()->broadcast("^B" + player->name() + " ^Bpurchased " + aux::article(shipmodel->name()));
		core::server()->send_sound(player, "game/buy-ship");

	} else {
		core::server()->send(player, "Usage: buy [^B" + helpstr + "^N]");
	}
}

/// a player sends standard hails
void func_hail(core::Player *player, std::string const &args)
{
	std::string  target;
	std::istringstream is(args);
	if (!(is >> target)) {
		core::server()->send(player, "Usage: hail [player]");
		return;
	}

	core::Player *targetplayer = core::server()->find_player(target);
	if (!targetplayer) {
		core::server()->send(player, "^BPlayer " + target + "^B not found.");
		return;
	}

	core::server()->send(player, "^BYou hail " + targetplayer->name() + "^B.");
	core::server()->send_sound(player, "com/hail");

	core::server()->send(targetplayer, "^B" + player->name() + "^B hails you!");
	core::server()->send_sound(targetplayer, "com/hail");
}

/// a player actives the hyperspace jump drive on his ship
void func_jump(core::Player *player, std::string const &args)
{
	if (!player->control())
		return;
	if (!player->control()->moduletype() == ship_enttype)
		return;
	Ship * ship = static_cast<Ship *>(player->control());
	ship->jump(args);
}

/// a player actives the kinetic impulse drive on his ship
void func_impulse(core::Player *player, std::string const &args)
{
	if (!player->control())
		return;
	if (!player->control()->moduletype() == ship_enttype)
		return;
	Ship * ship = static_cast<Ship *>(player->control());
	ship->impulse();
}

/* ---- The Game class --------------------------------------------- */

Game 			*Game::game_instance = 0;

Game::Game() : core::Module("Project::OSiRiON")
{
	game_instance = this;
	g_impulsespeed = 0;
}

Game::~Game()
{
	game_instance = 0;
}

void Game::init()
{
	module_running = false;

	ShipModel::clear();

	if (!load_world()) {
		abort();
		return;
	}
		
	if (!load_ships()) {
		abort();
		return;
	}

	// add engine game functions
	core::Func *func = 0;
	
	func = core::Func::add("join", (core::GameFuncPtr) func_join);
	func->set_info("join the game");
	func = core::Func::add("hail", (core::GameFuncPtr) func_hail);
	func->set_info("send a standard hail");
	func = core::Func::add("spectate", (core::GameFuncPtr) func_spectate);
	func->set_info("leave the game and spectate");

	func = core::Func::add("buy", (core::GameFuncPtr) func_buy);
	func->set_info("buy a ship");

	func = core::Func::add("jump", (core::GameFuncPtr) func_jump);
	func->set_info("[string] activate or deactivate hyperspace jump drive");

	func = core::Func::add("impulse", (core::GameFuncPtr) func_impulse);
	func->set_info("activate are deactive kinetic impulse drive");

	// add engine core functions
	func = core::Func::add("list_ship", (core::FuncPtr) func_list_ship);
	func->set_info("list ship statistics");

	g_impulsespeed = core::Cvar::get("g_impulsespeed", "15", core::Cvar::Game | core::Cvar::Archive);
	g_impulsespeed->set_info("[float] standard speed of the impulse drive");

	g_impulseacceleration = core::Cvar::get("g_impulseacceleration", "4", core::Cvar::Game | core::Cvar::Archive);
	g_impulseacceleration->set_info("[float] standard acceleration of the impulse drive");

	// indicate the module is ready to run frames
	module_running = true;
}

void Game::shutdown()
{
	g_impulsespeed = 0;
	// game functions are automaticly removed

	// remove engine core functions
	core::Func::remove("list_ship");

	ShipModel::clear();
	module_running = false;
}

bool Game::load_world()
{
	std::string inifilename("world");

	filesystem::IniFile worldini;
	worldini.open(inifilename);

	if (!worldini.is_open()) {
		con_error << "Could not open " << worldini.name() << std::endl;
		return false;
	}

	core::Zone *zone = 0;
	std::string label;

	while (worldini.getline()) {

		if (worldini.got_section()) {

			if (worldini.got_section("world")) {
				continue;
			} else {
				con_warn << worldini.name() << "  unknown section '" << worldini.section() << "' at line " << worldini.line() << std::endl;
			}

		} else if (worldini.section().compare("world") == 0 ) {
			if (worldini.got_key_string("zone", label)) {
				aux::to_label(label);
				zone = new core::Zone(label);
				core::Zone::add(zone);
			}
		}
	}
	worldini.close();

	if (!core::Zone::registry().size()) {
		con_error << "No zones found!" << std::endl;
		return false;
	}

	con_debug << "  " << worldini.name() << " " << core::Zone::registry().size() << " zones" << std::endl;

	for (core::Zone::Registry::iterator it = core::Zone::registry().begin(); it != core::Zone::registry().end(); it++) {
		if (!load_zone((*it).second)) {
			return false;
		}
	}

	if (!default_zone) {
		con_error << "No default zone found!" << std::endl;
		return false;
	}

	return true;
}

bool Game::load_zone(core::Zone *zone)
{
	using math::Vector3f;
	using math::Color;

	std::string inifilename("zones/");
	inifilename.append(zone->label());

	filesystem::IniFile zoneini;
	zoneini.open(inifilename);

	if (!zoneini.is_open()) {
		con_error << "Could not open " << zoneini.name() << std::endl;
		return false;
	}

	size_t count = 0;

	Planet *planet = 0;
	Star *star = 0;
	core::Entity *entity = 0;

	float direction;
	float pitch;
	float roll;

	bool b;

	std::string strval;

	// set th default sky
	zone->set_sky("sky");

	while (zoneini.getline()) {
		if (zoneini.got_key()) {
			if (zoneini.section().compare("zone") == 0) {
				if (zoneini.got_key_string("name", strval)) {
					zone->set_name(strval);
					continue;
				} else if (zoneini.got_key_string("sky", strval)) {
					zone->set_sky(strval);
					continue;
				} else if (zoneini.got_key_bool("default", b)) {
					if (b) default_zone = zone;
					continue;
				} else {
					con_warn << zoneini.name() << " unknown key '" << zoneini.key() << "' at line " << zoneini.line() << std::endl;
				}
			} else if (zoneini.section().compare("star") == 0) {
				if (zoneini.got_key_string("label", strval)) {
					aux::to_label(strval);
					star->entity_label.assign(strval);
					continue;
				 } else if (zoneini.got_key_string("name", star->entity_name))
					continue;
				else if (zoneini.got_key_vector3f("location", star->entity_location ))
					continue;
				else if (zoneini.got_key_color("color", star->entity_color))
					continue;
				else if (zoneini.got_key_angle("radius", star->entity_radius))
					continue;
				else
					con_warn << zoneini.name() << " unknown key '" << zoneini.key() << "' at line " << zoneini.line() << std::endl;

			} else if (zoneini.section().compare("planet") == 0) {
				if (zoneini.got_key_string("label", strval)) {
					aux::to_label(strval);
					planet->entity_label.assign(strval);
					continue;
				} else if (zoneini.got_key_string("name", planet->entity_name))
					continue;
				else if (zoneini.got_key_string("texture", planet->entity_texture))
					 continue;
				else if (zoneini.got_key_vector3f("location", planet->entity_location ))
					continue;
				else if (zoneini.got_key_color("color", planet->entity_color))
					continue;
				else if (zoneini.got_key_angle("radius", planet->entity_radius))
					continue;
				else
					con_warn << zoneini.name() << " unknown key '" << zoneini.key() << "' at line " << zoneini.line() << std::endl;

			} else if (zoneini.section().compare("entity") == 0) {
				std::string shapename;
				if (zoneini.got_key_string("shape", shapename)) {
					if (shapename.compare("axis") == 0) {
						entity->entity_shape = core::Entity::Axis;
					} else if (shapename.compare("cube") == 0) {
						entity->entity_shape = core::Entity::Cube;
					} else if (shapename.compare("diamond") == 0) {
						entity->entity_shape = core::Entity::Diamond;
					} else if (shapename.compare("sphere") == 0) {
						entity->entity_shape = core::Entity::Sphere;
					} else {
						con_warn << zoneini.name() << " unknown shape '" << shapename << "' at line " << zoneini.line() << std::endl;
					}
					continue;
				} else if (zoneini.got_key_string("label", strval)) {
					aux::to_label(strval);
					entity->entity_label.assign(strval);		
					continue;
				} else if (zoneini.got_key_string("name", entity->entity_name)) {
					continue;
				} else if (zoneini.got_key_string("model", entity->entity_modelname)) {
					continue;
				} else if (zoneini.got_key_angle("direction", direction)) {
					entity->axis().change_direction(direction);
					continue;
				} else if (zoneini.got_key_angle("pitch", pitch)) {
					entity->axis().change_pitch(pitch);
					continue;
				} else if (zoneini.got_key_angle("roll", roll)) {
					entity->axis().change_roll(roll);
					continue;
				} else if (zoneini.got_key_angle("radius", entity->entity_radius)) {
					continue;
				} else if (zoneini.got_key_vector3f("location", entity->entity_location)) {
					continue;
				} else if (zoneini.got_key_color("color", entity->entity_color)) {
					continue;
				} else if (zoneini.got_key_color("colorsecond", entity->entity_color_second)) {
					continue;
				} else {
					con_warn << zoneini.name() << " unknown key '" << zoneini.key() << "' at line " << zoneini.line() << std::endl;
				}
			}
		} else if (zoneini.got_section("zone")) {
			continue;

		} else if (zoneini.got_section("star")) {
			star = new Star();
			star->set_zone(zone);
			count ++;

		} else if (zoneini.got_section("planet")) {
			planet = new Planet();
			planet->set_zone(zone);
			count ++;

		} else if (zoneini.got_section("entity")) {
			entity = new core::Entity();
			entity->entity_flags += core::Entity::Static;
			entity->set_zone(zone);
			count ++;

		} else if (zoneini.got_section()) {
			con_warn << zoneini.name() << " unknown section '" << zoneini.section() << "' at line " << zoneini.line() << std::endl;
		}
	}
	zoneini.close();

	con_debug << "  " << zoneini.name() << " " << zone->content().size() << " entities" << std::endl;

	return true;
}

// read ship model specifications
bool Game::load_ships()
{
	using math::Vector3f;
	using math::Color;

	default_shipmodel = 0;

	filesystem::IniFile shipsini;
	shipsini.open("ships");
	if (!shipsini.is_open()) {
		con_error << "Could not open ini/ships.ini!" << std::endl;
		return false;
	}

	ShipModel *shipmodel = 0;
	std::string label;
	bool b;

	while (shipsini.getline()) {
		if (shipsini.got_key()) {
			if (shipsini.section().compare("ship") == 0) {
				if (shipsini.got_key_string("label", label)) {
					aux::to_label(label);
					shipmodel->shipmodel_label.assign(label);
					ShipModel::add(shipmodel);
					continue;
				} else if (shipsini.got_key_string("name",shipmodel->shipmodel_name)) {
					 continue;
				} else if (shipsini.got_key_string("model", shipmodel->shipmodel_modelname)) {
					continue;
				} else if (shipsini.got_key_bool("default", b)) {
					if (b) default_shipmodel = shipmodel;
					continue;
				} else if (shipsini.got_key_bool("jumpdrive", shipmodel->shipmodel_jumpdrive)) {
					continue;
				} else if (shipsini.got_key_float("acceleration", shipmodel->shipmodel_acceleration)) {
					continue;
				} else if (shipsini.got_key_float("maxspeed", shipmodel->shipmodel_maxspeed)) {
					continue;
				} else if (shipsini.got_key_float("turnspeed", shipmodel->shipmodel_turnspeed)) {
					math::clamp(shipmodel->shipmodel_turnspeed, 0.0f, 90.0f);
					continue;
				} else {
					con_warn << shipsini.name() << " unknown key '" << shipsini.key() << "' at line " << shipsini.line() << std::endl;
				}
			}
		} else if (shipsini.got_section("ship")) {
			shipmodel = new ShipModel();

			if (!default_shipmodel)
				default_shipmodel = shipmodel; 

		} else if (shipsini.got_section()) {
			con_warn << shipsini.name() << " unknown section '" << shipsini.section() << "' at line " << shipsini.line() << std::endl;
		}
	}
	shipsini.close();

	con_debug << "  " << shipsini.name() << " " << ShipModel::registry.size() << " ship models" << std::endl;

	if (!default_shipmodel) {
		con_error << "No default ship model found!\n";
		return false;
	}

	return true;
}

void Game::frame(float seconds)
{
	if (!running())
		return;
}

void Game::player_connect(core::Player *player)
{
	std::string args;
	player->set_zone(default_zone);
	func_spectate(player, args);
}

void Game::player_disconnect(core::Player *player)
{
	player->remove_asset(player->control());
}

} // namespace game