#include <stdint.h>
#include <nfapi/oai_integration/vendor_ext.h>
#include <openair1/PHY/INIT/lte_init.c>
#include <executables/split_headers.h>

#define FS6_BUF_SIZE 100*1000
static int sockFS6;
#if 0

void pdsch_procedures(PHY_VARS_eNB *eNB,
                      L1_rxtx_proc_t *proc,
                      int harq_pid,
                      LTE_eNB_DLSCH_t *dlsch,
                      LTE_eNB_DLSCH_t *dlsch1,
                      LTE_eNB_UE_stats *ue_stats,
                      int ra_flag) {
  int frame=proc->frame_tx;
  int subframe=proc->subframe_tx;
  LTE_DL_eNB_HARQ_t *dlsch_harq=dlsch->harq_processes[harq_pid];
  LTE_DL_FRAME_PARMS *fp=&eNB->frame_parms;

  // 36-212
  if (NFAPI_MODE==NFAPI_MONOLITHIC || NFAPI_MODE==NFAPI_MODE_PNF) { // monolthic OR PNF - do not need turbo encoding on VNF
    // Replace dlsch_encoding
    // data is in
    // dlsch->harq_processes[harq_pid]->e
    feedDlschBuffers(eNB,
                     dlsch_harq->pdu,
                     dlsch_harq->pdsch_start,
                     dlsch,
                     frame,
                     subframe,
                     &eNB->dlsch_rate_matching_stats,
                     &eNB->dlsch_turbo_encoding_stats,
                     &eNB->dlsch_turbo_encoding_waiting_stats,
                     &eNB->dlsch_turbo_encoding_main_stats,
                     &eNB->dlsch_turbo_encoding_wakeup_stats0,
                     &eNB->dlsch_turbo_encoding_wakeup_stats1,
                     &eNB->dlsch_interleaving_stats);
    // 36-211
    dlsch_scrambling(fp,
                     0,
                     dlsch,
                     harq_pid,
                     get_G(fp,
                           dlsch_harq->nb_rb,
                           dlsch_harq->rb_alloc,
                           dlsch_harq->Qm,
                           dlsch_harq->Nl,
                           dlsch_harq->pdsch_start,
                           frame,subframe,
                           0),
                     0,
                     frame,
                     subframe<<1);
    dlsch_modulation(eNB,
                     eNB->common_vars.txdataF,
                     AMP,
                     frame,
                     subframe,
                     dlsch_harq->pdsch_start,
                     dlsch,
                     dlsch->ue_type==0 ? dlsch1 : (LTE_eNB_DLSCH_t *)NULL);
  }

  dlsch->active = 0;
  dlsch_harq->round++;
}

phy_procedures_eNB_TX_fs6(int sockFS6, uint64_t TS) {
  uint8_t bufferZone[FS6_BUF_SIZE];
  receiveSubFrame(sockFS6, TS, bufferZone, sizeof(bufferZone) );

  // We got
  // subframe number
  //

  for (int aa = 0; aa < fp->nb_antenna_ports_eNB; aa++) {
    memset (&eNB->common_vars.txdataF[aa][subframe * fp->ofdm_symbol_size * fp->symbols_per_tti],
            0,
            fp->ofdm_symbol_size * (fp->symbols_per_tti) * sizeof (int32_t));
  }

  if (NFAPI_MODE==NFAPI_MONOLITHIC || NFAPI_MODE==NFAPI_MODE_PNF) {
    if (is_pmch_subframe(frame,subframe,fp)) {
      pmch_procedures(eNB,proc);
    } else {
      // this is not a pmch subframe, so generate PSS/SSS/PBCH
      common_signal_procedures(eNB,proc->frame_tx, proc->subframe_tx);
    }
  }

  if (ul_subframe < 10)if (ul_subframe < 10) { // This means that there is a potential UL subframe that will be scheduled here
      for (i=0; i<NUMBER_OF_UE_MAX; i++) {
#if (LTE_RRC_VERSION >= MAKE_VERSION(14, 0, 0))

        if (eNB->ulsch[i] && eNB->ulsch[i]->ue_type >0) harq_pid = 0;
        else
#endif
          harq_pid = subframe2harq_pid(fp,ul_frame,ul_subframe);

        if (eNB->ulsch[i]) {
          ulsch_harq = eNB->ulsch[i]->harq_processes[harq_pid];
          /* Store first_rb and n_DMRS for correct PHICH generation below.
           * For PHICH generation we need "old" values of last scheduling
           * for this HARQ process. 'generate_eNB_dlsch_params' below will
           * overwrite first_rb and n_DMRS and 'generate_phich_top', done
           * after 'generate_eNB_dlsch_params', would use the "new" values
           * instead of the "old" ones.
           *
           * This has been tested for FDD only, may be wrong for TDD.
           *
           * TODO: maybe we should restructure the code to be sure it
           *       is done correctly. The main concern is if the code
           *       changes and first_rb and n_DMRS are modified before
           *       we reach here, then the PHICH processing will be wrong,
           *       using wrong first_rb and n_DMRS values to compute
           *       ngroup_PHICH and nseq_PHICH.
           *
           * TODO: check if that works with TDD.
           */
          ulsch_harq->previous_first_rb = ulsch_harq->first_rb;
          ulsch_harq->previous_n_DMRS = ulsch_harq->n_DMRS;
        }
      }
    }

  num_pdcch_symbols = eNB->pdcch_vars[subframe&1].num_pdcch_symbols;
  num_dci           = eNB->pdcch_vars[subframe&1].num_dci;

  if (num_dci > 0)
    if (NFAPI_MODE==NFAPI_MONOLITHIC || NFAPI_MODE==NFAPI_MODE_PNF) {
      generate_dci_top(num_pdcch_symbols,
                       num_dci,
                       &eNB->pdcch_vars[subframe&1].dci_alloc[0],
                       0,
                       AMP,
                       fp,
                       eNB->common_vars.txdataF,
                       subframe);
      num_mdci = eNB->mpdcch_vars[subframe &1].num_dci;

      if (num_mdci > 0) {
        generate_mdci_top (eNB, frame, subframe, AMP, eNB->common_vars.txdataF);
      }
    }

  // Now scan UE specific DLSCH
  LTE_eNB_DLSCH_t *dlsch0,*dlsch1;

  for (UE_id=0; UE_id<NUMBER_OF_UE_MAX; UE_id++) {
    dlsch0 = eNB->dlsch[(uint8_t)UE_id][0];
    dlsch1 = eNB->dlsch[(uint8_t)UE_id][1];

    if ((dlsch0)&&(dlsch0->rnti>0)&&
        (dlsch0->active == 1)
       ) {
      // get harq_pid
      harq_pid = dlsch0->harq_ids[frame%2][subframe];
      AssertFatal(harq_pid>=0,"harq_pid is negative\n");

      if (harq_pid>=8) {
        if (dlsch0->ue_type==0)
          LOG_E(PHY,"harq_pid:%d corrupt must be 0-7 UE_id:%d frame:%d subframe:%d rnti:%x \n",
                harq_pid,UE_id,frame,subframe,dlsch0->rnti);
      } else {
        // generate pdsch
        pdsch_procedures_fs6(eNB,
                             proc,
                             harq_pid,
                             dlsch0,
                             dlsch1,
                             &eNB->UE_stats[(uint32_t)UE_id],
                             0);
      }
    } else if ((dlsch0)&&(dlsch0->rnti>0)&&
               (dlsch0->active == 0)
              ) {
      // clear subframe TX flag since UE is not scheduled for PDSCH in this subframe (so that we don't look for PUCCH later)
      dlsch0->subframe_tx[subframe]=0;
    }
  }

  generate_phich_top(eNB,
                     proc,
                     AMP);
}
#endif

void prach_eNB_extract(PHY_VARS_eNB *eNB,RU_t *ru,int frame,int subframe) {
}

void prach_eNB_process(PHY_VARS_eNB *eNB,RU_t *ru,int frame,int subframe) {
}

void phy_procedures_eNB_uespec_RX_extract(PHY_VARS_eNB *phy_vars_eNB,L1_rxtx_proc_t *proc) {
}

void phy_procedures_eNB_uespec_RX_process(PHY_VARS_eNB *phy_vars_eNB,L1_rxtx_proc_t *proc) {
}

void phy_procedures_eNB_TX_fs6() {
}

void DL_du_fs6(RU_t *ru, int frame, int subframe, uint64_t TS) {
  RU_proc_t *ru_proc=&ru->proc;

  for (int i=0; i<ru->num_eNB; i++) {
    uint8_t bufferZone[FS6_BUF_SIZE];
    receiveSubFrame(sockFS6, TS, bufferZone, sizeof(bufferZone) );
  }

  phy_procedures_eNB_TX_fs6();
  /* Fixme: datamodel issue: a ru is supposed to be connected to several eNB
  L1_rxtx_proc_t * L1_proc = &proc->L1_proc;
  ru_proc->timestamp_tx = L1_proc->timestamp_tx;
  ru_proc->subframe_tx  = L1_proc->subframe_tx;
  ru_proc->frame_tx     = L1_proc->frame_tx;
  */
  feptx_prec(ru);
  feptx_ofdm(ru);
  tx_rf(ru);
}

void UL_du_fs6(RU_t *ru, int frame, int subframe) {
  RU_proc_t *ru_proc=&ru->proc;
  int tmpf=frame, tmpsf=subframe;
  rx_rf(ru,&tmpf,&tmpsf);
  AssertFatal(tmpf==frame && tmpsf==subframe, "lost synchronization\n");
  ru_proc->frame_tx = (ru_proc->frame_tx+ru_proc->frame_offset)&1023;
  // Fixme: datamodel issue
  PHY_VARS_eNB *eNB = RC.eNB[0][0];
  L1_proc_t *proc           = &eNB->proc;
  L1_rxtx_proc_t *L1_proc = &proc->L1_proc;
  LTE_DL_FRAME_PARMS *fp = &ru->frame_parms;
  proc->frame_rx    = frame;
  proc->subframe_rx = subframe;

  if (NFAPI_MODE==NFAPI_MODE_PNF) {
    // I am a PNF and I need to let nFAPI know that we have a (sub)frame tick
    //add_subframe(&frame, &subframe, 4);
    //oai_subframe_ind(proc->frame_tx, proc->subframe_tx);
    oai_subframe_ind(proc->frame_rx, proc->subframe_rx);
  }

  uint8_t bufferZone[FS6_BUF_SIZE];
  prach_eNB_extract(eNB,NULL,proc->frame_rx,proc->subframe_rx);

  if (NFAPI_MODE==NFAPI_MONOLITHIC || NFAPI_MODE==NFAPI_MODE_PNF) {
    phy_procedures_eNB_uespec_RX_extract(eNB, &proc->L1_proc);
  }

  for (int i=0; i<ru->num_eNB; i++) {
    sendSubFrame(sockFS6,bufferZone, sizeof(bufferZone) );
  }
}

void DL_cu_fs6(RU_t *ru,int frame, int subframe) {
  // Fixme: datamodel issue
  PHY_VARS_eNB *eNB = RC.eNB[0][0];
  L1_proc_t *proc           = &eNB->proc;
  pthread_mutex_lock(&eNB->UL_INFO_mutex);
  eNB->UL_INFO.frame     = proc->frame_rx;
  eNB->UL_INFO.subframe  = proc->subframe_rx;
  eNB->UL_INFO.module_id = eNB->Mod_id;
  eNB->UL_INFO.CC_id     = eNB->CC_id;
  eNB->if_inst->UL_indication(&eNB->UL_INFO);
  pthread_mutex_unlock(&eNB->UL_INFO_mutex);
  uint8_t bufferZone[FS6_BUF_SIZE];
  phy_procedures_eNB_TX(eNB, &proc->L1_proc, 1);
  sendSubFrame(sockFS6, bufferZone, sizeof(bufferZone) );
}

void UL_cu_fs6(RU_t *ru,int frame, int subframe, uint64_t TS) {
  uint8_t bufferZone[FS6_BUF_SIZE];
  receiveSubFrame(sockFS6, TS, bufferZone, sizeof(bufferZone) );
  // Fixme: datamodel issue
  PHY_VARS_eNB *eNB = RC.eNB[0][0];
  L1_proc_t *proc           = &eNB->proc;
  prach_eNB_process(eNB,NULL,proc->frame_rx,proc->subframe_rx);
  release_UE_in_freeList(eNB->Mod_id);

  if (NFAPI_MODE==NFAPI_MONOLITHIC || NFAPI_MODE==NFAPI_MODE_PNF) {
    phy_procedures_eNB_uespec_RX_process(eNB, &proc->L1_proc);
  }
}

void *cu_fs6(void *arg) {
  RU_t               *ru      = (RU_t *)arg;
  RU_proc_t          *proc    = &ru->proc;
  int64_t           AbsoluteSubframe=-1;
  init_frame_parms(&ru->frame_parms,1);
  wait_sync("ru_thread");

  while(1) {
    AbsoluteSubframe++;
    int subframe=AbsoluteSubframe%10;
    int frame=(AbsoluteSubframe/10)%1024;
    UL_cu_fs6(ru, frame,subframe, AbsoluteSubframe);
    DL_cu_fs6(ru, frame,subframe);
  }

  return NULL;
}

void *du_fs6(void *arg) {
  RU_t               *ru      = (RU_t *)arg;
  RU_proc_t          *proc    = &ru->proc;
  int64_t           AbsoluteSubframe=-1;
  fill_rf_config(ru,ru->rf_config_file);
  init_frame_parms(&ru->frame_parms,1);
  phy_init_RU(ru);
  openair0_device_load(&ru->rfdevice,&ru->openair0_cfg);
  wait_sync("ru_thread");

  while(1) {
    AbsoluteSubframe++;
    int subframe=AbsoluteSubframe%10;
    int frame=(AbsoluteSubframe/10)%1024;
    UL_du_fs6(ru, frame,subframe);
    DL_du_fs6(ru, frame,subframe, AbsoluteSubframe);
  }

  return NULL;
}