extern "C" {
#include <stdarg.h>
#include <float.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
};

#include <random.h>
#include <address.h>
#include <mobilenode.h>
#include <cmu-trace.h>

#include "noah.h"

#define NOAH_BROADCAST_JITTER 0.01 // jitter for all broadcast packets
//#define NOAH_DEBUG

// Returns a random number between 0 and max
static inline double jitter (double max, int be_random_)
{
        return (be_random_ ? Random::uniform(max) : 0);
}

static class NOAHClass:public TclClass
{
public:
        NOAHClass ():TclClass ("Agent/NOAH") {}
        TclObject *create (int, const char *const *) {
                return (new NOAH_Agent ());
        }
} class_noah;

NOAH_Agent::NOAH_Agent (): Agent (PT_MESSAGE),
        myaddr_ (0), subnet_ (0), node_ (0), port_dmux_(0),
        be_random_ (1), use_mac_ (0)
{
        bind ("use_mac_", &use_mac_);
        bind ("be_random_", &be_random_);
}

int NOAH_Agent::command (int argc, const char *const *argv)
{
        if (argc == 3) {
                if (strcasecmp (argv[1], "addr") == 0) {
                        myaddr_ = Address::instance().str2addr(argv[2]);
                        subnet_ = Address::instance().get_subnetaddr(myaddr_);
                        return TCL_OK;
                }
                TclObject *obj;
                if ((obj = TclObject::lookup (argv[2])) == 0) {
                        fprintf (stderr, "%s: %s lookup of %s failed\n", __FILE__, argv[1],
                                 argv[2]);
                        return TCL_ERROR;
                }
                if (strcasecmp (argv[1], "tracetarget") == 0) {
                        tracetarget = (Trace *) obj;
                        return TCL_OK;
                }
                else if (strcasecmp (argv[1], "node") == 0) {
                        node_ = (MobileNode*) obj;
                        return TCL_OK;
                }
                else if (strcasecmp (argv[1], "port-dmux") == 0) {
                        port_dmux_ = (NsObject *) obj;
                        return TCL_OK;
                }
        }
        return (Agent::command (argc, argv));
}


void NOAH_Agent::trace (char *fmt,...)
{
        va_list ap;

        if (!tracetarget)
                return;

        va_start (ap, fmt);
        vsprintf (tracetarget->pt_->buffer(), fmt, ap);
        tracetarget->pt_->dump();
        va_end (ap);
}

void NOAH_Agent::lost_link (Packet *p)
{
        hdr_cmn *ch = HDR_CMN (p);

        if(use_mac_ == 0) {
                drop(p, DROP_RTR_MAC_CALLBACK);
                return;
        }

        if (!use_mac_ || ch->addr_type_ != NS_AF_INET)
                return;

        // Queue these packets up...
        recv(p, 0);
}

static void mac_callback (Packet * p, void *arg)
{
        ((NOAH_Agent *) arg)->lost_link (p);
}

void NOAH_Agent::forwardPacket (Packet * p)
{
        Scheduler & s = Scheduler::instance ();
        double now = s.clock ();
        hdr_cmn *ch = HDR_CMN (p);
        hdr_ip *iph = HDR_IP(p);

#ifdef NOAH_DEBUG
	  printf("%f NOAH forward (node %s) to %s (via %s), packet %i\n", Scheduler::instance().clock(),
	         Address::instance().print_nodeaddr(node_->address()),
               Address::instance().print_nodeaddr(iph->daddr()),
               Address::instance().print_nodeaddr(ch->next_hop_), ch->uid()); // JCW
#endif		   
	  if (node_->base_stn() == node_->address()) { // node is its own BS --> HA or FA
                ch->next_hop_ = Address::instance().get_nodeaddr(iph->daddr());
        } else { // MH sends to BS
                // JCW set next hop in mip-reg.cc when sending request etc.
                // don't set when destination already set by MIPMH
                if (ch->next_hop_ <= -2) // see packet.h
                        ch->next_hop_ = Address::instance().get_nodeaddr(node_->base_stn());
        }

        ch->direction() = hdr_cmn::DOWN;
        ch->addr_type_ = NS_AF_INET;
        ch->xmit_failure_ = mac_callback;
        ch->xmit_failure_data_ = this;

        assert (!HDR_CMN (p)->xmit_failure_ ||
                HDR_CMN (p)->xmit_failure_ == mac_callback);
#ifdef NOAH_DEBUG
        printf("%f NOAH forward (node %s) to %s (via %s), packet %i\n", Scheduler::instance().clock(),
               Address::instance().print_nodeaddr(node_->address()),
               Address::instance().print_nodeaddr(iph->daddr()),
               Address::instance().print_nodeaddr(ch->next_hop_), ch->uid()); // JCW
#endif
        target_->recv(p, (Handler *)0);
        return;
}

void NOAH_Agent::sendOutBCastPkt(Packet *p)
{
        Scheduler & s = Scheduler::instance ();
#ifdef NOAH_DEBUG
        hdr_ip *iph = (hdr_ip*)p->access(off_ip_);
        hdr_cmn *ch = (hdr_cmn *)p->access (off_cmn_);
        printf("%f NOAH broadcast (node %s) to %s, packet %i\n", Scheduler::instance().clock(),
               Address::instance().print_nodeaddr(node_->address()),
               Address::instance().print_nodeaddr(iph->daddr()), ch->uid()); // JCW
#endif
        s.schedule (target_, p, jitter(NOAH_BROADCAST_JITTER, be_random_));
}


void NOAH_Agent::recv (Packet * p, Handler *)
{
        hdr_ip *iph = HDR_IP(p);
        hdr_cmn *ch = HDR_CMN(p);
        int src = Address::instance().get_nodeaddr(iph->saddr());
        int dst = ch->next_hop();

        /*
         *  Must be a packet I'm originating...
         */
        if(src == myaddr_ && ch->num_forwards() == 0) {
                /*
                 * Add the IP Header
                 */

		    ch->size() += IP_HDR_LEN;
                iph->ttl_ = IP_DEF_TTL;
        }
        /*
         *  I received a packet that I sent.  Probably
         *  a routing loop.
         */
        else if(src == myaddr_) {

#ifdef NOAH_DEBUG
                printf("%f NOAH dropped packet (routing loop)\n", Scheduler::instance().clock());
#endif
                drop(p, DROP_RTR_ROUTE_LOOP);
                return;
        }
        /*
         *  Packet I'm forwarding...
         */
        else {
                /*
                 *  Check the TTL.  If it is zero, then discard.
                 */

                if(--iph->ttl_ == 0) {

#ifdef NOAH_DEBUG
                        printf("%f NOAH dropped packet (TTL)\n", Scheduler::instance().clock());
#endif
                        drop(p, DROP_RTR_TTL);
                        return;
                }
        }


        if ((u_int32_t) dst == IP_BROADCAST &&
            (iph->dport() != ROUTER_PORT)) {
                if (src == myaddr_) {
                        // handle brdcast pkt
                        sendOutBCastPkt(p);
                } else {
                        // hand it over to the port-demux
#ifdef NOAH_DEBUG
                        printf("%f NOAH receive (node %s) from %s, packet %i\n", Scheduler::instance().clock(),
                               Address::instance().print_nodeaddr(node_->address()),
                               Address::instance().print_nodeaddr(iph->daddr()), ch->uid()); // JCW
#endif
				port_dmux_->recv(p, (Handler*)0);
                }
        } else {

                forwardPacket(p);
        }
}

