#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <zlib.h>
#include <openssl/md2.h>
#include <openssl/md4.h>
#include <openssl/md5.h>

const unsigned int prime = 2000000011;

unsigned int karprabin (unsigned char *datac, int N) {
    assert(N%4==0);
    unsigned int *data=(unsigned int*)datac;
    N=N/4;
    int i;
    unsigned int result=0;
    for (i=0; i<N; i++) {
	result=(result*prime)+data[i];
    }
    return result;
}

// According to
//  P. L'Ecuyer, "Tables of Linear Congruential Generators of
//  Different Sizes and Good Lattice Structure", Mathematics of
//  Computation 68:225, 249--260 (1999).
// m=2^{32}-5  a=1588635695 is good.

const unsigned int mkr = 4294967291U;
const unsigned int akr = 1588635695U;


// But this is slower
unsigned int karprabinP (unsigned char *datac, int N) {
    assert(N%4==0);
    unsigned int *data=(unsigned int*)datac;
    N=N/4;
    int i;
    unsigned long long result=0;
    for (i=0; i<N; i++) {
	result=((result*akr)+data[i])%mkr;
    }
    return result;
}

float tdiff (struct timeval *start, struct timeval *end) {
    return (end->tv_sec-start->tv_sec) +1e-6*(end->tv_usec - start->tv_usec);
}

int main (int argc __attribute__((__unused__)), char *argv[] __attribute__((__unused__))) {
    struct timeval start, end;
    const int N=2<<20;
    unsigned char *data=malloc(N);
    int i;
    assert(data);
    for (i=0; i<N; i++) data[i]=random();

    // adler32
    {
	uLong a32 = adler32(0L, Z_NULL, 0);
	for (i=0; i<3; i++) {
	    gettimeofday(&start, 0);
	    a32 = adler32(a32, data, N);
	    gettimeofday(&end,   0);
	    float tm = tdiff(&start, &end);
	    printf("adler32=%lu, time=%9.6fs %9.6fns/b\n", a32, tm, 1e9*tm/N);
	}
    }

    // crc32
    {
	uLong c32 = crc32(0L, Z_NULL, 0);
	for (i=0; i<3; i++) {
	    gettimeofday(&start, 0);
	    c32 = crc32(c32, data, N);
	    gettimeofday(&end,   0);
	    float tm = tdiff(&start, &end);
	    printf("crc32=%lu, time=%9.6fs %9.6fns/b\n", c32, tm, 1e9*tm/N);
	}
    }

    // MD2
    {
	unsigned char buf[MD2_DIGEST_LENGTH];
	int j;
	for (i=0; i<3; i++) {
	    gettimeofday(&start, 0);
	    MD2(data, N, buf);
	    gettimeofday(&end,   0);
	    float tm = tdiff(&start, &end);
	    printf("md2=");
	    for (j=0; j<MD2_DIGEST_LENGTH; j++) {
		printf("%02x", buf[j]);
	    }
	    printf(" time=%9.6fs %9.6fns/b\n", tm, 1e9*tm/N);
	}
    }

    // MD4
    {
	unsigned char buf[MD4_DIGEST_LENGTH];
	int j;
	for (i=0; i<3; i++) {
	    gettimeofday(&start, 0);
	    MD4(data, N, buf);
	    gettimeofday(&end,   0);
	    float tm = tdiff(&start, &end);
	    printf("md4=");
	    for (j=0; j<MD4_DIGEST_LENGTH; j++) {
		printf("%02x", buf[j]);
	    }
	    printf(" time=%9.6fs %9.6fns/b\n", tm, 1e9*tm/N);
	}
    }

    // MD5
    {
	unsigned char buf[MD5_DIGEST_LENGTH];
	int j;
	for (i=0; i<3; i++) {
	    gettimeofday(&start, 0);
	    MD5(data, N, buf);
	    gettimeofday(&end,   0);
	    float tm = tdiff(&start, &end);
	    printf("md5=");
	    for (j=0; j<MD5_DIGEST_LENGTH; j++) {
		printf("%02x", buf[j]);
	    }
	    printf(" time=%9.6fs %9.6fns/b\n", tm, 1e9*tm/N);
	}
    }

    // karp rabin
    {
	for (i=0; i<3; i++) {
	    gettimeofday(&start, 0);
	    unsigned int kr = karprabin(data, N);
	    gettimeofday(&end,   0);
	    float tm = tdiff(&start, &end);
	    printf("kr=%ud time=%9.6fs %9.6fns/b\n", kr, tm, 1e9*tm/N);
	}
    }
    free(data);
    return 0;
}