/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include "system-precomp.h"  // Precompiled headers
//
#include <mrpt/config.h>
#include <mrpt/core/exceptions.h>
#include <mrpt/system/scheduler.h>

#ifdef MRPT_OS_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
//
#include <process.h>
#include <tlhelp32.h>
#else
#include <pthread.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#include <utime.h>

#include <cerrno>
#include <csignal>
#include <cstring>  // strerror()
#include <ctime>
#endif

#include <sys/stat.h>
#include <sys/types.h>
#ifdef MRPT_OS_APPLE
#include <mach/mach_init.h>
#include <mach/thread_act.h>
#include <sys/sysctl.h>
#endif

#include <iostream>

void mrpt::system::changeCurrentThreadPriority(TThreadPriority priority)
{
#ifdef MRPT_OS_WINDOWS
  // TThreadPriority is defined to agree with numbers expected by Win32 API:
  SetThreadPriority(GetCurrentThread(), priority);
#else
  const pthread_t tid = pthread_self();

  int ret, policy;
  struct sched_param param
  {
  };

  if (0 != (ret = pthread_getschedparam(tid, &policy, &param)))
  {
    std::cerr << "[mrpt::system::changeThreadPriority] Warning: Failed call to "
                 "pthread_getschedparam (error: `"
              << strerror(ret) << "`)" << std::endl;
    return;
  }

  policy = SCHED_RR;
  int min_prio = sched_get_priority_min(policy), max_prio = sched_get_priority_max(policy);
  if (min_prio < 0) min_prio = 1;  // Just in case of error to calls above (!)
  if (max_prio < 0) max_prio = 99;

  int prio = 0;
  switch (priority)
  {
    case tpLowests:
      prio = min_prio;
      break;
    case tpLower:
      prio = (max_prio + 3 * min_prio) / 4;
      break;
    case tpLow:
      prio = (max_prio + 2 * min_prio) / 3;
      break;
    case tpNormal:
      prio = (max_prio + min_prio) / 2;
      break;
    case tpHigh:
      prio = (2 * max_prio + min_prio) / 3;
      break;
    case tpHigher:
      prio = (3 * max_prio + min_prio) / 4;
      break;
    case tpHighest:
      prio = max_prio;
      break;
  }

  param.sched_priority = prio;
  if (0 != (ret = pthread_setschedparam(tid, policy, &param)))
  {
    std::cerr << "[mrpt::system::changeThreadPriority] Warning: Failed call to "
                 "pthread_setschedparam (error: `"
              << strerror(ret) << "`)" << std::endl;
    return;
  }
#endif
}

void mrpt::system::changeCurrentProcessPriority(TProcessPriority priority)
{
#ifdef MRPT_OS_WINDOWS
  DWORD dwPri;
  switch (priority)
  {
    case ppIdle:
      dwPri = IDLE_PRIORITY_CLASS;
      break;
    case ppNormal:
      dwPri = NORMAL_PRIORITY_CLASS;
      break;
    case ppHigh:
      dwPri = HIGH_PRIORITY_CLASS;
      break;
    case ppVeryHigh:
      dwPri = REALTIME_PRIORITY_CLASS;
      break;
    default:
      THROW_EXCEPTION("Invalid priority value");
  }
  SetPriorityClass(GetCurrentProcess(), dwPri);
#else
  int nice_val;
  switch (priority)
  {
    case ppIdle:
      nice_val = +19;
      break;
    case ppNormal:
      nice_val = 0;
      break;
    case ppHigh:
      nice_val = -10;
      break;
    case ppVeryHigh:
      nice_val = -20;
      break;
    default:
      THROW_EXCEPTION("Invalid priority value");
  }
  errno = 0;
  const int ret = nice(nice_val);
  if (ret == -1 && errno == EPERM)
  {
    std::cerr << "[mrpt::system::changeCurrentProcessPriority] Error "
                 "calling nice(): Not enough permissions.\n";
  }
#endif
}
