#include <fstream>
#include <iostream>
#include <string>

#include <sdsl/rrr_vector.hpp>

using namespace std;
using namespace sdsl;

#ifndef BLOCK_SIZE
#    define BLOCK_SIZE 31
#endif

#ifndef RANK_SAMPLE_DENS
#    define RANK_SAMPLE_DENS 32
#endif

using namespace std::chrono;
using timer = std::chrono::high_resolution_clock;

//! Performs random accesses on a vector and returns the sum of the accessed elements
/*!\param v     The container.
 *  \param rands Vector of locations which should be accessed. Length is a power of 2.
 *               Can be generated by method: util::rnd_positions<int_vector<64>>(log s, mask, v.size())
 *  \param mask  Mask which is used to perform the modulo s operation. See `rands`.
 *  \param times Number of iterations. If times > rands.size() array rands will be
 *               run through several times.
 */
template <class t_vec>
uint64_t test_random_access(t_vec const & v, int_vector<64> const & rands, uint64_t mask, uint64_t times = 100000000)
{
    uint64_t cnt = 0;
    for (uint64_t i = 0; i < times; ++i)
    {
        cnt += v[rands[i & mask]];
    }
    return cnt;
}

template <class t_vec>
uint64_t
test_inv_random_access(t_vec const & v, int_vector<64> const & rands, uint64_t mask, uint64_t times = 100000000)
{
    uint64_t cnt = 0;
    for (uint64_t i = 0; i < times; ++i)
    {
        cnt += v(rands[i & mask]);
    }
    return cnt;
}

int main(int argc, char * argv[])
{
    if (argc < 3)
    {
        cout << "Usage: " << argv[0] << " bit_vector_file" << endl;
        cout << " generates a rrr_vector<" << BLOCK_SIZE << "> with sample rate " << RANK_SAMPLE_DENS << endl;
        cout << " for the bitvector stored in bit_vector_file and run a benchmark" << endl;
        return 1;
    }

    typedef rrr_vector<BLOCK_SIZE, int_vector<>, RANK_SAMPLE_DENS> rrr_vec_type;
    typedef rrr_vec_type::select_1_type rrr_select_type;
    typedef rrr_vec_type::rank_1_type rrr_rank_type;
    bit_vector bv;
    if (load_from_file(bv, argv[1]))
    {
        cout << "# plain_size = " << size_in_bytes(bv) << endl;
        uint16_t k = atoi(argv[2]);
        auto start = timer::now();
        rrr_vec_type rrr_vector(bv);
        util::clear(bv);
        rrr_select_type rrr_sel(&rrr_vector);
        rrr_rank_type rrr_rank(&rrr_vector);
        auto stop = timer::now();
        cout << "# construct_time = " << duration_cast<milliseconds>(stop - start).count() << endl;
        rrr_vec_type::size_type args = rrr_rank(rrr_vector.size());
        cout << "# rrr_vector.size() = " << rrr_vector.size() << endl;
        cout << "# args = " << args << endl;
        cout << "# file_name = " << argv[1] << endl;
        cout << "# block_size = " << BLOCK_SIZE << endl;
        cout << "# sample_rate = " << k << endl;
        cout << "# rrr_size = " << size_in_bytes(rrr_vector) << endl;
        cout << "# bt_size = " << size_in_bytes(rrr_vector.bt) << endl;
        cout << "# btnr_size = " << size_in_bytes(rrr_vector.btnr) << endl;
        const uint64_t reps = 10000000;
        uint64_t mask = 0;
        uint64_t check = 0;
        int_vector<64> rands = util::rnd_positions<int_vector<64>>(20, mask, rrr_vector.size(), 17);
        start = timer::now();
        check = test_random_access(rrr_vector, rands, mask, reps);
        stop = timer::now();
        cout << "# access_time = " << duration_cast<nanoseconds>(stop - start).count() / (double)reps << endl;
        cout << "# access_check = " << check << endl;
        rands = util::rnd_positions<int_vector<64>>(20, mask, rrr_vector.size() + 1, 17);
        start = timer::now();
        check = test_inv_random_access(rrr_rank, rands, mask, reps);
        stop = timer::now();
        cout << "# rank_time = " << duration_cast<nanoseconds>(stop - start).count() / (double)reps << endl;
        cout << "# rank_check = " << check << endl;
        rands = util::rnd_positions<int_vector<64>>(20, mask, args, 17);
        for (uint64_t i = 0; i < rands.size(); ++i)
            rands[i] = rands[i] + 1;
        stop = timer::now();
        check = test_inv_random_access(rrr_sel, rands, mask, reps);
        stop = timer::now();
        cout << "# select_time = " << duration_cast<nanoseconds>(stop - start).count() / (double)reps << endl;
        cout << "# select_check = " << check << endl;
    }
}
