#include "donors_builder.h"
#include <saas/util/cluster/cluster.h>
#include <library/cpp/logger/global/global.h>

namespace NRTYReplication{

    struct TSlotWeightForRestore {
        bool BoostNSearch;
        bool BoostSameDC;
        NSearchMapParser::TShardIndex IntersectionSize;
        TString UniqKey;

        TSlotWeightForRestore(const NSearchMapParser::TSearchMapHost& healthSlot, const NSearchMapParser::TSearchMapHost& restoreSlot) {
            BoostNSearch = healthSlot.DisableSearch;
            NRTYCluster::TSlotData hsd(healthSlot.Name, healthSlot.SearchPort);
            NRTYCluster::TSlotData bsd(restoreSlot.Name, restoreSlot.SearchPort);
            BoostSameDC = hsd.GetDC() == bsd.GetDC();
            UniqKey = hsd.ShortSlotName();
            TInterval<NSearchMapParser::TShardIndex> intersection;
            if (healthSlot.Shards.Intersection(restoreSlot.Shards, intersection)) {
                IntersectionSize = intersection.GetLength();
            }
            else {
                IntersectionSize = 0;
            }
        }

        bool operator < (const TSlotWeightForRestore& item) const {
            if (BoostNSearch == item.BoostNSearch) {
                if (BoostSameDC == item.BoostSameDC) {
                    if (IntersectionSize == item.IntersectionSize)
                        return UniqKey < item.UniqKey;
                    else
                        return IntersectionSize < item.IntersectionSize;
                }
                else {
                    return item.BoostSameDC;
                }
            }
            else {
                return item.BoostNSearch;
            }
        }
    };

    NSearchMapParser::TSearchMapHost currentRestore;
    bool CompareHosts(const NSearchMapParser::TSearchMapHost* i, const NSearchMapParser::TSearchMapHost* j) {
        return TSlotWeightForRestore(*i, currentRestore) < TSlotWeightForRestore(*j, currentRestore);
    }

    void TDonorsBuilder::AddDownloadInfo(const NSearchMapParser::TSearchMapHost& restore, const NSearchMapParser::TSearchMapHost& donor) {
        NSearchMapParser::TShardsInterval interval;
        if (!restore.Shards.Intersection(donor.Shards, interval))
            return;
        while (true) {
            bool keyChange = false;
            for (ui32 i = 0; i < Intervals.size(); ++i) {
                TIntervalLink link = Intervals[i];
                NSearchMapParser::TShardsInterval intersection;
                if (interval.Intersection(link.Interval, intersection)) {
                    if (link.Restore.GetSlotName() != restore.GetSlotName()
                        && link.Donor && link.Donor->GetSlotName() != donor.GetSlotName()) {
                        continue;
                    }
                    if (link.Interval.CheckRightBorder(intersection.GetMin())) {
                        TIntervalLink leftMin(link.Restore, link.Interval.GetMin(), intersection.GetMin() - 1, link.Donor);
                        Intervals.push_back(leftMin);
                        keyChange = true;
                    }
                    if (link.Interval.CheckLeftBorder(intersection.GetMax())) {
                        TIntervalLink rightMax(link.Restore, intersection.GetMax() + 1, link.Interval.GetMax(), link.Donor);
                        Intervals.push_back(rightMax);
                        keyChange = true;
                    }
                    if (keyChange || link.Donor != &donor) {
                        TIntervalLink donorInt(link.Restore, intersection.GetMin(), intersection.GetMax(), &donor);
                        Intervals.push_back(donorInt);
                        Intervals.erase(Intervals.begin() + i);
                        break;
                    }
                }
            }
            if (!keyChange)
                break;
        }
    }

    TString TDonorsBuilder::Print() const {
        TString result;
        {
            TStringOutput so(result);
            so << "DONORS SCHEME" << Endl;
            for (ui32 i = 0; i < Intervals.size(); ++i) {
                so << Intervals[i].Interval.ToString() << ":" << Intervals[i].Restore.GetSlotName() << " <- ";
                if (Intervals[i].Donor)
                    so << Intervals[i].Donor->GetSlotName() << ":" << Intervals[i].Donor->Shards.ToString();
                else
                    so << "NO_DONOR";
                so << Endl;
            }
        }
        return result;
    }

    TVector<TIntervalLink> TDonorsBuilder::GetRestoreInfo(const NSearchMapParser::TSearchMapHost& host) const {
        TVector<TIntervalLink> result;
        for (ui32 i = 0; i < Intervals.size(); ++i) {
            if (Intervals[i].Restore.GetSlotName() == host.GetSlotName()) {
                for (ui32 test = 0; test < result.size(); ++test) {
                    if (result[test].Interval.Intersection(Intervals[i].Interval)) {
                        FAIL_LOG("Incorrect donors construction: %s", Print().data());
                    }
                }
                result.push_back(Intervals[i]);
            }
        }
        return result;
    }

    TVector<NSearchMapParser::TShardsInterval> TDonorsBuilder::GetDonorInfo(const NSearchMapParser::TSearchMapHost& host) const {
        TSet<NSearchMapParser::TShardsInterval> result;
        for (ui32 i = 0; i < Intervals.size(); ++i) {
            if (Intervals[i].Donor == &host)
                result.insert(Intervals[i].Interval);
        }
        return { result.begin(), result.end() };
    }

    bool TDonorsBuilder::BuildRemap() {
        TVector<NSearchMapParser::TSearchMapHost*> weightedHealthSlots;
        for (ui32 h = 0; h < UsefulSlots.size(); ++h) {
            weightedHealthSlots.push_back(&UsefulSlots[h]);
        }
        for (ui32 b = 0; b < RestoreSlots.size(); ++b) {
            currentRestore = RestoreSlots[b];
            Sort(weightedHealthSlots.begin(), weightedHealthSlots.end(), CompareHosts);
            for (ui32 add = 0; add < weightedHealthSlots.size(); ++add) {
                AddDownloadInfo(currentRestore, *weightedHealthSlots[add]);
            }
        }
        for (ui32 i = 0; i < Intervals.size(); ++i) {
            if (!Intervals[i].Donor)
                return false;
        }
        NOTICE_LOG << Print();
        return true;
    }

}
