#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "buffer.h"
#include "interpol.h"
#include "peakview.h"

#define BANDWITH 131072 


peakdata_t *peakdata_new(){
	peakdata_t *pd = malloc(sizeof(peakdata_t));
/*	printf ("new pd\n");*/
	pd->start = 0;
	pd->end = 0;
	pd->pstart = 0;
	pd->pend = 0;
	pd->status = PD_STATUS_NEW;
	pd->Lmin = NULL;
	pd->Lmax = NULL;
	pd->Rmin = NULL;
	pd->Rmax = NULL;
	return pd;
}

static void peakdata_delete(peakdata_t *pd){
	if (!pd) return;
	if (pd->status != PD_STATUS_DEAD) return;

/*	printf ("delete : %p (%ld - %ld)\n",pd,pd->pstart,pd->pend);*/
	if (pd->Lmin) free(pd->Lmin);
	pd->Lmin = NULL;
	if (pd->Lmax) free(pd->Lmax);
        pd->Lmax = NULL;
	if (pd->Rmin) free(pd->Rmin);
	pd->Rmin = NULL;
	if (pd->Rmax) free(pd->Rmax);
        pd->Rmax = NULL;
	free(pd);
	pd = NULL;
}

void peakdata_set_status(peakdata_t *pd, int status){
	if (!pd) return;
	if (pd->status == PD_STATUS_DEAD) return;
	pd->status = status;
}


static void* peakview_workthread(void *ptr){
	peakview_t *view = (peakview_t*)ptr;
	peakdata_t *pd, *tmp;
	struct timeval tv;
	int dirty;
	long l;

	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);

	while (!view->dead){	
		dirty = 0;
		peakview_lock(view);
		pd = speciallist_get_first(view->returnlist);
		while (pd && !dirty){
			tmp = NULL;
			if (pd->status == PD_STATUS_TODO){
				long oldpixel = 0;
				long newpixel = 0;
				signed char Lmin = 127;
				signed char Lmax = -127;
				signed char Rmin = 127;
				signed char Rmax = -127;
				signed char val;
				long gstart = (long)((float)pd->start / (float)pd->samplesperpixel);
				oldpixel = gstart;
/*				printf ("start:%ld -> end:%ld\n", pd->start, pd->end);*/
				for (l = pd->start; l <= pd->end; l++){
					val = (signed char) (buffer_readsample_interpolated(
						view->buf,1,(float)l,view->group) * 127.);
					if (val < Lmin) Lmin = val;
					if (val > Lmax) Lmax = val;
					if (buffer_get_channels(view->buf) > 1){
						val = (signed char) (buffer_readsample_interpolated(
                                                view->buf,2,(float)l,view->group) * 127.);
						if (val < Rmin) Rmin = val;
                                        	if (val > Rmax) Rmax = val;
					}
					newpixel = (long)((float)l / (float)pd->samplesperpixel);
					if (newpixel > oldpixel){
						*(pd->Lmin + oldpixel - gstart) = Lmin;
						*(pd->Lmax + oldpixel - gstart) = Lmax;
						if (buffer_get_channels(view->buf) > 1){
							*(pd->Rmin + oldpixel - gstart) = Rmin;
							*(pd->Rmax + oldpixel - gstart) = Rmax;
						}
						Lmin = 127;
						Lmax = -127;	
						Rmin = 127;
						Rmax = -127;
						oldpixel = newpixel;
					}/* else if (newpixel > oldpixel) */
				}
				*(pd->Lmin + oldpixel - gstart) = Lmin;
				*(pd->Lmax + oldpixel - gstart) = Lmax;
				if (buffer_get_channels(view->buf) > 1){
					*(pd->Rmin + oldpixel - gstart) = Rmin;
					*(pd->Rmax + oldpixel - gstart) = Rmax;
				}
				pd->status = PD_STATUS_DONE;
				dirty = 1;
			}else if (pd->status == PD_STATUS_DEAD){
				tmp = pd;
			}
			pd  = speciallist_get_next(view->returnlist,pd);
			if (tmp){
				speciallist_delete(view->returnlist,tmp);
				peakdata_delete(tmp);
			}
		}
		peakview_unlock(view);

               	tv.tv_sec = 0;
		if (dirty) tv.tv_usec = 100;
		else tv.tv_usec = 10000;
               	select(0, NULL, NULL, NULL, &tv);
	}

	/* dying -> clean up */
        pd = speciallist_get_first(view->returnlist);
        while(pd){
		peakdata_t *tmp = pd;	
                pd = speciallist_get_next(view->returnlist,pd);
		peakdata_set_status(tmp, PD_STATUS_DEAD);
		peakdata_delete(tmp);
        }

	if (view->buf && view->group){
                buffer_lock(view->buf);
                buffer_delete_group(view->buf, view->group);
                buffer_unlock(view->buf);
        }

        speciallist_destroy(view->returnlist);
        free (view);
        view = NULL;
/*	printf ("peakview closed - return\n");*/
	return NULL;
}


peakview_t* peakview_new(buffer_info_t* buf){
	pthread_attr_t tattr;
	pthread_t wt;
	/*int ret;*/
	peakview_t *view;
	if (!buf) return NULL;

	view = (peakview_t*) malloc (sizeof(peakview_t));

/*	printf ("peakview new\n");*/
	view->buf = buf;
	view->dead = 0;
/*	printf ("peakview get group\n");*/
	view->group = buffer_get_group(buf, DS_PRIORITY_RELAXED);
	buffer_set_area(buf, view->group,0, buffer_get_size(buf), BANDWITH);
	view->returnlist = speciallist_new();

	/*ret = */ pthread_attr_init(&tattr);
	/*ret = */ pthread_attr_setdetachstate(&tattr,PTHREAD_CREATE_DETACHED);	
	pthread_mutex_init (&view->peakviewmutex, NULL);
	pthread_create (&wt, &tattr, peakview_workthread, view);

	return view;
}

void peakview_delete(peakview_t* view){
	if (!view) return;
	
/*	printf ("peakview->delete");
	if (view->buf)
		if (view->buf->shortname)
			printf (" (%s)",view->buf->shortname);
	printf ("\n");
*/
	peakview_lock(view);
	view->dead = 1;
	peakview_unlock(view);
}

void peakview_clear(peakview_t* view, long start, long end){
	peakdata_t* pd;
        if (!view) return;
        if (view->dead) return;

	peakview_lock(view);
        pd = speciallist_get_first(view->returnlist);
        while(pd){
		if ((end < 1) || 
			((pd->start >= start) && (pd->end <= end))){
                	peakdata_set_status(pd,PD_STATUS_DEAD);
		}
                pd = speciallist_get_next(view->returnlist,pd);
        }
        peakview_unlock(view);
}

void peakview_set_buffer(peakview_t* view,buffer_info_t* buf){
	peakdata_t* pd;
	if (!view) return;
	if (view->dead) return;

	peakview_lock(view);
       	pd = speciallist_get_first(view->returnlist);
        while(pd){
                peakdata_set_status(pd,PD_STATUS_DEAD);
                pd = speciallist_get_next(view->returnlist,pd);
        }

	if (view->buf && view->group){
		buffer_lock(view->buf);
		buffer_delete_group(view->buf, view->group);
		buffer_unlock(view->buf);
	}
	view->buf = buf;
        view->start = 0;
        view->end = 0;
        view->pixels = 0;
        view->samplesperpixel = .0;
	if (buf){
/*		printf ("peakview get group\n");*/
		view->group = buffer_get_group(buf, DS_PRIORITY_RELAXED);
		buffer_set_area(buf, view->group, 0, buffer_get_size(buf), BANDWITH);
	}
	peakview_unlock(view);
}

void peakview_lock(peakview_t* view){
	pthread_mutex_lock (&view->peakviewmutex);
}

void peakview_unlock(peakview_t* view){
	pthread_mutex_unlock (&view->peakviewmutex);
}

void peakview_set_area(peakview_t* view, long start, long end, long pixels){
/*	int ok = 0;*/
	if (!view) return;
	if (view->dead) return;

	/* delete old list blocks */
	peakview_clear(view, 0, 0);

	peakview_lock(view);
	view->start = start;
	view->end = end;
	view->pixels = pixels;
	view->samplesperpixel = (float)(end - start) / (float)pixels;
/*	printf ("set area: %ld, %ld = %ldpixels (%.2f spp)\n",start,end,pixels,view->samplesperpixel);*/
	peakview_unlock(view);
}

void peakview_calculate(peakview_t* view, long start, long end){
	long blocklength; 
	long l;

	if (!view) return;
	if (view->dead) return;
	if (!view->buf) return;
	if (!view->end) return;
	if (!view->returnlist) return;

	if (start < view->start) start = view->start;
        if (end >= view->end) end = view->end;

	if (buffer_get_type(view->buf) != BUFFER_TYPE_DISCSTREAM){
		/* was ~10 seconds at once */
		blocklength = (long)((float)(end - start) / (float)(view->end - view->start)
			* view->samplesperpixel * 200);
		if (blocklength > 441000) blocklength = 441000;
		if (blocklength < 1) blocklength = 1;
	}else{
		/* 200 pixels at once (max) */
		blocklength = (long)((float)(end - start) / (float)(view->end - view->start)
				* view->samplesperpixel * 200);
		if (blocklength > 88200) blocklength = 88200;
		if (blocklength < 1) blocklength = 1;
	}

	peakview_clear(view, start, end);
	peakview_lock(view);
	if (buffer_get_type(view->buf) == BUFFER_TYPE_DISCSTREAM){
		/* this one is just empty to make waiting more comfortable */
                peakdata_t* pd = peakdata_new();
                pd->start = start;
                pd->end = end;
                pd->samplesperpixel = view->samplesperpixel;
                pd->pstart = (long)((float)(pd->start - view->start) / view->samplesperpixel);
                pd->pend = (long)((float)(pd->end - view->start) / view->samplesperpixel);
/*              printf ("pstart:%ld, pend:%ld\n",pd->pstart,pd->pend);*/
                pd->Lmin = calloc (sizeof(signed char), (pd->pend - pd->pstart + 2));
                pd->Lmax = calloc (sizeof(signed char), (pd->pend - pd->pstart + 2));
                if (!(pd->Lmin && pd->Lmax)) printf ("memory allocation error!");
                if (buffer_get_channels(view->buf) > 1){
                        pd->Rmin = calloc (sizeof(signed char), (pd->pend - pd->pstart + 2));
                        pd->Rmax = calloc (sizeof(signed char), (pd->pend - pd->pstart + 2));
                        if (!(pd->Rmin && pd->Rmax)) printf ("memory allocation error!");
                }else {
                        pd->Rmin = NULL;
                        pd->Rmax = NULL;
                }
                pd->status = PD_STATUS_DONE;
                speciallist_append(view->returnlist,pd);
	}

	for (l = start; l < end; l += blocklength){
		peakdata_t* pd = peakdata_new();
		pd->start = l;
		pd->end = l + blocklength;
		if (pd->end > end) pd->end = end;
		if (pd->start < pd->end){
			pd->pstart = (long)((float)(pd->start - view->start) / view->samplesperpixel);
			pd->pend = (long)((float)(pd->end - view->start) / view->samplesperpixel);
			pd->samplesperpixel = view->samplesperpixel;
/*			printf ("	%ld - %ld 	= %ld - %ld\n",pd->start,pd->end,pd->pstart,pd->pend);*/
                        pd->Lmin = malloc (sizeof(signed char) * (pd->pend - pd->pstart + 2));
                        pd->Lmax = malloc (sizeof(signed char) * (pd->pend - pd->pstart + 2));
                        if (!(pd->Lmin && pd->Lmax)) printf ("memory allocation error!");
                        if (buffer_get_channels(view->buf) > 1){
                                pd->Rmin = malloc (sizeof(signed char) * (pd->pend - pd->pstart + 2));
                                pd->Rmax = malloc (sizeof(signed char) * (pd->pend - pd->pstart + 2));
                                if (!(pd->Rmin && pd->Rmax)) printf ("memory allocation error!");
                        }else {
                                pd->Rmin = NULL;
                                pd->Rmax = NULL;
                        }
			pd->status = PD_STATUS_TODO;
			speciallist_append(view->returnlist,pd);	
		} else peakdata_delete(pd);
	}
	peakview_unlock(view);
}
        
peakdata_t *peakview_get_next(peakview_t* view){
	if (!view) return NULL;
	if (view->dead) return NULL;
	peakdata_t* pd = speciallist_get_first(view->returnlist);

        while(pd){
		if (pd->status == PD_STATUS_DONE){	
			return pd;
		}
		pd = speciallist_get_next(view->returnlist,pd);
        }
	return NULL;
}	
