/*
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under
 * the OAI Public License, Version 1.1  (the "License"); you may not use this file
 * except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.openairinterface.org/?page_id=698
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *-------------------------------------------------------------------------------
 * For more information about the OpenAirInterface (OAI) Software Alliance:
 *      contact@openairinterface.org
 */

#include "xran_fh_o_du.h"
#include "xran_pkt.h"
#include "xran_pkt_up.h"
#include "rte_ether.h"

#include "oran-config.h"
#include "oran-init.h"
#include "oaioran.h"

#include "common/utils/assertions.h"
#include "common_lib.h"

/* PRACH data samples are 32 bits wide (16bits for I/Q). Each packet contains
 * 840 samples for long sequence or 144 for short sequence. The payload length
 * is 840*16*2/8 octets.*/
#ifdef FCN_1_2_6_EARLIER
#define PRACH_PLAYBACK_BUFFER_BYTES (144 * 4L)
#else
#define PRACH_PLAYBACK_BUFFER_BYTES (840 * 4L)
#endif

// structure holding allocated memory for ports (multiple DUs) and sectors
// (multiple CCs)
static oran_port_instance_t gPortInst[XRAN_PORTS_NUM][XRAN_MAX_SECTOR_NR];
void *gxran_handle;

static uint32_t get_nSW_ToFpga_FTH_TxBufferLen(int mu, int sections)
{
  uint32_t xran_max_sections_per_slot = RTE_MAX(sections, XRAN_MIN_SECTIONS_PER_SLOT);
  uint32_t overhead = xran_max_sections_per_slot
                      * (RTE_PKTMBUF_HEADROOM + sizeof(struct rte_ether_hdr) + sizeof(struct xran_ecpri_hdr)
                         + sizeof(struct radio_app_common_hdr) + sizeof(struct data_section_hdr));
  if (mu <= 1) {
    return 13168 + overhead; /* 273*12*4 + 64* + ETH AND ORAN HDRs */
  } else if (mu == 3) {
    return 3328 + overhead;
  } else {
    assert(false && "numerology not supported\n");
  }
}

static uint32_t get_nFpgaToSW_FTH_RxBufferLen(int mu)
{
  /* note: previous code checked MTU:
   * mu <= 1: return mtu > XRAN_MTU_DEFAULT ? 13168 : XRAN_MTU_DEFAULT;
   * mu == 3: return mtu > XRAN_MTU_DEFAULT ? 3328 : XRAN_MTU_DEFAULT;
   * but I don't understand the interest: if the buffer is a big bigger, there
   * is no problem, or we could just set the MTU size as buffer size?!
   * Go with Max for the moment
   */
  if (mu <= 1) {
    return 13168; /* 273*12*4 + 64*/
  } else if (mu == 3) {
    return 3328;
  } else {
    assert(false && "numerology not supported\n");
  }
}

static struct xran_prb_map get_xran_prb_map_dl(const struct xran_fh_config *f)
{
  struct xran_prb_map prbmap = {
      .dir = XRAN_DIR_DL,
      .xran_port = 0,
      .band_id = 0,
      .cc_id = 0,
      .ru_port_id = 0,
      .tti_id = 0,
      .nPrbElm = 1,
  };
  struct xran_prb_elm *e = &prbmap.prbMap[0];
  e->nStartSymb = 0;
  e->numSymb = 14;
  e->nRBStart = 0;
  e->nRBSize = f->nDLRBs;
  e->nBeamIndex = 0;
  e->compMethod = f->ru_conf.compMeth;
  e->iqWidth = f->ru_conf.iqWidth;
  return prbmap;
}

static struct xran_prb_map get_xran_prb_map_ul(const struct xran_fh_config *f)
{
  struct xran_prb_map prbmap = {
      .dir = XRAN_DIR_UL,
      .xran_port = 0,
      .band_id = 0,
      .cc_id = 0,
      .ru_port_id = 0,
      .tti_id = 0,
      .start_sym_id = 0,
      .nPrbElm = 1,
  };
  struct xran_prb_elm *e = &prbmap.prbMap[0];
  e->nStartSymb = 0;
  e->numSymb = 14;
  e->nRBStart = 0;
  e->nRBSize = f->nULRBs;
  e->nBeamIndex = 0;
  e->compMethod = f->ru_conf.compMeth;
  e->iqWidth = f->ru_conf.iqWidth;
  return prbmap;
}

static uint32_t next_power_2(uint32_t num)
{
  uint32_t power = 2;
  while (power < num)
    power <<= 1;
  return power;
}

static uint32_t oran_allocate_uplane_buffers(
    void *instHandle,
    struct xran_buffer_list list[XRAN_MAX_ANTENNA_NR][XRAN_N_FE_BUF_LEN],
    struct xran_flat_buffer buf[XRAN_MAX_ANTENNA_NR][XRAN_N_FE_BUF_LEN][XRAN_NUM_OF_SYMBOL_PER_SLOT],
    uint32_t ant,
    uint32_t bufSize)
{
  xran_status_t status;
  uint32_t pool;
  // we need at least XRAN_N_FE_BUF_LEN * ant * XRAN_NUM_OF_SYMBOL_PER_SLOT
  // buffers, but xran_bm_init() uses rte_pktmbuf_pool_create() which
  // recommends to use a power of two for the buffers
  uint32_t numBufs = next_power_2(XRAN_N_FE_BUF_LEN * ant * XRAN_NUM_OF_SYMBOL_PER_SLOT);
  status = xran_bm_init(instHandle, &pool, numBufs, bufSize);
  AssertFatal(XRAN_STATUS_SUCCESS == status, "Failed at xran_bm_init(), status %d\n", status);
  printf("xran_bm_init() hInstance %p poolIdx %u elements %u size %u\n", instHandle, pool, numBufs, bufSize);
  int count = 0;
  for (uint32_t a = 0; a < ant; ++a) {
    for (uint32_t j = 0; j < XRAN_N_FE_BUF_LEN; ++j) {
      list[a][j].pBuffers = &buf[a][j][0];
      for (uint32_t k = 0; k < XRAN_NUM_OF_SYMBOL_PER_SLOT; ++k) {
        struct xran_flat_buffer *fb = &list[a][j].pBuffers[k];
        fb->nElementLenInBytes = bufSize;
        fb->nNumberOfElements = 1;
        fb->nOffsetInBytes = 0;
        void *ptr;
        void *mb;
        status = xran_bm_allocate_buffer(instHandle, pool, &ptr, &mb);
        AssertFatal(XRAN_STATUS_SUCCESS == status && ptr != NULL && mb != NULL,
                    "Failed at xran_bm_allocate_buffer(), status %d\n",
                    status);
        count++;
        fb->pData = ptr;
        fb->pCtrl = mb;
        memset(ptr, 0, bufSize);
      }
    }
  }
  printf("xran_bm_allocate_buffer() hInstance %p poolIdx %u count %d\n", instHandle, pool, count);
  return pool;
}

typedef struct oran_mixed_slot {
  uint32_t idx;
  uint32_t num_dlsym;
  uint32_t num_ulsym;
  uint32_t start_ulsym;
} oran_mixed_slot_t;
static oran_mixed_slot_t get_mixed_slot_info(const struct xran_frame_config *fconfig)
{
  oran_mixed_slot_t info = {0};
  for (size_t sl = 0; sl < fconfig->nTddPeriod; ++sl) {
    info.num_dlsym = info.num_ulsym = 0;
    for (size_t sym = 0; sym < XRAN_NUM_OF_SYMBOL_PER_SLOT; ++sym) {
      uint8_t t = fconfig->sSlotConfig[sl].nSymbolType[sym];
      if (t == 0 /* DL */) {
        info.num_dlsym++;
      } else if (t == 1 /* UL */) {
        if (info.num_ulsym == 0)
          info.start_ulsym = sym;
        info.num_ulsym++;
      } else if (t == 2 /* Mixed */) {
        info.idx = sl;
      } else {
        AssertFatal(false, "unknown symbol type %d\n", t);
      }
    }
    if (info.idx > 0)
      return info;
  }
  AssertFatal(false, "could not find mixed slot!\n");
  return info;
}

typedef struct oran_cplane_prb_config {
  uint8_t nTddPeriod;
  uint32_t mixed_slot_index;
  struct xran_prb_map slotMap;
  struct xran_prb_map mixedSlotMap;
} oran_cplane_prb_config;

static void oran_allocate_cplane_buffers(void *instHandle,
                                         struct xran_buffer_list list[XRAN_MAX_ANTENNA_NR][XRAN_N_FE_BUF_LEN],
                                         struct xran_flat_buffer buf[XRAN_MAX_ANTENNA_NR][XRAN_N_FE_BUF_LEN],
                                         uint32_t ant,
                                         uint32_t sect,
                                         uint32_t size_of_prb_map,
                                         const oran_cplane_prb_config *prb_conf)
{
  xran_status_t status;
  uint32_t poolSec;
  uint32_t numBufsSec = next_power_2(XRAN_N_FE_BUF_LEN * ant * XRAN_NUM_OF_SYMBOL_PER_SLOT * sect * XRAN_MAX_FRAGMENT);
  uint32_t bufSizeSec = sizeof(struct xran_section_desc);
  status = xran_bm_init(instHandle, &poolSec, numBufsSec, bufSizeSec);
  AssertFatal(XRAN_STATUS_SUCCESS == status, "Failed at xran_bm_init(), status %d\n", status);
  printf("xran_bm_init() hInstance %p poolIdx %u elements %u size %u\n", instHandle, poolSec, numBufsSec, bufSizeSec);

  uint32_t poolPrb;
  uint32_t numBufsPrb = next_power_2(XRAN_N_FE_BUF_LEN * ant * XRAN_NUM_OF_SYMBOL_PER_SLOT);
  uint32_t bufSizePrb = size_of_prb_map;
  status = xran_bm_init(instHandle, &poolPrb, numBufsPrb, bufSizePrb);
  AssertFatal(XRAN_STATUS_SUCCESS == status, "Failed at xran_bm_init(), status %d\n", status);
  printf("xran_bm_init() hInstance %p poolIdx %u elements %u size %u\n", instHandle, poolPrb, numBufsPrb, bufSizePrb);

  uint32_t count1 = 0;
  uint32_t count2 = 0;
  for (uint32_t a = 0; a < ant; a++) {
    for (uint32_t j = 0; j < XRAN_N_FE_BUF_LEN; ++j) {
      list[a][j].pBuffers = &buf[a][j];
      struct xran_flat_buffer *fb = list[a][j].pBuffers;
      fb->nElementLenInBytes = bufSizePrb;
      fb->nNumberOfElements = 1;
      fb->nOffsetInBytes = 0;
      void *ptr;
      void *mb;
      status = xran_bm_allocate_buffer(instHandle, poolPrb, &ptr, &mb);
      AssertFatal(XRAN_STATUS_SUCCESS == status && ptr != NULL && mb != NULL,
                  "Failed at xran_bm_allocate_buffer(), status %d\n",
                  status);
      count1++;
      fb->pData = ptr;
      fb->pCtrl = mb;

      // the original sample app code copies up to size_of_prb_map, but I think
      // this is wrong because the way it is computed leads to a number larger
      // than sizeof(map)
      struct xran_prb_map *p_rb_map = (struct xran_prb_map *)ptr;
      const struct xran_prb_map *src = &prb_conf->slotMap;
      if ((j % prb_conf->nTddPeriod) == prb_conf->mixed_slot_index)
        src = &prb_conf->mixedSlotMap;
      memcpy(p_rb_map, src, sizeof(*src));

      for (uint32_t elm_id = 0; elm_id < p_rb_map->nPrbElm; ++elm_id) {
        struct xran_prb_elm *pPrbElem = &p_rb_map->prbMap[elm_id];
        for (uint32_t k = 0; k < XRAN_NUM_OF_SYMBOL_PER_SLOT; ++k) {
          for (uint32_t m = 0; m < XRAN_MAX_FRAGMENT; ++m) {
            void *sd_ptr;
            void *sd_mb;
            status = xran_bm_allocate_buffer(instHandle, poolSec, &sd_ptr, &sd_mb);
            AssertFatal(XRAN_STATUS_SUCCESS == status,
                        "Failed at xran_bm_allocate_buffer(), status %d m %d k %d elm_id %d\n",
                        status,
                        m,
                        k,
                        elm_id);
            count2++;
            pPrbElem->p_sec_desc[k][m] = sd_ptr;
            memset(sd_ptr, 0, sizeof(struct xran_section_desc));
          }
        }
      }
    }
  }
  printf("xran_bm_allocate_buffer() hInstance %p poolIdx %u count %u\n", instHandle, poolPrb, count1);
  printf("xran_bm_allocate_buffer() hInstance %p poolIdx %u count %u\n", instHandle, poolSec, count2);
}

/* callback not actively used */
static void oai_xran_fh_rx_prach_callback(void *pCallbackTag, xran_status_t status)
{
  rte_pause();
}

static void oran_allocate_buffers(void *handle,
                                  int xran_inst,
                                  int num_sectors,
                                  oran_port_instance_t *portInstances,
                                  const struct xran_fh_config *fh_config)
{
  AssertFatal(num_sectors == 1, "only support one sector at the moment\n");
  oran_port_instance_t *pi = &portInstances[0];
  AssertFatal(handle != NULL, "no handle provided\n");
  uint32_t xran_max_antenna_nr = RTE_MAX(fh_config->neAxc, fh_config->neAxcUl);
  uint32_t xran_max_sections_per_slot = RTE_MAX(fh_config->max_sections_per_slot, XRAN_MIN_SECTIONS_PER_SLOT);
  uint32_t size_of_prb_map = sizeof(struct xran_prb_map) + sizeof(struct xran_prb_elm) * (xran_max_sections_per_slot - 1);

  pi->buf_list = _mm_malloc(sizeof(*pi->buf_list), 256);
  AssertFatal(pi->buf_list != NULL, "out of memory\n");
  oran_buf_list_t *bl = pi->buf_list;

  xran_status_t status;
  printf("xran_sector_get_instances() o_xu_id %d xran_handle %p\n", xran_inst, handle);
  status = xran_sector_get_instances(xran_inst, handle, num_sectors, &pi->instanceHandle);
  printf("-> hInstance %p\n", pi->instanceHandle);
  AssertFatal(status == XRAN_STATUS_SUCCESS, "get sector instance failed for XRAN nInstanceNum %d\n", xran_inst);

  const uint32_t txBufSize = get_nSW_ToFpga_FTH_TxBufferLen(fh_config->frame_conf.nNumerology, fh_config->max_sections_per_slot);
  oran_allocate_uplane_buffers(pi->instanceHandle, bl->src, bl->bufs.tx, xran_max_antenna_nr, txBufSize);

  oran_mixed_slot_t info = get_mixed_slot_info(&fh_config->frame_conf);
  struct xran_prb_map dlPm = get_xran_prb_map_dl(fh_config);
  struct xran_prb_map dlPmMixed = dlPm;
  dlPmMixed.prbMap[0].nStartSymb = 0;
  dlPmMixed.prbMap[0].numSymb = info.num_dlsym;
  oran_cplane_prb_config dlConf = {
      .nTddPeriod = fh_config->frame_conf.nTddPeriod,
      .mixed_slot_index = info.idx,
      .slotMap = dlPm,
      .mixedSlotMap = dlPmMixed,
  };
  oran_allocate_cplane_buffers(pi->instanceHandle,
                               bl->srccp,
                               bl->bufs.tx_prbmap,
                               xran_max_antenna_nr,
                               xran_max_sections_per_slot,
                               size_of_prb_map,
                               &dlConf);

  const uint32_t rxBufSize = get_nFpgaToSW_FTH_RxBufferLen(fh_config->frame_conf.nNumerology);
  oran_allocate_uplane_buffers(pi->instanceHandle, bl->dst, bl->bufs.rx, xran_max_antenna_nr, rxBufSize);

  struct xran_prb_map ulPm = get_xran_prb_map_ul(fh_config);
  struct xran_prb_map ulPmMixed = ulPm;
  ulPmMixed.prbMap[0].nStartSymb = info.start_ulsym;
  ulPmMixed.prbMap[0].numSymb = info.num_ulsym;
  oran_cplane_prb_config ulConf = {
      .nTddPeriod = fh_config->frame_conf.nTddPeriod,
      .mixed_slot_index = info.idx,
      .slotMap = ulPm,
      .mixedSlotMap = ulPmMixed,
  };
  oran_allocate_cplane_buffers(pi->instanceHandle,
                               bl->dstcp,
                               bl->bufs.rx_prbmap,
                               xran_max_antenna_nr,
                               xran_max_sections_per_slot,
                               size_of_prb_map,
                               &ulConf);

  // PRACH
  const uint32_t prachBufSize = PRACH_PLAYBACK_BUFFER_BYTES;
  oran_allocate_uplane_buffers(pi->instanceHandle, bl->prachdst, bl->bufs.prach, xran_max_antenna_nr, prachBufSize);
  // PRACH decomp buffer does not have separate DPDK-allocated memory pool
  // bufs, it points to the same pool as the prach buffer. Unclear to me why
  for (uint32_t a = 0; a < xran_max_antenna_nr; ++a) {
    for (uint32_t j = 0; j < XRAN_N_FE_BUF_LEN; ++j) {
      bl->prachdstdecomp[a][j].pBuffers = &bl->bufs.prachdecomp[a][j][0];
      for (uint32_t k = 0; k < XRAN_NUM_OF_SYMBOL_PER_SLOT; ++k) {
        struct xran_flat_buffer *fb = &bl->prachdstdecomp[a][j].pBuffers[k];
        fb->pData = bl->prachdst[a][j].pBuffers[k].pData;
      }
    }
  }

  struct xran_buffer_list *src[XRAN_MAX_ANTENNA_NR][XRAN_N_FE_BUF_LEN];
  struct xran_buffer_list *srccp[XRAN_MAX_ANTENNA_NR][XRAN_N_FE_BUF_LEN];
  struct xran_buffer_list *dst[XRAN_MAX_ANTENNA_NR][XRAN_N_FE_BUF_LEN];
  struct xran_buffer_list *dstcp[XRAN_MAX_ANTENNA_NR][XRAN_N_FE_BUF_LEN];
  struct xran_buffer_list *prach[XRAN_MAX_ANTENNA_NR][XRAN_N_FE_BUF_LEN];
  struct xran_buffer_list *prachdecomp[XRAN_MAX_ANTENNA_NR][XRAN_N_FE_BUF_LEN];
  for (uint32_t a = 0; a < XRAN_MAX_ANTENNA_NR; ++a) {
    for (uint32_t j = 0; j < XRAN_N_FE_BUF_LEN; ++j) {
      src[a][j] = &bl->src[a][j];
      srccp[a][j] = &bl->srccp[a][j];
      dst[a][j] = &bl->dst[a][j];
      dstcp[a][j] = &bl->dstcp[a][j];
      prach[a][j] = &bl->prachdst[a][j];
      prachdecomp[a][j] = &bl->prachdstdecomp[a][j];
    }
  }

  xran_5g_fronthault_config(pi->instanceHandle, src, srccp, dst, dstcp, oai_xran_fh_rx_callback, &portInstances->RxCbTag[0][0]);
  xran_5g_prach_req(pi->instanceHandle, prach, prachdecomp, oai_xran_fh_rx_prach_callback, &portInstances->PrachCbTag[0][0]);
}

int *oai_oran_initialize(const openair0_config_t *openair0_cfg)
{
  int32_t xret = 0;

  struct xran_fh_init init = {0};
  if (!set_fh_init(&init)) {
    printf("could not read FHI 7.2/ORAN config\n");
    return NULL;
  }
  print_fh_init(&init);

  /* read all configuration before starting anything */
  struct xran_fh_config xran_fh_config[XRAN_PORTS_NUM] = {0};
  for (int32_t o_xu_id = 0; o_xu_id < init.xran_ports; o_xu_id++) {
    if (!set_fh_config(o_xu_id, init.xran_ports, openair0_cfg, &xran_fh_config[o_xu_id])) {
      printf("could not read FHI 7.2/RU-specific config\n");
      return NULL;
    }
    print_fh_config(&xran_fh_config[o_xu_id]);
  }

  xret = xran_init(0, NULL, &init, NULL, &gxran_handle);
  if (xret != XRAN_STATUS_SUCCESS) {
    printf("xran_init failed %d\n", xret);
    exit(-1);
  }

  /** process all the O-RU|O-DU for use case */
  for (int32_t o_xu_id = 0; o_xu_id < init.xran_ports; o_xu_id++) {
    xret = xran_open(gxran_handle, &xran_fh_config[o_xu_id]);
    if (xret != XRAN_STATUS_SUCCESS) {
      printf("xran_open failed %d\n", xret);
      exit(-1);
    }

    int sector = 0;
    oran_port_instance_t *pi = &gPortInst[o_xu_id][sector];
    oran_allocate_buffers(gxran_handle, o_xu_id, 1, pi, &xran_fh_config[o_xu_id]);

    if ((xret = xran_reg_physide_cb(gxran_handle, oai_physide_dl_tti_call_back, NULL, 10, XRAN_CB_TTI)) != XRAN_STATUS_SUCCESS) {
      printf("xran_reg_physide_cb failed %d\n", xret);
      exit(-1);
    }
  }

  return (void *)gxran_handle;
}