/*
 * Copyright (c) 2004, Stony Brook University
 * Contributors: Ashish Raniwala
 * All rights reserved.
 *
 * See copyright notice included in the distribution for details
 *
 */

// raniwala: added for manual routing in wireless scenario
extern "C" {
#include <stdarg.h>
#include <float.h>
};

#include "manual.h"
#include "priqueue.h"

#include <random.h>

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

#define DSDV_BROADCAST_JITTER 0.01 // jitter for all broadcast packets

extern int global_flowid;
extern int flow_to_tag[];

void Manual_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 
Manual_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
Manual_Agent::output_rte(const char *prefix, fixed_rtable_ent * prte, Manual_Agent * a)
{
  a->trace("DFU: deimplemented");
  printf("DFU: deimplemented");

  prte = 0;
  prefix = 0;
#if 0
  printf ("%s%d %d %d %d %f %f %f %f 0x%08x\n",
	  prefix, prte->dst, prte->hop, prte->metric, prte->seqnum,
	  prte->udtime, prte->new_seqnum_at, prte->wst, prte->changed_at,
	  (unsigned int) prte->timeout_event);
  a->trace ("VTE %.5f %d %d %d %d %f %f %f %f 0x%08x",
          Scheduler::instance ().clock (), prte->dst, prte->hop, prte->metric,
	  prte->seqnum, prte->udtime, prte->new_seqnum_at, prte->wst, prte->changed_at,
	    prte->timeout_event);
#endif
}


static void 
mac_callback (Packet * p, void *arg)
{
	printf("mac_callback not implemented\n");
}

int 
Manual_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
Manual_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;
  int tag;
  int flowid;
  fixed_rtable_ent *prte;

  printf("%d: MANUAL routing agent called to forward packet\n", myaddr_);
  
  // 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());  
  tag = hdrc->_tag_;
  flowid = hdrc->_flowid_;

  if (tag == -1) {
  	// if tag does not exist in table
#ifndef	SPLIT_FLOWS
	if (flow_to_tag[flowid] == -1)
#endif
	{
		tag_table_ent * tmp;
  		// create a tag, and put into flow_to_tag[flowid]
		tmp = tagtable_->GetTag(dst);
		flow_to_tag[flowid] = tmp->tag;

#ifndef	SPLIT_FLOWS
		printf(">>>>>>>>>Flow %d assigned new tag %d\n", flowid, flow_to_tag[flowid]);
#endif
	}
  	// write tag on packet
	tag = hdrc->_tag_ = flow_to_tag[flowid];
  }

  if (diff_subnet(iph->daddr())) {
	   prte = table_->GetEntry (dst, tag);
	  if (prte) 
		  goto send;
	  
	  //int dst = (node_->base_stn())->address();
	  dst = node_->base_stn();
	  printf(">>>PROBLEM - LOOKING FOR BASE STATION!\n");
	  prte = table_->GetEntry (dst, tag);
	  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, tag);
  
  //  trace("VDEBUG-RX %d %d->%d %d %d 0x%08x 0x%08x %d %d", 
  //  myaddr_, iph->src(), iph->dst(), hdrc->next_hop_, hdrc->addr_type_,
  //  hdrc->xmit_failure_, hdrc->xmit_failure_data_,
  //  hdrc->num_forwards_, hdrc->opt_num_forwards);

  if (prte) {
       //printf("(%d)-have route for dst\n",myaddr_);
       goto send;
  }
  else { /* must queue the packet */
	    //printf("(%d)-no route, queue pkt\n",myaddr_);
      if (!prte->q) {
	  prte->q = new PacketQueue ();
      }

      prte->q->enque(p);

      if (verbose_)
	trace ("VBP %.5f _%d_ %d:%d -> %d:%d tag=%d", now, myaddr_, iph->saddr(),
	       iph->sport(), iph->daddr(), iph->dport(), tag);

      while (prte->q->length () > MAX_QUEUE_LENGTH)
	      drop (prte->q->deque (), DROP_RTR_QFULL);
      return;
   }


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

  printf("DEST = %d, NEXTHOP = %d nic_id = %d\n", dst, prte->hop, prte->nic_id);
  assert (!HDR_CMN (p)->xmit_failure_ ||
	  HDR_CMN (p)->xmit_failure_ == mac_callback);
  if (prte->nic_id == 1)
  	downtarget1_->recv(p, (Handler *)0);
  else if (prte->nic_id == 2)
  	downtarget2_->recv(p, (Handler *)0);
  else if (prte->nic_id == 3)
  	downtarget3_->recv(p, (Handler *)0);
  else if (prte->nic_id == 4)
  	downtarget4_->recv(p, (Handler *)0);
  else if (prte->nic_id == 5)
  	downtarget5_->recv(p, (Handler *)0);
  return;
}

void 
Manual_Agent::sendOutBCastPkt(Packet *p)
{
  Scheduler & s = Scheduler::instance ();
  printf("Broadcast not implemented!!\n");
}


void
Manual_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;
    }
  }
  
  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    
	    printf("How come we got a route update ?? \n");
  }
  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 ManualClass:public TclClass
{
  public:
  ManualClass ():TclClass ("Agent/ManualRouting")
  {
  }
  TclObject *create (int, const char *const *)
  {
    return (new Manual_Agent ());
  }
} class_manualrouting;

Manual_Agent::Manual_Agent (): Agent (PT_MESSAGE), ll_queue (0), 
  myaddr_ (0), subnet_ (0), node_ (0), port_dmux_(0),
  use_mac_ (0), verbose_ (1), downtarget1_ (0), downtarget2_ (0), downtarget3_(0), downtarget4_(0), downtarget5_(0) // constants
 { 
  int i;
  table_ = new FixedRoutingTable ();
  tagtable_ = new TagTable ();

  //bind ("use_mac_", &use_mac_);
  //bind ("verbose_", &verbose_);
  //DEBUG
  address = 0;
 
}

void
Manual_Agent::startUp()
{
 Time now = Scheduler::instance().clock();

  subnet_ = Address::instance().get_subnetaddr(myaddr_);
  //DEBUG
  address = Address::instance().print_nodeaddr(myaddr_);
  //printf("myaddress: %d -> %s\n",myaddr_,address);
  
  fixed_rtable_ent rte;
  bzero(&rte, sizeof(rte));

  rte.dst = myaddr_;
  rte.hop = myaddr_;
  rte.tag = -1;

  rte.q = 0;		// Don't buffer pkts for self!
  
  table_->AddEntry (rte);
  printf("Self address = %d\n", myaddr_);

}

int 
Manual_Agent::command (int argc, const char *const *argv)
{
  if (argc == 2) {
      if (strcmp (argv[1], "start-manual-routing") == 0) {
	  startUp();
	  return (TCL_OK);
      }
      else if (strcmp (argv[1], "dumprtab") == 0) {
	  Packet *p2 = allocpkt ();
	  hdr_ip *iph2 = HDR_IP(p2);
	  fixed_rtable_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, "Manual_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;
      }
      // raniwala: adding multiple down-targets
      else if (strcasecmp (argv[1], "down-target-1") == 0) {
	      downtarget1_ = (NsObject *) obj;
	      return TCL_OK;
      }
      else if (strcasecmp (argv[1], "down-target-2") == 0) {
	      downtarget2_ = (NsObject *) obj;
	      return TCL_OK;
      }
      else if (strcasecmp (argv[1], "down-target-3") == 0) {
	      downtarget3_ = (NsObject *) obj;
	      return TCL_OK;
      }   
      else if (strcasecmp (argv[1], "down-target-4") == 0) {
	      downtarget4_ = (NsObject *) obj;
	      return TCL_OK;
      }   
      else if (strcasecmp (argv[1], "down-target-5") == 0) {
	      downtarget5_ = (NsObject *) obj;
	      return TCL_OK;
      }   
  }
  else if (argc==4) {
	if (strcasecmp (argv[1], "add-route-tag") == 0) {
		// format: add-next-hop dst hop
		tag_table_ent new_entry;
		new_entry.dst = atoi(argv[2]);
		new_entry.tag = atoi(argv[3]);
		tagtable_->AddTag(new_entry);
	        return TCL_OK;
	}
 }
 else if (argc==6) {
	if (strcasecmp (argv[1], "add-next-hop") == 0) {
		// format: add-next-hop dst hop
		fixed_rtable_ent new_entry;
		new_entry.dst = atoi(argv[2]);
		new_entry.tag = atoi(argv[3]);
		new_entry.hop = atoi(argv[4]);
		new_entry.nic_id = atoi(argv[5]);
		new_entry.q = NULL;
		table_->AddEntry(new_entry);
	        return TCL_OK;
	}
 }
  
  printf("Calling superclass Agent's command function!");
  return (Agent::command (argc, argv));
}




