/******************************************************************************
 *
 *  Copyright 2012 Broadcom Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 *  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.
 *
 ******************************************************************************/

/*******************************************************************************
 *
 *  Filename:      bt_utils.cc
 *
 *  Description:   Miscellaneous helper functions
 *
 *
 ******************************************************************************/

#define LOG_TAG "bt_utils"

#include "bt_utils.h"

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <unistd.h>
#include <mutex>

#define A2DP_RT_PRIORITY 1
#ifndef OS_GENERIC
#include <processgroup/sched_policy.h>
#endif

#include "bt_types.h"
#include "btcore/include/module.h"
#include "osi/include/compat.h"
#include "osi/include/log.h"
#include "osi/include/properties.h"

/*******************************************************************************
 *  Type definitions for callback functions
 ******************************************************************************/
static pthread_once_t g_DoSchedulingGroupOnce[TASK_HIGH_MAX];
static bool g_DoSchedulingGroup[TASK_HIGH_MAX];
static std::mutex gIdxLock;
static int g_TaskIdx;
static int g_TaskIDs[TASK_HIGH_MAX];
#define INVALID_TASK_ID (-1)

static future_t* init(void) {
  int i;

  for (i = 0; i < TASK_HIGH_MAX; i++) {
    g_DoSchedulingGroupOnce[i] = PTHREAD_ONCE_INIT;
    g_DoSchedulingGroup[i] = true;
    g_TaskIDs[i] = INVALID_TASK_ID;
  }

  return NULL;
}

static future_t* clean_up(void) {
  return NULL;
}

EXPORT_SYMBOL extern const module_t bt_utils_module = {.name = BT_UTILS_MODULE,
                                                       .init = init,
                                                       .start_up = NULL,
                                                       .shut_down = NULL,
                                                       .clean_up = clean_up,
                                                       .dependencies = {NULL}};

/*****************************************************************************
 *
 * Function        check_do_scheduling_group
 *
 * Description     check if it is ok to change schedule group
 *
 * Returns         void
 *
 ******************************************************************************/
static void check_do_scheduling_group(void) {
  char buf[PROPERTY_VALUE_MAX];
  int len = osi_property_get("debug.sys.noschedgroups", buf, "");
  if (len > 0) {
    int temp;
    if (sscanf(buf, "%d", &temp) == 1) {
      g_DoSchedulingGroup[g_TaskIdx] = temp == 0;
    }
  }
}

/*****************************************************************************
 *
 * Function        raise_priority_a2dp
 *
 * Description     Raise task priority for A2DP streaming
 *
 * Returns         void
 *
 ******************************************************************************/
void raise_priority_a2dp(tHIGH_PRIORITY_TASK high_task) {
  int rc = 0;
  int tid = gettid();

  {
    std::lock_guard<std::mutex> lock(gIdxLock);
    g_TaskIdx = high_task;

// TODO(armansito): Remove this conditional check once we find a solution
// for system/core on non-Android platforms.
#if defined(OS_GENERIC)
    rc = -1;
#else   // !defined(OS_GENERIC)
    pthread_once(&g_DoSchedulingGroupOnce[g_TaskIdx],
                 check_do_scheduling_group);
    if (g_DoSchedulingGroup[g_TaskIdx]) {
      // set_sched_policy does not support tid == 0
      rc = set_sched_policy(tid, SP_AUDIO_SYS);
    }
#endif  // defined(OS_GENERIC)

    g_TaskIDs[high_task] = tid;
  }

  if (rc) {
    LOG_WARN(LOG_TAG, "failed to change sched policy, tid %d, err: %d", tid,
             errno);
  }

  // make A2DP threads use RT scheduling policy since they are part of the
  // audio pipeline
  {
    struct sched_param rt_params;
    rt_params.sched_priority = A2DP_RT_PRIORITY;

    const int rc = sched_setscheduler(tid, SCHED_FIFO, &rt_params);
    if (rc != 0) {
      LOG_ERROR(LOG_TAG,
                "%s unable to set SCHED_FIFO priority %d for tid %d, error %s",
                __func__, A2DP_RT_PRIORITY, tid, strerror(errno));
    }
  }
}