/*
 *  Copyright (c) 2014, Oculus VR, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant 
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */

#include "DS_ByteQueue.h"
#include <string.h> // Memmove
#include <stdlib.h> // realloc
#include <stdio.h>


using namespace DataStructures;

ByteQueue::ByteQueue()
{
	readOffset=writeOffset=lengthAllocated=0;
	data=0;
}
ByteQueue::~ByteQueue()
{
	Clear(_FILE_AND_LINE_);
	

}
void ByteQueue::WriteBytes(const char *in, unsigned length, const char *file, unsigned int line)
{
	unsigned bytesWritten;
	bytesWritten=GetBytesWritten();
	if (lengthAllocated==0 || length > lengthAllocated-bytesWritten-1)
	{
		unsigned oldLengthAllocated=lengthAllocated;
		// Always need to waste 1 byte for the math to work, else writeoffset==readoffset
		unsigned newAmountToAllocate=length+oldLengthAllocated+1;
		if (newAmountToAllocate<256)
			newAmountToAllocate=256;
		lengthAllocated=lengthAllocated + newAmountToAllocate;
		data=(char*)rakRealloc_Ex(data, lengthAllocated, file, line);
		if (writeOffset < readOffset)
		{
			if (writeOffset <= newAmountToAllocate)
			{
				memcpy(data + oldLengthAllocated, data, writeOffset);
				writeOffset=readOffset+bytesWritten;
			}
			else
			{
				memcpy(data + oldLengthAllocated, data, newAmountToAllocate);
				memmove(data, data+newAmountToAllocate, writeOffset-newAmountToAllocate);
                writeOffset-=newAmountToAllocate;
			}
		}
	}

	if (length <= lengthAllocated-writeOffset)
		memcpy(data+writeOffset, in, length);
	else
	{
		// Wrap
		memcpy(data+writeOffset, in, lengthAllocated-writeOffset);
		memcpy(data, in+(lengthAllocated-writeOffset), length-(lengthAllocated-writeOffset));
	}
	writeOffset=(writeOffset+length) % lengthAllocated;
}
bool ByteQueue::ReadBytes(char *out, unsigned maxLengthToRead, bool peek)
{
	unsigned bytesWritten = GetBytesWritten();
	unsigned bytesToRead = bytesWritten < maxLengthToRead ? bytesWritten : maxLengthToRead;
	if (bytesToRead==0)
		return false;
	if (writeOffset>=readOffset)
	{
		memcpy(out, data+readOffset, bytesToRead);
	}
	else
	{
		unsigned availableUntilWrap = lengthAllocated-readOffset;
		if (bytesToRead <= availableUntilWrap)
		{
			memcpy(out, data+readOffset, bytesToRead);
		}
		else
		{
			memcpy(out, data+readOffset, availableUntilWrap);
			memcpy(out+availableUntilWrap, data, bytesToRead-availableUntilWrap);
		}
	}

	if (peek==false)
		IncrementReadOffset(bytesToRead);
		
	return true;
}
char* ByteQueue::PeekContiguousBytes(unsigned int *outLength) const
{
	if (writeOffset>=readOffset)
		*outLength=writeOffset-readOffset;
	else
		*outLength=lengthAllocated-readOffset;
	return data+readOffset;
}
void ByteQueue::Clear(const char *file, unsigned int line)
{
	if (lengthAllocated)
		rakFree_Ex(data, file, line );
	readOffset=writeOffset=lengthAllocated=0;
	data=0;
}
unsigned ByteQueue::GetBytesWritten(void) const
{
	if (writeOffset>=readOffset)
		return writeOffset-readOffset;
	else
		return writeOffset+(lengthAllocated-readOffset);
}
void ByteQueue::IncrementReadOffset(unsigned length)
{
	readOffset=(readOffset+length) % lengthAllocated;
}
void ByteQueue::DecrementReadOffset(unsigned length)
{
	if (length>readOffset)
		readOffset=lengthAllocated-(length-readOffset);
	else
		readOffset-=length;
}
void ByteQueue::Print(void)
{
	unsigned i;
	for (i=readOffset; i!=writeOffset; i++)
		RAKNET_DEBUG_PRINTF("%i ", data[i]);
	RAKNET_DEBUG_PRINTF("\n");
}
