/*  $Id: irc_connection_generic.cpp,v 1.10 2004/01/10 12:14:44 mbn Exp $
**
**  ClanLib Game SDK
**  Copyright (C) 2003  The ClanLib Team
**  For a total list of contributers see the file CREDITS.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This library 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
**  Lesser General Public License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
*/

#include "irc_connection_generic.h"
#include "API/Core/System/event_listener.h"
#include "API/Core/System/error.h"
#include "API/Core/System/log.h"

/////////////////////////////////////////////////////////////////////////////
// CL_IRCConnection_Generic construction:

CL_IRCConnection_Generic::CL_IRCConnection_Generic(const std::string &server, const std::string &port) :
	sock(CL_Socket::tcp), signal_error(false), ref_count(0)
{
	sock.connect(CL_IPAddress(server, port));

	thread = CL_Thread(this);
	thread.start();
}

CL_IRCConnection_Generic::~CL_IRCConnection_Generic()
{
	thread.wait();
}

/////////////////////////////////////////////////////////////////////////////
// CL_IRCConnection_Generic attributes:


/////////////////////////////////////////////////////////////////////////////
// CL_IRCConnection_Generic operations:

void CL_IRCConnection_Generic::add_ref()
{
	ref_count++;
}

void CL_IRCConnection_Generic::release_ref()
{
	ref_count--;
	if (ref_count == 0) delete this;
}

/////////////////////////////////////////////////////////////////////////////
// CL_IRCConnection_Generic implementation:

void CL_IRCConnection_Generic::run()
{
	try
	{
		std::string last_line;

		char buffer[512];
		while (true)
		{
			CL_EventListener events;
			events.add_trigger(sock.get_read_trigger());
			events.add_trigger(&quit_trigger);
			events.wait();
			if (quit_trigger.get_flag()) break;
			
			int received = sock.recv(buffer, 512);
			if (received == 0) break;
		
			int start = 0;
			for (int i=0; i<received; i++)
			{
				if (buffer[i] == '\n')
				{
					last_line.append(buffer+start, i-start+1);
					start = i+1;

	 				CL_MutexSection mutex_lock(&mutex);
					received_queue.push(last_line);
				
					last_line = std::string();
				}
			}
			last_line.append(buffer+start, received-start);
		}
	}
	catch (CL_Error error)
	{
		signal_error = true;
		error_message = error.message;
	}
}
	
void CL_IRCConnection_Generic::keep_alive()
{
	CL_MutexSection mutex_lock(&mutex);
	while (!received_queue.empty())
	{
		std::string line = received_queue.front();
		received_queue.pop();
		
		// Parse line:
		std::string prefix, command;
		std::vector<std::string> args;
		line = line.substr(0, line.length()-2); // cut off CR-LF
		line.append(" "); // whitespace at end. Makes parsing easier.

		// Find low level message escapes:
		std::string::size_type pos = 0;
		pos = 0;
		while (pos != std::string::npos)
		{
			pos = line.find('\020', pos);
			if (pos == std::string::npos || pos+1 == std::string::npos) break;
			switch (line[pos+1])
			{
			case '\020':
				line.replace( pos, 2, 1, '\020' );
				break;

			case '0':
				line.replace( pos, 2, 1, '\0' );
				break;

			case 'n':
				line.replace( pos, 2, 1, '\n' );
				break;

			case 'r':
				line.replace( pos, 2, 1, '\r' );
				break;
			};
			pos++;
		}

		// Split line prefix and command:
		pos = 0;
		std::string::size_type endpos;
		if (line[0] == ':') // prefix
		{
			pos = line.find(" ", 1)+1;
			prefix = line.substr(1, pos-2);
		}
		
		endpos = line.find(" ", pos);
		command = line.substr(pos, endpos-pos);
		pos = endpos+1;
		
		while (pos != line.length())
		{
			if (line[pos] == ':')
			{
				args.push_back(line.substr(pos+1, line.length()-pos-2));
				break;
			}
		
			endpos = line.find(" ", pos);
			args.push_back(line.substr(pos, endpos-pos));
			pos = endpos+1;
		}
		
		// Handle command:
		int numeric_value = atoi(command.c_str());
		if (numeric_value != 0)
		{
			sig_numeric_reply(prefix, numeric_value, args);
			
			if (numeric_value == RPL_NAMREPLY)
			{
				const std::string &self = args[0];
				const std::string &channel = args[2];
				const std::string &users = args[3];
				std::vector<std::string> nicks;

				std::string::size_type pos = 0;
				while (pos < users.length())
				{
					std::string::size_type new_pos = users.find(" ", pos);
					if (new_pos == users.npos) new_pos = users.length();
					nicks.push_back(users.substr(pos, new_pos-pos));
					pos = new_pos+1;
				}
				
				sig_name_reply(self, channel, nicks);
			}
			
			continue;
		}
		
		sig_command_received(prefix, command, args);

		int num_args = args.size();
		if (command == "NICK" && num_args == 1) sig_nick(prefix, args[0]);
		else if (command == "JOIN" && num_args == 1) sig_join(prefix, args[0]);
		else if (command == "PART" && num_args >= 1)
		{
			if (num_args == 1) // No part reason
			{
				std::string empty;
				sig_part(prefix, args[0], empty);
			}
			else // User made comment about why it left
			{
				sig_part(prefix, args[0], args[1]);
			}
		}
		else if (command == "MODE")
		{
			std::vector<std::string> params;
			for (int i=2; i<num_args; i++) params.push_back(args[i]);
			sig_mode(prefix, args[0], args[1], params);
		}
		else if (command == "TOPIC" && num_args == 2) sig_topic(prefix, args[0], args[1]);
		else if (command == "INVITE" && num_args == 2) sig_invite(prefix, args[0], args[1]);
		else if (command == "KICK" && num_args == 2) sig_kick(prefix, args[0], args[1], "");
		else if (command == "KICK" && num_args == 3) sig_kick(prefix, args[0], args[1], args[2]);
		else if (command == "PRIVMSG" && num_args == 2)
		{
			if (args[1][0] == '\001') // ctcp
			{
				std::string line = args[1].substr(1, args[1].length()-2);

				// Find ctcp level message escapes:
				std::string::size_type pos = 0;
				pos = 0;
				while (pos != std::string::npos)
				{
					pos = line.find('\\', pos);
					if (pos == std::string::npos || pos+1 == std::string::npos) break;
					switch (line[pos+1])
					{
					case '\\':
						line.replace( pos, 2, 1, '\\' );
						break;

					case 'a':
						line.replace( pos, 2, 1, '\001' );
						break;
					};
					pos++;
				}

				std::string command, data;
				std::string::size_type data_pos = line.find(' ');
				if (data_pos == std::string::npos)
				{
					command = line;
				}
				else
				{
					command = line.substr(0, data_pos);
					data = line.substr(data_pos+1);
				}

				sig_privmsg_ctcp(prefix, args[0], command, data);
			}
			else
			{
				sig_privmsg(prefix, args[0], args[1]);
			}
		}
		else if (command == "NOTICE" && num_args == 2)
		{
			if (args[1][0] == '\001') // ctcp
			{
				std::string line = args[1].substr(1, args[1].length()-2);

				// Find ctcp level message escapes:
				std::string::size_type pos = 0;
				pos = 0;
				while (pos != std::string::npos)
				{
					pos = line.find('\\', pos);
					if (pos == std::string::npos || pos+1 == std::string::npos) break;
					switch (line[pos+1])
					{
					case '\\':
						line.replace( pos, 2, 1, '\\' );
						break;

					case 'a':
						line.replace( pos, 2, 1, '\001' );
						break;
					};
					pos++;
				}

				std::string command, data;
				std::string::size_type data_pos = line.find(' ');
				if (data_pos == std::string::npos)
				{
					command = line;
				}
				else
				{
					command = line.substr(0, data_pos);
					data = line.substr(data_pos+1);
				}

				sig_notice_ctcp(prefix, args[0], command, data);
			}
			else
			{
				sig_notice(prefix, args[0], args[1]);
			}
		}
		else if (command == "PING" && num_args == 1) sig_ping(args[0], "");
		else if (command == "PING" && num_args == 2) sig_ping(args[0], args[1]);
		else sig_unknown_command_received(prefix, command, args);
	}
	
	if (signal_error)
	{
		sig_socket_error(error_message);
		signal_error = false;
	}
}
