/*
 * OpenMRCP - Open Source Media Resource Control Protocol Stack
 * Copyright (C) 2007, Cepstral LLC
 *
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Author(s):
 * Arsen Chaloyan <achaloyan@yahoo.com>
 *
 * Contributor(s):
 *
 */

#include <apr_thread_cond.h>
#include "audio_buffer.h"

typedef enum {
	WRITER_STATE_NONE,
	WRITER_STATE_WAIT_FOR_WRITE,
	WRITER_STATE_WAIT_FOR_READ_COMPLETE
} writer_state_t;

typedef enum {
	READER_STATE_NONE,
	READER_STATE_PAUSED
} reader_state_t;

struct audio_buffer_t {
	char               *data;
	apr_size_t          max_size;
	apr_size_t          actual_size;
	apr_size_t          head;
	apr_size_t          tail;

	apr_byte_t          opened;
	writer_state_t      writer_state;
	reader_state_t      reader_state;

	apr_thread_mutex_t *guard;
	apr_thread_cond_t  *wait_object;

	apr_pool_t         *pool;
};

static void audio_buffer_cyclic_write(audio_buffer_t *buffer, void *data, apr_size_t size);
static void audio_buffer_cyclic_read(audio_buffer_t *buffer, void *data, apr_size_t size);


audio_buffer_t* audio_buffer_create(apr_size_t size, apr_pool_t *pool)
{
	audio_buffer_t *buffer = apr_palloc(pool,sizeof(audio_buffer_t));
	buffer->pool = pool;
	buffer->max_size = size;
	buffer->actual_size = 0;
	buffer->data = apr_palloc(pool,size);
	buffer->head = buffer->tail = 0;
	buffer->opened = 0;
	buffer->writer_state = WRITER_STATE_NONE;
	buffer->reader_state = READER_STATE_NONE;

	apr_thread_mutex_create(&buffer->guard,APR_THREAD_MUTEX_UNNESTED,pool);
	apr_thread_cond_create(&buffer->wait_object,pool);

	return buffer;
}

apr_status_t audio_buffer_destroy(audio_buffer_t *buffer)
{
	apr_thread_mutex_destroy(buffer->guard);
	apr_thread_cond_destroy(buffer->wait_object);
	return APR_SUCCESS;
}

apr_status_t audio_buffer_open(audio_buffer_t *buffer)
{
	apr_thread_mutex_lock(buffer->guard);

	buffer->actual_size = 0;
	buffer->head = buffer->tail = 0;
	buffer->opened = 1;

	apr_thread_mutex_unlock(buffer->guard);
	return APR_SUCCESS;
}

apr_status_t audio_buffer_close(audio_buffer_t *buffer)
{
	apr_thread_mutex_lock(buffer->guard);

	if(buffer->opened) {
		buffer->opened = 0;
		if(buffer->writer_state != WRITER_STATE_NONE) {
			buffer->writer_state = WRITER_STATE_NONE;
			apr_thread_cond_signal(buffer->wait_object);
		}
	}

	apr_thread_mutex_unlock(buffer->guard);
	return APR_SUCCESS;
}

apr_status_t audio_buffer_pause(audio_buffer_t *buffer)
{
	apr_thread_mutex_lock(buffer->guard);
	if(buffer->opened) {
		buffer->reader_state = READER_STATE_PAUSED;
	}
	apr_thread_mutex_unlock(buffer->guard);
	return APR_SUCCESS;
}

apr_status_t audio_buffer_resume(audio_buffer_t *buffer)
{
	apr_thread_mutex_lock(buffer->guard);
	if(buffer->opened) {
		buffer->reader_state = READER_STATE_NONE;
	}
	apr_thread_mutex_unlock(buffer->guard);
	return APR_SUCCESS;
}

apr_status_t audio_buffer_read(audio_buffer_t *buffer, void *data, apr_size_t size)
{
	apr_status_t status = APR_SUCCESS;
	apr_thread_mutex_lock(buffer->guard);

	if(buffer->opened) {
		if(buffer->reader_state == READER_STATE_NONE) {
			if(buffer->actual_size >= size) {
				audio_buffer_cyclic_read(buffer,data,size);

				if(buffer->writer_state == WRITER_STATE_WAIT_FOR_WRITE) {
					if(buffer->actual_size <= buffer->max_size/2) {
						buffer->writer_state = WRITER_STATE_NONE;
						apr_thread_cond_signal(buffer->wait_object);
					}
				}
			}
			else {
				memset(data,0,size);
				if(buffer->writer_state == WRITER_STATE_WAIT_FOR_READ_COMPLETE) {
					buffer->writer_state = WRITER_STATE_NONE;
					apr_thread_cond_signal(buffer->wait_object);
				}
				// failed
			}
		}
		else { /*paused*/
			memset(data,0,size);
		}
	}
	else {
		memset(data,0,size);
	}

	apr_thread_mutex_unlock(buffer->guard);
	return status;
}

apr_status_t audio_buffer_write(audio_buffer_t* buffer, void *data, apr_size_t size)
{
	apr_size_t write_size;
	apr_thread_mutex_lock(buffer->guard);

	if(buffer->opened) {
		while(size) {
			write_size = buffer->max_size - buffer->actual_size;
			if(write_size < size) {
				if(write_size) {
					audio_buffer_cyclic_write(buffer,data,write_size);
					size -= write_size;
					data = (char*)data + write_size;
				}

				/* wait for next write */
				buffer->writer_state = WRITER_STATE_WAIT_FOR_WRITE;
				apr_thread_cond_wait(buffer->wait_object,buffer->guard);
				if(!buffer->opened) {
					break;
				}
			}
			else {
				audio_buffer_cyclic_write(buffer,data,size);
				size = 0;
			}
		}
	}

	apr_thread_mutex_unlock(buffer->guard);
	return APR_SUCCESS;
}

apr_status_t audio_buffer_wait_for_read_complete(audio_buffer_t *buffer)
{
	apr_thread_mutex_lock(buffer->guard);
	
	if(buffer->opened) {
		buffer->writer_state = WRITER_STATE_WAIT_FOR_READ_COMPLETE;
		apr_thread_cond_wait(buffer->wait_object,buffer->guard);
	}
	
	apr_thread_mutex_unlock(buffer->guard);
	return APR_SUCCESS;
}

static void audio_buffer_cyclic_read(audio_buffer_t *buffer, void *data, apr_size_t size)
{
	if(buffer->tail + size > buffer->max_size) {
		apr_size_t size0 = buffer->max_size - buffer->tail;
		memcpy(data, buffer->data + buffer->tail, size0);
		memcpy((char*)data + size0, buffer->data, size - size0);
	}
	else {
		memcpy(data, buffer->data + buffer->tail, size);
	}

	buffer->tail = (buffer->tail + size) % buffer->max_size;
	buffer->actual_size -= size;
#if 0
	printf("AudioBuffer: Read [%lu,%lu,%lu]\n",buffer->head, buffer->tail, buffer->actual_size);
#endif
}

static void audio_buffer_cyclic_write(audio_buffer_t *buffer, void *data, apr_size_t size)
{
	if(buffer->head + size > buffer->max_size) {
		apr_size_t size0 = buffer->max_size - buffer->head;
		memcpy(buffer->data + buffer->head, data, size0);
		memcpy(buffer->data, (char*)data + size0, size - size0);
	}
	else {
		memcpy(buffer->data + buffer->head, data, size);
	}

	buffer->head = (buffer->head + size) % buffer->max_size;
	buffer->actual_size += size;
#if 0
	printf("AudioBuffer: Write [%lu,%lu,%lu]\n",buffer->head, buffer->tail, buffer->actual_size);
#endif
}
