#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

#include <dlfcn.h>
#include <pthread.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <linux/soundcard.h>

#include <ladspa.h>

#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>
#include <vorbis/vorbisenc.h>

#include <shout/shout.h>

#include <ao/ao.h>


#define NUM_THREADS 5
#define BUFSIZE 64

#define MESSUREINTERVAL 12
#define TRESHOLD 8000

#define STOP 0
#define PLAY 1
#define MUTE 2

#define HARD 0
#define FADE 1

#define SAMPLERATE 48000

ao_device *device;

pthread_mutex_t  mut;
int count = 0;

typedef struct {
    char *proto;
    char *host;
    int port;
    char path[255];
} URI;

typedef struct {
   short left;
   short right;
} Sample;

typedef struct {
   float left;
   float right;
} Fsample;

Fsample out[BUFSIZE];

LADSPA_Descriptor	*desc;
LADSPA_Handle		*handle;
LADSPA_Data			limit, lahead, atten, gain;
LADSPA_Data			in1[BUFSIZE], in2[BUFSIZE];
LADSPA_Data			out1[BUFSIZE], out2[BUFSIZE];

ogg_stream_state	os;
vorbis_dsp_state	vd;
vorbis_block		vb;
ogg_packet			op;
ogg_page			og;
vorbis_info			vi;
vorbis_comment		vc;

shout_t *shout;

typedef struct {
	URI uri;
	int state;
	int go;
	int offset;		// offset in seconds
	int start;		// start time (0 = immidiatly)
	int length;		// play length (0 = till end)
	int method;		// overlay method (hard, fade)
	float volfac;	// volumefactor
} Player;

Player players[NUM_THREADS];


/*
 *
 */
amp_prep() {
	void *so_handle;
	LADSPA_Descriptor_Function func;

	if (!(so_handle = dlopen("/usr/lib/ladspa/amp.so",RTLD_LAZY))) {
		fprintf(stderr, "Error opening shared ladspa object.\n");
		exit(1);
	}
	func = (LADSPA_Descriptor_Function)dlsym(so_handle, "ladspa_descriptor");
	if ( dlerror() != NULL) {
		fprintf(stderr, "Error accessing ladspa descriptor.\n");
		exit(1);
	} 

	desc = (LADSPA_Descriptor *)func(1);
	handle = desc->instantiate(desc,SAMPLERATE);

	gain = .5;

	desc->connect_port(handle, 0, &gain);
	desc->connect_port(handle, 1, in1);
	desc->connect_port(handle, 2, out1);
	desc->connect_port(handle, 3, in2);
	desc->connect_port(handle, 4, out2);
}


fade(Sample *pcmbuf, int n, float fact) {
	int i;

	for(i=0;i<=n/sizeof(pcmbuf)-1;i++) {
		in1[i] = (pcmbuf[i].left / 32768.0) * fact;
		in2[i] = (pcmbuf[i].right / 32768.0) * fact;
	}
	desc->run(handle, n/sizeof(pcmbuf));

	for(i=0;i<=n/sizeof(pcmbuf)-1;i++) {
		pcmbuf[i].left = (int)(out1[i] * 32768);
		pcmbuf[i].right = (int)(out2[i] * 32768);
	}
}
	

/*
 *
 */
amp(Sample *pcmbuf, int n) {
	int i;

	for(i=0;i<=n/sizeof(pcmbuf)-1;i++) {
		in1[i] = (pcmbuf[i].left / 32768.0);
		in2[i] = (pcmbuf[i].right / 32768.0);
	}
	desc->run(handle, n/sizeof(pcmbuf));

	for(i=0;i<=n/sizeof(pcmbuf)-1;i++) {
		pcmbuf[i].left = (int)(out1[i] * 32768);
		pcmbuf[i].right = (int)(out2[i] * 32768);
	}
}
	


/*
 *
 */
/*
limiter_prep() {
	desc = (LADSPA_Descriptor *)ladspa_descriptor(0);

	handle = desc->instantiate(desc,SAMPLERATE);

	limit = -3.0;
	lahead = .5;
	atten = 0;

	desc->connect_port(handle, 0, &limit);
	desc->connect_port(handle, 1, &lahead);
	desc->connect_port(handle, 2, &atten);
	desc->connect_port(handle, 3, in1);
	desc->connect_port(handle, 4, in2);
	desc->connect_port(handle, 5, out1);
	desc->connect_port(handle, 6, out2);

	desc->activate(handle);
}
*/



/*
 *
 */
/*
limiter(Sample *pcmbuf, int n) {
	int i;

	for(i=0;i<=n/sizeof(pcmbuf)-1;i++) {
		in1[i] = (pcmbuf[i].left / 32768.0);
		in2[i] = (pcmbuf[i].right / 32768.0);
	}

	desc->run(handle, n/sizeof(pcmbuf));

	for(i=0;i<=n/sizeof(pcmbuf)-1;i++) {
		pcmbuf[i].left = (int)(out1[i] * 32768);
		pcmbuf[i].right = (int)(out2[i] * 32768);
	}
}
*/



/*
 *
 */
mixer(Sample *pcmbuf, int n)
{
	int i;
	for(i=0;i<=n/sizeof(pcmbuf)-1;i++) {
		if(pcmbuf[i].left == pcmbuf[i].right == 0) {
			out[i].left += (pcmbuf[i].left / 32768.0);
			out[i].right += (pcmbuf[i].right / 32768.0);
		} else {
			out[i].left += (pcmbuf[i].left / 32768.0) / 1.4142136;
			out[i].right += (pcmbuf[i].right / 32768.0) / 1.4142136;
		}
	}
}




/*
 *
 */
int nread(void *ptr, size_t size, size_t nmemb, FILE *stream) {
	int ret;
	ret= (fread(ptr,size,nmemb,stream));
	return(ret);

}

int nclose() { return(1); }
int ntell() { return(0); }
nseek() { return(-1); }


ov_callbacks net_callbacks = {
	&nread,
	&nseek,
	&nclose,
	&ntell
};

ov_callbacks file_callbacks = {
	&fread,
	&fseek,
	&fclose,
	&ftell
};


/*
 *
 */
FILE *playstream_open(URI uri) {
	int sd, rc, i;
	struct sockaddr_in localAddr, servAddr;
	struct hostent *h;
	FILE *fd;
	char s[255];

	h = gethostbyname(uri.host);

	if(h==NULL) {
		perror("cant resolve host");
		return(NULL);
	}

	servAddr.sin_family = h->h_addrtype;
	memcpy((char *) &servAddr.sin_addr.s_addr, h->h_addr_list[0], h->h_length);
	servAddr.sin_port = htons(uri.port);

	sd = socket(AF_INET, SOCK_STREAM, 0);
	if(sd<0) {
		perror("cannot open socket");
		exit(1);
	}

    localAddr.sin_family = AF_INET;
	localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	localAddr.sin_port = htons(0);

	rc = bind(sd, (struct sockaddr *) &localAddr, sizeof(localAddr));
	if(rc<0) {
		printf("cannot bind port TCP\n");
		perror("error ");
		exit(1);
	}

	rc = connect(sd, (struct sockaddr *) &servAddr, sizeof(servAddr));
	if(rc<0) {
		perror("cannot connect ");
		exit(1);
	}

	fd = fdopen(sd,"r+");
	if(fd == NULL) {
		perror("cannot get FILE *");
		exit(1);
	}

	strcpy(s, "GET ");
	strcat(s, uri.path);
	strcat(s, " HTTP/1.0\r\nAccept: */*\r\nConnection: Keep-Alive");

	fprintf(fd,"%s\r\n\r\n",s);
	fflush(fd);

	return(fd);
}




/*
 *
 */
void *playstream(void *arg) {
	Player *player = arg;
	OggVorbis_File vf;
	int eof=0;
	int current_section;
	char **ptr;
	vorbis_info *vi;
	Sample pcmout[BUFSIZE];
	long ret;
	FILE *fp;
	float fact=1;
	int n=0;

	if(!strcmp(player->uri.proto,"http")) {
		fp = playstream_open(player->uri);

		if((eof=ov_open_callbacks(fp, &vf, NULL, 0, net_callbacks)) < 0) {
			fprintf(stderr,"Input does not appear to be an Ogg bitstream (%d).\n",eof);
			exit(1);
		}
	} else {
		fp = fopen(player->uri.path,"r");

		ov_open(fp, &vf, NULL, 0);
	}

	ptr=ov_comment(&vf,-1)->user_comments;
	vi=ov_info(&vf,-1);
	while(*ptr){
		fprintf(stderr,"%s\n",*ptr);
		++ptr;
	}
    while(!eof){
		pthread_mutex_lock(&mut);
        ret=ov_read(&vf,(char *)pcmout,sizeof(pcmout),0,2,1,&current_section);
        if (ret == 0) {
            eof=1;
        } else if (ret < 0) {
        } else {

			if(!player->go) {
				if(player->method==FADE) {
					if(n%125==0) {
						fact=fact*.8;
						printf("%d, %d, %d, %f\n",time(NULL),n,n%125,fact);
					}
					n++;
					if(fact < 0.00001) {
						pthread_mutex_unlock(&mut);
						player->state = STOP;
						break;
					}
					fade(pcmout,ret,fact);
				} else {
						pthread_mutex_unlock(&mut);
						player->state = STOP;
						break;
				}
			}

			if(player->state == PLAY) {
				mixer(pcmout, ret);
			}
        }
		pthread_mutex_unlock(&mut);
    }

    ov_clear(&vf);
    	printf("some pthread is gone\n");
	pthread_exit(NULL);
}




/*
 *
 */
void *record()
{
	int fd,r,val,bp;
	int i, j;
	int n, z=0;
	Sample pcmout[BUFSIZE];
	int sum=0, now;
	int otime;
	int take;

	otime = time(NULL);
	fd = open("/dev/dsp",O_RDONLY);
	if(fd<0) {
		fprintf(stderr,"Can't open audio driver.\n");
		exit(-1);
	}
	r = ioctl(fd,SNDCTL_DSP_RESET,NULL);
	if(r>=0) {
		val = 16;
		r = ioctl(fd,SNDCTL_DSP_SAMPLESIZE,&val);
	}
	if(r>=0) {
		val = AFMT_S16_LE;
		r = ioctl(fd,SNDCTL_DSP_SETFMT,&val);
	}
	if(r>=0) {
		val = 1;
		r = ioctl(fd,SNDCTL_DSP_STEREO,&val);
	}
	if(r>=0) {
		val = SAMPLERATE;
		r = ioctl(fd,SNDCTL_DSP_SPEED,&val);
	}
	if(r<0) {
		fprintf(stderr,"Sound device did not accept settings.\n");
		exit(-1);
	}
	for(;;) {
		pthread_mutex_lock(&mut);
		read(fd,pcmout,sizeof(pcmout));
		mixer(pcmout,sizeof(pcmout));

		z++;
		now = time(NULL);
		take = 0;

		for(j=0;j<=BUFSIZE-1;j++) {
			take += abs(pcmout[j].left);
			sum += take;
		}
		if(take > TRESHOLD) {
			// puts("signal detected");
			players[2].state=MUTE;
		}
		if((now - otime) >= MESSUREINTERVAL/2) {
			if(sum > TRESHOLD) {
				// puts("signal detected");
				players[2].state=MUTE;
			} else {
				// puts("no signal detected");
				players[2].state=PLAY;
			}
			sum = 0;
			z = 0;
			otime = time(NULL);
		}
		pthread_mutex_unlock(&mut);

	}

	close(fd);
}




/*
 *
 */
upstream_open() {

	if (!(shout = shout_new())) {
		printf("Could not allocate shout_t\n");
		return 1;
	}

	if (shout_set_host(shout, "10.0.0.4") != SHOUTERR_SUCCESS) {
		printf("Error setting hostname: %s\n", shout_get_error(shout));
		exit(1);
	}

	shout_set_port(shout, 8000);
	if (shout_set_password(shout, "hack4me") != SHOUTERR_SUCCESS) {
		printf("Error setting password: %s\n", shout_get_error(shout));
		exit(1);
	}

	if (shout_set_mount(shout, "/cast100.ogg") != SHOUTERR_SUCCESS) {
		printf("Error setting mount: %s\n", shout_get_error(shout));
		exit(1);
	}

	shout_set_format(shout, SHOUT_FORMAT_VORBIS);

	if (shout_open(shout) != SHOUTERR_SUCCESS) {
		printf("cannot Connect to server...\n");
		exit(1);
	}
}



/*
 *
 */
upstream(void *buff,long n) {
	long ret;
	
	if (n > 0) {
		ret = shout_send(shout, buff, n);
		if (ret != SHOUTERR_SUCCESS) {
			printf("DEBUG: Send error: %s\n", shout_get_error(shout));
		}
	}
	shout_sync(shout);
}





/*
 *
 */
int oggstream_open() {
	int ret;
	int i, founddata;

	vorbis_info_init(&vi);

	ret=vorbis_encode_init_vbr(&vi,2,SAMPLERATE,.5);

	if(ret) {
		printf("could not open oggstream");
		exit(1);
	}

	vorbis_comment_init(&vc);
	vorbis_comment_add_tag(&vc,"ENCODER","fcore");
	vorbis_comment_add_tag(&vc,"TITLE","reboot.fm test transmission");

	vorbis_analysis_init(&vd,&vi);
	vorbis_block_init(&vd,&vb);
  
	srand(time(NULL));
	ogg_stream_init(&os,rand());

	{
		ogg_packet header;
		ogg_packet header_comm;
		ogg_packet header_code;

		vorbis_analysis_headerout(&vd,&vc,&header,&header_comm,&header_code);
		ogg_stream_packetin(&os,&header);
		ogg_stream_packetin(&os,&header_comm);
		ogg_stream_packetin(&os,&header_code);

		while(1) {
			int result=ogg_stream_flush(&os,&og);
			if(result==0)
				return;
			upstream(og.header,og.header_len);
			upstream(og.body,og.body_len);
		}
	}
}




/*
 *
 */
oggstream() {
	long i;

	if(0) {
		vorbis_analysis_wrote(&vd,0);
	} else {
		float **buffer=vorbis_analysis_buffer(&vd,BUFSIZE);
		for(i=0;i<BUFSIZE;i++) {
			buffer[0][i] = out[i].left + 0.01;
			buffer[1][i] = out[i].right + 0.01;
		}
		vorbis_analysis_wrote(&vd,i);
	}

	while(vorbis_analysis_blockout(&vd,&vb)==1) {
		vorbis_analysis(&vb,NULL);
		vorbis_bitrate_addblock(&vb);

		while(vorbis_bitrate_flushpacket(&vd,&op)) {
			ogg_stream_packetin(&os,&op);
        
			while(1) {
				int result=ogg_stream_pageout(&os,&og);
				if(result==0)break;
				upstream(og.header,og.header_len);
				upstream(og.body,og.body_len);
				ogg_page_eos(&og);
			}
		}
	}
}



/*
 *
 */
oggstream_close() {
	ogg_stream_clear(&os);
	vorbis_block_clear(&vb);
	vorbis_dsp_clear(&vd);
	vorbis_comment_clear(&vc);
	vorbis_info_clear(&vi);
} 



/*
 *
 */
void *playout() {
	upstream_open();
	oggstream_open();
	while(1) {
		pthread_mutex_lock(&mut);
//		amp(pcmout,256);
		    ao_play(device, (char *)&out, sizeof(out));
//		write(1,out,sizeof(out));
		oggstream();
		memset(&out,0,sizeof(out));
		pthread_mutex_unlock(&mut);
	}
}


uriparse(char *ts, URI *uri) {
	char ifs[] = ":/";
	char *sp, *strtok_r();
	char *residue;
	char s[255];

	strcpy(s,ts);
	strcpy(uri->path,"");
	uri->port = 0;
	uri->proto = uri->host = (char *)0;
	uri->proto = strtok_r(s,ifs,&sp);
	if(!strcmp(uri->proto,"file")) {
		while(residue = strtok_r(0,ifs,&sp)) {
			strcat(uri->path,"/");
			strcat(uri->path,residue);
		}
	} else {
		uri->host = strtok_r(0,ifs,&sp);
		uri->port = atoi(strtok_r(0,ifs,&sp));
		while(residue = strtok_r(0,ifs,&sp)) {
			strcat(uri->path,"/");
			strcat(uri->path,residue);
		}
	}
}


/*
 *
 */
int main (int argc, char *argv[])
{
	pthread_t threads[NUM_THREADS];
	int rc, t;
	ao_sample_format format;
	int default_driver;
	char s[80];

	amp_prep();

	pthread_attr_t sched;
	struct sched_param fifo_param;

	pthread_attr_init(&sched);

	pthread_attr_setschedpolicy(&sched, SCHED_FIFO);

	fifo_param.sched_priority = 50;
	pthread_attr_setschedparam(&sched, &fifo_param);


	ao_initialize();
	default_driver = ao_default_driver_id();
    format.bits = 16;
    format.channels = 2;
    format.rate = SAMPLERATE;
    format.byte_format = AO_FMT_LITTLE;

	memset(&out,0,sizeof(out));
	
    device = ao_open_live(default_driver, &format, NULL);
    if (device == NULL) {
        fprintf(stderr, "Error opening device.\n");
        return 1;
    }

	pthread_mutex_init(&mut, NULL);

	rc = pthread_create(&threads[0], &sched, playout, (void *)NULL);
	//rc = pthread_create(&threads[1], &sched, record, (void *)NULL);
	
	
	while(1) {
		printf("> ");
		gets(s);
		if(!strcmp(s,"play 1")) {

			players[0].go = 1;
			players[0].state = PLAY;
			players[0].volfac = 1.0;
			players[0].method = HARD;
			uriparse("file:///home/j/media/ogg/t1.ogg",&players[0].uri);

			rc = pthread_create(&threads[2], &sched, playstream, (void *)(&players[0]));

		}
		if(!strcmp(s,"play 2")) {

			players[1].go = 1;
			players[1].state = PLAY;
			players[1].volfac = 1.0;
			players[1].method = HARD;
			uriparse("file:///home/j/media/ogg/t2.ogg",&players[1].uri);

			rc = pthread_create(&threads[3], &sched, playstream, (void *)(&players[1]));

		}
		if(!strcmp(s,"play 3")) {

			players[2].go = 1;
			players[2].state = PLAY;
			players[2].volfac = 1.0;
			players[2].method = HARD;
			uriparse("http://10.0.0.4:8000/bootcast.ogg",&players[2].uri);

			rc = pthread_create(&threads[4], &sched, playstream, (void *)(&players[2]));

		}
		if(!strcmp(s,"kill 1")) { players[0].go = 0; }
		if(!strcmp(s,"kill 2")) { players[1].go = 0; }
		if(!strcmp(s,"kill 3")) { players[2].go = 0; }
		if(!strcmp(s,"exit")) { exit(1); }
		
	}
	pthread_exit(NULL);

	ao_close(device);
	ao_shutdown();

}
