/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * Copyright 2017 Cisco Systems, Inc.
 */

#include "fapi_stub.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <pthread.h>
#include <unistd.h>

#include <mutex>
#include <queue>
#include <list>

struct phy_pdu {
  phy_pdu() : buffer_len(1500), buffer(0), len(0) {
    buffer = (char *) malloc(buffer_len);
  }

  virtual ~phy_pdu() {
    free(buffer);
  }


  unsigned buffer_len;
  char *buffer;
  unsigned len;
};

class fapi_private {
  std::mutex mutex;
  std::queue<phy_pdu *> rx_buffer;

  std::queue<phy_pdu *> free_store;
 public:

  fapi_private()
    : byte_count(0), tick(0), first_dl_config(false) {
  }

  phy_pdu *allocate_phy_pdu() {
    phy_pdu *pdu = 0;
    mutex.lock();

    if(free_store.empty()) {
      pdu = new phy_pdu();
    } else {
      pdu = free_store.front();
      free_store.pop();
    }

    mutex.unlock();
    return pdu;
  }

  void release_phy_pdu(phy_pdu *pdu) {
    mutex.lock();
    free_store.push(pdu);
    mutex.unlock();
  }

  bool rx_buffer_empty() {
    bool empty;
    mutex.lock();
    empty = rx_buffer.empty();
    mutex.unlock();
    return empty;
  }


  void push_rx_buffer(phy_pdu *buff) {
    mutex.lock();
    rx_buffer.push(buff);
    mutex.unlock();
  }

  phy_pdu *pop_rx_buffer() {
    phy_pdu *buff = 0;
    mutex.lock();

    if(!rx_buffer.empty()) {
      buff = rx_buffer.front();
      rx_buffer.pop();
    }

    mutex.unlock();
    return buff;
  }

  uint32_t byte_count;
  uint32_t tick;
  bool first_dl_config;

};

extern "C"
{
  typedef struct fapi_internal {
    fapi_t _public;

    fapi_cb_t callbacks;

    uint8_t state;
    fapi_config_t config;

    int rx_sock;
    int tx_sock;
    struct sockaddr_in tx_addr;

    uint32_t tx_byte_count;
    uint32_t tick;

    fapi_private *fapi;

  } fapi_internal_t;
}

void send_uplink_indications(fapi_internal_t *instance, uint16_t sfn_sf) {
  fapi_harq_ind_t harq_ind;
  (instance->callbacks.fapi_harq_ind)(&(instance->_public), &harq_ind);
  fapi_crc_ind_t crc_ind;
  crc_ind.header.message_id = FAPI_CRC_INDICATION;
  crc_ind.header.length = 0; //??;
  crc_ind.sfn_sf = sfn_sf;
  crc_ind.body.number_of_crcs = 1;
  crc_ind.body.pdus[0].rx_ue_info.handle = 0; //??
  crc_ind.body.pdus[0].rx_ue_info.rnti = 0; //??
  crc_ind.body.pdus[0].rel8_pdu.crc_flag = 1;
  (instance->callbacks.fapi_crc_ind)(&(instance->_public), &crc_ind);

  if(!instance->fapi->rx_buffer_empty()) {
    fapi_rx_ulsch_ind_t rx_ind;
    memset(&rx_ind, 0, sizeof(rx_ind));
    rx_ind.header.message_id = FAPI_RX_ULSCH_INDICATION;
    rx_ind.sfn_sf = sfn_sf;
    phy_pdu *buff = 0;
    int i = 0;
    std::list<phy_pdu *> free_list;

    do {
      buff = instance->fapi->pop_rx_buffer();

      if(buff != 0) {
        if(buff->len == 0) {
          printf("[FAPI] Buffer length = 0\n");
        }

        rx_ind.body.pdus[i].rx_ue_info.handle = 0xDEADBEEF;
        rx_ind.body.pdus[i].rx_ue_info.rnti = 0x4242;
        rx_ind.body.pdus[i].rel8_pdu.length = buff->len;
        //rx_ind.pdus[i].rel8_pdu.data_offset;
        //rx_ind.pdus[i].rel8_pdu.ul_cqi;
        //rx_ind.pdus[i].rel8_pdu.timing_advance;
        rx_ind.body.data[i] = buff->buffer;
        rx_ind.body.number_of_pdus++;
        i++;
        instance->fapi->byte_count += buff->len;
        free_list.push_back(buff);
      }
    } while(buff != 0 && i < 8);

    (instance->callbacks.fapi_rx_ulsch_ind)(&(instance->_public), &rx_ind);

    for(phy_pdu *pdu : free_list) {
      instance->fapi->release_phy_pdu(pdu);
      //free(tx_req.tx_request_body.tx_pdu_list[j].segments[0].segment_data);
    }
  } else {
    fapi_rx_ulsch_ind_t rx_ind;
    memset(&rx_ind, 0, sizeof(rx_ind));
    rx_ind.header.message_id = FAPI_RX_ULSCH_INDICATION;
    rx_ind.sfn_sf = sfn_sf;
    (instance->callbacks.fapi_rx_ulsch_ind)(&(instance->_public), &rx_ind);
  }

  fapi_rx_cqi_ind_t cqi_ind;
  cqi_ind.sfn_sf = sfn_sf;
  (instance->callbacks.fapi_rx_cqi_ind)(&(instance->_public), &cqi_ind);
  fapi_rx_sr_ind_t sr_ind;
  sr_ind.sfn_sf = sfn_sf;
  (instance->callbacks.fapi_rx_sr_ind)(&(instance->_public), &sr_ind);
  fapi_rach_ind_t rach_ind;
  rach_ind.sfn_sf = sfn_sf;
  (instance->callbacks.fapi_rach_ind)(&(instance->_public), &rach_ind);
  fapi_srs_ind_t srs_ind;
  srs_ind.sfn_sf = sfn_sf;
  (instance->callbacks.fapi_srs_ind)(&(instance->_public), &srs_ind);
  /*
  nfapi_lbt_dl_indication_t lbt_ind;
  memset(&lbt_ind, 0, sizeof(lbt_ind));
  lbt_ind.header.message_id = NFAPI_LBT_DL_INDICATION;
  lbt_ind.header.phy_id = config->phy_id;
  lbt_ind.sfn_sf = sfn_sf;
  nfapi_pnf_p7_lbt_dl_ind(config, &lbt_ind);

  vendor_ext_p7_ind ve_p7_ind;
  memset(&ve_p7_ind, 0, sizeof(ve_p7_ind));
  ve_p7_ind.header.message_id = P7_VENDOR_EXT_IND;
  ve_p7_ind.header.phy_id = config->phy_id;
  ve_p7_ind.error_code = NFAPI_MSG_OK;
  nfapi_pnf_p7_vendor_extension(config, &(ve_p7_ind.header));
  */
  fapi_nb_harq_ind_t nb_harq_ind;
  nb_harq_ind.sfn_sf = sfn_sf;
  (instance->callbacks.fapi_nb_harq_ind)(&(instance->_public), &nb_harq_ind);
  fapi_nrach_ind_t nrach_ind;
  nrach_ind.sfn_sf = sfn_sf;
  (instance->callbacks.fapi_nrach_ind)(&(instance->_public), &nrach_ind);
}

void *fapi_thread_start(void *ptr) {
  set_thread_priority(81);
  fapi_internal_t *instance = (fapi_internal_t *)ptr;
  uint16_t sfn_sf_dec = 0;
  uint32_t last_tv_usec = 0;
  uint32_t last_tv_sec = 0;
  uint32_t millisec;
  uint32_t last_millisec = -1;
  uint16_t catchup = 0;

  while(1) {
    // get the time
    struct timeval sf_start;
    (void)gettimeofday(&sf_start, NULL);
    uint16_t sfn_sf = ((((sfn_sf_dec) / 10) << 4) | (((sfn_sf_dec) - (((sfn_sf_dec) / 10) * 10)) & 0xF));
    // increment the sfn/sf - for the next subframe
    sfn_sf_dec++;

    if(sfn_sf_dec > 10239)
      sfn_sf_dec = 0;

    fapi_subframe_ind_t ind;
    ind.sfn_sf = sfn_sf;

    if(instance->fapi->first_dl_config)
      send_uplink_indications(instance, sfn_sf);

    if(instance->tick == 1000) {
      if(instance->tx_byte_count > 0) {
        printf("[FAPI] Tx rate %u bytes/sec\n", instance->tx_byte_count);
        instance->tx_byte_count = 0;
      }

      instance->tick = 0;
    }

    instance->tick++;
    (instance->callbacks.fapi_subframe_ind)(&(instance->_public), &ind);
    {
      phy_pdu *pdu = instance->fapi->allocate_phy_pdu();
      int len = recvfrom(instance->rx_sock, pdu->buffer, pdu->buffer_len, MSG_DONTWAIT, 0, 0);

      if(len > 0) {
        pdu->len = len;
        instance->fapi->push_rx_buffer(pdu);
      } else {
        instance->fapi->release_phy_pdu(pdu);
      }
    }

    if(catchup) {
      catchup--;
    } else {
      struct timespec now_ts;
      struct timespec sleep_ts;
      struct timespec sleep_rem_ts;
      // get the current time
      clock_gettime(CLOCK_MONOTONIC, &now_ts);
      // determine how long to sleep before the start of the next 1ms
      sleep_ts.tv_sec = 0;
      sleep_ts.tv_nsec = 1e6 - (now_ts.tv_nsec % 1000000);
      int nanosleep_result = nanosleep(&sleep_ts, &sleep_rem_ts);

      if(nanosleep_result != 0)
        printf("*** nanosleep failed or was interrupted\n");

      clock_gettime(CLOCK_MONOTONIC, &now_ts);
      millisec = now_ts.tv_nsec / 1e6;

      if(last_millisec != -1 && ((last_millisec + 1 ) % 1000) != millisec) {
        printf("*** missing millisec %u %u\n", last_millisec, millisec);
        catchup = millisec - last_millisec - 1;
      }

      last_millisec = millisec;
    }
  }
}

extern "C"
{
  fapi_t *fapi_create(fapi_cb_t *callbacks, fapi_config_t *config) {
    fapi_internal_t *instance = (fapi_internal *)calloc(1, sizeof(fapi_internal_t));
    instance->callbacks = *callbacks;
    instance->config = *config;
    instance->state = 0;
    instance->fapi = new fapi_private();
    return (fapi_t *)instance;
  }

  void fapi_destroy(fapi_t *fapi) {
    fapi_internal_t *instance = (fapi_internal_t *)fapi;
    delete instance->fapi;
    free(instance);
  }

  void *fapi_rx_thread_start(void *ptr) {
    set_thread_priority(60);
    fapi_internal_t *instance = (fapi_internal_t *)ptr;

    while(1) {
      phy_pdu *pdu = instance->fapi->allocate_phy_pdu();
      int len = recvfrom(instance->rx_sock, pdu->buffer, pdu->buffer_len, 0, 0, 0);

      if(len > 0) {
        pdu->len = len;
        instance->fapi->push_rx_buffer(pdu);
      } else {
        instance->fapi->release_phy_pdu(pdu);
      }
    }
  }

  void fapi_start_data(fapi_t *fapi, unsigned rx_port, const char *tx_address, unsigned tx_port) {
    fapi_internal_t *instance = (fapi_internal_t *)fapi;
    printf("[FAPI] Rx Data from %u\n", rx_port);
    printf("[FAPI] Tx Data to %s:%u\n", tx_address, tx_port);
    instance->rx_sock = socket(AF_INET, SOCK_DGRAM, 0);

    if(instance->rx_sock < 0) {
      printf("[FAPI] Failed to create socket\n");
      return;
    }

    struct sockaddr_in addr;

    memset(&addr, 0, sizeof(addr));

    addr.sin_family = AF_INET;

    addr.sin_port = htons(rx_port);

    addr.sin_addr.s_addr = INADDR_ANY;

    int bind_result = bind(instance->rx_sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));

    if(bind_result == -1) {
      printf("[FAPI] Failed to bind to port %u\n", rx_port);
      close(instance->rx_sock);
      return ;
    }

    instance->tx_sock = socket(AF_INET, SOCK_DGRAM, 0);
    instance->tx_addr.sin_family = AF_INET;
    instance->tx_addr.sin_port = htons(tx_port);
    instance->tx_addr.sin_addr.s_addr = inet_addr(tx_address);
  }


  void fill_tlv(fapi_tlv_t tlvs[], uint8_t count, uint8_t tag, uint8_t len, uint16_t value) {
    tlvs[count].tag = tag;
    tlvs[count].value = value;
    tlvs[count].length = len;
  }

  int fapi_param_request(fapi_t *fapi, fapi_param_req_t *req) {
    fapi_internal_t *instance = (fapi_internal_t *)fapi;
    fapi_param_resp_t resp;
    resp.header.message_id = FAPI_PARAM_RESPONSE;
    resp.error_code = FAPI_MSG_OK;
    resp.number_of_tlvs = 0;
    fill_tlv(resp.tlvs, resp.number_of_tlvs++, FAPI_PHY_STATE_TAG, 2, instance->state);

    if(instance->state == 0) {
      if(instance->config.duplex_mode == 0) {
        // -- TDD
        // Downlink Bandwidth Support
        // Uplink Bandwidth Support
        // Downlink Modulation Support
        // Uplink Modulation Support
        // PHY Antenna Capability
        // Release Capability
        // MBSFN Capability
      } else if(instance->config.duplex_mode == 1) {
        // -- FDD
        // Downlink Bandwidth Support
        fill_tlv(resp.tlvs, resp.number_of_tlvs++, FAPI_PHY_CAPABILITIES_DL_BANDWIDTH_SUPPORT_TAG, 2, instance->config.dl_channel_bw_support);
        // Uplink Bandwidth Support
        fill_tlv(resp.tlvs, resp.number_of_tlvs++, FAPI_PHY_CAPABILITIES_UL_BANDWIDTH_SUPPORT_TAG, 2, instance->config.ul_channel_bw_support);
        // Downlink Modulation Support
        // Uplink Modulation Support
        // PHY Antenna Capability
        // Release Capability
        // MBSFN Capability
        // LAA Capability
      }
    } else {
      if(instance->config.duplex_mode == 0) {
        // -- TDD
        // Downlink Bandwidth Support
        // Uplink Bandwidth Support
        // Downlink Modulation Support
        // Uplink Modulation Support
        // PHY Antenna Capability
        // Release Capability
        // MBSFN Capability
        // Duplexing Mode
        // PCFICH Power Offset
        // P-B
        // DL Cyclic Prefix Type
        // UL Cyclic Prefix Type
        // RF Config
        // PHICH Config
        // SCH Config
        // PRACH Config
        // PUSCH Config
        // PUCCH Config
        // SRS Config
        // Uplink Reference Signal Config
        // TDD Frame Structure Config
        // Data Report Mode
      } else if(instance->config.duplex_mode == 1) {
        // FDD
        // Downlink Bandwidth Support
        // Uplink Bandwidth Support
        // Downlink Modulation Support
        // Uplink Modulation Support
        // PHY Antenna Capability
        // Release Capability
        // MBSFN Capability
        // LAA Capability
        // Duplexing Mode
        // PCFICH Power Offset
        // P-B
        // DL Cyclic Prefix Type
        // UL Cyclic Prefix Type
        // RF Config
        // PHICH Config
        // SCH Config
        // PRACH Config
        // PUSCH Config
        // PUCCH Config
        // SRS Config
        // Uplink Reference Signal Config
        // Data Report Mode
      }
    }

    //todo fill
    (instance->callbacks.fapi_param_response)(fapi, &resp);
    return 0;
  }

  int fapi_config_request(fapi_t *fapi, fapi_config_req_t *req) {
    fapi_internal_t *instance = (fapi_internal_t *)fapi;
    fapi_config_resp_t resp;
    resp.header.message_id = FAPI_CONFIG_RESPONSE;
    resp.error_code = FAPI_MSG_OK;
    (instance->callbacks.fapi_config_response)(fapi, &resp);
    return 0;
  }

  int fapi_start_request(fapi_t *fapi, fapi_start_req_t *req) {
    fapi_internal_t *instance = (fapi_internal_t *)fapi;
    pthread_t fapi_thread;
    pthread_create(&fapi_thread, NULL, &fapi_thread_start, instance);
    return 0;
  }

  int fapi_dl_config_request(fapi_t *fapi, fapi_dl_config_req_t *req) {
    fapi_internal_t *instance = (fapi_internal_t *)fapi;
    instance->fapi->first_dl_config = true;
    return 0;
  }
  int fapi_ul_config_request(fapi_t *fapi, fapi_ul_config_req_t *req) {
    fapi_internal_t *instance = (fapi_internal_t *)fapi;
    return 0;
  }
  int fapi_hi_dci0_request(fapi_t *fapi, fapi_hi_dci0_req_t *req) {
    fapi_internal_t *instance = (fapi_internal_t *)fapi;
    return 0;
  }
  int fapi_tx_request(fapi_t *fapi, fapi_tx_req_t *req) {
    fapi_internal_t *instance = (fapi_internal_t *)fapi;

    for(int i = 0; i < req->body.number_of_pdus; ++i) {
      uint16_t len = req->body.pdus[i].pdu_length;
      uint32_t *data = req->body.pdus[i].tlvs[0].value;
      //printf("[FAPI] sfnsf:%d len:%d\n", req->sfn_sf,len);
      //
      instance->tx_byte_count += len;
      int sendto_result = sendto(instance->tx_sock, data, len, 0, (struct sockaddr *)&(instance->tx_addr), sizeof(instance->tx_addr));

      if(sendto_result == -1) {
        // error
      }
    }

    return 0;
  }
}

