#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <md4.h>

/*
 * Hashes a file and returns a ed2k:// URL
 *
 * Usage:   ed2k filename
 *
 * (c) 2007 Niklas Olmes <niklas@noxa.de>
 *
 */
/*
 * A ed2k:// hash is simply a MD4-Hash of its 9500K chunk hashes,
 * or the MD4-Hash of the first chunk, if smaller than a chunk.
 *
 * Nothing fancy here.
 */

#define ED2K_CHUNKSIZE	(9500*1024)

const char hex[16] = "0123456789abcdef";

int
main(int argc, char **argv)
{
	MD4_CTX ctx, cctx;
	struct stat *sb;
	unsigned char hash[16];
	size_t pos;
	char phash[33];
	unsigned char *data;
	int fd, i;

	if (argc < 2)
		errx(-1, "need filename as argument");

	if ((sb = calloc(1, sizeof(struct stat))) == NULL)
		err(-1, "calloc() failed");

	if (stat(argv[1], sb) != 0) {
		free(sb);
		err(-1, "stat() returned non-zero");
	}

#ifdef DEBUG
	(void)fprintf(stderr, "file size: %lld bytes\n", (long long)sb->st_size);
#endif

	if ((fd = open(argv[1], O_RDONLY)) < 0) {
		free(sb);
		err(-1, "open() failed");
	}

	/* We just assume we can mmap() the whole file in one piece. */
	if ((data = mmap(NULL, sb->st_size, PROT_READ, MAP_SHARED, fd, 0)) == NULL) {
		free(sb);
		(void)close(fd);
		err(-1, "mmap() failed");
	}

	MD4Init(&ctx);

	if (sb->st_size <= ED2K_CHUNKSIZE) {
		MD4Update(&ctx, data, sb->st_size);
	} else {
		int blocks, cur_block;
		unsigned char *partials;
#ifdef DEBUG
		int i;
#endif

		blocks = sb->st_size / ED2K_CHUNKSIZE;
		partials = calloc(1, blocks * 16);

#ifdef DEBUG
		fprintf(stderr, "%d blocks\n", blocks);
#endif

		for (pos = 0, cur_block = 0; cur_block <= blocks; cur_block++, pos += ED2K_CHUNKSIZE) {
			MD4Init(&cctx);
			MD4Update(&cctx, data + pos, (sb->st_size - pos >= ED2K_CHUNKSIZE) ? ED2K_CHUNKSIZE : sb->st_size - pos);
			MD4Final(partials + (cur_block * 16) , &cctx);
#ifdef DEBUG
			fprintf(stderr, "%d: ", cur_block);
			for (i = 0; i < 16; i++) {
				fprintf(stderr, "%02hx ", *(partials + (cur_block * 16) + i));
			}
			fprintf(stderr, "\n");
#endif
		}

		MD4Update(&ctx, partials, 16 * (blocks + 1));
		free(partials);
	}

	MD4Final(hash, &ctx);
	free(sb);
	(void)close(fd);

	(void)memset(phash, '\0', sizeof(phash));
	for (i = 0; i < 16; i++) {
		phash[i<<1] = hex[(hash[i] & 0xf0) >> 4];
		phash[(i<<1)+1] = hex[hash[i] & 0x0f];
	}

	(void)printf("ed2k://|file|%s|%lld|%s|\n", argv[1], (long long)sb->st_size, phash);

	return 0;
}
