/*
 * SPDX-License-Identifier: LicenseRef-CSSL-1.0
 */
 
#include "rrc_proto.h"
#include "common/utils/bits.h"
 
static bool check_resourcesForInterference(const NR_CSI_MeasConfig_t *meas, NR_CSI_ResourceConfigId_t res_id, bool is_CSIIM)
{
  NR_CSI_ResourceConfig_t *res = NULL;
  if (meas->csi_ResourceConfigToAddModList) {
    for (int i = 0; i < meas->csi_ResourceConfigToAddModList->list.count; i++) {
      if (meas->csi_ResourceConfigToAddModList->list.array[i]->csi_ResourceConfigId == res_id)
        res = meas->csi_ResourceConfigToAddModList->list.array[i];
    }
  }
  if (!res) {
    LOG_E(NR_RRC, "No CSI-Resource matching with resourcesForInterference set in report config\n");
    return false;
  } else {
    if (is_CSIIM) {
      if (res->csi_RS_ResourceSetList.present != NR_CSI_ResourceConfig__csi_RS_ResourceSetList_PR_csi_IM_ResourceSetList) {
        LOG_E(NR_RRC, "resourcesForInterference doesn't point to CSI-IM resource\n");
        return false;
      }
    } else {
      if (res->csi_RS_ResourceSetList.present != NR_CSI_ResourceConfig__csi_RS_ResourceSetList_PR_nzp_CSI_RS_SSB
          || !res->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->nzp_CSI_RS_ResourceSetList) {
        LOG_E(NR_RRC, "NZP CSI-RS resourcesForInterference doesn't point to NZP CSI-RS resource\n");
        return false;
      }
    }
  }
  return true;
}

static bool check_resourcesForChannelMeasurement(const NR_CSI_MeasConfig_t *meas,
                                                 NR_CSI_ResourceConfigId_t res_id,
                                                 struct NR_CSI_ReportConfig__reportQuantity quantity)
{
  NR_CSI_ResourceConfig_t *res = NULL;
  if (meas->csi_ResourceConfigToAddModList) {
    for (int i = 0; i < meas->csi_ResourceConfigToAddModList->list.count; i++) {
      if (meas->csi_ResourceConfigToAddModList->list.array[i]->csi_ResourceConfigId == res_id)
        res = meas->csi_ResourceConfigToAddModList->list.array[i];
    }
  }
  if (!res) {
    LOG_E(NR_RRC, "No CSI-Resource matching with resourcesForChannelMeasurement set in report config\n");
    return false;
  } else {
    if (res->csi_RS_ResourceSetList.present != NR_CSI_ResourceConfig__csi_RS_ResourceSetList_PR_nzp_CSI_RS_SSB) {
      LOG_E(NR_RRC, "resourcesForChannelMeasurement doesn't point to CSI-RS-SSB resource\n");
      return false;
    } else {
      if (!res->csi_RS_ResourceSetList.choice.nzp_CSI_RS_SSB->nzp_CSI_RS_ResourceSetList
          && (quantity.present != NR_CSI_ReportConfig__reportQuantity_PR_ssb_Index_RSRP
          && quantity.present != NR_CSI_ReportConfig__reportQuantity_PR_NOTHING
          && quantity.present != NR_CSI_ReportConfig__reportQuantity_PR_none)) {
        LOG_E(NR_RRC, "resourcesForChannelMeasurement is for CRI measurement but doen't point to an CRI-RS resource\n");
        return false;
      }
    }
  }
  return true;
}

static bool check_csi_resourceMapping_consistency(NR_CSI_RS_ResourceMapping_t resourceMapping)
{
  bool valid = true;
  struct NR_CSI_RS_ResourceMapping__density *density = &resourceMapping.density;
  switch(resourceMapping.frequencyDomainAllocation.present) {
    case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_row1:
      if (resourceMapping.nrofPorts != NR_CSI_RS_ResourceMapping__nrofPorts_p1)
        valid = false;
      else if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_noCDM)
        valid = false;
      else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_three
               && density->present != NR_CSI_RS_ResourceMapping__density_PR_NOTHING)
        valid = false;
      break;
    case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_row2:
      if (resourceMapping.nrofPorts != NR_CSI_RS_ResourceMapping__nrofPorts_p1)
        valid = false;
      else if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_noCDM)
        valid = false;
      else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_dot5
               && density->present != NR_CSI_RS_ResourceMapping__density_PR_one)
        valid = false;
      break;
    case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_row4:
      if (resourceMapping.nrofPorts != NR_CSI_RS_ResourceMapping__nrofPorts_p4)
        valid = false;
      else if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2)
        valid = false;
      else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_one
               && density->present != NR_CSI_RS_ResourceMapping__density_PR_NOTHING)
        valid = false;
      break;
    case NR_CSI_RS_ResourceMapping__frequencyDomainAllocation_PR_other:
      switch(resourceMapping.nrofPorts) {
        case NR_CSI_RS_ResourceMapping__nrofPorts_p1:
          valid = false;  // 1 port is row 1 or 2 not other
          break;
        case NR_CSI_RS_ResourceMapping__nrofPorts_p2:
          if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2)
            valid = false;
          else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_dot5
                   && density->present != NR_CSI_RS_ResourceMapping__density_PR_one)
            valid = false;
          break;
        case NR_CSI_RS_ResourceMapping__nrofPorts_p4:
          // row 5 -> l0+1
          if (resourceMapping.firstOFDMSymbolInTimeDomain == 13)
            valid = false;
          else if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2)
            valid = false;
          else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_one
                   && density->present != NR_CSI_RS_ResourceMapping__density_PR_NOTHING)
            valid = false;
          break;
        case NR_CSI_RS_ResourceMapping__nrofPorts_p8: {
          uint32_t freq32 = 0;
          freq32 = (uint32_t)BIT_STRING_to_uint8(&resourceMapping.frequencyDomainAllocation.choice.other);
          if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2 || count_bits(&freq32, 1) != 4) {
            // row 7 and 8 -> l0+1
            if (resourceMapping.firstOFDMSymbolInTimeDomain == 13)
              valid = false;
          }
          if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2
              && resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2)
            valid = false;
          else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_one
                   && density->present != NR_CSI_RS_ResourceMapping__density_PR_NOTHING)
            valid = false;
          break;
        }
        case NR_CSI_RS_ResourceMapping__nrofPorts_p12:
          if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2) {
            // row 10 -> l0+1
            if (resourceMapping.firstOFDMSymbolInTimeDomain == 13)
              valid = false;
          } else if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2)
            valid = false;
          else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_one
                   && density->present != NR_CSI_RS_ResourceMapping__density_PR_NOTHING)
            valid = false;
          break;
        case NR_CSI_RS_ResourceMapping__nrofPorts_p16:
          // l0 + 1 in all cases
          if (resourceMapping.firstOFDMSymbolInTimeDomain == 13)
            valid = false;
          else if (resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2
                   && resourceMapping.cdm_Type != NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2)
            valid = false;
          else if (density->present != NR_CSI_RS_ResourceMapping__density_PR_dot5
                   && density->present != NR_CSI_RS_ResourceMapping__density_PR_one)
            valid = false;
        case NR_CSI_RS_ResourceMapping__nrofPorts_p24:
        case NR_CSI_RS_ResourceMapping__nrofPorts_p32:
          if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_noCDM)
            valid = false;
          else if (resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_cdm4_FD2_TD2
              || resourceMapping.cdm_Type == NR_CSI_RS_ResourceMapping__cdm_Type_fd_CDM2) {
            // row 13, 14 and 16, 17 -> l0+1 and l1 (which should be larger than l0+1
            if (resourceMapping.firstOFDMSymbolInTimeDomain == 13)
              valid = false;
            if (!resourceMapping.firstOFDMSymbolInTimeDomain2
                || *resourceMapping.firstOFDMSymbolInTimeDomain2 <= resourceMapping.firstOFDMSymbolInTimeDomain + 1)
              valid = false;
          } else {
            // row 15 and 18 -> l0+3
            if (resourceMapping.firstOFDMSymbolInTimeDomain > 10)
              valid = false;
          }
          if (valid
              && (density->present != NR_CSI_RS_ResourceMapping__density_PR_dot5
              && density->present != NR_CSI_RS_ResourceMapping__density_PR_one))
            valid = false;
          break;
        default:
          valid = false;
      }
      break;
    default:
      valid = false;
  }
  if (!valid)
    return false;
  return true;
}

bool check_csi_report_consistency(const NR_CSI_MeasConfig_t *meas)
{
  if (!meas || !meas->csi_ReportConfigToAddModList)
    return true;
  for (int i = 0; i < meas->csi_ReportConfigToAddModList->list.count; i++) {
    NR_CSI_ReportConfig_t *csirep = meas->csi_ReportConfigToAddModList->list.array[i];
    if (!check_resourcesForChannelMeasurement(meas, csirep->resourcesForChannelMeasurement, csirep->reportQuantity))
      return false;
    if (csirep->csi_IM_ResourcesForInterference) {
      if (!check_resourcesForInterference(meas, *csirep->csi_IM_ResourcesForInterference, true))
        return false;
    }
    if (csirep->nzp_CSI_RS_ResourcesForInterference) {
      if (!check_resourcesForInterference(meas, *csirep->nzp_CSI_RS_ResourcesForInterference, false))
        return false;
    }
  }
  return true;
}

static bool check_csi_resource_consistency(const NR_CSI_MeasConfig_t *meas)
{
  if (!meas || !meas->nzp_CSI_RS_ResourceToAddModList)
    return true;
  for (int i = 0; i < meas->nzp_CSI_RS_ResourceToAddModList->list.count; i++) {
    NR_NZP_CSI_RS_Resource_t *res = meas->nzp_CSI_RS_ResourceToAddModList->list.array[i];
    if (!check_csi_resourceMapping_consistency(res->resourceMapping))
      return false;
  }
  return true;
}

static bool check_csi_MeasConfig(struct NR_SetupRelease_CSI_MeasConfig *csi_MeasConfig)
{
  const NR_CSI_MeasConfig_t *meas = csi_MeasConfig->choice.setup;
  if (!check_csi_resource_consistency(meas))
    return false;
  return true;
}

static bool check_srs_config(NR_SRS_Config_t *srs_Config)
{
  if (srs_Config->srs_ResourceToAddModList) {
    for (int i = 0; i < srs_Config->srs_ResourceToAddModList->list.count; i++) {
      NR_SRS_Resource_t *res = srs_Config->srs_ResourceToAddModList->list.array[i];
      int start = NR_SYMBOLS_PER_SLOT - res->resourceMapping.startPosition - 1;
      int num = 1 << res->resourceMapping.nrofSymbols;
      if (start + num > NR_SYMBOLS_PER_SLOT) {
        LOG_E(NR_RRC, "The configured SRS resource exceeds the slot boundary\n");
        return false;
      }
    }
  }
  return true;
}

static bool check_ul_bwp_config(NR_BWP_UplinkDedicated_t *bwp_Config)
{
  if (bwp_Config->srs_Config && bwp_Config->srs_Config->choice.setup) {
    if (!check_srs_config(bwp_Config->srs_Config->choice.setup))
      return false;
  }
  return true;
}

bool check_cellgroup_config(const NR_CellGroupConfig_t *cgConfig)
{
  if (cgConfig->spCellConfig && cgConfig->spCellConfig->spCellConfigDedicated) {
    NR_ServingCellConfig_t *spCellConfigDedicated = cgConfig->spCellConfig->spCellConfigDedicated;
    if (spCellConfigDedicated->csi_MeasConfig) {
      if (!check_csi_MeasConfig(spCellConfigDedicated->csi_MeasConfig))
        return false;
    }
    if (spCellConfigDedicated->uplinkConfig) {
      NR_UplinkConfig_t *ul_Config = spCellConfigDedicated->uplinkConfig;
      if (ul_Config->initialUplinkBWP) {
        if (!check_ul_bwp_config(ul_Config->initialUplinkBWP))
          return false;
      }
      if (ul_Config->uplinkBWP_ToAddModList) {
        for (int i = 0; i < ul_Config->uplinkBWP_ToAddModList->list.count; i++) {
          NR_BWP_Uplink_t *ul_bwp = ul_Config->uplinkBWP_ToAddModList->list.array[i];
          if (ul_bwp->bwp_Dedicated) {
            if (!check_ul_bwp_config(ul_bwp->bwp_Dedicated))
              return false;
          }
        }
      }
    }
  }

  if (cgConfig->ext1 && cgConfig->ext1->reportUplinkTxDirectCurrent) {
    LOG_E(NR_RRC, "Reporting of UplinkTxDirectCurrent not implemented\n");
    return false;
  }
  if (cgConfig->sCellToReleaseList) {
    LOG_E(NR_RRC, "Secondary serving cell release not implemented\n");
    return false;
  }
  if (cgConfig->sCellToAddModList) {
    LOG_E(NR_RRC, "Secondary serving cell addition not implemented\n");
    return false;
  }
  return true;
}
