#pragma once

#include <library/cpp/int128/int128.h>

#include <saas/api/common.h>

#include <saas/protos/timestamp.pb.h>
#include <saas/protos/positions.pb.h>
#include <util/folder/path.h>
#include <util/generic/map.h>
#include <util/generic/string.h>
#include <util/system/rwlock.h>
#include <util/datetime/base.h>
#include <util/generic/hash.h>

namespace NRTYServer {
    template <class TSnapshot>
    class IStreamData {
    public:
        bool IsPresent() const {
            TReadGuard g(ReadWriteMutex);
            return IsPresentNoLock();
        }

        TSnapshot GetCurrentSnapshot() const {
            TReadGuard g(ReadWriteMutex);
            return Snapshot;
        }

        void Clear() {
            TWriteGuard g(ReadWriteMutex);
            ClearNoLock();
        }

    protected:
        bool IsPresentNoLock() const {
            return !Snapshot.empty();
        }
        void ClearNoLock() {
            Snapshot.clear();
        }

    protected:
        mutable TRWMutex ReadWriteMutex;

        TSnapshot Snapshot;
    };

    template <class TBaseStreamData, class TDerived>
    class IIndexStreamData : public TBaseStreamData {
    public:
        IIndexStreamData(const TString& indexDir, const TString& filename)
            : IndexDir(indexDir)
            , Filename(filename)
        {}

        void Flush() const {
            FlushToDir(IndexDir);
        }

        void Read() {
            TWriteGuard g(TBaseStreamData::ReadWriteMutex);
            ReadNoLock();
        }

        void FlushToDir(const TFsPath& dir) const {
            TReadGuard g(TBaseStreamData::ReadWriteMutex);
            FlushToDirNoLock(dir);
        }

    protected:
        void ReadNoLock();
        void FlushToDirNoLock(const TFsPath& dir) const;

        void ProcessError(const TString& message) const;
    protected:
        const TFsPath IndexDir;
        const TString Filename;
    };

    using TStreamId = ui32;
    using TTimestampValue = ui128;

    struct TTs {
        TTimestampValue MaxValue = 0;
        TTimestampValue MinValue = 0;
        TTimestampValue AvgsSum = 0;
        ui32 AvgsPortions = 0;
        ui64 UpdateTimestamp = 0;
    };

    using TTimestampSnapshot = TMap<TStreamId, TTs>;

    class TBaseTimestamp : public IStreamData<TTimestampSnapshot> {
    public:
        template <class... TArgs>
        TTimestampValue GetMax(TArgs... args) const {
            TReadGuard g(ReadWriteMutex);
            return GetMaxNoLock(std::forward<TArgs>(args)...);
        }

        template <class... TArgs>
        TTimestampValue GetMin(TArgs... args) const {
            TReadGuard g(ReadWriteMutex);
            return GetMinNoLock(std::forward<TArgs>(args)...);
        }

        template <class... TArgs>
        ui64 GetAvg(TArgs... args) const {
            TReadGuard g(ReadWriteMutex);
            return GetAvgNoLock(std::forward<TArgs>(args)...);
        }

        template <class... TArgs>
        TTimestampValue Get(TArgs... args) const {
            return GetMax(std::forward<TArgs>(args)...);
        }

        template <class... TArgs>
        ui64 GetUpdateTimestamp(TArgs... args) const {
            TReadGuard g(ReadWriteMutex);
            return GetUpdateTimestampNoLock(std::forward<TArgs>(args)...);
        }

        void Set(TStreamId streamId, TTimestampValue value, ui64 updateTimestamp = Now().Seconds()) {
            TWriteGuard g(ReadWriteMutex);
            SetNoLock(streamId, value, updateTimestamp);
        }

        void Reset(TStreamId streamId) {
            Set(streamId, 0);
        }

        void Update(TStreamId streamId, TTimestampValue value, ui64 updateTimestamp = Now().Seconds()) {
            TWriteGuard g(ReadWriteMutex);
            UpdateNoLock(streamId, value, updateTimestamp, value, 1);
        }

        void Merge(const TBaseTimestamp& other, ui64 updateTimestamp = Now().Seconds());

    protected:
        TTimestampValue GetMaxNoLock() const {
            return CommonSnapshot.MaxValue;
        }

        TTimestampValue GetMinNoLock() const {
            return CommonSnapshot.MinValue;
        }

        ui64 GetAvgNoLock() const {
            return (CommonSnapshot.AvgsPortions != 0) ? GetLow(CommonSnapshot.AvgsSum) / CommonSnapshot.AvgsPortions : 0;
        }

        ui64 GetUpdateTimestampNoLock() const {
            return CommonSnapshot.UpdateTimestamp;
        }

        TTimestampValue GetMaxNoLock(TStreamId streamId) const {
            auto ptr = Snapshot.FindPtr(streamId);
            return ptr ? ptr->MaxValue : TTimestampValue(0);
        }

        TTimestampValue GetMinNoLock(TStreamId streamId) const {
            auto ptr = Snapshot.FindPtr(streamId);
            return ptr ? ptr->MinValue : TTimestampValue(0);
        }

        ui64 GetAvgNoLock(TStreamId streamId) const {
            auto ptr = Snapshot.FindPtr(streamId);
            return (ptr && ptr->AvgsPortions != 0) ? GetLow(ptr->AvgsSum) / ptr->AvgsPortions : 0;
        }

        ui64 GetUpdateTimestampNoLock(TStreamId streamId) const {
            auto ptr = Snapshot.FindPtr(streamId);
            return ptr ? ptr->UpdateTimestamp : 0;
        }

        void SetNoLock(TStreamId streamId, TTimestampValue timestamp, ui64 updateTimestamp = Now().Seconds());

        void RecalculateCommonSnapshot();

        void UpdateData(TTs& ts, TTimestampValue value, ui64 updateTimestamp, TTimestampValue avgSum = 0, ui64 avgPortions = 0);

        void UpdateCommonData(TTs& ts, TTimestampValue value, ui64 updateTimestamp, TTimestampValue avgSum = 0, ui64 avgPortions = 0);

        void UpdateStream(TStreamId streamId, TTimestampValue value, ui64 updateTimestamp = Now().Seconds(), TTimestampValue avgSum=0, ui64 avgPortions=0);

        void UpdateNoLock(TStreamId streamId, TTimestampValue value, ui64 updateTimestamp = Now().Seconds(), TTimestampValue avgSum=0, ui64 avgPortions=0);

        void MergeNoLock(const TBaseTimestamp& other, ui64 updateTimestamp = Now().Seconds());

        void ClearNocLock();

    private:
        TTs CommonSnapshot;
    };

    using TPositionsMap = THashMap<TString, TPositionValue>;

    using TPositionsSnapshot = TMap<TStreamId, TPositionsMap>;

    class TBasePositions : public IStreamData<TPositionsSnapshot>{
    public:
        TPositionValue Get(TStreamId streamId, const TString& key) const;

        void Update(TStreamId streamId, const TString& key, TPositionValue value);

        void Merge(const TBasePositions& other);

    protected:
        void UpdateNoLock(TStreamId streamId, const TString& key, TPositionValue value);
    };

    class TIndexTimestamp : public IIndexStreamData<TBaseTimestamp, TIndexTimestamp> {
    public:
        explicit TIndexTimestamp(const TString& indexDir);

        using TProtoType = TTimestamp;

        void FillProtobuf(TTimestamp& protobuf) const;
        void LoadFromProtobuf(const TTimestamp& protobuf);
    };

    class TIndexPositions : public IIndexStreamData<TBasePositions, TIndexPositions> {
    public:
        explicit TIndexPositions(const TString& indexDir);

        using TProtoType = TPositions;

        void FillProtobuf(TPositions& protobuf) const;
        void LoadFromProtobuf(const TPositions& protobuf);
    };
}
