/*-------------------------------------------------------------------------
 * drawElements Stream Library
 * ---------------------------
 *
 * Copyright 2014 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.
 *
 *//*!
 * \file
 * \brief Thread safe ringbuffer
 *//*--------------------------------------------------------------------*/
#include "deRingbuffer.h"

#include "deInt32.h"
#include "deMemory.h"
#include "deSemaphore.h"

#include <stdlib.h>
#include <stdio.h>

struct deRingbuffer_s
{
	deInt32			blockSize;
	deInt32			blockCount;
	deInt32*		blockUsage;
	deUint8*		buffer;

	deSemaphore		emptyCount;
	deSemaphore		fullCount;

	deInt32			outBlock;
	deInt32			outPos;

	deInt32			inBlock;
	deInt32			inPos;

	deBool			stopNotified;
	deBool			consumerStopping;
};

deRingbuffer* deRingbuffer_create (deInt32 blockSize, deInt32 blockCount)
{
	deRingbuffer* ringbuffer = (deRingbuffer*)deCalloc(sizeof(deRingbuffer));

	DE_ASSERT(ringbuffer);
	DE_ASSERT(blockCount > 0);
	DE_ASSERT(blockSize > 0);

	ringbuffer->blockSize	= blockSize;
	ringbuffer->blockCount	= blockCount;
	ringbuffer->buffer		= (deUint8*)deMalloc(sizeof(deUint8) * blockSize * blockCount);
	ringbuffer->blockUsage	= (deInt32*)deMalloc(sizeof(deUint32) * blockCount);
	ringbuffer->emptyCount	= deSemaphore_create(ringbuffer->blockCount, DE_NULL);
	ringbuffer->fullCount	= deSemaphore_create(0, DE_NULL);

	if (!ringbuffer->buffer		||
		!ringbuffer->blockUsage	||
		!ringbuffer->emptyCount	||
		!ringbuffer->fullCount)
	{
		if (ringbuffer->emptyCount)
			deSemaphore_destroy(ringbuffer->emptyCount);
		if (ringbuffer->fullCount)
			deSemaphore_destroy(ringbuffer->fullCount);
		deFree(ringbuffer->buffer);
		deFree(ringbuffer->blockUsage);
		deFree(ringbuffer);
		return DE_NULL;
	}

	memset(ringbuffer->blockUsage, 0, sizeof(deInt32) * blockCount);

	ringbuffer->outBlock	= 0;
	ringbuffer->outPos		= 0;

	ringbuffer->inBlock		= 0;
	ringbuffer->inPos		= 0;

	ringbuffer->stopNotified		= DE_FALSE;
	ringbuffer->consumerStopping	= DE_FALSE;

	return ringbuffer;
}

void deRingbuffer_stop (deRingbuffer* ringbuffer)
{
	/* Set notify to true and increment fullCount to let consumer continue */
	ringbuffer->stopNotified = DE_TRUE;
	deSemaphore_increment(ringbuffer->fullCount);
}

void deRingbuffer_destroy (deRingbuffer* ringbuffer)
{
	deSemaphore_destroy(ringbuffer->emptyCount);
	deSemaphore_destroy(ringbuffer->fullCount);

	free(ringbuffer->buffer);
	free(ringbuffer->blockUsage);
	free(ringbuffer);
}

static deStreamResult producerStream_write (deStreamData* stream, const void* buf, deInt32 bufSize, deInt32* written)
{
	deRingbuffer* ringbuffer = (deRingbuffer*)stream;

	DE_ASSERT(stream);
	/* If ringbuffer is stopping return error on write */
	if (ringbuffer->stopNotified)
	{
		DE_ASSERT(DE_FALSE);
		return DE_STREAMRESULT_ERROR;
	}

	*written = 0;

	/* Write while more data available */
	while (*written < bufSize)
	{
		deInt32		writeSize	= 0;
		deUint8*	src			= DE_NULL;
		deUint8*	dst			= DE_NULL;

		/* If between blocks accuire new block */
		if (ringbuffer->inPos == 0)
		{
			deSemaphore_decrement(ringbuffer->emptyCount);
		}

		writeSize	= deMin32(ringbuffer->blockSize - ringbuffer->inPos, bufSize - *written);
		dst			= ringbuffer->buffer + ringbuffer->blockSize * ringbuffer->inBlock + ringbuffer->inPos;
		src			= (deUint8*)buf + *written;

		deMemcpy(dst, src, writeSize);

		ringbuffer->inPos += writeSize;
		*written += writeSize;
		ringbuffer->blockUsage[ringbuffer->inBlock] += writeSize;

		/* Block is full move to next one (or "between" this and next block) */
		if (ringbuffer->inPos == ringbuffer->blockSize)
		{
			ringbuffer->inPos = 0;
			ringbuffer->inBlock++;

			if (ringbuffer->inBlock == ringbuffer->blockCount)
				ringbuffer->inBlock = 0;
			deSemaphore_increment(ringbuffer->fullCount);
		}
	}

	return DE_STREAMRESULT_SUCCESS;
}

static deStreamResult producerStream_flush (deStreamData* stream)
{
	deRingbuffer* ringbuffer = (deRingbuffer*)stream;

	DE_ASSERT(stream);

	/* No blocks reserved by producer */
	if (ringbuffer->inPos == 0)
		return DE_STREAMRESULT_SUCCESS;

	ringbuffer->inPos		= 0;
	ringbuffer->inBlock++;

	if (ringbuffer->inBlock == ringbuffer->blockCount)
		ringbuffer->inBlock = 0;

	deSemaphore_increment(ringbuffer->fullCount);
	return DE_STREAMRESULT_SUCCESS;
}

static deStreamResult producerStream_deinit (deStreamData* stream)
{
	DE_ASSERT(stream);

	producerStream_flush(stream);

	/* \note mika Stream doesn't own ringbuffer, so it's not deallocated */
	return DE_STREAMRESULT_SUCCESS;
}

static deStreamResult consumerStream_read (deStreamData* stream, void* buf, deInt32 bufSize, deInt32* read)
{
	deRingbuffer* ringbuffer = (deRingbuffer*)stream;

	DE_ASSERT(stream);

	*read = 0;
	DE_ASSERT(ringbuffer);

	while (*read < bufSize)
	{
		deInt32		writeSize	= 0;
		deUint8*	src			= DE_NULL;
		deUint8*	dst			= DE_NULL;

		/* If between blocks accuire new block */
		if (ringbuffer->outPos == 0)
		{
			/* If consumer is set to stop after everything is consumed,
			 * do not block if there is no more input left
			 */
			if (ringbuffer->consumerStopping)
			{
				/* Try to accuire new block, if can't there is no more input */
				if (!deSemaphore_tryDecrement(ringbuffer->fullCount))
				{
					return DE_STREAMRESULT_END_OF_STREAM;
				}
			}
			else
			{
				/* If not stopping block until there is more input */
				deSemaphore_decrement(ringbuffer->fullCount);
				/* Ringbuffer was set to stop */
				if (ringbuffer->stopNotified)
				{
					ringbuffer->consumerStopping = DE_TRUE;
				}
			}

		}

		writeSize	= deMin32(ringbuffer->blockUsage[ringbuffer->outBlock] - ringbuffer->outPos, bufSize - *read);
		src			= ringbuffer->buffer + ringbuffer->blockSize * ringbuffer->outBlock + ringbuffer->outPos;
		dst			= (deUint8*)buf + *read;

		deMemcpy(dst, src, writeSize);

		ringbuffer->outPos += writeSize;
		*read += writeSize;

		/* Block is consumed move to next one (or "between" this and next block) */
		if (ringbuffer->outPos == ringbuffer->blockUsage[ringbuffer->outBlock])
		{
			ringbuffer->blockUsage[ringbuffer->outBlock] = 0;
			ringbuffer->outPos = 0;
			ringbuffer->outBlock++;

			if (ringbuffer->outBlock == ringbuffer->blockCount)
				ringbuffer->outBlock = 0;

			deSemaphore_increment(ringbuffer->emptyCount);
		}
	}

	return DE_STREAMRESULT_SUCCESS;
}


static deStreamResult consumerStream_deinit (deStreamData* stream)
{
	DE_ASSERT(stream);
	DE_UNREF(stream);

	return DE_STREAMRESULT_SUCCESS;
}

/* There are no sensible errors so status is always good */
deStreamStatus dummy_getStatus (deStreamData* stream)
{
	DE_UNREF(stream);

	return DE_STREAMSTATUS_GOOD;
}

/* There are no sensible errors in ringbuffer */
static const char* dummy_getError (deStreamData* stream)
{
	DE_ASSERT(stream);
	DE_UNREF(stream);
	return DE_NULL;
}

static const deIOStreamVFTable producerStreamVFTable = {
	DE_NULL,
	producerStream_write,
	dummy_getError,
	producerStream_flush,
	producerStream_deinit,
	dummy_getStatus
};

static const deIOStreamVFTable consumerStreamVFTable = {
	consumerStream_read,
	DE_NULL,
	dummy_getError,
	DE_NULL,
	consumerStream_deinit,
	dummy_getStatus
};

void deProducerStream_init (deOutStream* stream, deRingbuffer* buffer)
{
	stream->ioStream.streamData = (deStreamData*)buffer;
	stream->ioStream.vfTable = &producerStreamVFTable;
}

void deConsumerStream_init (deInStream* stream, deRingbuffer* buffer)
{
	stream->ioStream.streamData = (deStreamData*)buffer;
	stream->ioStream.vfTable = &consumerStreamVFTable;
}