#include "document_makeup.h"
#include "read_write_makeup_manager.h"

#include <util/generic/set.h>
#include <util/generic/utility.h>
#include "zones_description.h"

namespace NZonesMakeup {

    void TDocumentMergerWriter::SerializeForMerger(NRTYMerger::IHeader* from, NRTYMerger::IHeader* to, IOutputStream& os) {
        TZonesDescription* fromZD = dynamic_cast<TZonesDescription*>(from);
        TZonesDescription* toZD = dynamic_cast<TZonesDescription*>(to);
        CHECK_WITH_LOG(fromZD);
        CHECK_WITH_LOG(toZD);
        CHECK_WITH_LOG(fromZD->GetZoneCount() >= ZoneLength.size());
        toZD->DecodeSpans(SpansZones, *fromZD, true);
        ZoneLength = toZD->DecodeLengths(ZoneLength, *fromZD, true);
        Serialize(os, false);
    }

    void TDocumentMergerWriter::DeserializeForMerger(IInputStream& os) {
        IsCorrectDocument = true;
        Deserialize(os, TReadWriteRTYMakeupManager::SerializeVersion, false);
    }

    void TDocumentMakeup::Serialize(IOutputStream& out, bool deep) const {
        VERIFY_WITH_LOG(IsCorrectDocument, "Incorrect document serialization attempt");

        if (deep) {
            MakeupStorage->GetZonesDescription()->Serialize(out);
        }

        const ui32 sizeSpans = SpansZones.size();
        out.Write(&sizeSpans, sizeof(sizeSpans));
        if (sizeSpans)
            out.Write(&SpansZones[0], sizeSpans * sizeof(SpansZones[0]));
        const ui32 size = ZoneLength.size();
        out.Write(&size, sizeof(size));
        if (size)
            out.Write(&ZoneLength[0], sizeof(ZoneLength[0]) * size);
    }

    void TDocumentMakeup::CleanSpans() {
        TSpansZones tempVector(MakeupStorage->GetAllocatorsStorage().GetSpansZonesAllocator());
        SpansZones.swap(tempVector);
    }

    void TDocumentMakeup::Deserialize(IInputStream& in, int /*version*/, bool deep) {
        VERIFY_WITH_LOG(IsCorrectDocument, "Incorrect document deserialization attempt");
        ZoneLength.clear();

        THolder<TZonesDescription> zd;
        if (deep)
            zd.Reset(new TZonesDescription(in, *dynamic_cast<TZonesDescription*>(MakeupStorage->GetZonesDescription())));
        ui32 sizeSpans;
        in.Load(&sizeSpans, sizeof(sizeSpans));
        if (sizeSpans) {
            SpansZones.resize(sizeSpans, 0);
            ui32 readBytes = in.Load(&SpansZones[0], sizeSpans * sizeof(SpansZones[0]));
            VERIFY_WITH_LOG(readBytes == sizeSpans * sizeof(SpansZones[0]), "Incorrect load size: %u, expected: %lu", readBytes, sizeSpans * sizeof(SpansZones[0]));
            if (!!zd)
                MakeupStorage->GetZonesDescription()->DecodeSpans(SpansZones, *zd);
        }
        ui32 size;
        in.Load(&size, sizeof(size));
        if (size) {
            ZoneLength.resize(size, 0);
            ui32 readBytes = in.Load(&ZoneLength[0], size * sizeof(ZoneLength[0]));
            VERIFY_WITH_LOG(readBytes == size * sizeof(ZoneLength[0]), "Incorrect load size: %u, expected: %lu", readBytes, size * sizeof(ZoneLength[0]));
            if (!!zd)
                ZoneLength = MakeupStorage->GetZonesDescription()->DecodeLengths(ZoneLength, *zd);
        }

        BuildMakeup();

    }

    bool TDocumentMakeup::operator == (const TDocumentMakeup& other) const {
        if (MakeupStorage->GetZonesDescription()->GetZoneCount() != other.MakeupStorage->GetZonesDescription()->GetZoneCount())
            return false;
        TSet<TString> myZonesNames, otherZonesNames;
        for (ui32 i = 0; i < MakeupStorage->GetZonesDescription()->GetZoneCount(); ++i) {
            myZonesNames.insert(MakeupStorage->GetZonesDescription()->GetZoneName(i));
            otherZonesNames.insert(other.MakeupStorage->GetZonesDescription()->GetZoneName(i));
        }

        if (other.SpansZones.size() != SpansZones.size())
            return false;

        for (ui32 i = 0; i < SpansZones.size() / 3; ++i) {
            bool keyFind = false;
            TString zoneName = MakeupStorage->GetZonesDescription()->GetZoneName(SpansZones[3 * i]);
            for (ui32 j = 0; j < other.SpansZones.size() / 3; ++j) {
                TString otherZoneName = other.MakeupStorage->GetZonesDescription()->GetZoneName(other.SpansZones[3 * j]);
                if (otherZoneName == zoneName) {
                    if (SpansZones[3 * i + 1] == other.SpansZones[3 * j + 1] && SpansZones[3 * i + 2] == other.SpansZones[3 * j + 2]) {
                        keyFind = true;
                        break;
                    }
                }
            }
            if (!keyFind)
                return false;
        }
        return true;
    }


    TDocumentMakeup::TDocumentMakeup(IMakeupStorage* makeupStorage, bool correct)
        : ZoneLength((makeupStorage ? makeupStorage->GetAllocatorsStorage() : TMakeupAllocatorsStorage::Default()).GetLengthZonesAllocator())
        , MakeupStorage(makeupStorage)
        , SentsMakeup((makeupStorage ? makeupStorage->GetAllocatorsStorage() : TMakeupAllocatorsStorage::Default()).GetSentsMakeupAllocator())
        , SpansZones((makeupStorage ? makeupStorage->GetAllocatorsStorage() : TMakeupAllocatorsStorage::Default()).GetSpansZonesAllocator())
        , IsCorrectDocument(correct)
    {}

    bool TDocumentMakeup::GetIsCorrectDocument() const {
        return IsCorrectDocument;
    }

    ui32 TDocumentMakeup::GetZoneLengthInWords(const ui32 zoneNumber) const {
        VERIFY_WITH_LOG(IsCorrectDocument, "Incorrect document usage attempt");
        if (zoneNumber < ZoneLength.size())
            return ZoneLength[zoneNumber];
        else
            return 0;
    }

    const IMakeupStorage* TDocumentMakeup::GetMakeupStorage() const {
        return MakeupStorage;
    }

    TDocumentMakeup::TDocumentMakeup(IMakeupStorage* makeupStorage, IInputStream& in, int version, bool deep)
        : ZoneLength(makeupStorage->GetAllocatorsStorage().GetLengthZonesAllocator())
        , MakeupStorage(makeupStorage)
        , SentsMakeup(makeupStorage->GetAllocatorsStorage().GetSentsMakeupAllocator())
        , SpansZones(makeupStorage->GetAllocatorsStorage().GetSpansZonesAllocator())
    {
        IsCorrectDocument = true;
        Deserialize(in, version, deep);
    }

    TDocumentMakeup::TDocumentMakeup(IMakeupStorage* makeupStorage
        , const TDocumentMakeup& doc)
        : ZoneLength(makeupStorage->GetAllocatorsStorage().GetLengthZonesAllocator())
        , MakeupStorage(makeupStorage)
        , SentsMakeup(makeupStorage->GetAllocatorsStorage().GetSentsMakeupAllocator())
        , SpansZones(makeupStorage->GetAllocatorsStorage().GetSpansZonesAllocator())
    {
        IsCorrectDocument = true;
        ZoneLength = MakeupStorage->GetZonesDescription()->DecodeLengths(doc.ZoneLength, *doc.MakeupStorage->GetZonesDescription());
        SpansZones = doc.SpansZones;
        MakeupStorage->GetZonesDescription()->DecodeSpans(SpansZones, *doc.MakeupStorage->GetZonesDescription());
        BuildMakeup();
    }

    void TDocumentMakeup::BuildMakeup() {
        if (!MakeupStorage || !MakeupStorage->GetDoClean())
            return;
        CHECK_WITH_LOG(!SentsMakeup.size());
        SentsMakeup.clear();
        ui32 zonesLengthsBorder = 0;
        for (ui32 i = 0; i < SpansZones.size() / 3; ++i) {
            TZoneId id = MakeupStorage->GetZonesDescription()->GetZoneId(SpansZones[3 * i]);
            if (id) {
                TZoneMakeup::MarkSentsBySpan(SentsMakeup, id, SpansZones[3 * i + 1], SpansZones[3 * i + 2]);
                if (zonesLengthsBorder < SpansZones[3 * i])
                    zonesLengthsBorder = SpansZones[3 * i];
            }
        }
        if (ZoneLength.size() > 0) {
            CHECK_WITH_LOG(zonesLengthsBorder < ZoneLength.size());
            TLengthZones zoneLengthNew(MakeupStorage->GetAllocatorsStorage().GetLengthZonesAllocator());
            zoneLengthNew.assign(ZoneLength.begin(), ZoneLength.begin() + zonesLengthsBorder + 1);
            ZoneLength = zoneLengthNew;
        }
        if (MakeupStorage->GetDoClean())
            CleanSpans();
    }

    void TDocumentMakeup::StoreZone(NZonesMakeup::TZoneNumber zoneNumber, ui16 sentBegin, ui16 /*wordBegin*/, ui32 wordBeginInDoc, ui16 sentEnd, ui16 /*wordEnd*/, ui32 wordEndInDoc) {
        VERIFY_WITH_LOG(IsCorrectDocument, "Incorrect document processing attempt");
        SpansZones.push_back(zoneNumber);
        SpansZones.push_back(sentBegin);
        SpansZones.push_back(sentEnd);
        if (zoneNumber >= ZoneLength.size())
            ZoneLength.resize(zoneNumber + 1, 0);
        ZoneLength[zoneNumber] = Min<ui32>(Max<NZonesMakeup::TLengthZones::value_type>(), ZoneLength[zoneNumber] + (wordEndInDoc - wordBeginInDoc));
    }

    bool TDocumentMakeup::operator != (const TDocumentMakeup& other) const {
        return !operator == (other);
    }
}
