#include <network.h>
#include <save.h>
#include <daemons.h>

string my_name = "*yatmim";
string my_password = "CROATOAN";
string *ok_ips = ({});
int irn_enabled = 1;
int irn_maxtry = 32;
int convert_channel = 0;

mapping routers = ([
  "*i4" : ([ "ip" : "204.209.44.3", "port" : 8080, "password" : "STENDEC" ]),
]);

static mapping chan_conv = ([
  //"*i4" : ([ "dead_test" : "test" ])
]);

mapping irn_connections = ([]);
mapping irn_sockets = ([]);

void eventSetup();
static void write_data(int fd, mixed data);
static varargs void SendList(mixed data, int fd, string type);
varargs void SendWholeList(int fd,string type);
static void begin_socket_handoff(int i);

static int GoodPeer(int fd, mixed data){
    string ip = explode(socket_address(fd)," ")[0];
    trr("IRN: hit GoodPeer","yellow");
    if(member_array(ip,ok_ips) == -1){
	//trr("irn: bad ip");
	return 0;
    }

    if(member_array(data[2], keys(routers)) == -1){
	//trr("IRN: unknown peer");
	return 0;
    }
    if(data[4] != my_name){
	//trr("IRN: they don't know my name. I am "+my_name+". They think I am: "+identify(data[4]));
	return 0;
    }

    if(data[6]["client_password"] != routers[data[2]]["password"]){
	//trr("IRN: wrong client password");
	return 0;
    }
    if(data[6]["server_password"] != my_password){
	//trr("IRN: wrong server password");
	return 0;
    }
    irn_connections[data[2]]["fd"] = fd;
    trr("IRN: returning 1!","yellow");
    return 1;
}

static int ValidatePeer(int fd, mixed data){
    string ip = explode(socket_address(fd)," ")[0];
    //trr("IRN: hit ValidatePeer","green");
    if(member_array(ip,ok_ips) == -1){
	//trr("IRN: bad ip");
	return 0;
    }
    if(!irn_connections[data[2]]){
	//trr("IRN: no such connection.");
	return 0;
    }
    if(member_array(data[2], keys(routers)) == -1){
	//trr("IRN: unknown peer");
	return 0;
    }

    foreach(mixed key, mixed val in routers){
	if(routers[key]["ip"] == ip && irn_connections[key]["fd"] != -1 &&
	  irn_connections[key]["fd"] != fd ){
	    //trr("IRN: wrong fd I got "+fd+" but expected "+ irn_connections[key]["fd"]);
	    return 0;
	}
    }
    //trr("returning 1!","green");
    return 1;
}

static string id_mud(int fd){
    string *ret = ({});
    foreach(mixed element in keys(irn_connections)){
	if(irn_connections[element]["fd"] == fd) ret += ({ element });
    }
    return implode(ret,", ");
}

void irn_clear(){
    foreach(mixed key, mixed val in irn_connections){
	this_object()->close_connection(irn_connections[key]["fd"]);
    }
    foreach(mixed key, mixed val in irn_sockets){
	this_object()->close_connection(key);
    }

    irn_connections = ([]);
    irn_sockets = ([]);

    foreach(string key, mixed val in routers){
	if(key == my_name){
	    map_delete(routers, key);
	    continue;         }
	ok_ips += ({ routers[key]["ip"] });
	if(!irn_connections[key]){
	    irn_connections[key] = (["blocking" : 1, "type": MUD, "fd": -1, "data" : 0 ]);
	}
    }
    save_object(SAVE_ROUTER);
}

varargs void irn_setup(int clear){
    if(!irn_enabled) return;
    if(clear){
	foreach(mixed key, mixed val in irn_connections){
	    this_object()->close_connection(irn_connections[key]["fd"]);
	}
	foreach(mixed key, mixed val in irn_sockets){
	    this_object()->close_connection(key);
	}
	irn_connections = ([]);
	irn_sockets = ([]);
    }
    foreach(string key, mixed val in routers){
	if(key == my_name){
	    map_delete(routers, key);
	    continue;
	}
	ok_ips += ({ routers[key]["ip"] });
	if(!irn_connections[key]){
	    irn_connections[key] = (["blocking" : 1, "type": MUD, "fd": -1, "data" :
	      0 ]);
	}
	else {
	    trr("sockstat on "+key+": "+identify(socket_status(irn_connections[key]["fd"])),"white");
	    if(socket_status(irn_connections[key]["fd"]) &&
	      socket_status(irn_connections[key]["fd"])[1] == "CLOSED")
		irn_connections[key]["fd"] = -1;
	    trr("sock staus on "+key+": "+irn_connections[key]["fd"],"white");
	}
    }

    foreach(string name in keys(routers)){
	int tmp_fd, sockstat;

	if(irn_connections[name]["fd"] != -1) continue;
	tmp_fd = socket_create(MUD, "irn_read_callback","irn_close_callback"); 
	if(tmp_fd < 0){
	    trr("irn: Couldn't create socket. errorcode: "+socket_error(tmp_fd));
	    return;
	}

	sockstat = socket_bind(tmp_fd, 0);
	//trr("socket_bind: "+sockstat);

	if(sockstat < 0){
	    trr("irn: Couldn't bind socket. errorcode: "+socket_error(sockstat));
	    return;
	}

	irn_connections[name]["fd"] = tmp_fd;
	irn_sockets[tmp_fd] = (["name" : name]);

	sockstat = socket_connect(irn_connections[name]["fd"], routers[name]["ip"]+" "+
	  routers[name]["port"], "irn_read_callback", "irn_write_callback");
	//trr("socket_connect: "+sockstat);

	if(sockstat < 0){
	    trr("irn: Couldn't connect to "+name+", errorcode: "+socket_error(sockstat));
	    this_object()->close_connection(irn_connections[name]["fd"]);
	    irn_connections[name]["fd"] = -1;
	}
	this_object()->Report();
	begin_socket_handoff(tmp_fd);
	call_out( "eventSendStartup", 5, tmp_fd);
    }
}

varargs void eventSendStartup(int fd){
    mixed *targets = ({});
    //trr("irn: hit eventSendStartup","yellow");
    if(!irn_enabled) return;
    //trr("irn: hit eventSendStartup","green");
    if(!fd) targets = keys(irn_connections);
    else if(irn_sockets[fd]["name"]) targets = ({ irn_sockets[fd]["name"] });
    else targets = keys(irn_connections);
    if(!sizeof(targets)){
	call_out( "irn_setup", 5, 1 );
	return;
    }
    this_object()->Report();
    foreach(mixed element in targets){
	trr("irn: sending statup to "+element+" on fd "+irn_connections[element]["fd"]);
	write_data(irn_connections[element]["fd"],
	  ({
	    "irn-startup-req",
	    5,
	    my_name,
	    "foo",
	    element,
	    "bar",
	    ([ "client_password" : my_password, "server_password" : routers[element]["password"] ])
	})
      );
}
}

static void irn_write_callback(int fd){
    //trr("hit irn_write_callback for fd"+fd);
}

static void irn_close_callback(int fd){
    if(!irn_enabled) return;
    irn_connections[id_mud(fd)]["fd"] = -1;
    map_delete(irn_sockets, fd);
    trr("I'm wanting to close "+id_mud(fd)+" on fd"+fd+" now.");
    call_out( "eventSendStartup", 5);
}

static void irn_read_callback(int fd, mixed data){
    int i;
    string tmp="";
    mapping MudList = ([]);
    if(!irn_enabled) return;
    if(this_object()->query_mudinfo())
	MudList = this_object()->query_mudinfo();
    else MudList = ([ "wt" : "f" ]);
    //trr("irn: hit irn_read_callback for fd"+fd+", "+id_mud(fd));
    if(!data || !sizeof(data)){
	//trr("irn: no data");
    }
    else {
	if(bufferp(data)){
	    //trr("data buffer: "+identify(read_buffer(data)));
	    for(i=0;i<sizeof(data);i++){
		tmp += sprintf("%c",data[i]);
	    }
	}
	else {
	    //trr("data: "+identify(data));
	}
    }
    //trr("tmp: "+tmp);
    //trr("mudinfo keys: "+identify(keys(MudList)));
    switch(data[0]){
    case "irn-startup-req" :
	trr("IRN got a startup request on fd"+fd);
	if(!GoodPeer(fd, data)) return;
	trr("IRN startup from "+data[2]+" accepted.");
	irn_connections[data[2]]["fd"] = fd;
	//trr("irn_connections[\""+data[2]+"\"][\"fd\"]: "+irn_connections[data[2]]["fd"]);
	irn_sockets[fd] = ([ "name" : data[2] ]);
    //SendList(MudList, fd);
    SendWholeList(fd);
    trr(data[2]+" has joined IRN on socket "+fd);
    return;
case "irn-mudlist-altered" :
    trr("irn: received irn-mudlist-altered");
    if(!ValidatePeer(fd, data)) {
	trr("irn: peer failed validation.");
	//this_object()->close_connection(fd);
	//if(irn_sockets[fd]) map_delete(irn_sockets, fd);
	//if(irn_connections[data[2]]) map_delete(irn_connections, data[2]);
	return;
    }
    //trr("got new delta. sending to router.");
    this_object()->ReceiveList(data[7],"mudlist");
    break;
case "irn-mudlist-delta" :
    trr("irn: got mudlist delta");
    if(!ValidatePeer(fd, data)) {
	trr("irn: peer failed validation.");
	//this_object()->close_connection(fd);
	//if(irn_sockets[fd]) map_delete(irn_sockets, fd);
	//if(irn_connections[data[2]]) map_delete(irn_connections, data[2]);
	return;
    }
    //trr("irn: got new delta. sending to router.");
    this_object()->ReceiveList(data[7],"mudlist");
    break;
case "irn-chanlist-altered" :
    trr("irn: received irn-chanlist-altered");
    if(!ValidatePeer(fd, data)) {
	trr("irn: peer failed validation.");
	//this_object()->close_connection(fd);
	//if(irn_sockets[fd]) map_delete(irn_sockets, fd);
	//if(irn_connections[data[2]]) map_delete(irn_connections, data[2]);
	return;
    }
    //trr("got new delta. sending to router.");
    this_object()->ReceiveList(data[7],"chanlist");
    break;
case "irn-chanlist-delta" :
    trr("irn: got chanlist delta");
    if(!ValidatePeer(fd, data)) {
	trr("irn: peer failed validation.");
	//this_object()->close_connection(fd);
	//if(irn_sockets[fd]) map_delete(irn_sockets, fd);
	//if(irn_connections[data[2]]) map_delete(irn_connections, data[2]);
	return;
    }
    //trr("irn: got new delta. sending to router.");
    this_object()->ReceiveList(data[7],"chanlist");
    break;
case "irn-data" :
    if(!ValidatePeer(fd, data)) {
	trr("irn-data: peer on fd"+fd+" failed validation.");
	//this_object()->close_connection(fd);
	//if(irn_sockets[fd]) map_delete(irn_sockets, fd);
	//if(irn_connections[data[2]]) map_delete(irn_connections, data[2]);
	return;
    }
    //trr("irn: got new data, type: "+data[0]+". sending to router.");
    if(convert_channel && !strsrch(data[6][0],"chan") && strsrch(data[6][0],"chan-user")){
	if(chan_conv[irn_sockets[fd]["name"]]){
	    foreach(string key, string val in chan_conv[irn_sockets[fd]["name"]]){
		//trr("data[6][6]: "+data[6][6]);
		data[6][6] = replace_string(data[6][6],key,val);
	    }
	}
    } 
    this_object()->read_callback(data[6][2],data[6]);
    break;
default : 
    if(!ValidatePeer(fd, data)) {
	//this_object()->close_connection(fd);
	//if(irn_sockets[fd]) map_delete(irn_sockets, fd);
	//if(irn_connections[data[2]]) map_delete(irn_connections, data[2]);
	return;
    }
    //trr("irn: got i3 data, type: "+data[1][0]+". sending to router.");
    this_object()->read_callback(data[6][2],data[6]);
    return;
}
}

static varargs void SendList(mixed data, int fd, string type){
    int *targets = ({});
    mixed *outbound = ({});
    mapping tmp = ([]);
    if(!type || !sizeof(type)) type = "irn-mudlist-delta";
    if(type == "mudlist") type = "irn-mudlist-delta";
    if(type == "chanlist") type = "irn-chanlist-delta";
    //trr("list type: "+type);
    //trr("irn: trying to send list","white");
    if(!irn_enabled) return;
    if(!mapp(data)){
	//trr("irn: Tried to send a non-map. guilty stack: "+get_stack(),"cyan");
	return;
    }
    //trr("1");
    tmp = data;
    if(mapp(tmp)){
	foreach(mixed key, mixed val in tmp){
	    if(!mapp(val)){
		//trr("irn: Non mapping val. Key: "+key+". stack: "+get_stack(),"yellow");
		//trr("irn: guilty stack: "+get_stack(),"cyan");
	    }
	}
    }
    //trr("2");
    if(!fd) targets = keys(irn_sockets);
    else targets = ({ fd });
    foreach(int router in targets){
	//trr("3");
	if(type == "irn-chanlist-delta"){
	    //print_long_string(find_player("cratylus"),"I'm trying to send this: "+identify(data)+" to "+router);
	    //continue;
	}
	write_data(router, ({
	    type,
	    5,
	    my_name,
	    0,
	    irn_sockets[router]["name"],
	    0,
	    time(),
	    data
	  }) );
    }
}

varargs void SendWholeList(int fd, string type){
    mapping tmp = ([]);
    if(!type || !sizeof(type)) type = "mudlist";
    if(!irn_enabled) return;
    if(!fd) fd = 0;
    //trr("SendWholeList type: "+type);
    switch(type){
    case "mudlist" :
	tmp = this_object()->query_mudinfo();
	break;
    case "chanlist" :
	tmp = this_object()->query_chaninfo();
	break;
    default :
	trr("irn SendWholeList error: bad type "+type);
	return;
    }
    foreach(mixed key, mixed val in tmp){
	//trr("irn: Sending "+key,"white");
	if(type == "mudlist") if(mapp(val)) SendList( ([ key : val ]), fd, type );
	if(type == "chanlist"){
	    foreach(mixed key2, mixed val2 in val){
		SendList( ([ key : ([ key2 : val2 ]) ]), fd, type );
	    }
	}
    }
}

static void SendMessage(mixed data){
    //trr("irn: received SendMessage call","blue");
    if(!irn_enabled) return;
    foreach(int router in keys(irn_sockets)){
	if(convert_channel && !strsrch(data[0],"chan") && strsrch(data[0],"chan-user")){
	    if(member_array(irn_sockets[router]["name"], keys(chan_conv)) != -1){
		foreach(string key, string val in chan_conv[irn_sockets[router]["name"]]){
		    //trr("data[6]: "+data[6]);
		    data[6] = replace_string(data[6],val,key);
		    //trr("data: "+identify(data));
		}
	    }
	}
	write_data(router, ({
	    "irn-data",
	    5,
	    my_name,
	    0,
	    "*",
	    0,
	    data
	  }) );
    }
}

string Report(){
    string ret = "IRN: connections: ";
    foreach(mixed key, mixed val in irn_connections){
	if(!key) continue;
	ret += key+":"+irn_connections[key]["fd"]+" ";
    }
    ret += "\n";
    ret += "IRN: sockets: ";
    foreach(mixed key, mixed val in irn_sockets){
	int sstat = -1;
	string statmess = "DISCONNECTED";
	if(irn_connections[val["name"]]){
	    sstat = irn_connections[val["name"]]["fd"];
	    if(sstat > 0 && socket_status(key)) statmess = socket_status(key)[1];
	    ret += key +":"+val["name"]+":"+statmess+" ";
	}
	ret += "\n";
    }
    trr("irn: irn_connections: "+identify(irn_connections),"green");
    trr("irn: irn_sockets: "+identify(irn_sockets),"cyan");
    foreach(mixed element in keys(irn_sockets)){
	trr("socket_status("+element+"): "+identify(socket_status(element)),"green");
    }
    return ret;
}

static void begin_socket_handoff(int fd){
    object rsock = find_object(RSOCKET_D);
    if(!irn_enabled) return;
    if(!rsock) rsock = load_object(RSOCKET_D);
    if(!rsock){
	trr("IRN: Socket handoff failed. Couldn't load RSOCKET_D");
	return;
    }
    socket_release(fd, rsock, "complete_socket_handoff");
}
