#include "logbackend.h"

#include <balancer/kernel/coro/universal_sleep.h>

#include <util/system/guard.h>

using namespace NSrvKernel;

bool TLogRecordQueue::TryEnqueue(const TLogRecord& record) {
    // TryEnqueue is NOT thread-safe!
    // It is used in a single thread, and a pointer to TLogRecordQueue
    //  is passed to another thread atomically afterwards.
    if (Queue_.Size() >= Opts_.MaxQueueSize) {
        return false;
    } else {
        Queue_.PushBack(new TSelfSufficientLogRecord(record));
        return true;
    }
}

bool TLogRecordQueue::SubmitRecommended() const {
    return Queue_.Size() > 0 && Queue_.Size() % RecommendedQueueSize_ == 0;
}

const TLogRecordQueue::TQueueType& TLogRecordQueue::GetData() const noexcept {
    return Queue_;
}

TLogsThread::TLogsThread(TContExecutor& executor, IThreadFactory& pool, TBackendPtr slave)
    : Slave_(slave)
    , Executor_(executor)
{
    if (Slave_) {
        Thread_ = pool.Run(this);
    }
}

bool TLogsThread::TrySubmit(TLogRecordQueuePtr queue) noexcept {
    TTryGuard<TMutex> guard(Mutex_);
    if (guard.WasAcquired()) {
        if (ReceivedQueue_ == nullptr && AtomicGet(IsShutdowned_) == 0) {
            ReceivedQueue_ = queue;
            CondVar_.Signal();
            return true;
        }
    }
    return false;
}

TLogsThread::~TLogsThread() noexcept {
    Dispose();
}

void TLogsThread::ReopenLog() {
    DoWithTryGuard([&] {
        Y_ENSURE(AtomicGet(IsShutdowned_) == 0);
        Y_ENSURE(AtomicGet(ReopenState_) == EReopenState::RS_READY);
        AtomicSet(ReopenState_, EReopenState::RS_REQUESTED);
        CondVar_.Signal();
    });

    while (AtomicGet(ReopenState_) != EReopenState::RS_PROCESSED) {
        Y_ENSURE(UniversalSleep(TDuration::MilliSeconds(1)) != ECANCELED);
    }

    DoWithTryGuard([&] {
        std::exception_ptr error = nullptr;
        std::swap(error, ReopenError_);
        AtomicSet(ReopenState_, EReopenState::RS_READY);
        if (error != nullptr) {
            std::rethrow_exception(error);
        }
    });
}

void TLogsThread::DoExecute() {
    while (true) {
        TGuard<TMutex> guard(Mutex_);
        while (ReceivedQueue_ == nullptr && AtomicGet(ReopenState_) != EReopenState::RS_REQUESTED && AtomicGet(IsShutdowned_) == 0) {
            CondVar_.Wait(Mutex_);
        }

        if (AtomicGet(ReopenState_) == EReopenState::RS_REQUESTED) {
            DoReopenLog();
            AtomicSet(ReopenState_, EReopenState::RS_PROCESSED);
        }

        if (ReceivedQueue_ != nullptr) {
            HandleQueue(*ReceivedQueue_);
            ReceivedQueue_.Reset();
        }

        if (AtomicGet(IsShutdowned_) == 1) {
            break;
        }
    }
}

void TLogsThread::DoDispose() noexcept {
    if (Slave_) {
        {
            TGuard<TMutex> guard(Mutex_);
            AtomicSet(IsShutdowned_, 1);
            CondVar_.Signal();
        }
        Thread_->Join();
    }
}

void TLogsThread::HandleQueue(const TLogRecordQueue& queue) {
    try {
        for (const auto& i: queue.GetData()) {
            Slave_->WriteData(*i);
        }
    } catch (...) {
    }
}

template <typename F>
void TLogsThread::DoWithTryGuard(F&& action) {
    while (true) {
        TTryGuard<TMutex> guard(Mutex_);
        if (guard.WasAcquired()) {
            std::forward<F>(action)();
            break;
        }
        Y_ENSURE(Executor_.Running()->SleepT(TDuration::MilliSeconds(1)) != ECANCELED);
    }
}

void TLogsThread::DoReopenLog() noexcept {
    try {
        Slave_->ReopenLog();
    } catch (...) {
        ReopenError_ = std::current_exception();
    }
}

TContextSafeLogBackend::TContextSafeLogBackend(TContExecutor* executor, const TLogQueueOpts& opts, TLogsThread* thread,
        TSharedCounter processedItems, TSharedCounter lostItems)
    : Executor_(executor)
    , Opts_(opts)
    , Queue_(new TLogRecordQueue(Opts_))
    , LogsThread_(thread)
    , ProcessedLogItems_(std::move(processedItems))
    , LostLogItems_(std::move(lostItems))
{
    SubmitCont_ = Executor_->Create<TContextSafeLogBackend, &TContextSafeLogBackend::SubmitLoop>(this, "logs_regular_submit_cont");
}

TContextSafeLogBackend::~TContextSafeLogBackend() noexcept {
    Dispose();
}

void TContextSafeLogBackend::WriteData(const TLogRecord& record) {
    if (!Queue_->TryEnqueue(record)) {
        TrySubmit(true);
        Queue_->TryEnqueue(record);
    } else if (Queue_->SubmitRecommended()) {
        TrySubmit(false);
    }
}

void TContextSafeLogBackend::ReopenLog() {
    LogsThread_->ReopenLog();
}

void TContextSafeLogBackend::DoDispose() noexcept {
    SubmitContState_.SetKilled();
    TrySubmit(true);
}

void TContextSafeLogBackend::TrySubmit(bool dropOnFailure) noexcept {
    size_t queueSize = Queue_->GetData().size();
    if (queueSize == 0) {
        return;
    }

    TLogRecordQueuePtr queue = new TLogRecordQueue(Opts_);
    Queue_.Swap(queue);

    if (LogsThread_->TrySubmit(queue)) {
        ProcessedLogItems_.Add(queueSize);
    } else {
        if (dropOnFailure) {
            LostLogItems_.Add(queueSize);
            ProcessedLogItems_.Add(queueSize);
        } else {
            Queue_.Swap(queue);   // keeping the original queue
        }
    }
}

void TContextSafeLogBackend::SubmitLoop(TCont* cont) noexcept {
    while (SubmitContState_.IsAlive()) {
        if (cont->SleepT(Opts_.FlushInterval) == ECANCELED) {
            break;
        }
        TrySubmit(false);
    }
}
