#include "capped_buffered_output.h"

#include <util/system/yassert.h>

void TCappedBufferedOutputBase::DoWrite(const void* vbuf, size_t len) {
    const char* buf = static_cast<const char*>(vbuf);
    Y_ASSERT(GetBufferAvail() > 0); // a full buffer should've been dumped previously, something went wrong
    if (len >= GetBufferAvail() && !Buffer.Empty()) {
        // we have data in the buffer from the previous write => need to append new data to old data
        // but it either doesn't fit (len > GetBufferAvail) or completely fills the buffer (len == GetBufferAvail)
        PushDataIntoBuffer(buf, len);
        Y_ASSERT(Buffer.Size() == WriteCap);   // buffer should be full now
        DumpBufferToSlave();
    }
    while (len >= WriteCap) {
        // dump data chunks directly into output until remaining part is less than WriteCap and fits into buffer
        DumpDataToSlave(buf, len);
    }
    if (len > 0) {
        PushDataIntoBuffer(buf, len);
    }
    Y_ASSERT(len == 0); // buf completely consumed
}

void TCappedBufferedOutputBase::DumpBufferToSlave() {
    Slave->Write(Buffer.Data(), Buffer.Size());
    Buffer.Clear();
}

void TCappedBufferedOutputBase::DumpDataToSlave(const char*& buf, size_t& len) {
    Y_ASSERT(Buffer.Empty());  // no data reordering
    Y_ASSERT(len >= WriteCap); // incomplete chunks always go through the buffer, no direct dumps
    Slave->Write(buf, WriteCap);
    buf += WriteCap;
    len -= WriteCap;
}

void TCappedBufferedOutputBase::PushDataIntoBuffer(const char*& buf, size_t& len) {
    size_t pushLen = (len <= GetBufferAvail() ? len : GetBufferAvail());
    Buffer.Append(buf, pushLen);
    buf += pushLen;
    len -= pushLen;
}

void TCappedBufferedOutputBase::DoFlush() {
    if (!Buffer.Empty()) {
        DumpBufferToSlave();
    }
    Slave->Flush();
}

void TCappedBufferedOutputBase::DoFinish() {
    IOutputStream::DoFinish();
    Slave->Finish();
}
