/*
 * Thread to inject packets to a network
 *
 * Copyright (C) 2003--2007  Enrico Zini <enrico@debian.org>
 *
 * 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
 */

#include "util/netsender.h"
#include "util/output.h"

#include <wibble/sys/thread.h>
#include <wibble/sys/mutex.h>

extern "C" {
#include <libnet.h>
}

#include <list>
#include <queue>

//#define DEBUG(args...) fprintf(stderr, ##args)
#define DEBUG(args...) do {} while(0)

using namespace std;
using namespace wibble::sys;

namespace util {

static NetSender* instance = 0;
static bool requested = false;

void NetSender::configure(const std::string& iface)
{
	if (instance)
	{
		delete instance;
		instance = 0;
	}
	instance = new NetSender(iface);
}

NetSender& NetSender::get()
{
	if (!requested)
	{
		// Don't start unless something needs it
		util::Starter::get().add(instance, 200);
		requested = true;
	}
	return *instance;
}

NetSender::NetSender(const string& iface)
	: quitRequested(false), iface(iface), local_hardware_addr(0), ln_context(0)
{
}


NetSender::~NetSender()
{
	try {
		requestQuit();
		DEBUG("NS~-Canceled\n");
	} catch (wibble::exception::System& e) {
		warning("%s in NetSender destructor\n", e.what());
	}

	DEBUG("LN-Deleting %p\n", ln_context);
	try {
		if (ln_context)
			libnet_destroy(ln_context);
	} catch (exception& e) {
		DEBUG(e.what());
	}
	DEBUG("LN-Deleted\n");
}

void NetSender::startableStart()
{
	debug("Starting net sender\n");
	if (!(ln_context = libnet_init(LIBNET_LINK_ADV, const_cast<char*>(iface.c_str()), ln_errbuf)))
		throw wibble::exception::Libnet(ln_errbuf, "opening link interface");
	start();
}


struct libnet_ether_addr* NetSender::getMACAddress()
{
	MutexLock lock(macMutex);

	if (!local_hardware_addr)
		if (!(local_hardware_addr = libnet_get_hwaddr(ln_context)))
			throw wibble::exception::Libnet(ln_context, "trying to determine local hardware address");

	return local_hardware_addr;
}

libnet_t* NetSender::getLibnetContext() { return ln_context; }

void NetSender::post(Buffer packet)
{
	MutexLock lock(pktMutex);
	immediatePackets.push(packet);
	pktCond.broadcast();
}

void NetSender::post(Buffer packet, int msinterval, int mstimeout)
{
	post(packet);
	for (int i = msinterval; i < mstimeout; i += msinterval)
		post(packet, i);
}

void NetSender::post(Buffer packet, int delay)
{
	MutexLock lock(pktMutex);
	
	//int totDelay = 0;
	for (list<TimedPacket>::iterator i = scheduledPackets.begin();
			i != scheduledPackets.end(); i++)
	{
		if (delay < i->delay)
		{
			scheduledPackets.insert(i, TimedPacket(delay, packet));
			i->delay -= delay;
			pktCond.broadcast();
			return;
		}
		delay -= i->delay;
	}
	scheduledPackets.push_back(TimedPacket(delay, packet));
	pktCond.broadcast();
}

void NetSender::requestQuit()
{
	MutexLock lock(pktMutex);
	quitRequested = true;
	pktCond.broadcast();
}

Buffer NetSender::nextPacket()
{
	DEBUG("NS-NP-PreLock\n");
	MutexLock lock(pktMutex);
	DEBUG("NS-NP-PreLocked\n");

	while (true)
	{
		if (!immediatePackets.empty())
		{
			DEBUG("NS-NP-Immediate\n");
			Buffer res = immediatePackets.front();
			immediatePackets.pop();
			return res;
		}

		if (!scheduledPackets.empty())
		{
			DEBUG("NS-NP-Scheduled\n");
			int wtime = scheduledPackets.front().delay;
			
			// Compute the absolute waiting timeout
			struct timeval before;
			gettimeofday(&before, 0);
			
			struct timespec abstime;
			abstime.tv_sec = before.tv_sec;
			abstime.tv_nsec = before.tv_usec * 1000;

			abstime.tv_sec += wtime / 1000;
			abstime.tv_nsec += (wtime % 1000) * 1000000;
			if (abstime.tv_nsec > 1000000000)
			{
				abstime.tv_sec++;
				abstime.tv_nsec -= 1000000000;
			}
			
			pktCond.wait(lock, abstime);
			if (quitRequested)
			{
				DEBUG("NS-NP-QUIT0\n");
				return Buffer(0);
			}

			struct timeval after;
			gettimeofday(&after, 0);
			
			int elapsed = (after.tv_sec * 1000 + after.tv_usec / 1000) - (before.tv_sec * 1000 + before.tv_usec / 1000);

			scheduledPackets.front().delay -= elapsed;
			while (!scheduledPackets.empty() && scheduledPackets.front().delay <= 0)
			{
				immediatePackets.push(scheduledPackets.front().packet);
				scheduledPackets.pop_front();
			}
		} else {
			DEBUG("NS-NP-Wait\n");
			pktCond.wait(lock);
			if (quitRequested)
			{
				DEBUG("NS-NP-QUIT1\n");
				return Buffer(0);
			}
			DEBUG("NS-NP-Waited\n");
		}
	}
}

void* NetSender::main()
{
	// Let the signals be caught by some other process
		DEBUG("NS-Main-Start\n");
	sigset_t sigs, oldsigs;
	sigfillset(&sigs);
	sigdelset(&sigs, SIGFPE);
	sigdelset(&sigs, SIGILL);
	sigdelset(&sigs, SIGSEGV);
	sigdelset(&sigs, SIGBUS);
	sigdelset(&sigs, SIGABRT);
	sigdelset(&sigs, SIGIOT);
	sigdelset(&sigs, SIGTRAP);
	sigdelset(&sigs, SIGSYS);
	pthread_sigmask(SIG_SETMASK, &sigs, &oldsigs);
		DEBUG("NS-Main-Start1\n");

	try {
		while (true)
		{
			DEBUG("NS-Main-Loop\n");
			Buffer b = nextPacket();
			if (quitRequested)
			{
				DEBUG("NS-Main-QUIT0\n");
				return 0;
			}
			DEBUG("NS-Main-GotPacket\n");
			if (libnet_write_link(ln_context, (u_char*)b.data(), b.size()) == -1)
				throw wibble::exception::Libnet(ln_context, "writing raw ethernet packet");
			DEBUG("NS-Main-Written\n");
			//fprintf(stderr, "Sent packet\n");
		}
	} catch (std::exception& e) {
		error("%s.  Quitting NetSender thread.\n", e.what());
	}
	DEBUG("NS-Main-Quit\n");
	return 0;
}

}

// vim:set ts=4 sw=4:
