/**
 *
 * @copyright Copyright (c) 2018-2021, ownCloud GmbH
 * @license OCL
 * @author Christian Kamm mail@ckamm.de
 * @author Hannah von Reth hannah.vonreth@owncloud.com
 *
 * This code is covered by the ownCloud Commercial License.
 *
 * You should have received a copy of the ownCloud Commercial License
 * along with this program. If not, see <https://owncloud.com/licenses/owncloud-commercial/>.
 *
 */

#include "hydrationdevice.h"

#include "common/filesystembase.h"
#include "common/utility.h"

using namespace OCC::FileSystem::SizeLiterals;

namespace {
// Calling CfExecute(TRANSFER_DATA) to feed received data will fail for some data lengths:
// Feeding 8192 bytes is proven to work, but odd amounts below that may fail with
// "The cloud operation is invalid". To avoid these failures the received data is buffered
// if necessary.
// Update 2022: The assumption is that we need a multiple of the 4k block size
constexpr qsizetype minChunkSizeC = 4_kb * 1000;
}

namespace OCC {

PlaceholderHydrationDevice::PlaceholderHydrationDevice(
    CfAPIUtil::CfOpdata opdata,
    quint64 startOffset,
    quint64 size)
    : _opdata(opdata)
    , _offset(startOffset)
    , _size(size)
{
    // reserve a buffer with twice the size
    _buffer.reserve(minChunkSizeC * 2);
    _progressCompleted.QuadPart = startOffset;
}

qint64 PlaceholderHydrationDevice::writeData(const char *data, qint64 maxSize)
{
    _buffer.append(data, maxSize);

    // Feed data if it has an acceptable size or if it's the final chunk.
    while (!_buffer.isEmpty() && (_buffer.size() >= minChunkSizeC || _buffer.size() == _size - _offset)) {
        auto feedAmount = std::min<uint64_t>(_buffer.size(), minChunkSizeC);

        auto opInfo = _opdata.toOpinfo();
        opInfo.Type = CF_OPERATION_TYPE_TRANSFER_DATA;
        opInfo.SyncStatus = nullptr; // can convey extra user-visible text through this

        CF_OPERATION_PARAMETERS opParams = {};
        opParams.ParamSize = CF_SIZE_OF_OP_PARAM(TransferData);
        opParams.TransferData.Flags = CF_OPERATION_TRANSFER_DATA_FLAG_NONE;
        opParams.TransferData.Offset.QuadPart = _offset;
        opParams.TransferData.Length.QuadPart = feedAmount;
        opParams.TransferData.CompletionStatus = STATUS_SUCCESS;
        opParams.TransferData.Buffer = _buffer.constData();

        HRESULT ok = CfExecute(&opInfo, &opParams);
        if (FAILED(ok)) {
            qCWarning(lcVfs) << "CfExecute error in writeData" << Utility::formatWinError(ok);
            return -1;
        }

        _offset += feedAmount;
        // inplace righ()
        const auto newSize = _buffer.size() - feedAmount;
        memcpy(_buffer.data(), _buffer.data() + feedAmount, newSize);
        _buffer.resize(newSize);
    }

    // refresh Windows Copy Dialog progress anyhow

    LARGE_INTEGER progressTotal = {};
    progressTotal.QuadPart = _size;

    _progressCompleted.QuadPart += maxSize;


    HRESULT ok = CfReportProviderProgress(_opdata.connectionKey, _opdata.transferKey, progressTotal, _progressCompleted);
    if (FAILED(ok)) {
        qCWarning(lcVfs) << "CfReportProviderProgress error in writeData" << Utility::formatWinError(ok);
    }
    return maxSize;
}

void PlaceholderHydrationDevice::close()
{
    if (_buffer.size() > 0) {
        qCCritical(lcVfs) << "Closed PlaceholderHydrationDevice while buffer still filled" << _buffer.size() << _offset << _size;
    }
    QIODevice::close();
}

}
