#include <sys/mman.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <glib.h>
#include <sys/time.h>
#include <altivec.h>

#define MB (1024 * 1024)
#define SIZE 3 * MB

typedef unsigned char uchar;
guchar *dest;

static void
disaster (const char *what, int eno)
{
    fprintf (stderr, "%s%s%s\n", what,
	     eno? ": " : "",
	     eno? strerror (eno) : "");
    exit (-1);
}

static double
timeval_to_ms (const GTimeVal *timeval)
{
  return (timeval->tv_sec * G_USEC_PER_SEC + timeval->tv_usec) / 1000.0;
}

static double
time_diff (const GTimeVal *first,
	   const GTimeVal *second)
{
  double first_ms = timeval_to_ms (first);
  double second_ms = timeval_to_ms (second);

  return first_ms - second_ms;
}

static void
read8 (const guchar *source, gsize size)
{
    int i;
    char *tmp = dest;
    while ( size-- ) {
        *tmp = *source;
        ++tmp;
        ++source;
    }
}

static void
read32 (const guchar *source, gsize size)
{
    int i;
    unsigned int *tmp = (unsigned int*)dest;
    unsigned int *src = (unsigned int*)(source);
    while ( size ) {
        *tmp = *src;
        ++src;
        ++tmp;
        size -= 4;
    }
}

static void
readA (const guchar *source, gsize size)
{
    unsigned int tmp[4];
    int i = 0;
    int vsize = sizeof(vector unsigned int);

    while ( ((long)source & 0x1F) && (size))
    {
        read8(source, 1);
        ++source; --size;
    }

    while (size > 0) {
        vector unsigned int ld = vec_ld(0, (unsigned int*)source);
        vec_st(ld, 0, (unsigned int*)(dest+i));
        source += 16;
        i += 16;
        size -= 16;
    }
}


static void
readM (const guchar *source, gsize size)
{
    __builtin_memcpy(dest, source, size);
}

static gulong
run_bench (const char *header, void (* func) (const guchar *, gsize), const guchar *data, gsize size)
{
    GTimeVal before, after;
    unsigned long elapsed;

    g_get_current_time (&before);

    (* func) (data, size);

    g_get_current_time (&after);

    elapsed = time_diff (&after, &before);

    g_print ("%30s: %d ms (%f MB per second)\n", header, elapsed, (1000.0 * size / (double)elapsed)/MB);

    return elapsed;
}

static const guchar *
get_framebuffer (void)
{
    int fd = open ("/dev/fb0", O_RDWR);
    const guchar *framebuffer;

    if (fd < 0)
	disaster ("Could not open framebuffer", errno);

    printf ("fd: %d\n", fd);

    framebuffer = mmap (NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    if (framebuffer == (guchar *)-1)
	disaster ("Could not open framebuffer", errno);

    return framebuffer;
}

int
main ()
{
    const guchar *framebuffer = get_framebuffer ();
    dest = malloc(sizeof(guchar)*SIZE);

    run_bench (" 8 bit a time", read8, framebuffer, SIZE);
    run_bench ("32 bit a time", read32, framebuffer, SIZE);
    run_bench ("Altivec 128 bit a time", readA, framebuffer, SIZE);
    run_bench ("Memcpy ", readM, framebuffer, SIZE);
    run_bench ("Local 32 bit a time", read32, dest, SIZE);

    free(dest);
    return 0;
}

