/************************************************
*
*  Chunked Data Source
*  Copyright (C) 2020
*  John Ryland
*  All rights reserved
*
************************************************/
#include <cassert>
#include "ChunkedDataSource.hpp"

namespace
{
    std::string removeCRLF(std::string aLine)
    {
        if (aLine[aLine.size()-1] == '\n')
            return aLine.substr(0, aLine.size()-1);
        return aLine;
    }
}

ChunkedDataSource::ChunkedDataSource(size_t aChunkSize, unsigned aRetryAttempts)
    : mChunkSize(aChunkSize)
    , mRetryAttempts(aRetryAttempts)
{
}

ChunkedReaderProxy::ChunkedReaderProxy(ChunkedDataSource& aProxiedSource)
    : OpenedDataSource()
    , mProxiedSource(aProxiedSource)
    , mBufferIndex(0)
{
    mBufferedData.reserve(mProxiedSource.ChunkSize() * 2);
}

ChunkedReaderProxy::~ChunkedReaderProxy()
{
    // delete mProxiedSource;
}

bool ChunkedReaderProxy::ReadBytes(size_t aNumberOfBytes, ReadCompletionCallback aCallback)
{
    ssize_t availableBytes = mBufferedData.size() - mBufferIndex;

    // Refill buffers a chunk at a time to meet the read request
    while (availableBytes < static_cast<ssize_t>(aNumberOfBytes))
    {
        ChunkedDataSource::ReadResult result = mProxiedSource.ReadChunk(mBufferedData);
        if (result == ChunkedDataSource::ReadResult::eError)
        {
            return false;
        }
        availableBytes = mBufferedData.size() - mBufferIndex;
    }

    // Fulfill the request
    assert(availableBytes > static_cast<ssize_t>(aNumberOfBytes));
    const auto start = mBufferedData.begin() + mBufferIndex;
    aCallback(ReadResult::eSuccess, VectorSlice<uint8_t>(start, start + aNumberOfBytes));
    mBufferIndex += aNumberOfBytes;

    // Fix up the buffer to contain up to a chunk of data with space for a 2nd chunk
    const size_t chunkSize = mProxiedSource.ChunkSize();
    assert(chunkSize != 0);
    if (mBufferIndex > chunkSize)
    {
        // Copy last chunk to start of buffer
        const size_t numberOfChunks = mBufferedData.size() / chunkSize;
        const size_t lastChunk = numberOfChunks - 1;
        const size_t lastChunkOffset = lastChunk * chunkSize;
        assert(mBufferIndex >= lastChunkOffset);
        const auto start = mBufferedData.begin() + lastChunkOffset;
        const auto end = start + chunkSize;
        mBufferedData.resize(chunkSize);
        std::copy(start, end, mBufferedData.begin());
        mBufferIndex -= lastChunkOffset;
    }

    return true;
}

bool ChunkedReaderProxy::ReadLine(ReadCompletionCallback aCallback)
{
    uint8_t ch = -1;
    std::vector<uint8_t> line;
    line.reserve(64);
    while (ch && ch != '\n')
    {
        ReadBytes(1, [&ch](ReadResult aResult, VectorSlice<uint8_t> aBytesRead)
        {
            ch = *aBytesRead.begin();
        });
        line.push_back(ch);
    }
    //line = removeCRLF(line);
    aCallback(ReadResult::eSuccess, VectorSlice<uint8_t>(line.begin(), line.end()));
}

bool ChunkedReaderProxy::ReadAll(ReadCompletionCallback aCallback)
{
    return ReadBytes(mProxiedSource.GetDataSize(), aCallback);
}
