/*
 * Minbif - IRC instant messaging gateway
 * Copyright(C) 2009 Romain Bignon
 *
 * 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, version 2 of the License.
 *
 * 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.
 */

#include <iostream>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <glib/gmain.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include "daemon_fork.h"
#include "../config.h"
#include "irc/irc.h"
#include "irc/user.h"
#include "irc/message.h"
#include "irc/replies.h"
#include "../config.h"
#include "../callback.h"
#include "../log.h"
#include "../minbif.h"
#include "../util.h"
#include "../sock.h"

DaemonForkServerPoll::DaemonForkServerPoll(Minbif* application)
	: ServerPoll(application),
	  irc(NULL),
	  read_cb(NULL)
{
	std::vector<ConfigSection*> sections = conf.GetSection("irc")->GetSectionClones("daemon");
	if(sections.empty())
	{
		b_log[W_ERR] << "Missing section irc/daemon";
		throw ServerPollError();
	}

	ConfigSection* section = sections[0];
	const char* bind_addr = section->GetItem("bind")->String().c_str();
	uint16_t port = (uint16_t)section->GetItem("port")->Integer();

	if(section->GetItem("background")->Boolean())
	{
		int r = fork();
		if(r < 0)
		{
			b_log[W_ERR] << "Unable to start in background: " << strerror(errno);
			exit(EXIT_FAILURE);
		}
		else if(r > 0)
			exit(EXIT_SUCCESS);
		chdir("/");
		if(isatty(0)) close(0);
		if(isatty(1)) close(1);
		if(isatty(2)) close(2);
	}

	static struct sockaddr_in fsocket;
	fsocket.sin_family = AF_INET;
	fsocket.sin_addr.s_addr = inet_addr(bind_addr);
	fsocket.sin_port = htons(port);

	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(sock < 0)
	{
		b_log[W_ERR] << "Unable to create a socket: " << strerror(errno);
		throw ServerPollError();
	}

	unsigned int reuse_addr = 1;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof reuse_addr);

	if(bind(sock, (struct sockaddr *) &fsocket, sizeof fsocket) < 0 ||
	   listen(sock, 5) < 0)
	{
		b_log[W_ERR] << "Unable to listen on " << bind_addr << ":" << port
			     << ": " << strerror(errno);
		throw ServerPollError();
	}

	read_cb = new CallBack<DaemonForkServerPoll>(this, &DaemonForkServerPoll::new_client_cb);
	read_id = glib_input_add(sock, (PurpleInputCondition)PURPLE_INPUT_READ,
				       g_callback_input, read_cb);
}

DaemonForkServerPoll::~DaemonForkServerPoll()
{
	if(read_id >= 0)
		g_source_remove(read_id);
	delete read_cb;
	if(sock >= 0)
		close(sock);

	delete irc;

	for(vector<child_t*>::iterator it = childs.begin(); it != childs.end(); ++it)
	{
		close((*it)->fd);
		delete (*it)->read_cb;
		g_source_remove((*it)->read_id);
		delete *it;
	}
}

bool DaemonForkServerPoll::new_client_cb(void*)
{
	struct sockaddr_in newcon;
	socklen_t addrlen = sizeof newcon;
	int new_socket = accept(sock, (struct sockaddr *) &newcon, &addrlen);

	if(new_socket < 0)
	{
		b_log[W_WARNING] << "Could not accept new connection: " << strerror(errno);
		return true;
	}

	int fds[2];
	if(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1)
	{
		b_log[W_WARNING] << "Unable to create IPC socket for client: " << strerror(errno);
		fds[0] = fds[1] = -1;
	}
	sock_make_nonblocking(fds[0]);
	sock_make_nonblocking(fds[1]);

	b_log[W_INFO] << "fd0: " << fds[0];
	b_log[W_INFO] << "fd1: " << fds[1];

	pid_t client_pid = fork();

	if(client_pid < 0)
	{
		b_log[W_ERR] << "Unable to fork while receiving a new connection: " << strerror(errno);
		return true;
	}
	else if(client_pid > 0)
	{
		/* Parent */
		b_log[W_INFO] << "Creating new process with pid " << client_pid;
		close(new_socket);
		if(fds[0] >= 0)
		{
			child_t* child = new child_t();
			child->fd = fds[0];
			child->read_cb = new CallBack<DaemonForkServerPoll>(this, &DaemonForkServerPoll::ipc_read, child);
			child->read_id = glib_input_add(fds[0], (PurpleInputCondition)PURPLE_INPUT_READ,
						       g_callback_input, read_cb);
			childs.push_back(child);
			close(fds[1]);
		}
	}
	else
	{
		/* Child */
		close(sock);
		sock = -1;
		g_source_remove(read_id);
		read_id = -1;
		delete read_cb;
		read_cb = NULL;

		if(fds[1] >= 0)
		{
			sock = fds[1];
			read_cb = new CallBack<DaemonForkServerPoll>(this, &DaemonForkServerPoll::ipc_read);
			read_id = glib_input_add(sock, (PurpleInputCondition)PURPLE_INPUT_READ,
						       g_callback_input, read_cb);
			close(fds[0]);
			for(vector<child_t*>::iterator it = childs.begin(); it != childs.end();)
			{
				child_t* child = *it;
				close(child->fd);
				delete child->read_cb;
				g_source_remove(child->read_id);
				it = childs.erase(it);
			}
		}

		try
		{
			irc = new irc::IRC(this, new_socket,
				      conf.GetSection("irc")->GetItem("hostname")->String(),
				      conf.GetSection("irc")->GetItem("ping")->Integer());
		}
		catch(irc::AuthError &e)
		{
			b_log[W_ERR] << "Unable to start the IRC daemon";
			getApplication()->quit();
		}
	}
	return true;
}

DaemonForkServerPoll::ipc_cmds_t DaemonForkServerPoll::ipc_cmds[] = {
	{ MSG_WALLOPS,    &DaemonForkServerPoll::m_wallops },
};

void DaemonForkServerPoll::m_wallops(child_t* child, irc::Message m)
{
	if(child)
		ipc_master_broadcast(m);
	else if(irc)
		irc->getUser()->send(irc::Message(MSG_WALLOPS).setSender(m.getArg(0))
				                         .addArg(m.getArg(1)));
}

bool DaemonForkServerPoll::ipc_read(void* data)
{
	child_t* child = NULL;
	if(data)
		child = static_cast<child_t*>(data);

	static char buf[512];
	ssize_t r;
	int fd;
	if(child)
		fd = child->fd;
	else
		fd = sock;
	if((r = recv(fd, buf, sizeof buf - 1, MSG_PEEK)) <= 0)
	{
		b_log[W_ERR] << "Error while receiving IPC data";
		if(r < 0 && sockerr_again())
			return true;
		if(child)
		{
			for(vector<child_t*>::iterator it = childs.begin(); it != childs.end();)
				if(child->fd == (*it)->fd)
					it = childs.erase(it);
				else
					++it;

			g_source_remove(child->read_id);
			delete child->read_cb;
			delete child;
		}
		else
		{
			g_source_remove(read_id);
			read_id = -1;
			delete read_cb;
			read_cb = NULL;
			sock = -1;
		}
		return false;
	}
	buf[r] = 0;
	b_log[W_INFO] << "Received " << buf;
	irc::Message m = irc::Message::parse(buf);

	unsigned i = 0;
	for(; i < (sizeof ipc_cmds / sizeof *ipc_cmds) && m.getCommand() != ipc_cmds[i].cmd; ++i)
		;

	if(i >= (sizeof ipc_cmds / sizeof *ipc_cmds))
		return true;

	(this->*ipc_cmds[i].func)(child, m);

	return true;
}

void DaemonForkServerPoll::ipc_master_send(child_t* child, const irc::Message& m)
{
	string msg = m.format();
	b_log[W_INFO] << "Master sent " << msg;
	if(write(child->fd, msg.c_str(), msg.size()) <= 0)
		b_log[W_ERR] << "Error while sending: " << strerror(errno);
}

void DaemonForkServerPoll::ipc_master_broadcast(const irc::Message& m)
{
	for(vector<child_t*>::iterator it = childs.begin(); it != childs.end(); ++it)
		ipc_master_send(*it, m);
}

void DaemonForkServerPoll::ipc_child_send(const irc::Message& m)
{
	string msg = m.format();
	b_log[W_INFO] << "Child sent to " << sock << ": " << msg;
	if(write(sock, msg.c_str(), msg.size()) <= 0)
		b_log[W_ERR] << "Error while sending: " << strerror(errno);
}

bool DaemonForkServerPoll::ipc_send(const irc::Message& m)
{
	if(irc)
		ipc_child_send(m);
	else
		ipc_master_broadcast(m);
	return true;
}

void DaemonForkServerPoll::log(size_t level, string msg) const
{
	if(msg.find("\n") != string::npos)
		msg = msg.substr(0, msg.find("\n"));

	string cmd = MSG_NOTICE;
	if(level & W_DEBUG)
		cmd = MSG_PRIVMSG;

	if(irc)
		irc->getUser()->send(irc::Message(cmd).setSender(irc)
							     .setReceiver(irc->getUser())
							     .addArg(msg));
	else
		std::cout << msg << std::endl;
}

void DaemonForkServerPoll::rehash()
{
	if(irc)
		irc->rehash();
}

void DaemonForkServerPoll::kill(irc::IRC* irc)
{
	assert(!irc || irc == this->irc);

	_CallBack* stop_cb = new CallBack<DaemonForkServerPoll>(this, &DaemonForkServerPoll::stopServer_cb);
	g_timeout_add(0, g_callback_delete, stop_cb);
}

bool DaemonForkServerPoll::stopServer_cb(void*)
{
	delete irc;
	irc = NULL;

	getApplication()->quit();
	return false;
}

