#include "shared_loader.h"
#include "file_manager.h"

namespace NSrvKernel {

    bool IsStatEqual(const TFileStat& l, const TFileStat& r) noexcept {
        const auto getKey = [](const TFileStat& stat) {
            return std::tie(stat.MTime, stat.CTime, stat.INode, stat.Size);
        };
        return getKey(l) == getKey(r);
    }

    TSharedFileReloader::TSharedFileReloader(TContExecutor* executor, TThreadedQueue* queue,
        TString path, TDuration interval, THolder<ISharedDataLoader> loader) noexcept
        : Executor_(executor)
        , Queue_(queue)
        , Loader_(std::move(loader))
        , Path_(std::move(path))
        , CheckInterval_(interval)
    {
        Y_VERIFY(Loader_);
        Y_VERIFY(Executor_);
        Y_VERIFY(Queue_);
    }

    void TSharedFileReloader::Start() noexcept {
        if (Task_.Running()) {
            return;
        }
        Task_ = TCoroutine{"file_reloader_cont", Executor_, [&] {
            class TCallback : public TThreadedQueue::ICallback {
            public:
                TCallback(TString path, const TFileStat& currentStat) noexcept
                    : Path_(std::move(path))
                    , Stat_(currentStat)
                {}

                TMaybe<TString> ExtractData() noexcept {
                    return std::move(Data_);
                }

                TFileStat GetStat() const noexcept {
                    return Stat_;
                }

            private:
                bool UpdateFileStat() noexcept {
                    const TFileStat next = TFileManager::Instance().GetStat(Path_);
                    if (IsStatEqual(Stat_, next)) {
                        return false;
                    }
                    Stat_ = next;
                    return true;
                }

                void UpdateFileData() {
                    if (Stat_.NLinks) {
                        const THolder<IInputStream> in = TFileManager::Instance().GetInput(Path_);
                        Data_ = in->ReadAll();
                    } else {
                        Data_ = Nothing();
                    }
                }

                void DoRun() noexcept override {
                    try {
                        if (UpdateFileStat()) {
                            UpdateFileData();
                        }
                    } catch (...) {
                    }
                }

            private:
                TString Path_;
                TFileStat Stat_;
                TMaybe<TString> Data_;
            };

            auto* cont = Executor_->Running();

            auto callback = MakeHolder<TCallback>(Path_, Stat_);
            while (!cont->Cancelled()) {
                auto returnedCallback = Queue_->Run(callback.Release(), TInstant::Max());
                if (returnedCallback) {
                    const TFileStat newStat = returnedCallback->GetStat();
                    if (!IsStatEqual(newStat, Stat_)) {
                        Stat_ = newStat;
                        TMaybe<TString> data = returnedCallback->ExtractData();
                        Loader_->Parse(std::move(data));
                    }

                    callback = std::move(returnedCallback);
                } else {
                    callback = MakeHolder<TCallback>(Path_, Stat_);
                }
                cont->SleepT(CheckInterval_);
            }
        }};
    }

    void TSharedFileReloader::Stop() noexcept {
        Task_ = {};
    }

    TInstant TSharedFileReloader::LastModifiedTime() const noexcept {
        return TInstant::Seconds(Stat_.MTime);
    }
}
