/*
 * aggregatorbuffer.{cc,hh}
 *
 * Roberto Riggio
 * Copyright (c) 2008, CREATE-NET
 *
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions    
 * are met:
 * 
 *   - Redistributions of source code must retain the above copyright 
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright 
 *     notice, this list of conditions and the following disclaimer in 
 *     the documentation and/or other materials provided with the 
 *     distribution.
 *   - Neither the name of the CREATE-NET nor the names of its 
 *     contributors may be used to endorse or promote products derived 
 *     from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */

#include <click/config.h>
#include "aggregatorbuffer.hh"
#include <click/confparse.hh>
#include <click/error.hh>
#include <click/straccum.hh>
#include <click/standard/scheduleinfo.hh>
#include <clicknet/ether.h>
#include <elements/wing/arptablemulti.hh>
#include <elements/wing/linktablemulti.hh>
CLICK_DECLS

#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))

enum { H_RESET, H_DROPS, H_BYTEDROPS, H_CREATES, H_DELETES, H_CAPACITY, H_LIST_QUEUE, H_MAX_BURST_SIZE, H_MIN_BURST_SIZE, H_MAX_DELAY };

AggregatorBuffer::AggregatorBuffer() : 
  _task(this), _timer(&_task)
{
  _fair_table = new FairTable();
  _head_table = new HeadTable();
  _queue_pool = new QueuePool();
  reset();
}

AggregatorBuffer::~AggregatorBuffer()
{
  _map_lock.acquire_write();

  // de-allocate fair-table
  TableItr itr = _fair_table->begin();
  while(itr != _fair_table->end()){
    release_queue(itr.value());
    itr++;
  } // end while
  _fair_table->clear();
  delete _fair_table;

  // de-allocate head-table
  HeadItr itr_head = _head_table->begin();
  while(itr_head != _head_table->end()) {
    Packet* p = itr_head.value();
    if(p){
      p->kill();
    }
    itr_head++;
  } // end while
  _head_table->clear();
  delete _head_table;

  // de-allocate pool
  clean_pool();
  _queue_pool->clear();
  delete _queue_pool;
  _map_lock.release_write();
}

uint32_t 
AggregatorBuffer::compute_deficit(Packet* p)
{
  if (!p)
    return 0;
  if (!_lt)
    return p->length();
  IPAddress to = _arp_table->reverse_lookup(_next);
  uint32_t metric = _lt->get_host_metric_from_me(to);
  if (!metric)
    return 0;
  return (p->length() * metric) / _scaling;
}

void 
AggregatorBuffer::clean_pool(){
  _pool_lock.acquire_write();
  PoolItr itr = _queue_pool->begin();
  while(itr != _queue_pool->end()){
    Packet* p = 0;
    // destroy queued packets
    do{
      p = (*itr)->pull();
      // flush
      if(p){
	    p->kill();
      } // end if
    }while(p);
    itr++;
  } // end while
  _pool_lock.release_write();
}

void * 
AggregatorBuffer::cast(const char *n)
{
    if (strcmp(n, "AggregatorBuffer") == 0)
	return (AggregatorBuffer *)this;
    else if (strcmp(n, Notifier::FULL_NOTIFIER) == 0)
	return static_cast<Notifier*>(&_full_note);
    else
	return NotifierQueue::cast(n);
}

void 
AggregatorBuffer::reset()
{
  _drops=0;
  _bdrops=0;
  _creates=0;
  _deletes=0;
  _sleepiness=0;

  if(!_fair_table->empty()) 
    _next = _fair_table->begin().key();
  else
    _next = EtherAddress();
}

int 
AggregatorBuffer::configure(Vector<String>& conf, ErrorHandler* errh) 
{
  _lt=0;
  _arp_table=0;
  _capacity=500;
  _quantum=1500;
  _scaling=1500;
  _max_burst=1500;
  _min_burst=60;
  _max_delay=20;
  _period=2000;

  int res = cp_va_kparse(conf, this, errh,
			"ETHTYPE", cpkM, cpUnsignedShort, &_et,
			"CAPACITY", 0, cpUnsigned, &_capacity,
			"QUANTUM", 0, cpUnsigned, &_quantum,
			"SCALING", 0, cpInteger, &_scaling,
			"MAX_BURST", 0, cpInteger, &_max_burst,
			"MIN_BURST", 0, cpInteger, &_min_burst,
			"DELAY", 0, cpUnsigned, &_max_delay,
			"PERIOD", 0, cpUnsigned, &_period,
 			"LT", 0, cpElement, &_lt, 
			"ARP", 0, cpElement, &_arp_table,
			cpEnd);

  if (_lt && _lt->cast("LinkTableMulti") == 0) 
      return errh->error("LT element is not a LinkTableMulti");
  
  if (_arp_table && _arp_table->cast("ARPTable") == 0) 
    return errh->error("ARPTable element is not a ARPTable");

  _full_note.initialize(Notifier::FULL_NOTIFIER, router());
  _full_note.set_active(true, false);
  _empty_note.initialize(Notifier::EMPTY_NOTIFIER, router());

  return res;

}

int
AggregatorBuffer::initialize(ErrorHandler *errh)
{
    ScheduleInfo::initialize_task(this, &_task, errh);
    _timer.initialize(this);
    return 0;
}

void 
AggregatorBuffer::push(int, Packet* p)
{ 

  click_ether *e = (click_ether *)p->data();
  EtherAddress dhost = EtherAddress(e->ether_dhost);

  _map_lock.acquire_write();
  // get queue for dhost
  AggregatorBufferQueue* q = _fair_table->get(dhost);
  
  // no queue, get one
  if(!q){
    q = request_queue();
    if(_fair_table->empty()) 
      _next = dhost;
    _fair_table->find_insert(dhost, q);  
    _head_table->find_insert(dhost, 0);  
  } // end if 

  if (!p->timestamp_anno()) {
    p->timestamp_anno().assign_now();
  }  
  p->timestamp_anno() += Timestamp::make_msec(_max_delay);

  // push packet on queue or fail
  if(q->push(p)){
    _empty_note.wake();
    if (q->_size == q->_capacity) {
      _full_note.sleep();
    }
  }else{
    // queue overflow, destroy the packet
    if (_drops == 0)
      click_chatter("%{element}: overflow", this);
    _bdrops += p->length();
    p->kill();
    _drops++;
  } // end if
  _map_lock.release_write();

}

bool
AggregatorBuffer::run_task(Task *)
{
  _empty_note.wake();
  return true;
}

Packet * 
AggregatorBuffer::pull(int)
{

    if (_fair_table->empty()) {
        if (++_sleepiness == SLEEPINESS_TRIGGER) {
          _empty_note.sleep();
        }
        return 0;
    }

    bool trash = false;
    bool send = false;
    _map_lock.acquire_write();

    TableItr active = _fair_table->find(_next);
    HeadItr head = _head_table->find(_next);

    EtherAddress key = active.key();
    AggregatorBufferQueue* queue = active.value();

    _sleepiness = 0;

    Packet *p = 0;
    if (head.value()) {
      p = head.value();
      _head_table->find_insert(_next, 0);
    } else if (queue->top() && (queue->top()->timestamp_anno() <= Timestamp::now())) {
      // packet ready for output
      p = queue->aggregate(_et);
    }

    if (!p) {
      queue->_deficit = 0;
      if ((queue->_size == 0) && (++queue->_trash == TRASH_TRIGGER)) {
          trash = true;
      }
    } else if (compute_deficit(p) <= queue->_deficit) {
      queue->_deficit -= compute_deficit(p);
      _full_note.wake();
      send = true;
    } else {
      _head_table->find_insert(_next, p);
      queue->_trash = 0;
    }
  
    active++;

    if (trash) {
        release_queue(queue);
        _fair_table->erase(key);
        _head_table->erase(key);
    } 

    if (!_fair_table->empty()) {
      if (active == _fair_table->end()) {
        active = _fair_table->begin();
      }
      _next = active.key();
    } else {
      _next = EtherAddress();
    }

    // Check if there is queue that will expire earlier
    TableItr itr = _fair_table->begin();
    Timestamp expire = Timestamp(0);

    while(itr != _fair_table->end()) {
      if (itr.value()->top()) {
        if ((expire == Timestamp(0)) || (expire > itr.value()->top()->timestamp_anno())) {
          _next = itr.key();
          itr.value()->_deficit += _quantum;
          expire = itr.value()->top()->timestamp_anno();
        }
      }
      itr++;
    }

    Timestamp expiry = expire - Timer::adjustment();
    if (expiry > Timestamp::now()) {
      _timer.schedule_at(expiry);
    } else {
      _task.fast_reschedule();
    }

    _empty_note.sleep();
    _map_lock.release_write();

    return (send) ? p : 0;
}

String 
AggregatorBuffer::list_queues()
{
  StringAccum result;

  _map_lock.acquire_read();
  TableItr itr = _fair_table->begin();
  result << "Key,Capacity,Packets,Bytes\n";
  while(itr != _fair_table->end()){
    result << (itr.key()).unparse() << ","
	   << itr.value()->_capacity << "," 
	   << itr.value()->_size << "," 
	   << itr.value()->_bsize << "\n";
    itr++;
  } // end while
  _map_lock.release_read();

  return(result.take_string());
}

AggregatorBufferQueue * 
AggregatorBuffer::request_queue() 
{
  AggregatorBufferQueue* q = 0;
  _pool_lock.acquire_write();

  if(_queue_pool->size() == 0){
    _queue_pool->push_back(new AggregatorBufferQueue(_capacity, _quantum, _max_burst));
    _creates++;
  } // end if

  // check out queue and remove from queue pool
  q = _queue_pool->back();
  _queue_pool->pop_back();
  _pool_lock.release_write();
  q->reset(_quantum, _max_burst);

  return(q);
}

void 
AggregatorBuffer::release_queue(AggregatorBufferQueue* q)
{
  _pool_lock.acquire_write();    
  _queue_pool->push_back(q);
  _pool_lock.release_write();
}

void 
AggregatorBuffer::add_handlers()
{
  add_read_handler("reset", read_handler, (void*)H_RESET);
  add_read_handler("queue_creates", read_handler, (void*)H_CREATES);
  add_read_handler("queue_deletes", read_handler, (void*)H_DELETES);
  add_read_handler("drops", read_handler, (void*)H_DROPS);
  add_read_handler("byte_drops", read_handler, (void*)H_BYTEDROPS);
  add_read_handler("capacity", read_handler, (void*)H_CAPACITY);
  add_read_handler("list_queues", read_handler, (void*)H_LIST_QUEUE);
}


String 
AggregatorBuffer::read_handler(Element *e, void *thunk)
{
  AggregatorBuffer *c = (AggregatorBuffer *)e;
  switch ((intptr_t)thunk) {
  case H_RESET:
    c->reset();
    return(String(""));
  case H_CREATES:
    return(String(c->creates()) + "\n");
  case H_DELETES:
    return(String(c->deletes()) + "\n");
  case H_DROPS:
    return(String(c->drops()) + "\n");
  case H_BYTEDROPS:
    return(String(c->bdrops()) + "\n");
  case H_CAPACITY:
    return(String(c->capacity()) + "\n");
  case H_LIST_QUEUE:
    return(c->list_queues());
  default:
    return "<error>\n";
  }
}

CLICK_ENDDECLS
ELEMENT_REQUIRES(NotifierQueue ARPTableMulti LinkTableMulti)
EXPORT_ELEMENT(AggregatorBuffer)
