/*
htop - Scheduling.c
(C) 2023 htop dev team
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/

#include "config.h" // IWYU pragma: keep

#include "Scheduling.h"

#ifdef SCHEDULER_SUPPORT

#include <assert.h>
#include <stddef.h>

#include "FunctionBar.h"
#include "ListItem.h"
#include "Macros.h"
#include "Object.h"
#include "Panel.h"
#include "XUtils.h"


static const SchedulingPolicy policies[] = {
   [SCHED_OTHER] = { "Other",      SCHED_OTHER, false },
#ifdef SCHED_BATCH
   [SCHED_BATCH] = { "Batch",      SCHED_BATCH, false },
#endif
#ifdef SCHED_IDLE
   [SCHED_IDLE]  = { "Idle",       SCHED_IDLE,  false },
#endif
   [SCHED_FIFO]  = { "FiFo",       SCHED_FIFO,  true },
   [SCHED_RR]    = { "RoundRobin", SCHED_RR,    true },
};

#ifdef SCHED_RESET_ON_FORK
static bool reset_on_fork = false;
#endif


Panel* Scheduling_newPolicyPanel(int preSelectedPolicy) {
   Panel* this = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc("Select ", "Cancel "));
   Panel_setHeader(this, "New policy:");

#ifdef SCHED_RESET_ON_FORK
   Panel_add(this, (Object*) ListItem_new(reset_on_fork ? "Reset on fork: on" : "Reset on fork: off", -1));
#endif

   for (size_t i = 0; i < ARRAYSIZE(policies); i++) {
      if (!policies[i].name)
         continue;

      Panel_add(this, (Object*) ListItem_new(policies[i].name, policies[i].id));
      if (policies[i].id == preSelectedPolicy)
         Panel_setSelected(this, (int) i);
   }

   return this;
}

void Scheduling_togglePolicyPanelResetOnFork(Panel* schedPanel) {
#ifdef SCHED_RESET_ON_FORK
   reset_on_fork = !reset_on_fork;

   ListItem* item = (ListItem*) Panel_get(schedPanel, 0);

   free_and_xStrdup(&item->value, reset_on_fork ? "Reset on fork: on" : "Reset on fork: off");
#else
   (void)schedPanel;
#endif
}

Panel* Scheduling_newPriorityPanel(int policy, int preSelectedPriority) {
   if (policy < 0 || (size_t)policy >= ARRAYSIZE(policies) || policies[policy].name == NULL)
      return NULL;

   if (!policies[policy].prioritySupport)
      return NULL;

   int min = sched_get_priority_min(policy);
   if (min < 0)
      return NULL;
   int max = sched_get_priority_max(policy);
   if (max < 0)
      return NULL;

   Panel* this = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc("Select ", "Cancel "));
   Panel_setHeader(this, "Priority:");

   for (int i = min; i <= max; i++) {
      char buf[16];
      xSnprintf(buf, sizeof(buf), "%d", i);
      Panel_add(this, (Object*) ListItem_new(buf, i));
      if (i == preSelectedPriority)
         Panel_setSelected(this, i);
   }

   return this;
}

static bool Scheduling_setPolicy(Process* p, Arg arg) {
   const SchedulingArg* sarg = arg.v;
   int policy = sarg->policy;

   assert(policy >= 0);
   assert((size_t)policy < ARRAYSIZE(policies));
   assert(policies[policy].name);

   const struct sched_param param = { .sched_priority = policies[policy].prioritySupport ? sarg->priority : 0 };

   #ifdef SCHED_RESET_ON_FORK
   if (reset_on_fork)
      policy &= SCHED_RESET_ON_FORK;
   #endif

   int r = sched_setscheduler(Process_getPid(p), policy, &param);

   /* POSIX says on success the previous scheduling policy should be returned,
    * but Linux always returns 0. */
   return r != -1;
}

bool Scheduling_rowSetPolicy(Row* row, Arg arg) {
   Process* p = (Process*) row;
   assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));
   return Scheduling_setPolicy(p, arg);
}

const char* Scheduling_formatPolicy(int policy) {
#ifdef SCHED_RESET_ON_FORK
   policy = policy & ~SCHED_RESET_ON_FORK;
#endif

   switch (policy) {
      case SCHED_OTHER:
         return "OTHER";
      case SCHED_FIFO:
         return "FIFO";
      case SCHED_RR:
         return "RR";
#ifdef SCHED_BATCH
      case SCHED_BATCH:
         return "BATCH";
#endif
#ifdef SCHED_IDLE
      case SCHED_IDLE:
         return "IDLE";
#endif
#ifdef SCHED_DEADLINE
      case SCHED_DEADLINE:
         return "EDF";
#endif
      default:
         return "???";
   }
}

/*
 * Gather scheduling policy (thread-specific data)
 */
void Scheduling_readProcessPolicy(Process* proc) {
   proc->scheduling_policy = sched_getscheduler(Process_getPid(proc));
}
#endif  /* SCHEDULER_SUPPORT */
