#ifndef GUESSNET_UTIL_NETSENDER_H
#define GUESSNET_UTIL_NETSENDER_H

/*
 * 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 <wibble/exception.h>
#include <wibble/sys/buffer.h>
#include <wibble/sys/thread.h>
#include <wibble/sys/mutex.h>
#include "nettypes.h"
#include "util/starter.h"
#include <string>
#include <queue>
#include <list>

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

namespace wibble {
namespace exception {

class Libnet : public Generic
{
protected:
	std::string _libnet_errmsg;

public:
	Libnet(const libnet_t* ln_context, const std::string& context) throw ()
		: Generic(context)
	{
		const char* msg = libnet_geterror(const_cast<libnet_t*>(ln_context));
		_libnet_errmsg = msg ? msg : "(can't provide an error message: libnet returned null from libnet_geterror!)";
	}
	Libnet(const char* ln_errbuf, const std::string& context) throw ()
		: Generic(context), _libnet_errmsg(ln_errbuf) {}
	Libnet(const std::string& context) throw ()
		: Generic(context), _libnet_errmsg() {}
	~Libnet() throw () {}

	virtual const char* type() const throw () { return "libnet"; }

	virtual std::string desc() const throw ()
	{
		if (_libnet_errmsg.size())
			return _libnet_errmsg;
		else
			return "Unknown libnet error";
	}
};

}
}

namespace util {

/*
 * Injects ethernet packets to a given interface.
 *
 * Every public method is thread-safe
 */
class NetSender : public wibble::sys::Thread, public util::Startable
{
protected:
	class TimedPacket
	{
	public:
		int delay;
		wibble::sys::Buffer packet;

		TimedPacket(int delay, wibble::sys::Buffer packet) throw () : delay(delay), packet(packet) {}
	};

	bool quitRequested;
	std::string iface;
	struct libnet_ether_addr* local_hardware_addr;
	wibble::sys::Mutex macMutex;
	wibble::sys::Mutex pktMutex;
	wibble::sys::Condition pktCond;
	std::queue<wibble::sys::Buffer> immediatePackets;

	// Delta-list of scheduled packets with their send delay
	std::list<TimedPacket> scheduledPackets;

	// Libnet error buffer
	char ln_errbuf[LIBNET_ERRBUF_SIZE];
	// Libnet context
	libnet_t* ln_context;

	virtual void* main();

	wibble::sys::Buffer nextPacket();
	void requestQuit();


	NetSender(const std::string& iface);

public:
	~NetSender();

	void startableStart();
	void startableStop() { requestQuit(); }

	struct libnet_ether_addr* getMACAddress();
	libnet_t *getLibnetContext();

	// Send packet once
	void post(wibble::sys::Buffer packet);
	
	// Send packet at regular intervals.
	// msinterval: interval between sends (in milliseconds)
	// mstimeout: keep sending for mstimeout time (in millisecond)
	void post(wibble::sys::Buffer packet, int msinterval, int mstimeout);

	// Post a packet after the given delay
	void post(wibble::sys::Buffer packet, int delay);

	static void configure(const std::string& iface);
	static NetSender& get();
};

}

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