/**
 **	File ......... MinionSocket.cpp
 **	Published ....  2004-04-17
 **	Author ....... grymse@alhem.net
**/
/*
Copyright (C) 2004  Anders Hedstrom

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#ifdef _WIN32
#pragma warning(disable:4786)
#endif
#ifdef HAVE_OPENSSL

#include <stdio.h>
#include <string>
#include <vector>
#include <map>
#include <ctype.h>

#include "socket_include.h"
#include "Parse.h"
#include "MinderHandler.h"
#include "Uid.h"
#include "Utility.h"
#include "MinionSocket.h"

using std::string;

#ifdef _DEBUG
#define DEB(x) x
#else
#define DEB(x) 
#endif



// statics
//MinionSocket::connect_v MinionSocket::m_connect;


MinionSocket::MinionSocket(SocketHandler& h) : CTcpSocket(h)
,m_remote_id("")
,m_ip(0)
,m_port(0)
,m_bIDVerified(false)
,m_bStopMessage(false)
,m_messagecount(0)
,m_seencount(0)
,m_remote_host_id(0)
{
	SetLineProtocol();
}


MinionSocket::MinionSocket(SocketHandler& h,const std::string& id,ipaddr_t l,port_t s)
:CTcpSocket(h)
,m_remote_id(id)
,m_ip(l)
,m_port(s)
,m_bIDVerified(true)
,m_bStopMessage(false)
,m_messagecount(0)
,m_seencount(0)
,m_remote_host_id(0)
{
	SetLineProtocol();

	std::string str;
	l2ip(l,str);
DEB(	printf(" new connect: %s:%d\n",str.c_str(),s);)
}


MinionSocket::~MinionSocket()
{
}


/*
ICrypt *MinionSocket::AllocateCrypt()
{
	return new HCrypt;
}
*/


void MinionSocket::OnDelete()
{
	// %! avregistrera i connection-lista
//	Remove(m_remote_id,m_ip,m_port);
	SendConnectList();
}


void MinionSocket::OnConnect()
{
//	printf("Connected to: %s\n",GetRemoteAddress().c_str());
	SendHello("Hello");
}


void MinionSocket::SendHello(const std::string& cmd)
{
	std::string str;
	l2ip(my_ip,str);
	char msg[200];
	sprintf(msg,"%s_%s:%s:%d:%s:%ld\n",cmd.c_str(),m_remote_id.c_str(),str.c_str(),my_port,
		static_cast<MinderHandler&>(Handler()).GetID().c_str(),
		static_cast<MinderHandler&>(Handler()).GetHostId() );
	{
		Uid ruid(m_remote_id);
//printf("encrypt with remote id '%s'\n",m_remote_id.c_str());
		memcpy(GetKey_m2minion() + 8,ruid.GetBuf(),16);
//		Send( Utility::base64(msg) + "\n" );
		Send( encrypt(GetKey_m2minion(), msg) + "\n" );
	}
}


void MinionSocket::SendConnectList()
{
	static_cast<MinderHandler&>(Handler()).SendConnectList();
}


void MinionSocket::OnLine(const std::string& line)
{
	std::string line_decrypt;
	std::string cmd;
	Uid myid(static_cast<MinderHandler&>(Handler()).GetID());

	memcpy(GetKey_m2minion() + 8,myid.GetBuf(),16);
	if (!decrypt(GetKey_m2minion(),line,line_decrypt))
	{
		SetCloseAndDelete();
		return;
	}
//	Parse pa(decrypt(GetKey_m2minion(),line),"_:");
	Parse pa(line_decrypt, "_:");

	m_messagecount++;
	pa.getword(cmd);
	if (!m_bIDVerified)
	{
		if (cmd == "Hello")
		{
			std::string id; // supposed to be my id
			std::string ipstr; // remote ip
			std::string remote_id;
			pa.getword(id);
			pa.getword(ipstr);
			port_t port = (port_t)pa.getvalue(); // remote listen port
			pa.getword(remote_id);
			long remote_host_id = pa.getvalue();
			ipaddr_t ip;
			int max = GetMaxConnections(); //atoi(config["max_connections"].c_str());
			max = (max == 0) ? 4 : max;

			u2ip(ipstr, ip);

			if (id == static_cast<MinderHandler&>(Handler()).GetID() &&
				!static_cast<MinderHandler&>(Handler()).FindMinion(remote_id) &&
				static_cast<MinderHandler&>(Handler()).Count() < max * 2 + 1)
			{
DEB(				printf("My ID verified!\n");)
				m_remote_id = remote_id;
				m_ip = ip; // remote ip
				m_port = port; // remote listen port
				m_bIDVerified = true;
				m_remote_host_id = remote_host_id;
				SendHello("Hi");
			}
			else
			{
				{
					Uid ruid(remote_id);
					memcpy(GetKey_m2minion() + 8,ruid.GetBuf(),16);
					Send( encrypt(GetKey_m2minion(), "Bye") + "\n" );
				}
				SetCloseAndDelete(true);
			}
		}
		else
		{
DEB(			fprintf(stderr,"Ignoring message\n");)
		}
	}
	else
	{
		if (!OnVerifiedLine(cmd, pa))
		{
			// %! notify?
			SetCloseAndDelete();
		}
	}
}

bool MinionSocket::OnVerifiedLine(const std::string& cmd,Parse& pa)
{
	std::string str;

	if (static_cast<MinderHandler&>(Handler()).Debug() )
	{
		str = "&l&fCommand: " + cmd + "&n\n";
		Notify( str );
	}

DEB(		printf("Incoming command: '%s'\n",cmd.c_str());)
	if (cmd == "Tip")
	{
		// Tip _ host id
		std::string hid = pa.getword();
//printf("Tip from: %s\n",hid.c_str());
		static_cast<MinderHandler&>(Handler()).SendTop( hid );
	}
	else
	if (cmd == "Top")
	{
		unsigned long hid = pa.getvalue();
//printf("Top to: %ld\n",hid);
		if (hid == static_cast<MinderHandler&>(Handler()).GetHostId())
		{
			std::string src = pa.getword();
			std::string vstr = Utility::base64d(pa.getword());
			std::string os = pa.getword();
//printf(" (Top from: %s)\n",src.c_str());
			std::string dst;
			if (isdigit(os[0]))
			{
				dst = os;
				os = "";
			}
			else
			{
				dst = pa.getword();
			}
			FILE *fil = fopen("top_map.dot","at");
			if (!fil)
				fil = fopen("top_map.dot","wt");
			if (fil)
			{
				fprintf(fil,"\t\"%s\" [label=\"%s\\n%s\\n%s\"]\n",
					src.c_str(),
					src.c_str(),
					vstr.c_str(),
					os.c_str());
				while (dst.size())
				{
					fprintf(fil,"\t\"%s\" -> \"%s\"\n",
						src.c_str(),
						dst.c_str());
					//
					pa.getword(dst);
				}
				fclose(fil);
			}
		}
		else
		{
//printf("Top ignored: %ld != %ld\n",hid,static_cast<MinderHandler&>(Handler()).GetHostId());
		}
	}
	else
	if (cmd == "Accept")
	{
		long hid = pa.getvalue();
		long mid = pa.getvalue();
		std::string msg;
		if (static_cast<MinderHandler&>(Handler()).StoreGet(hid,mid,msg))
		{
			Uid ruid(m_remote_id);
			memcpy(GetKey_m2minion() + 8,ruid.GetBuf(),16);
			Send( encrypt(GetKey_m2minion(), msg) + "\n" );
		}
	}
	else
	if (cmd == "Try")
	{
		long hid = pa.getvalue();
		long mid = pa.getvalue();
		if (!static_cast<MinderHandler&>(Handler()).Seen(hid,mid,true))
		{
			std::string msg;
			msg = "Accept_" + Utility::l2string(hid);
			msg += ":" + Utility::l2string(mid);
			Uid ruid(m_remote_id);
			memcpy(GetKey_m2minion() + 8,ruid.GetBuf(),16);
			Send( encrypt(GetKey_m2minion(), msg) + "\n" );
		}
		else
		{
			m_seencount++;
		}
	}
	else
	if (cmd == "KeepAlive")
	{
		// good for you
		m_bStopMessage = true;
	}
	else
	if (cmd == "ConnectList")
	{
		std::string id;
		while (m_clist.size())
		{
			std::list<std::string>::iterator it = m_clist.begin();
			m_clist.erase(it);
		}
		pa.getword(id);
		while (id.size())
		{
			m_clist.push_back(id);
			//
			pa.getword(id);
		}
	}
	else
	if (cmd == "Message")
	{
		std::string hid = pa.getword();
		std::string mid = pa.getword();
		int ttl = pa.getvalue();
		std::string msg_in = pa.getword();
		unsigned long host_id = atol(hid.c_str());
		unsigned long message_id = atol(mid.c_str());
		ulong_v hosts;

		if (!static_cast<MinderHandler&>(Handler()).Seen(host_id,message_id))
		{
DEB(				printf("Message\n hostid: %ld\n messageid: %ld\n ttl: %d\n message:\n%s\n--end of message\n", host_id, message_id, ttl, msg_in.c_str());)
DEB(				printf("Message:\n%s\n--End of Message\n",Utility::base64d(msg_in).c_str());)
			if (static_cast<MinderHandler&>(Handler()).Debug() )
			{
				str = "&l&fMessage: Not Seen&n\n";
				Notify( str );
			}
			std::string msg = Utility::base64d(msg_in);
			m_bStopMessage = false;
			{
				Uid myid(static_cast<MinderHandler&>(Handler()).GetID());
				memcpy(GetKey_m2minion() + 8,myid.GetBuf(),16);
				OnLine( encrypt(GetKey_m2minion(), msg ) );
			}
			if (!m_bStopMessage && ttl--)
			{
				while (host_id > 0)
				{
					hosts.push_back(host_id);
					//
					host_id = pa.getvalue();
				}
//				Parse pa(msg,"_");
//				std::string cmd = pa.getword();
//				if (cmd == "Pong")
				// always add host id to message before forwarding
				{
					msg += ":" + Utility::l2string(static_cast<MinderHandler&>(Handler()).GetHostId());
					msg_in = Utility::base64(msg);
				}
				static_cast<MinderHandler&>(Handler()).SendMessage(hid, mid, ttl, msg_in, m_clist, hosts);
			}
		}
		else
		{
			m_seencount++;
			if (static_cast<MinderHandler&>(Handler()).Debug() )
			{
				str = "&l&fMessage: Seen&n\n";
				Notify( str );
			}
		}
	}
	else
	if (cmd == "Hi")
	{
		SendConnectList();
	}
	else
	if (cmd == "Bye")
	{
		SetCloseAndDelete(true);
	}
	else
	{
		return false;
	}
	return true;
}


void MinionSocket::OnAccept()
{
DEB(	printf("Incoming connection from: %s\n",GetRemoteAddress().c_str());)
}


#endif // HAVE_OPENSSL


syntax highlighted by Code2HTML, v. 0.9.1