/*
 * Author:
 *         Kendy Kutzner <kutzner@ira.uka.de>
 *
 * (c) 2005 Research Group Dr. Fuhrmann, University of Karlsruhe, Germany
 * All rights reserved.
 *
 * $Id: message.cc,v 1.26 2006/01/05 15:54:38 kutzner Exp $
 *
 */

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

#include <assert.h>

#include "message.h"
#include "debug.h"
#include "utils.h"

// memory for static objects
int msg::next_id = 0;

string typestring = "Type";
string deststring = "Destination";
string msgidstring = "MsgId";
string sourcestring = "Source";
string numwantstring = "NumbersWanted";
string numprovidedstring = "NumbersProvided";
string noderefstring = "NodeRef";
string upcallstring = "Upcall";
string serviceidstring = "ServiceId";
string successstring = "Success";
string timestampstring = "Timestamp";

bool msg::_names_initialized = false;

map<msg::msgtype,string> msg::_messagenames;

void
msg::initnames(){
	_messagenames.insert(std::pair<msgtype,string>(EMPTY, "emptyMessage"));
	_messagenames.insert(std::pair<msgtype,string>(WNL, "WantNewLinks"));
	_messagenames.insert(std::pair<msgtype,string>(RL, "RecommendLinks"));
	_messagenames.insert(std::pair<msgtype,string>(PING, "Ping"));
	_messagenames.insert(std::pair<msgtype,string>(DATA, "Data"));
	_messagenames.insert(std::pair<msgtype,string>(REG, "Register"));
	_messagenames.insert(std::pair<msgtype,string>(REGREP, "RegisterReply"));
	_messagenames.insert(std::pair<msgtype,string>(UNREG, "Unregister"));
	_messagenames.insert(std::pair<msgtype,string>(GETINFO, "GetInfo"));
	_messagenames.insert(std::pair<msgtype,string>(INFO, "Info"));
	_names_initialized = true;
	dbg.print(dbg.MESG, 8, "%i message names available\n", _messagenames.size());
}

string
msg::getname(){
	switch(_type){
		case EMPTY:   return "emptyMessage";
		case WNL:     return "WantNewLinks";
		case RL:      return "RecommendLinks";
		case PING:    return "Ping";
		case DATA:    return "Data";
		case REG:     return "Register";
		case REGREP:  return "RegisterReply";
		case UNREG:   return "Unregister";
		case GETINFO: return "GetInfo";
		case INFO:    return "Info";
	}
	stringstream s;
	s << "error in msg::getname(" ;
	s << _type ; 
	s << ") not defined";
	s << endl;
	return s.str();
}

////
// convert a message to a raw message
//

void
msg::toraw(rawmessage *rm){
	rm->addval(typestring, getname());
	rm->addval(deststring, _target.tostring());
	rm->addval(sourcestring, _source.tostring());
	rm->addval(msgidstring, _msgid);
	rm->addvald(timestampstring, _timestamp);
}

void
msg_wnl::toraw(rawmessage *rm){
	msg::toraw(rm);
	rm->addval(numwantstring, _numbers_wanted);
}

void
msg_rl::toraw(rawmessage *rm){
	msg::toraw(rm);
	rm->addval(numprovidedstring, _reflist.size());
	int c = 0;
	for (list<noderef>::const_iterator i = _reflist.begin(); i!=_reflist.end(); i++){
		noderef nr = *i;
		rm->addval(noderefstring+itos(c++), nr.tostring());
	}
}

void
msg_ping::toraw(rawmessage *rm){
	msg::toraw(rm);
}

/*
void
msg_pong::toraw(rawmessage *rm){
	msg::toraw(rm);
	dbg.print(dbg.MESG, 1, "impl. missing\n");
}
*/

void
msg_data::toraw(rawmessage *rm){
	msg::toraw(rm);
	rm->addval(serviceidstring, _serviceid.tostring());
	rm->addval(upcallstring, _upcall);
	rm->putbin(&_data);
}

void
msg_register::toraw(rawmessage *rm){
	msg::toraw(rm);
	rm->addval(serviceidstring, _serviceid.tostring());
}

void
msg_register_reply::toraw(rawmessage *rm){
	msg_register::toraw(rm);
	rm->addvalb(successstring, _success);
}

void
msg_unregister::toraw(rawmessage *rm){
	msg::toraw(rm);
	rm->addval(serviceidstring, _serviceid.tostring());
}

void
msg_getinfo::toraw(rawmessage *rm){
	msg::toraw(rm);
}

void
msg_info::toraw(rawmessage *rm){
	msg::toraw(rm);
	rm->putbin(&_data);
}


////
// create a message _from_ a raw message
//

msg *
msg::createfromrm(rawmessage *rm){
	dbg.print(dbg.PARSE, 6, "msg::operator new called\n");
	msg m(rm); // this is not very efficient: conversion rm->msg just to determine type
	dbg.print(dbg.PARSE, 7, "message seems to be of type %s\n", m.getname().c_str());
	switch (m.type()){
		case WNL: return new msg_wnl(rm); // next conversion rm->msg_wnl, msg m is discarded
		case RL: return new msg_rl(rm);
		case DATA: return new msg_data(rm);
		case PING: return new msg_ping(rm);
		case REG: return new msg_register(rm);
		case REGREP: return new msg_register_reply(rm);
		case GETINFO: return new msg_getinfo(rm);
		case INFO: return new msg_info(rm);
		default: dbg.print(dbg.PARSE, 5, "unknown message type %i (%s)\n", m.type(), m.getname().c_str());
	}
	return NULL;
}

msg::msg(rawmessage *rm){
	dbg.print(dbg.PARSE, 7, "ctor msg with rawmessage %p\n", (void*)rm);
	string type = rm->getval(typestring);
	_type = EMPTY;
	for(map<msgtype,string>::const_iterator i = _messagenames.begin(); i != _messagenames.end(); i++){
		if(i->second == type){
			_type = (msgtype)i->first; // XXX cast warning
		}
	}
	_target = id(rm->getval(deststring));
	_source = noderef(rm->getval(sourcestring));
	_msgid = rm->getint(msgidstring);
	_timestamp = rm->getdouble(timestampstring);
	double now = dbltime();
	const double clockskewdetectionthreshold = 5.0;
	if (_timestamp > now + clockskewdetectionthreshold){
	    dbg.print(dbg.PARSE, 4, "clock skew detected: msg from %f seconds in the future received\n", _timestamp - now);
	}
	dbg.print(dbg.PARSE, 8, "message parsed: target %s, source %s, msgid %i\n", _target.tostring().c_str(), _source.tostring().c_str(), _msgid);
}

msg_wnl::msg_wnl(rawmessage *rm)
:msg(rm){
	_numbers_wanted = rm->getint(numwantstring);
}

msg_rl::msg_rl(rawmessage *rm)
:msg(rm){
	dbg.print(dbg.PARSE, 7, "ctor msg_rl with rawmessage %p\n", (void*)rm);
	unsigned int num_provided = rm->getint(numprovidedstring);
	dbg.print(dbg.PARSE, 8, "message parsed: num_provided %i\n", num_provided);
	for (unsigned int i = 0; i < num_provided; i++){
		string tmp = rm->getval(noderefstring+itos(i));
		if (tmp.size()>0){
			dbg.print(dbg.PARSE, 8, "message parsed: next ref %s\n", tmp.c_str());
			add(noderef(tmp));
		}
	}
}

msg_ping::msg_ping(rawmessage *rm)
:msg(rm){
}

msg_data::msg_data(rawmessage *rm)
:msg(rm){
	string sid = rm->getval(serviceidstring);
	_serviceid = serviceid(sid);
	_upcall = rm->getbool(upcallstring);
	rm->getbin(&_data);
}

msg_register::msg_register(rawmessage *rm)
:msg(rm){
	string sid = rm->getval(serviceidstring);
	_serviceid = serviceid(sid);
}

msg_register_reply::msg_register_reply(rawmessage *rm)
:msg_register(rm){
	_success = rm->getbool(successstring);
}

msg_unregister::msg_unregister(rawmessage *rm)
:msg(rm){
	_serviceid = serviceid(rm->getval(serviceidstring));
}

msg_getinfo::msg_getinfo(rawmessage *rm)
:msg(rm){
}

msg_info::msg_info(rawmessage *rm)
:msg(rm){
	rm->getbin(&_data);
}

////
// ctors
//

msg::msg(){
	dbg.print(dbg.MESG,8,"msg::msg()\n");
	dbg.print(dbg.MESG, 8, "%i message names available\n", _messagenames.size());
	_type = EMPTY;
	_msgid = next_id++;
	_timestamp = dbltime();
	if (!_names_initialized) initnames();
	// XXX this stuff doesn't belong to each msg! static?
}

msg_wnl::msg_wnl()
:msg(){
	dbg.print(dbg.MESG,8,"msg_wnl::msg_wnl()\n");
	dbg.print(dbg.MESG, 8, "%i message names available\n", _messagenames.size());
	_type = WNL;
}

msg_wnl::msg_wnl(id msgtarget, int num)
:msg(){
	dbg.print(dbg.MESG,8,"msg_wnl::msg_wnl(id,num)\n");
	dbg.print(dbg.MESG, 8, "%i message names available\n", _messagenames.size());
	settarget(msgtarget);
	_numbers_wanted = num;
	_type = WNL;
}

msg_rl::msg_rl()
:msg(){
	_type = RL;
	_reflist.clear();
}

msg_rl::msg_rl(msg_wnl* om)
:msg(){
	_type = RL;
	_reflist.clear();
	_target = om->source();
	_msgid = om->msgid();
}

msg_ping::msg_ping()
:msg(){
	_type = PING;
}

msg_data::msg_data()
:msg(){
	_data.clear();
	_upcall = false; // is this useful?
	_type = DATA;
}

msg_register::msg_register()
:msg(){
	_type = REG;
}

msg_register_reply::msg_register_reply()
:msg_register(){
	_success = false;
	_type = REGREP;
}

msg_unregister::msg_unregister()
:msg(){
	_type = UNREG;
}

msg_getinfo::msg_getinfo()
:msg(){
	_type = GETINFO;
}

msg_info::msg_info()
:msg(){
	_type = INFO;
	_data.clear();
}


/*
msg_pong::msg_pong()
:msg_ping(){
	_type = PONG;
}

msg_pong::msg_pong(msg_ping*m)
:msg_ping(){
	_source = m->target();
	_target = m->source();
	_type = PONG;
}
*/

////
// messages with functionality
//

void
msg_rl::add(noderef ref){
	if (!ref.empty()){
		_reflist.push_back(ref);
		dbg.print(dbg.MESG, 8, "added %s as ref %i to msg_rl %p\n", ref.tostring().c_str(), _reflist.size(), (void*)this);
	} else {
		dbg.print(dbg.MESG, 8, "did not add %s as next ref to msg_rl %p\n", ref.tostring().c_str(), (void*)this);
	}
}

noderef
msg_rl::remove(){
	assert(_reflist.size()>0);
	noderef ref = _reflist.front();
	_reflist.pop_front();
	dbg.print(dbg.MESG, 8, "removed %s from msg_rl %p (%i refs left)\n", ref.tostring().c_str(), (void*)this, _reflist.size());
	return ref;
}

