#include <sys/types.h>
#include <sys/stat.h>
#include <sys/poll.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <time.h>

#include "fifos.h"

#ifndef FIFOS_BLKSZ
#define FIFOS_BLKSZ 8192
#endif
#ifndef FIFOS_MAX_EERCNT
#define FIFOS_MAX_ERRCNT 10
#endif

static unsigned int calc_bufsize(unsigned int size, double in_rate, double out_rate) {
	unsigned int ret;

	if (in_rate>out_rate) return(0);
	ret=size*(1.0000-(in_rate/out_rate));
	ret/=8192; ret+=1; ret*=8192;
	if (ret>=size) {
		PRINTERR("We'd need to buffer more than all of the data! [ret=%i, adjusted to ret=%i]", ret, size);
		ret=size;
	}

	return(ret);
}

static double calc_rate(time_t start, time_t stop, unsigned int bytes, unsigned int bufsize) {
	double ret;

	if (start==stop) {
		ret=bufsize*100000;
	} else {	
		ret=((double) bytes)/((double) (stop-start));
	}
	return(ret);
}

int print_help(char *msg) {
	fprintf(stderr, "Usage: fifos <size>\n");
	if (msg) fprintf(stderr, "\n%s\n" , msg);
	return(1);
}

int main(int argc, char **argv) {
	unsigned int bufloc, bufloc_s, bufloc_r, bufsize=(1024*1024), bufsize_n, strsize;
	unsigned int n;
	struct pollfd poll_info[2];
	signed int eof=0;
	signed int polltimeout=-1;
	time_t start, stop, poll_writelock=0;
	ssize_t x;
	double in_rate, out_rate;
	char *buf;
	int sockfl;
	int pollret;
	int errcnt=0;

	if (argv[1]==NULL) {
		return(print_help("You must specify the size."));
	}
	strsize=atoi(argv[1]);
	SPOTVAR_I(strsize);

	buf=malloc(bufsize);
	bufloc=0;

	/* Fill the buffer and calculate our input rate. */
	start=time(NULL);
	while (bufloc<bufsize) {
		n=(bufsize-bufloc);
		if (n>FIFOS_BLKSZ) n=FIFOS_BLKSZ;
		x=read(STDIN_FILENO, buf+bufloc, n);
		if (x==0) { eof=1; break; }
		if (x<0) { eof=x; break; }
		bufloc+=x;
	}
	stop=time(NULL);
	in_rate=calc_rate(start, stop, bufloc, bufsize);
	SPOTVAR_D(in_rate);

	/* Empty the buffer and calculate our output rate. */
	bufloc_s=bufloc;
	start=time(NULL);
	while (bufloc) {
		n=bufloc;
		if (n>FIFOS_BLKSZ) n=FIFOS_BLKSZ;
		x=write(STDOUT_FILENO, buf+bufsize-bufloc, n);
		if (x!=n) {
			PRINTERR("We could not write all our data, exiting.");
			return(1);
		}
		bufloc-=x;
	}
	stop=time(NULL);
	out_rate=calc_rate(start, stop, bufloc_s, bufsize);
	SPOTVAR_D(out_rate);

	/* If there's no more data, no sense in doing anything else. */
	if (eof) return(0);

	/* Our stream is actually a little bit smaller (now, that is). */
	strsize-=bufloc_s;

	/* Calculate our new buffer size and allocate it. */
	bufsize=calc_bufsize(strsize, in_rate, out_rate);
	free(buf);
	/* We allocate the entire stream size because we'll need the rest later. */
#ifdef FIFOS_BUF0
	if (bufsize!=0) buf=malloc(strsize);
#else
	if (bufsize==0) bufsize=1024*128;
	buf=malloc(strsize);
#endif
	SPOTVAR_I(bufsize);

	/*
         * Fill the buffer, again.  Change its size if the
         *  input rate changes enough.
         */

	PRINTERR_X("Preparing to fill the buffer [%i bytes], this will take %lfs.", bufsize, bufsize/in_rate);
	bufloc=0;
	bufloc_r=0;
	start=time(NULL);
	while (bufloc<bufsize) {
		n=(bufsize-bufloc);
		if (n>FIFOS_BLKSZ) n=FIFOS_BLKSZ;
		x=read(STDIN_FILENO, buf+bufloc, n);
		if (x==0) { eof=1; break; }
		if (x<0) { eof=x; break; }
		bufloc+=x;
		/* Every 25 blocks, re-evaluate our position. */
		if (bufloc%(FIFOS_BLKSZ*25)==0 && bufloc>=(FIFOS_BLKSZ*(1048576/FIFOS_BLKSZ))) {
			stop=time(NULL);
			in_rate=calc_rate(start, stop, bufloc, bufsize);
			SPOTVAR_D(in_rate);
			bufsize_n=calc_bufsize(strsize, in_rate, out_rate);
			SPOTVAR_I(bufsize_n);
		}
	}
	PRINTERR("Done filling the buffer, going to FIFO.");

#ifdef FIFOS_BUF0
	/* If we're not going buffering, don't attempt it. */
	if (bufsize==0) {
		PRINTERR("Going to read/write loop [no buffering needed].");
		while (1) {
			buf=malloc(FIFOS_BLKSZ);
			x=read(STDIN_FILENO, buf, FIFOS_BLKSZ);
			if (x<0) { PERROR("read"); break; }
			if (x==0) break;
			x=write(STDOUT_FILENO, buf, x);
			if (x<0) { PERROR("write"); break; }
		}
	}
	if (bufsize==0) return(0);
#endif

	/* Make STDIN and STDOUT non-blocking. */
	sockfl=fcntl(STDIN_FILENO, F_GETFL);
	sockfl|=O_NONBLOCK;
	fcntl(STDIN_FILENO, F_SETFL, sockfl);
	sockfl=fcntl(STDOUT_FILENO, F_GETFL);
	sockfl|=O_NONBLOCK;
	fcntl(STDOUT_FILENO, F_SETFL, sockfl);

	/*
         *  Insert the rest of the data into the FIFO
         *  and remove the amount as needed.
         */
	poll_info[0].fd=STDIN_FILENO;
	poll_info[1].fd=STDOUT_FILENO;
	poll_info[0].events=POLLIN;
	poll_info[1].events=POLLOUT;
	while (1) {
		poll_info[0].revents=0;
		poll_info[1].revents=0;
		polltimeout=((poll_writelock)?(5):(-1));
		pollret=poll(poll_info, 2, polltimeout);
		if (pollret<0) {
			if (errcnt==FIFOS_MAX_ERRCNT) {
				PRINTERR("Maximum error count reach, exiting.");
				break;
			}
			errcnt++;
		}
		if (poll_writelock) {
			if (time(NULL)>=poll_writelock && poll_info[1].fd<0) {
				poll_info[1].fd=-poll_info[1].fd;
				poll_writelock=0;
			}
		}
		if (pollret==0) continue;
		if (poll_info[0].revents) {
			if (eof) continue;

			if ((poll_info[0].revents&POLLIN)!=POLLIN) {
				PRINTERR("Error reading from input, removing...");
				poll_info[0].fd=-1;
				continue;
			}
			n=(strsize-bufloc);
			if (n>FIFOS_BLKSZ) n=FIFOS_BLKSZ;
			x=read(poll_info[0].fd, buf+bufloc, n);
			if (x<0) {
				PERROR("read");
				continue;
			}
			PRINTERR("Read %i[of attempted %i] bytes of data [bufloc=%i].", x, n, bufloc);
			if (x==0) {
				PRINTERR("Got EOF?");
				continue;
			}
			bufloc+=x;
		}
		if (poll_info[1].revents) {
			if ((poll_info[1].revents&POLLOUT)!=POLLOUT) {
				PRINTERR("Error error writing to output, exiting...");
				break;
			}
			n=(bufloc-bufloc_r);
			if (n>FIFOS_BLKSZ) n=8192;
			if (bufloc_r==strsize) {
				PRINTERR("Stream exhausted, exiting...");
				break;
			}
			if (n<=0) {
				PRINTERR("Not enough data in FIFO to write, oops! [bufloc_r=%i, strsize=%i, bufloc=%i]", bufloc_r, strsize, bufloc);
				PRINTERR_X("Disabling writes for 30 seconds because of buffering problems.");
				poll_info[1].fd=-poll_info[1].fd;
				poll_writelock=time(NULL)+30;
				continue;
			} 
			x=write(poll_info[1].fd, buf+bufloc_r, n);
			if (x<0) {
				PERROR("write");
				continue;
			}
			PRINTERR("Wrote %i bytes of data [bufloc_r=%i].", x, bufloc_r);
			bufloc_r+=x;
		}
	}

	return(0);
}
