/* $Id: progress.c,v 1.1 2007-07-24 07:29:41 gophi Exp $ */

#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>

struct
{
	pthread_mutex_t mtx;
	time_t start, cur;
	size_t count;
} data;

static const char *qualify(double *data, int level)
{
	if (level < 3 && *data > 1024) {
		*data /= 1024;
		return qualify(data, level + 1);
	}

	if (level == 0)
		return "";
	else if (level == 1)
		return "k";
	else if (level == 2)
		return "M";

	return "G";
}

static void do_log(void)
{
	time_t start, cur, td;
	double count;
	double speed;
	const char *count_qual;
	const char *speed_qual;

	pthread_mutex_lock(&data.mtx);
	start = data.start;
	cur = data.cur;
	count = data.count;
	pthread_mutex_unlock(&data.mtx);

	td = (cur >= start) ? (cur - start) : 0;
	speed = td ? ((double) count / td) : 0;

	count_qual = qualify(&count, 0);
	speed_qual = qualify(&speed, 0);

	fprintf(stderr, "time=%u count=%.2f%sB speed=%.2f%sB/s         \r", td, count, count_qual, speed, speed_qual);
	fflush(stderr);
}

static void destroy_thread(pthread_t thread)
{
	pthread_cancel(thread);
	pthread_join(thread, NULL);

	do_log();
	fprintf(stderr, "\n");
}

static void *thread_logger(void *arg)
{
	for (;;) {
		do_log();
		sleep(1);
	}

	return arg;
}

int main(void)
{
	pthread_t logger;

	data.start = data.cur = time(NULL);
	data.count = 0;

	assert(pthread_mutex_init(&data.mtx, NULL) == 0);
	assert(pthread_create(&logger, NULL, thread_logger, NULL) == 0);

	for (;;) {
		char buf[1024], *p = buf;
		ssize_t rrs, size;

		rrs = read(0, buf, sizeof(buf));
		if (rrs == -1) {
			int se = errno;

			if (errno == EAGAIN || errno == EINTR)
				continue;

			destroy_thread(logger);
			fprintf(stderr, "read(): %s\n", strerror(se));

			exit(EXIT_FAILURE);
		} else if (rrs == 0)
			break;

		size = rrs;

		while (rrs) {
			ssize_t wrs;

			wrs = write(1, p, rrs);
			if (wrs == -1) {
				int se = errno;

				if (errno == EAGAIN || errno == EINTR)
					continue;

				destroy_thread(logger);
				fprintf(stderr, "write(): %s\n", strerror(se));

				exit(EXIT_FAILURE);
			}

			// need to check for 0?

			rrs -= wrs;
			p += wrs;
		}

		pthread_mutex_lock(&data.mtx);
		data.cur = time(NULL);
		data.count += size;
		pthread_mutex_unlock(&data.mtx);
	}

	destroy_thread(logger);

	return 0;
}
