/*
 * SPDX-License-Identifier: LicenseRef-CSSL-1.0
 */

/*!
 * \brief Top-level routines for generating and decoding the PRACH physical channel V15.4 2018-12
 */

#include "PHY/defs_gNB.h"
#include "SCHED_NR/sched_nr.h"
#include "PHY/NR_TRANSPORT/nr_transport_proto.h"
#include "PHY/NR_TRANSPORT/nr_transport_common_proto.h"
#include "openair1/PHY/NR_TRANSPORT/nr_prach.h"

void init_nr_prach(PHY_VARS_gNB *gNB)
{
  int num_prach = 16;
  bool ret;
  ret = spsc_q_alloc(&gNB->prach_ru_queue, num_prach, sizeof(prach_item_t));
  DevAssert(ret);
  ret = spsc_q_alloc(&gNB->prach_l1rx_queue, num_prach, sizeof(prach_item_t));
  DevAssert(ret);
}

void reset_nr_prach(PHY_VARS_gNB *gNB)
{
  spsc_q_free(&gNB->prach_ru_queue);
  spsc_q_free(&gNB->prach_l1rx_queue);
}

void free_nr_prach_entry(prach_item_t *p)
{
  free(p->prach_buf);
}

static bool drop_old_prach(const void *data, void *user)
{
  const prach_item_t *p = data;
  const fsn_t *now = user;
  // account for long PRACH over more than 1 slot
  const fsn_t t = {p->frame, p->slot + p->num_slots - 1, now->mu};
  bool drop = fsn_in_the_past(t, *now);
  if (drop)
    LOG_E(NR_PHY, "%4d.%2d PRACH job is in the past (%4d.%2d)\n", now->f, now->s, t.f, t.s);
  return drop;
}

static bool get_current_prach(const void *data, void *user)
{
  const prach_item_t *p = data;
  const fsn_t *now = user;
  // account for long PRACH over more than 1 slot
  const fsn_t t = {p->frame, p->slot + p->num_slots - 1, now->mu};
  return fsn_equal(t, *now);
}

bool get_next_nr_prach(spsc_q_t *q, const fsn_t *now, prach_item_t *p)
{
  spsc_q_drop_while(q, drop_old_prach, (void *)now);
  return spsc_q_get_if(q, get_current_prach, (void *)now, p, sizeof(*p));
}

void nr_schedule_rx_prach(PHY_VARS_gNB *gNB, int SFN, int Slot, nfapi_nr_prach_pdu_t *prach_pdu)
{
  const int fmt = prach_pdu->prach_format;
  const NR_DL_FRAME_PARMS *fp = &gNB->frame_parms;
  const nfapi_nr_prach_config_t *cfg = &gNB->gNB_config.prach_config;
  const nfapi_nr_num_prach_fd_occasions_t *occ = &cfg->num_prach_fd_occasions_list[prach_pdu->num_ra];
  prach_item_t prach = {
      .frame = SFN,
      .slot = Slot,
      .num_slots = fmt < 4 ? get_long_prach_dur(fmt, fp->numerology_index) : 1,
      .pdu = *prach_pdu,
      .rootSequenceIndex = occ->prach_root_sequence_index.value,
      .numrootSequenceIndex = occ->num_root_sequences.value,
      .msg1_frequencystart = occ->k1.value,
      .mu = cfg->prach_sub_c_spacing.value,
      .prach_sequence_length = cfg->prach_sequence_length.value,
      .restricted_set = cfg->restricted_set_config.value,
      .numerology_index = fp->numerology_index,
      .nb_rx = gNB->gNB_config.carrier_config.num_rx_ant.value,
      .Xu = gNB->X_u,
      .rx_prach = &gNB->rx_prach,
      // TODO can be made permanently allocated?
      .prach_buf = calloc_or_fail(1, sizeof(c16_t) * prach.nb_rx * NUMBER_OF_NR_RU_PRACH_OCCASIONS_MAX * NR_PRACH_SEQ_LEN_L),
  };
  if (gNB->common_vars.beam_id) {
    int n_symb = get_nr_prach_duration(prach_pdu->prach_format);
    AssertFatal(prach_pdu->beamforming.dig_bf_interface < NFAPI_MAX_NUM_BG_IF,
                "impossible beams size %d\n",
                prach_pdu->beamforming.dig_bf_interface);
    for (int i = 0; i < prach_pdu->beamforming.dig_bf_interface; i++) {
      int fapi_beam_idx = prach_pdu->beamforming.prgs_list[0].dig_bf_interface_list[i].beam_idx;
      int start_symb = prach_pdu->prach_start_symbol + i * n_symb;
      int bitmap = SL_to_bitmap(start_symb, n_symb);
      prach.beams[i] = beam_index_allocation(gNB->enable_analog_das,
                                              fapi_beam_idx,
                                              &gNB->common_vars,
                                              Slot,
                                              gNB->frame_parms.symbols_per_slot,
                                              bitmap);
    }
  }
  bool found = spsc_q_put(&gNB->prach_ru_queue, &prach, sizeof(prach));
  if (!found)
    LOG_W(NR_PHY, "%4d.%2d PRACH occ queue is full: dropping PRACH request\n", SFN, Slot);
}

static void rx_nr_prach_ru_internal(prach_item_t *p,
                                    int beam_id,
                                    int prachStartSymbol,
                                    int prachOccasion,
                                    int32_t **rxdata,
                                    NR_DL_FRAME_PARMS *fp,
                                    int N_TA_offset)
{
  int sample_offset_slot;
  const int sum = fp->ofdm_symbol_size + fp->nb_prefix_samples;
  const int sum0 = fp->ofdm_symbol_size + fp->nb_prefix_samples0;
  if (prachStartSymbol == 0) {
    sample_offset_slot = 0;
  } else if (fp->slots_per_subframe == 1) {
    if (prachStartSymbol <= 7)
      sample_offset_slot = sum * (prachStartSymbol - 1) + sum0;
    else
      sample_offset_slot = sum * (prachStartSymbol - 2) + sum0 * 2;
  } else {
    if (!(p->slot % (fp->slots_per_subframe / 2)))
      sample_offset_slot = sum * (prachStartSymbol - 1) + sum0;
    else
      sample_offset_slot = sum * prachStartSymbol;
  }

  LOG_D(PHY,
        "frame %d, slot %d: doing rx_nr_prach_ru for format %d, numRA %d, prachStartSymbol %d, prachOccasion %d\n",
        p->frame,
        p->slot,
        p->pdu.prach_format,
        p->pdu.num_ra,
        prachStartSymbol,
        prachOccasion);
  int reps;
  int Ncp;
  int dftlen;
  int mu = p->numerology_index;

  if (p->prach_sequence_length == 0) {
    LOG_D(PHY,
          "PRACH in %d.%d, format %d, msg1_frequencyStart %d\n",
          p->frame,
          p->slot,
          p->pdu.prach_format,
          p->msg1_frequencystart);
    switch (p->pdu.prach_format) {
      case 0:
        reps = 1;
        Ncp = 3168;
        dftlen = 24576;
        break;

      case 1:
        reps = 2;
        Ncp = 21024;
        dftlen = 24576;
        break;

      case 2:
        reps = 4;
        Ncp = 4688;
        dftlen = 24576;
        break;

      case 3:
        reps = 4;
        Ncp = 3168;
        dftlen = 6144;
        break;

      default:
        AssertFatal(1 == 0, "Illegal prach format %d for length 839\n", p->pdu.prach_format);
        break;
    }
  } else {
    LOG_D(PHY,
          "PRACH in %d.%d, format %s, msg1_frequencyStart %d,startSymbol %d\n",
          p->frame,
          p->slot,
          prachfmt[p->pdu.prach_format],
          p->msg1_frequencystart,
          prachStartSymbol);
    switch (p->pdu.prach_format) {
      case 4: // A1
        reps = 2;
        Ncp = 288 >> mu;
        break;

      case 5: // A2
        reps = 4;
        Ncp = 576 >> mu;
        break;

      case 6: // A3
        reps = 6;
        Ncp = 864 >> mu;
        break;

      case 7: // B1
        reps = 2;
        Ncp = 216 >> mu;
        break;

        /*
        // B2 and B3 do not exist in FAPI
        case 4: //B2
          reps = 4;
          Ncp = 360 >> mu;
          break;

        case 5: //B3
          reps = 6;
          Ncp = 504 >> mu;
          break;
        */

      case 8: // B4
        reps = 12;
        Ncp = 936 >> mu;
        break;

      case 9: // C0
        reps = 1;
        Ncp = 1240 >> mu;
        break;

      case 10: // C2
        reps = 4;
        Ncp = 2048 >> mu;
        break;

      default:
        AssertFatal(1 == 0, "unknown prach format %x\n", p->pdu.prach_format);
        break;
    }
    dftlen = 2048 >> mu;
  }

  //actually what we should be checking here is how often the current prach crosses a 0.5ms boundary.
  //I am not quite sure for which paramter set this would be the case,
  //so I will ignore it for now and just check if the prach starts on a 0.5ms boundary
  if (p->numerology_index == 0) {
    if (prachStartSymbol == 0 || prachStartSymbol == 7)
      Ncp += 16;
  } else {
    if (p->slot % (fp->slots_per_subframe / 2) == 0 && prachStartSymbol == 0)
      Ncp += 16;
  }

  switch(fp->samples_per_subframe) {
  case 7680:
    // 5 MHz @ 7.68 Ms/s
    Ncp >>= 2;
    dftlen >>= 2;
    break;

  case 15360:
    // 10, 15 MHz @ 15.36 Ms/s
    Ncp >>= 1;
    dftlen >>= 1;
    break;

  case 23040:
    // 20 MHz @ 23.04 Ms/s
    Ncp = (Ncp * 3) / 4;
    dftlen = (dftlen * 3) / 4;
    break;

  case 30720:
    // 20, 25, 30 MHz @ 30.72 Ms/s
    break;

  case 46080:
    // 40 MHz @ 46.08 Ms/s
    Ncp = (Ncp*3)/2;
    dftlen = (dftlen*3)/2;
    break;

  case 61440:
    // 40, 50, 60 MHz @ 61.44 Ms/s
    Ncp <<= 1;
    dftlen <<= 1;
    break;

  case 92160:
    // 50, 60, 70, 80, 90 MHz @ 92.16 Ms/s
    Ncp *= 3;
    dftlen *= 3;
    break;

  case 122880:
    // 70, 80, 90, 100 MHz @ 122.88 Ms/s
    Ncp <<= 2;
    dftlen <<= 2;
    break;

  case 184320:
    // 100 MHz @ 184.32 Ms/s
    Ncp = Ncp*6;
    dftlen = dftlen*6;
    break;

  case 245760:
    // 200 MHz @ 245.76 Ms/s
    Ncp <<= 3;
    dftlen <<= 3;
    break;

  default:
    AssertFatal(1==0,"sample rate %f MHz not supported for numerology %d\n", fp->samples_per_subframe / 1000.0, mu);
  }

  const dft_size_idx_t dftsize = get_dft(dftlen);

  // Do forward transform
  if (LOG_DEBUGFLAG(DEBUG_PRACH)) {
    LOG_D(PHY, "rx_prach: Doing PRACH FFT for nb_rx:%d Ncp:%d dftlen:%d\n", p->nb_rx, Ncp, dftlen);
  }

  const unsigned int K = get_prach_K(p->prach_sequence_length, p->pdu.prach_format, p->numerology_index, p->mu);
  const uint8_t kbar = get_PRACH_k_bar(p->mu, p->numerology_index);

  int n_ra_prb = p->msg1_frequencystart;
  int k                   = (12*n_ra_prb) - 6*fp->N_RB_UL;

  int N_ZC = (p->prach_sequence_length == 0) ? 839 : 139;

  if (k<0) k+=(fp->ofdm_symbol_size);
  
  k*=K;
  k+=kbar;

  for (int aa = 0; aa < p->nb_rx; aa++) {
    // Fixme: slot or slot makes no sense ???
    int slot2 = p->prach_sequence_length ? p->slot : p->slot;
    int idx = aa + beam_id * p->nb_rx;
    c16_t *prach = (c16_t *)&rxdata[idx][get_samples_slot_timestamp(fp, slot2) + sample_offset_slot - N_TA_offset];

    // do DFT
    c16_t *prach2 = prach + Ncp;
    c16_t rxsigF_tmp[N_ZC];
    memset(rxsigF_tmp, 0, sizeof(rxsigF_tmp));
    for (int i = 0; i < reps; i++, prach2 += dftlen) {
      c16_t tmp[dftlen] __attribute__((aligned(32)));
      dft(dftsize, (int16_t *)prach2, (int16_t *)tmp, 1);
      // Coherent combining of PRACH repetitions (assumes channel does not change, to be revisted for "long" PRACH)
      LOG_D(PHY, "Doing PRACH combining of %d reptitions N_ZC %d\n", reps, N_ZC);
      //    if (k+N_ZC > dftlen) { // PRACH signal is split around DC
      int k2 = k;
      for (int j = 0; j < N_ZC; j++, k2++) {
        if (k2 == dftlen)
          k2 = 0;
        rxsigF_tmp[j] = c16add(rxsigF_tmp[j], tmp[k2]);
      }
    }
    memcpy(p->prach_buf[aa][prachOccasion], rxsigF_tmp, sizeof(rxsigF_tmp));
  }
}

void rx_nr_prach_ru(prach_item_t *p, int32_t **rxdata, NR_DL_FRAME_PARMS *fp, int N_TA_offset)
{
  int N_dur = get_nr_prach_duration(p->pdu.prach_format);
  LOG_D(NR_PHY_RACH, "%d.%d try to decode %d occasions \n", p->frame, p->slot, p->pdu.num_prach_ocas);
  for (int prach_oc = 0; prach_oc < p->pdu.num_prach_ocas; prach_oc++) {
    int prachStartSymbol = p->pdu.prach_start_symbol + prach_oc * N_dur;
    int beam_id = p->beams[prach_oc];
    // comment FK: the standard 38.211 section 5.3.2 has one extra term +14*N_RA_slot. This is because there prachStartSymbol is
    // given wrt to start of the 15kHz slot or 60kHz slot. Here we work slot based, so this function is anyway only called in slots
    // where there is PRACH. Its up to the MAC to schedule another PRACH PDU in the case there are there N_RA_slot \in {0,1}.
    rx_nr_prach_ru_internal(p, beam_id, prachStartSymbol, prach_oc, rxdata, fp, N_TA_offset);
  }
}

rx_prach_out_t rx_nr_prach(const prach_item_t *in, int occasion)
{
  rx_prach_out_t out = {};
  uint16_t preamble_index0 = 0;
  uint16_t numshift = 0;
  int first_nonzero_root_idx = 0;
  bool new_dft = false;
  int log2_ifft_size = 10;

  const int nb_rx = in->nb_rx;
  const int NCS = in->pdu.num_cs;
  const int prach_fmt = in->pdu.prach_format;
  const int N_ZC = in->prach_sequence_length == 0 ? 839 : 139;

  LOG_D(NR_PHY_RACH,
        "L1 PRACH RX: rooSequenceIndex %d, numRootSeqeuences %d, NCS %d, N_ZC %d, format %d \n",
        in->rootSequenceIndex,
        in->numrootSequenceIndex,
        NCS,
        N_ZC,
        prach_fmt);

  if (LOG_DEBUGFLAG(DEBUG_PRACH)) {
    if ((in->frame & 1023) < 20)
      LOG_D(PHY, "PRACH (gNB) : running rx_prach for slot %d, rootSequenceIndex %d\n", in->slot, in->rootSequenceIndex);
  }

  start_meas(in->rx_prach);

  const uint16_t *prach_root_sequence_map =
      in->prach_sequence_length == 0 ? prach_root_sequence_map_0_3 : prach_root_sequence_map_abc;

  // PDP is oversampled, e.g. 1024 sample instead of 839
  // Adapt the NCS (zero-correlation zones) with oversampling factor e.g. 1024/839
  int NCS2 = N_ZC == 839 ? (NCS << 10) / 839 : (NCS << 8) / 139;

  if (NCS2 == 0)
    NCS2 = N_ZC;

  int preamble_offset = 0, preamble_offset_old = 99;

  int16_t preamble_shift = 0;
  const int dft_sz = N_ZC == 839 ? 1024 : 256;
  int32_t prach_ifft[dft_sz] __attribute__((aligned(32)));
  for (int preamble_index = 0; preamble_index < 64; preamble_index++) {
    if (LOG_DEBUGFLAG(DEBUG_PRACH)) {
      int en = dB_fixed(signal_energy((int32_t *)in->prach_buf[0][occasion], N_ZC == 839 ? 840 : 140));
      if (en > 60)
        LOG_D(PHY, "frame %d, slot %d : Trying preamble %d \n", in->frame, in->slot, preamble_index);
    }
    if (in->restricted_set == 0) {
      // This is the relative offset in the root sequence table (5.7.2-4 from 36.211) for the given preamble index
      preamble_offset = ((NCS==0)? preamble_index : (preamble_index/(N_ZC/NCS)));

      if (preamble_offset != preamble_offset_old) {
        preamble_offset_old = preamble_offset;
        new_dft = true;
        // This is the \nu corresponding to the preamble index
        preamble_shift  = 0;
      } else {
        preamble_shift -= NCS;

        if (preamble_shift < 0)
          preamble_shift += N_ZC;
      }
    } else { // This is the high-speed case
      new_dft = false;
      uint16_t nr_du[NR_PRACH_SEQ_LEN_L - 1];
      nr_fill_du(N_ZC, prach_root_sequence_map, nr_du);
      // set preamble_offset to initial rootSequenceIndex and look if we need more root sequences for this
      // preamble index and find the corresponding cyclic shift
      // Check if all shifts for that root have been processed
      int n_shift_ra = 0, n_shift_ra_bar, d_start = 0;
      if (preamble_index0 == numshift) {
        bool not_found = true;
        new_dft = true;
        preamble_index0 -= numshift;
        while (not_found) {
          // current root depending on rootSequenceIndex
          int index = (in->rootSequenceIndex + preamble_offset) % N_ZC;
          int u = nr_du[prach_root_sequence_map[index]];
          uint16_t n_group_ra = 0;

          if (u < (N_ZC / 3) && u >= NCS) {
            n_shift_ra = u / NCS;
            d_start = (u << 1) + (n_shift_ra * NCS);
            n_group_ra = N_ZC / d_start;
            n_shift_ra_bar = max(0, (N_ZC - (u << 1) - (n_group_ra * d_start)) / N_ZC);
          } else if (u >= (N_ZC / 3) && u <= ((N_ZC - NCS) >> 1)) {
            n_shift_ra = (N_ZC - (u << 1)) / NCS;
            d_start = N_ZC - (u << 1) + (n_shift_ra * NCS);
            n_group_ra = u / d_start;
            n_shift_ra_bar = min(n_shift_ra, max(0, (u - (n_group_ra * d_start)) / NCS));
          } else {
            n_shift_ra = 0;
            n_shift_ra_bar = 0;
          }

          // This is the number of cyclic shifts for the current root u
          numshift = (n_shift_ra * n_group_ra) + n_shift_ra_bar;
          // skip to next root and recompute parameters if numshift==0
          numshift > 0 ? not_found = false : preamble_offset++;
        }
      }

      if (n_shift_ra>0)
        preamble_shift = -(d_start * (preamble_index0 / n_shift_ra)
                           + (preamble_index0 % n_shift_ra) * NCS); // minus because the channel is h(t -\tau + Cv)
      else
        preamble_shift = 0;

      if (preamble_shift < 0)
        preamble_shift+=N_ZC;

      preamble_index0++;

      if (preamble_index == 0)
        first_nonzero_root_idx = preamble_offset;
    }

    // Compute DFT of RX signal (conjugate in->rxsigF[occasion], results in conjugate output) for each new rootSequenceIndex
    if (LOG_DEBUGFLAG(DEBUG_PRACH)) {
      int en = dB_fixed(signal_energy((int32_t *)in->prach_buf[0][occasion], 840));
      if (en>60)
        LOG_D(PHY,
              "frame %d, slot %d : preamble index %d, NCS %d, N_ZC/NCS %d: offset %d, preamble shift %d , en %d)\n",
              in->frame,
              in->slot,
              preamble_index,
              NCS,
              N_ZC / NCS,
              preamble_offset,
              preamble_shift,
              en);
    }

    LOG_D(NR_PHY_RACH,
          "PRACH RX preamble_index %d, preamble_offset %d, preamb shift %d new dft %d\n",
          preamble_index,
          preamble_offset,
          preamble_shift,
          new_dft);

    if (new_dft) {
      new_dft = false;

      c16_t *Xu = in->Xu[preamble_offset - first_nonzero_root_idx];
      LOG_D(PHY,"PRACH RX new dft preamble_offset-first_nonzero_root_idx %d\n",preamble_offset-first_nonzero_root_idx);

      memset(prach_ifft, 0, sizeof(prach_ifft));
      if (LOG_DUMPFLAG(DEBUG_PRACH)) {
        LOG_M("prach_rxF0.m", "prach_rxF0", in->prach_buf[0][occasion], N_ZC, 1, 1);
        LOG_M("prach_rxF1.m", "prach_rxF1", in->prach_buf[1][occasion], 6144, 1, 1);
      }
      c16_t prachF[dft_sz] __attribute__((aligned(32)));
      for (int aa = 0; aa < nb_rx; aa++) {
        // Do componentwise product with Xu* on each antenna
        for (int offset = 0; offset < N_ZC; offset++) {
          prachF[offset] = c16MulConjShift(Xu[offset], in->prach_buf[aa][occasion][offset], 15);
        }
        memset(prachF + N_ZC, 0, sizeof(*prachF) * (dft_sz - N_ZC));
        // Now do IFFT of size 1024 (N_ZC=839) or 256 (N_ZC=139)
        c16_t prach_ifft_tmp[dft_sz] __attribute__((aligned(32)));
        idft(get_idft(dft_sz), (int16_t *)prachF, (int16_t *)prach_ifft_tmp, 1);
        // compute energy and accumulate over receive antennas
        for (int i = 0; i < dft_sz; i++)
          prach_ifft[i] += squaredMod(prach_ifft_tmp[i]);

        if (LOG_DUMPFLAG(DEBUG_PRACH)) {
          if (aa == 0)
            LOG_M("prach_rxF_comp0.m","prach_rxF_comp0", prachF, 1024, 1, 1);
          if (aa == 1)
            LOG_M("prach_rxF_comp1.m","prach_rxF_comp1", prachF, 1024, 1, 1);
        }

      } // antennas_rx

      // Normalization of energy over ifft and receive antennas
      if (N_ZC == 839) {
        log2_ifft_size = 10;
        for (int i = 0; i < 1024; i++)
          prach_ifft[i] = (prach_ifft[i]>>log2_ifft_size)/nb_rx;
      } else {
        log2_ifft_size = 8;
        for (int i = 0; i < 256; i++)
          prach_ifft[i] = (prach_ifft[i]>>log2_ifft_size)/nb_rx;
      }

    } // new dft

    // check energy in nth time shift, for

    int preamble_shift2 = preamble_shift == 0 ? 0 : (preamble_shift << log2_ifft_size) / N_ZC;

    for (int i = 0; i < NCS2; i++) {
      int lev = prach_ifft[preamble_shift2 + i];
      int levdB = dB_fixed_times10(lev);
      if (levdB > out.max_preamble_energy || (levdB == out.max_preamble_energy && out.max_preamble_delay > i)) {
        LOG_D(NR_PHY_RACH, "preamble_index %d, delay %d en %d dB > %d dB\n", preamble_index, i, levdB, out.max_preamble_energy);
        out.max_preamble_energy = levdB;
        out.max_preamble_delay = i; // Note: This has to be normalized to the 30.72 Ms/s sampling rate
        out.max_preamble = preamble_index;
      }
    }
  } // preamble_index

  // The conversion from *max_preamble_delay from TA value is done here.
  // It is normalized to the 30.72 Ms/s, considering the numerology, N_RB and the sampling rate
  // See table 6.3.3.1 -1 and -2 in 38211.

  // Format 0, 1, 2: 24576 samples @ 30.72 Ms/s, 98304 samples @ 122.88 Ms/s
  // By solving:
  // max_preamble_delay * ( (24576*(fs/30.72M)) / 1024 ) / fs = TA * 16 * 64 / 2^mu * Tc

  // Format 3: 6144 samples @ 30.72 Ms/s, 24576 samples @ 122.88 Ms/s
  // By solving:
  // max_preamble_delay * ( (6144*(fs/30.72M)) / 1024 ) / fs = TA * 16 * 64 / 2^mu * Tc

  // Format >3: 2048/2^mu samples @ 30.72 Ms/s, 2048/2^mu * 4 samples @ 122.88 Ms/s
  // By solving:
  // max_preamble_delay * ( (2048/2^mu*(fs/30.72M)) / 256 ) / fs = TA * 16 * 64 / 2^mu * Tc
  int mu = in->numerology_index;
  if (in->prach_sequence_length == 0) {
    if (prach_fmt == 0 || prach_fmt == 1 || prach_fmt == 2)
      out.max_preamble_delay *= 3 * (1 << mu) / 2;
    else if (prach_fmt == 3)
      out.max_preamble_delay *= 3 * (1 << mu) / 8;
  } else
    out.max_preamble_delay /= 2;

  stop_meas(in->rx_prach);
  return out;
}
