/*
   net/netclient.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.h>
#include <zlib.h>

#include <iostream>
#include <sstream>

#include "sys/sys.h"
#include "core/net.h"
#include "core/application.h"
#include "core/stats.h"

namespace core
{

NetClient::NetClient(std::string host, int port, int fd) :
		client_host(host)
{
	client_error = true;
	client_state = Connecting;
	client_fd = fd;
	client_host = host;
	client_port = port;

	client_player = new Player(this);

	if (!fd) {
		con_warn << "Network invalid client file descriptor!" << std::endl;
		abort();
		return;
	}
	client_addr.sin_family = AF_INET;
	client_addr.sin_port = htons(port);
	client_addr.sin_addr.s_addr = inet_addr(host.c_str());
	memset(client_addr.sin_zero, '\0', sizeof(client_addr.sin_zero));

	if (client_addr.sin_addr.s_addr == INADDR_NONE) {
		con_warn << "Network invalid client address " << host << "!" << std::endl;
		abort();
		return;
	}

	sendq.clear();
	messageblock.clear();

	client_keepalive = application()->time();
	client_timeout = application()->time();

	client_error = false;
}

NetClient::~NetClient()
{
	con_print << host()  << ":" << port() << " disconnected" << std::endl;
	delete client_player;
}

void NetClient::abort()
{
	client_error = true;
}

std::string NetClient::host() const
{
	return client_host;
}

int NetClient::port() const
{
	return client_port;
}

Player *NetClient::player()
{
	return client_player;
}

bool NetClient::has_messages() const
{
	return (recvq.size() > 0);
}

void NetClient::retreive(std::string & message)
{
	if (recvq.size() > 0) {
		message.assign(recvq.front());
		recvq.pop_front();
	} else {
		message.clear();
	}
}

// receive data and decode it into lines
void NetClient::receive(char *data)
{
	const char *c = data;

	while (*c) {
		if ((*c == '\n') || (*c == '\r')) {
			if (messageblock.size() > 0) {
				recvq.push_back(messageblock);
				messageblock.clear();
			}
		} else {
			if (messageblock.size() < FRAMESIZE) {
				messageblock += *c;
			} else {
				con_warn << "Incoming message exceeds " << FRAMESIZE << " bytes!\n";
				messageblock.clear();
			}
		}
		c++;
	}

	client_timeout = application()->time();
}

void NetClient::send_raw(std::string const &msg)
{
	if (error())
		return;

	if ((sendq.size()) && (sendq.size() + msg.size() >= BLOCKSIZE - 16)) {
		transmit();
	}

	sendq.append(msg);
}

void NetClient::transmit()
{
	if (!sendq.size()) {
		if (client_keepalive + NETTIMEOUT / 2 < application()->time()) {
			sendq.assign("ping\n");
		} else {
			return;
		}
	} else if (sendq.size() >= BLOCKSIZE - 16) {
		con_warn << host() << ":" << port() << " outgoing data exceeds " << BLOCKSIZE - 16 << " bytes!\n";
		sendq.clear();
		return;
	}

	unsigned char	zbuf[BLOCKSIZE];
	const char	*data = 0;
	uLong		compressed_size = (uLong) BLOCKSIZE - 5;
	uLong 		uncompressed_size = (uLong) sendq.size();
	size_t		total_size = 0;

	memset(zbuf, 0, sizeof(zbuf));

	Stats::network_uncompressed_bytes_sent += sendq.size();

	// zlib compress
	int status = compress((Bytef*)(zbuf + 4), &compressed_size, (Bytef*)sendq.c_str(), uncompressed_size);

	if ((status == Z_OK) && (compressed_size + 4 < sendq.size())) {
		// add a header to the compress packet
		data = (char *) zbuf;
		total_size = (size_t)(compressed_size + 4);
		zbuf[0] = '\xff';
		zbuf[1] = '\xff';
		zbuf[2] = compressed_size % 256;
		zbuf[3] = compressed_size >> 8;
	} else {
		data = sendq.c_str();
		total_size = sendq.size();
	}

	size_t		total_sent = 0;

	while (total_sent < total_size && !error()) {
		ssize_t bytes_sent = ::sendto(fd(), data, total_size - total_sent, 0,
					      (struct sockaddr *) & client_addr, sizeof(client_addr));

		if (bytes_sent < 0) {
			abort();
			return;
		}

		total_sent += bytes_sent;
		data += bytes_sent;
		Stats::network_bytes_sent += bytes_sent;
	}

	sendq.clear();
	client_keepalive = application()->time();
}

}