/* creation and destruction of buffers */
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/sysinfo.h>
#include <unistd.h>
#include <math.h>
#include "interpol.h"
#include "speciallist.h"
#include "buffer.h"

#define MAXPATHLEN 256
const long ds_buffersize = 44100;

void buffer_lock(buffer_info_t* buf){
	pthread_mutex_lock (&buf->buffermutex);
}

void buffer_unlock(buffer_info_t* buf){
	pthread_mutex_unlock (&buf->buffermutex);
}


static int strnflst(const char *s1,const  char *s2,int n){
        /* sucht s2 in s1 bis maximal s1[n]*/
        int i;          
        int len2 = strlen(s2);
        int found = -1; 
                        
        if (n >  strlen(s1)){n = strlen(s1);}
        if (n < 0){ n = 0;}             
                
        for (i=0;i<n;i++){ 
                if(!strncasecmp((char*)(s1+i),s2,len2)){
                        found = i;
                }       
        }               
/*      printf("found=%d\n",found);*/
        return found;
}

buffer_info_t* buffer_new(int id, long rate){
	buffer_info_t* buf;

	buf = (buffer_info_t*) malloc (sizeof(buffer_info_t));

	buf->id = id;
	buf->type = BUFFER_TYPE_MEMORY;
	buf->status = BUFFER_STATUS_NEW;
	buf->parent_samplerate = rate;
	buf->samplerate_factor = 1.;
	buf->group_max = -1;
	buf->pending = 0;
	buf->error = 0;
	buf->percent_filled = 0.;
	buf->normalized = 0;
	buf->snap_offset = 0;
	buf->volume_factor = 1.;
	buf->filename= NULL;
	buf->shortname = NULL;
	buf->chunk_list = NULL;
//	printf ("buffer get initial group\n");
	buffer_get_group(buf, DS_PRIORITY_NORMAL);
	pthread_mutex_init (&buf->buffermutex, NULL);

	return buf;
}

void buffer_delete_group(buffer_info_t* buf, int gid){
/*	int i;*/
	if (!buf) return;
        if (!buf->chunk_list) return;
	if (buf->group_max < gid) return;
        if (!buf->chunk_list[gid]) return;

	/* delete only when DISCSTREAM */
	if (gid > 1){
        	chunkgroup_delete(buf->chunk_list[gid]);
        	buf->chunk_list[gid] = NULL;	
	}
/*
	printf ("deleting group %d from %s      -> ",gid, buf->shortname);
	printf ("state now: ");
	for (i = 0; i <=buf->group_max; i++){
		if (buf->chunk_list[i]) printf ("%d, ",i);
	}
	printf ("\n");
*/
}

int buffer_delete(buffer_info_t* buf){
	int i;

	/* LATER: care for filechunks !*/
	for (i = buf->group_max; i > 0; i--)
		buffer_delete_group(buf, i);

	/* delete the initial group: */
	if (buf->chunk_list[0]) {
		chunkgroup_delete(buf->chunk_list[0]);
		buf->chunk_list[0] = NULL;
	}

	free (buf->chunk_list);
	buf->chunk_list = NULL;

	buf->pending = BUFFER_STATUS_DEAD;
	if (buf->filename) free (buf->filename);
	buf->filename = NULL;
	if (buf->shortname) free (buf->shortname);
	buf->shortname = NULL;
		
	return 0;
}

int buffer_get_type(buffer_info_t* buf){
	if (!buf) return 0;
	return buf->type;
}


/* filechunk group funktions: */

void buffer_set_area(buffer_info_t* buf, int group, long start, long end, long bandwith){
	if (!buf->chunk_list) return;
        if (!buf->chunk_list[group]) return;

	if (start < 0) start = 0;
	if (start > buffer_get_size(buf)) start = buffer_get_size(buf);
	if (end < 0) end = 0;
        if (end > buffer_get_size(buf)) end = buffer_get_size(buf);
	
	start = (long)((float)start * buf->samplerate_factor);
	end   = (long)((float)end   * buf->samplerate_factor);

	if (start > end){
		long tmp = start;
		start = end;
		end = tmp;

	}
/*
	printf ("area set: %ld - %ld of %ld, bandwidth:%ld\n",
			start,end, buffer_get_size(buf),bandwith);
*/
	buf->chunk_list[group]->area_start = start;
	buf->chunk_list[group]->area_end = end;
	buf->chunk_list[group]->area_bandwith = bandwith;	
}

static int buffer_append_group(buffer_info_t* buf, int priority){
	chunk_group_t* group = chunkgroup_new(++buf->group_max, priority);

	buf->chunk_list = realloc(buf->chunk_list, sizeof(chunk_group_t) * (buf->group_max));
	buf->chunk_list[buf->group_max] = group;
	return buf->group_max;
}

int buffer_get_group(buffer_info_t* buf, int priority){
	if (buf->type == BUFFER_TYPE_DISCSTREAM){
		int i = buffer_append_group(buf, priority);
		buf->chunk_list[i]->priority = priority;
		return i;
	}else{ 
		if (buf->chunk_list == NULL){
			return buffer_append_group(buf, DS_PRIORITY_NORMAL);
		}
		return 0;
	}
}

int buffer_get_channels(buffer_info_t* buf){
	if (!buf->chunk_list) return 0;
	if (!buf->chunk_list[0]) return 0;
	if (!buf->chunk_list[0]->readfc) return 0;

	return filechunk_get_channels(buf->chunk_list[0]->readfc);
}

/* feedback functions: */

float buffer_get_sampleratefactor(buffer_info_t* buf, long srate){
	if (!buf) return 0;
	if (!buf->chunk_list) return 0;
	if (!buf->chunk_list[0]->readfc) return 0;

	if (buf->type != BUFFER_TYPE_MEMORY)	
		return ((float)filechunk_get_samplerate(buf->chunk_list[0]->readfc) / (float)srate ) ;
	else return 1.;
}


void buffer_set_channels(buffer_info_t* buf, int channels){
	if (!buf) return;
	if (!buf->chunk_list[0]) return;
	if (buf->type != BUFFER_TYPE_MEMORY) return;
	if (filechunk_get_channels(buf->chunk_list[0]->readfc) == channels) return;

/*	printf ("%s set channels to %d\n",buf->shortname, channels);	*/
	if ((channels > 0) && (channels < 3)){
		int gid;
		for (gid = 0; gid <= buf->group_max; gid++){
			if (buf->chunk_list[gid]){ 
				filechunk_set_channels(buf->chunk_list[gid]->readfc, channels);
				filechunk_set_channels(buf->chunk_list[gid]->prevfc, channels);
				filechunk_set_channels(buf->chunk_list[gid]->nextfc, channels);
				filechunk_set_channels(buf->chunk_list[gid]->wrapfc, channels);
			}
		}
	}
}

/* not in use:
void buffer_set_snap_offset(buffer_info_t* buf, long offset){
	if (buf) {
		if (offset < 0) offset = 0;
        	if (offset > buffer_get_size(buf)) offset = buffer_get_size(buf);
		buf->snap_offset  = offset;
	}
}

long buffer_get_snap_offset(buffer_info_t* buf){
	if (buf) return buf->snap_offset;
	return 0;
}
*/

int buffer_get_status(buffer_info_t* buf){
	if (!buf) return 0;
	return buf->status;
}

long buffer_get_size(buffer_info_t* buf){
	if (!buf) return 0;
	if (!buf->chunk_list) return 0;
	if (!buf->chunk_list[0]) return 0;
        if (!buf->chunk_list[0]->readfc) return 0;

	if (buf->type == BUFFER_TYPE_DISCSTREAM)
		return (long)((float)filechunk_get_filelength(buf->chunk_list[0]->readfc) /
			buf->samplerate_factor);
	else	return (long)((float)filechunk_get_length(buf->chunk_list[0]->readfc) /
                        buf->samplerate_factor);
}

/*
long buffer_get_corrected_size(buffer_info_t* buf, long srate){
	float f = buffer_get_sampleratefactor(buf,srate);
        if (!buf->chunk_list) return 0;
        if (!buf->chunk_list[0]->readfc) return 0;

        if (buf->type == BUFFER_TYPE_DISCSTREAM)
                return (long)((float)filechunk_get_filelength(buf->chunk_list[0]->readfc) * f);
        else    return (long)((float)filechunk_get_length(buf->chunk_list[0]->readfc) * f);
}
*/

int is_inside (filechunk_t* fc, long index) {
	if (filechunk_get_end(fc)   <= index) return 0;
	if (filechunk_get_start(fc) > index) return 0;
	return 1;
}

int is_inside_done (filechunk_t* fc, long index) {
        if (filechunk_get_done(fc)  < index) return 0;
        if (filechunk_get_start(fc) > index) return 0;
        return 1; 
}

static void shift_right(chunk_group_t * cg){
	filechunk_t* tempfc = cg->readfc;
        cg->readfc = cg->nextfc;
        cg->nextfc = cg->prevfc;
        cg->prevfc = tempfc;
}

static void shift_left(chunk_group_t * cg){
        filechunk_t* tempfc = cg->readfc;
        cg->readfc = cg->prevfc;
        cg->prevfc = cg->nextfc;
        cg->nextfc = tempfc;
}

static float valuehelper(filechunk_t* fc, int channel, double index){
	if (filechunk_get_priority(fc) == DS_PRIORITY_RELAXED){
		struct timeval tv;
		long l = 0;
		while (((filechunk_get_done(fc) + filechunk_get_start(fc)) 
			< index) && (l++ < 5 /* was 1000 */)){
                                tv.tv_sec = 0;
                                tv.tv_usec = 10000; /* was 100*/
                                select(0, NULL, NULL, NULL, &tv);
                }
	}
	if (filechunk_get_length (fc) < 1 ) return 0.0;
	if (channel == 1){
		/*float inter4pol_float (float* buf, double index, long size);*/
        	return inter4pol_float(fc->L, index - filechunk_get_start(fc), filechunk_get_done(fc));
        } else {    
		return inter4pol_float(fc->R, index - filechunk_get_start(fc), filechunk_get_done(fc));
	}
}
 
static long wrapindex (long index, long start, long end){
	if ((index >= start) && (index <= end)) return index;
/*	printf ("start:%ld, end:%ld, index:%ld\n",start,end,index);*/
	if (index > end) return start + (index - start) % (end - start);
	if (index < start) return end - (end - index) % (end - start);
	return index; 
}

float buffer_readsample_interpolated(buffer_info_t* buf, int channel, double index, int group){
	if (!buf) return 0.;
	if (!buf->chunk_list) return 0;
	if (!buf->chunk_list[group]) return 0;

	index *=  buf->samplerate_factor;
	double dindex = index;

	if (buf->type == BUFFER_TYPE_DISCSTREAM){ 
		if (buf->group_max < group) return 0;

		if (!buf->chunk_list[group]->readfc){
                	buf->chunk_list[group]->readfc = filechunk_new(buf->filename);
                	if (buf->type == BUFFER_TYPE_DISCSTREAM){
/*				printf ("make new filechunks for %s, group %d\n", buf->shortname, group);*/
                        	buf->chunk_list[group]->nextfc = filechunk_new(buf->filename);
                        	buf->chunk_list[group]->prevfc = filechunk_new(buf->filename);
                        	buf->chunk_list[group]->wrapfc = filechunk_new(buf->filename);
                	}
        	}
		if ( is_inside(buf->chunk_list[group]->readfc,index)){
			return valuehelper(buf->chunk_list[group]->readfc,channel,index) * buf->volume_factor;
		} else {
			chunk_group_t* cg = buf->chunk_list[group];
			long goodsize = cg->area_bandwith * 2;
			if (goodsize < ds_buffersize) goodsize = ds_buffersize;
		        if (!is_inside(cg->readfc,index) && is_inside(cg->nextfc,index) ) {
				/* we are inside nextfc */
				long wi = (long)index - cg->area_bandwith;
				wi = wrapindex(wi, cg->area_start, 
					cg->area_end ? cg->area_end : filechunk_get_filelength (cg->readfc));
				if (is_inside(cg->readfc,wi)){
					/* still in acceptable range */
					return valuehelper(cg->nextfc,channel,index) * buf->volume_factor;
				}else{
					/* not still in acceptable range */
					long s,e;
                               		buffer_lock(buf);
					shift_right(cg);
                               		s = filechunk_get_end(cg->readfc) - 5;
                               		e = s + goodsize;
                               		if (e > filechunk_get_filelength(cg->readfc))
                                       		e = filechunk_get_filelength(cg->readfc);
					if (cg->area_end && (s >= cg->area_end)){
/*						printf ("wrap\n");*/
						s = cg->area_start;
                                                e = s + goodsize;
						if (e > filechunk_get_filelength(cg->readfc))
                                                        e = filechunk_get_filelength(cg->readfc);
					}else if (cg->area_end && (e >= cg->area_end)){
						long ws = cg->area_start;
						long we = ws + goodsize;
						if (we > filechunk_get_filelength(cg->readfc))
							we = filechunk_get_filelength(cg->readfc);
						filechunk_set(cg->wrapfc,ws, we, cg->priority, group);
					}
                               		filechunk_set(cg->nextfc,s, e, cg->priority, group);
                               		buffer_unlock(buf);
					return valuehelper(cg->readfc,channel,index) * buf->volume_factor;
				}
				
                	} else if (!is_inside(cg->readfc,index) && is_inside(cg->prevfc,index)) {
				long wi = (long)index + cg->area_bandwith;
                                wi = wrapindex(wi, cg->area_start, 
                                        cg->area_end ? cg->area_end : filechunk_get_filelength (cg->readfc));
                                if (is_inside(cg->readfc, wi)){
/*					printf ("still ok\n");*/
					return valuehelper(cg->prevfc,channel,index) * buf->volume_factor;
				}else{
                                        /* not still in acceptable range */
					/*printf ("previous\n");*/
                    	 		long s,e;
                               		buffer_lock(buf);
					shift_left(cg);
                               		e = filechunk_get_start(cg->readfc) + 5;
                               		s = e - goodsize;
                               		if (s < 0) s = 0; 
					if (cg->area_start >= s){
						e = cg->area_end;
                                                s = e - goodsize;
                                                if (s < 0) s = 0;
					}else if (cg->area_end && (s <= cg->area_end)){
                                                long we = cg->area_end;
                                                long ws = we - goodsize;
                                                if (ws < 1) ws = 0;
                                                filechunk_set(cg->wrapfc, ws, we, cg->priority, group);
                                        }
                               		filechunk_set(cg->prevfc,s, e, cg->priority, group);
                               		buffer_unlock(buf);
					return valuehelper(cg->readfc,channel,index) * buf->volume_factor;
				}
			} else if (!is_inside(cg->readfc,index) && is_inside_done(cg->wrapfc,index)) {
				return valuehelper(cg->wrapfc,channel,index) * buf->volume_factor;
				/* should be more sophisticated here */
                	} else {
				long s,e;
				s = (long)index - (goodsize / 2);
				e = (long)index + (goodsize / 2);
				if (s < 0) {s = 0;e = goodsize;}
				if (e > filechunk_get_filelength(cg->readfc)) 
					e = filechunk_get_filelength(cg->readfc);
				buffer_lock(buf);
				if (cg->priority == DS_PRIORITY_RELAXED)
					filechunk_set(cg->readfc,s, e, cg->priority, group);
				else {
					filechunk_set(cg->readfc,s, e, DS_PRIORITY_URGENT, group);
					valuehelper(cg->readfc,channel,index);
				}
				s += goodsize;
				if (s < cg->area_end)
					e = s + goodsize;
				else {
					s = cg->area_start;
					e = s + goodsize;
				}
				filechunk_set(cg->nextfc, s, e, cg->priority, group);

				s -= goodsize * 2;
				e = s + goodsize;
				if (s < cg->area_start){
					e = cg->area_end;
					s -= goodsize;	
				}
				filechunk_set(cg->prevfc, s, e, cg->priority, group);
				buffer_unlock(buf);
/*
				if (group == 4){
                                        printf ("fcs rearranged index:%.0lf, goodsize:%ld, group:%d, read=%ld-%ld, next=%ld-%ld, prev=%ld-%ld, wrap=%ld-%ld\n",
                                                index,goodsize, group,
                                                filechunk_get_start(cg->readfc),filechunk_get_end(cg->readfc),
                                                filechunk_get_start(cg->nextfc),filechunk_get_end(cg->nextfc),
                                                filechunk_get_start(cg->prevfc),filechunk_get_end(cg->prevfc),
                                                filechunk_get_start(cg->wrapfc),filechunk_get_end(cg->wrapfc));
                                        fflush(stdout);
                                }
*/


/*				if (cg->priority == DS_PRIORITY_RELAXED)*/
					return valuehelper(cg->readfc,channel,index) * buf->volume_factor;

				/*printf ("sample = zero because cpu = too slow\n");*/
				return 0.;
			}
		}
	} else { /* not a diskstream */
		if (dindex >= (double)buffer_get_size(buf)) {
			/*printf ("ouside read bounds\n");*/
			return 0.0;
		}

		/* LATER: wrap interpolation around loopborders */
		/* this is quite expensive: */
		if (filechunk_get_length (buf->chunk_list[group]->readfc) < 1){
			printf ("chunk too small\n");
 			return 0.0;
		}
		if (channel == 1){ 
			return inter4pol_float(buf->chunk_list[group]->readfc->L,
					dindex,(double)buffer_get_size(buf) * (double)buf->samplerate_factor) 
				* buf->volume_factor;
		} else { 	
			return inter4pol_float(buf->chunk_list[group]->readfc->R,
					dindex,(double)buffer_get_size(buf) * (double)buf->samplerate_factor) 
				* buf->volume_factor;
		}
	}
	return 0.;
}

void buffer_writesample(buffer_info_t* buf, int channel, long index, int group, float sample){
	if (!buf) return;
	if (!buf->chunk_list) return;
        if (!(buf->group_max >= group)) return;
	if (!buf->chunk_list[0]->readfc) return;
	if (buf->type == BUFFER_TYPE_DISCSTREAM) return;  /* never write to a discstream */

        index *= buf->samplerate_factor;
	
	filechunk_write_sample(buf->chunk_list[group]->readfc, channel, index, sample);
}

void buffer_write_sampleblock(buffer_info_t* buf, long index, long size, int group, float* Lbuf, float* Rbuf){
	if (!buf) return;
	if (!buf->chunk_list) return;
	if (!(buf->group_max >= group)) return;
	if (!buf->chunk_list[0]->readfc) return;
	if (buf->type == BUFFER_TYPE_DISCSTREAM) return;  /* never write to a discstream */

	index *= buf->samplerate_factor;

	filechunk_write_sampleblock(buf->chunk_list[group]->readfc, index, size, Lbuf, Rbuf);
}

float buffer_analyze(buffer_info_t* buf, int gid, long start,long end){
//	int group = gid;
	struct timeval tv;
	float val = .0;
	float max = .0;
	long  l;
	int group;

	if (!buf) return 0.;
        if (!buf->chunk_list) return 0;
	if (buf->type != BUFFER_TYPE_DISCSTREAM) group = 0;
	group = buffer_get_group (buf, DS_PRIORITY_RELAXED);
        buffer_set_area(buf, group, start, end, 32767);
/*
        start = (long)((float)start * buf->samplerate_factor);
        end   = (long)((float)end * buf->samplerate_factor);
*/

	if (end < 1)            		{ end = buffer_get_size(buf);}	
	if (start < 0)          		{ start = 0; }
	if (end < start)     			{ end = buffer_get_size(buf);}
	if (start > buffer_get_size(buf))  	{ start = 0; }
	if (end > buffer_get_size(buf))    	{ end = buffer_get_size(buf); }

/*	printf ("analyze from %ld to %ld\n",start,end);*/
	for (l=start; l <= end;l++){
		val = fabs(buffer_readsample_interpolated(buf,1,l,group) / buf->volume_factor);
		if (val > max) max = val;
		if (buffer_get_channels(buf) > 1){
			val = fabs(buffer_readsample_interpolated(buf,2,(float)l,group) / buf->volume_factor);
			if (val > max) max = val;
		}
		if (!(l % 1000000)){
/*			printf ("normalize:%ld\n",l);*/
			tv.tv_sec = 0;
			tv.tv_usec = 100;
			select(0, NULL, NULL, NULL, &tv);
		}
	}
	buffer_delete_group(buf, group);
//	printf ("max:%f\n",max);
	return max;
}

void buffer_normalize(buffer_info_t* buf, float factor, long start, long end){
	 /* normalze all if end < start or end == 0 */
	long l;
	struct timeval tv;
	float val;
	int group; 

	if (!buf) return;
        if (!buf->chunk_list) return;
	if (buf->type == BUFFER_TYPE_DISCSTREAM) return;

	group = buffer_get_group (buf, DS_PRIORITY_RELAXED);
        buffer_set_area(buf, group, start, end, 32767);

/*
        start = (long)((float)start * buf->samplerate_factor);
        end   = (long)((float)end * buf->samplerate_factor);
*/


	if (start < 0)				{ start = 0; }
	if (!(end > start))			{ end = buffer_get_size(buf);}
	if (start > buffer_get_size(buf))	{ start = 0; }
	if (end > buffer_get_size(buf))		{ end = buffer_get_size(buf); }

	buffer_lock(buf);

	for (l = start; l <= end; l++){
		val = buffer_readsample_interpolated(buf,1,(float)l,group) * factor;
		buffer_writesample(buf,1,(float)l,0,val);
		if (buffer_get_channels(buf) > 1){
			val = buffer_readsample_interpolated(buf,2,(float)l,group) * factor;
                	buffer_writesample(buf,2,(float)l,group,val);
		}
		if (!(l % 100000)){
                        tv.tv_sec = 0;
                        tv.tv_usec = 1000;
			buffer_unlock(buf);
                        select(0, NULL, NULL, NULL, &tv);
			buffer_lock(buf);
                }
	}
	buf->normalized = 1;
/*	printf ("%s normalized:%d\n",buf->shortname, buf->normalized);*/
	buffer_unlock(buf);
	buffer_delete_group (buf, group);
}

void buffer_set_volume_factor(buffer_info_t* buf, float factor){
	if (!buf) return;
	buffer_lock(buf);
	buf->volume_factor = factor;
	buf->normalized = 1;
        buffer_unlock(buf);
}

float buffer_get_volume_factor(buffer_info_t* buf){
	if (buf) return buf->volume_factor;
	return 0.;
}

void buffer_set_filename(buffer_info_t* buf, char* name){
        buf->filename = (char*) realloc(buf->filename,sizeof(char) * strlen(name) + 1);
	strcpy(buf->filename,name);
	return;
}

void buffer_set_shortname(buffer_info_t* buf, char* name){
        buf->shortname = (char*) realloc(buf->shortname,sizeof(char) * strlen(name) + 1);
        strcpy(buf->shortname,name);
        return;
}  

char* buffer_get_shortname(buffer_info_t* buf){
	if (!buf) return "no buffer";
	if (!buf->shortname) return "no name";
	return buf->shortname;
}

static filechunk_t* buffer_order_filechunk(buffer_info_t* buf, long start, long end, int priority, int group){
	filechunk_t* fc;

	if (buf->filename) fc = filechunk_new(buf->filename);
	else fc = filechunk_new(NULL);

	if (!fc){
		fprintf(stderr, "could not open filechunk for reading!\n");
		return NULL;
	}
	if (start > filechunk_get_filelength(fc)){
		fprintf (stderr,"wrong start - not creating filechunk\n");
		filechunk_delete(fc);
		return NULL;
	}
	if (end < 0) end = filechunk_get_length(fc);
	if (!filechunk_set(fc, start, end, priority, group)){
		filechunk_delete(fc);
		return NULL;		
	}

	return fc;
}

void buffer_resize(buffer_info_t* buf, long new_size){
	filechunk_t* fc;

        if (buf->type == BUFFER_TYPE_DISCSTREAM){ 
		return; /* not possible for discstreams */
	}else{
        	if (!buf->chunk_list[0]->readfc){
			fc = buffer_order_filechunk(buf, 0, new_size, DS_PRIORITY_NONE,0);
			buf->chunk_list[0]->readfc = fc;
		}else {
			filechunk_set(buf->chunk_list[0]->readfc,0,new_size,DS_PRIORITY_NONE,0);
		}
/*		buf->pending = BUFFER_STATUS_READY;*/
	}
}


void buffer_open_discstream (buffer_info_t* buf, char* fname, long srate){
	int lp;
/*	long s,l; */
	filechunk_t* fc;
	char* sn;
	float min,sec;

	buf->type = BUFFER_TYPE_DISCSTREAM;
	buf->filename = (char*) malloc (sizeof(char) * (strlen(fname) + 1));
        strcpy(buf->filename,fname);
	if ((fc = buffer_order_filechunk(buf,0,1,DS_PRIORITY_URGENT,0))){
		lp = strnflst (fname,"/",-1);
		sn = calloc(sizeof(char), (strlen(fname) - lp + 16));
        	sec = (float)filechunk_get_filelength(fc) / (float)filechunk_get_samplerate (fc);
        	min = (int)(sec / 60.);
        	sec -= min * 60;
        	snprintf (sn,strlen(fname) - lp + 16,"DS: %s (%0.0f:%02.0f)",(char*)(buf->filename + lp + 1),min,sec);
		buffer_set_shortname(buf,sn);
		buf->chunk_list[0]->readfc = fc;
		buf->chunk_list[0]->nextfc = filechunk_new(buf->filename);
		buf->chunk_list[0]->prevfc = filechunk_new(buf->filename);
		buf->chunk_list[0]->wrapfc = filechunk_new(buf->filename);
		buf->pending = BUFFER_STATUS_READY;
		buf->samplerate_factor = (float)filechunk_get_samplerate(fc) / (float)buf->parent_samplerate;
	}else buf->status = BUFFER_STATUS_DEAD;
/*	printf ("samplerate factor :%.4f\n",buf->samplerate_factor);*/
	return;
}

void buffer_loadfile(buffer_info_t* buf, char* fname, long srate){
	/* create a reding thread, wait for join, return success*/
	int lp;
	filechunk_t* fc;
	char* sn;
	float min,sec;

	buf->type = BUFFER_TYPE_FILEBUFFER;
	buf->filename = (char*) malloc (sizeof(char) * (strlen(fname) + 1));
	strcpy(buf->filename,fname);
	if ((fc = buffer_order_filechunk(buf,0,-1, DS_PRIORITY_NORMAL,0))){
		lp = strnflst   (fname,"/",-1);
        	sn = calloc(sizeof(char), (strlen(fname) - lp + 16));
        	sec = (float)filechunk_get_filelength(fc) / (float)filechunk_get_samplerate (fc);
        	min = (int)(sec / 60.);
        	sec -= min * 60;
        	snprintf (sn,strlen(fname) - lp + 16,"FB: %s (%0.0f:%02.0f)",(char*)(buf->filename + lp + 1),min,sec);
        	buffer_set_shortname(buf,sn);
		buf->chunk_list[0]->readfc = fc;
		buf->pending = BUFFER_STATUS_TOLOAD;
		buf->samplerate_factor = (float)filechunk_get_samplerate(fc) / (float)buf->parent_samplerate;
	}else buf->status = BUFFER_STATUS_DEAD;
	return;
}

static int check_fc_disc(filechunk_t *fc, int priority){
	if (!fc) return 0;
	if (fc->priority != priority) return 0;
	if ((fc->length == fc->done)) return 0;
	if (!fc->L) return 0;

/*	if (!fc->L) printf ("!fc->L !!! :l:%ld, done:%ld\n ",fc->length,fc->done); */
	return filechunk_read_chunk(fc);
}

static void* buffer_discthread(void *ptr){
	speciallist_t *buffer_list = (speciallist_t*)ptr;
	buffer_info_t *buf;
	int dirty,i,j,k,d;
	
        struct timeval tv;

/*	printf ("buffer disc thread launched\n");*/
	while(1){
		dirty = 0;
		/* check for pending disc operations sorted by priority: */
		/* actions for lower priority will only happen if higher ones didn't */
		for (i = 0; (i < DS_PRIORITY_NONE) && !dirty; i++){
			buf = speciallist_get_first(buffer_list);
                	while (buf){
				k = 0;
                                buffer_lock(buf);
				for (j = 0; j <= buf->group_max; j++){
					if (buf->chunk_list[j]){
/*					printf ("j:%d\n",j);*/
						d = check_fc_disc(buf->chunk_list[j]->readfc, i);
						if (d > 0) k += d;
						d = check_fc_disc(buf->chunk_list[j]->nextfc, i);
						if (d < 1) k = 0;
						d = check_fc_disc(buf->chunk_list[j]->prevfc, i);
						if (d < 1) k = 0;
						d = check_fc_disc(buf->chunk_list[j]->wrapfc, i);
                                        	if (d < 1) k = 0;
					}
				}
                                if (k){ 
					dirty++;
					if (buf->type == BUFFER_TYPE_FILEBUFFER){
						buf->pending = BUFFER_STATUS_LOADING;
						buf->percent_filled =
							(double)filechunk_get_done(buf->chunk_list[0]->readfc) /
							(double)filechunk_get_length(buf->chunk_list[0]->readfc);
					}
                                }else{
					if (buf->chunk_list[0]){
						buf->percent_filled = (double)filechunk_get_done(buf->chunk_list[0]->readfc) /
                                                        	(double)filechunk_get_length(buf->chunk_list[0]->readfc);
						if ((buf->percent_filled == 1.) && (buf->status != BUFFER_STATUS_READY)) 
							buf->pending = BUFFER_STATUS_READY;
					}
				}
                                buffer_unlock(buf);
				buf = speciallist_get_next(buffer_list,buf);
                	}
		}

		if (!dirty){
	        	tv.tv_sec = 0;
        		tv.tv_usec = 10000; /* max 100 times a sec */
			select(0, NULL, NULL, NULL, &tv);
		}
	}
	/* won't happen: */
	return NULL;
}

void buffer_discthread_launch(speciallist_t *buffer_list){
	pthread_t lt;
	pthread_create (&lt, NULL, buffer_discthread, buffer_list);
}


int  buffer_save (buffer_info_t* buf, char* fname, long srate, long start, long end){
	SF_INFO*        sfinfo = (SF_INFO*) malloc(sizeof(SF_INFO));
        SNDFILE*        sf;     
	sf_count_t	frames;
	sf_count_t      frames_done = 0;
	sf_count_t	frames_at_once = 32767;
	struct timeval tv;
	float		*tbuf;
	long i;
	int channels = buffer_get_channels(buf);
	int group = buffer_get_group (buf, DS_PRIORITY_RELAXED);
	buffer_set_area(buf, group, start, end, frames_at_once);

	/* LATER: move this to disc thread */

        tv.tv_sec = 0;
        tv.tv_usec = 100000;

	sfinfo->samplerate = srate;
	sfinfo->channels = buffer_get_channels(buf);
	/* LATER allow other formats */
	sfinfo->format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;

	sf = sf_open (fname, SFM_WRITE, sfinfo);

	if (!sf) return -1;
	
	tbuf = calloc (sizeof(float), frames_at_once * channels);	
	if (!tbuf) return -1;

	while (frames_done < (end - start)){
		frames = end - (start + frames_done);
		if (frames > frames_at_once) frames = frames_at_once;

		if (channels > 1){
			for (i = 0; i < frames; i++){
				*(tbuf + i * channels) = 
					buffer_readsample_interpolated(buf, 1,
						(float)(start + frames_done + i),group);
				*(tbuf + i * channels + 1) = 
					buffer_readsample_interpolated(buf, 2,
						(float)(start + frames_done + i),group);
			}
		}else{
			for (i = 0; i < frames; i++){
                                *(tbuf + i * channels) =        
                                        buffer_readsample_interpolated(buf, 1,
                                                (float)(start + frames_done + i),group);
			}
		}
		frames = sf_writef_float(sf, tbuf, frames);
		if (frames < 1){
			free(tbuf);
			if (group) buffer_delete_group(buf, group);
			return -1;
		}/* else printf ("%ld frames written\n",(long)frames);*/
		frames_done += frames;
                select(0, NULL, NULL, NULL, &tv);
	}

	sf_close(sf);
	free(tbuf);	
	if (group) buffer_delete_group(buf, group);
	return 1;
}
