#pragma once

#include <util/generic/singleton.h>

#include <mapreduce/yt/interface/client.h>

#include <robot/library/yt/static/table.h>

#include "config.h"

namespace NWebmaster {

using namespace NJupiter;

struct TWorkflow {
    enum EProcessState {
        NEW,
        IN_PROGRESS,
        DONE,
    };

    static TWorkflow& Instance() {
        return *Singleton<TWorkflow>();
    }

    static const TWorkflow& CInstance() {
        return Instance();
    }

    struct TAttrName {
        static constexpr char const CurrentSnapshot[]       = "current_snapshot";
        static constexpr char const PrevSnapshot[]          = "prev_snapshot";
        static constexpr char const ProcessedSnapshot[]     = "processed_snapshot";
        static constexpr char const InProgress[]            = "in_progress";
        static constexpr char const CurrentPreparat[]       = "preparat_current_timestamp";
        static constexpr char const PrevPreparat[]          = "preparat_previous_timestamp";
    };

    TWorkflow()
        : AttrRoot(TConfig::CInstance().TABLE_LINKS_ROOT)
    {
    }

    void SwitchCurrentSnapshot(NYT::IClientBasePtr client, time_t newCurrentSnapshot) {
        NYT::ITransactionPtr tx = client->StartTransaction();
        const time_t oldCurrentSnapshot = GetCurrentSnapshot(tx);
        if (oldCurrentSnapshot == newCurrentSnapshot) {
            ythrow yexception() << "SwitchCurrentSnapshot: invalid new snapshot " << newCurrentSnapshot;
        }
        SetPrevSnapshot(tx, oldCurrentSnapshot);
        SetCurrentSnapshot(tx, newCurrentSnapshot);
        tx->Commit();
    }

    bool GetInProgressSnapshots(NYT::IClientBasePtr client, time_t &prevSnapshotTs, time_t &currSnapshotTs) {
        NYT::TNode inProgressNode;
        try {
            inProgressNode = GetYtAttr(client, AttrRoot, TAttrName::InProgress);
        } catch (yexception &) {
            return false;
        }
        prevSnapshotTs = inProgressNode[TAttrName::PrevSnapshot].AsInt64();
        currSnapshotTs = inProgressNode[TAttrName::CurrentSnapshot].AsInt64();
        return true;
    }

    EProcessState BeginProcessSnapshots(NYT::IClientBasePtr client, time_t &prevSnapshotTs, time_t &currSnapshotTs) {
        time_t ts = 0;
        if (GetInProgressSnapshots(client, ts, ts)) {
            return IN_PROGRESS;
        }

        currSnapshotTs = GetCurrentSnapshot(client);
        const time_t processedSnapshotTs = GetProcessedSnapshot(client);

        if (currSnapshotTs == processedSnapshotTs) {
            return DONE;
        }

        if (currSnapshotTs < processedSnapshotTs) {
            ythrow yexception() << "BeginProcessSnapshots: currSnapshotTs < processedSnapshotTs";
        }

        if (currSnapshotTs <= prevSnapshotTs) {
            ythrow yexception() << "BeginProcessSnapshots: currSnapshotTs <= prevSnapshotTs";
        }

        prevSnapshotTs = GetPrevSnapshot(client);
        SetYtAttr(client, AttrRoot, TAttrName::InProgress, NYT::TNode()
            (TAttrName::CurrentSnapshot, currSnapshotTs)
            (TAttrName::PrevSnapshot, prevSnapshotTs)
        );

        return NEW;
    }

    void EndProcessSnapshots(NYT::IClientBasePtr client) {
        time_t prevTs = 0, currTs = 0;
        if (!GetInProgressSnapshots(client, prevTs, currTs)) {
            ythrow yexception() << "EndProcessSnapshots: there is no process in progress";
        }
        SetProcessedSnapshot(client, currTs);
        client->Remove(JoinYtMeta(AttrRoot, TAttrName::InProgress));
    }

    time_t GetCurrentSnapshot(NYT::IClientBasePtr client) const {
        return GetYtAttr(client, AttrRoot, TAttrName::CurrentSnapshot).AsInt64();
    }

    time_t GetPrevSnapshot(NYT::IClientBasePtr client) const {
        return GetYtAttr(client, AttrRoot, TAttrName::PrevSnapshot).AsInt64();
    }

    time_t GetProcessedSnapshot(NYT::IClientBasePtr client) const {
        return GetYtAttr(client, AttrRoot, TAttrName::ProcessedSnapshot).AsInt64();
    }

    void SetMergeSource(NYT::IClientBasePtr client, const TString &table, time_t prevSnapshotTs, time_t currSnapshotTs) {
        SetYtAttr(client, table, TAttrName::CurrentPreparat, currSnapshotTs);
        SetYtAttr(client, table, TAttrName::PrevPreparat, prevSnapshotTs);
    }

private:
    void SetCurrentSnapshot(NYT::IClientBasePtr client, time_t newCurrentSnapshot) {
        SetYtAttr(client, AttrRoot, TAttrName::CurrentSnapshot, newCurrentSnapshot);
    }

    void SetPrevSnapshot(NYT::IClientBasePtr client, time_t newPrevSnapshot) {
        SetYtAttr(client, AttrRoot, TAttrName::PrevSnapshot, newPrevSnapshot);
    }

    void SetProcessedSnapshot(NYT::IClientBasePtr client, time_t newProcessedSnapshot) {
        SetYtAttr(client, AttrRoot, TAttrName::ProcessedSnapshot, newProcessedSnapshot);
    }

public:
    TString AttrRoot;
};

} //namespace NWebmaster
