#include <protocol/protocol.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <sys/sockio.h>
#include <errno.h>
#include <elib/table.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <machine/param.h>
#include <sys/mbuf.h>
#include <elib/string.h>
#include <stddef.h>
#include <elib/machine.h>
#define INET 1
#include <net/netisr.h>
#ifdef BPF
#include <sys/time.h>
#include <net/bpf.h>
#endif 

#ifdef __NetBSD__
#include <sys/cdefs.h>
#include <machine/psl.h>
#endif

static table interface_table=0;

typedef struct ip_interface {
  layer l;
  struct ifnet *ifp;
  int unit;
  int strict;
  char *name;
  int connected;
  void (*address_function)();
  void *a;
  void (*remove_address_function)();
  void *ga;
  void *bpf_hook;
} *ip_interface;

static void free_mbuf(pbuffer b)
{
  struct mbuf *h,*k=(struct mbuf *)b->parent;
  
  MFREE(k,h);
  if (b->next) dereference_buffer(b->next);
  recycle_buffer_desc(b);
}


static int compare_interface(ip_interface a,struct ifnet *b)
{
  return((string_equal(a->name,b->if_name)) && (a->unit == b->if_unit));
}

struct ifnet *ifp_from_name(char *name,int unit)
{
  struct ifnet *i;
  for (i=ifnet;i;i=i->if_next)
    if ((string_equal(name,i->if_name)) && (unit == i->if_unit)) return(i);
  return(i);
}

void disable_interface(ip_interface i)
{
  i->ifp->if_flags = 0; 
  i->ifp->if_output=0;
#ifdef SUNOS
  i->ifp->if_input=0;
#endif
}

void ip_shutdown()
{
  if (interface_table)
    iterate_table_entries(interface_table,disable_interface,0);
}

int interface_key(struct ifnet *a)
{
  return(key_from_string(a->if_name)+a->if_unit);
}

#define IPADDRESS_FROM_SOCKADDR(a) \
 (*(unsigned int *)(&(((struct sockaddr_in *)(a))->sin_addr)))
static int if_ioctl(struct ifnet *ifp,int cmd,struct ifreq *ifr)
{
  ip_interface i= (ip_interface)table_find(interface_table,ifp);
  short flags;
  unsigned int ip_address;
  int j;
  struct ifaddr *a;

  printf ("interface ioctl %x connected: %d addrs: ",cmd,i->connected);
  for(j=0,a=i->ifp->if_addrlist;a;a=a->ifa_next,j++){
    printf ("   address:%x broadcast:%x\n",
	    IPADDRESS_FROM_SOCKADDR(&a->ifa_addr),
	    IPADDRESS_FROM_SOCKADDR(&a->ifa_broadaddr));
  }
  printf ("\n");

  switch (cmd){
  case SIOCGIFADDR:
    bzero(ifr->ifr_addr.sa_data, 6);
    break;
    
  case SIOCSIFADDR:
    if (i->address_function)
      (*i->address_function)
	(((struct sockaddr_in *) ifr)->sin_addr.s_addr,0,i->a);
    break;

#ifdef MULTICAST 
  case SIOCADDMULTI:
    if (i->address_function)
      (*i->address_function) 
	(((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr.s_addr,0,i->ga);
    break;
  case SIOCDELMULTI:    
    if (i->remove_address_function)
      (*i->remove_address_function) 
	(((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr.s_addr,0,i->ga);
    break;
#endif
  case SIOCSIFFLAGS:
    break;
/*  default:
    return(EAFNOSUPPORT);
    break;*/
  }
  return (0);
}



static int if_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst)
{
  ip_interface i;
  int len=0;
  pbuffer b,k,*g=&b;
  struct mbuf *n;
  int boffset=0,moffset=0,j,xferlen;

  i= (ip_interface)table_find(interface_table,ifp);
  if (!(ifp->if_flags & IFF_UP) ||
      !i->connected){
    printf ("discard\n");
    for (n=m,m=0;n;n=m)  MFREE(n,m);
    return (0); /*(ENETDOWN);*/
  }
  if (dst->sa_family != AF_INET)  return (EAFNOSUPPORT);
#ifdef BPF
  if (i->bpf_hook) bpf_mtap(i->bpf_hook,m);
#endif
  printf ("sending\n");
  if (i->strict){
    b=copy_mbuf_down(i->l,m);
  } else {
    b=wrap_mbuf_chain(m);
    b->down=b; /*??*/
  }
  write_down(i->l,b,0);
  dereference_buffer(b);
  ifp->if_opackets++;
  return (0);
}

static void ip_interface_top(layer l, pbuffer b, int f)
{
  ip_interface i=(ip_interface)(l->state);
  struct mbuf *m,*n=0;
  pbuffer k=b;
  int len,boffset=0,moffset=0,xferlen,j;
  i->ifp->if_ipackets++;
  
  if (b){
    if (!IF_QFULL(&ipintrq))  {
      m=translate_mbuf(b);
#ifdef BPF
      if (i->bpf_hook) bpf_mtap(i->bpf_hook,m);
#endif
      IF_ENQUEUE(&ipintrq, m);
      schednetisr(NETISR_IP);
    } else {
      IF_DROP(&ipintrq);
    }
  }
  if (f & PROTOCOL_CONNECTED){
    debug("ipvc","connecting %s\n",i->name);
    i->connected=1;
  }
}

static void ip_shutdown_stack(layer l,pbuffer b,int f)
{
  ip_interface i=(ip_interface)(l->state);
  if (f & PROTOCOL_STACK_SHUTDOWN){
    i->ifp->if_flags&=(0xffffffff^(IFF_RUNNING|IFF_UP));
    printf ("disconnecting %s\n",i->name);
    i->connected=0;
  }
}

/* ignore unit right now*/
layer create_ip_interface(char *name,int unit,int mtu,int strict,
			  void (*address_function)(),void *a,
			  void (*remove_address_function)(),void *ga)
{
  int k = high_priority();
  struct ifnet *ifp;
  ip_interface new;
  int old=1;
  int j;
  struct ifaddr *ad;

  if (!(ifp=ifp_from_name(name,unit))){
    ifp= (struct ifnet *) allocate_memory(sizeof(struct ifnet));
    ifp->if_name=duplicate_string(name);
    ifp->if_unit=unit;
    ifp->if_flags=0;
    old=0;
  } else {
    for(j=0,ad=ifp->if_addrlist;ad;ad=ad->ifa_next,j++)
      if (address_function) { 
	(*address_function)
	  (IPADDRESS_FROM_SOCKADDR(&ad->ifa_addr),0,a);
	/*      (*address_function)
		(IPADDRESS_FROM_SOCKADDR(&ad->ifa_broadaddr),0,a);*/
      }
  }
  if (!interface_table) interface_table=create_table(compare_interface,
						     interface_key);
  
  if (!(new=table_find(interface_table,ifp))){
    new=(ip_interface) allocate_memory(sizeof(struct ip_interface));
    new->name = duplicate_string(name);
    new->unit = unit;
    table_insert(interface_table,new,ifp);
  }
  new->ifp = ifp;
  new->connected=0;
  new->address_function=address_function;
  new->a=a;
  new->remove_address_function=remove_address_function;
  new->ga=ga;
  new->strict=strict;
  ifp->if_output = if_output;
  ifp->if_ioctl = if_ioctl;
  ifp->if_mtu = mtu;
  if(!old) {
    new->bpf_hook=0;
    ifp->if_next=0;
    ifp->if_flags |= IFF_RUNNING /*| IFF_BROADCAST*/ ;
    ifp->if_snd.ifq_head=0;
    ifp->if_snd.ifq_tail=0;
    ifp->if_snd.ifq_len=0;
    ifp->if_snd.ifq_maxlen=0;
    ifp->if_snd.ifq_drops=0;
#ifdef sun
    ifp->if_init = 0;
    ifp->if_memmap= 0;
#endif
    ifp->if_reset = 0;
    ifp->if_metric = 0;
    ifp->if_addrlist=0;
    ifp->if_watchdog = 0;
    ifp->if_ipackets=0;
    ifp->if_ierrors=0;
    ifp->if_opackets=0;
    ifp->if_oerrors=0;
    ifp->if_collisions=0;
#ifdef MULTICAST
    ifp->if_flags |= IFF_MULTICAST;
#endif
    if_attach(ifp);
#ifdef BPF
    bpfattach(&new->bpf_hook, new->ifp, DLT_NULL, 0);
#endif
  }
  reset_priority(k);
  return(new->l=create_layer(new,ip_shutdown_stack,ip_interface_top,0));
}

