#include <iostream>
#include <fstream>
#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(const t_vec& v, const int_vector<64>& 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(const t_vec& v, const int_vector<64>& 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;
    }
}
