#ifndef HAVE_CONFIG_H
#include "config.h"
#endif

#include <glib.h>
#include <string.h>

#include "buffer.h"
#include "debug.h"

static void audioresample_buffer_free_mem (AudioresampleBuffer * buffer,
    void *);
static void audioresample_buffer_free_subbuffer (AudioresampleBuffer * buffer,
    void *priv);


AudioresampleBuffer *
audioresample_buffer_new (void)
{
  AudioresampleBuffer *buffer;

  buffer = g_new0 (AudioresampleBuffer, 1);
  buffer->ref_count = 1;
  return buffer;
}

AudioresampleBuffer *
audioresample_buffer_new_and_alloc (int size)
{
  AudioresampleBuffer *buffer = audioresample_buffer_new ();

  buffer->data = g_malloc (size);
  buffer->length = size;
  buffer->free = audioresample_buffer_free_mem;

  return buffer;
}

AudioresampleBuffer *
audioresample_buffer_new_with_data (void *data, int size)
{
  AudioresampleBuffer *buffer = audioresample_buffer_new ();

  buffer->data = data;
  buffer->length = size;
  buffer->free = audioresample_buffer_free_mem;

  return buffer;
}

AudioresampleBuffer *
audioresample_buffer_new_subbuffer (AudioresampleBuffer * buffer, int offset,
    int length)
{
  AudioresampleBuffer *subbuffer = audioresample_buffer_new ();

  if (buffer->parent) {
    audioresample_buffer_ref (buffer->parent);
    subbuffer->parent = buffer->parent;
  } else {
    audioresample_buffer_ref (buffer);
    subbuffer->parent = buffer;
  }
  subbuffer->data = buffer->data + offset;
  subbuffer->length = length;
  subbuffer->free = audioresample_buffer_free_subbuffer;

  return subbuffer;
}

void
audioresample_buffer_ref (AudioresampleBuffer * buffer)
{
  buffer->ref_count++;
}

void
audioresample_buffer_unref (AudioresampleBuffer * buffer)
{
  buffer->ref_count--;
  if (buffer->ref_count == 0) {
    if (buffer->free)
      buffer->free (buffer, buffer->priv);
    g_free (buffer);
  }
}

static void
audioresample_buffer_free_mem (AudioresampleBuffer * buffer, void *priv)
{
  g_free (buffer->data);
}

static void
audioresample_buffer_free_subbuffer (AudioresampleBuffer * buffer, void *priv)
{
  audioresample_buffer_unref (buffer->parent);
}


AudioresampleBufferQueue *
audioresample_buffer_queue_new (void)
{
  return g_new0 (AudioresampleBufferQueue, 1);
}

int
audioresample_buffer_queue_get_depth (AudioresampleBufferQueue * queue)
{
  return queue->depth;
}

int
audioresample_buffer_queue_get_offset (AudioresampleBufferQueue * queue)
{
  return queue->offset;
}

void
audioresample_buffer_queue_free (AudioresampleBufferQueue * queue)
{
  GList *g;

  for (g = g_list_first (queue->buffers); g; g = g_list_next (g)) {
    audioresample_buffer_unref ((AudioresampleBuffer *) g->data);
  }
  g_list_free (queue->buffers);
  g_free (queue);
}

void
audioresample_buffer_queue_push (AudioresampleBufferQueue * queue,
    AudioresampleBuffer * buffer)
{
  queue->buffers = g_list_append (queue->buffers, buffer);
  queue->depth += buffer->length;
}

AudioresampleBuffer *
audioresample_buffer_queue_pull (AudioresampleBufferQueue * queue, int length)
{
  GList *g;
  AudioresampleBuffer *newbuffer;
  AudioresampleBuffer *buffer;
  AudioresampleBuffer *subbuffer;

  g_return_val_if_fail (length > 0, NULL);

  if (queue->depth < length) {
    return NULL;
  }

  RESAMPLE_LOG ("pulling %d, %d available", length, queue->depth);

  g = g_list_first (queue->buffers);
  buffer = g->data;

  if (buffer->length > length) {
    newbuffer = audioresample_buffer_new_subbuffer (buffer, 0, length);

    subbuffer = audioresample_buffer_new_subbuffer (buffer, length,
        buffer->length - length);
    g->data = subbuffer;
    audioresample_buffer_unref (buffer);
  } else {
    int offset = 0;

    newbuffer = audioresample_buffer_new_and_alloc (length);

    while (offset < length) {
      g = g_list_first (queue->buffers);
      buffer = g->data;

      if (buffer->length > length - offset) {
        int n = length - offset;

        memcpy (newbuffer->data + offset, buffer->data, n);
        subbuffer =
            audioresample_buffer_new_subbuffer (buffer, n, buffer->length - n);
        g->data = subbuffer;
        audioresample_buffer_unref (buffer);
        offset += n;
      } else {
        memcpy (newbuffer->data + offset, buffer->data, buffer->length);

        queue->buffers = g_list_delete_link (queue->buffers, g);
        offset += buffer->length;
        audioresample_buffer_unref (buffer);
      }
    }
  }

  queue->depth -= length;
  queue->offset += length;

  return newbuffer;
}

AudioresampleBuffer *
audioresample_buffer_queue_peek (AudioresampleBufferQueue * queue, int length)
{
  GList *g;
  AudioresampleBuffer *newbuffer;
  AudioresampleBuffer *buffer;
  int offset = 0;

  g_return_val_if_fail (length > 0, NULL);

  if (queue->depth < length) {
    return NULL;
  }

  RESAMPLE_LOG ("peeking %d, %d available", length, queue->depth);

  g = g_list_first (queue->buffers);
  buffer = g->data;
  if (buffer->length > length) {
    newbuffer = audioresample_buffer_new_subbuffer (buffer, 0, length);
  } else {
    newbuffer = audioresample_buffer_new_and_alloc (length);
    while (offset < length) {
      buffer = g->data;

      if (buffer->length > length - offset) {
        int n = length - offset;

        memcpy (newbuffer->data + offset, buffer->data, n);
        offset += n;
      } else {
        memcpy (newbuffer->data + offset, buffer->data, buffer->length);
        offset += buffer->length;
      }
      g = g_list_next (g);
    }
  }

  return newbuffer;
}

void
audioresample_buffer_queue_flush (AudioresampleBufferQueue * queue)
{
  GList *g;

  for (g = g_list_first (queue->buffers); g; g = g_list_next (g)) {
    audioresample_buffer_unref ((AudioresampleBuffer *) g->data);
  }
  g_list_free (queue->buffers);
  queue->buffers = NULL;
  queue->depth = 0;
  queue->offset = 0;
}