/*
 * Copyright 2008-2013 Various Authors
 * Copyright 2004 Timo Hirvonen
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include "buffer.h"
#include "xmalloc.h"
#include "locking.h"
#include "debug.h"

/*
 * chunk can be accessed by either consumer OR producer, not both at same time
 * -> no need to lock
 */
struct chunk {
	char data[CHUNK_SIZE];

	/* index to data, first filled byte */
	unsigned int l;

	/* index to data, last filled byte + 1
	 *
	 * there are h - l bytes available (filled)
	 */
	unsigned int h : 31;

	/* if chunk is marked filled it can only be accessed by consumer
	 * otherwise only producer is allowed to access the chunk
	 */
	unsigned int filled : 1;
};

unsigned int buffer_nr_chunks;

static pthread_mutex_t buffer_mutex = CMUS_MUTEX_INITIALIZER;
static struct chunk *buffer_chunks = NULL;
static unsigned int buffer_ridx;
static unsigned int buffer_widx;

void buffer_init(void)
{
	free(buffer_chunks);
	buffer_chunks = xnew(struct chunk, buffer_nr_chunks);
	buffer_reset();
}

void buffer_free(void)
{
	free(buffer_chunks);
}

/*
 * @pos: returned pointer to available data
 *
 * Returns number of bytes available at @pos
 *
 * After reading bytes mark them consumed calling buffer_consume().
 */
int buffer_get_rpos(char **pos)
{
	struct chunk *c;
	int size = 0;

	cmus_mutex_lock(&buffer_mutex);
	c = &buffer_chunks[buffer_ridx];
	if (c->filled) {
		size = c->h - c->l;
		*pos = c->data + c->l;
	}
	cmus_mutex_unlock(&buffer_mutex);

	return size;
}

/*
 * @pos: pointer to buffer position where data can be written
 *
 * Returns number of bytes can be written to @pos.  If the return value is
 * non-zero it is guaranteed to be >= 1024.
 *
 * After writing bytes mark them filled calling buffer_fill().
 */
int buffer_get_wpos(char **pos)
{
	struct chunk *c;
	int size = 0;

	cmus_mutex_lock(&buffer_mutex);
	c = &buffer_chunks[buffer_widx];
	if (!c->filled) {
		size = CHUNK_SIZE - c->h;
		*pos = c->data + c->h;
	}
	cmus_mutex_unlock(&buffer_mutex);

	return size;
}

void buffer_consume(int count)
{
	struct chunk *c;

	BUG_ON(count < 0);
	cmus_mutex_lock(&buffer_mutex);
	c = &buffer_chunks[buffer_ridx];
	BUG_ON(!c->filled);
	c->l += count;
	if (c->l == c->h) {
		c->l = 0;
		c->h = 0;
		c->filled = 0;
		buffer_ridx++;
		buffer_ridx %= buffer_nr_chunks;
	}
	cmus_mutex_unlock(&buffer_mutex);
}

/* chunk is marked filled if free bytes < 1024 or count == 0 */
int buffer_fill(int count)
{
	struct chunk *c;
	int filled = 0;

	cmus_mutex_lock(&buffer_mutex);
	c = &buffer_chunks[buffer_widx];
	BUG_ON(c->filled);
	c->h += count;

	if (CHUNK_SIZE - c->h < 1024 || (count == 0 && c->h > 0)) {
		c->filled = 1;
		buffer_widx++;
		buffer_widx %= buffer_nr_chunks;
		filled = 1;
	}

	cmus_mutex_unlock(&buffer_mutex);
	return filled;
}

void buffer_reset(void)
{
	int i;

	cmus_mutex_lock(&buffer_mutex);
	buffer_ridx = 0;
	buffer_widx = 0;
	for (i = 0; i < buffer_nr_chunks; i++) {
		buffer_chunks[i].l = 0;
		buffer_chunks[i].h = 0;
		buffer_chunks[i].filled = 0;
	}
	cmus_mutex_unlock(&buffer_mutex);
}

int buffer_get_filled_chunks(void)
{
	int c;

	cmus_mutex_lock(&buffer_mutex);
	if (buffer_ridx < buffer_widx) {
		/*
		 * |__##########____|
		 *    r         w
		 *
		 * |############____|
		 *  r           w
		 */
		c = buffer_widx - buffer_ridx;
	} else if (buffer_ridx > buffer_widx) {
		/*
		 * |#######______###|
		 *         w     r
		 *
		 * |_____________###|
		 *  w            r
		 */
		c = buffer_nr_chunks - buffer_ridx + buffer_widx;
	} else {
		/*
		 * |################|
		 *     r
		 *     w
		 *
		 * |________________|
		 *     r
		 *     w
		 */
		if (buffer_chunks[buffer_ridx].filled) {
			c = buffer_nr_chunks;
		} else {
			c = 0;
		}
	}
	cmus_mutex_unlock(&buffer_mutex);
	return c;
}
