/*
 * minstrel.{cc,hh} -- sets wifi txrate annotation on a packet
 * Roberto
 *
 * Copyright (c) 2010 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/packet_anno.hh>
#include <click/straccum.hh>
#include <clicknet/ether.h>
#include <clicknet/wifi.h>
#include <elements/wifi/availablerates.hh>
#include "minstrel.hh"

CLICK_DECLS

Minstrel::Minstrel() 
  : _timer(this) {
}

Minstrel::~Minstrel() {
}

void Minstrel::run_timer(Timer *)
{
	_timer.schedule_after_msec(_period);
}

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

int Minstrel::configure(Vector<String> &conf, ErrorHandler *errh)
{
	_rate = 2;
	_tries = WIFI_MAX_RETRIES + 1;
	_offset = 0;
	_period = 100;
	_active = true;
	_debug = false;
	if (cp_va_kparse(conf, this, errh,
				"RT", cpkM, cpElement, &_rtable,
				"RATE", 0, cpUnsigned, &_rate,
				"TRIES", 0, cpUnsigned, &_tries,
				"OFFSET", 0, cpUnsigned, &_offset,
				"PERIOD", 0, cpUnsigned, &_period,
				"ACTIVE", 0, cpBool, &_active,
				"DEBUG", 0, cpBool, &_debug,
			 cpEnd) < 0)
		return -1;

	return 0;
}

void Minstrel::process_feedback(Packet *p_in) {

	if (!p_in) {
		return;
	}

	uint8_t *dst_ptr = (uint8_t *) p_in->data() + _offset;
	uint8_t *src_ptr = (uint8_t *) p_in->data() + _offset + 6;
	EtherAddress dst = EtherAddress(dst_ptr);
	EtherAddress src = EtherAddress(src_ptr);
	struct click_wifi_extra *ceh = WIFI_EXTRA_ANNO(p_in);
	bool success = !(ceh->flags & WIFI_EXTRA_TX_FAIL);

	/* don't record info for bcast packets */
	if (dst == EtherAddress::make_broadcast()) {
		return;
	}

	/* rate wasn't set */
	if (ceh->rate == 0) {
		return;
	}

	DstInfo *nfo = _neighbors.findp(dst);
	if (!nfo) {
		if (_debug) {
			click_chatter("%{element} :: %s :: no info for %s",
					this, 
					__func__,
					dst.unparse().c_str());
		}
		return;
	}

	if (_debug) {
		click_chatter("%{element} :: %s :: %s -> %s rate: %d, retries: %d, success %u",
				this, 
				__func__,
				src.unparse().c_str(), 
				dst.unparse().c_str(),
				ceh->rate, 
				ceh->retries, 
				success);
	}

	nfo->add_result(ceh->rate, ceh->retries + 1, success);
	
	return;

}

void Minstrel::assign_rate(Packet *p_in)
{

	if (!p_in) {
		return;
	}

	uint8_t *dst_ptr = (uint8_t *) p_in->data() + _offset;
	EtherAddress dst = EtherAddress(dst_ptr);
	struct click_wifi_extra *ceh = WIFI_EXTRA_ANNO(p_in);

	if (dst.is_group() || !dst) {
		Vector<int> rates = _rtable->lookup(EtherAddress::make_broadcast());
		if (rates.size()) {
			ceh->rate = rates[0];
		} else {
			ceh->rate = _rate;
		}
		ceh->max_tries = _tries;
		return;
	}

	DstInfo *nfo = _neighbors.findp(dst);
	if (!nfo || !nfo->_rates.size()) {
		_neighbors.insert(dst, DstInfo(dst, _rtable->lookup(dst)));
		nfo = _neighbors.findp(dst);
	}

	ceh->magic = WIFI_EXTRA_MAGIC;
	ceh->rate = _rate;
	ceh->max_tries = _tries;

	return;

}

Packet* Minstrel::pull(int port) {
	Packet *p = input(port).pull();
	if (p && _active) {
		assign_rate(p);
	}
	return p;
}

void Minstrel::push(int port, Packet *p_in)
{
	if (!p_in) {
		return;
	}
	if (port != 0) {
		process_feedback(p_in);
	} else {
		assign_rate(p_in);
	}
	checked_output_push(port, p_in);
}

String Minstrel::print_rates()
{
	StringAccum sa;
	for (NeighborIter iter = _neighbors.begin(); iter.live(); iter++) {
		DstInfo nfo = iter.value();
		sa << nfo._eth << "\n";
	}
	return sa.take_string();
}

enum {
	H_RATES, H_DEBUG
};

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

int Minstrel::write_handler(const String &in_s, Element *e, void *vparam, ErrorHandler *errh) {
	Minstrel *d = (Minstrel *) 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");
		d->_debug = debug;
		break;
	}
	}
	return 0;
}

void Minstrel::add_handlers() {
	add_read_handler("rates", read_handler, H_RATES);
	add_read_handler("debug", read_handler, H_DEBUG);
	add_write_handler("debug", write_handler, H_DEBUG);
}

CLICK_ENDDECLS
EXPORT_ELEMENT(AutoRateFallback)

