/*
 * A "simulator" testbed for simclick library. Note that this could
 * have been just a normal old C file except for the fact that I
 * decided I wanted to use an existing template heap class, and
 * it seemed like as good a time as any to exercise my rusty
 * STL skills.
 */

#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <stl.h>
#include <hash_map.h>
#include "CUT_BinHeap.h"
#include "click/simclick.h"

const int TESTSIM_IFID_KERNELTAP=0;
const int TESTSIM_IFID_FIRSTIF=1;

class Simulator {
public:
  Simulator();
  virtual ~Simulator(); 
  
  int nextevent();
  struct timeval gettime() { return cursimtime_; };

  class SimTime : public timeval {
  public:
    SimTime() {tv_sec = 0, tv_usec = 0;};
    SimTime(long sec,long usec) { tv_sec = sec, tv_usec = usec; };
    SimTime(struct timeval tv) {tv_sec = tv.tv_sec,tv_usec = tv.tv_usec;};

    bool operator<(const struct timeval& tv) const { 
      return ((tv_sec < tv.tv_sec) ||
	      ((tv_sec == tv.tv_sec) && (tv_usec < tv.tv_usec)));
    }
    bool operator==(const struct timeval& tv) const {
      return ((tv_sec == tv.tv_sec) && (tv_usec == tv.tv_usec));
    }
    bool operator>(const struct timeval& tv) const {
      return ((tv_sec > tv.tv_sec) ||
	      ((tv_usec == tv.tv_usec) && (tv_usec > tv.tv_usec)));
    }
    SimTime operator+(SimTime rhs) const {
      SimTime result;
      result.tv_sec = tv_sec + rhs.tv_sec + (tv_usec + rhs.tv_usec)/1000000;
      result.tv_usec = (tv_usec + rhs.tv_usec) % 1000000;
      return result;
    }
  };

  // Base class for all simulator events
  class SimEvent {
  public:
    SimEvent() {};
    virtual ~SimEvent();  
    virtual int go(SimTime* when) = 0;
  };
protected:
  SimTime cursimtime_;
  typedef CUT_BinHeap< SimTime,SimEvent*,less_equal<SimTime> > SimBinHeap;
  SimBinHeap eventheap_;
};

Simulator::Simulator() {
}

Simulator::~Simulator() {
}

int
Simulator::nextevent() {
  SimBinHeap::Pix pix = eventheap_.find_top();
  SimTime etime = eventheap_.key(pix);
  SimEvent* event = eventheap_.data(pix);
  cursimtime_ = etime;
  event->go(&etime);
  eventheap_.deq();
  delete event;
  return eventheap_.size();
}

Simulator::SimEvent::~SimEvent() {}

class TestClickSimulator : public Simulator {
public:
  TestClickSimulator();
  virtual ~TestClickSimulator();

  int add_node(char* clickfile);
  void handle_packet_from_click(simclick_click node,int ifid,int ptype,
				const unsigned char* data,int len);
  void handle_schedule_from_click(simclick_click node,struct timeval* when);
  void add_lan_entry(simclick_click node,int ifid,int lanid);
  void add_lan_entry(int nodenum,int ifid,int lanid);
  simclick_click get_node(int nodenum);

  class PacketEvent : public Simulator::SimEvent {
  public:
    PacketEvent();
    virtual ~PacketEvent();
    virtual int go(SimTime* when);

    simclick_click clickinst_;
    simclick_sim siminst_;
    int ifid_;
    unsigned char* data_;
    int len_;
    int ptype_;
  };

  class ScheduledEvent : public Simulator::SimEvent {
  public:
    ScheduledEvent();
    virtual ~ScheduledEvent();
    virtual int go(SimTime* when);

    simclick_click clickinst_;
    simclick_sim siminst_;
  };
protected:
  simclick_simstate clickstate_;
  struct netif {
    netif(simclick_click n,int i) { node=n,ifid=i; }
    simclick_click node;
    int ifid;
    bool operator==(const netif& rhs) const {
      return((node == rhs.node) && (ifid == rhs.ifid));
    }
  };
  struct hash<netif> {
    size_t operator()(const netif& x) const {
      // Probably not much of a hash function, but it
      // should do for now.
      return ((int)x.node + x.ifid);
    }
  };

  vector<simclick_click> clickrouters_;
  hash_map<netif,int> netiftolanid_;
  hash_map< int,vector<netif> > lanidtonetif_;
};

TestClickSimulator::TestClickSimulator() {
  memset(&clickstate_,0,sizeof(simclick_simstate));
}

TestClickSimulator::~TestClickSimulator() {
}

int
TestClickSimulator::add_node(char* clickfile) {
  int result = -1;
  simclick_click newnode = simclick_click_create((simclick_sim)this,
						 clickfile,&clickstate_);
  if (newnode) {
    clickrouters_.push_back(newnode);
    result = clickrouters_.size();
  }
  
  return result;
}

void
TestClickSimulator::handle_packet_from_click(simclick_click node,int ifid,
					     int ptype,
					     const unsigned char* data,int len)
{
  // Use the node-ifid combo to find the lanid, and then use the lanid
  // to get the list of node-ifid combos attached to it.
  netif fromif(node,ifid);

  int onlan = netiftolanid_[fromif];
  int i = 0;
  int n = lanidtonetif_[onlan].size();

  for (i=0;i<n;i++) {
    // Load up the simulator queue with packets to send
    SimTime newtime = cursimtime_;
    // Assume overhead of 0.1ms (pulled out of air)
    newtime.tv_usec += 100;
    PacketEvent* pkt = new PacketEvent();
    pkt->clickinst_ = lanidtonetif_[onlan][i].node;
    pkt->siminst_ = (simclick_sim*)this;
    pkt->ifid_ = lanidtonetif_[onlan][i].ifid;
    pkt->data_ = new unsigned char[len];
    pkt->len_ = len;
    pkt->ptype_ = ptype;
    memcpy(pkt->data_,data,len);
    eventheap_.insert(newtime,pkt);
    fprintf(stderr,"Added send packet event: clickinst: %d ifid: %d time: %d %d\n",(int)(pkt->clickinst_),pkt->ifid_,newtime.tv_sec,newtime.tv_usec);
  }
}

void
TestClickSimulator::handle_schedule_from_click(simclick_click node,
					       struct timeval* when) {
  // Stuff a click trigger event into the simulator queue
  ScheduledEvent* sevent = new ScheduledEvent;
  SimTime newtime(*when);
  sevent->clickinst_ = node;
  sevent->siminst_ = (simclick_sim*)this;
  eventheap_.insert(newtime,sevent);
}

simclick_click
TestClickSimulator::get_node(int nodenum) {
  return clickrouters_[nodenum];
}

void
TestClickSimulator::add_lan_entry(int nodenum,int ifid,int lanid) {
  add_lan_entry(clickrouters_[nodenum],ifid,lanid);
}

void
TestClickSimulator::add_lan_entry(simclick_click node,int ifid,int lanid) {
  netif newif(node,ifid);
  netiftolanid_[newif] = lanid;
  lanidtonetif_[lanid].push_back(newif);
}

int
TestClickSimulator::PacketEvent::go(SimTime* when) {
  int result = 0;
  struct simclick_simpacketinfo pinfo;
  simclick_simstate curstate;

  curstate.curtime.tv_sec = when->tv_sec;
  curstate.curtime.tv_usec = when->tv_usec;
  pinfo.id = 2;
  pinfo.fid =2;
  fprintf(stderr,"Dispatching send packet event: clickinst: %d ifid: %d time: %d %d pid %d fid %d\n",(int)clickinst_,ifid_,when->tv_sec,when->tv_usec,pinfo.id,pinfo.fid);
  simclick_click_send(clickinst_,&curstate,ifid_,ptype_,data_,len_,&pinfo);
  
  return result;
}

TestClickSimulator::PacketEvent::PacketEvent() {
  clickinst_ = 0;
  ifid_ = -1;
  ptype_ = -1;
  data_ = 0;
  len_  = 0;
}

TestClickSimulator::PacketEvent::~PacketEvent() {
  if (data_) {
    delete[] data_;
  }
}

TestClickSimulator::ScheduledEvent::ScheduledEvent() {
}

TestClickSimulator::ScheduledEvent::~ScheduledEvent() {
}

int
TestClickSimulator::ScheduledEvent::go(SimTime* when) {
  int result = 0;

  simclick_simstate curstate;
  curstate.curtime.tv_sec = when->tv_sec;
  curstate.curtime.tv_usec = when->tv_usec;
  simclick_click_run(clickinst_,&curstate);
  
  return result;
}

static TestClickSimulator thesim;

int main(int argc,char** argv) {
  int result = 0;
  int i = 0;
  const int numclicks = 3;
  const unsigned char mypacket[] = "this is my bogus packet\n";

  printf("Testing the simclick interface...\n");

  char* scripts[numclicks] = {"../conf/test-simclick-udpgen.click", \
				"../conf/test-simclick-device.click", \
				"../conf/test-simclick-device.click"};

  for (i=0;i<numclicks;i++) {
    printf("Creating a SimClick click instance with %s\n",scripts[i]);
    thesim.add_node(scripts[i]);
  }

  // eth0 of the traffic source (node 0) goes on the same 
  // lan as eth0 of node 1
  thesim.add_lan_entry(0,1,1);
  thesim.add_lan_entry(1,1,1);

  // Put eth1 of node 0 and eth0 of node 1 on the same lan
  thesim.add_lan_entry(1,2,2);
  thesim.add_lan_entry(2,1,2);

  // Put eth1 of node1 and eth0 of node 1 on the same lan
  //thesim.add_lan_entry(1,1,3);
  //thesim.add_lan_entry(2,2,3);

  simclick_simstate startstate;
  startstate.curtime.tv_sec = 0;
  startstate.curtime.tv_usec = 0;
  // Send a packet out to eth0 of node 0.
  //printf("About to send out test packet on node 0, eth0...\n");
  //simclick_click_send(thesim.get_node(0),&startstate,TESTSIM_IFID_FIRSTIF,
  //		      SIMCLICK_PTYPE_ETHER,mypacket,sizeof(mypacket));

  // Prime the simulator pump
  Simulator::SimTime now;
  int endtime = 60;
  Simulator::SimTime tick(0,10000);

  printf("About to start pumping the simulator...\n");
  while (thesim.gettime().tv_sec < endtime) {
    // Insert a clock tick, then run the simulator for a step.
    thesim.handle_schedule_from_click(thesim.get_node(0),&now);
    thesim.nextevent();
    now = now + tick;
  }

  printf("Done.\n");

  return result;
}

int
simclick_sim_ifid_from_name(simclick_sim siminst,const char* ifname) {
  int ifid = -1;
  char* devname = NULL;

  fprintf(stderr,"Woo! Got a request for %s\n",ifname);
  /*
   * Provide a mapping between a textual interface name
   * and the id numbers used. This is so that click scripts
   * can still refer to an interface as, say, /dev/eth0.
   */
  if (strstr(ifname,"tap") || strstr(ifname,"tun")) {
    /*
     * A tapX or tunX interface goes to and from the kernel -
     * always TESTSIM_IFID_KERNELTAP
     */
    ifid = TESTSIM_IFID_KERNELTAP;
  }
  else if ((devname = strstr(ifname,"eth"))) {
    /*
     * Anything with an "eth" followed by a number is a
     * regular interface. Add the number to TESTSIM_IFID_FIRSTIF
     * to get the handle.
     */
    while (*devname && !isdigit(*devname)) {
      devname++;
    }
    if (*devname) {
      ifid = atoi(devname) + TESTSIM_IFID_FIRSTIF;
    }
  }
  fprintf(stderr,"Corresponds to simdev number %d\n",ifid);
  return ifid;
}

void
simclick_sim_ipaddr_from_name(simclick_sim siminst,const char* ifname,
			      char* buf,int len) {
}

void
simclick_sim_macaddr_from_name(simclick_sim siminst,const char* ifname,
			       char* buf,int len) {
}

int
simclick_sim_send_to_if(simclick_sim siminst,simclick_click clickinst,
			int ifid,int type,const unsigned char* data,int len,
			simclick_simpacketinfo* pinfo) {
  int result = 0;
  // XXX print pinfo data
  fprintf(stderr,"Packet incoming on clickinst %d ifid %d\n",(int)clickinst,ifid);
  thesim.handle_packet_from_click(clickinst,ifid,type,data,len);
  fprintf(stderr,"Exiting simclick_send_to_if...\n");
  return result;
}

int
simclick_sim_schedule(simclick_sim siminst,simclick_click clickinst,
		      struct timeval* when) {
  int result = 0;
  thesim.handle_schedule_from_click(clickinst,when);
  return result;
}


syntax highlighted by Code2HTML, v. 0.9.1