/*
 * winggatewayselector.{cc,hh}
 * John Bicket, Roberto Riggio, Stefano Testi
 *
 * Copyright (c) 1999-2001 Massachusetts Institute of Technology
 * Copyright (c) 2009 CREATE-NET
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>
#include <click/confparse.hh>
#include <click/error.hh>
#include <click/glue.hh>
#include <click/straccum.hh>
#include <click/ipaddress.hh>
#include <clicknet/ether.h>
#include "arptablemulti.hh"
#include "wingpacket.hh"
#include "linktablemulti.hh"
#include "winggatewayselector.hh"
CLICK_DECLS

WINGGatewaySelector::WINGGatewaySelector() :
	_ip(), _link_table(0), _arp_table(0), _jitter(1000), _max_seen_size(100), _period(5000), _timer(this), _debug(false) {
	_seq = Timestamp::now().usec();
}

WINGGatewaySelector::~WINGGatewaySelector() {
}

int WINGGatewaySelector::configure(Vector<String> &conf, ErrorHandler *errh) {
	_is_gw = false;
	if (cp_va_kparse(conf, this, errh, "IP", cpkM, cpIPAddress, &_ip, "LT", 0, cpElement, &_link_table,
			"ARP", 0, cpElement,
			&_arp_table, "PERIOD", 0, cpUnsigned, &_period, "JITTER", 0,
			cpUnsigned, &_jitter, "EXPIRE", 0, cpUnsigned, &_expire, "GW", 0,
			cpBool, &_is_gw, "DEBUG", 0, cpBool, &_debug, cpEnd) < 0)
		return -1;

	if (_link_table && _link_table->cast("LinkTableMulti") == 0)
		return errh->error("LT element is not a LinkTableMulti");
	if (_arp_table && _arp_table->cast("ARPTableMulti") == 0)
		return errh->error("ARP element is not an ARPTableMulti");

	return 0;
}

int WINGGatewaySelector::initialize(ErrorHandler *) {
	_timer.initialize(this);
	_timer.schedule_now();
	return 0;
}

void WINGGatewaySelector::run_timer(Timer *) {
	cleanup();
	if (_is_gw) {
		Vector<int> ifs = _link_table->get_local_interfaces();
		for (int i = 0; i < ifs.size(); i++) {
			start_ad(ifs[i]);
		}
		_seq++;
	}
	unsigned max_jitter = _period / 10;
	unsigned j = click_random(0, 2 * max_jitter);
	Timestamp delay = Timestamp::make_msec(_period + j - max_jitter);
	_timer.schedule_at(Timestamp::now() + delay);
}

void WINGGatewaySelector::start_ad(int iface) {
	NodeAddress src = NodeAddress(_ip, iface);
	EtherAddress eth_src = _arp_table->lookup(src);
	if (eth_src.is_group()) {
		click_chatter("%{element} :: %s :: arp lookup failed for src %s (%s)", 
				this,
				__func__,
				src.unparse().c_str(),
				eth_src.unparse().c_str());

	}
	if (_debug) {
		click_chatter("%{element} :: %s :: gateway %s seq %d iface %u", 
				this, __func__,
				_ip.unparse().c_str(), 
				_seq,
				iface);
	}
	int len = wing_packet::len_wo_data(0);
	WritablePacket *p = Packet::make(len + sizeof(click_ether));
	if (!p) {
		click_chatter("%{element} :: %s :: cannot make packet!", this, __func__);
		return;
	}
	click_ether *eh = (click_ether *) p->data();
	struct wing_packet *pk = (struct wing_packet *) (eh + 1);
	memset(pk, '\0', len);
	pk->_type = WING_PT_GATEWAY;
	pk->set_qdst(_ip);
	pk->set_seq(_seq);
	memcpy(eh->ether_shost, eth_src.data(), 6);
	memset(eh->ether_dhost, 0xff, 6);
	output(0).push(p);
}

void WINGGatewaySelector::forward_ad_hook() {
	Timestamp now = Timestamp::now();
	Vector<int> ifs = _link_table->get_local_interfaces();
	for (int x = 0; x < _seen.size(); x++) {
		if (_seen[x]._to_send < now && !_seen[x]._forwarded) {
			_link_table->dijkstra(false);
			for (int i = 0; i < ifs.size(); i++) {	
				forward_ad(ifs[i], &_seen[x]);
			}
			_seen[x]._forwarded = true;
		}
	}
}

void WINGGatewaySelector::forward_ad(int iface, Seen *s) {
	NodeAddress src = NodeAddress(_ip, iface);
	EtherAddress eth_src = _arp_table->lookup(src);
	if (eth_src.is_group()) {
		click_chatter("%{element} :: %s :: arp lookup failed for src %s (%s)", 
				this,
				__func__,
				src.unparse().c_str(),
				eth_src.unparse().c_str());

	}
	PathMulti best = _link_table->best_route(s->_gw, false);
	if (!_link_table->valid_route(best)) {
		click_chatter("%{element} :: %s :: invalid route from %s", 
				this,
				__func__, 
				s->_gw.unparse().c_str());
		return;
	}
	if (_debug) {
		click_chatter("%{element} :: %s :: gateway %s seq %d iface %u", 
				this, __func__,
				s->_gw.unparse().c_str(), 
				s->_seq,
				iface);
	}
	int hops = best.size() - 1;
	int len = wing_packet::len_wo_data(hops);
	WritablePacket *p = Packet::make(len + sizeof(click_ether));
	if (!p) {
		click_chatter("%{element} :: %s :: cannot make packet!", this, __func__);
		return;
	}
	click_ether *eh = (click_ether *) p->data();
	struct wing_packet *pk = (struct wing_packet *) (eh + 1);
	memset(pk, '\0', len);
	pk->_type = WING_PT_GATEWAY;
	pk->set_qdst(s->_gw);
	pk->set_seq(s->_seq);
	pk->set_num_links(hops);
	for (int i = 0; i < hops; i++) {
		pk->set_link(i, best[i].dep(), best[i + 1].arr(), 
				_link_table->get_link_metric(best[i].dep(), best[i + 1].arr()), 
				_link_table->get_link_metric(best[i + 1].arr(), best[i].dep()), 
				_link_table->get_link_seq(best[i].dep(), best[i + 1].arr()), 
				_link_table->get_link_age(best[i].dep(), best[i + 1].arr()),
				_link_table->get_link_channel(best[i].dep(), best[i + 1].arr()));
	}
	memcpy(eh->ether_shost, eth_src.data(), 6);
	memset(eh->ether_dhost, 0xff, 6);
	output(0).push(p);
}

IPAddress WINGGatewaySelector::best_gateway() {
	IPAddress best_gw = IPAddress();
	int best_metric = 0;
	Timestamp now = Timestamp::now();
	for (GWIter iter = _gateways.begin(); iter.live(); iter++) {
		GWInfo nfo = iter.value();
		Timestamp expire = nfo._last_update + Timestamp::make_msec(_expire);
		PathMulti p = _link_table->best_route(nfo._node, false);
		int metric = _link_table->get_route_metric(p);
		if (now < expire && metric && ((!best_metric) || best_metric > metric)
				&& !_ignore.findp(nfo._node) && (!_allow.size()
				|| _allow.findp(nfo._node))) {
			best_gw = nfo._node;
			best_metric = metric;
		}
	}
	return best_gw;
}

void WINGGatewaySelector::cleanup() {
	GWTable new_table;
	Timestamp now = Timestamp::now();
	for (GWIter iter = _gateways.begin(); iter.live(); iter++) {
		GWInfo nfo = iter.value();
		Timestamp expire = nfo._last_update + Timestamp::make_msec(_expire);
		if (now < expire) {
			new_table.insert(nfo._node, nfo);
		}
	}
	_gateways.clear();
	for (GWIter iter = new_table.begin(); iter.live(); iter++) {
		GWInfo nfo = iter.value();
		_gateways.insert(nfo._node, nfo);
	}
}

void WINGGatewaySelector::push(int, Packet *p_in) {
	click_ether *eh = (click_ether *) p_in->data();
	struct wing_packet *pk = (struct wing_packet *) (eh + 1);
	if (pk->_type != WING_PT_GATEWAY) {
		click_chatter("%{element} :: %s :: bad packet type %d", 
				this,
				__func__, 
				pk->_type);
		p_in->kill();
		return;
	}
	IPAddress gw = pk->qdst();
	uint32_t seq = pk->seq();
	if (!gw) {
		if (_debug) {
			click_chatter("%{element} :: %s :: unable to get qdst", this, __func__);
		}
		p_in->kill();
		return;
	}
	if (_debug) {
		click_chatter("%{element} :: %s :: got gateway adv gateway %s seq %d", 
				this, 
				__func__,
				gw.unparse().c_str(), 
				seq);
	}
	/* update the metrics from the packet */
	for (int i = 0; i < pk->num_links(); i++) {
		NodeAddress a = pk->get_link_dep(i);
		NodeAddress b = pk->get_link_arr(i);
		uint32_t fwd_m = pk->get_link_fwd(i);
		uint32_t rev_m = pk->get_link_rev(i);
		uint32_t seq = pk->get_link_seq(i);
		uint32_t channel = pk->get_link_channel(i);
		if (!fwd_m || !rev_m || !seq || !channel) {
			p_in->kill();
			return;
		}
		if (fwd_m && !_link_table->update_link(a, b, seq, 0, fwd_m, channel)) {
			click_chatter("%{element} :: %s :: couldn't update fwd_m %s > %d > %s",
					this, 
					__func__, 
					a.unparse().c_str(), 
					fwd_m,
					b.unparse().c_str());
		}
		if (rev_m && !_link_table->update_link(b, a, seq, 0, rev_m, channel)) {
			click_chatter("%{element} :: %s :: couldn't update rev_m %s < %d < %s",
					this, 
					__func__, 
					b.unparse().c_str(), 	
					rev_m,
					a.unparse().c_str());
		}
	}
	int si = 0;
	for (si = 0; si < _seen.size(); si++) {
		if (gw == _seen[si]._gw && seq == _seen[si]._seq) {
			_seen[si]._count++;
			p_in->kill();
			return;
		}
	}
	if (si == _seen.size()) {
		if (_seen.size() >= _max_seen_size) {
			_seen.pop_front();
			si--;
		}
		_seen.push_back(Seen(gw, seq));
	}		
	_seen[si]._count++;
	_seen[si]._when = Timestamp::now();
	GWInfo *nfo = _gateways.findp(gw);
	if (!nfo) {
		_gateways.insert(gw, GWInfo());
		nfo = _gateways.findp(gw);
		nfo->_first_update = Timestamp::now();
		nfo->_seen = 0;
	}
	nfo->_node = gw;
	nfo->_last_update = Timestamp::now();
	nfo->_seen++;
	if (_is_gw) {
		p_in->kill();
		return;
	}
	/* schedule timer */
	int delay = click_random(1, _jitter);
	_seen[si]._to_send = _seen[si]._when + Timestamp::make_msec(delay);
	_seen[si]._forwarded = false;
	Timer *t = new Timer(static_forward_ad_hook, (void *) this);
	t->initialize(this);
	t->schedule_after_msec(delay);
	p_in->kill();
	return;
}

String WINGGatewaySelector::print_gateway_stats() {
	StringAccum sa;
	Timestamp now = Timestamp::now();
	for (GWIter iter = _gateways.begin(); iter.live(); iter++) {
		GWInfo nfo = iter.value();
		sa << nfo._node.unparse().c_str() << " ";
		sa << "seen " << nfo._seen << " ";
		sa << "first_update " << now - nfo._first_update << " ";
		sa << "last_update " << now - nfo._last_update << " ";
		PathMulti p = _link_table->best_route(nfo._node, false);
		int metric = _link_table->get_route_metric(p);
		sa << "current_metric " << metric << "\n";
	}
	return sa.take_string();
}

enum {
	H_IS_GATEWAY,
	H_GATEWAY_STATS,
	H_ALLOW,
	H_ALLOW_ADD,
	H_ALLOW_DEL,
	H_ALLOW_CLEAR,
	H_IGNORE,
	H_IGNORE_ADD,
	H_IGNORE_DEL,
	H_IGNORE_CLEAR
};

String WINGGatewaySelector::read_handler(Element *e, void *thunk) {
	WINGGatewaySelector *f = (WINGGatewaySelector *) e;
	switch ((uintptr_t) thunk) {
	case H_IS_GATEWAY:
		return String(f->_is_gw) + "\n";
	case H_GATEWAY_STATS:
		return f->print_gateway_stats();
	case H_IGNORE: {
		StringAccum sa;
		for (NodeIter iter = f->_ignore.begin(); iter.live(); iter++) {
			IPAddress node = iter.key();
			sa << node.unparse().c_str() << "\n";
		}
		return sa.take_string();
	}
	case H_ALLOW: {
		StringAccum sa;
		for (NodeIter iter = f->_allow.begin(); iter.live(); iter++) {
			IPAddress node = iter.key();
			sa << node.unparse().c_str() << "\n";
		}
		return sa.take_string();
	}
	default:
		return String();
	}
}

int WINGGatewaySelector::write_handler(const String &in_s, Element *e,
		void *vparam, ErrorHandler *errh) {
	WINGGatewaySelector *f = (WINGGatewaySelector *) e;
	String s = cp_uncomment(in_s);
	switch ((intptr_t) vparam) {
	case H_IS_GATEWAY: {
		bool b;
		if (!cp_bool(s, &b))
			return errh->error("is_gateway parameter must be boolean");
		f->_is_gw = b;
		break;
	}
	case H_IGNORE_ADD: {
		IPAddress ip;
		if (!cp_ip_address(s, &ip)) {
			return errh->error("ignore_add parameter must be IPAddress");
		}
		f->_ignore.insert(ip, ip);
		break;
	}
	case H_IGNORE_DEL: {
		IPAddress ip;
		if (!cp_ip_address(s, &ip)) {
			return errh->error("ignore_add parameter must be IPAddress");
		}
		f->_ignore.remove(ip);
		break;
	}
	case H_IGNORE_CLEAR: {
		f->_ignore.clear();
		break;
	}
	case H_ALLOW_ADD: {
		IPAddress ip;
		if (!cp_ip_address(s, &ip)) {
			return errh->error("ignore_add parameter must be IPAddress");
		}
		f->_allow.insert(ip, ip);
		break;
	}
	case H_ALLOW_DEL: {
		IPAddress ip;
		if (!cp_ip_address(s, &ip)) {
			return errh->error("ignore_add parameter must be IPAddress");
		}
		f->_allow.remove(ip);
		break;
	}
	case H_ALLOW_CLEAR: {
		f->_allow.clear();
		break;
	}
	}
	return 0;
}

void WINGGatewaySelector::add_handlers() {
	add_read_handler("is_gateway", read_handler, (void *) H_IS_GATEWAY);
	add_read_handler("gateway_stats", read_handler, (void *) H_GATEWAY_STATS);
	add_read_handler("ignore", read_handler, (void *) H_IGNORE);
	add_read_handler("allow", read_handler, (void *) H_ALLOW);
	add_write_handler("is_gateway", write_handler, (void *) H_IS_GATEWAY);
	add_write_handler("ignore_add", write_handler, (void *) H_IGNORE_ADD);
	add_write_handler("ignore_del", write_handler, (void *) H_IGNORE_DEL);
	add_write_handler("ignore_clear", write_handler, (void *) H_IGNORE_CLEAR);
	add_write_handler("allow_add", write_handler, (void *) H_ALLOW_ADD);
	add_write_handler("allow_del", write_handler, (void *) H_ALLOW_DEL);
	add_write_handler("allow_clear", write_handler, (void *) H_ALLOW_CLEAR);
}

CLICK_ENDDECLS
ELEMENT_REQUIRES(LinkTableMulti ARPTableMulti)
EXPORT_ELEMENT(WINGGatewaySelector)
