/*
 * Author:
 *         Kendy Kutzner <kutzner@ira.uka.de>
 *
 * (c) 2005 Research Group Dr. Fuhrmann, University of Karlsruhe, Germany
 * All rights reserved.
 *
 * $Id: libigor.cc,v 1.43 2006/02/02 17:59:47 kutzner Exp $
 *
 */

/*
  libigor.cpp
  created: 28.11.2004

  $Id: libigor.cc,v 1.43 2006/02/02 17:59:47 kutzner Exp $
*/

#include <assert.h>
#include <iostream>
#include <string.h>
#include <malloc.h>
extern "C"
{
#include <unistd.h>
}
#include "igor.h"

#include <sys/types.h>
#include <sys/socket.h>

#include "libigor.h"
#include "connection.h"
#include "message.h"
#include "buf.h"
#include "sha1.h"
#define _IN_MAIN_
#include "debug.h"

#include <map>
using namespace std;

#define ENVIRONMENTVARIABLE "IGOR"
#define DEFAULTTRANSPORT "localhost:11072"

void * (*igor_malloc)(size_t) THROW ;
void (*igor_free)(void *) THROW ;

enum state { EMPTY, CONNWAIT, REGPENDING, READY, CLOSING, CLOSED };

struct libconnection {
	struct igor_apptype type;
	enum state state;
	conn connection;
	char * lasterror;
	msg_data * sendon;
};

map<int,struct libconnection*> connectionlist ;

struct libconnection * sd2c(int sd){
	struct libconnection * c = (connectionlist.find(sd))->second;
	return c;
}

void
saigorfree(sa_igor * sa){
	if (sa->len && sa->address) // both values are set. We assume the content is valid
		igor_free((void*)(sa->address));
	// either we free()d the ptr, or one of the values were not set. Now we can clean everything.
	sa->len = 0;
	sa->address = NULL;
	sa->mustbehashed = false;
}

void
apptypefree(igor_apptype * at){
	if (at->len && at->apptype) // both values are set. We assume the content is valid
		igor_free((void*)(at->apptype));
	// either we free()d the ptr, or one of the values were not set. Now we can clean everything.
	at->len = 0;
	at->apptype = NULL;
	at->mustbehashed = false;
}

int
igor_hashit_sa(sa_igor *s){
	if (!(s->mustbehashed))
			return IGOR_ERR_NOERR;
	unsigned char * buf = NULL;
	buf = (unsigned char*)(igor_malloc(igor_SHA_DIGEST_LENGTH));
	if (!buf)
		return IGOR_ERR_OOM;
	igor_SHA1((unsigned char*)(s->address), s->len, buf);
	saigorfree(s);
	s->address = (char *)buf;
	s->len = igor_SHA_DIGEST_LENGTH;
	s->mustbehashed = 0;
	return IGOR_ERR_NOERR;
}

int
igor_hashit_apptype(igor_apptype *t){
	if (!(t->mustbehashed))
			return IGOR_ERR_NOERR;
	unsigned char * buf = NULL;
	buf = (unsigned char*)(igor_malloc(igor_SHA_DIGEST_LENGTH));
	if (!buf)
		return IGOR_ERR_OOM;
	igor_SHA1((unsigned char*)(t->apptype), t->len, buf);
	igor_free((void*)(t->apptype));
	t->apptype = (char *)buf;
	t->len = igor_SHA_DIGEST_LENGTH;
	t->mustbehashed = 0;
	return IGOR_ERR_NOERR;
}

int
copy_saigor(const sa_igor * src, sa_igor * dst){
	saigorfree(dst); // maybe not necessary if the buffer fits. optimization possible
	char * buf = (char*) igor_malloc(src->len);
	if (!(buf))
		return IGOR_ERR_OOM;
	dst->len = src->len;
	dst->mustbehashed = src->mustbehashed;
	memcpy(buf, src->address, dst->len);
	dst->address = buf;
	return IGOR_ERR_NOERR;
}

int
copy_apptype(const igor_apptype * src, igor_apptype * dst){
	apptypefree(dst); // maybe not necessary if the buffer fits. optimization possible
	char * buf = (char*) igor_malloc(src->len);
	if (!(buf))
		return IGOR_ERR_OOM;
	dst->len = src->len;
	dst->mustbehashed = src->mustbehashed;
	memcpy(buf, src->apptype, dst->len);
	dst->apptype = buf;
	return IGOR_ERR_NOERR;
}

int saigor2id(const sa_igor* s1, id * i){
	if (s1->len == 0){
		i->setempty();
		return IGOR_ERR_NOERR;
	}
	if (s1->mustbehashed){
		int ret;
		sa_igor s2;
		memset(&s2, 0, sizeof(sa_igor));
		ret = copy_saigor(s1, &s2);
		if (ret!=IGOR_ERR_NOERR)
			return ret;
		ret = igor_hashit_sa(&s2);
		if (ret!=IGOR_ERR_NOERR)
			return ret;
		i->fromraw(s2.address, s2.len);
		saigorfree(&s2);
	} else {
		i->fromraw(s1->address, s1->len);
	}
	return IGOR_ERR_NOERR;
}

int id2saigor(id * const i , sa_igor * s){
	s->mustbehashed = 0;
	char * buf;
	if (s->len){
		// optimization possible: if buffer fits, don't free/malloc it
		saigorfree(s);
	}
	s->len = i->rawlength();
	buf = (char*)(igor_malloc(s->len));
	if (!(buf)){
		s->len = 0;
		return IGOR_ERR_OOM;
	}
	memcpy(buf, i->raw(), s->len);
	s->address = buf;
	return IGOR_ERR_NOERR;
}

void igor_init() __attribute__((constructor)) ;

void igor_init()
{
	dbg.print("%s\n", __PRETTY_FUNCTION__);
	igor_malloc = malloc;
	igor_free = free;
}

void _init ()
{
	dbg.print("%s\n", __PRETTY_FUNCTION__);
	igor_init();
}

int next_socket_descriptor(){
	static int i = 0;
	return ++i;
}

int igor_send_internal(struct libconnection *c, msg *m){
	try {
		c->connection.send(m);
	} catch (conn::e_should_close) {
		c->connection.close();
		c->lasterror = "send failed";
		return IGOR_ERR_NOTCONNECTED;
	}
	return IGOR_ERR_NOERR;
}

int igor_socket ()
{
	struct libconnection * c = new libconnection;
	if (!c){
		// lasterror = "Could not allocate connection state";
		return IGOR_ERR_OOM;
	}
	c->lasterror = "OK";
	c->state = EMPTY;
	c->sendon = NULL;
	c->type.len = 0;
	c->type.apptype = NULL;
	char * transport = getenv(ENVIRONMENTVARIABLE);
	if (!transport)
		transport = DEFAULTTRANSPORT;
	int openret = c->connection.open(transport);
	if (openret){
		c->lasterror = "Call to connection->open() failed";
		return IGOR_ERR_NOTCONNECTED;
	}
	c->state = CONNWAIT;
	int sd = next_socket_descriptor();
	connectionlist.insert(std::pair<int,libconnection*>(sd, c));
	return sd;
}

int
igor_bind(int sd, const struct igor_apptype * t){
	struct libconnection * c = sd2c(sd);
	if (!c){
		return IGOR_ERR_INVAL;
	}
	c->lasterror = "OK";
	if (!t){
		c->lasterror = "t invalid";
		return IGOR_ERR_INVAL;
	}
	if (!(t->apptype) || !(t->len)){
		c->lasterror = "t->apptype or t->len invalid";
		return IGOR_ERR_INVAL;
	}
	int copyret = copy_apptype(t, &(c->type));
	if (copyret != IGOR_ERR_NOERR){
		c->lasterror = "could not copy applicaton type";
		return copyret;
	}
	int hashret = igor_hashit_apptype(&(c->type));
	if (hashret != IGOR_ERR_NOERR){
		c->lasterror = "could not hash application type";
		return hashret;
	}
	c->connection.make_blocking();
	// send registration message
	serviceid id;
	id.fromraw(c->type.apptype, c->type.len);
	msg_register m;
	m.setserviceid(id);
	if (igor_send_internal(c, &m) != IGOR_ERR_NOERR){
		return IGOR_ERR_NOTCONNECTED;
	}
	c->state = REGPENDING;
	// wait for registration reply
	msg * mr;
	bool callagain; // even it is not used in the blocking case here
	try {
		mr = c->connection.recv(&callagain);
	} catch (conn::e_should_close){
		c->connection.close();
		c->lasterror = "read failed";
		// ??? do we need to delete mr in this case?
		return IGOR_ERR_NOTCONNECTED;
	}
	assert(mr->type() == msg::REGREP);
	msg_register_reply * mrc = dynamic_cast<msg_register_reply *>(mr);
	assert(mrc);
	if (!(mrc->success())){
		c->lasterror = "received register_reply; success=false";
		return IGOR_ERR_NOTCONNECTED; // XXX is there a better code in this case?
	}
	delete mr;
	c->state = READY;
	return IGOR_ERR_NOERR;
}

int igor_close (int sd)
{
	///\bug unregister (or trust igor that the unregistration is done at close time)
	struct libconnection * c = sd2c(sd);
	if (!c){
		return IGOR_ERR_INVAL;
	}
	c->lasterror = "OK";
	c->state = CLOSING;
	c->connection.close();
	c->state = CLOSED;
	delete c;
	connectionlist.erase(sd);
	return IGOR_ERR_NOERR;
}

int igor_send(struct libconnection * c, msg_data *m){
	return igor_send_internal(c, m);
}

int 
igor_sendto (int sd, const void *msg, size_t len, bool upcall, const struct sa_igor * dest){
	sa_igor src;
	src.address = NULL;
	src.len = 0;
	src.mustbehashed = 0;
	struct libconnection * c = sd2c(sd);
	if (!c){
		return IGOR_ERR_INVAL;
	}
	c->lasterror = "OK";
	if (!msg){
		c->lasterror = "msg invalid";
		return IGOR_ERR_INVAL;
	}
	if (c->state!=READY){
		c->lasterror = "connection not ready";
		return IGOR_ERR_NOTCONNECTED;
	}
	msg_data * m = new msg_data();
	buf b;
	b.put((uint8*)msg, len);
	m->setdata(&b);
	b.clear();
	m->setupcall(upcall);
	id destinationid, sourceid;
	saigor2id(dest, &destinationid);
	saigor2id(&src, &sourceid);
	m->settarget(destinationid);
	m->setsource(sourceid);
	serviceid id;
	id.fromraw(c->type.apptype, c->type.len);
	m->setserviceid(id);
	return igor_send(c, m);
}

int 
igor_recvfromto (int sd, void **msg, size_t * len, bool * final, struct sa_igor * dest, struct sa_igor * src)
{
	struct libconnection * c = sd2c(sd);
	if (!c){
		return IGOR_ERR_INVAL;
	}
	c->lasterror = "OK";
	buf *b;
	class msg * m = NULL;
	if (!msg){
		c->lasterror = "msg null ptr";
		return IGOR_ERR_INVAL;
	}
	while (!m){ // if there is a msg left from the last call, the while loop will be skipped
		try {
			bool callagain; // not used, since libigor uses blocking communication
			m = c->connection.recv(&callagain);
		} catch (conn::e_should_close){
			c->connection.close();
			c->lasterror = "read failed";
			// ??? do we need to delete m in this case?
			return IGOR_ERR_NOTCONNECTED;
		}
		if (!m)
			continue;
		if (m->type()!=msg::DATA){
			// XXX just ignore everything which is not a data mesg
			delete m;
			m = NULL;
		}
	}
	// at this point we are sure we have a valid data msg
	class msg_data * md = dynamic_cast<msg_data*>(m);
	// *final = ! (md->upcall());
	id source = md->source(); // this conversion is neccesary because src may be a noderef
	id destination = md->target();
	if (id2saigor(&source, src) == IGOR_ERR_OOM) {
		c->lasterror = "could not copy source";
		delete md;
		saigorfree(src);
		saigorfree(dest);
		return IGOR_ERR_OOM;
	}
	if (id2saigor(&destination, dest) == IGOR_ERR_OOM) {
		c->lasterror = "could not copy destination";
		delete md;
		saigorfree(src);
		saigorfree(dest);
		return IGOR_ERR_OOM;
	}
	b = md->getdata();
	*final = !(md->upcall());
	*msg = igor_malloc(b->size());
	if (!*msg){
		c->lasterror = "could not copy msg to application";
		delete md; // this message is lost
		b->clear();
		saigorfree(src);
		saigorfree(dest);
		return IGOR_ERR_OOM;
	}
	*len = b->size();
	memcpy(*msg, b->getraw(), *len);
	if (md->upcall()){
		if (c->sendon)
			delete c->sendon;
		c->sendon = md;
	} else {
		delete m;
	}
	return IGOR_ERR_NOERR;

}

int igor_sendon(int sd){
	if (!sd){
		return IGOR_ERR_INVAL;
	}
	struct libconnection * c = sd2c(sd);
	if (!c){
		return IGOR_ERR_INVAL;
	}
	if (!(c->sendon)){
		c->lasterror = "no message to pass on";
		return IGOR_ERR_INVAL;
	}
	int ret = igor_send(c, c->sendon);
	delete c->sendon;
	c->sendon = NULL;
	return ret;
}

const char *
igor_strerror(int sd){
	struct libconnection * c = sd2c(sd);
	if (!c){
		return "invalid socket descriptor";
	}
	return c->lasterror;
}

const char *
igor_strerror_r(int code){
	switch (code){
		case IGOR_ERR_NOERR:
			return "no error";
		case IGOR_ERR_NOTCONNECTED:
			return "this socket is not connected";
		case IGOR_ERR_OOM:
			return "out of memory";
		case IGOR_ERR_INVAL:
			return "invalid value";
		default:
			return "unkown error code";
	}
}

bool 
igor_sa_cmp(const sa_igor* sa1, const sa_igor* sa2){
	sa_igor t1, t2;
	if (sa1->mustbehashed == sa2->mustbehashed){
		if (sa1->len == sa2->len){
			return !memcmp(sa1->address, sa2->address, sa1->len);
		} else {
			return false;
		}
	}
	copy_saigor(sa1, &t1);
	copy_saigor(sa2, &t2); // one of these is not neccessary
	igor_hashit_sa(&t1);
	igor_hashit_sa(&t2);
	return igor_sa_cmp(&t1, &t2);
}

#ifdef __cplusplus
// all the C++ stuff

/** This class has a single purpose: Initialize IGOR.
 * It is done by a global instance of this class.
 * The ctor of this global instance should be called during library load.
 */
class igor_initializer {
public:
	igor_initializer();
};

/** The global instance to initialize things.
 */
igor_initializer theigorinitializer;

/** Simple ctor for library initializiation.
 * Calls into the C code for doing things.
 */
igor_initializer::igor_initializer(){
	dbg.print("%s\n", __PRETTY_FUNCTION__);
	igor_init();
}

csa_igor::csa_igor(){
	len = 0;
	address = NULL;
	mustbehashed = 0;
}

csa_igor::~csa_igor(){
	saigorfree(rawpointer());
}

csa_igor &
csa_igor::operator=(const csa_igor& other){
	saigorfree(rawpointer());
	copy_saigor(other.rawpointer(), rawpointer());
	return *this;
}

bool
csa_igor::operator==(const csa_igor & other) const {
	return igor_sa_cmp(rawpointer(), other.rawpointer());
}

csa_igor::csa_igor(const csa_igor& other) : len(other.len), mustbehashed(other.mustbehashed) {
	// char *tmp = (char *) malloc(strlen(sa.address) + 1); // who wrote this? me?
	char * tmp = (char*) igor_malloc(len);
	assert(tmp);
	memcpy(tmp, other.address,len);
	address = tmp;
}

/** Cast the class down to the C-like structure.
 * It returns the this pointer and thereby assumes that sa_igor and csa_igor have the same memory layout (at least in the front).
 */
sa_igor *
csa_igor::rawpointer() const{
	assert(sizeof(sa_igor)==sizeof(csa_igor));
	return (sa_igor*)(this); ///\bug get rid of this ugly cast!
}
#endif // __cplusplus
