/*
 * Author:
 *         Kendy Kutzner <kutzner@ira.uka.de>
 *
 * (c) 2005 Research Group Dr. Fuhrmann, University of Karlsruhe, Germany
 * All rights reserved.
 *
 * $Id: connection.cc,v 1.35 2005/12/29 16:21:58 kutzner Exp $
 *
 */

/** \file
 * Implementation of class \link conn conn \endlink
 */

#include <string>
#include <sstream>
using namespace std;

#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/types.h>
#include "connection.h"
#include "igor.h"
#include "buf.h"
#include "debug.h"
#include "rawmessage.h"
#include "utils.h"

/**
 * initially, every connection is void
 */

conn::conn(){
	_state = EMPTY;
	_sock = -1;
}

/**
 * Open a new connection.
 * This function should be ipv6 ready.
 * After successful return, the connection is either in state OPENING or READY.
 * \param transport String containing networkaddress, colon, transport port
 * \return 0 on success, -1 otherwise.
 */

int
conn::open(string transport){
	string host;
	string port;
	if(transport.find(':')){
		host = transport.substr(0,transport.find(':'));
		port = transport.substr(transport.find(':')+1, transport.size());
	} else {
		host = transport;
		port = "";
	}
	dbg.print(dbg.CONN, 6, "try connecting to host %s and port %s\n", host.c_str(), port.c_str());
	
	struct addrinfo hints, *ai, *aibackup;
	char hbuf[NI_MAXHOST];
	char sbuf[NI_MAXSERV];
	memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_CANONNAME;
	int gaierror = getaddrinfo(host.c_str(), port.c_str(), &hints, &ai);
	if (gaierror){
		dbg.print(dbg.CONN, 4, "getaddrinfo: %s(%i)\n", gai_strerror(gaierror), gaierror);
		return -1;
	}
	aibackup = ai;
	do { // in case getaddrinfo returned more than one and the first is not usable
		int gnierror = getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV);
		if (gnierror){
			dbg.print(dbg.CONN, 4, "getnameinfo: %s(%i)\n", strerror(errno), errno);
			continue;
		}
		dbg.print(dbg.CONN, 6, "try connecting to host %s and port %s\n", hbuf, sbuf);
		_sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
		if (_sock < 0){
			dbg.print(dbg.CONN, 6, "socket(): %s\n", strerror(errno));
			continue;
		}
		int fcntlres = fcntl(_sock, F_SETFL, O_NONBLOCK);
		if (fcntlres) PERRORANDEXIT;
		int connectres = connect(_sock, ai->ai_addr, ai->ai_addrlen);
		if (connectres == 0){
			state_change(READY);
			freeaddrinfo(aibackup);
			return 0;
		}
		switch (errno){
			case EINPROGRESS:
				state_change(OPENING);
				freeaddrinfo(aibackup);
				return 0;
			default:
				dbg.print(dbg.CONN, 6, "connect(): %s\n", strerror(errno));
				::close(_sock);
				_sock = -1;
				break;
		}
	} while ((ai = ai->ai_next));
	freeaddrinfo(aibackup);
	return -1;
}

int
conn::open(const char * transport){
	string s;
	s.append(transport);
	return open(s);
}

/**
 * Start listening on a local port. Further the connection is made
 * non-blocking. Many calls to libc-functions are done. If one of these calls
 * fails, the program is shut down via exit(). 
 * \todo Don't shut down the whole program when a nameserver is dead.
 * \param port The port number to listen at. When no port number or 0 is
 * supplied, the OS will select the port number for us.
 */
void
conn::make_listen(int port){
	char myhostname[257];
	assert(_state==EMPTY);
	struct addrinfo hints, *ai;
	int family_backup;
	// first, get our own address
	// this is necessary to inform other nodes of our transport address
	memset(&hints, 0, sizeof(addrinfo));
	hints.ai_flags = AI_PASSIVE;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_family = PF_UNSPEC;
	if (gethostname(myhostname,256)<0) PERRORANDEXIT;
	int gairet = getaddrinfo(myhostname, itoa(port), &hints, &ai);
	if (gairet) {
		dbg.print(dbg.CONN, 4, "getaddrinfo: %s(%i)\n", gai_strerror(gairet), gairet);
		exit(-1);
	}
	memcpy(&_sa, ai->ai_addr, ai->ai_addrlen); 
	family_backup = ai->ai_family;
	freeaddrinfo(ai);
	// next, get socket parameters for listening socket
	memset(&hints, 0, sizeof(addrinfo));
	hints.ai_flags = AI_PASSIVE;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_family = PF_UNSPEC;
	gairet = getaddrinfo(NULL, itoa(port), &hints, &ai);
	if (gairet) {
		dbg.print(dbg.CONN, 4, "getaddrinfo: %s(%i)\n", gai_strerror(gairet), gairet);
		exit(-1);
	}
	_sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
	if ((_sock < 0) && (errno == EAFNOSUPPORT)){
		// these problems can occur on mixed v4/v6 systems where v6 is not really supported.
		dbg.print(dbg.CONN, 4, "stupid getaddrinfo returned not supported address family  %i\n", ai->ai_family);
		ai->ai_family = family_backup;
		_sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
	}
	if(_sock < 0) PERRORANDEXIT;
	// ok, we have the socket, we can bind it
	int bindres = bind(_sock, ai->ai_addr, ai->ai_addrlen);
	freeaddrinfo(ai);
	if (bindres) PERRORANDEXIT;
	// now update the port number in case 0 was supplied
	sockaddr_in sa;
	socklen_t sasize = sizeof(sa);
	int gsnres = getsockname(_sock, (sockaddr*)(&sa), &sasize);
	if (gsnres) PERRORANDEXIT;
	((struct sockaddr_in*)(&_sa))->sin_port = sa.sin_port; /// \bug evil cast
	// ready to make it really listening
	int listenres = listen(_sock, 5);
	if (listenres) PERRORANDEXIT;
	int fcntlres = fcntl(_sock, F_SETFL, O_NONBLOCK);
	if (fcntlres) PERRORANDEXIT;
	state_change(LISTEN);
	return;
}

/**
 * Switch to blocking communication. The default is nonblocking. Default is
 * nonblocking sockets; this call will revert to blocking communication.
 * \bug Missing error checking.
 */
void
conn::make_blocking(){
	fcntl(_sock, F_SETFL, 0);
	// XXX what to do on failure?
	return;
}

/**
 * Create a new connection by accepting one from a listening socket. The
 * method should be called when a listening connection becomes readable. If
 * there is a waiting connection to be accepted, a new connection object is
 * created and put in the state READY.
 * \return The new connection object or NULL if accept was not successful.
 */
conn*
conn::do_accept(){
	conn * c;
	assert(_state == LISTEN);
	c = new conn;
	assert(c);
	socklen_t salen = (socklen_t)sizeof(c->_sa);
	c->_sock = accept(_sock, (struct sockaddr*)&(c->_sa), &salen);
	if (c->_sock < 0){
		dbg.print(dbg.CONN, 4, "accept(fd %i): %s\n", _sock, strerror(errno));
		delete c;
		return NULL;
	}
	int fcntlres = fcntl(c->_sock, F_SETFL, O_NONBLOCK);
	if (fcntlres) PERRORANDEXIT;
	c->state_change(READY);
	return c;
}
	
/**
 * Shutdown the object and close the connection if neccessary.
 */

conn::~conn(){
	switch(_state){
		case EMPTY:
		case CLOSED:
			break;
		case OPENING:
		case LISTEN:
		case READY:
		case CLIENT:
			close();
			break;
		default:
			dbg.print(dbg.CONN, 4, "error in conn dtor (state %i)\n", _state);
	}
	return;
}

/**
 * Send one message. 
 * 
 * Before the message is sent, it has to be convertet to a raw message. 
 * It is done in this function because the raw format depends on the link type (think simulation).
 *
 * If m is NULL, try to send any remaining parts of older messages
 *
 * \return
 *  - 0: Everything is fine
 *  - -1: error (should not occur, should throw the exception
 *  - 1: only part of the message were written. The function should be called with m=NULL again.
 */
int
conn::send(msg *m) throw (e_should_close) {
	if (m){
		_msg_out.inc();
		rawmessage rm;
		dbg.print(dbg.CONN, 6, "sending message of type %s to fd %i\n", m->getname().c_str(), _sock);
		m->toraw(&rm);
		rm.unparse(&_send_buf);
	}
	dbg.print(dbg.CONN, 6, "trying to send %i bytes to fd %i\n", _send_buf.size(), _sock);
	if (_send_buf.size()==0){
		dbg.print(dbg.CONN, 4, "nothing to send, check program logic\n");
		return 0;
	}
	dbg.print(dbg.CONN, 8, "buf is '%%s'\n");//, _send_buf.getraw());
	int wres = write(_sock, _send_buf.getraw(), _send_buf.size());
	dbg.print(dbg.CONN, 6, "write returned %i\n", wres);
	if ((wres == -1) && (errno == EAGAIN)){
		dbg.print(dbg.CONN, 6, "fd %i is still busy\n", _sock);
		return 1;
	} else if (wres < 0){
		dbg.print(dbg.CONN, 5, "write to fd %i returned %s, closing connection\n", _sock, strerror(errno));
		throw e_should_close("error writing");
	}
	_bytes_out.add(wres);
	if (wres != _send_buf.size()){
		dbg.print(dbg.CONN, 6, "wrote only part (%i/%i) of a buffer.\n", wres, _send_buf.size()); 
		_send_buf.get(NULL, wres);
		return 1;
	}
	_send_buf.clear(); // everything is sent out
	return 0;
}

/**
 * Try to parse a message from the receive buffer.
 * \return NULL if nothing could be parsed and the message is successful. Caller is responsible for deleting the message.
 */

msg *
conn::recv_parsemsg(){
	dbg.print(dbg.CONN, 8, "current buf size %i content '%%s'\n", _receive_buf.size());// _receive_buf.getraw());
	rawmessage * rm = new rawmessage;
	rm->parse(&_receive_buf);
	if (!rm->complete()){
		delete rm;
		dbg.print(dbg.CONN, 6, "parsing finished without a new msg\n");
		return NULL;
	}
	msg *m = msg::createfromrm(rm);
	delete rm;
	_msg_in.inc();
	return m;
}

/**
 * Try to read one message.
 * If the received thing is not a complete message, NULL is returned.
 * \param callagain This call-by-reference flag signals whether we expect to read further data soon. In this case the caller should call this method again. If it is set to false, the next invocation should occur after select().
 * \return A successful received message.
 */
msg *
conn::recv(bool * callagain) throw (e_should_close) {
	*callagain = false;
	dbg.print(dbg.CONN, 6, "recv starting fd %i\n", _sock);
	// first try to parse what's left from the last call
	msg * m = recv_parsemsg();
	if (m){
		*callagain = true;
		return m;
	}
	uint8 b[IGOR_SOCKET_READ_BUFFER+1];
	int readres;
	readres = read(_sock, b, IGOR_SOCKET_READ_BUFFER);
	dbg.print(dbg.CONN, 6, "read returned %i\n", readres);
	if (readres == 0){ // eof / close
		throw e_should_close("read returned 0");
	} else if (readres < 0) { // error
		if (errno == EAGAIN){ // harmless error
			dbg.print(dbg.CONN, 6, "read returned nothing (EAGAIN)\n");
			// don't return here, we can still try to parse the bytes in the buffer
		} else {
			dbg.print(dbg.CONN, 4, "read(fd %i): %s\n", _sock, strerror(errno));
			throw e_should_close("read returned -1");
		}
	} else {
		_receive_buf.put(b,readres);
		_bytes_in.add(readres);
	}
	if (IGOR_SOCKET_READ_BUFFER == readres) { // maybe there is even more to receive
		*callagain = true;
	}
	if (OPENING == _state)
		setready();
	m = recv_parsemsg();
	if (m){ // if there is one message, there may be another
		*callagain = true;
	}
	dbg.print(dbg.CONN, 6, "recv finished\n");
	return m; 
}

/**
 * Shutdown of a connection. The TCP-Connection is closed. Associated buffers
 * are cleared.
 */
void
conn::close(){
	if (_state == CLOSED)
		return;
	::close(_sock);
	state_change(CLOSED);
	dbg.print(dbg.CONN, 5, "the just closed connection (fd %i) sent %li bytes in %li messages and received %li bytes in %li messages. %i/%i bytes are left for recv/send\n", _sock, _bytes_out.get(), _msg_out.get(), _bytes_in.get(), _msg_in.get(), _receive_buf.size(), _send_buf.size());
	_receive_buf.clear();
	_send_buf.clear();
	_sock = -1;
	return;
}

/**
 * Help debugging by doing state changes only via this method.
 * The old and new state are printed to the debug facility.
 * \todo Check wether this state transition is valid.
 * \param next The new state.
 */
void
conn::state_change(conn_state next){
	if (_state!=next)
		dbg.print(dbg.CONN, 5, "connection with fd %i changed state from %s to %s\n", _sock, statename(_state), statename(next));
	_state = next;
}

/**
 * Create a string describing the connection. The string will have the form
 * (hostname of the remote address)(colon)(port at the remote side). Such a
 * string is suitable to feed back to open() at other places.
 * \return The just described string.
 */
string
conn::transportname(){
	char hbuf[NI_MAXHOST];
	char sbuf[NI_MAXSERV];
	stringstream s;
	int gniret = getnameinfo((sockaddr*)(&_sa), sizeof(_sa), hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), 0); // allow non-numeric output?
	if (gniret)
		dbg.print(dbg.CONN, 4, "getnameinfo on fd %i: %s(%i)\n", _sock, gai_strerror(gniret), gniret);
	s << hbuf;
	s << ':';
	s << sbuf;
	return s.str();
}

/**
 * Declare this connection to be a client conntection. This is possible only
 * when the connection was READY before.
 */
void
conn::setclient(){
	assert(_state==READY);
	state_change(CLIENT);
}

/**
 * Set the connection to READY. This is useful for connections in state
 * OPENING. The user of the connection receives the first data and tells the
 * connection object that the connection is open now.
 */
void
conn::setready(){
	if (_state==OPENING){
		state_change(READY);
	}
}

/**
 * Connections keep some statistics of sent/received messages/bytes. This
 * method can be used to get these statistics out of the connection object.
 */
void 
conn::copy_counters(stat_counter * m_in, stat_counter * m_out, stat_counter * b_in, stat_counter * b_out){
	m_in->add(_msg_in.get());
	m_out->add(_msg_out.get());
	b_in->add(_bytes_in.get());
	b_out->add(_bytes_out.get());
	return;
}
