#pragma once

#include "block_merger.h"
#include <library/cpp/deprecated/mapped_file/mapped_file.h>
#include <library/cpp/streams/special/buffered_throttled_file.h>
#include <util/system/file.h>

namespace NRTYMerger {

    class ISerializableObjectsFactory {
    public:
        virtual ~ISerializableObjectsFactory() {};
        virtual IHeader::TPtr BuildDefaultHeader() const = 0;
        virtual IObject::TPtr BuildDefaultObject() const = 0;
    };

    class ISourceFileReader: public ISourceReader {
    private:
        TFile HdrFile;
        TFile DocsFile;
        TMappedFile DocsFileMap;
        const ISerializableObjectsFactory& Factory;
        TVector<ui64> Table;
        ui32 CountDocs;
    public:

        ISourceFileReader(const TString& hdrFileName, const TString& docsFileName, const ISerializableObjectsFactory& factory)
            : HdrFile(hdrFileName, RdOnly)
            , DocsFile(docsFileName, RdOnly)
            , DocsFileMap(DocsFile, TFileMap::oRdOnly)
            , Factory(factory)
        {
            BuildFastAcccessTable();
        }

        ~ISourceFileReader() override {}

        void BuildFastAcccessTable() {
            TMappedFileInput mfi(DocsFile);
            IObject::TPtr object = Factory.BuildDefaultObject();
            const ui64 av0 = mfi.Avail();
            CountDocs = 0;
            while (mfi.Avail()) {
                Table.push_back(av0 - mfi.Avail());
                object->DeserializeForMerger(mfi);
                ++CountDocs;
            }
        }

        IHeader::TPtr ReadHeader() override {
            TMappedFileInput mfi(HdrFile);
            IHeader::TPtr header = Factory.BuildDefaultHeader();
            header->DeserializeForMerger(mfi);
            return header;
        }

        IObject::TPtr ReadObject(ui64 position) override {
            TMemoryInput mi(DocsFileMap.getData(Table[position]), DocsFileMap.getSize() - Table[position]);
            IObject::TPtr object = Factory.BuildDefaultObject();
            object->DeserializeForMerger(mi);
            return object;
        }

        ui32 GetCountDocs() const {
            return CountDocs;
        }
    };

    class IDestFileWriter: public IDestWriter {
    private:
        TString HdrFileName;
        TString DocsFileName;
        const ISerializableObjectsFactory& Factory;
        TBufferedThrottledFileOutputStream HeaderOutput;
        TBufferedThrottledFileOutputStream DocsOutput;
        IHeader::TPtr Header;
        ui32 DocsCount = 0;

    public:

        IDestFileWriter(const TString& hdrFileName, const TString& docsFileName, const ISerializableObjectsFactory& factory, const TThrottle::TOptions& WriteOption)
            : HdrFileName(hdrFileName)
            , DocsFileName(docsFileName)
            , Factory(factory)
            , HeaderOutput(HdrFileName, WriteOption)
            , DocsOutput(DocsFileName, WriteOption)
        {
            Header = Factory.BuildDefaultHeader();
        }

        ~IDestFileWriter() override {}

        bool WriteHeader(ui32 docsCount) override {
            DocsCount = docsCount;
            return true;
        }

        bool AddHeader(IHeader* /*header*/) override {
            return true;
        }

        bool WriteObject(IHeader::TPtr& header, IObject::TPtr& object) override {
            try {
                object->SerializeForMerger(header.Get(), Header.Get(), DocsOutput);
                return true;
            } catch (...) {
                ERROR_LOG << "WriteObject exception: " << CurrentExceptionMessage() << Endl;
                return false;
            }
        }

        bool Finish() override {
            try {
                Header->SerializeForMerger(HeaderOutput, DocsCount);
                return true;
            } catch (...) {
                ERROR_LOG << "WriteHeader exception: " << CurrentExceptionMessage() << Endl;
                return false;
            }
        }
    };

};
