#include "throttle_drv.h"
#include "ethernet_drv.h"
#include <errno.h>
#include "display.h"
#include "stdlib.h"
#include <string.h>
#include <time.h>
// #include <arpa/inet.h>

#define THROTTLENET_PROTO 0x544e

#define THR_DST_ADR_POS      0
#define THR_SRC_ADR_POS      (THR_DST_ADR_POS  + sizeof(((thr_header_t*)0)->dst_adr))             //6
#define THR_ETH_TYP_POS      (THR_SRC_ADR_POS  + sizeof(((thr_header_t*)0)->src_adr))             //12
#define THR_CHN_ID_POS       (THR_ETH_TYP_POS  + sizeof(((thr_header_t*)0)->eth_type))            //14
#define THR_FRAG_NUM_POS     (THR_CHN_ID_POS   + sizeof(((thr_header_t*)0)->chn_id))              //15
#define THR_FRAG_TOT_NUM_POS (THR_FRAG_NUM_POS + sizeof(((thr_header_t*)0)->frag_num))            //17
#define THR_FRAG_LEN_POS     (THR_FRAG_TOT_NUM_POS + sizeof(((thr_header_t*)0)->frag_num_tot))    //19
#define THR_PAYLOAD_POS      (THR_FRAG_LEN_POS + sizeof(((thr_header_t*)0)->frag_len))            //21
#define THR_MSG_HEADER_LEN   THR_PAYLOAD_POS

#define THR_MSG_DST_ADR(thr_msg)       (struct ether_addr*)(&thr_msg[THR_DST_ADR_POS])
#define THR_MSG_SRC_ADR(thr_msg)       (struct ether_addr*)(&thr_msg[THR_SRC_ADR_POS])
#define THR_MSG_ETH_TYP(thr_msg)       *(unsigned short*)(&thr_msg[THR_ETH_TYP_POS])
#define THR_MSG_CHN_ID(thr_msg)        *(unsigned char*)(&thr_msg[THR_CHN_ID_POS])
#define THR_MSG_FRAG_NUM(thr_msg)      *(unsigned short*)(&thr_msg[THR_FRAG_NUM_POS])
#define THR_MSG_FRAG_TOT_NUM(thr_msg)  *(unsigned short*)(&thr_msg[THR_FRAG_TOT_NUM_POS])
#define THR_MSG_FRAG_LEN(thr_msg)      *(unsigned short*)(&thr_msg[THR_FRAG_LEN_POS])
#define THR_MSG_PAYLOAD(thr_msg)       (unsigned char*)(&thr_msg[THR_PAYLOAD_POS])


/* local type for the Throttle Channel structure */
struct thr_chn_t
{
	struct ether_addr dst_adr;         /* destination MAC address                    */
	unsigned char id;                  /* Channel id                                 */
	unsigned short frag_size;          /* Fragment size                              */
	unsigned short freq;               /* Message transmission frequency             */
	thr_msg_handler_t funct;           /* Callback function invoked at the reception */
	unsigned short msg_length;
	unsigned char* p_msg;
	struct ether_addr last_sender_adr;/* src MAC address of last message */
};

/* Type for the Throttle message */
typedef struct
{
   struct ether_addr dst_adr;  /* destination MAC address  */
   struct ether_addr src_adr;  /* source MAC address       */
   unsigned short eth_type;              /* ethernet packet type     */
   unsigned char  chn_id;                /* channel identification   */
   unsigned short frag_num;              /* fragment number          */
   unsigned short frag_num_tot;          /* total fragment number    */
   unsigned short frag_len;              /* fragment length          */
}thr_header_t;

/* Local functions */
static int thr_msg_check(const unsigned char* thr_msg, unsigned short chn_id, unsigned short length);

static struct eth_int_t* eth_int = NULL;

/**
* @fn         int thr_init(const char* eth_int_name)
*
* @brief      Initializes the Throttle Net Driver, enabling the Ethernet Raw communication for the eth_int_name interface.
*
* @param [in] eth_int_name: ethernet interface name (e.g. "eth0","eth1")
*
* @retval     int: 0 on success; -1 on error and errno is set appropriately
*
*******************************************************************************/
int thr_init(const char* eth_int_name)
{
   int ret = 0;

   if (NULL == eth_int_name)
   {
      ret = -1;
      errno = EINVAL;
      DISPLAY_ERR("Input parameter NULL");
   }
   else
   {
      eth_int = eth_open(eth_int_name); /* open the Ethernet socket */
      if (NULL == eth_int)
      {
         ret = -1;
      }
   }
   return(ret);
}

/**
* @fn         struct thr_chn_t* thr_open_chn(const char* dst_adr, unsigned char chn_id, unsigned char frag_size, unsigned short freq, const thr_msg_handler_t funct);
*
* @brief      Open a logic channel
*
* @param [in] dst_adr: destination MAC address;
* @param [in] chn_id: identification of the channel;
* @param [in] frag_size: fragment size;
* @param [in] freq: frequency of trasmission for each fragment;
* @param [in] msg_handler: callback invoked when a message is completely received;
*
* @retval     struct thr_chn_t*: pointer to the Channel structure. NULL on error and errno is set appropriately.
*
*******************************************************************************/
struct thr_chn_t* thr_open_chn(const struct ether_addr* dst_adr, unsigned char chn_id, unsigned char frag_size, unsigned short freq, const thr_msg_handler_t funct)
{
   struct thr_chn_t* tmp_chn = NULL; /* pointer to Channel structure */

   /* TO-DO: check if a similar channel has already been created */

   /* Input parameters not valid ? */
   if ((NULL == dst_adr) || (0 == frag_size))
   {
      errno = EINVAL;
      DISPLAY_ERR("Input parameter(s) NULL");
   }
   else
   {
      /* allocate memory for the Channel structure */
      tmp_chn = (struct thr_chn_t*)malloc(sizeof(struct thr_chn_t));
      if (NULL == tmp_chn)
      {
         DISPLAY_ERR("Error allocating memory!");
      }
      else
      {
         /* Update the Channel structure */
         memcpy(&tmp_chn->dst_adr, dst_adr, sizeof(tmp_chn->dst_adr));
         tmp_chn->id = chn_id;
         tmp_chn->frag_size = frag_size;
         tmp_chn->freq = freq;
         tmp_chn->funct = funct;
      }
#ifdef DEBUG
      printf("thr_open_chn: callback = %x\n", tmp_chn->funct);
#endif

   }

   return(tmp_chn);
}

void thr_close_chn(struct thr_chn_t* c){
   free(c);
}

/**
* @fn         int thr_send(const struct thr_chn_t* thr_chn, const char* data, unsigned int length)
*
* @brief      Sends a Throttle message. A channel must be opened first.
*
* @param [in] thr_chn: pointer to the Throttle channel descriptor;
* @param [in] data: data buffer to be sent;
* @param [in] length: length of the data buffer;
*
* @retval     int: number of byte sent, on success. -1 on error and errno is set appropriately.
*
*******************************************************************************/
int thr_send(const struct thr_chn_t* thr_chn, const char* data, unsigned int length)
{
   unsigned char* thr_msg;
   unsigned char i;
   int ret = 0;

   /* Check the input parameters */
   if ((NULL == thr_chn) || (NULL == data) || (0 == length))
   {
      ret = -1;
      errno = EINVAL;
      DISPLAY_ERR("Input parameter(s) NULL");
   }
   else
   {
      if (0 == thr_chn->frag_size)
      {
         ret = -1;
         errno = ERANGE;
         DISPLAY_ERR("Division by zero");
      }
      else
      {
         /* allocate memory for the Throttle Message */
         thr_msg = (unsigned char*)malloc(THR_MSG_HEADER_LEN + thr_chn->frag_size);
         if (NULL == thr_msg)
         {
            ret = -1;
            DISPLAY_ERR("Error allocating memory!");
         }
         else
         {
            /* Compose the Ethernet Frame to be sent */
            memcpy(THR_MSG_DST_ADR(thr_msg), &thr_chn->dst_adr, sizeof(thr_chn->dst_adr)); /* Destiantion MAC Address  */
            eth_getMACadr(eth_int, THR_MSG_SRC_ADR(thr_msg));                        /* Source MAC Address       */
            THR_MSG_ETH_TYP(thr_msg) = htons(THROTTLENET_PROTO);                     /* Ethernet Packet Type     */
            THR_MSG_CHN_ID(thr_msg) = thr_chn->id;                                   /* Channel identification   */
            THR_MSG_FRAG_TOT_NUM(thr_msg) = ((length - 1) / thr_chn->frag_size) + 1; /* Total number of fragment */

            struct timespec thr_time;
            thr_time.tv_sec = thr_chn->freq / 1000;
            thr_time.tv_nsec = (thr_chn->freq % 1000) * 1000000;

            /* Message is splitted into fragments and they are sent */
            for (i = 1; i <= THR_MSG_FRAG_TOT_NUM(thr_msg); i++)
            {
               THR_MSG_FRAG_NUM(thr_msg) = i; /* fragment number */
               /* update the fragment length */
               if (length >= thr_chn->frag_size)
               {
                  THR_MSG_FRAG_LEN(thr_msg) = thr_chn->frag_size;
                  length -= thr_chn->frag_size;
               }
               else
               {
                  THR_MSG_FRAG_LEN(thr_msg) = length;
               }
               memcpy ((void*)THR_MSG_PAYLOAD(thr_msg), (void*)data, THR_MSG_FRAG_LEN(thr_msg));                /* update the payload */
               ret = eth_send(eth_int, &thr_msg[0], (THR_MSG_HEADER_LEN + THR_MSG_FRAG_LEN(thr_msg))); /* send the message   */
               if (-1 == ret)  /*Error during the Ethernet trasmission ? */
               {
                  DISPLAY_ERR("Error during Throttle msg trasmission!");
                  break;
               }
               else
               {
                  data += THR_MSG_FRAG_LEN(thr_msg);
                  nanosleep(&thr_time, NULL);
               }
            }
            /*deallocate the memory */
            free(thr_msg);
            thr_msg = NULL;
         }
      }
   }
   return(ret);
}

int thr_receive(struct thr_chn_t* thr_chn, unsigned char* data, void* param)
{
   unsigned char* thr_msg = NULL;
   unsigned char* p_data = NULL;
   unsigned short frag_index = 1;
   short byte_rec = 0;
   unsigned char msg_received = 0;
   int ret;

   if ((NULL == thr_chn) || (NULL == data) || (NULL == thr_chn->funct) || (NULL == param))
   {
      ret = -1;
      errno = EINVAL;
      DISPLAY_ERR("Input parameter(s) NULL");
   }
   else
   {
      /* allocate memory for the Throttle Message */
      thr_msg = (unsigned char*)malloc(THR_MSG_HEADER_LEN + thr_chn->frag_size);
      if (NULL == thr_msg)
      {
         ret = -1;
         DISPLAY_ERR("Error allocating memory!");
      }
      else
      {
         p_data = data;
         do
         {
            byte_rec = eth_receive(eth_int, thr_msg, (THR_MSG_HEADER_LEN + thr_chn->frag_size));  /* receive the Ethernet Raw packet */
            if (-1 == byte_rec)
            {
               /* discard the message */
               DISPLAY_ERR("Error during Throttle msg reception: Fragment discarded.");
            }
            else
            {
               /* check if the Ethernet Raw message is correct (Ethernet protocol, Channel ID, length) */
               ret = thr_msg_check(thr_msg, thr_chn->id, byte_rec);
               if (ret < 0)
               {
                  //printf("Throttle msg mismatch: Fragment discarded.\n");
               }
               else
               {
#ifdef DEBUG
                  printf("thr_receive: Message Index %d on %d. Actual Index %d\n", THR_MSG_FRAG_NUM(thr_msg), THR_MSG_FRAG_TOT_NUM(thr_msg), frag_index);
#endif
                  if (frag_index == THR_MSG_FRAG_NUM(thr_msg)) /* The fragment is the one expected ? */
                  {
                     /* Rebuild the original data linking the payloads of each fragment */
                     memcpy((void*)p_data, (void*)THR_MSG_PAYLOAD(thr_msg), THR_MSG_FRAG_LEN(thr_msg));
                     p_data += THR_MSG_FRAG_LEN(thr_msg);  /* update the pointer to the buffer   */
                     frag_index++;                /* update the fragment index          */
                     ret = p_data - data;         /* update the number of received byte */
                     if (frag_index > THR_MSG_FRAG_TOT_NUM(thr_msg))
                     {
                        msg_received = 1;
                     }
                  }
                  else
                  {
                     printf("thr_receive: Fragment mismatch: Fragment discarded.\n");
                     frag_index = 1;
                     p_data = data;
                  }
               }
            }
         }while (msg_received != 1);

         thr_chn->p_msg = (unsigned char*)malloc(ret);
         if (NULL == thr_chn->p_msg)
         {
            ret = -1;
            DISPLAY_ERR("Error allocating memory!");
         }
         else
         {
#ifdef DEBUG
            printf("thr_receive: Number of byte receive %d\n",ret);
#endif
            thr_chn->msg_length = ret;
            memcpy(thr_chn->p_msg, data, ret); /* copy the msg into the thr structure */
            memcpy(&thr_chn->last_sender_adr, THR_MSG_SRC_ADR(thr_msg), 6); /* ... and the address of the sender */
#ifdef DEBUG
            printf("thr_receive: calling %x\n", thr_chn->funct);
#endif
            (thr_chn->funct)(param);
            free(thr_chn->p_msg);
            thr_chn->p_msg = NULL;
         }

         /* deallocate memory for the Throttle Message */
         free(thr_msg);
         thr_msg = NULL;
         p_data = NULL;
      }
   }
   return(ret);
}


static int thr_msg_check(const unsigned char* thr_msg, unsigned short chn_id, unsigned short length)
{
   int ret = 0;

   if (length > THR_MSG_HEADER_LEN)                    /* Ethernet Raw Packet contains a valid Payload ? */
   {
      if (THROTTLENET_PROTO == ntohs(THR_MSG_ETH_TYP(thr_msg))) /* Is Ethernet Type THROTTLENET Protocol ?        */
      {
         if (THR_MSG_CHN_ID(thr_msg) == chn_id)                 /* Is Channel identification correct ?            */
         {
            /* throttle message is correct */
         }
         else
         {
            ret = -1;
         }
      }
      else
      {
         /* discard the message */
         ret = -2;
      }
   }
   else
   {
      /* discard the message */
      ret = -3;
   }
   return(ret);
}


int thr_read(struct thr_chn_t* thr_chn, unsigned char* data, int length)
{
   int ret = 0;

   if ((NULL == thr_chn) || (NULL == data))
   {
      ret = -1;
      errno = EINVAL;
      DISPLAY_ERR("Input parameter(s) NULL");
   }
   else
   {
      if (length > thr_chn->msg_length)
      {
         length = thr_chn->msg_length;
#ifdef DEBUG
         printf("thr_read: truncating length to %d\n", length);
#endif
      }
#ifdef DEBUG
      printf("thr_read: calling memcpy(%x, %x, %d\n", data, thr_chn->p_msg, length);
#endif
      memcpy(data, thr_chn->p_msg, length); /* copy the msg into the thr structure */
      ret = length;
      thr_chn->msg_length = 0;
   }
   return(ret);
}

struct ether_addr* get_sender_addr(struct thr_chn_t* ch) {
	return &ch->last_sender_adr;
}

unsigned char get_channel(struct thr_chn_t* ch) {
	return ch->id;
}