#include <mac.h>
#include <net.h>
#include <routing.h>

static net_addr_t local_addr= NET_ADDR_ANY;

typedef struct {
  net_addr_t dst;
  saddr_t    nh;
} net_rt_entry_t;

#define NET_RT_LEN 8
static net_rt_entry_t net_rt[NET_RT_LEN];
unsigned char net_rt_num_routes;

/**
 * Print a network address.
 */
void net_addr_print(net_addr_t addr) {
  unsigned char * addr_ptr= &addr;
  printdec(*(addr_ptr+3));
  print(".");
  printdec(*(addr_ptr+2));
  print(".");
  printdec(*(addr_ptr+1));
  print(".");
  printdec(*addr_ptr);
}

/**
 * Add an entry to the FIB.
 */
int net_rt_add(net_addr_t dst, saddr_t nh) {
  print("net_rt:add\r\n");
  print("+--dst:");
  net_addr_print(dst);
  print(" nh:");
  printhex(nh);
  print("\n\r");
  if (net_rt_num_routes >= NET_RT_LEN)
    return -1;
  unsigned char i;
  unsigned char free= NET_RT_LEN;
  for (i= 0; i < NET_RT_LEN; i++) {
    if (net_rt[i].dst == dst) {
      net_rt[i].nh= nh;
      return 0;
    } else if (net_rt[i].dst == NET_ADDR_ANY) {
      if (free == NET_RT_LEN)
	free= i;
    }
  }
  net_rt[free].dst= dst;
  net_rt[free].nh= nh;
  return 0;
}

/**
 * Perform a FIB lookup (exact match is used).
 */
saddr_t net_rt_lookup(net_addr_t dst) {
  unsigned char i= 0;
  for (; i < NET_RT_LEN; i++) {
    if (net_rt[i].dst == dst)
      return net_rt[i].nh;
  }
  routing_send_rreq(dst);
  return 0xffff;
}

/**
 * Initialize the FIB.
 */
void net_rt_init() {
  unsigned char i;
  net_rt_num_routes= 0;
  for (i= 0; i < NET_RT_LEN; i++)
    net_rt[i].dst= NET_ADDR_ANY;
}

/**
 * Print the FIB entries.
 */
void net_rt_dump() {
  unsigned char i;
  for (i= 0; i < NET_RT_LEN; i++)
    if (net_rt[i].dst != NET_ADDR_ANY) {
      printdec(i);
      print(": ");
      net_addr_print(net_rt[i].dst);
      print(" -> ");
      printhex(net_rt[i].nh);
      print("\n\r");
    }
}

/**
 * Initialize the network layer:
 * - define the local address
 * - initialize the forwarding table (FIB)
 */
void net_init(net_addr_t addr) {
  print("net:init\r\n");
  local_addr= addr;
  net_rt_init();
}

/**
 * Send a network datagram.
 */
int net_send(net_addr_t dst, unsigned char protocol,
	     const unsigned char * payload, unsigned char payload_len) {
  // TO BE IMPLEMENTED : deliver locally originated datagrams
  if (dst == local_addr)
    return;

#define NET_MSG_LEN 100
  static unsigned char net_msg[NET_MSG_LEN];
  net_hdr_t * hdr= &net_msg;
  hdr->src= local_addr;
  hdr->dst= dst;
  hdr->ttl= 10;
  hdr->protocol= protocol;
  memcpy(&net_msg[sizeof(net_hdr_t)], payload, payload_len);
  saddr_t mac_dst= net_rt_lookup(dst);
  if (mac_dst == 0xffff)
    return -1;
  
  while (!mac_can_send());
  mac_send(mac_dst, MAC_TYPE_NETWORK, 0,
	   net_msg, sizeof(net_hdr_t) + payload_len);
  return 0;
}

/**
 * Process a received network datagram.
 */
void net_recv(mac_frame * frame) {
  print("net:rx\n\r");
  net_hdr_t * hdr= (net_hdr_t *) &(frame->payload[1]);
  if (hdr->dst == local_addr) {
    print("*--from:");
    net_addr_print(hdr->src);
    print("\n\r*--to  :");
    net_addr_print(hdr->dst);
    print("\n\r*--ttl :");
    printdec(hdr->ttl);
    print("\n\r*--prot:");
    printhex(hdr->protocol);
    print("\n\r+--payl:");
    unsigned char i= sizeof(net_hdr_t) + 1;
    for (; i < frame->len; i++) {
      printhex(frame->payload[i]);
      print(" ");
    }
    print("\n\r");
  } else {
    print("net:fwd\n\r");
    hdr->ttl--;
    if (hdr->ttl < 1) {
      print("net:ttl expired");
      return;
    }
    saddr_t mac_dst= net_rt_lookup(hdr->dst);
    if (mac_dst == 0xFFFF) {
      print("net:no route to dst");
      net_addr_print(hdr->dst);
      print("\n\r");
      return;
    }
    while (!mac_can_send());
    mac_send(mac_dst, MAC_TYPE_NETWORK, 0, hdr, sizeof(net_hdr_t));
  }
}

/**
 * Return local network address.
 */
net_addr_t net_get_local_addr() {
  return local_addr;
}
