#pragma once

/// This code should not be used directly unless you really understand what you do.
/// If you need threads, use thread pool functionality in <util/thread/factory.h>
/// @see SystemThreadFactory()

#include <util/generic/ptr.h>
#include <util/generic/string.h>

#include "defaults.h"
#include "progname.h"

bool SetHighestThreadPriority();
bool SetLowestThreadPriority();

class TThread {
    template <typename Callable>
    struct TCallableParams;
    struct TPrivateCtor {};

public:
    using TThreadProc = void* (*)(void*);
    using TId = size_t;

    struct TParams {
        TThreadProc Proc;
        void* Data;
        size_t StackSize;
        void* StackPointer;
        // See comments for `SetCurrentThreadName`
        TString Name = GetProgramName();

        inline TParams()
            : Proc(nullptr)
            , Data(nullptr)
            , StackSize(0)
            , StackPointer(nullptr)
        {
        }

        inline TParams(TThreadProc proc, void* data)
            : Proc(proc)
            , Data(data)
            , StackSize(0)
            , StackPointer(nullptr)
        {
        }

        inline TParams(TThreadProc proc, void* data, size_t stackSize)
            : Proc(proc)
            , Data(data)
            , StackSize(stackSize)
            , StackPointer(nullptr)
        {
        }

        inline TParams& SetName(const TString& name) noexcept {
            Name = name;

            return *this;
        }

        inline TParams& SetStackSize(size_t size) noexcept {
            StackSize = size;

            return *this;
        }

        inline TParams& SetStackPointer(void* ptr) noexcept {
            StackPointer = ptr;

            return *this;
        }
    };

    TThread(const TParams& params);
    TThread(TThreadProc threadProc, void* param);

    template <typename Callable>
    TThread(Callable&& callable)
        : TThread(TPrivateCtor{},
                  MakeHolder<TCallableParams<Callable>>(std::forward<Callable>(callable)))
    {
    }

    TThread(TParams&& params)
        : TThread((const TParams&)params)
    {
    }

    TThread(TParams& params)
        : TThread((const TParams&)params)
    {
    }

    ~TThread();

    void Start();

    void* Join();
    void Detach();
    bool Running() const noexcept;
    TId Id() const noexcept;

    static TId ImpossibleThreadId() noexcept;
    static TId CurrentThreadId() noexcept;

    /*
     * Returns numeric thread id, as visible in e. g. htop.
     * Consider using this value for logging.
     */
    static TId CurrentThreadNumericId() noexcept;

    // NOTE: Content of `name` will be copied.
    //
    // NOTE: On Linux thread name is limited to 15 symbols which is probably the smallest one among
    // all platforms. If you provide a name longer than 15 symbols it will be cut. So if you expect
    // `CurrentThreadName` to return the same name as `name` make sure it's not longer than 15
    // symbols.
    static void SetCurrentThreadName(const char* name);

    // NOTE: Will return empty string where CanGetCurrentThreadName() returns false.
    static TString CurrentThreadName();

    // NOTE: Depends on a platform version.
    // Will return true for Darwin, Linux or fresh Windows 10.
    static bool CanGetCurrentThreadName();

private:
    struct TCallableBase {
        virtual ~TCallableBase() = default;
        virtual void run() = 0;

        static void* ThreadWorker(void* arg) {
            static_cast<TCallableBase*>(arg)->run();
            return nullptr;
        }
    };

    template <typename Callable>
    struct TCallableParams: public TCallableBase {
        TCallableParams(Callable&& callable)
            : Callable_(std::forward<Callable>(callable))
        {
        }

        Callable Callable_;

        void run() override {
            Callable_();
        }
    };

    TThread(TPrivateCtor, THolder<TCallableBase> callable);

private:
    class TImpl;
    THolder<TImpl> Impl_;
};

class ISimpleThread: public TThread {
public:
    ISimpleThread(size_t stackSize = 0);

    virtual ~ISimpleThread() = default;

    virtual void* ThreadProc() = 0;
};

struct TCurrentThreadLimits {
    TCurrentThreadLimits() noexcept;

    const void* StackBegin;
    size_t StackLength;
};