#pragma once

#include <util/datetime/base.h>
#include <util/generic/ylimits.h>
#include <util/generic/utility.h>
#include <library/cpp/deprecated/atomic/atomic.h>
#include <library/cpp/logger/global/global.h>

/**
 *  A small note.
 *  "Where are my DDK streams" in the Phased mode? "How dare I"?
 *
 *  If you are worried about, just do not enable it - this logic is probably too simple for you to understand.
 *  While multiple streams could have been accounted for in this code - like, I might have created NRTYServer::TIndexTimestamp
 *  for some amount of the documents exceeding a given deadline, then check some stats, and only then close the index;
 *  or I could have provided separate Start-Stop ranges for each stream - there is absolutely no practical use
 *  in checking such constraints.
 *
 *  Just go with the last timestamp. When one DDK stream is delayed and another is not, using the last means taking the best.
 *  Even if some whacko starts sending invalid timestamps in a row running into the future, he will be caught by the logic below.
 *
 *  Believe me, it's safe. It never finishes prematurely, not like the previous Great Architect of RTYServer did when planning
 *  this wonderful piece of software.
 *
 */

struct TDiskIndexTtl final {
private:
    const ui32 TimeToLiveSec;
    const bool PhasedMode;
    const i32 Phase;
public:
    TAtomic StartSeconds = 0;
    mutable ui32 TimeExtra = 0;

    TAtomic PhasedStopTimestamp = TAtomicBase(Max<ui32>());
    TAtomic PhasedStopSignal = 0;
public:
    TDiskIndexTtl(ui32 ttl, i32 phase=-1)
        : TimeToLiveSec(ttl)
        , PhasedMode(phase >= 0 && ttl > 0)
        , Phase(PhasedMode ? phase % ttl : -1)
    {
    }

    inline operator bool() const {
        return !!TimeToLiveSec;
    }

    Y_FORCE_INLINE bool IsPhasedMode() const {
        return PhasedMode;
    }

private:
    Y_FORCE_INLINE ui32 MinPhasedTtl() const {
        // Phased mode: time during which signals must be ignored
        return TimeToLiveSec / 2;
    }

    Y_FORCE_INLINE ui32 MaxPhasedTtl() const {
        // Phased mode: max time to wait in case the PhasedStopTimestamp is not reached (when docstream is delayed)
        return TimeToLiveSec + Min(TimeToLiveSec, 120u);
    }

    inline static ui32 NextPhase(ui32 x, ui32 period, i32 minIncrement, i32 phase) {
        ui32 t = (x - phase + (period - 1)) / period * period + phase;
        while (t < x + minIncrement)
            t += period;
        return t;
    }

    inline ui32 GetStop(ui32 now, ui32 firstDoc) const {
        firstDoc = ClampVal(firstDoc, now - TimeToLiveSec, now);
        return NextPhase(firstDoc, TimeToLiveSec, MinPhasedTtl(), Phase);
    }

public:
    using TValue = TInstant::TValue;

    void Start(TValue nowTimestamp, ui64 firstDocTimestamp) {
        if (AtomicCas(&StartSeconds, nowTimestamp, 0)) {
            if (IsPhasedMode()) {
                ui32 stop = GetStop(static_cast<ui32>(nowTimestamp), static_cast<ui32>(firstDocTimestamp));
                AtomicSet(PhasedStopTimestamp, stop);
                AtomicSet(PhasedStopSignal, 0);
                INFO_LOG << "TDiskIndexTtl::Start;ttl=" << TimeToLiveSec << ";now=" << nowTimestamp
                         << ";from=" << StartSeconds << ":d" << firstDocTimestamp
                         << ";to=" << GetDeadline() << ":d" << PhasedStopTimestamp << Endl;
            }
        }
    }

    Y_FORCE_INLINE bool HadSignal(TValue nowTimestamp) const {
        Y_VERIFY_DEBUG(StartSeconds);
        return AtomicGet(PhasedStopSignal) && nowTimestamp >= MinPhasedTtl() + static_cast<ui64>(StartSeconds) + TimeExtra;
    }

    Y_FORCE_INLINE ui64 GetDeadline() const {
        Y_VERIFY_DEBUG(StartSeconds);
        return (IsPhasedMode() ? MaxPhasedTtl() : TimeToLiveSec) + static_cast<ui64>(StartSeconds) + TimeExtra;
    }
};
