--- /dev/null
+#include "api/internal/io/RollingBuffer_p.h"
+using namespace BamTools;
+using namespace BamTools::Internal;
+
+#include <iostream> // for debug
+
+#include <climits>
+#include <cstring>
+#include <algorithm>
+#include <string>
+using namespace std;
+
+// ------------------------------
+// RollingBuffer implementation
+// ------------------------------
+
+RollingBuffer::RollingBuffer(size_t growth)
+ : m_bufferGrowth(growth)
+{
+ // buffer always contains at least 1 (maybe empty) byte array
+ m_data.push_back( ByteArray() );
+
+ // set cleared state
+ Clear();
+}
+
+RollingBuffer::~RollingBuffer(void) { }
+
+size_t RollingBuffer::BlockSize(void) const {
+
+ // if only one byte array in buffer <- needed?
+ if ( m_tailBufferIndex == 0 )
+ return m_tail - m_head;
+
+ // otherwise return remaining num bytes in first array
+ const ByteArray& first = m_data.front();
+ return first.Size() - m_head;
+}
+
+bool RollingBuffer::CanReadLine(void) const {
+ return IndexOf('\n') != string::npos;
+}
+
+void RollingBuffer::Chop(size_t n) {
+
+ // update buffer size
+ if ( n > m_totalBufferSize )
+ m_totalBufferSize = 0;
+ else
+ m_totalBufferSize -= n;
+
+ // loop until target case hit
+ for ( ; ; ) {
+
+ // if only one array, decrement tail
+ if ( m_tailBufferIndex == 0 ) {
+ m_tail -= n;
+
+ // if all data chopped
+ if ( m_tail <= m_head ) {
+ m_head = 0;
+ m_tail = 0;
+ }
+ return;
+ }
+
+ // if there's room in last byte array to 'chop', just decrement tail
+ if ( n <= m_tail ) {
+ m_tail -= n;
+ return;
+ }
+
+ // otherwise we're going to overlap our internal byte arrays
+ // reduce our chop amount by the amount of data in the last byte array
+ n -= m_tail;
+
+ // remove last byte array & set tail to it's end
+ m_data.pop_back();
+ --m_tailBufferIndex;
+ m_tail = m_data.at(m_tailBufferIndex).Size();
+ }
+
+ // if buffer is now empty, reset state & clear up memory
+ if ( IsEmpty() )
+ Clear();
+}
+
+void RollingBuffer::Clear(void) {
+
+ // remove all byte arrays (except first)
+ m_data.erase( m_data.begin()+1, m_data.end() );
+
+ // clear out first byte array
+ m_data[0].Resize(0);
+ m_data[0].Squeeze();
+
+ // reset index & size markers
+ m_head = 0;
+ m_tail = 0;
+ m_tailBufferIndex = 0;
+ m_totalBufferSize = 0;
+}
+
+void RollingBuffer::Free(size_t n) {
+
+ // update buffer size
+ if ( n > m_totalBufferSize )
+ m_totalBufferSize = 0;
+ else
+ m_totalBufferSize -= n;
+
+ // loop until target case hit
+ for ( ; ; ) {
+
+ const size_t blockSize = BlockSize();
+
+ // if there's room in current array
+ if ( n < blockSize ) {
+
+ // shift 'head' over @n bytes
+ m_head += n;
+
+ // check for emptied, single byte array
+ if ( m_head == m_tail && m_tailBufferIndex == 0 ) {
+ m_head = 0;
+ m_tail = 0;
+ }
+
+ break;
+ }
+
+ // otherwise we need to check next byte array
+ // first update amount to remove
+ n -= blockSize;
+
+ // special case - there was only 1 array
+ if ( m_data.size() == 1 ) {
+ if ( m_data.at(0).Size() != m_bufferGrowth )
+ m_data[0].Resize(m_bufferGrowth);
+ m_head = 0;
+ m_tail = 0;
+ m_tailBufferIndex = 0;
+ break;
+ }
+
+ // otherwise, remove first array and move to next iteration
+ m_data.pop_front();
+ --m_tailBufferIndex;
+ m_head = 0;
+ }
+
+ // if buffer is now empty, reset state & clear up memory
+ if ( IsEmpty() )
+ Clear();
+}
+
+size_t RollingBuffer::IndexOf(char c) const {
+
+ size_t index(0);
+
+ // iterate over byte arrays
+ const size_t numBuffers = m_data.size();
+ for ( size_t i = 0; i < numBuffers; ++i ) {
+ const ByteArray& current = m_data.at(i);
+
+ // if on first array, use head; else 0
+ const size_t start = ( (i==0) ? m_head : 0 );
+
+ // if on last array, set end; else use current byte array size
+ const size_t end = ( (i==m_tailBufferIndex) ? m_tail : current.Size());
+
+ // look through this iteration's byte array for @c
+ const char* p = current.ConstData()+start;
+ for ( size_t j = start; j < end; ++j ) {
+ if ( *p++ == c )
+ return index;
+ ++index;
+ }
+ }
+
+ // no match found
+ return string::npos;
+}
+
+bool RollingBuffer::IsEmpty(void) const {
+ return (m_tailBufferIndex == 0) && (m_tail == 0);
+}
+
+size_t RollingBuffer::Read(char* dest, size_t max) {
+
+ size_t bytesToRead = std::min(Size(), max);
+ size_t bytesReadSoFar = 0;
+
+ while ( bytesReadSoFar < bytesToRead ) {
+ const char* readPtr = ReadPointer();
+ size_t blockBytes = std::min( (bytesToRead - bytesReadSoFar), BlockSize() );
+ if ( dest )
+ memcpy(dest+bytesReadSoFar, readPtr, blockBytes);
+ bytesReadSoFar += blockBytes;
+ Free(blockBytes);
+ }
+
+ return bytesReadSoFar;
+}
+
+size_t RollingBuffer::ReadLine(char* dest, size_t max) {
+
+ // if we can't read line or if max is 0
+ if ( !CanReadLine() || max == 0 )
+ return 0;
+
+ // otherwise, read until we hit newline
+ size_t bytesReadSoFar = 0;
+ bool finished = false;
+ while ( !finished ) {
+
+ const size_t index = IndexOf('\n');
+ const char* readPtr = ReadPointer();
+ size_t bytesToRead = std::min( (index+1)-bytesReadSoFar, BlockSize() );
+ bytesToRead = std::min( bytesToRead, (max-1)-bytesReadSoFar );
+ memcpy(dest+bytesReadSoFar, readPtr, bytesToRead);
+ bytesReadSoFar += bytesToRead;
+ Free(bytesToRead);
+
+ if ( !((bytesReadSoFar < index+1)&&(bytesReadSoFar < max-1)) )
+ finished = true;
+ }
+
+ // null terminate 'dest' & return numBytesRead
+ dest[bytesReadSoFar] = '\0';
+ return bytesReadSoFar;
+}
+
+string RollingBuffer::ReadLine(size_t max) {
+
+ ByteArray result;
+ result.Resize(max);
+
+ size_t numBytesRead = 0;
+
+ // if max not provided, we need to read incrementally
+ if ( max == 0 ) {
+ max = UINT_MAX;
+
+ // make sure we leave room for null terminator
+ result.Resize(1);
+
+ size_t readResult;
+ do {
+ result.Resize(std::min(max, result.Size()+m_bufferGrowth));
+ readResult = ReadLine(result.Data() + numBytesRead, result.Size() - numBytesRead);
+ if ( readResult > 0 || numBytesRead == 0 )
+ numBytesRead += readResult;
+ } while ( readResult == m_bufferGrowth && result[numBytesRead-1] != '\n');
+ }
+
+ // otherwise read line with provided max
+ else numBytesRead = ReadLine(result.Data(), result.Size());
+
+ // adjust byte array depending on numBytesRead
+ if ( numBytesRead == 0 )
+ result.Clear();
+ else
+ result.Resize(numBytesRead);
+
+ // return string from byte array
+ return string(result.ConstData(), result.Size());
+}
+
+const char* RollingBuffer::ReadPointer(void) const {
+
+ // return null if empty buffer
+ if ( m_data.empty() )
+ return 0;
+
+ // otherwise return pointer to current position
+ const ByteArray& first = m_data.front();
+ return first.ConstData() + m_head;
+}
+
+char* RollingBuffer::Reserve(size_t n) {
+
+ // if empty buffer
+ if ( m_totalBufferSize == 0 ) {
+ m_data[0].Resize( std::max(m_bufferGrowth, n) );
+ m_totalBufferSize += n;
+ m_tail = n;
+ return m_data[m_tailBufferIndex].Data();
+ }
+
+ // increment buffer's byte count
+ m_totalBufferSize += n;
+
+ // if buffer already contains enough space to fit @n more bytes
+ if ( (m_tail + n) <= m_data.at(m_tailBufferIndex).Size() ) {
+
+ // fetch write pointer at current 'tail', increment tail by @n & return
+ char* ptr = m_data[m_tailBufferIndex].Data() + m_tail;
+ m_tail += n;
+ return ptr;
+ }
+
+ // if last byte array isn't half full
+ if ( m_tail < m_data.at(m_tailBufferIndex).Size()/2 ) {
+
+ // we'll allow simple resize
+ m_data[m_tailBufferIndex].Resize(m_tail + n);
+
+ // fetch write pointer at current 'tail', increment tail by @n & return
+ char* ptr = m_data[m_tailBufferIndex].Data() + m_tail;
+ m_tail += n;
+ return ptr;
+ }
+
+ // otherwise, shrink last byte array to current used size
+ m_data[m_tailBufferIndex].Resize(m_tail);
+
+ // then append new byte array
+ m_data.push_back( ByteArray() );
+ ++m_tailBufferIndex;
+ m_data[m_tailBufferIndex].Resize( std::max(m_bufferGrowth, n) );
+ m_tail = n;
+
+ // return write-able pointer on new array
+ return m_data[m_tailBufferIndex].Data();
+}
+
+size_t RollingBuffer::Size(void) const {
+ return m_totalBufferSize;
+}
+
+void RollingBuffer::Write(const char* src, size_t n) {
+ char* writePtr = Reserve(n);
+ memcpy(writePtr, src, n);
+}