#include <benchmark/benchmark.h>

#include <string>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <iostream>
#include <vector>
#include <tuple>

#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

using namespace std;
static const size_t pageSize = PAGE_SIZE;
static size_t fsize = 1024 * (1ull << 20);
static size_t pagesTotal = fsize / pageSize;

class Fd {
    int m_fd = -1;
public:
    int get() { return m_fd; }
    void set(int fd) { m_fd = fd; }
    Fd() {}
    explicit Fd(int fd) : m_fd{fd} {}
    ~Fd() {
        if (m_fd >= 0)
            close(m_fd);
    }
};

int dummy = 0;

void fillPageJunk(void *ptr)
{
    uint64_t seed = (unsigned long long)rand() | ((unsigned long long)rand() << 32);
    uint64_t *target = (uint64_t*)ptr;
    for (int i = 0; i < pageSize / sizeof(uint64_t); i++) {
        *target = seed ^ (uint64_t)(uintptr_t)target;
        seed = (seed << 1) | ((seed >> 63) & 1);
        target++;
    }
}

class FileMap {
    string m_name;
    size_t m_size;
    void *m_ptr = nullptr;
    Fd m_fileFd;
public:
    enum Hint {
       FILE_MAP_HINT_NONE,
       FILE_MAP_HINT_RAND,
       FILE_MAP_HINT_LINEAR,
    };
    FileMap(const string &name, size_t size, Hint hint = FILE_MAP_HINT_NONE) : m_name{name}, m_size{size} {
        int fd = open(name.c_str(), O_CREAT | O_RDWR, S_IRWXU);
        if (fd < 0) {
            cout << "Error: open failed for " << name << ": " << strerror(errno) << endl;
            exit(1);
        }
        m_fileFd.set(fd);
        fallocate(m_fileFd.get(), 0, 0, size);
        unlink(name.c_str());
        m_ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fileFd.get(), 0);
        if ((int)(uintptr_t)m_ptr == -1) {
            cout << "Error: mmap failed: " << (int)(uintptr_t)m_ptr << ": " << strerror(errno) << endl;
            exit(1);
        }
        switch (hint) {
        case FILE_MAP_HINT_NONE: break;
        case FILE_MAP_HINT_RAND:
            madvise(m_ptr, m_size, MADV_RANDOM);
            break;
        case FILE_MAP_HINT_LINEAR:
            madvise(m_ptr, m_size, MADV_SEQUENTIAL);
            break;
        }
        for (int i = 0; i < m_size / pageSize; i++) {
            uint8_t *targetPtr = (uint8_t*)m_ptr + 4096ull * i;
            fillPageJunk(targetPtr);
        }
    }
    void benchRandomRead(unsigned int targetPage) {
        uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * targetPage;
        dummy += *targetPtr;
    }
    void benchRandomWrite(unsigned int targetPage) {
        uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * targetPage;
        *targetPtr = dummy;
    }
    void benchLinearRead(unsigned int j) {
        uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * j;
        dummy += *targetPtr;
    }
    void benchLinearWrite(unsigned int j) {
        uint8_t *targetPtr = (uint8_t*)m_ptr + pageSize * j;
        *targetPtr = dummy;
    }
    void dropCache() {
        int ret1 = msync(m_ptr, m_size, MS_SYNC | MS_INVALIDATE);
        madvise(m_ptr, m_size, MADV_DONTNEED);
        (void)ret1;
    }
    ~FileMap() {
        if (m_ptr)
            munmap(m_ptr, m_size);
    }

};

static void benchRandomRead(benchmark::State& state) {
    FileMap file{"/data/local/tmp/mmap_test", fsize};
    while (state.KeepRunning()) {
        unsigned int targetPage = rand() % pagesTotal;
        file.benchRandomRead(targetPage);
    }
    state.SetBytesProcessed(state.iterations() * pageSize);
}
BENCHMARK(benchRandomRead);

static void benchRandomWrite(benchmark::State& state) {
    FileMap file{"/data/local/tmp/mmap_test", fsize};
    while (state.KeepRunning()) {
        unsigned int targetPage = rand() % pagesTotal;
        file.benchRandomWrite(targetPage);
    }
    state.SetBytesProcessed(state.iterations() * pageSize);
}
BENCHMARK(benchRandomWrite);

static void benchLinearRead(benchmark::State& state) {
   FileMap file{"/data/local/tmp/mmap_test", fsize};
   unsigned int j = 0;
   while (state.KeepRunning()) {
       file.benchLinearRead(j);
       j = (j + 1) % pagesTotal;
   }
   state.SetBytesProcessed(state.iterations() * pageSize);
}
BENCHMARK(benchLinearRead);

static void benchLinearWrite(benchmark::State& state) {
   FileMap file{"/data/local/tmp/mmap_test", fsize};
   unsigned int j = 0;
   while (state.KeepRunning()) {
       file.benchLinearWrite(j);
       j = (j + 1) % pagesTotal;
   }
   state.SetBytesProcessed(state.iterations() * pageSize);
}
BENCHMARK(benchLinearWrite);

BENCHMARK_MAIN();