#pragma once
#include <util/datetime/base.h>
#include <util/system/guard.h>
#include <util/system/mutex.h>

class ISloth {
public:
    virtual ~ISloth() {
    }

    virtual TDuration GetCurrentSleepDuration() = 0;
    virtual TDuration Sleep() = 0;
    virtual void IncDuration() = 0;
    virtual void DecDuration() = 0;
};

class TSlothExp : public ISloth {
public:
    TSlothExp(TDuration initialSleepDuration = TDuration::Zero(), TDuration minSleepThreshold = TDuration::Zero(), TDuration maxSleepDuration = TDuration::Max(), ui32 expBase = 2)
        : CurrentSleepDuration(initialSleepDuration)
        , MinSleepThreshold(minSleepThreshold)
        , MaxSleepDuration(maxSleepDuration)
        , ExpBase(expBase)
    {
        // Zero threshold is not allowed because it's a dead-end for the exponential algorithm. Default value
        // for MinSleepThreshold is the initial sleep duration. If user sets BOTH initial and min threshold
        // to zero then he gets a stub instance that never actually sleeps.
        if (MinSleepThreshold == TDuration::Zero()) {
            MinSleepThreshold = CurrentSleepDuration;
        }
    }
    virtual ~TSlothExp() {
    }

    virtual TDuration GetCurrentSleepDuration() {
        TGuard<TMutex> g(Mutex);
        return CurrentSleepDuration;
    }

    virtual TDuration Sleep() {
        // Sleep is thread-safe in a sense that we get a consistent CurrentSleepDuration value.
        // It may change between the GetCurrentSleepDuration() and ::Sleep() but we obviously can't hold
        // the mutex during the sleep. Neither such change matters anyway.
        TDuration csd = GetCurrentSleepDuration();
        if (csd != TDuration::Zero()) {
            ::Sleep(csd);
        }
        return csd;
    }

    virtual void DecDuration() {
        TGuard<TMutex> g(Mutex);
        if (CurrentSleepDuration == TDuration::Zero()) {
            return;
        }
        CurrentSleepDuration /= ExpBase;
        if (CurrentSleepDuration < MinSleepThreshold) {
            CurrentSleepDuration = TDuration::Zero();
        }
    }

    virtual void IncDuration() {
        TGuard<TMutex> g(Mutex);
        if (CurrentSleepDuration == TDuration::Zero()) {
            CurrentSleepDuration = MinSleepThreshold;
        } else if (CurrentSleepDuration != MaxSleepDuration) {
            CurrentSleepDuration *= ExpBase;
            if ( CurrentSleepDuration > MaxSleepDuration) {
                CurrentSleepDuration = MaxSleepDuration;
            }
        }
    }

private:
    TDuration CurrentSleepDuration;
    TDuration MinSleepThreshold;
    TDuration MaxSleepDuration;
    ui32 ExpBase;
    mutable TMutex Mutex;
};
