/*
 * WINGQuerier.{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 "wingquerier.hh"
#include <click/ipaddress.hh>
#include <click/confparse.hh>
#include <click/error.hh>
#include <click/glue.hh>
#include <click/straccum.hh>
#include <clicknet/ether.h>
#include "wingforwarder.hh"
#include "linktablemulti.hh"
#include "arptablemulti.hh"
#include "wingpacket.hh"
CLICK_DECLS

WINGQuerier::WINGQuerier() :
	_forwarder(0), _link_table(0), _arp_table(0), _debug(false) {
}

WINGQuerier::~WINGQuerier() {
}

int WINGQuerier::configure(Vector<String> &conf, ErrorHandler *errh) {

	_query_wait = Timestamp(5);
	_time_before_switch = Timestamp(5);
	if (cp_va_kparse(conf, this, errh, 
				"FWD", cpkM, cpElement,	&_forwarder, 
				"LT", cpkM, cpElement, &_link_table, 
				"TIME_BEFORE_SWITCH", 0, cpTimestamp, &_time_before_switch,
				"QUERY_WAIT", 0, cpTimestamp, &_query_wait, 
				"DEBUG", 0, cpBool, &_debug, 
				cpEnd) < 0)
		return -1;

	if (_forwarder->cast("WINGForwarder") == 0) {
		return errh->error("FWD element is not a WINGForwarder");
	}
	if (_link_table->cast("LinkTableMulti") == 0) {
		return errh->error("LT element is not a LinkTableMulti");
	}

	return 0;
}

void WINGQuerier::push(int, Packet *p_in) {
	IPAddress dst = p_in->dst_ip_anno();
	if (!dst) {	
		click_chatter("%{element} :: %s :: got invalid dst %s", 
				this, 
				__func__,
				dst.unparse().c_str());
		p_in->kill();
		return;
	}
	DstInfo *nfo = _queries.findp(dst);
	if (!nfo) {
		_queries.insert(dst, DstInfo(dst));
		nfo = _queries.findp(dst);
		nfo->_best_metric = 0;
		nfo->_count = 0;
		nfo->_p = PathMulti();
		nfo->_last_query = Timestamp(0);
		nfo->_last_switch = Timestamp(0);
		nfo->_first_selected = Timestamp(0);
	}
	Timestamp now = Timestamp::now();
	Timestamp expire = nfo->_last_switch + _time_before_switch;
	if (!nfo->_best_metric || !nfo->_p.size() || expire < now) {
		PathMulti best = _link_table->best_route(dst, true);
		bool valid = _link_table->valid_route(best);
		nfo->_last_switch.assign_now();
		if (valid) {
			if (nfo->_p != best) {
				nfo->_first_selected.assign_now();
			}
			nfo->_p = best;
			nfo->_best_metric = _link_table->get_route_metric(best);
		} else {
			nfo->_p = PathMulti();
			nfo->_best_metric = 0;
		}
	}
	if (nfo->_best_metric) {
		p_in = _forwarder->encap(p_in, nfo->_p);
		if (p_in) {
			output(0).push(p_in);
		}
		return;
	}
	if (_debug) {
		click_chatter("%{element} :: %s :: no valid route to %s", 
				this,
				__func__, 
				dst.unparse().c_str());
	}
	if ((nfo->_last_query + _query_wait) < Timestamp::now()) {
		nfo->_last_query.assign_now();
		nfo->_count++;
		output(1).push(p_in);
		return;
	} 
	p_in->kill();
}

String WINGQuerier::print_queries() {
	StringAccum sa;
	Timestamp now = Timestamp::now();
	for (DstTable::const_iterator iter = _queries.begin(); iter.live(); iter++) {
		DstInfo dst = iter.value();
		PathMulti best = _link_table->best_route(dst._ip, true);
		int best_metric = _link_table->get_route_metric(best);
		sa << dst._ip;
		sa << " query_count " << dst._count;
		sa << " best_metric " << dst._best_metric;
		sa << " last_query_ago " << now - dst._last_query;
		sa << " first_selected_ago " << now - dst._first_selected;
		sa << " last_switch_ago " << now - dst._last_switch;
		sa << " current_path_metric " << _link_table->get_route_metric(dst._p);
		sa << " [ " << route_to_string(dst._p) << " ]";
		sa << " best_metric " << best_metric;
		sa << " best_route [ " << route_to_string(best) << " ]";
		sa << "\n";
	}
	return sa.take_string();
}

enum {
	H_DEBUG, H_RESET, H_QUERIES
};

String WINGQuerier::read_handler(Element *e, void *thunk) {
	WINGQuerier *c = (WINGQuerier *) e;
	switch ((intptr_t) (thunk)) {
	case H_DEBUG:
		return String(c->_debug) + "\n";
	case H_QUERIES:
		return c->print_queries();
	default:
		return "<error>\n";
	}
}

int WINGQuerier::write_handler(const String &in_s, Element *e, void *vparam,
		ErrorHandler *errh) {
	WINGQuerier *td = (WINGQuerier *) e;
	String s = cp_uncomment(in_s);
	switch ((intptr_t) vparam) {
	case H_DEBUG: {
		bool debug;
		if (!cp_bool(s, &debug))
			return errh->error("debug parameter must be boolean");
		td->_debug = debug;
		break;
	}
	case H_RESET: {
		td->_queries.clear();
		break;
	}
	}
	return 0;
}

void WINGQuerier::add_handlers() {
	add_read_handler("debug", read_handler, H_DEBUG);
	add_read_handler("queries", read_handler, H_QUERIES);
	add_write_handler("debug", write_handler, H_DEBUG);
	add_write_handler("reset", write_handler, H_RESET);
}

CLICK_ENDDECLS
ELEMENT_REQUIRES(WINGForwarder LinkTableMulti)
EXPORT_ELEMENT(WINGQuerier)
