#include "thread.h"

#if defined(_linux_) // not 'unix', because not any Posix system supports per-thread niceness & scheduling
#include <pthread.h>
#include <sched.h>
#include <sys/resource.h>
#include <unistd.h>
//
// syscall arguments, from linux kernel:  include/linux/ioprio.h
// MUST be moved to util
//
#include <errno.h>
#include <syscall.h>
#define IOPRIO_CLASS_SHIFT (13)
#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data)

enum {
    IOPRIO_CLASS_NONE,
    IOPRIO_CLASS_RT,
    IOPRIO_CLASS_BE,
    IOPRIO_CLASS_IDLE,
};

enum {
    IOPRIO_WHO_PROCESS = 1,
    IOPRIO_WHO_PGRP,
    IOPRIO_WHO_USER,
};

static int ioprio_set(long tid, int cl, int prio) {
    return (int)syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, tid, IOPRIO_PRIO_VALUE(cl, prio));
}

#else // not _linux_
#define SCHED_BATCH SCHED_OTHER
#define SCHED_IDLE SCHED_OTHER
#define SCHED_RESET_ON_FORK (0)
#endif

namespace {
    //
    // Note: we cannot use TThread::TId as argument anywhere here, cause it is hashed (obfuscated) due to mysterious reasons
    // It would be fixed when this code is moved to util.
    //
    inline int SetSchedParam(TScheduling opts, int nice) noexcept {
        int rc = 0;
#ifdef _linux_
        struct sched_param param;
        int cpuScheduler, newScheduler, prio;
        pthread_t tid = pthread_self();
        rc = pthread_getschedparam(tid, &cpuScheduler, &param);
        if (rc)
            return errno;

        prio = getpriority(PRIO_PROCESS, 0); // NB: it is actually for the thread, not for the whole process (Linux specifics)
        if (errno)
            return errno;

        newScheduler = !(opts & TScheduling::CpuBatch) ? SCHED_OTHER : SCHED_BATCH;
        newScheduler |= cpuScheduler & SCHED_RESET_ON_FORK;

        if (cpuScheduler != newScheduler) {
            param.sched_priority = 0;
            rc = pthread_setschedparam(tid, newScheduler, &param);
            if (rc)
                return errno;
        }

        rc = setpriority(PRIO_PROCESS, 0, prio + nice);
        if (rc)
            return errno;

#else // not _linux_
        Y_UNUSED(opts);
        Y_UNUSED(nice);
        Y_UNUSED(rc);
#endif
        return rc;
    }

    inline int SetIoPrio(TScheduling opts) noexcept {
        int rc = 0;
#ifdef _linux_
        if (!!(opts & TScheduling::IoIdle)) {
            rc = ioprio_set(pthread_self(), IOPRIO_CLASS_IDLE, 0);
            if (rc)
                rc = errno;
        }
#else
        Y_UNUSED(opts);
        Y_UNUSED(rc);
#endif // _linux_
        return 0;
    }
}

int SetSchedOpts(TScheduling opts, int nice) noexcept {
    int rc = 0;
    if (!!(opts & TScheduling::CpuRt)) {
        SetHighestThreadPriority();
    } else {
        // lower the thread priority (if needed)
        rc = SetSchedParam(opts, nice);
    }
    if (!rc)
        SetIoPrio(opts);
    return rc;
}
