/*
 * Copyright 2016 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/codec/SkStreamBuffer.h"

SkStreamBuffer::SkStreamBuffer(std::unique_ptr<SkStream> stream)
    : fStream(std::move(stream))
    , fPosition(0)
    , fBytesBuffered(0)
    , fHasLengthAndPosition(fStream->hasLength() && fStream->hasPosition())
    , fTrulyBuffered(0)
{}

SkStreamBuffer::~SkStreamBuffer() {
    fMarkedData.foreach([](size_t, SkData** data) { (*data)->unref(); });
}

const char* SkStreamBuffer::get() const {
    SkASSERT(fBytesBuffered >= 1);
    if (fHasLengthAndPosition && fTrulyBuffered < fBytesBuffered) {
        const size_t bytesToBuffer = fBytesBuffered - fTrulyBuffered;
        char* dst = SkTAddOffset<char>(const_cast<char*>(fBuffer), fTrulyBuffered);
        SkDEBUGCODE(const size_t bytesRead =)
        // This stream is rewindable, so it should be safe to call the non-const
        // read()
        const_cast<SkStream*>(fStream.get())->read(dst, bytesToBuffer);
        SkASSERT(bytesRead == bytesToBuffer);
        fTrulyBuffered = fBytesBuffered;
    }
    return fBuffer;
}

bool SkStreamBuffer::buffer(size_t totalBytesToBuffer) {
    // FIXME (scroggo): What should we do if the client tries to read too much?
    // Should not be a problem in GIF.
    SkASSERT(totalBytesToBuffer <= kMaxSize);

    if (totalBytesToBuffer <= fBytesBuffered) {
        return true;
    }

    if (fHasLengthAndPosition) {
        const size_t remaining = fStream->getLength() - fStream->getPosition() + fTrulyBuffered;
        fBytesBuffered = std::min(remaining, totalBytesToBuffer);
    } else {
        const size_t extraBytes = totalBytesToBuffer - fBytesBuffered;
        const size_t bytesBuffered = fStream->read(fBuffer + fBytesBuffered, extraBytes);
        fBytesBuffered += bytesBuffered;
    }
    return fBytesBuffered == totalBytesToBuffer;
}

size_t SkStreamBuffer::markPosition() {
    SkASSERT(fBytesBuffered >= 1);
    if (!fHasLengthAndPosition) {
        sk_sp<SkData> data(SkData::MakeWithCopy(fBuffer, fBytesBuffered));
        SkASSERT(nullptr == fMarkedData.find(fPosition));
        fMarkedData.set(fPosition, data.release());
    }
    return fPosition;
}

sk_sp<SkData> SkStreamBuffer::getDataAtPosition(size_t position, size_t length) {
    if (!fHasLengthAndPosition) {
        SkData** data = fMarkedData.find(position);
        SkASSERT(data);
        SkASSERT((*data)->size() == length);
        return sk_ref_sp<SkData>(*data);
    }

    SkASSERT(length <= fStream->getLength() &&
             position <= fStream->getLength() - length);

    const size_t oldPosition = fStream->getPosition();
    if (!fStream->seek(position)) {
        return nullptr;
    }

    sk_sp<SkData> data(SkData::MakeUninitialized(length));
    void* dst = data->writable_data();
    const bool success = fStream->read(dst, length) == length;
    fStream->seek(oldPosition);
    return success ? data : nullptr;
}
