#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <fcntl.h>

/* definitions */
#define GUARD_SIZE      (32*1024)       /* 32K */
#define BUFFER_SIZE     (32*1024*1024)  /* 32M */
#define MIN_BUFFER_SIZE (1024) /* smallest amount of data to try buffering */

#define BUFFER_REBUFFER_WATERMARK (BUFFER_SIZE/4) /* rebuffer is usage < this */
#define MAX_WASTED_SPACE (BUFFER_SIZE/2)

#define MAX_HANDLES 128

typedef unsigned char BYTE; /* incase its not defined yet */
#define MAX_PATH 290
struct handle {
    bool inuse;         /* is handle being used currently */
    /* file variables */
    int    fd;          /* file fd, <0 means not open */
    bool   finished;    /* true if finished reading the file */
    size_t file_offset; /* offset of the start of the data buffered */
    /* memory vairables */
    BYTE  *data;        /* start of the buffered data */
    size_t data_len;    /* length of the buffered data */
    BYTE  *last_read;   /* end of the last read block from this file */
};


/* Global vairables */
BYTE *buffer;
BYTE *BUFFER_END; /* saves recalculating this every read */

BYTE *write_ptr; /* write data here */
BYTE *last_read; /* pointer to the end of the last block of data "unbuffered" */
BYTE *valid_data;/* only data "between" valid_data and write_ptr is still valid */

struct handle handles[MAX_HANDLES];
int handles_used; /* number of handles actually in use */
char filename[MAX_PATH]; /* filename of the next file to open. */
/* internal functions */
/* returns the number of bytes currently used.
   *wasted_space is set to the number of bytes
    between last_read and valid_data */
static size_t buffer_usage(size_t *wasted_space)
{
    size_t usage = 0, temp;
    if (handles_used == 0)
        return 0;
    if (wasted_space == NULL)
        wasted_space = &temp;
    /* Case 1: |----V**L***W---| */
    if (valid_data <= write_ptr)
    {
        *wasted_space = last_read - valid_data;
        usage = (write_ptr - valid_data);
    }
    /* Case 2: |***W----V**L***| */
    else if (write_ptr < valid_data)
    {
        if (last_read < valid_data) /* |*L**W----V*****| */
            *wasted_space = (BUFFER_END - valid_data) + (last_read - buffer);
        else                        /* |***W----V**L***| */
            *wasted_space = last_read - valid_data;
        usage = BUFFER_SIZE - (valid_data - write_ptr);
    }
    return usage;
}

bool need_rebuffer(void)
{
    size_t usage, wasted = 0, free;
    usage = buffer_usage(&wasted);
    free = BUFFER_SIZE - usage;
    return ((free >= BUFFER_REBUFFER_WATERMARK) ||
            wasted > MAX_WASTED_SPACE);
}

static void limit_wasted_space(void)
{
    size_t usage, wasted = 0;
    usage = buffer_usage(&wasted);
    /* this should be done more smartly so some rewind buffer still exists */
    if (wasted > MAX_WASTED_SPACE)
        valid_data = last_read;
}
/* read_from_from:
    fd - file to read from
    buffer - buffer to read into
    *length - maximum amount of data to read.
        On return *length becomes the amount of data actually read.
    Returns false if the file is finished, or error.
            true if there is still data to rbe read.
*/
static bool read_from_file(int fd, char *buffer, size_t *length)
{
    char buf[32];
    size_t ret = 0, data_read = 0;
    while (*length > 0)
    {
        ret = read(fd, &buffer[data_read], *length);
        if (ret <= 0)
        {
            *length = data_read;
            return false; /* file finished */
        }
        else
        {
            data_read += ret;
            *length -= ret;
        }
        /* possibly yield() here */
    }
    *length = data_read;
    return true;
}

/* buffer as much of handle_id as possible.
   returns true if there is more data to buffer,
   false if handle isnt being used or file is finished */
static bool buffer_handle(int handle_id)
{
    struct handle *h = &handles[handle_id];
    size_t available_space;
    bool adding_to_existing = false;
    if (h->inuse == false ||
        h->finished == true)
        return false;
    if (h->fd < 0 && filename[0]) /* file closed, reopen */
    {
        h->fd = open(filename, O_RDONLY);
        if (h->fd < 0)
            return false;
        if (h->file_offset > 0)
            lseek(h->fd, h->file_offset, SEEK_SET);
    }
    limit_wasted_space(); /* free up some more space possibly */
    available_space = BUFFER_SIZE - buffer_usage(NULL);
    /* check if we are adding more data to a handle */
    if (h->data)
    {
        BYTE* data_end = h->data + h->data_len;
        if (data_end > BUFFER_END)
            data_end -= BUFFER_SIZE;
        if (data_end == write_ptr)
            adding_to_existing = true;
    }
    if (BUFFER_END - write_ptr < available_space)
    {
        /* for the moment dont buffer around the wrap,
           it will be called again automatically to rebuffer from the start */
        available_space = BUFFER_END - write_ptr;
    }
    h->finished = !read_from_file(h->fd, write_ptr, &available_space);
    if (adding_to_existing)
        h->data_len += available_space;
    else
    {
        h->data = write_ptr;
        h->data_len = available_space;
    }
    if (h->finished)
        close(h->fd);
    write_ptr += available_space;
    
    if (BUFFER_END <= write_ptr)
    {
        write_ptr = buffer;
    }
    return h->finished == false;
}
/* exported functions */
void buffer_init(void);
int bufopen(char *filename, size_t offset);
int bufseek(int handle_id, size_t offset);
int bufclose(int handle_id);
long bufgetdata(int handle_id, size_t size, char **destptr);

void buffer_init(void)
{
    int i;
    buffer = (BYTE*)malloc(sizeof(BYTE)*(BUFFER_SIZE+GUARD_SIZE));
    if (!buffer)
    {
        printf("couldnt allocated buffer\n");
        exit(1);
    }
    BUFFER_END = buffer + BUFFER_SIZE;
    write_ptr = buffer;
    last_read = buffer;
    valid_data = buffer;
    handles_used = 0;
    for (i=0; i<MAX_HANDLES; i++)
        handles[i].inuse = false;
}

/* Request a file be buffered
    filename: name of the file t open
    offset:   starting offset to buffer from the file
    RETURNS:  <0 if the file cannot be opened, or one file already 
                queued to be opened, otherwise the handle for the file in the buffer
*/
int bufopen(char *file, size_t offset)
{
    int i;
    if (handles_used >= MAX_HANDLES)
        return -1; 
    for (i=0; i<MAX_HANDLES; i++)
    {
        if (handles[i].inuse == false)
        {
            if (/*ata_is_active()*/1) /* open now seen as the disk is being used */
            {
                handles[i].fd = open(file, O_RDONLY);
                if (handles[i].fd < 0)
                    return handles[i].fd;
                if (offset)
                    lseek(handles[i].fd, offset, SEEK_SET);
            }
            else if (filename[0]) /* already one file queued to open */
                return -1;
            else
            {
                handles[i].fd = -1;
                strcpy(filename, file);
            }
            handles[i].file_offset = offset;
            handles[i].data = NULL;
            handles[i].inuse = true;
            handles[i].finished = false;
            handles[i].last_read = NULL;
            break;
        }
    }
    if (i<MAX_HANDLES)
    {
        handles_used++;
    //    rb->queue_post(&buffer_event_queue, B_BUFFER_FILE, i);
        return i;
    }
    return -1;
}

/* set *destptr to the start of the buffer for the handle.
   size is the min it wants to read, data may be copied into the guard buffer
   so the data can be one continuous block.
   returns number of bytes available. 
   No guarentees are made about how long the buffer will be valid for.
*/
long bufgetdata(int handle_id, size_t size, char **destptr)
{
    long ret = 0;
    size_t data_len;
    struct handle *h = &handles[handle_id];
    if (h->inuse == false)
        return 0;
#if 0
    if (h->last_read == NULL)
        h->last_read = h->data;
    if ((h->last_read + size < BUFFER_END) && 
         (h->last_read + size < h->data + h->data_len))
    {
        *destptr = h->last_read;
        /* only give as much as requested for now */
        ret = size;
        h->last_read += size;
        last_read == h->last_read;
    }
#endif
    last_read = h->data + h->data_len;
    if (last_read > BUFFER_END)
        last_read - BUFFER_SIZE;
    return ret;
}

/* close the handle */
int bufclose(int handle_id)
{
    struct handle *h = &handles[handle_id];
    /* if (h->data) -- not sure about these...
        last_read = h->data + h->data_len; */
    if (h->fd >= 0)
        close(h->fd);
    h->inuse = false;
    handles_used--;
    return 0;
}

long bufread(int handle_id, size_t size, BYTE *dest)
{
    struct handle *h = &handles[handle_id];
    size_t buffered_data;
    if (h->inuse == false ||
        h->data == NULL)
        return -1;
    if (h->last_read == NULL)
        h->last_read = h->data;
    buffered_data = h->data_len - (h->last_read - h->data);
    if (buffered_data == 0)
        return 0;
    if (buffered_data < size)
    {
        size = buffered_data;
    }
    if (h->last_read + size > BUFFER_END) /* copy in 2 bits */
    {
        size_t read = BUFFER_END - h->last_read;
        memcpy(dest, h->last_read, read);
        memcpy(dest+read, buffer, size - read);
    }
    else memcpy(dest, h->last_read, size);
    h->last_read += size;
    return size;
}
        













int main(int argc, char *argv[])
{
    int next_file = 1;
    int last_handle = -1;
    int handle_order[MAX_HANDLES];
    int reading_handle = 0;
    bool done = false;
    BYTE read_buffer[GUARD_SIZE];
    buffer_init();
    while (done == false)
    {
        if (next_file <= argc && need_rebuffer())
        {
            printf("buffer usage: %d handles_used: %d\n", buffer_usage(NULL),handles_used);
            if ( (!handles_used || 
                  handles[handle_order[last_handle]].finished == true))
            {
                int h = bufopen(argv[next_file++], 0);
                if (h >= 0)
                {
                    printf("new handle %d\n",h);
                    last_handle++;
                    handle_order[last_handle] = h;
                    buffer_handle(handle_order[last_handle]);
                }
            }
            else 
            {
                printf("buffering handle %d\n",handle_order[last_handle]);
                buffer_handle(handle_order[last_handle]);
            }
        }
        else
        {
            long total = 0, read;
            char file[MAX_PATH];
            int fd;
            snprintf(file, MAX_PATH, "/home/jonno/buffering_test/file%d.mp3", reading_handle);
            fd = open(file, O_CREAT|O_TRUNC|O_WRONLY);
            if (fd < 0)
                exit(1);
            if (next_file > argc && reading_handle >= last_handle)
                    done = true;
            while (1)
            {
                read = bufread(handle_order[reading_handle], GUARD_SIZE,read_buffer);
                total += read;
                write(fd, read_buffer, read);
                if (read <= 0)
                {
                    printf("finished reading %d, %d\n",handle_order[reading_handle], total);
                    bufclose(handle_order[reading_handle]);
                    close(fd);
                    reading_handle++;
                    break;
                }
            }
        }
    }
    
    free(buffer);
    return 0;
}
