#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/time.h>

#include "pagingtest.h"

int pageinout_test(int test_runs, bool cache, unsigned long long file_size) {
    int fd;
    char tmpname[] = "pageinoutXXXXXX";
    unsigned char *vec;
    int i;
    unsigned long long j;
    volatile char *buf;
    int ret = -1;
    int rc;
    struct timeval begin_time, end_time, elapsed_time, total_time_in, total_time_out;
    long pagesize = sysconf(_SC_PAGE_SIZE);

    timerclear(&total_time_in);
    timerclear(&total_time_out);

    fd = create_tmp_file(tmpname, file_size);
    if (fd < 0) {
        return -1;
    }

    vec = alloc_mincore_vec(file_size);
    if (vec == NULL) {
        goto err_alloc;
    }

    buf = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (buf == ((void *)-1)) {
        fprintf(stderr, "Failed to mmap file: %s\n", strerror(errno));
        goto err_mmap;
    }

    if (!check_caching((void *)buf, vec, file_size, false)) {
        goto err;
    }

    if (!cache) {
        //madvise and fadvise as random to prevent prefetching
        rc = madvise((void *)buf, file_size, MADV_RANDOM) ||
               posix_fadvise(fd, 0, file_size, POSIX_FADV_RANDOM);
        if (rc) {
            goto err;
        }
    }

    for (i = 0; i < test_runs; i++) {
        gettimeofday(&begin_time, NULL);
        //read every page into the page cache
        for (j = 0; j < file_size; j += pagesize) {
            buf[j];
        }
        gettimeofday(&end_time, NULL);

        timersub(&end_time, &begin_time, &elapsed_time);
        timeradd(&total_time_in, &elapsed_time, &total_time_in);

        if (!check_caching((void *)buf, vec, file_size, true)) {
            goto err;
        }

        gettimeofday(&begin_time, NULL);
        rc = madvise((void *)buf, file_size, MADV_DONTNEED) ||
               posix_fadvise(fd, 0, file_size, POSIX_FADV_DONTNEED);
        gettimeofday(&end_time, NULL);
        if (rc) {
            fprintf(stderr, "posix_fadvise/madvise DONTNEED failed\n");
            goto err;
        }

        timersub(&end_time, &begin_time, &elapsed_time);
        timeradd(&total_time_out, &elapsed_time, &total_time_out);

        if (!check_caching((void *)buf, vec, file_size, false)) {
            goto err;
        }
    }

    printf("%scached page-in: %llu MB/s\n", cache ? "" : "un",
             (file_size * test_runs * USEC_PER_SEC) /
             (1024 * 1024 * (total_time_in.tv_sec * USEC_PER_SEC + total_time_in.tv_usec)));
    printf("%scached page-out (clean): %llu MB/s\n", cache ? "" : "un",
             (file_size * test_runs * USEC_PER_SEC) /
             (1024 * 1024 * (total_time_out.tv_sec * USEC_PER_SEC + total_time_out.tv_usec)));

    ret = 0;

err:
    munmap((void *)buf, file_size);
err_mmap:
    free(vec);
err_alloc:
    close(fd);
    return ret;
}
