#include "ff-mac.h"
#include "ff-mac-sched-sap.h"
#include "ff-mac-csched-sap.h"
#include "ff-mac-init.h"
#include "log.h"
#include "assertions.h"

//#undef LOG_D
//#define LOG_D LOG_I

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

/* number of callbacks */
#define N 9

/* this structure stores required data for OAI to work with FAPI */
/* it is the private version of fapi_interface_t */
struct fapi {
  fapi_interface_t fi;   /* the start of the structure matches fapi_interface_t */
  pthread_mutex_t mutex[N];
  pthread_cond_t cond[N];
  volatile int req_id[N];
  volatile int rsp_id[N];
  struct CschedCellConfigCnfParameters CschedCellConfigCnfParameters;
  struct SchedDlConfigIndParameters SchedDlConfigIndParameters;
};

#define LOCK(fi, fn) do { \
    LOG_D(MAC, "%s: locking fn %d fi %p mutex %p\n", __FUNCTION__, fn, fi, &fi->mutex[fn]); \
    if (pthread_mutex_lock(&fi->mutex[fn])) \
      AssertFatal(0, "%s:%d:%s: mutex error\n", __FILE__, __LINE__, __FUNCTION__); \
  } while (0)

#define UNLOCK(fi, fn) do { \
    LOG_D(MAC, "%s: unlocking fn %d fi %p mutex %p\n", __FUNCTION__, fn, fi, &fi->mutex[fn]); \
    if (pthread_mutex_unlock(&fi->mutex[fn])) \
      AssertFatal(0, "%s:%d:%s: mutex error\n", __FILE__, __LINE__, __FUNCTION__); \
  } while (0)

#define CHECK(fi, fn) do { \
    if (fi->req_id[fn] != fi->rsp_id[fn]) \
      AssertFatal(0, "%s:%d:%s: check error\n", __FILE__, __LINE__, __FUNCTION__); \
  } while (0)

#define WAIT(fi, fn) do { \
    LOG_D(MAC, "%s: WAIT fn %d req %d rsp %d\n", __FUNCTION__, fn, fi->req_id[fn], fi->rsp_id[fn]); \
    while (fi->req_id[fn] == fi->rsp_id[fn]) \
      if (pthread_cond_wait(&fi->cond[fn], &fi->mutex[fn])) \
        AssertFatal(0, "%s:%d:%s: cond error\n", __FILE__, __LINE__, __FUNCTION__); \
  } while (0)

#define DONE_callback(fi, fn) do { \
    fi->req_id[fn]++; \
    if (pthread_cond_signal(&fi->cond[fn])) \
      AssertFatal(0, "%s:%d:%s: mutex error\n", __FILE__, __LINE__, __FUNCTION__); \
  } while (0)

#define DONE_wrapper(fi, fn) do { \
    fi->rsp_id[fn]++; \
  } while (0)

/* SCHED "wrappers" */

void SchedDlConfigInd(fapi_interface_t *_fi, struct SchedDlConfigIndParameters *params)
{
  struct fapi *fi = (struct fapi *)_fi;
  int fn = 0;
  LOG_D(MAC, "SchedDlConfigInd enter\n");

  LOCK(fi, fn);
  WAIT(fi, fn);

  *params = fi->SchedDlConfigIndParameters;

  DONE_wrapper(fi, fn);
  UNLOCK(fi, fn);

  LOG_D(MAC, "SchedDlConfigInd leave\n");
}

void SchedUlConfigInd(fapi_interface_t *_fi, struct SchedUlConfigIndParameters *params)
{
  int fn = 1;
}

/* CSCHED "wrappers" */

void CschedCellConfigCnf(fapi_interface_t *_fi, struct CschedCellConfigCnfParameters *params)
{
  struct fapi *fi = (struct fapi *)_fi;
  int fn = 2;
  LOG_D(MAC, "CschedCellConfigCnf enter\n");

  LOCK(fi, fn);
  WAIT(fi, fn);

  *params = fi->CschedCellConfigCnfParameters;

  DONE_wrapper(fi, fn);
  UNLOCK(fi, fn);

  LOG_D(MAC, "CschedCellConfigCnf leave\n");
}

void CschedUeConfigCnf(fapi_interface_t *_fi, struct CschedUeConfigCnfParameters *params)
{
  int fn = 3;
}

void CschedLcConfigCnf(fapi_interface_t *_fi, struct CschedLcConfigCnfParameters *params)
{
  int fn = 4;
}

void CschedLcReleaseCnf(fapi_interface_t *_fi, struct CschedLcReleaseCnfParameters *params)
{
  int fn = 5;
}

void CschedUeReleaseCnf(fapi_interface_t *_fi, struct CschedUeReleaseCnfParameters *params)
{
  int fn = 6;
}

void CschedUeConfigUpdateInd(fapi_interface_t *_fi, struct CschedUeConfigUpdateIndParameters *params)
{
  int fn = 7;
}

void CschedCellConfigUpdateInd(fapi_interface_t *_fi, struct CschedCellConfigUpdateIndParameters *params)
{
  int fn = 8;
}

/* SCHED callbacks */

void SchedDlConfigInd_callback(void *callback_data, const struct SchedDlConfigIndParameters *params)
{
  struct fapi *fi = callback_data;
  int fn = 0;
  LOG_D(MAC, "SchedDlConfigInd_callback enter\n");

  LOCK(fi, fn);
  CHECK(fi, fn);

  fi->SchedDlConfigIndParameters = *params;

  DONE_callback(fi, fn);
  UNLOCK(fi, fn);

  LOG_D(MAC, "SchedDlConfigInd_callback leave\n");
}

void SchedUlConfigInd_callback(void *callback_data, const struct SchedUlConfigIndParameters *params)
{
  int fn = 1;
}

/* CSCHED callbacks */

void CschedCellConfigCnf_callback(void *callback_data, const struct CschedCellConfigCnfParameters *params)
{
  struct fapi *fi = callback_data;
  int fn = 2;
  LOG_D(MAC, "CschedCellConfigCnf_callback enter\n");

  LOCK(fi, fn);
  CHECK(fi, fn);

  fi->CschedCellConfigCnfParameters = *params;

  DONE_callback(fi, fn);
  UNLOCK(fi, fn);

  LOG_D(MAC, "CschedCellConfigCnf_callback leave\n");
}

void CschedUeConfigCnf_callback(void *callback_data, const struct CschedUeConfigCnfParameters *params)
{
  int fn = 3;
}

void CschedLcConfigCnf_callback(void *callback_data, const struct CschedLcConfigCnfParameters *params)
{
  int fn = 4;
}

void CschedLcReleaseCnf_callback(void *callback_data, const struct CschedLcReleaseCnfParameters *params)
{
  int fn = 5;
}

void CschedUeReleaseCnf_callback(void *callback_data, const struct CschedUeReleaseCnfParameters *params)
{
  int fn = 6;
}

void CschedUeConfigUpdateInd_callback(void *callback_data, const struct CschedUeConfigUpdateIndParameters *params)
{
  int fn = 7;
}

void CschedCellConfigUpdateInd_callback(void *callback_data, const struct CschedCellConfigUpdateIndParameters *params)
{
  int fn = 8;
}

fapi_interface_t *init_fapi(void)
{
  struct fapi *ret;
  int i;

  LOG_I(MAC, "FAPI initialization\n");

  ret = calloc(1, sizeof(struct fapi));
  if (ret == NULL) {
    LOG_E(MAC, "init_fapi: memory allocation error\n");
    return NULL;
  }

  for (i = 0; i < N; i++) {
    if (pthread_mutex_init(&ret->mutex[i], NULL)) {
      LOG_E(MAC, "init_fapi: mutex init error\n");
      exit(1);
    }
    if (pthread_cond_init(&ret->cond[i], NULL)) {
      LOG_E(MAC, "init_fapi: cond init error\n");
      exit(1);
    }
    ret->req_id[i] = 0;
    ret->rsp_id[i] = 0;
  }

  ret->fi.sched = SchedInit(ret,
                      SchedDlConfigInd_callback,
                      SchedUlConfigInd_callback,
                      CschedCellConfigCnf_callback,
                      CschedUeConfigCnf_callback,
                      CschedLcConfigCnf_callback,
                      CschedLcReleaseCnf_callback,
                      CschedUeReleaseCnf_callback,
                      CschedUeConfigUpdateInd_callback,
                      CschedCellConfigUpdateInd_callback);

  if (ret->fi.sched == NULL) {
    LOG_E(MAC, "init_fapi: SchedInit failed\n");
    free(ret);
    return NULL;
  }

  return (fapi_interface_t *)ret;
}