#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "simdxorshift128plus.h"
#include "xorshift128plus.h"
#include "simdaesdragontamer.h"

#define RDTSC_START(cycles)                                                   \
    do {                                                                      \
        register unsigned cyc_high, cyc_low;                                  \
        __asm volatile(                                                       \
            "cpuid\n\t"                                                       \
            "rdtsc\n\t"                                                       \
            "mov %%edx, %0\n\t"                                               \
            "mov %%eax, %1\n\t"                                               \
            : "=r"(cyc_high), "=r"(cyc_low)::"%rax", "%rbx", "%rcx", "%rdx"); \
        (cycles) = ((uint64_t)cyc_high << 32) | cyc_low;                      \
    } while (0)

#define RDTSC_FINAL(cycles)                                                   \
    do {                                                                      \
        register unsigned cyc_high, cyc_low;                                  \
        __asm volatile(                                                       \
            "rdtscp\n\t"                                                      \
            "mov %%edx, %0\n\t"                                               \
            "mov %%eax, %1\n\t"                                               \
            "cpuid\n\t"                                                       \
            : "=r"(cyc_high), "=r"(cyc_low)::"%rax", "%rbx", "%rcx", "%rdx"); \
        (cycles) = ((uint64_t)cyc_high << 32) | cyc_low;                      \
    } while (0)




/**
Sorting stuff
*/

int qsort_compare_uint32_t(const void *a, const void *b) {
    return ( *(uint32_t *)a - *(uint32_t *)b );
}
uint32_t *create_sorted_array(uint32_t length) {
    uint32_t *array = malloc(length * sizeof(uint32_t));
    for (uint32_t i = 0; i < length; i++) array[i] = (uint32_t) rand();
    qsort(array, length, sizeof(*array), qsort_compare_uint32_t);
    return array;
}

uint32_t *create_random_array(uint32_t count) {
    uint32_t *targets = malloc(count * sizeof(uint32_t));
    for (uint32_t i = 0; i < count; i++) targets[i] = (uint32_t) rand();
    return targets;
}



// flushes the array from cache
void array_cache_flush(uint32_t* B, int32_t length) {
    const int32_t CACHELINESIZE = 64;// 64 bytes per cache line
    for(int32_t  k = 0; k < length; k += CACHELINESIZE/sizeof(uint32_t)) {
        __builtin_ia32_clflush(B + k);
    }
}

// tries to put the array in cache
void array_cache_prefetch(uint32_t* B, int32_t length) {
    const int32_t CACHELINESIZE = 64;// 64 bytes per cache line
    for(int32_t  k = 0; k < length; k += CACHELINESIZE/sizeof(uint32_t)) {
        __builtin_prefetch(B + k);
    }
}



int sortAndCompare(uint32_t * shuf, uint32_t * orig, uint32_t size) {
    qsort(shuf, size, sizeof(uint32_t), qsort_compare_uint32_t);
    qsort(orig, size, sizeof(uint32_t), qsort_compare_uint32_t);
    for(uint32_t k = 0; k < size; ++k)
        if(shuf[k] != orig[k]) {
            printf("[bug]\n");
            return -1;
        }
    return 0;
}


/*
 * Prints the best number of operations per cycle where
 * test is the function call, answer is the expected answer generated by
 * test, repeat is the number of times we should repeat and size is the
 * number of operations represented by test.
 */
#define BEST_TIME(test, pre, repeat, size)                         \
        do {                                                              \
            printf("%s: ", #test);                                        \
            fflush(NULL);                                                 \
           for(int k = 0; k < 1; ++k){ uint64_t cycles_start, cycles_final, cycles_diff;             \
            uint64_t min_diff = (uint64_t)-1;                             \
            for (int i = 0; i < repeat; i++) {                            \
                pre;                                                       \
                __asm volatile("" ::: /* pretend to clobber */ "memory"); \
                RDTSC_START(cycles_start);                                \
                test;                     \
                RDTSC_FINAL(cycles_final);                                \
                cycles_diff = (cycles_final - cycles_start);              \
                if (cycles_diff < min_diff) min_diff = cycles_diff;       \
            }                                                             \
            uint64_t S = size;                                            \
            float cycle_per_op = (min_diff) / (double)S;                  \
            printf(" %.2f cycles per operation ", cycle_per_op);}           \
            printf("\n");                                                 \
            fflush(NULL);                                                 \
 } while (0)

void demo(int size) {
    printf("Shuffling arrays of size %d \n",size);
    printf("Time reported in number of cycles per array element.\n");
    printf("Tests assume that array is in cache as much as possible.\n");
    xorshift128plus_key_t mykey = {.part1=324,.part2=4444};
    avx_xorshift128plus_key_t myavxkey;
    avx_xorshift128plus_init(324,4444,&myavxkey);
    int repeat = 500;
    uint32_t * testvalues = create_random_array(size);
    uint32_t * pristinecopy = malloc(size * sizeof(uint32_t));
    memcpy(pristinecopy,testvalues,sizeof(uint32_t) * size);
    if(sortAndCompare(testvalues, pristinecopy, size)!=0) return;
    BEST_TIME(xorshift128plus_shuffle32(&mykey,testvalues,size), array_cache_prefetch(testvalues,size), repeat, size);
    if(sortAndCompare(testvalues, pristinecopy, size)!=0) return;
    BEST_TIME(avx_xorshift128plus_shuffle32(&myavxkey,testvalues,size), array_cache_prefetch(testvalues,size), repeat, size);
    if(sortAndCompare(testvalues, pristinecopy, size)!=0) return;
    BEST_TIME(aesdragontamer_shuffle32(&g_aesdragontamer_state,testvalues,size), array_cache_prefetch(testvalues,size), repeat, size);
     free(testvalues);
    free(pristinecopy);
    printf("\n");
}

int main() {
    demo(10000);
    demo(1000000);
    return 0;
}

