/**********************************************
 * * Author: Zhibin Wu, WINLAB, Rutgers University
 * * Date  August 2003
 * *
 * **********************************************/

extern "C" {
#include <stdarg.h>
#include <float.h>
};

#include "fixrt.h"
#include "priqueue.h"

#include <random.h>

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

//#include <file.h>

#define ROUTE_FILE "scripts/routes"  //zhibinwu

#define FixRT_ALMOST_NOW     0.1 // jitter used for events that should be effectively
				// instantaneous but are jittered to prevent
				// synchronization
#define FixRT_BROADCAST_JITTER 0.01 // jitter for all broadcast packets

#define IP_DEF_TTL   32 // default TTTL

#undef TRIGGER_UPDATE_ON_FRESH_SEQNUM
//#define TRIGGER_UPDATE_ON_FRESH_SEQNUM
/* should the receipt of a fresh (newer) sequence number cause us
   to send a triggered update?  If undef'd, we'll only trigger on
   routing metric changes */

// Returns a random number between 0 and max

static inline double 
jitter (double max, int be_random_)
{
  return (be_random_ ? Random::uniform(max) : 0);
}

   
void
FixRT_Agent::makeRoutingTable(char *fn) {
        int src,dst,nexthop,hoplen;
        FILE *fp = fopen(fn,"r");
        if ( fp == NULL)printf("FIXRT: configure routing table file open failed!\n");
        while(fscanf(fp,"%d %d %d %d\n",&src,&dst,&nexthop,&hoplen)!=EOF) {
                if (myaddr_ != src) {
                        continue;
                } else {
                        fixrt_ent rte;
                        bzero(&rte, sizeof(rte));
                        rte.dst = dst;
                        rte.hop = nexthop;
                        rte.metric = hoplen;
                        //printf("update route being called initially for route from %d to %d.\n",src,dst);
                        table_->AddEntry (rte);
                }
        }
        fclose(fp);
}

void FixRT_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 
FixRT_Agent::tracepkt (Packet * p, double now, int me, const char *type)
{
  char buf[1024];

  unsigned char *walk = p->accessdata ();

  int ct = *(walk++);
  int seq, dst, met;

  snprintf (buf, 1024, "V%s %.5f _%d_ [%d]:", type, now, me, ct);
  while (ct--)
    {
      dst = *(walk++);
      dst = dst << 8 | *(walk++);
      dst = dst << 8 | *(walk++);
      dst = dst << 8 | *(walk++);
      met = *(walk++);
      seq = *(walk++);
      seq = seq << 8 | *(walk++);
      seq = seq << 8 | *(walk++);
      seq = seq << 8 | *(walk++);
      snprintf (buf, 1024, "%s (%d,%d,%d)", buf, dst, met, seq);
    }
  // Now do trigger handling.
  //trace("VTU %.5f %d", now, me);
  if (verbose_)
    trace ("%s", buf);
}

// Prints out an rtable element.
void
FixRT_Agent::output_rte(const char *prefix, fixrt_ent  * prte, FixRT_Agent * a)
{
  a->trace("DFU: deimplemented");
  printf("DFU: deimplemented");

  prte = 0;
  prefix = 0;
#if 0
  printf ("%s%d %d %d\n",
	  prefix, prte->dst, prte->hop, prte->metric);
  a->trace ("VTE %.5f %d %d %d",
          Scheduler::instance ().clock (), prte->dst, prte->hop, prte->metric,
	 );
#endif
}



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


int 
FixRT_Agent::diff_subnet(int dst) 
{
	char* dstnet = Address::instance().get_subnetaddr(dst);
	if (subnet_ != NULL) {
		if (dstnet != NULL) {
			if (strcmp(dstnet, subnet_) != 0) {
				delete [] dstnet;
				return 1;
			}
			delete [] dstnet;
		}
	}
	//assert(dstnet == NULL);
	return 0;
}



void
FixRT_Agent::forwardPacket (Packet * p)
{
  hdr_ip *iph = HDR_IP(p);
  Scheduler & s = Scheduler::instance ();
  double now = s.clock ();
  hdr_cmn *hdrc = HDR_CMN (p);
  int dst;
  fixrt_ent  *prte;
  
  // We should route it.
  //printf("(%d)-->forwardig pkt\n",myaddr_);
  // set direction of pkt to -1 , i.e downward
  hdrc->direction() = hdr_cmn::DOWN;

  // if the destination is outside mobilenode's domain
  // forward it to base_stn node
  // Note: pkt is not buffered if route to base_stn is unknown

  dst = Address::instance().get_nodeaddr(iph->daddr());  
  if (diff_subnet(iph->daddr())) {
	   prte = table_->GetEntry (dst);
	  //if (prte && prte->metric != BIG) 
	   if (prte)  goto send;
	  
	  //int dst = (node_->base_stn())->address();
	  dst = node_->base_stn();
	  prte = table_->GetEntry (dst);
	  if (prte) 
		  goto send;
	  
	  else {
		  //drop pkt with warning
		  fprintf(stderr, "warning: Route to base_stn not known: dropping pkt\n");
		  Packet::free(p);
		  return;
	  }
  }
  
  prte = table_->GetEntry (dst);
  
   if (prte)
    {
       //printf("(%d)-have route for dst\n",myaddr_);
       goto send;
    }
  else
    { // Brand new destination
      //this is impossible for fix routing, we are going to do nothing
      // with this kind of packet, and let it disappear here.
            return;
    }


 send:
  hdrc->addr_type_ = NS_AF_INET;
  hdrc->xmit_failure_ = mac_callback;
  hdrc->xmit_failure_data_ = this;
  if (prte->metric > 1)
	  hdrc->next_hop_ = prte->hop;
  else
	  hdrc->next_hop_ = dst;
  if (verbose_)
	  trace ("Routing pkts outside domain: \
VFP %.5f _%d_ %d:%d -> %d:%d", now, myaddr_, iph->saddr(),
		 iph->sport(), iph->daddr(), iph->dport());  

  assert (!HDR_CMN (p)->xmit_failure_ ||
	  HDR_CMN (p)->xmit_failure_ == mac_callback);
  target_->recv(p, (Handler *)0);
  return;
  
}

void 
FixRT_Agent::sendOutBCastPkt(Packet *p)
{
  Scheduler & s = Scheduler::instance ();
  // send out bcast pkt with jitter to avoid sync
  s.schedule (target_, p, jitter(FixRT_BROADCAST_JITTER, be_random_));
}


void
FixRT_Agent::recv (Packet * p, Handler *)
{
  hdr_ip *iph = HDR_IP(p);
  hdr_cmn *cmh = HDR_CMN(p);
  int src = Address::instance().get_nodeaddr(iph->saddr());
  int dst = cmh->next_hop();
  /*
   *  Must be a packet I'm originating...
   */
  if(src == myaddr_ && cmh->num_forwards() == 0) {
    /*
     * Add the IP Header
     */
    cmh->size() += IP_HDR_LEN;    
    iph->ttl_ = IP_DEF_TTL;
  }
  /*
   *  I received a packet that I sent.  Probably
   *  a routing loop.
   */
  else if(src == myaddr_) {
    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) {
      drop(p, DROP_RTR_TTL);
      return;
    }
  }
 //do not need to update

  if ((src != myaddr_) && (iph->dport() == ROUTER_PORT))
  {
  }
  /*  {
	    // XXX disable this feature for mobileIP where
	    // the MH and FA (belonging to diff domains)
	    // communicate with each other.

	    // Drop pkt if rtg update from some other domain:
	    // if (diff_subnet(iph->src())) 
	    // drop(p, DROP_OUTSIDE_SUBNET);
	    //else    
	    processUpdate(p);
    }*/
  else 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
		    
		    port_dmux_->recv(p, (Handler*)0);
	     }
	  }
  else 
    {
	    forwardPacket(p);
    }
}

static class FixRTClass:public TclClass
{
  public:
  FixRTClass ():TclClass ("Agent/FixRT")
  {
  }
  TclObject *create (int, const char *const *)
  {
    return (new FixRT_Agent ());
  }
} class_FixRT;

FixRT_Agent::FixRT_Agent (): Agent (PT_MESSAGE), ll_queue (0), 
  myaddr_ (0), subnet_ (0), node_ (0), port_dmux_(0),
  be_random_ (1), verbose_ (0)  
  // constants
 {
  table_ = new FixRTable();
   //DEBUG
  address = 0;
 
}

void
FixRT_Agent::startUp()
{ 
   makeRoutingTable(ROUTE_FILE); 
}

int 
FixRT_Agent::command (int argc, const char *const *argv)
{
  if (argc == 2)
    {
      if (strcmp (argv[1], "start-fixrt") == 0)
	{
	  startUp();
	  return (TCL_OK);
	}
      else if (strcmp (argv[1], "dumprtab") == 0)
	{
	  Packet *p2 = allocpkt ();
	  hdr_ip *iph2 = HDR_IP(p2);
	  fixrt_ent  *prte;

	  printf ("Table Dump %d[%d]\n----------------------------------\n",
		  iph2->saddr(), iph2->sport());
	trace ("VTD %.5f %d:%d\n", Scheduler::instance ().clock (),
		 iph2->saddr(), iph2->sport());

	  /*
	   * Freeing a routing layer packet --> don't need to
	   * call drop here.
	   */
	Packet::free (p2);

//	  for (table_->InitLoop (); (prte = table_->NextLoop ());)
//	    output_rte ("\t", prte, this);

	  printf ("\n");

	  return (TCL_OK);
	}
      else if (strcasecmp (argv[1], "ll-queue") == 0)
	{
	if (!(ll_queue = (PriQueue *) TclObject::lookup (argv[2])))
	    {
	      fprintf (stderr, "FixRT_Agent: ll-queue lookup of %s failed\n", argv[2]);
	      return TCL_ERROR;
	    }

	  return TCL_OK;
	}

    }
  else if (argc == 3)
    {
      if (strcasecmp (argv[1], "addr") == 0) {
	 int temp;
	 temp = Address::instance().str2addr(argv[2]);
	 myaddr_ = temp;
	 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));
}




