#include "mapreduce.h"

#include <saas/api/action.h>
#include <saas/api/mr_client/processors/processors.h>
#include <saas/library/sharding/sharding.h>
#include <saas/library/yt/attr_cache/attr_cache.h>
#include <saas/rtyserver/common/common_messages.h>
#include <saas/rtyserver/common/stream_messages.h>
#include <saas/rtyserver/common/message_collect_server_info.h>
#include <saas/rtyserver/common/search_control.h>
#include <saas/rtyserver/docfetcher/library/stream_basics.h>
#include <saas/rtyserver/docfetcher/library/types.h>
#include <saas/rtyserver/synchronizer/library/sync.h>

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

#include <library/cpp/logger/global/global.h>
#include <library/cpp/mediator/global_notifications/system_status.h>

#include <util/generic/deque.h>

namespace NFusion {

struct TChunk {
    TString Path;
    ui64 RowCount = 0;
    ui32 Timestamp = 0;
};

struct TMapReduceRecord {
    TActionPtr Action;
    TMapReducePosition Position;
};

struct IMapReduceQueue {
    virtual ~IMapReduceQueue() = default;
    virtual bool Get(TMapReduceRecord& record) = 0;
    virtual bool IsExhausted() const = 0;
    virtual void ReportStatus(NJson::TJsonValue& status) = 0;
};

namespace {
    constexpr ui32 ExhaustThreshold = 3;
    constexpr ui64 RecordBackupBit = 1ull << 63;
    constexpr ui32 ErrorsCapacity = 5;
    const TStringBuf ErrorsStatusKey = "Errors";
    const TStringBuf SearchableTimestampKey = "SearchableTimestamp";

    const TDuration SnapshotLockTimeout = TDuration::Seconds(15);

    const TDuration MasterCacheLifeTime = TDuration::Seconds(10);
    const TDuration ChunkCacheLifeTime = TDuration::Minutes(5);
    const size_t AttributeCacheSize = 1000;

    void InitializeYT() {
        static bool init = false;
        if (!init) {
            init = true;
            NYT::JoblessInitialize();
        }
    }


    struct TErrorsStorage {
        TErrorsStorage(TStringBuf statusKey, ui32 capacity)
            : StatusKey(statusKey)
            , Capacity(capacity)
        {
            CHECK_WITH_LOG(Capacity > 0);
            CHECK_WITH_LOG(!StatusKey.empty());
        }

        void AddError(TString message) {
            AddError(message, TInstant::Now().MicroSeconds());
        }

        void AddError(TString message, ui64 timestampMicroseconds) {
            ERROR_LOG << message << Endl;
            TWriteGuard guard(Mutex);
            while (Errors.size() >= Capacity) {
                Errors.pop_back();
            }
            Errors.emplace_front(std::move(message), timestampMicroseconds);
        }

        void ReportStatus(NJson::TJsonValue& status) const {
            NJson::TJsonValue jsonErrors(NJson::JSON_ARRAY);
            {
                TReadGuard guard(Mutex);
                for (auto& error : Errors) {
                    NJson::TJsonValue record(NJson::JSON_MAP);
                    record.InsertValue("error", error.first);
                    record.InsertValue("time_us", ToString(error.second));
                    jsonErrors.AppendValue(std::move(record));
                }
            }
            status.InsertValue(StatusKey, std::move(jsonErrors));
        }

    private:
        TRWMutex Mutex;
        TDeque<std::pair<TString, ui64>> Errors;
        const TStringBuf StatusKey;
        const size_t Capacity = 1;
    };

    i64 GetRowCount(NYT::IClientBase& ytClient, NSaas::TAttributeCache& cache, const TString& path, TDuration lifetime, bool throwOnError) {
        auto attr = cache.Get(ytClient, path, "row_count", lifetime);
        if (attr == nullptr) {
            ERROR_LOG << "Can not read " << path << "/@row_count" << Endl;
            if (throwOnError) {
                ythrow yexception() << "Can not read " << path << "/@row_count";
            }
            return -1;
        }
        return attr->AsInt64();
    }

    struct TTableIterator {
        TTableIterator(const NFusion::TMapReduceStreamConfig& config, NSaas::TAttributeCache& cache, TDuration cacheLifeTime,
                       TString tablePath, const NYT::TReadLimit& lowerLimit)
            : Config(config)
            , AttrCache(cache)
            , CacheLifeTime(cacheLifeTime)
            , TablePath(std::move(tablePath))
            , LowerLimit(lowerLimit)
            , UpperLimit(Default<NYT::TReadLimit>())
        {
            Initialize();
        }

        TTableIterator(const NFusion::TMapReduceStreamConfig& config, NSaas::TAttributeCache& cache, TDuration cacheLifeTime,
                       TString tablePath, const NYT::TReadLimit& lowerLimit, const NYT::TReadLimit& upperLimit, ui64 bitMask = 0)
            : Config(config)
            , AttrCache(cache)
            , CacheLifeTime(cacheLifeTime)
            , TablePath(std::move(tablePath))
            , LowerLimit(lowerLimit)
            , UpperLimit(upperLimit)
            , BitMask(bitMask)
        {
            Initialize();
        }

        const NYT::TYaMRRow& GetRow() const {
            return Reader->GetRow();
        }

        ui64 GetRowIndex() const {
            return Reader->GetRowIndex() | BitMask;
        }

        bool IsValid() const {
            return Reader->IsValid();
        }

        void Next() {
            Reader->Next();
        }

        void MoveTo(ui64 rowIndex) {
            ui64 realIndex = rowIndex & ~BitMask;
            ui64 current = Reader->GetRowIndex();
            if (current > realIndex) {
                ERROR_LOG << "Trying to seek to invalid position " << realIndex << " < " << current << Endl;
                return;
            }
            LowerLimit = NYT::TReadLimit().RowIndex(realIndex);
            InitializeReader();
        }

        void MoveBackwards() {
            ui64 newIndex = 0;
            if (Reader->IsValid()) {
                if (Reader->GetRowIndex() == 0) {
                    return;
                }
                newIndex = Reader->GetRowIndex() - 1;
            } else {
                i64 rowCount = GetRowCount(*TxClient, AttrCache, SnapshotPath, CacheLifeTime, false/*no exceptions*/);
                if (rowCount <= 0) {
                    return;
                }
                newIndex = rowCount - 1;
            }
            LowerLimit = NYT::TReadLimit().RowIndex(newIndex);
            InitializeReader();
        }

        NYT::IClientBase& GetYtClient() {
            return *TxClient;
        }

    private:
        void Initialize() {
            Client = NYT::CreateClient(Config.Server, NYT::TCreateClientOptions().Token(Config.Token));
            if (!Config.YTHosts.empty()) {
                NYT::TConfig::Get()->Hosts = Config.YTHosts;
            }
            TxClient = Client->StartTransaction();
            auto lock = TxClient->Lock(TablePath, NYT::LM_SNAPSHOT, NYT::TLockOptions().Waitable(true));
            lock->GetAcquiredFuture().Wait(SnapshotLockTimeout);
            SnapshotPath = TString::Join('#', GetGuidAsString(lock->GetLockedNodeId()));
            INFO_LOG << "Acquired snapshot lock " << SnapshotPath << " for " << TablePath << Endl;
            InitializeReader();
        }

        void InitializeReader() {
            NYT::TReadRange range;
            range.LowerLimit(LowerLimit);
            range.UpperLimit(UpperLimit);
            NYT::TRichYPath path;
            path.Path(SnapshotPath);
            path.AddRange(range);
            Reader = TxClient->CreateTableReader<NYT::TYaMRRow>(path, NYT::TTableReaderOptions().CreateTransaction(false));
            Y_ENSURE(Reader);

            if (Reader->IsValid()) {
                DEBUG_LOG << "Initialized table reader for " << TablePath << SnapshotPath << " at position " << Reader->GetRowIndex() << Endl;
            }
        }

        const NFusion::TMapReduceStreamConfig& Config;
        NSaas::TAttributeCache& AttrCache;
        const TDuration CacheLifeTime;
        const TString TablePath;
        TString SnapshotPath;
        NYT::TReadLimit LowerLimit;
        NYT::TReadLimit UpperLimit;
        NYT::IClientPtr Client;
        NYT::ITransactionPtr TxClient;
        NYT::TTableReaderPtr<NYT::TYaMRRow> Reader;
        ui64 BitMask = 0;
    };

    TVector<TChunk> GetYtQueueChunks(const NFusion::TMapReduceStreamConfig& config, NSaas::TAttributeCache& cache,
                                     const TString& tablePath, ui32 fromTimestamp, bool withPreviousRecord, bool readRowCounts) {
        INFO_LOG << "Read master table " << tablePath << " from " << fromTimestamp << ", with_previous=" << withPreviousRecord << Endl;
        TVector<TChunk> result;
        TTableIterator iterator(config, cache, MasterCacheLifeTime, tablePath, NYT::TReadLimit().Key(ToString(fromTimestamp)));
        if (withPreviousRecord) {
            iterator.MoveBackwards();
        }
        TChunk chunk;
        for (; iterator.IsValid(); iterator.Next()) {
            auto& row = iterator.GetRow();
            chunk.Path = row.Value;
            chunk.Timestamp = FromString<ui32>(row.Key);
            if (readRowCounts) {
                chunk.RowCount = GetRowCount(iterator.GetYtClient(), cache, chunk.Path, MasterCacheLifeTime, true/*throw on error*/);
            }
            if (!chunk.Timestamp) {
                ERROR_LOG << "Incorrect table timestamp (" << row.Key <<") for table: " << chunk.Path << Endl;
                continue;
            }
            result.push_back(chunk);
            INFO_LOG << "Discover chunk " << chunk.Timestamp << ", " << chunk.Path << Endl;
        }
        return result;
    }

    struct TMapReduceQueue : IMapReduceQueue {
        TMapReduceQueue(const TMapReduceStreamConfig& config, TMapReducePosition position, ui32 streamId)
            : Config(config)
            , CurrentPosition(position)
            , StreamId(streamId)
        {
            try {
                RowProcessor.Reset(NSaas::IRowProcessor::TFactory::Construct(config.RowProcessor, {}));
                const bool needToReadRowCounts = !!Config.BackupTable;
                UpdateChunks(needToReadRowCounts);
                // |position| points to a previously read record so we need to increment it
                if ((position.TableRecord | RecordBackupBit) != Max<ui64>()) {
                    ++position.TableRecord;
                } else {
                    ++position.TableTimestamp;
                    position.TableRecord = 0;
                }
                if (!Config.BackupTable || !BackupChunk.Path) {
                    AssertCorrectIndex(position.TableRecord < RecordBackupBit, "Found record index from backup while backup is not configured");
                } else {
                    if (position.TableRecord & RecordBackupBit) {
                        if (position.TableTimestamp == BackupChunk.Timestamp) {
                            NOTICE_LOG << "Continue to read backup table " << BackupChunk.Path << Endl;
                        } else {
                            NOTICE_LOG << "Can not find backup table for timestamp " << position.TableTimestamp
                                       << ". Applying backup " << BackupChunk.Path << Endl;
                            position.TableTimestamp = BackupChunk.Timestamp;
                            position.TableRecord = RecordBackupBit;
                        }
                    } else {
                        if (Chunks.empty() || position.TableTimestamp < Chunks.begin()->first) {
                            NOTICE_LOG << "Can not find " << position.TableTimestamp << " in the master table. Applying backup " << BackupChunk.Path << Endl;
                            position.TableTimestamp = BackupChunk.Timestamp;
                            position.TableRecord = RecordBackupBit;
                        } else if (Config.BackupSizeThresholdPercent > 0) {
                            double threshold = (100 + Config.BackupSizeThresholdPercent) * 0.01;
                            double totalRowCount = GetTotalRowCount(position.TableTimestamp, BackupChunk.Timestamp);
                            if (totalRowCount >= BackupChunk.RowCount * threshold) {
                                position.TableTimestamp = BackupChunk.Timestamp;
                                position.TableRecord = RecordBackupBit;
                                NOTICE_LOG << "Main stream is too far away(" << totalRowCount << "/" << BackupChunk.RowCount << "). Applying backup " << BackupChunk.Path << Endl;
                            }
                        }
                    }
                    if (position.TableRecord == RecordBackupBit) {
                        // FIXME(salmin). At this point we can only disable search in the mapreduce stream.
                        // Switch from the backup chunk to the next regular chunk happens seamlessly, so there's no
                        // clear point in the current implementation to execute EnableSearch().
                        // To avoid a stale SearchDisabled counter in the TBaseStream we run the global DisableSearch
                        // directly. Saas devops will run global EnableSearch manually.
                        NOTICE_LOG << "Disabling search before applying backup" << Endl;
                        NRTYServer::DisableSearch();

                        NOTICE_LOG << "Removing all final indexes before applying backup" << Endl;
                        NRTYServer::ClearFinalIndexes();
                        NOTICE_LOG << "Removed all final indexes successfully" << Endl;
                    }
                }
                ResetIterator(position);
            } catch (...) {
                RecoverMode = true;
                Errors.AddError(TString::Join("Unable to initialize TMapReduceQueue. ", CurrentExceptionMessage()));
            }
        }

        // returns total number of rows in [fromTimestamp, toTimestamp] interval of the MR-stream
        double GetTotalRowCount(ui32 fromTimestamp, ui32 toTimestamp) const {
            ui64 totalRows = 0;
            for (auto& chunk : Chunks) {
                if (chunk.first > toTimestamp) {
                    break;
                }
                if (chunk.first >= fromTimestamp) {
                    totalRows += chunk.second.RowCount;
                }
            }
            return static_cast<double>(totalRows);
        }

        bool Get(TMapReduceRecord& record) override {
            try {
                if (!Iterator.Defined() || !Iterator->IsValid()) {
                    UpdateChunks(false);
                    NextChunk();
                    GetSearchableTimestamp();
                    if (!Iterator.Defined()) {
                        ++ExhaustCount;
                        return false;
                    }
                    CHECK_WITH_LOG(Iterator->IsValid());
                }
                auto& row = Iterator->GetRow();
                ui64 recordIndex = Iterator->GetRowIndex();
                try {
                    auto node = NYT::TNode()("key", row.Key)("subkey", row.SubKey)("value", row.Value);
                    record.Action = MakeAtomicShared<NSaas::TAction>(RowProcessor->ProcessRowSingle(node));
                } catch (yexception& e) {
                    e << " at { row=" << recordIndex << ", key=" << TString{row.Key}.Quote() << ", subkey=" << TString{row.SubKey}.Quote() << " }";
                    throw ;
                }
                record.Position.TableTimestamp = CurrentPosition.TableTimestamp;
                record.Position.TableRecord = recordIndex;
                ExhaustCount = 0;
                Iterator->Next();
                if (!Iterator->IsValid()) {
                    record.Position.TableRecord |= Max<i64>();
                }
                CurrentPosition.TableRecord = recordIndex;
                return true;
            } catch (...) {
                Errors.AddError(TString::Join("Unable to read a document from YT-pull stream. ", CurrentExceptionMessage()));
                RecoverMode = true;
                Iterator.Clear();
                return false;
            }
        }

        bool IsExhausted() const override {
            return ExhaustCount >= ExhaustThreshold;
        }

        void ReportStatus(NJson::TJsonValue& status) override {
            ui64 currentTimestamp = 0, timestamp = GetSearchableTimestamp();
            if (status.IsMap()) {
                currentTimestamp = status.GetMapSafe()[SearchableTimestampKey].GetUInteger();
            }
            if (timestamp > currentTimestamp) {
                status.InsertValue(SearchableTimestampKey, timestamp);
            }
            Errors.ReportStatus(status);
        }

    private:
        TMapReducePosition GetIndexedPosition() const {
            TMessageGetIndexTimestamp message;
            SendGlobalMessage(message);
            auto timestamps = message.GetTimestamp().GetCurrentSnapshot();
            auto t = timestamps.find(StreamId);
            if (t == timestamps.end()) {
                return {};
            }
            return NFusion::TimestampToPosition(t->second.MaxValue);
        }

        ui32 GetSearchableTimestamp() const {
            auto position = GetIndexedPosition();
            if ((position.TableRecord | RecordBackupBit) == Max<ui64>()) {
                return position.TableTimestamp;
            }

            auto guard = Guard(ChunksMutex);
            if (Chunks.empty()) {
                ERROR_LOG << "List of chunks is empty" << Endl;
                return 0;
            }
            auto it = Chunks.lower_bound(position.TableTimestamp);
            if (it == Chunks.begin()) {
                ERROR_LOG << "List of chunks is too short, current=" << position.TableTimestamp << ", first_chunk_ts=" << Chunks.begin()->second.Timestamp << Endl;
                return 0;
            }
            --it;
            return it->first;
        }

        void UpdateChunks(bool readRowCounts) {
            auto indexedPosition = GetIndexedPosition();
            bool withPreviousRecord = (indexedPosition.TableRecord | RecordBackupBit) != Max<ui64>();
            TVector<TChunk> chunks = GetYtQueueChunks(Config, AttributeCache, Config.Table, indexedPosition.TableTimestamp, withPreviousRecord, readRowCounts);
            TVector<TChunk> backupChunks;
            if (Config.BackupTable) {
                try {
                    backupChunks = GetYtQueueChunks(Config, AttributeCache, Config.BackupTable, 0, false, readRowCounts);
                } catch (...) {
                    Errors.AddError(TString::Join("Unable to read master table. ", CurrentExceptionMessage()));
                }
            }
            auto guard = Guard(ChunksMutex);
            Chunks.clear();
            for (auto& chunk : chunks) {
                auto insertResult = Chunks.emplace(chunk.Timestamp, chunk);
                if (insertResult.second) {
                    DEBUG_LOG << "Registering queue chunk " << chunk.Timestamp << ":" << chunk.Path << Endl;
                }
            }
            if (Config.BackupTable && !backupChunks.empty()) {
                BackupChunk = backupChunks.back();
            }
        }

        void NextChunk() {
            auto it = Chunks.lower_bound(CurrentPosition.TableTimestamp);
            if (RecoverMode) {
                if (it != Chunks.end()) {
                    ResetIterator(CurrentPosition);
                    RecoverMode = false;
                    if (Iterator.Defined() && Iterator->IsValid()) {
                        return;
                    }
                }
                RecoverMode = false;
            }
            if (it != Chunks.end() && it->first == CurrentPosition.TableTimestamp) {
                ++it;
            }
            for (;; ++it) {
                if (it == Chunks.end()) {
                    Iterator.Clear();
                    return;
                }
                auto& chunk = it->second;
                INFO_LOG << "Switching to chunk " << chunk.Timestamp << ":" << chunk.Path << Endl;
                ResetIterator({0, chunk.Timestamp});
                if (!Iterator.Defined() || Iterator->IsValid()) {
                    return;
                }
            }
        }

        void ResetIterator(TMapReducePosition position) {
            Iterator.Clear();
            TChunk chunk;
            ui64 backupBit = 0;
            if (position.TableRecord & RecordBackupBit) {
                chunk = BackupChunk;
                backupBit = RecordBackupBit;
                INFO_LOG << "Start reading from backup table " << chunk.Path << Endl;
            } else {
                auto it = Chunks.lower_bound(position.TableTimestamp);
                if (it == Chunks.end()) {
                    Errors.AddError(TString::Join("Cannot find chunk for position ",
                                    ToString(position.TableTimestamp), "-", ToString(position.TableRecord)));
                    return;
                }
                if (it->first != position.TableTimestamp) {
                    Errors.AddError(TString::Join("Cannot find chunk with timestamp ", ToString(position.TableTimestamp)));
                }
                chunk = it->second;
                INFO_LOG << "Start reading from " << chunk.Path << Endl;
            }
            if (position.TableTimestamp != chunk.Timestamp) {
                position.TableTimestamp = chunk.Timestamp;
                position.TableRecord = backupBit;
            }
            auto lowerLimit = NYT::TReadLimit().Key(NSaas::GetShardString(Config.ShardMin));
            auto upperLimit = NYT::TReadLimit().Key(NSaas::GetShardString(Config.ShardMax + 1));
            Iterator.ConstructInPlace(Config, AttributeCache, ChunkCacheLifeTime, chunk.Path, lowerLimit, upperLimit, backupBit);
            if (position.TableRecord > 0 && Iterator->IsValid()) {
                Iterator->MoveTo(position.TableRecord);
            }
            CurrentPosition = position;
        }

        TMutex ChunksMutex;
        const TMapReduceStreamConfig& Config;
        THolder<NSaas::IRowProcessor> RowProcessor;
        TMap<ui32, TChunk> Chunks;
        TChunk BackupChunk;
        TMapReducePosition CurrentPosition;
        TMaybe<TTableIterator> Iterator;
        size_t ExhaustCount = 0;
        ui32 StreamId = 0;
        bool RecoverMode = false;

        NSaas::TAttributeCache AttributeCache{AttributeCacheSize};
        TErrorsStorage Errors{ErrorsStatusKey, ErrorsCapacity};
    };
}  // namespace

NRTYServer::TTimestampValue PositionToTimestamp(TMapReducePosition position) {
    return {position.TableTimestamp, position.TableRecord};
}

TMapReducePosition TimestampToPosition(NRTYServer::TTimestampValue timestamp) {
    TMapReducePosition result;
    result.TableTimestamp = GetHigh(timestamp);
    result.TableRecord = GetLow(timestamp);
    return result;
}

TMapReduceStream::TMapReduceStream(const TDocFetcherModule& owner, const TMapReduceStreamConfig& config, const TRTYServerConfig& globalConfig, TLog& log)
    : TBase(owner, config, globalConfig, log)
    , SignalsHolder<>(config)
    , Config(config)
    , Metrics(config.Name)
{
    InitializeYT();
}

TMapReduceStream::~TMapReduceStream() {
}

void TMapReduceStream::OnDocStreamStart() {
    const auto& snapshot = GetTimestampSnapshot();

    TMapReducePosition startPosition;
    startPosition.TableTimestamp = 0;
    startPosition.TableRecord = 0;

    const ui32 positionStream = GetSubStream(Config.StreamId, SubStreamPositionAux);
    const auto p = snapshot.find(positionStream);
    if (p != snapshot.end()) {
        INFO_LOG << "Stream " << Config.Name << ": found position timestamp " << positionStream << " " << p->second.MaxValue << Endl;
        startPosition = NFusion::TimestampToPosition(p->second.MaxValue);
    } else {
        WARNING_LOG << "Stream " << Config.Name << ": cannot find position timestamp " << positionStream << Endl;
    }

    Queue = MakeHolder<TMapReduceQueue>(Config, startPosition, positionStream);
}

void TMapReduceStream::OnDocStreamStop() {
    Queue.Reset();
}

TDuration TMapReduceStream::GetDelay() const {
    return GetDelayFromSubStream(
        GetTimestampSnapshot(),
        GetSubStream(Config.StreamId, SubStreamDocumentAux)
    );
}

bool TMapReduceStream::IsExhausted() const {
    return Queue->IsExhausted();
}

void TMapReduceStream::OnExhausted(TTimeToSleep& timeToSleep) {
    if (LastIncoming >= LastExhausted) {
        Signals()->ProcessExhausted();
        if (Config.ReopenIndexesOnExhaustion) {
            TMessageCollectServerInfo serverInfo(/* isHumanReadable = */ false);
            if (SendGlobalMessage(serverInfo) && serverInfo.GetDiskQueueSize() == 0) {
                NOTICE_LOG << "Stream " << Config.Name << " reopening indexers on exhausted YT-pull stream" << Endl;
                ReopenIndexers();
                LastExhausted = TInstant::Now();
            }
        }
    }
    if (Config.MasterTableCheckInterval > 0) {
        timeToSleep = TDuration::Seconds(Config.MasterTableCheckInterval);
    }
}

TActionPtr TMapReduceStream::GetDoc(TMap<TString, TString>& propsToLog, bool& /*skipSleepOnError*/) {
    CHECK_WITH_LOG(Queue);

    TMapReduceRecord payload;
    if (!Queue->Get(payload)) {
        return nullptr;
    }

    CHECK_WITH_LOG(payload.Action);
    NSaas::TDocument& doc = payload.Action->GetDocument();
    if (!doc.HasTimestamp()) {
        doc.SetTimestamp(payload.Position.TableTimestamp);
    }
    if (!doc.HasStreamId()) {
        doc.SetStreamId(GetSubStream(Config.StreamId, SubStreamDocumentAux));
    }
    doc.AddExtraTimestamp(
        GetSubStream(Config.StreamId, SubStreamPositionAux),
        NFusion::PositionToTimestamp(payload.Position)
    );

    propsToLog["position"] = ToString(payload.Position.TableRecord);
    return std::move(payload.Action);
}

void TMapReduceStream::ReportStatus(NJson::TJsonValue& status) {
    if (Queue) {
        Queue->ReportStatus(status);
    } else {
        ERROR_LOG << "MapReduce queue is not initialized" << Endl;
    }
}

}
