/*
 * 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 "nr_pdcp_ue_manager.h"

#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>

#include "LOG/log.h"

typedef struct {
  pthread_mutex_t lock;
  nr_pdcp_ue_t    **ue_list;
  int             ue_count;
  int             enb_flag;
} nr_pdcp_ue_manager_internal_t;

nr_pdcp_ue_manager_t *new_nr_pdcp_ue_manager(int enb_flag)
{
  nr_pdcp_ue_manager_internal_t *ret;

  ret = calloc(1, sizeof(nr_pdcp_ue_manager_internal_t));
  if (ret == NULL) {
    LOG_E(PDCP, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
    exit(1);
  }

  pthread_mutexattr_t Attr;
  pthread_mutexattr_init(&Attr);
  // We need to be recursive because pdcp locks before forwarding, and calls in same thread,
  // so, if same thread wants to send something back, and there is no itti intermediate thread, we deadlock on ourselves
  pthread_mutexattr_settype(&Attr, PTHREAD_MUTEX_RECURSIVE);
  if (pthread_mutex_init(&ret->lock, &Attr))
    abort();
  ret->enb_flag = enb_flag;

  return ret;
}

int nr_pdcp_manager_get_enb_flag(nr_pdcp_ue_manager_t *_m)
{
  nr_pdcp_ue_manager_internal_t *m = _m;
  return m->enb_flag;
}

void nr_pdcp_manager_lock(nr_pdcp_ue_manager_t *_m)
{
  nr_pdcp_ue_manager_internal_t *m = _m;
  if (pthread_mutex_lock(&m->lock)) {
    LOG_E(PDCP, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
    exit(1);
  }
}

void nr_pdcp_manager_unlock(nr_pdcp_ue_manager_t *_m)
{
  nr_pdcp_ue_manager_internal_t *m = _m;
  if (pthread_mutex_unlock(&m->lock)) {
    LOG_E(PDCP, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
    exit(1);
  }
}

/* must be called with lock acquired */
nr_pdcp_ue_t *nr_pdcp_manager_get_ue(nr_pdcp_ue_manager_t *_m, ue_id_t rntiMaybeUEid)
{
  /* TODO: optimze */
  nr_pdcp_ue_manager_internal_t *m = _m;
  int i;

  for (i = 0; i < m->ue_count; i++)
    if (m->ue_list[i]->rntiMaybeUEid == rntiMaybeUEid)
      return m->ue_list[i];

  LOG_D(PDCP, "%s:%d:%s: new UE ID/RNTI 0x%" PRIx64 "\n", __FILE__, __LINE__, __FUNCTION__, rntiMaybeUEid);

  m->ue_count++;
  m->ue_list = realloc(m->ue_list, sizeof(nr_pdcp_ue_t *) * m->ue_count);
  if (m->ue_list == NULL) {
    LOG_E(PDCP, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
    exit(1);
  }
  m->ue_list[m->ue_count-1] = calloc(1, sizeof(nr_pdcp_ue_t));
  if (m->ue_list[m->ue_count-1] == NULL) {
    LOG_E(PDCP, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
    exit(1);
  }

  m->ue_list[m->ue_count - 1]->rntiMaybeUEid = rntiMaybeUEid;

  return m->ue_list[m->ue_count-1];
}

/* must be called with lock acquired */
void nr_pdcp_manager_remove_ue(nr_pdcp_ue_manager_t *_m, ue_id_t rntiMaybeUEid)
{
  nr_pdcp_ue_manager_internal_t *m = _m;
  nr_pdcp_ue_t *ue;
  int i;
  int j;

  for (i = 0; i < m->ue_count; i++)
    if (m->ue_list[i]->rntiMaybeUEid == rntiMaybeUEid)
      break;

  if (i == m->ue_count) {
    LOG_D(PDCP, "%s:%d:%s: warning: UE ID/RNTI 0x%" PRIx64 " not found\n", __FILE__, __LINE__, __FUNCTION__, rntiMaybeUEid);
    return;
  }

  ue = m->ue_list[i];

  for (j = 0; j < 2; j++)
    if (ue->srb[j] != NULL)
      ue->srb[j]->delete_entity(ue->srb[j]);

  for (j = 0; j < 5; j++)
    if (ue->drb[j] != NULL)
      ue->drb[j]->delete_entity(ue->drb[j]);

  free(ue);

  m->ue_count--;
  if (m->ue_count == 0) {
    free(m->ue_list);
    m->ue_list = NULL;
    return;
  }

  memmove(&m->ue_list[i], &m->ue_list[i+1],
          (m->ue_count - i) * sizeof(nr_pdcp_ue_t *));
  m->ue_list = realloc(m->ue_list, m->ue_count * sizeof(nr_pdcp_ue_t *));
  if (m->ue_list == NULL) {
    LOG_E(PDCP, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
    exit(1);
  }
}

/* must be called with lock acquired */
void nr_pdcp_ue_add_srb_pdcp_entity(nr_pdcp_ue_t *ue, int srb_id, nr_pdcp_entity_t *entity)
{
  if (srb_id < 1 || srb_id > 2) {
    LOG_E(PDCP, "%s:%d:%s: fatal, bad srb id\n", __FILE__, __LINE__, __FUNCTION__);
    exit(1);
  }

  srb_id--;

  if (ue->srb[srb_id] != NULL) {
    LOG_E(PDCP, "%s:%d:%s: fatal, srb already present\n",
          __FILE__, __LINE__, __FUNCTION__);
    exit(1);
  }

  ue->srb[srb_id] = entity;
}

/* must be called with lock acquired */
void nr_pdcp_ue_add_drb_pdcp_entity(nr_pdcp_ue_t *ue, int drb_id, nr_pdcp_entity_t *entity)
{
  if (drb_id < 1 || drb_id > 5) {
    LOG_E(PDCP, "%s:%d:%s: fatal, bad drb id\n", __FILE__, __LINE__, __FUNCTION__);
    exit(1);
  }

  drb_id--;

  if (ue->drb[drb_id] != NULL) {
    LOG_E(PDCP, "%s:%d:%s: fatal, drb already present\n",
          __FILE__, __LINE__, __FUNCTION__);
    exit(1);
  }

  ue->drb[drb_id] = entity;
}

/* must be called with lock acquired */
nr_pdcp_ue_t **nr_pdcp_manager_get_ue_list(nr_pdcp_ue_manager_t *_m)
{
  nr_pdcp_ue_manager_internal_t *m = _m;
  return m->ue_list;
}

/* must be called with lock acquired */
int nr_pdcp_manager_get_ue_count(nr_pdcp_ue_manager_t *_m)
{
  nr_pdcp_ue_manager_internal_t *m = _m;
  return m->ue_count;
}

bool nr_pdcp_get_first_ue_id(nr_pdcp_ue_manager_t *_m, ue_id_t *ret)
{
  nr_pdcp_ue_manager_internal_t *m = _m;
  if (m->ue_count == 0)
    return false;
  *ret = m->ue_list[0]->rntiMaybeUEid;
  return true;
}
