//
// Temporary - MUST be moved to util
//

#include "threadpool.h"

#include <library/cpp/logger/global/global.h>
#include <library/cpp/balloc/optional/operators.h>
#include <util/generic/maybe.h>

namespace {
    //
    // boilerplate & some copypaste from util/thread/factory.cpp
    // 1. MUST be removed when moving to util
    // 2. it is better to set sched_attrs before pthread_create
    //
    using IThread = IThreadFactory::IThread;
    using IThreadAble = IThreadFactory::IThreadAble;
    using TThreadScheduling = TPriorityThreadPool::TThreadScheduling;

    class TThreadAbleWrapper final: public IThreadAble {
    public:
        TThreadAbleWrapper(const TThreadScheduling& scheduling, IThreadAble* thr)
            : Scheduling_(scheduling)
            , Threadable(thr)
        {
        }

    private:
        void SetBallocMode(bool disabled) {
            if (disabled)
                ThreadDisableBalloc();
            else
                ThreadEnableBalloc();
        }

        void DoExecute() override {
            THolder<TThreadAbleWrapper> self(this);
            int rc = SetSchedOpts(Scheduling_.Class, Scheduling_.CpuNice);
            Y_VERIFY_DEBUG(rc == 0);

            if (Scheduling_.NoAlloc.Defined())
                SetBallocMode(Scheduling_.NoAlloc.GetRef());

            Threadable->Execute();
        }

    private:
        TThreadScheduling Scheduling_;
        IThreadAble* Threadable;
    };

    class TPrioThread final: public IThread {
    public:
        TPrioThread(const TThreadScheduling& scheduling)
            : Scheduling_(scheduling)
        {
        }

        ~TPrioThread() override {
            if (Thr_) {
                Thr_->Detach();
            }
        }

        void DoRun(IThreadAble* func) override {
            IThreadAble* wrapper = new TThreadAbleWrapper(Scheduling_, func);
            Thr_.Reset(new TThread(ThreadProc, wrapper));
            Thr_->Start();
        }

        void DoJoin() noexcept override {
            if (!Thr_) {
                return;
            }

            Thr_->Join();
            Thr_.Destroy();
        }

    private:
        static void* ThreadProc(void* func) {
            ((IThreadAble*)(func))->Execute();

            return nullptr;
        }

    private:
        TThreadScheduling Scheduling_;
        THolder<TThread> Thr_;
    };
}

TPriorityThreadPool::TPriorityThreadPool(TScheduling opts, int cpuNice, TMaybe<bool> noAlloc)
    : Opts_(TThreadScheduling{opts, noAlloc, cpuNice})
{
    VERIFY_WITH_LOG(ValidateOpts(Opts_.Class), "Invalid scheduling opts: %i", static_cast<int>(Opts_.Class));
}

IThreadFactory::IThread* TPriorityThreadPool::DoCreate() {
    return new TPrioThread(Opts_);
}
