/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ANDROID_UTIL_ENCODED_BUFFER_H
#define ANDROID_UTIL_ENCODED_BUFFER_H

#include <stdint.h>
#include <vector>

namespace android {
namespace util {

/**
 * A stream of bytes containing a read pointer and a write pointer,
 * backed by a set of fixed-size buffers.  There are write functions for the
 * primitive types stored by protocol buffers, but none of the logic
 * for tags, inner objects, or any of that.
 *
 * Terminology:
 *      *Pos:       Position in the whole data set (as if it were a single buffer).
 *      *Index:     Index of a buffer within the mBuffers list.
 *      *Offset:    Position within a buffer.
 */
class EncodedBuffer
{
public:
    EncodedBuffer();
    EncodedBuffer(size_t chunkSize);
    ~EncodedBuffer();

    class Pointer {
    public:
        Pointer();
        Pointer(size_t chunkSize);

        size_t pos() const;
        size_t index() const;
        size_t offset() const;

        Pointer* move(size_t amt);
        inline Pointer* move() { return move(1); };
        Pointer* rewind();

        Pointer copy() const;

    private:
        size_t mChunkSize;
        size_t mIndex;
        size_t mOffset;
    };

    /**
     * Clears the buffer by rewinding its write pointer to avoid de/allocate buffers in heap.
     */
    void clear();

    /******************************** Write APIs ************************************************/

    /**
     * Returns the number of bytes written in the buffer
     */
    size_t size() const;

    /**
     * Returns the write pointer.
     */
    Pointer* wp();

    /**
     * Returns the current position of write pointer, if the write buffer is full, it will automatically
     * rotate to a new buffer with given chunkSize. If NULL is returned, it means NO_MEMORY
     */
    uint8_t* writeBuffer();

    /**
     * Returns the writeable size in the current write buffer .
     */
    size_t currentToWrite();

    /**
     * Write a single byte to the buffer.
     */
    void writeRawByte(uint8_t val);

    /**
     * Write a varint32 into the buffer. Return the size of the varint.
     */
    size_t writeRawVarint32(uint32_t val);

    /**
     * Write a varint64 into the buffer. Return the size of the varint.
     */
    size_t writeRawVarint64(uint64_t val);

    /**
     * Write Fixed32 into the buffer.
     */
    void writeRawFixed32(uint32_t val);

    /**
     * Write Fixed64 into the buffer.
     */
    void writeRawFixed64(uint64_t val);

    /**
     * Write a protobuf header. Return the size of the header.
     */
    size_t writeHeader(uint32_t fieldId, uint8_t wireType);

    /********************************* Edit APIs ************************************************/
    /**
     * Returns the edit pointer.
     */
    Pointer* ep();

    /**
     * Read a single byte at ep, and move ep to next byte;
     */
    uint8_t readRawByte();

    /**
     * Read varint starting at ep, ep will move to pos of next byte.
     */
    uint64_t readRawVarint();

    /**
     * Read 4 bytes starting at ep, ep will move to pos of next byte.
     */
    uint32_t readRawFixed32();

    /**
     * Read 8 bytes starting at ep, ep will move to pos of next byte.
     */
    uint64_t readRawFixed64();

    /**
     * Edit 4 bytes starting at pos.
     */
    void editRawFixed32(size_t pos, uint32_t val);

    /**
     * Copy _size_ bytes of data starting at __srcPos__ to wp, srcPos must be larger than wp.pos().
     */
    void copy(size_t srcPos, size_t size);

    /********************************* Read APIs ************************************************/
    class iterator;
    friend class iterator;
    class iterator {
    public:
        iterator(const EncodedBuffer& buffer);

        /**
         * Returns the number of bytes written in the buffer
         */
        size_t size() const;

        /**
         * Returns the size of total bytes read.
         */
        size_t bytesRead() const;

        /**
         * Returns the read pointer.
         */
        Pointer* rp();

        /**
         * Returns the current position of read pointer, if NULL is returned, it reaches end of buffer.
         */
        uint8_t const* readBuffer();

        /**
         * Returns the readable size in the current read buffer.
         */
        size_t currentToRead();

        /**
         * Returns true if next bytes is available for read.
         */
        bool hasNext();

        /**
         * Reads the current byte and moves pointer 1 bit.
         */
        uint8_t next();

        /**
         * Read varint from iterator, the iterator will point to next available byte.
         */
        uint64_t readRawVarint();

    private:
        const EncodedBuffer& mData;
        Pointer mRp;
    };

    /**
     * Returns the iterator of EncodedBuffer so it guarantees consumers won't be able to modified the buffer.
     */
    iterator begin() const;

private:
    size_t mChunkSize;
    std::vector<uint8_t*> mBuffers;

    Pointer mWp;
    Pointer mEp;

    inline uint8_t* at(const Pointer& p) const; // helper function to get value
};

} // util
} // android

#endif // ANDROID_UTIL_ENCODED_BUFFER_H

