/*
 * How to build:
 *
 *   $ gcc -lz -llzo2 ./mycompress.c -o mycompress
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <zlib.h>
#include <lzo/lzo1x.h>

enum
{
	BLOCK_SIZE = 4096
};

enum compression
{
	GLIB = 0,
	LZO
};

static inline size_t max(size_t a, size_t b)
{
	return a >= b ? a : b;
}

static inline size_t compressBoundLZO(size_t size)
{
	return size + size / 16 + 64 + 3;
}

static inline double getdtime(void)
{
	struct timeval tv;
	gettimeofday(&tv, NULL);
	return (double)tv.tv_sec + (double)tv.tv_usec * 0.001 * 0.001;
}

static void print_usage(void)
{
	printf("usage: mycompress [glib|lzo] <input file> <output file>\n");
}

int main(int argc, char **argv)
{
	enum compression flag_compress;
	FILE *fi = NULL, *fo = NULL;
	char *bufout = NULL;
	lzo_bytep wrkmem = NULL;
	unsigned long size_out, len_bufout;
	double t_compress_start, t_compress_end, t_compress_total;
	double t_io_start, t_io_end, t_io_total;
	size_t total_input_bytes, total_output_bytes;

	if (argc != 4) {
		print_usage();
		return EXIT_FAILURE;
	}

	if (strncmp(argv[1], "glib", 4) == 0) {
		flag_compress = GLIB;
	}

	else if (strncmp(argv[1], "lzo", 3) == 0) {
		if (lzo_init() != LZO_E_OK) {
			fprintf(stderr, "lzo_init() failed\n");
			return EXIT_FAILURE;
		}
		flag_compress = LZO;
	}

	else {
		return EXIT_FAILURE;
	}

	fi = fopen(argv[2], "r");
	if (!fi) {
		perror("fopen");
		goto error;
	}
	fo = fopen(argv[3], "w");
	if (!fo) {
		perror("fopen");
		goto error;
	}
	switch (flag_compress) {
	case GLIB:
		len_bufout = compressBound(BLOCK_SIZE);

		break;
	case LZO:
		len_bufout = compressBoundLZO(BLOCK_SIZE);
		break;
	}
	bufout = malloc(len_bufout);
	if (!bufout) {
		perror("malloc");
		goto error;
	}
	wrkmem = malloc(LZO1X_1_MEM_COMPRESS);
	if (!wrkmem) {
		perror("malloc");
		goto error;
	}

	t_compress_start = t_compress_end = t_compress_total = 0;
	t_io_start = t_io_end = t_io_total = 0;
	total_input_bytes = total_output_bytes = 0;

	while (!ferror(fi)) {
		char buf[BLOCK_SIZE];
		unsigned long size_out;

		total_input_bytes += BLOCK_SIZE;

		if (fread(buf, sizeof(buf), 1, fi) != 1) {
			if (feof(fi))
				break;
			perror("fread");
			goto error;
		}

		/*
		 * For size_out, GLIB needs output buffer size, while
		 * LZO original data size.
		 */
		switch (flag_compress) {
		case GLIB: size_out = len_bufout; break;
		case LZO: size_out = BLOCK_SIZE; break;
		}

		t_compress_start = getdtime();
		switch (flag_compress) {
		case GLIB:
			if (compress2(bufout, &size_out, buf, BLOCK_SIZE,
				      Z_BEST_SPEED) != Z_OK) {
				fprintf(stderr, "glib compression failed\n");
				goto error;
			}
			break;
		case LZO:
			if (lzo1x_1_compress(buf, BLOCK_SIZE, bufout,
					     &size_out, wrkmem) != LZO_E_OK) {
				fprintf(stderr, "lzo compression failed\n");
				goto error;
			}
			break;
		}

		t_compress_end = getdtime();
		t_compress_total += t_compress_end - t_compress_start;

		t_io_start = getdtime();
		if (fwrite(bufout, size_out, 1, fo) != 1) {
			perror("fwrite");
			goto error;
		}
		t_io_end = getdtime();
		t_io_total += t_io_end - t_io_start;

		total_output_bytes += size_out;
	}

	printf("Input size: %d bytes\n", total_input_bytes);
	printf("Compression Time: %lf seconds\n", t_compress_total);
	printf("Output size: %d bytes\n", total_output_bytes);
	printf("IO Time: %lf seconds\n", t_io_total);

error:
	if (fi)
		fclose(fi);
	if (fo)
		fclose(fo);
	free(wrkmem);
	return 0;
}
