#include "ip.h"
#include "tcp.h"
#include "trace.h"
#include "tcp-actcc.h"
#include "blocklist.h"	/* The methods for the list of blocked sources */

template <class Q>
class Active : public Q {
protected:
    NsObject *final_drop;	/* The actual drop object for the queue */
    NsObject *my_node;		/*  The classifier the enqueues things on me */
    int off_ip_;		/*  Offset to the IP header */
    int off_cmn_;		/*  Offset to the common header */
    int off_tcp_;		/*  Offset to the common header */
    int off_act_tcp_;		/*  Offset to the Active TCP header */
    class Dropper: public NsObject {
	Active *parent;		/* The parent of this object */
	NsObject *&drop_tgt;	/* The drop target for this queue.
                                   Note that it is a reference to a
                                   pointer, so it is automatically
                                   updated by subsequent changes to
                                   the underlying pointer. */
    public:
	Dropper(Active *p, NsObject *&d) :parent(p), drop_tgt(d) {}
	virtual void recv(Packet *p, Handler *h=0);
    } *dropper;			/* An NS object to send feedback after
				   which it automatically passes
				   packets to the final_drop object,
				   which is the object ns assigned
				   this queue as a drop node. */
#ifdef ACTIVE_RED
    /* Yes this is henious */
    Dropper *early_dropper;     /* Catch RED early drops */
    NsObject *final_early_drop;
#endif
 public:
    // Dropper needs to access these
    int nextid;
    class ActiveBlocklist *block;/*  A list of blocked connections */
public:
    Active(): nextid(0) {
	
	/* init helper classes classes */
	
	block = new ActiveBlocklist;
	if ( drop_ ) {
	    final_drop = drop_;
	    dropper = new Dropper(this,final_drop);
	    drop_ = dropper;
	}
	else {
	    dropper = 0;
	    final_drop = 0;
	}
#ifdef ACTIVE_RED
	if ( de_drop_ ) {
	    final_early_drop = de_drop_;
	    early_dropper = new Dropper(this,final_early_drop);
	    de_drop_ = early_dropper;
	}
	else {
	    early_dropper = 0;
	    final_early_drop = 0;
	}
#endif	
    }
    ~Active() {
	if (dropper) delete dropper;
#ifdef ACTIVE_RED
	if (early_dropper) delete early_dropper;
#endif
	if (block) delete block;
    }
    int command(int, const char*const*);
    virtual void enque(Packet *);
    void feedback(Packet*, int=0, int=0, int =-1);
};

template <class Q>
void Active<Q>::Dropper::recv(Packet *p, Handler *h=0)
{
/* This intercepts dropped packets from the underlying queue and sends
   feedback to the TCP sources telling them to reduce their windows.
   The ns justification for this is that it allows the underlying
   queues to implement whatever drop policy they'd like.  The Active<>
   class uses this routine to catch packets on their way to the bit
   bucket and initiate feedback.  This routine also set a filter that
   blocks traffic from the source until it has reduced its window or
   it has had a set amount of traffic discarded. We do not block
   connections in slow start. */

    hdr_cmn *cmn_hdr = HDR_CMN(p);
    hdr_ip *hdr = HDR_IP(p);
    hdr_act_tcp *act_tcp_hdr = HDR_ACT_TCP(p);
    nsaddr_t src = hdr->src();
    nsaddr_t dst = hdr->dst();
    int w = act_tcp_hdr->window();

      if ( cmn_hdr->ptype() == PT_TCP && !parent->block->blocked(src,dst) ) { 
  	if ( parent->block->block(src,dst,w,w/2,parent->nextid) )
  	    parent->feedback(p,w,parent->nextid++);
      } 
      else if ( cmn_hdr->ptype() == PT_ACTIVE ) 
  	fprintf(stderr,"dropping feedback!?\n"); 
 
    /* Send the dropped packet to the real drop target */
    
    drop_tgt->recv(p,h);
}

template <class Q>
int Active<Q>::command(int argc, const char*const* argv)
{
/* This overrides the underlying queue's tcl command that specifies
   the drop target, allowing the Active<> class to interpose a dropper
   there.  The argument to a tcl "drop-target" command is assigned to
   Active::final_drop, used by the Dropper class to actually discard
   the packet.  The my-node command is also added, which, with a small
   change to the ns-lib.tcl code, allows the dropper to send
   packets. */

    Tcl& tcl = Tcl::instance();
    if (argc == 2) {
	if (strcmp(argv[1], "drop-target") == 0) {
	    if (final_drop != 0)
		tcl.resultf("%s", final_drop->name());
	    return (TCL_OK);
	}
#ifdef ACTIVE_RED
	if (strcmp(argv[1], "early-drop-target") == 0) {
	    if (final_early_drop != 0)
		tcl.resultf("%s", final_early_drop->name());
	    return (TCL_OK);
	}
#endif
	if (strcmp(argv[1], "my-node") == 0) {
	    if ( my_node != 0)
		tcl.resultf("%s", my_node->name());
	    return (TCL_OK);
	}
    } else if (argc == 3) {
	if (strcmp(argv[1], "drop-target") == 0) {
	    NsObject* p = (NsObject*)TclObject::lookup(argv[2]);
	    if (p == 0) {
		tcl.resultf("no object %s", argv[2]);
		return (TCL_ERROR);
	    }
	    if ( dropper == NULL ) {
		dropper = new Dropper(this,final_drop);
		drop_ = dropper;
	    }
	    final_drop = p;

	    return (TCL_OK);
	}
#ifdef ACTIVE_RED
	if (strcmp(argv[1], "early-drop-target") == 0) {
	    NsObject* p = (NsObject*)TclObject::lookup(argv[2]);
	    if (p == 0) {
		tcl.resultf("no object %s", argv[2]);
		return (TCL_ERROR);
	    }
	    if ( early_dropper == NULL ) {
		early_dropper = new Dropper(this,final_early_drop);
		de_drop_ = early_dropper;
	    }
	    final_early_drop = p;
	    return (TCL_OK);
	}
#endif
	if (strcmp(argv[1], "my-node") == 0) {
	    NsObject* p = (NsObject*)TclObject::lookup(argv[2]);
	    if (p == 0) {
		tcl.resultf("no object %s", argv[2]);
		return (TCL_ERROR);
	    }
	    my_node = p;
	    return (TCL_OK);
	}
    }
    return (Q::command(argc, argv));
}


template <class Q>
void Active<Q>::feedback(Packet *p,int window=0, int ss=0, int id=-1) {
    hdr_ip *hdr = HDR_IP(p);
    hdr_tcp *tcp_hdr = HDR_TCP(p);
    hdr_act_tcp *act_tcp_hdr =HDR_ACT_TCP(p);
    nsaddr_t src = hdr->src();
    nsaddr_t dst = hdr->dst();
    int w = (window) ? window : act_tcp_hdr->window();
    ActiveTcpAgent *agent = act_tcp_hdr->src;

	/* The host must have sent a pointer to itself (this is a
           simulation issue) */
	
    if ( agent ) {

	/* allocate and build a feedback packet.  We use the source
	   Agent to allocate the packet to avoid scheduling evils.
	   See tcp-actcc.h before messing with this! */
	
	Packet *closer = agent->alloc_fb_pkt();
	
	hdr_ip *closer_hdr = HDR_IP(closer);
	hdr_cmn *closer_cmn_hdr = HDR_CMN(closer);
	hdr_act_tcp *closer_act_tcp_hdr = HDR_ACT_TCP(closer);

	closer_hdr->dst() = src;
	closer_hdr->src() = dst;
	closer_cmn_hdr->ptype() = PT_ACTIVE;
	closer_act_tcp_hdr->window() = w;
	closer_act_tcp_hdr->killed() = tcp_hdr->seqno();
	closer_act_tcp_hdr->ss() = ss;
	closer_act_tcp_hdr->id() = id;
	
	closer_hdr->ttl() = 255;
	
	/* Send the feedback packet to the attached classifier to be
	   routed to the TCP source.  The call is to a recv with a
	   NULL handler, i.e. the signature we're calling is
	   object::recv(Packet *, Handler*=0); */
	
	my_node->recv(closer);
    }
}
    
template <class Q>
void Active<Q>::enque(Packet *p)
{
/* Drop blocked packets, otherwise pass them to the underlying queue class. */
    
    hdr_ip *hdr = HDR_IP(p);
    hdr_act_tcp *act_tcp_hdr = HDR_ACT_TCP(p);
    nsaddr_t src = hdr->src();
    nsaddr_t dst = hdr->dst();
    int w = act_tcp_hdr->window();
    int id = act_tcp_hdr->id();

    if ( block->blocked(src,dst,w,id) > 0 ) {
	drop(p);
    }
    else {
	Q::enque(p);
    }
}

/* This creates the interface to tcl that allows tcl to create
   Active<> classes */

template <class Q>
class ActiveClass : public TclClass {
public:
    ActiveClass(char *name) : TclClass(name) {}
    TclObject* create(int, const char*const* ) {
	return (new Active<Q>());
    }
};
