/* soundfile i/o */
#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 "filechunk.h"
#include "speciallist.h"

#define MAXBLOCKSIZE 65536

filechunk_t* filechunk_new(char* filename){
	filechunk_t* fc  = (filechunk_t*) malloc(sizeof(filechunk_t));

	fc->start = 0;
        fc->end = 0; 
        fc->length = 0;
	fc->done = 0;
	fc->sf = 0;
	fc->group = 0;
	fc->channels = 2;
	fc->sfinfo = NULL;
	fc->filename = NULL;
	fc->L = NULL;
	fc->R = NULL;

	if (filename){
		fc->filename = calloc (sizeof(char),strlen(filename) + 1);
		strncpy (fc->filename, filename, strlen(filename));
		fc->sfinfo = (SF_INFO*) malloc(sizeof(SF_INFO));
		fc->sf = sf_open (fc->filename, SFM_READ, fc->sfinfo);
		if ((fc->sf == NULL) || (fc->sfinfo == NULL)){
			fprintf (stderr,"could not open soundfile %s\n%s\n",fc->filename,sf_strerror(fc->sf));
               		free(fc->sfinfo);
			fc->sfinfo = NULL;
               		free(fc->filename); 
			fc->filename = NULL;
                	return NULL; 
        	}       
		fc->length = fc->sfinfo->frames;
		fc->channels = fc->sfinfo->channels;
	}

	return fc;
}

void filechunk_delete(filechunk_t* fc){
	if (!fc) return;

/*	printf ("delete fc:%s\n",fc->filename);*/
	if (fc->sf) sf_close(fc->sf);
	if (fc->sfinfo) free(fc->sfinfo);
	if (fc->filename) free(fc->filename);
	if (fc->L) free(fc->L);
	if (fc->R) free(fc->R);
	free(fc);
	fc = NULL;
}
long filechunk_get_start (filechunk_t* fc){
        if (!fc) return -1;
	//printf ("%p filescunkstart",fc);
        return fc->start;
}

long filechunk_get_end (filechunk_t* fc){
        if (!fc) return -1;
        return fc->end;
}

long filechunk_get_length (filechunk_t* fc){
	if (!fc) return -1;
	return fc->length;
}

long filechunk_get_done (filechunk_t* fc){
	if (!fc) return -1;
	return fc->done;
}

int filechunk_get_channels (filechunk_t* fc){
	if (!fc) return 0;
	return fc->channels;
}

void filechunk_set_channels (filechunk_t* fc, int channels){
	if (!fc) return;
	if (fc->sfinfo) return;
	if (channels == fc->channels) return;

	if (channels < 1) channels = 1;
	if (channels > 2) channels = 2;
	if (channels == 1){
		if (fc->R) free(fc->R);
		fc->R = NULL;
		fc->channels = channels;
	}else{
		long oldsize = filechunk_get_length(fc);
		filechunk_resize(fc, 0);
		fc->channels = channels;
		filechunk_resize(fc, oldsize);
	}
}

long filechunk_get_filelength (filechunk_t* fc){
	if (!fc) return 0;
	if (!fc->sfinfo) return 0;
	return fc->sfinfo->frames;
}

long filechunk_get_samplerate (filechunk_t* fc){
        if (!fc) return 0; 
        if (!fc->sfinfo) return 0;
        return fc->sfinfo->samplerate;
}

int filechunk_get_priority (filechunk_t* fc){
        if (!fc) return 0;
        return fc->priority;
}


float filechunk_get_sample(filechunk_t* fc, int channel, long sample){
	if (channel)	return	*(fc->R + sample);
	else		return	*(fc->L + sample);
}

void filechunk_write_sample(filechunk_t* fc, int channel, long sample, float value){
	if ((channel > 1) && (fc->R))	*(fc->R + sample) = value;
	else if (fc->L) 		*(fc->L + sample) = value;
}

void filechunk_write_sampleblock(filechunk_t* fc, long index, long size, float* Lbuf, float* Rbuf){
	if (Lbuf) 			memcpy ((void*)(fc->L + index), (void*)Lbuf, size * sizeof(float));
	if ((fc->channels > 1) && Rbuf) memcpy ((void*)(fc->R + index), (void*)Rbuf, size * sizeof(float));
}

long filechunk_resize(filechunk_t* fc, long new_size){
        long l,i,max;
        max  = 44100; /* ~1 secs at once */
        float* tmp = NULL;

        if (!new_size){
                if (fc->L) free(fc->L);
                if (fc->R) free(fc->R);
                fc->L = NULL; 
		fc->R = NULL;
                fc->length = 0;
		fc->done = 0;
                return 0;
        }

        if ((getpagesize() * sysconf (_SC_AVPHYS_PAGES)) < (new_size * sizeof(float) * fc->channels)){
		if (fc->L) free (fc->L);
                if (fc->R) free (fc->R);
                fc->L = NULL; 
		fc->R = NULL;
                fc->length = 0;
		fc->done = 0;
		fprintf(stderr,"not enough memory!\n");
                return 0;
        }

        for (i = 0;i < (new_size / max);i++){
                tmp = (float*) realloc(fc->L, sizeof(float) * (i + 1) * max);
                if (tmp){
                        fc->L = tmp;
                        memset ((void*)(fc->L + (i  * max)),0,max * sizeof(float));
                }else{
			free (fc->L);
			free (fc->R);
                        fc->length = 0;
                        return 0;
                }
                if (fc->channels > 1){
                        tmp = (float*) realloc(fc->R, sizeof(float) * (i + 1) * max);
                        if (tmp){
                                fc->R = tmp;
                                memset ((void*)(fc->R + (i  * max)),0,max * sizeof(float));
                        }else{
				free (fc->L);
				free (fc->R);
                                fc->length = 0;
                                return 0;
                        }
                }
        }

        l = new_size % max;
        tmp = (float*) realloc(fc->L, sizeof(float) * (i * max + l));
        if (tmp){
                fc->L = tmp;
                memset ((void*)(fc->L + (i * max)),0,l * sizeof(float));
        }else{
		free (fc->L);
		free (fc->R);
                fc->length = 0;
                return 0;
        }
        if (fc->channels > 1){
                tmp = (float*) realloc(fc->R, sizeof(float) * (i * max + l));
                if (tmp){
                        fc->R = tmp;
                        memset ((void*)(fc->R + (i * max)),0,l * sizeof(float));
                }else{
			free (fc->R);
			free (fc->L);
                        fc->length = 0;
                        return 0;
                }
        }
        fc->length = new_size;

/*	printf ("length:%ld, L:%d, R:%d\n",fc->length,(int)fc->L,(int)fc->R);*/
	return fc->length;
}

int filechunk_get_group(filechunk_t* fc){
	return fc->group;
}

long filechunk_set (filechunk_t* fc, long start, long end, int priority, int group){
	if (!fc) return 0;

	fc->priority = priority;
	fc->group = group;
        if (start > end){
                long t = end;
                end = start;
                start = t;
        }
        if ((start < 0) || (end == start)) return 0;
        fc->start = start;
        fc->end = end;
	
	if (fc->sfinfo){
        	if (fc->sfinfo->frames < fc->start){
/*			fprintf (stderr,"error: start(%ld) > filesize(%ld)!\n", 
				fc->start, (long)fc->sfinfo->frames);
*/
			fc->start = 0;
			fc->end  = 0;
			fc->length = 0;
			fc->done = 0;
                	return 0;
        	}

        	if (fc->sfinfo->frames < fc->end){
                	fc->end = fc->sfinfo->frames;
        	}
	}

        fc->length = fc->end - fc->start;

	if (fc->sfinfo){
        	sf_seek(fc->sf,(sf_count_t)fc->start,SEEK_SET);
/*		if (fc->channels == 1) fc->R = fc->L;*/
		if (fc->channels == 1) fc->R = NULL;
	}
        if (!filechunk_resize(fc,fc->length)) return 0;

/*	if (priority == DS_PRIORITY_NONE) fc->done = fc->length;
	else */fc->done = 0;

/*	printf ("resized to:%ld, with priority %d and group %d\n",fc->length,fc->priority, fc->group);*/
	filechunk_read_chunk(fc);

        return fc->length;
}


long filechunk_read_chunk (filechunk_t* fc){
	float*		tmp;
	long 		l;
	sf_count_t	frames_read = 1; 
	long 		blocksize = MAXBLOCKSIZE; /* a maximum of 32768 frames at once */
	
	if (!fc->sf) return -1;
	if ((fc->length - fc->done) < blocksize) blocksize = fc->length - fc->done;
	tmp = (float*) malloc (sizeof(float) * blocksize *  fc->channels);

	if (tmp == NULL){ 
		fprintf(stderr,"could not allocate enough memory for the temporary buffer\n");
		return -1;
	}

	/* sf_seek ? */
/*	printf ("sf:%d, tmp:%d, size:%ld\n",(int)fc->sf,(int)tmp,blocksize);*/
	frames_read = sf_readf_float(fc->sf,tmp,(sf_count_t)blocksize);
/*	printf ("%ld read, %ld done, size:%ld\n",blocksize, fc->done,fc->length);*/

	if (fc->channels > 1){
		for (l = 0;l < frames_read;l++){
			*(fc->L + l + fc->done) = *(tmp + l * fc->channels);
			*(fc->R + l + fc->done) = *(tmp + (l * fc->channels + 1));
		}
	}else{
		for (l = 0;l < frames_read;l++){
			*(fc->L + l + fc->done) = *(tmp + l * fc->channels);
		}
	}

	fc->done += frames_read;

        free (tmp);
	return frames_read;
}


chunk_group_t* chunkgroup_new(int gid, int priority){
	chunk_group_t* group = (chunk_group_t*)malloc(sizeof(chunk_group_t));
	group->gid = gid;
	group->priority = priority;
	group->readfc = NULL;
	group->nextfc = NULL;
	group->prevfc = NULL;
	group->wrapfc = NULL;
	group->area_start = 	0;
	group->area_end = 	0;	
	group->area_bandwith = 	0;
	return group;
}
	
void chunkgroup_delete(chunk_group_t* group){
	if (!group) return;

	if (group->readfc) filechunk_delete(group->readfc);
	if (group->nextfc) filechunk_delete(group->nextfc);
	if (group->prevfc) filechunk_delete(group->prevfc);
	if (group->wrapfc) filechunk_delete(group->wrapfc);
	free(group);
}
