#ifndef VCIMULTINIC_CHAN_HELPER_H
#define VCIMULTINIC_CHAN_HELPER_H

#include <sys/mbuf.h>


///////////////////////////////////////////////////////////////////////////////////////////////////

// buf = actual container
static inline void
vmn_chan_print_container(uint8_t	*buf)
{

#ifdef VCIMULTINIC_DEBUG
  aprint_normal("vcimultinic: Entering %s\n", __func__);
#endif

  // Printing the actuel container internals values
  for (size_t i = 0; i < 4096; i++)
    {
      if (i != 0)
    	{
    	  if ((i % 4) == 0)
     	    aprint_normal(" ");
    	  if ((i % 72) == 0)
    	    aprint_normal("\n");
    	}
      aprint_normal("%02x", buf[i]);
    }
  aprint_normal("\n");

  return;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

// buf = actual container
static inline void
vmn_chan_update_container_metadata(struct vcimultinic_softc	*sc,
				   uint8_t			*buf)
{
#ifdef VCIMULTINIC_DEBUG
  aprint_normal("vcimultinic: Entering %s\n", __func__);
#endif

  // Because pkt_ptr starts at 4, so we minus it
  buf[0] = ((sc->tx_buf->pkt_ptr - 4) / 2) & 0x000000ff;
  buf[1] = (((sc->tx_buf->pkt_ptr - 4) / 2) & 0x0000ff00) >> 8;
  // Because wptr starts at 34 * 4, so we minus it
  buf[2] = (sc->tx_buf->wptr - 34 * 4) & 0x000000ff;
  buf[3] = ((sc->tx_buf->wptr - 34 * 4) & 0x0000ff00) >> 8;

#ifdef VCIMULTINIC_DEBUG
  aprint_normal("vcimultinic: [%s] wptr = 0x%04x, pkt_ptr = 0x%04x\n", __func__, (sc->tx_buf->wptr - 34 * 4), ((sc->tx_buf->pkt_ptr - 4) / 2));
  aprint_normal("vcimultinic: [%s] buf 4*8 = 0x%02x%02x%02x%02x\n", __func__, buf[0], buf[1], buf[2], buf[3]);
  aprint_normal("vcimultinic: [%s] buf 32  = 0x%08x\n", __func__, *((uint32_t*) &(buf[0])));
#endif

  return;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

// buf = actual container
static inline int
vmn_chan_send_container(struct vcimultinic_softc	*sc,
			uint8_t				*buf)
{
  uint8_t	ret = 0;

#ifdef VCIMULTINIC_DEBUG
  aprint_normal("vcimultinic: Entering %s\n", __func__);
#endif

  // Update container metadata
  vmn_chan_update_container_metadata(sc, buf);

#ifdef VCIMULTINIC_DEBUG
  /* vmn_chan_print_container(buf); */
#endif

  switch (sc->tx_buf->current_hw_buf)
    {
    case 0:
      {
	if ((VMN_CHAN_READ_STATUS_REG(sc, NIC_TX_DESC_HI_0) & 0x80000000) == 0)
	  {
#ifdef VCIMULTINIC_DEBUG
	    aprint_normal("vcimultinic: [%s] Writing to hardware container 0 from soft buffer %d \n", __func__, sc->tx_buf->current_sw_buf);
#endif
	    for (size_t i = 0; i < sc->tx_buf->wptr; i += 4) // sc->tx_buf->wptr MUST be aligned on 4 bytes
	      {
		// workaround to read buf as a 4 bytes integer
		uint32_t	*wtmp = (uint32_t*) &(buf[i]);
		VMN_CHAN_WRITE_TX_DATA(sc, 0, i, *wtmp);
#ifdef VCIMULTINIC_DEBUG
		/* aprint_normal("vcimultinic: [%s] Writing %x\n", __func__, *wtmp); */
#endif
	      }
	    /* Acknoledge the sending of the container in the channel */
	    VMN_CHAN_WRITE_REG(sc, NIC_TX_DESC_HI_0, 0x80000000);

	    ret = 1;
	  }
	break;
      }
    case 1:
      {
    	if ((VMN_CHAN_READ_STATUS_REG(sc, NIC_TX_DESC_HI_1) & 0x80000000) == 0)
	  {
#ifdef VCIMULTINIC_DEBUG
	    aprint_normal("vcimultinic: [%s] Writing to hardware container 1 from soft buffer %d\n", __func__, sc->tx_buf->current_sw_buf);
#endif
	    for (size_t i = 0; i < sc->tx_buf->wptr; i += 4) // sc->tx_buf->wptr MUST be aligned on 4 bytes
	      {
		// workaround to read buf as a 4 bytes integer
		uint32_t	*wtmp = (uint32_t*) &(buf[i]);
		VMN_CHAN_WRITE_TX_DATA(sc, 1, i, *wtmp);
	      }
	    /* Acknoledge the sending of the container in the channel */
	    VMN_CHAN_WRITE_REG(sc, NIC_TX_DESC_HI_1, 0x80000000);

	    ret = 1;

	  }
	break;
      }
    default:
      {
#ifdef VCIMULTINIC_DEBUG
	aprint_normal("vcimultinic: [%s] No free TX container, this should not happen!\n", __func__);
#endif
      }
    }

  // Resetting values for the next container
  sc->tx_buf->pkt_ptr = 4;
  sc->tx_buf->wptr = 34 * 4; // buffers start after 34 words of 32 bits
  sc->tx_buf->current_sw_buf = (sc->tx_buf->current_sw_buf + 1) % VMN_TX_SW_NBUF;  // using the next container in main memory
  sc->tx_buf->current_hw_buf = (sc->tx_buf->current_hw_buf + 1) % VMN_TX_HW_NBUF;  // using the next container in nic

  return ret;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

// buf = actual container
static inline int
vmn_chan_receive_container(struct vcimultinic_softc	*sc,
			   uint8_t			*buf)
{
  uint8_t	ret = 0;

#ifdef VCIMULTINIC_DEBUG
  /* aprint_normal("vcimultinic: Entering %s\n", __func__); */
#endif

#ifdef VCIMULTINIC_DEBUG
  /* vmn_chan_print_container(buf); */
#endif

  switch (sc->rx_buf->current_hw_buf)
    {
    case 0:
      {
	// Read if we have container 0 ready for reading...
	if ((VMN_CHAN_READ_STATUS_REG(sc, NIC_RX_DESC_HI_0) & 0x80000000) != 0)
	  {
#ifdef VCIMULTINIC_DEBUG
	    aprint_normal("vcimultinic: [%s] reading hardware container 0 to soft buffer %d\n", __func__, sc->rx_buf->current_sw_buf);
#endif
	    for (size_t i = 0; i < 4096; i += 4) // We read a full page at the moment
	      {
	    	uint32_t data = VMN_CHAN_READ_RX_DATA(sc, 0, i);
	    	buf[i]   = (data & 0xFF000000) >> 24;
	    	buf[i+1] = (data & 0x00FF0000) >> 16;
	    	buf[i+2] = (data & 0x0000FF00) >> 8;
	    	buf[i+3] = (data & 0x000000FF);
	      }
	    // Acknowledge the copy of the container from the channel
	    VMN_CHAN_WRITE_REG(sc, NIC_RX_DESC_HI_0, 0x00000000);

	    ret = 1;
	  }
	break;
      }
    case 1:
      {
	// ... or if we have container 1 ready for reading
	if ((VMN_CHAN_READ_STATUS_REG(sc, NIC_RX_DESC_HI_1) & 0x80000000) != 0)
	  {
#ifdef VCIMULTINIC_DEBUG
	    aprint_normal("vcimultinic[%s] reading hardware container 1 to soft buffer %d\n", __func__, sc->rx_buf->current_sw_buf);
#endif
	    for (size_t i = 0; i < 4096; i += 4) // We read a full page at the moment
	      {
		uint32_t data = VMN_CHAN_READ_RX_DATA(sc, 1, i);
		buf[i]   = (data & 0xFF000000) >> 24;
		buf[i+1] = (data & 0x00FF0000) >> 16;
		buf[i+2] = (data & 0x0000FF00) >> 8;
		buf[i+3] = (data & 0x000000FF);
	      }
	    // Acknowledge the copy of the container from the channel
	    VMN_CHAN_WRITE_REG(sc, NIC_RX_DESC_HI_1, 0x00000000);

	    ret = 1;
	  }
	break;
      }
    default:
      {
#ifdef VCIMULTINIC_DEBUG
	aprint_normal("vcimultinic[%s] We should never get here!!!\n", __func__);
#endif
      }
    }

  // Resetting values for the next container
  sc->rx_buf->pkt_ptr = 4;
  sc->rx_buf->rptr = 34 * 4; // buffers start after 34 words of 32 bits
  sc->rx_buf->current_sw_buf = (sc->rx_buf->current_sw_buf + 1) % VMN_RX_SW_NBUF;
  sc->rx_buf->current_hw_buf = (sc->rx_buf->current_hw_buf + 1) % VMN_RX_HW_NBUF;

  
#ifdef VCIMULTINIC_DEBUG
  /* aprint_normal("vcimultinic[%s] nothing to read\n", __func__); */
#endif

  return ret; // nothing to read
}

///////////////////////////////////////////////////////////////////////////////////////////////////

static inline struct mbuf *
vmn_get_free_mbuf(void)
{
  struct mbuf	*m = NULL;

  // getting a free mbuf
  MGETHDR(m, M_DONTWAIT, MT_DATA);
  if (m != NULL)
    {
      MCLGET(m, M_DONTWAIT);
      if ((m->m_flags & M_EXT) == 0)
	{
	  m_freem(m);
#ifdef VCIMULTINIC_DEBUG
	  aprint_normal("vcimultinic: [%s] free mbuf, but cluster error\n", __func__);
#endif
	  m = NULL;
	}
    }
  else
    {
#ifdef VCIMULTINIC_DEBUG
      aprint_normal("vcimultinic: [%s] Unable to get a free mbuf\n", __func__);
#endif
    }

  return m;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

// buf = actual container
static inline int
vmn_chan_unpack_rx_container(struct vcimultinic_softc	*sc,
			     uint8_t			*buf)
{
  struct ifnet *ifp = &sc->ns_ec.ec_if;
  // from container metadata
  uint16_t	pkt_nb = 0;	// number of packets to read
  uint16_t	words_nb = 0;	// number of meaningful words to read
  // internal variables
  uint32_t	pkt_ptr = 4;	// start of the packets size metadatas
  uint32_t	rptr = 34 * 4;	// buffers start after 34 words of 32 bits
  uint16_t	pkt_current_size = 0;
  struct mbuf	*m = NULL;

#ifdef VCIMULTINIC_DEBUG
  /* aprint_normal("vcimultinic: Entering %s\n", __func__); */
#endif

#ifdef VCIMULTINIC_DEBUG
  /* vmn_chan_print_container(buf); */
#endif

  // Reading metadata from container
  words_nb = ((buf[0] << 8) + buf[1]) - 34; // We remove the metadata words size to keep only the network packet real words size
  pkt_nb = (buf[2] << 8) + buf[3];

#ifdef VCIMULTINIC_DEBUG
  aprint_normal("vcimultinic: [%s] container has %u packets for a size of %u words (%u bytes)\n", __func__, pkt_nb, words_nb, words_nb * sizeof(uint32_t));
#endif

  if (words_nb == 0)
    {
#ifdef VCIMULTINIC_DEBUG
      aprint_normal("vcimultinic: [%s] container is empty...\n", __func__);
#endif
      return 1; // error, container empty...
    }

  for (size_t i = 0; i < pkt_nb; i++)
    {
      pkt_current_size = (buf[pkt_ptr] << 8) + buf[pkt_ptr + 1];
      pkt_ptr += 2;

#ifdef VCIMULTINIC_DEBUG
      /* aprint_normal("vcimultinic: [%s] packet[%d/%d] size is %u bytes\n", __func__, i, pkt_nb - 1, pkt_current_size); */
#endif

      if (pkt_current_size < (ETHER_MIN_LEN - ETHER_CRC_LEN))
	{
#ifdef VCIMULTINIC_DEBUG
	  aprint_normal("vcimultinic: [%s] packet[%u] too small %u < %u\n", __func__, i, pkt_current_size, (ETHER_MIN_LEN - ETHER_CRC_LEN));
#endif
	  continue;
	}

      if (pkt_current_size > (ETHER_MAX_LEN - ETHER_CRC_LEN))
	{
#ifdef VCIMULTINIC_DEBUG
	  aprint_normal("vcimultinic: [%s] packet[%u] too big %u > %u\n", __func__, i, pkt_current_size, (ETHER_MIN_LEN - ETHER_CRC_LEN));
#endif
	  continue;
	}

      // getting a new mbuf for the current packet
      if (NULL == (m = vmn_get_free_mbuf()))
	{ // error
#ifdef VCIMULTINIC_DEBUG
	  aprint_normal("vcimultinic: [%s] no free mbuf, dropping that packet\n", __func__);
#endif
	  ifp->if_ierrors++;
	  break;
	}
      else
	{ // we have a free mbuf
	  for (size_t j = 0; j < pkt_current_size; j++, rptr++)
	    {
	      m->m_ext.ext_buf[j] = buf[rptr];
	    }

	  // deliver packet ...
	  m->m_pkthdr.rcvif = ifp;
	  m->m_pkthdr.len = m->m_len = pkt_current_size;
	  (*ifp->if_input)(ifp, m);
	}
    }

  return 0;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

static void
vmn_chan_update_ifstats(struct vcimultinic_softc	*sc)
{
  struct ifnet *ifp = &sc->ns_ec.ec_if;
  uint32_t	reg = 0;

  /*!
   *  \todo TODO TODO TODO TODO TODO
   */

  /* reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh, g_mac4_addr_offset); */
  reg = 0x0;
  ifp->if_opackets += reg & 0x00ffffff;
  /* oerr_underr = reg >> 24; */

  reg = 0x0;
  ifp->if_ipackets += reg & 0x00ffffff;

  ifp->if_oerrors += 0x0;
  ifp->if_collisions += 0x0;
  ifp->if_ierrors += 0x0;

  return;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

#endif /* VCIMULTINIC_CHAN_HELPER_H */
