/** 
 * @file llbuffer.cpp
 * @author Phoenix
 * @date 2005-09-20
 * @brief Implementation of the segments, buffers, and buffer arrays.
 *
 * $LicenseInfo:firstyear=2005&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"
#include "llbuffer.h"

#include "llmath.h"
#include "llstl.h"
#include "llthread.h"
#include <iterator>

#define ASSERT_LLBUFFERARRAY_MUTEX_LOCKED() llassert(!mMutexp || mMutexp->isSelfLocked())

/** 
 * LLSegment
 */
LLSegment::LLSegment() :
	mData(nullptr),
	mSize(0),
	mChannel(0)
{
}

LLSegment::LLSegment(S32 channel, U8* data, S32 data_len) :
	mData(data),
	mSize(data_len),
	mChannel(channel)
{
}

LLSegment::~LLSegment()
{
}

bool LLSegment::isOnChannel(S32 channel) const
{
	return (mChannel == channel);
}

S32 LLSegment::getChannel() const
{
	return mChannel;
}

void LLSegment::setChannel(S32 channel)
{
	mChannel = channel;
}


U8* LLSegment::data() const
{
	return mData;
}

S32 LLSegment::size() const
{
	return mSize;
}

bool LLSegment::operator==(const LLSegment& rhs) const
{
	if((mData != rhs.mData)||(mSize != rhs.mSize)||(mChannel != rhs.mChannel))
	{
		return false;
	}
	return true;
}

/** 
 * LLHeapBuffer
 */
LLHeapBuffer::LLHeapBuffer() :
	mBuffer(nullptr),
	mSize(0),
	mNextFree(nullptr),
	mReclaimedBytes(0)
{
	const S32 DEFAULT_HEAP_BUFFER_SIZE = 16384;
	allocate(DEFAULT_HEAP_BUFFER_SIZE);
}

LLHeapBuffer::LLHeapBuffer(S32 size) :
	mBuffer(nullptr),
	mSize(0),
	mNextFree(nullptr),
	mReclaimedBytes(0)
{
	allocate(size);
}

LLHeapBuffer::LLHeapBuffer(const U8* src, S32 len) :
	mBuffer(nullptr),
	mSize(0),
	mNextFree(nullptr),
	mReclaimedBytes(0)
{
	if((len > 0) && src)
	{
		allocate(len);
		if(mBuffer)
		{
			memcpy(mBuffer, src, len);	/*Flawfinder: ignore*/
		}
	}
}

// virtual
LLHeapBuffer::~LLHeapBuffer()
{
	delete[] mBuffer;
	mBuffer = nullptr;
	mSize = 0;
	mNextFree = nullptr;
}

S32 LLHeapBuffer::bytesLeft() const
{
	return (mSize - (mNextFree - mBuffer));
}

// virtual
bool LLHeapBuffer::createSegment(
	S32 channel,
	S32 size,
	LLSegment& segment)
{
	// get actual size of the segment.
	S32 actual_size = llmin(size, (mSize - S32(mNextFree - mBuffer)));

	// bail if we cannot build a valid segment
	if(actual_size <= 0)
	{
		return false;
	}

	// Yay, we're done.
	segment = LLSegment(channel, mNextFree, actual_size);
	mNextFree += actual_size;
	return true;
}

// virtual
bool LLHeapBuffer::reclaimSegment(const LLSegment& segment)
{
	if(containsSegment(segment))
	{
		mReclaimedBytes += segment.size();
		if(mReclaimedBytes == mSize)
		{
			// We have reclaimed all of the memory from this
			// buffer. Therefore, we can reset the mNextFree to the
			// start of the buffer, and reset the reclaimed bytes.
			mReclaimedBytes = 0;
			mNextFree = mBuffer;
		}
		else if(mReclaimedBytes > mSize)
		{
			LL_WARNS() << "LLHeapBuffer reclaimed more memory than allocated."
				<< " This is probably programmer error." << LL_ENDL;
		}
		return true;
	}
	return false;
}

// virtual
bool LLHeapBuffer::containsSegment(const LLSegment& segment) const
{
	// *NOTE: this check is fairly simple because heap buffers are
	// simple contiguous chunks of heap memory.
	if((mBuffer > segment.data())
	   || ((mBuffer + mSize) < (segment.data() + segment.size())))
	{
		return false;
	}
	return true;
}

void LLHeapBuffer::allocate(S32 size)
{
	mReclaimedBytes = 0;
	try
	{
		mBuffer = new U8[size];
		mSize = size;
		mNextFree = mBuffer;
	}
	catch (...)
	{
	}
}


/** 
 * LLBufferArray
 */
LLBufferArray::LLBufferArray() :
	mNextBaseChannel(0),
	mMutexp(nullptr)
{
}

LLBufferArray::~LLBufferArray()
{
	std::for_each(mBuffers.begin(), mBuffers.end(), DeletePointer());
	mBuffers.clear();
	delete mMutexp;
}

// static
LLChannelDescriptors LLBufferArray::makeChannelConsumer(
	const LLChannelDescriptors& channels)
{
	LLChannelDescriptors rv(channels.out());
	return rv;
}

void LLBufferArray::lock()
{
	if(mMutexp)
	{
		mMutexp->lock() ;
	}
}

void LLBufferArray::unlock()
{
	if(mMutexp)
	{
		mMutexp->unlock() ;
	}
}

LLMutex* LLBufferArray::getMutex()
{
	return mMutexp ;
}

void LLBufferArray::setThreaded(bool threaded)
{
	if(threaded)
	{
		if(!mMutexp)
		{
			mMutexp = new LLMutex();
		}
	}
	else
	{
		if(mMutexp)
		{
			delete mMutexp ;
			mMutexp = nullptr ;
		}
	}
}

LLChannelDescriptors LLBufferArray::nextChannel()
{
	LLChannelDescriptors rv(mNextBaseChannel++);
	return rv;
}

//mMutexp should be locked before calling this.
S32 LLBufferArray::capacity() const
{
	ASSERT_LLBUFFERARRAY_MUTEX_LOCKED();

	S32 total = 0;
	const_buffer_iterator_t iter = mBuffers.begin();
	const_buffer_iterator_t end = mBuffers.end();
	for(; iter != end; ++iter)
	{
		total += (*iter)->capacity();
	}
	return total;
}

bool LLBufferArray::append(S32 channel, const U8* src, S32 len)
{
	LLMutexLock lock(mMutexp) ;

	std::vector<LLSegment> segments;
	if(copyIntoBuffers(channel, src, len, segments))
	{
		mSegments.insert(mSegments.end(), segments.begin(), segments.end());
		return true;
	}
	return false;
}

//mMutexp should be locked before calling this.
bool LLBufferArray::prepend(S32 channel, const U8* src, S32 len)
{
	ASSERT_LLBUFFERARRAY_MUTEX_LOCKED();

	std::vector<LLSegment> segments;
	if(copyIntoBuffers(channel, src, len, segments))
	{
		mSegments.insert(mSegments.begin(), segments.begin(), segments.end());
		return true;
	}
	return false;
}

bool LLBufferArray::insertAfter(
	segment_iterator_t segment,
	S32 channel,
	const U8* src,
	S32 len)
{
	std::vector<LLSegment> segments;

	LLMutexLock lock(mMutexp) ;
	if(mSegments.end() != segment)
	{
		++segment;
	}
	if(copyIntoBuffers(channel, src, len, segments))
	{
		mSegments.insert(segment, segments.begin(), segments.end());
		return true;
	}
	return false;
}

//mMutexp should be locked before calling this.
LLBufferArray::segment_iterator_t LLBufferArray::splitAfter(U8* address)
{
	ASSERT_LLBUFFERARRAY_MUTEX_LOCKED();

	segment_iterator_t end = mSegments.end();
	segment_iterator_t it = getSegment(address);
	if(it == end)
	{
		return end;
	}

	// We have the location and the segment.
	U8* base = (*it).data();
	S32 size = (*it).size();
	if(address == (base + size))
	{
		// No need to split, since this is the last byte of the
		// segment. We do not want to have zero length segments, since
		// that will only incur processing overhead with no advantage.
		return it;
	}
	S32 channel = (*it).getChannel();
	LLSegment segment1(channel, base, (address - base) + 1);
	*it = segment1;
	segment_iterator_t rv = it;
	++it;
	LLSegment segment2(channel, address + 1, size - (address - base) - 1);
	mSegments.insert(it, segment2);
	return rv;
}
							   
//mMutexp should be locked before calling this.
LLBufferArray::segment_iterator_t LLBufferArray::beginSegment()
{
	ASSERT_LLBUFFERARRAY_MUTEX_LOCKED();
	return mSegments.begin();
}

//mMutexp should be locked before calling this.
LLBufferArray::segment_iterator_t LLBufferArray::endSegment()
{
	ASSERT_LLBUFFERARRAY_MUTEX_LOCKED();
	return mSegments.end();
}

//mMutexp should be locked before calling this.
LLBufferArray::segment_iterator_t LLBufferArray::constructSegmentAfter(
	U8* address,
	LLSegment& segment)
{
	ASSERT_LLBUFFERARRAY_MUTEX_LOCKED();
	segment_iterator_t rv = mSegments.begin();
	segment_iterator_t end = mSegments.end();
	if(!address)
	{
		if(rv != end)
		{
			segment = (*rv);
		}
	}
	else
	{
		// we have an address - find the segment it is in.
		for( ; rv != end; ++rv)
		{
			if((address >= (*rv).data())
			   && (address < ((*rv).data() + (*rv).size())))
			{
				if((++address) < ((*rv).data() + (*rv).size()))
				{
					// it's in this segment - construct an appropriate
					// sub-segment.
					segment = LLSegment(
						(*rv).getChannel(),
						address,
						(*rv).size() - (address - (*rv).data()));
				}
				else
				{
					++rv;
					if(rv != end)
					{
						segment = (*rv);
					}
				}
				break;
			}
		}
	}
	if(rv == end)
	{
		segment = LLSegment();
	}
	return rv;
}

//mMutexp should be locked before calling this.
LLBufferArray::segment_iterator_t LLBufferArray::getSegment(U8* address)
{
	ASSERT_LLBUFFERARRAY_MUTEX_LOCKED();
	segment_iterator_t end = mSegments.end();
	if(!address)
	{
		return end;
	}
	segment_iterator_t it = mSegments.begin();
	for( ; it != end; ++it)
	{
		if((address >= (*it).data())&&(address < (*it).data() + (*it).size()))
		{
			// found it.
			return it;
		}
	}
	return end;
}

//mMutexp should be locked before calling this.
LLBufferArray::const_segment_iterator_t LLBufferArray::getSegment(
	U8* address) const
{
	ASSERT_LLBUFFERARRAY_MUTEX_LOCKED();
	const_segment_iterator_t end = mSegments.end();
	if(!address)
	{
		return end;
	}
	const_segment_iterator_t it = mSegments.begin();
	for( ; it != end; ++it)
	{
		if((address >= (*it).data())
		   && (address < (*it).data() + (*it).size()))
		{
			// found it.
			return it;
		}
	}
	return end;
}

/*
U8* LLBufferArray::getAddressAfter(U8* address) 
{
	U8* rv = NULL;
	segment_iterator_t it = getSegment(address);
	segment_iterator_t end = mSegments.end();
	if(it != end)
	{
		if(++address < ((*it).data() + (*it).size()))
		{
			// it's in the same segment
			rv = address;
		}
		else
		{
			// it's in the next segment
			if(++it != end)
			{
				rv = (*it).data();
			}
		}
	}
	return rv;
}
*/

S32 LLBufferArray::countAfter(S32 channel, U8* start) const
{
	S32 count = 0;
	S32 offset = 0;
	const_segment_iterator_t it;

	LLMutexLock lock(mMutexp) ;
	const_segment_iterator_t end = mSegments.end();
	if(start)
	{
		it = getSegment(start);
		if(it == end)
		{
			return count;
		}
		if(++start < ((*it).data() + (*it).size()))
		{
			// it's in the same segment
			offset = start - (*it).data();
		}
		else if(++it == end)
		{
			// it's in the next segment
			return count;
		}
	}
	else
	{
		it = mSegments.begin();
	}
	while(it != end)
	{
		if((*it).isOnChannel(channel))
		{
			count += (*it).size() - offset;
		}
		offset = 0;
		++it;
	}
	return count;
}

U8* LLBufferArray::readAfter(
	S32 channel,
	U8* start,
	U8* dest,
	S32& len) const
{
	U8* rv = start;
	if(!dest || len <= 0)
	{
		return rv;
	}
	S32 bytes_left = len;
	len = 0;
	S32 bytes_to_copy = 0;
	const_segment_iterator_t it;

	LLMutexLock lock(mMutexp) ;
	const_segment_iterator_t end = mSegments.end();
	if(start)
	{
		it = getSegment(start);
		if(it == end)
		{
			return rv;
		}
		if((++start < ((*it).data() + (*it).size()))
		   && (*it).isOnChannel(channel))
		{
			// copy the data out of this segment
			S32 bytes_in_segment = (*it).size() - (start - (*it).data());
			bytes_to_copy = llmin(bytes_left, bytes_in_segment);
			memcpy(dest, start, bytes_to_copy); /*Flawfinder: ignore*/
			len += bytes_to_copy;
			bytes_left -= bytes_to_copy;
			rv = start + bytes_to_copy - 1;
			++it;
		}
		else
		{
			++it;
		}
	}
	else
	{
		it = mSegments.begin();
	}
	while(bytes_left && (it != end))
	{
		if(!((*it).isOnChannel(channel)))
		{
			++it;
			continue;
		}
		bytes_to_copy = llmin(bytes_left, (*it).size());
		memcpy(dest + len, (*it).data(), bytes_to_copy); /*Flawfinder: ignore*/
		len += bytes_to_copy;
		bytes_left -= bytes_to_copy;
		rv = (*it).data() + bytes_to_copy - 1;
		++it;
	}
	return rv;
}

U8* LLBufferArray::seek(
	S32 channel,
	U8* start,
	S32 delta) const
{
	ASSERT_LLBUFFERARRAY_MUTEX_LOCKED();
	const_segment_iterator_t it;
	const_segment_iterator_t end = mSegments.end();
	U8* rv = start;
	if(0 == delta)
	{
		if((U8*)npos == start)
		{
			// someone is looking for end of data. 
			segment_list_t::const_reverse_iterator rit = mSegments.rbegin();
			segment_list_t::const_reverse_iterator rend = mSegments.rend();
			while(rit != rend)
			{
				if(!((*rit).isOnChannel(channel)))
				{
					++rit;
					continue;
				}
				rv = (*rit).data() + (*rit).size();
				break;
			}
		}
		else if(start)
		{
			// This is sort of a weird case - check if zero bytes away
			// from current position is on channel and return start if
			// that is true. Otherwise, return NULL.
			it = getSegment(start);
			if((it == end) || !(*it).isOnChannel(channel))
			{
				rv = nullptr;
			}
		}
		else
		{
			// Start is NULL, so return the very first byte on the
			// channel, or NULL.
			it = mSegments.begin();
			while((it != end) && !(*it).isOnChannel(channel))
			{
				++it;
			}
			if(it != end)
			{
				rv = (*it).data();
			}
		}
		return rv;
	}
	if(start)
	{
		it = getSegment(start);
		if((it != end) && (*it).isOnChannel(channel))
		{
			if(delta > 0)
			{
				S32 bytes_in_segment = (*it).size() - (start - (*it).data());
				S32 local_delta = llmin(delta, bytes_in_segment);
				rv += local_delta;
				delta -= local_delta;
				++it;
			}
			else
			{
				S32 bytes_in_segment = start - (*it).data();
				S32 local_delta = llmin(llabs(delta), bytes_in_segment);
				rv -= local_delta;
				delta += local_delta;
			}
		}
	}
	else if(delta < 0)
	{
		// start is NULL, and delta indicates seeking backwards -
		// return NULL.
		return nullptr;
	}
	else
	{
		// start is NULL and delta > 0
		it = mSegments.begin();
	}
	if(delta > 0)
	{
		// At this point, we have an iterator into the segments, and
		// are seeking forward until delta is zero or we run out
		while(delta && (it != end))
		{
			if(!((*it).isOnChannel(channel)))
			{
				++it;
				continue;
			}
			if(delta <= (*it).size())
			{
				// it's in this segment
				rv = (*it).data() + delta;
			}
			delta -= (*it).size();
			++it;
		}
		if(delta && (it == end))
		{
			// Whoops - sought past end.
			rv = nullptr;
		}
	}
	else //if(delta < 0)
	{
		// We are at the beginning of a segment, and need to search
		// backwards.
		segment_list_t::const_reverse_iterator rit(it);
		segment_list_t::const_reverse_iterator rend = mSegments.rend();
		while(delta && (rit != rend))
		{
			if(!((*rit).isOnChannel(channel)))
			{
				++rit;
				continue;
			}
			if(llabs(delta) <= (*rit).size())
			{
				// it's in this segment.
				rv = (*rit).data() + (*rit).size() + delta;
				delta = 0;
			}
			else
			{
				delta += (*rit).size();
			}
			++rit;
		}
		if(delta && (rit == rend))
		{
			// sought past the beginning.
			rv = nullptr;
		}
	}
	return rv;
}

//test use only
bool LLBufferArray::takeContents(LLBufferArray& source)
{
	LLMutexLock lock(mMutexp);
	source.lock();

	std::copy(
		source.mBuffers.begin(),
		source.mBuffers.end(),
		std::back_insert_iterator<buffer_list_t>(mBuffers));
	source.mBuffers.clear();
	std::copy(
		source.mSegments.begin(),
		source.mSegments.end(),
		std::back_insert_iterator<segment_list_t>(mSegments));
	source.mSegments.clear();
	source.mNextBaseChannel = 0;
	source.unlock();

	return true;
}

//mMutexp should be locked before calling this.
LLBufferArray::segment_iterator_t LLBufferArray::makeSegment(
	S32 channel,
	S32 len)
{
	ASSERT_LLBUFFERARRAY_MUTEX_LOCKED();
	// start at the end of the buffers, because it is the most likely
	// to have free space.
	LLSegment segment;
	buffer_list_t::reverse_iterator it = mBuffers.rbegin();
	buffer_list_t::reverse_iterator end = mBuffers.rend();
	bool made_segment = false;
	for(; it != end; ++it)
	{
		if((*it)->createSegment(channel, len, segment))
		{
			made_segment = true;
			break;
		}
	}
	segment_iterator_t send = mSegments.end();
	if(!made_segment)
	{
		LLBuffer* buf = new LLHeapBuffer;
		mBuffers.push_back(buf);
		if(!buf->createSegment(channel, len, segment))
		{
			// failed. this should never happen.
			return send;
		}
	}

	// store and return the newly made segment
	mSegments.insert(send, segment);
	std::list<LLSegment>::reverse_iterator rv = mSegments.rbegin();
	++rv;
	send = rv.base();
	return send;
}

//mMutexp should be locked before calling this.
bool LLBufferArray::eraseSegment(const segment_iterator_t& erase_iter)
{
	ASSERT_LLBUFFERARRAY_MUTEX_LOCKED();

	// Find out which buffer contains the segment, and if it is found,
	// ask it to reclaim the memory.
	bool rv = false;
	LLSegment segment(*erase_iter);
	buffer_iterator_t iter = mBuffers.begin();
	buffer_iterator_t end = mBuffers.end();
	for(; iter != end; ++iter)
	{
		// We can safely call reclaimSegment on every buffer, and once
		// it returns true, the segment was found.
		if((*iter)->reclaimSegment(segment))
		{
			rv = true;
			break;
		}
	}

	// No need to get the return value since we are not interested in
	// the interator retured by the call.
	(void)mSegments.erase(erase_iter);
	return rv;
}

//mMutexp should be locked before calling this.
bool LLBufferArray::copyIntoBuffers(
	S32 channel,
	const U8* src,
	S32 len,
	std::vector<LLSegment>& segments)
{
	ASSERT_LLBUFFERARRAY_MUTEX_LOCKED();
	if(!src || !len) return false;
	S32 copied = 0;
	LLSegment segment;
	buffer_iterator_t it = mBuffers.begin();
	buffer_iterator_t end = mBuffers.end();
	for(; it != end;)
	{
		if(!(*it)->createSegment(channel, len, segment))
		{
			++it;
			continue;
		}
		segments.push_back(segment);
		S32 bytes = llmin(segment.size(), len);
		memcpy(segment.data(), src + copied, bytes);  /* Flawfinder: Ignore */
		copied += bytes;
		len -= bytes;
		if(0 == len)
		{
			break;
		}
	}
	while(len)
	{
		LLBuffer* buf = new LLHeapBuffer;
		mBuffers.push_back(buf);
		if(!buf->createSegment(channel, len, segment))
		{
			// this totally failed - bail. This is the weird corner
			// case were we 'leak' memory. No worries about an actual
			// leak - we will still reclaim the memory later, but this
			// particular buffer array is hosed for some reason.
			// This should never happen.
			return false;
		}
		segments.push_back(segment);
		memcpy(segment.data(), src + copied, segment.size());	/*Flawfinder: ignore*/
		copied += segment.size();
		len -= segment.size();
	}
	return true;
}
