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

#include <string>
#include <sstream>

#include "sys/sys.h"
#include "filesystem/filesystem.h"
#include "core/application.h"
#include "core/cvar.h"
#include "core/entityprojectile.h"
#include "core/gameconnection.h"
#include "core/net.h"


namespace core
{
const unsigned long INFOTIMEOUT = 2500; // 2500ms info request timeout

GameConnection* GameConnection::connection_instance = 0;

GameConnection::GameConnection(std::string const &connectionstr)
{
	connection_instance = this;
	connection_network = 0;
	connection_netframe = 0;
	
	// clear current guid
	localplayer()->guid().clear();
	
	// read keys.ini
	std::string filename(filesystem::homedir());
	filename.append("keys.ini");
	
	std::ifstream ifs(filename.c_str());
	if (ifs.is_open()) {
		filesystem::IniStream inistr;
		
		while(inistr.getline(ifs)) {
			if (inistr.in_section("client")) {
				std::string s;
				if (inistr.got_key_string("key", s)) {
					aux::to_lowercase(s);
					aux::trim(s);
					localplayer()->guid().assign(s);
					if (!localplayer()->guid().is_valid()) {
						con_warn << "keys.ini invalid client key '" << s <<  "' at line " << inistr.line() << std::endl;
						
					}
				}
			}
		}
		
		ifs.close();
	}

	// if no valid key was found, generate one
	if (!localplayer()->guid().is_valid()) {
		localplayer()->guid().generate();
	}
	
	// write keys.ini
	std::ofstream ofs(filename.c_str());
	if (!ofs.is_open()) {
		con_warn << "Could not write " << filename << std::endl;
	} else  {
		ofs << "; keys.ini" << std::endl;
		ofs << "; Project::OSiRiON client identification" << std::endl;
		ofs << "; DO NOT EDIT OR DELETE THIS FILE" << std::endl;
		ofs << "; If you do, you will not be able to use existing characters in multiplayer games" << std::endl;
		ofs << std::endl;
		ofs << "[client]" << std::endl;
		ofs << "key=" << localplayer()->guid().str() << std::endl;
		ofs.close();
	}
	
	con_print << "Using client key " << localplayer()->guid().str() << std::endl;
	
	// split hostname into host and port
	unsigned int port = DEFAULTPORT;
	std::string host(connectionstr);
	size_t found = host.find(':');

	if (found != std::string::npos) {
		std::istringstream str(host.substr(found + 1));
		if (str >> port) {
			host.erase(found, std::string::npos);
		} else {
			con_print << "Invalid hostname '" << host << "'\n";
			abort();
			return;
		}

	}

	// initiate the network connection
	connection_network = new NetConnection();
	connection_network->connect(host, port);

	if (!connection_network->connected()) {
		abort();
		return;
	}

	// send connect request
	connection_network->send_connect();
	connection_network->transmit();

	if (!connection_network->connected()) {
		abort();
		return;
	}

	game_players.push_back(localplayer());
	set_playerlist_timestamp(timestamp());
	
	set_interactive(true);
	set_running(true);
}

GameConnection::~GameConnection()
{
	if (connection_network) {
		connection_network->disconnect();
		delete connection_network;
	}

	connection_instance = 0;
}

Info *GameConnection::request_info(const unsigned int id)
{
	if (!id) {
		con_warn << "Information requested for illegal id 0!" << std::endl;
		return 0;
	}
	
	// find the info record
	Info *info = Info::find(id);
	if (!info) {
		info = new Info(id);
	}

	if (info->type() || (timestamp() < info->timestamp() + INFOTIMEOUT) )
		return info;

	// send an information request to the server
	if (connection_network) {
		connection_network->send_info_request(info);
		connection_network->transmit();
	} else {
		info->add_text("^RNot connected.");
		info->set_timestamp(0);
	}
	return info;
}

Inventory *GameConnection::request_inventory(Entity *entity)
{
	if (!entity) {
		con_warn << "Inventory request for NULL entity" << std::endl;
		return 0;
	}
	
	if (entity->inventory() && connection_network) {
		connection_network->send_inventory_request(entity);
		connection_network->transmit();
	}	
	
	return (entity->inventory());
}

void GameConnection::forward(std::string const &cmdline)
{
	if (!connection_network->connected())
		return;

	connection_network->send_command(cmdline);
}

void GameConnection::rcon(std::string const &cmdline)
{
	if (!connection_network->connected())
		return;

	connection_network->send_rcon(cmdline);
}

void GameConnection::say(std::string const &args)
{
	if (!connection_network->connected())
		return;

	connection_network->send_say(args);
}

void GameConnection::shout(std::string const &args)
{
	if (!connection_network->connected())
		return;

	connection_network->send_shout(args);
}

void GameConnection::private_message(std::string const &args)
{
	if (!connection_network->connected())
		return;

	connection_network->send_private_message(args);
}

void GameConnection::frame(unsigned long timestamp)
{
	if (!running())
		return;

	if (!connection_network->connected()) {
		abort();
		return;
	}

	// get incoming messages
	connection_network->frame();
	
	// update client-sde state
	for (Entity::Registry::iterator it = Entity::registry().begin(); it != Entity::registry().end(); it++) {
		Entity *entity = (*it).second;
		
		if (entity->type() == Entity::Projectile) {
			EntityProjectile *projectile = static_cast<EntityProjectile *>(entity);
			if (projectile->state() == Entity::Normal) {
				unsigned long elapsed = application()->timestamp() - projectile->timestamp();
				if (elapsed > 0) {
					projectile->set_location(projectile->location() + projectile->axis().forward() * projectile->speed() * ((float) elapsed / 1000.0f));
					projectile->set_timestamp(application()->timestamp());
				}
			}
		}
	}

	float f = 0;
	if (core::Cvar::net_framerate->value()) {
		f =  1000.0f / core::Cvar::net_framerate->value();
		if (connection_netframe + f > timestamp) {
			return;
		}
	}

	connection_netframe = timestamp;

	if (connection_network->state() == NetConnection::Connected) {

		if (localcontrol() && localcontrol()->dirty()) {
			connection_network->send_client_update(localcontrol());
			localcontrol()->set_dirty(false);

		}

		if (localplayer()->dirty()) {
			connection_network->send_playerinfo();

		}
	}

	set_timestamp(connection_network->timestamp());
	
	connection_network->transmit();
}

}