/*******************************************************************************
 * Copyright (C) 2016 Advanced Micro Devices, Inc. All rights reserved.
 ******************************************************************************/

#include <cmath>
#include <cstddef>
#include <functional>
#include <iostream>
#include <sstream>
#include <valarray>

#include "./rider.h"
#include "rocfft.h"
#include <boost/program_options.hpp>

#include "./misc.h"

namespace po = boost::program_options;

//	This is used with the program_options class so that the user can type an
//  integer on the command line
//	and we store into an enum varaible
template <class _Elem, class _Traits>
std::basic_istream<_Elem, _Traits>& operator>>(std::basic_istream<_Elem, _Traits>& stream,
                                               rocfft_array_type&                  atype)
{
    unsigned tmp;
    stream >> tmp;
    atype = rocfft_array_type(tmp);
    return stream;
}

// similarly for transform type
template <class _Elem, class _Traits>
std::basic_istream<_Elem, _Traits>& operator>>(std::basic_istream<_Elem, _Traits>& stream,
                                               rocfft_transform_type&              ttype)
{
    unsigned tmp;
    stream >> tmp;
    ttype = rocfft_transform_type(tmp);
    return stream;
}

template <typename T>
int transform(size_t*                 lengths,
              const size_t*           inStrides,
              const size_t*           outStrides,
              size_t                  batchSize,
              const size_t*           inOffset,
              const size_t*           outOffset,
              rocfft_array_type       inArrType,
              rocfft_array_type       outArrType,
              rocfft_result_placement place,
              rocfft_precision        precision,
              rocfft_transform_type   transformType,
              double                  scale,
              bool                    packed,
              int                     deviceId,
              int                     platformId,
              bool                    printInfo,
              unsigned                profile_count,
              const int               ntrial)
{
    //	Our command line does not specify what dimension FFT we wish to
    //    transform; we decode
    //	this from the lengths that the user specifies for X, Y, Z.  A length of
    //    one means that
    //	The user does not want that dimension.

    const size_t     max_dimensions = 3;
    size_t           strides[4];
    size_t           o_strides[4];
    size_t           fftVectorSize                   = 0;
    size_t           fftVectorSizePadded             = 0;
    size_t           fftBatchSize                    = 0;
    size_t           outfftVectorSize                = 0;
    size_t           outfftVectorSizePadded          = 0;
    size_t           outfftBatchSize                 = 0;
    size_t           size_of_input_buffers_in_bytes  = 0;
    size_t           size_of_output_buffers_in_bytes = 0;
    unsigned         number_of_output_buffers        = 0;
    unsigned         dim                             = 1;
    void*            input_device_buffers[2]         = {NULL, NULL};
    void*            output_device_buffers[2]        = {NULL, NULL};
    std::vector<int> device_id;

    if(lengths[1] > 1)
    {
        dim = 2;
    }
    if(lengths[2] > 1)
    {
        dim = 3;
    }

    strides[0] = inStrides[0];
    strides[1] = inStrides[1];
    strides[2] = inStrides[2];
    strides[3] = inStrides[3];

    o_strides[0] = outStrides[0];
    o_strides[1] = outStrides[1];
    o_strides[2] = outStrides[2];
    o_strides[3] = outStrides[3];

    fftVectorSize       = lengths[0] * lengths[1] * lengths[2];
    fftVectorSizePadded = strides[3];
    fftBatchSize        = fftVectorSizePadded * batchSize;

    size_t Nt = 1 + lengths[0] / 2;

    if(place == rocfft_placement_inplace)
    {
        outfftVectorSize       = fftVectorSize;
        outfftVectorSizePadded = fftVectorSizePadded;
        outfftBatchSize        = fftBatchSize;
    }
    else
    {
        outfftVectorSize       = fftVectorSize;
        outfftVectorSizePadded = o_strides[3];
        outfftBatchSize        = outfftVectorSizePadded * batchSize;
    }

    // Real to complex case
    if((inArrType == rocfft_array_type_real) || (outArrType == rocfft_array_type_real))
    {
        fftVectorSizePadded = strides[3];
        fftBatchSize        = fftVectorSizePadded * batchSize;

        outfftVectorSizePadded = o_strides[3];
        outfftBatchSize        = outfftVectorSizePadded * batchSize;

        fftVectorSize    = lengths[0] * lengths[1] * lengths[2];
        outfftVectorSize = fftVectorSize;
    }

    switch(outArrType)
    {
    case rocfft_array_type_complex_interleaved:
        number_of_output_buffers        = 1;
        size_of_output_buffers_in_bytes = outfftBatchSize * sizeof(std::complex<T>);
        break;
    case rocfft_array_type_complex_planar:
        number_of_output_buffers        = 2;
        size_of_output_buffers_in_bytes = outfftBatchSize * sizeof(T);
        break;
    case rocfft_array_type_hermitian_interleaved:
        number_of_output_buffers        = 1;
        size_of_output_buffers_in_bytes = outfftBatchSize * sizeof(std::complex<T>);
        break;
    case rocfft_array_type_hermitian_planar:
        number_of_output_buffers        = 2;
        size_of_output_buffers_in_bytes = outfftBatchSize * sizeof(T);
        break;
    case rocfft_array_type_real:
        number_of_output_buffers        = 1;
        size_of_output_buffers_in_bytes = outfftBatchSize * sizeof(T);
        break;
    default:
        throw std::runtime_error("Invalid input array format");
    }

    // Fill the input buffers
    switch(inArrType)
    {
    case rocfft_array_type_complex_interleaved:
    {
        size_of_input_buffers_in_bytes = fftBatchSize * sizeof(std::complex<T>);

        setupBuffers(device_id,
                     size_of_input_buffers_in_bytes,
                     1,
                     input_device_buffers,
                     size_of_output_buffers_in_bytes,
                     number_of_output_buffers,
                     output_device_buffers);

        std::vector<std::complex<T>> input(fftBatchSize);

        // set zero
        for(unsigned i = 0; i < fftBatchSize; ++i)
        {
            input[i] = 0;
        }

        // impulse test case
        for(size_t b = 0; b < batchSize; b++)
        {
            size_t p3 = b * strides[3];
            for(size_t k = 0; k < lengths[2]; k++)
            {
                size_t p2 = p3 + k * strides[2];
                for(size_t j = 0; j < lengths[1]; j++)
                {
                    size_t p1 = p2 + j * strides[1];
                    for(size_t i = 0; i < lengths[0]; i++)
                    {
                        size_t p0 = p1 + i * strides[0];
                        input[p0] = 1;
                    }
                }
            }
        }

        HIP_V_THROW(hipMemcpy(input_device_buffers[0],
                              &input[0],
                              size_of_input_buffers_in_bytes,
                              hipMemcpyHostToDevice),
                    "hipMemcpy failed");
    }
    break;
    case rocfft_array_type_complex_planar:
    {
        size_of_input_buffers_in_bytes = fftBatchSize * sizeof(T);

        setupBuffers(device_id,
                     size_of_input_buffers_in_bytes,
                     2,
                     input_device_buffers,
                     size_of_output_buffers_in_bytes,
                     number_of_output_buffers,
                     output_device_buffers);

        std::vector<T> real(fftBatchSize);
        std::vector<T> imag(fftBatchSize);

        // set zero
        for(unsigned i = 0; i < fftBatchSize; ++i)
        {
            real[i] = 0;
            imag[i] = 0;
        }

        // impulse test case
        for(size_t b = 0; b < batchSize; b++)
        {
            size_t p3 = b * strides[3];
            for(size_t k = 0; k < lengths[2]; k++)
            {
                size_t p2 = p3 + k * strides[2];
                for(size_t j = 0; j < lengths[1]; j++)
                {
                    size_t p1 = p2 + j * strides[1];
                    for(size_t i = 0; i < lengths[0]; i++)
                    {
                        size_t p0 = p1 + i * strides[0];
                        real[p0]  = 1;
                    }
                }
            }
        }

        HIP_V_THROW(hipMemcpy(input_device_buffers[0],
                              &real[0],
                              size_of_input_buffers_in_bytes,
                              hipMemcpyHostToDevice),
                    "hipMemcpy failed");
        HIP_V_THROW(hipMemcpy(input_device_buffers[1],
                              &imag[0],
                              size_of_input_buffers_in_bytes,
                              hipMemcpyHostToDevice),
                    "hipMemcpy failed");
    }
    break;
    case rocfft_array_type_hermitian_interleaved:
    {
        size_of_input_buffers_in_bytes = fftBatchSize * sizeof(std::complex<T>);

        setupBuffers(device_id,
                     size_of_input_buffers_in_bytes,
                     1,
                     input_device_buffers,
                     size_of_output_buffers_in_bytes,
                     number_of_output_buffers,
                     output_device_buffers);

        std::vector<std::complex<T>> input(fftBatchSize);

        // set zero
        for(unsigned i = 0; i < fftBatchSize; ++i)
        {
            input[i] = 0;
        }

        // impulse test case
        for(size_t b = 0; b < batchSize; b++)
        {
            size_t p3 = b * strides[3];
            input[p3] = static_cast<T>(outfftVectorSize);
        }

        HIP_V_THROW(hipMemcpy(input_device_buffers[0],
                              &input[0],
                              size_of_input_buffers_in_bytes,
                              hipMemcpyHostToDevice),
                    "hipMemcpy failed");
    }
    break;
    case rocfft_array_type_hermitian_planar:
    {
        size_of_input_buffers_in_bytes = fftBatchSize * sizeof(T);

        setupBuffers(device_id,
                     size_of_input_buffers_in_bytes,
                     2,
                     input_device_buffers,
                     size_of_output_buffers_in_bytes,
                     number_of_output_buffers,
                     output_device_buffers);

        std::vector<T> real(fftBatchSize);
        std::vector<T> imag(fftBatchSize);

        // set zero
        for(unsigned i = 0; i < fftBatchSize; ++i)
        {
            real[i] = 0;
            imag[i] = 0;
        }

        // impulse test case
        for(size_t b = 0; b < batchSize; b++)
        {
            size_t p3 = b * strides[3];
            real[p3]  = static_cast<T>(outfftVectorSize);
        }

        HIP_V_THROW(hipMemcpy(input_device_buffers[0],
                              &real[0],
                              size_of_input_buffers_in_bytes,
                              hipMemcpyHostToDevice),
                    "hipMemcpy failed");
        HIP_V_THROW(hipMemcpy(input_device_buffers[1],
                              &imag[0],
                              size_of_input_buffers_in_bytes,
                              hipMemcpyHostToDevice),
                    "hipMemcpy failed");
    }
    break;
    case rocfft_array_type_real:
    {
        size_of_input_buffers_in_bytes = fftBatchSize * sizeof(T);

        setupBuffers(device_id,
                     size_of_input_buffers_in_bytes,
                     1,
                     input_device_buffers,
                     size_of_output_buffers_in_bytes,
                     number_of_output_buffers,
                     output_device_buffers);

        std::vector<T> real(fftBatchSize);

        // set zero
        for(unsigned i = 0; i < fftBatchSize; ++i)
        {
            real[i] = 0;
        }

        // impulse test case
        for(size_t b = 0; b < batchSize; b++)
        {
            size_t p3 = b * strides[3];
            for(size_t k = 0; k < lengths[2]; k++)
            {
                size_t p2 = p3 + k * strides[2];
                for(size_t j = 0; j < lengths[1]; j++)
                {
                    size_t p1 = p2 + j * strides[1];
                    for(size_t i = 0; i < lengths[0]; i++)
                    {
                        size_t p0 = p1 + i * strides[0];
                        real[p0]  = 1;
                    }
                }
            }
        }

        HIP_V_THROW(hipMemcpy(input_device_buffers[0],
                              &real[0],
                              size_of_input_buffers_in_bytes,
                              hipMemcpyHostToDevice),
                    "hipMemcpy failed");
    }
    break;
    default:
    {
        throw std::runtime_error("Input layout format not yet supported");
    }
    break;
    }

    LIB_V_THROW(rocfft_setup(), " rocfft_setup failed");

    rocfft_plan             plan = NULL;
    rocfft_plan_description desc = NULL;
    rocfft_execution_info   info = NULL;

    if((place == rocfft_placement_inplace) && packed && (scale == 1.0) && (inOffset[0] == 0)
       && (inOffset[1] == 0) && (outOffset[0] == 0) && (outOffset[1] == 0))
    {
        LIB_V_THROW(rocfft_plan_create(
                        &plan, place, transformType, precision, dim, lengths, batchSize, NULL),
                    "rocfft_plan_create failed");
    }
    else
    {
        LIB_V_THROW(rocfft_plan_description_create(&desc), "rocfft_plan_description_create failed");

        LIB_V_THROW(rocfft_plan_description_set_data_layout(desc,
                                                            inArrType,
                                                            outArrType,
                                                            inOffset,
                                                            outOffset,
                                                            3,
                                                            strides,
                                                            strides[3],
                                                            3,
                                                            o_strides,
                                                            o_strides[3]),
                    "rocfft_plan_description_data_layout failed");
        /*
            if(scale != 1.0)
            {
                    if(precision == rocfft_precision_single)
                            LIB_V_THROW(
       rocfft_plan_description_set_scale_float( desc, (float)scale ),
       "rocfft_plan_description_set_scale_float failed" );
                    else
                            LIB_V_THROW(
       rocfft_plan_description_set_scale_double( desc, scale ),
       "rocfft_plan_description_set_scale_double failed" );
            }
    */
        LIB_V_THROW(rocfft_plan_create(
                        &plan, place, transformType, precision, dim, lengths, batchSize, desc),
                    "rocfft_plan_create failed");
    }

    // print
    LIB_V_THROW(rocfft_plan_get_print(plan), "rocfft_plan_get_print failed");

    // get the buffersize
    size_t workBufferSize = 0;
    LIB_V_THROW(rocfft_plan_get_work_buffer_size(plan, &workBufferSize),
                "rocfft_plan_get_work_buffer_size failed");

    // allocate the intermediate buffer
    void* workBuffer = NULL;

    if(workBufferSize)
    {
        HIP_V_THROW(hipMalloc(&workBuffer, workBufferSize), "Creating intmediate Buffer failed");
    }

    switch(inArrType)
    {
    case rocfft_array_type_complex_interleaved:
    case rocfft_array_type_complex_planar:
    case rocfft_array_type_hermitian_interleaved:
    case rocfft_array_type_hermitian_planar:
    case rocfft_array_type_real:
        break;
    default:
        //	Don't recognize input layout
        return rocfft_status_invalid_arg_value;
    }

    switch(outArrType)
    {
    case rocfft_array_type_complex_interleaved:
    case rocfft_array_type_complex_planar:
    case rocfft_array_type_hermitian_interleaved:
    case rocfft_array_type_hermitian_planar:
    case rocfft_array_type_real:
        break;
    default:
        //	Don't recognize output layout
        return rocfft_status_invalid_arg_value;
    }

    if((place == rocfft_placement_inplace) && (inArrType != outArrType))
    {
        switch(inArrType)
        {
        case rocfft_array_type_complex_interleaved:
        {
            if((outArrType == rocfft_array_type_complex_planar)
               || (outArrType == rocfft_array_type_hermitian_planar))
            {
                throw std::runtime_error("Cannot use the same buffer for "
                                         "interleaved->planar in-place transforms");
            }
            break;
        }
        case rocfft_array_type_complex_planar:
        {
            if((outArrType == rocfft_array_type_complex_interleaved)
               || (outArrType == rocfft_array_type_hermitian_interleaved))
            {
                throw std::runtime_error("Cannot use the same buffer for "
                                         "planar->interleaved in-place transforms");
            }
            break;
        }
        case rocfft_array_type_hermitian_interleaved:
        {
            if(outArrType != rocfft_array_type_real)
            {
                throw std::runtime_error("Cannot use the same buffer for "
                                         "interleaved->planar in-place transforms");
            }
            break;
        }
        case rocfft_array_type_hermitian_planar:
        {
            throw std::runtime_error("Cannot use the same buffer for "
                                     "planar->interleaved in-place transforms");
            break;
        }
        case rocfft_array_type_real:
        {
            if((outArrType == rocfft_array_type_complex_planar)
               || (outArrType == rocfft_array_type_hermitian_planar))
            {
                throw std::runtime_error("Cannot use the same buffer for "
                                         "interleaved->planar in-place transforms");
            }
            break;
        }
        default:
            throw std::runtime_error("Invalid input array format");
        }
    }

    void** BuffersOut = (place == rocfft_placement_inplace) ? NULL : &output_device_buffers[0];

    LIB_V_THROW(rocfft_execution_info_create(&info), "rocfft_execution_info_create failed");
    if(workBuffer != NULL)
        LIB_V_THROW(rocfft_execution_info_set_work_buffer(info, workBuffer, workBufferSize),
                    "rocfft_execution_info_set_work_buffer failed");

    // Execute once for basic functional test
    LIB_V_THROW(rocfft_execute(plan, input_device_buffers, BuffersOut, info),
                "rocfft_execute failed");

    HIP_V_THROW(hipDeviceSynchronize(), "hipDeviceSynchronize failed");

    std::vector<float>  gpu_time(ntrial);
    std::vector<double> wall_time(ntrial);

    if(profile_count > 1)
    {
        Timer tr;

        hipEvent_t start, stop;
        HIP_V_THROW(hipEventCreate(&start), "hipEventCreate failed");
        HIP_V_THROW(hipEventCreate(&stop), "hipEventCreate failed");

        for(int itrial = 0; itrial < ntrial; ++itrial)
        {
            tr.Start();
            HIP_V_THROW(hipEventRecord(start), "hipEventRecord failed");
            for(unsigned i = 0; i < profile_count; ++i)
            {
                LIB_V_THROW(rocfft_execute(plan, input_device_buffers, BuffersOut, info),
                            "rocfft_execute failed");
                // HIP_V_THROW( hipDeviceSynchronize(), "hipDeviceSynchronize failed" );
            }

            HIP_V_THROW(hipEventRecord(stop), "hipEventRecord failed");
            HIP_V_THROW(hipEventSynchronize(stop), "hipEventSynchronize failed");

            hipEventElapsedTime(&gpu_time[itrial], start, stop);
            gpu_time[itrial] /= (float)profile_count;

            wall_time[itrial] = tr.Sample() / ((double)profile_count);
        }

        HIP_V_THROW(hipDeviceSynchronize(), "hipDeviceSynchronize failed");

        size_t totalLen = 1;
        for(int i = 0; i < dim; i++)
            totalLen *= lengths[i];

        double constMultiplier = 1.0;
        if((inArrType == rocfft_array_type_real) || (outArrType == rocfft_array_type_real))
            constMultiplier = 2.5;
        else
            constMultiplier = 5.0;

        double opsconst = (double)batchSize * constMultiplier * (double)totalLen
                          * log((double)totalLen) / log(2.0);
        double bytes
            = (double)batchSize * 2.0 * (double)totalLen; // the scalar 2.0 is because read & write

        if((transformType == rocfft_transform_type_complex_forward)
           || (transformType == rocfft_transform_type_complex_inverse))
        {
            if(precision == rocfft_precision_single)
                bytes *= sizeof(float) * 2;
            else
                bytes *= sizeof(double) * 2;
        }
        else
        {
            if(precision == rocfft_precision_single)
                bytes *= sizeof(float);
            else
                bytes *= sizeof(double);
        }

        std::cout << "\nExecution gpu time:";
        for(const auto& i : gpu_time)
        {
            std::cout << " " << i;
        }
        std::cout << " ms" << std::endl;
        std::cout << "Execution wall time:";
        for(const auto& i : wall_time)
        {
            std::cout << " " << 1e3 * i;
        }
        std::cout << " ms" << std::endl;
        std::cout << "Execution gflops (wall time):";
        for(const auto& i : wall_time)
        {
            std::cout << " " << opsconst / (1e9 * i);
        }
        std::cout << std::endl;

        std::cout << "Bandwidth GB/s (wall time):";
        for(const auto& i : wall_time)
        {
            std::cout << " " << bytes / (1e9 * i);
        }
        std::cout << std::endl;
    }

    if(workBuffer)
        HIP_V_THROW(hipFree(workBuffer), "hipFree failed");

    if(desc != NULL)
        LIB_V_THROW(rocfft_plan_description_destroy(desc),
                    "rocfft_plan_description_destroy failed");

    if(info != NULL)
        LIB_V_THROW(rocfft_execution_info_destroy(info), "rocfft_execution_info_destroy failed");

    bool   checkflag = false;
    double err_ratio = 1E-6;

    // Read and check output data
    // This check is not valid if the FFT is executed multiple times inplace.
    //
    if((place == rocfft_placement_notinplace) || (profile_count == 1))
    {
        switch(outArrType)
        {
        case rocfft_array_type_hermitian_interleaved:
        case rocfft_array_type_complex_interleaved:
        {
            std::vector<std::complex<T>> output(outfftBatchSize);

            if(place == rocfft_placement_inplace)
            {
                HIP_V_THROW(hipMemcpy(&output[0],
                                      input_device_buffers[0],
                                      size_of_input_buffers_in_bytes,
                                      hipMemcpyDeviceToHost),
                            "hipMemcpy failed");
            }
            else
            {
                HIP_V_THROW(hipMemcpy(&output[0],
                                      BuffersOut[0],
                                      size_of_output_buffers_in_bytes,
                                      hipMemcpyDeviceToHost),
                            "hipMemcpy failed");
            }

            // check output data
            for(unsigned i = 0; i < outfftBatchSize; ++i)
            {
                if(0 == (i % outfftVectorSizePadded))
                {
                    if(fabs(output[i].real() - outfftVectorSize) / outfftVectorSize > err_ratio)
                    {
                        checkflag = true;
                        break;
                    }
                }
                else
                {
                    if(fabs(output[i].real()) > (err_ratio * outfftVectorSize))
                    {
                        checkflag = true;
                        break;
                    }
                }

                if(fabs(output[i].imag()) > (err_ratio * outfftVectorSize))
                {
                    checkflag = true;
                    break;
                }
            }
        }
        break;
        case rocfft_array_type_hermitian_planar:
        case rocfft_array_type_complex_planar:
        {
            std::valarray<T> real(outfftBatchSize);
            std::valarray<T> imag(outfftBatchSize);

            if(place == rocfft_placement_inplace)
            {
                HIP_V_THROW(hipMemcpy(&real[0],
                                      input_device_buffers[0],
                                      size_of_input_buffers_in_bytes,
                                      hipMemcpyDeviceToHost),
                            "hipMemcpy failed");
                HIP_V_THROW(hipMemcpy(&imag[0],
                                      input_device_buffers[1],
                                      size_of_input_buffers_in_bytes,
                                      hipMemcpyDeviceToHost),
                            "hipMemcpy failed");
            }
            else
            {
                HIP_V_THROW(hipMemcpy(&real[0],
                                      BuffersOut[0],
                                      size_of_output_buffers_in_bytes,
                                      hipMemcpyDeviceToHost),
                            "hipMemcpy failed");
                HIP_V_THROW(hipMemcpy(&imag[0],
                                      BuffersOut[1],
                                      size_of_output_buffers_in_bytes,
                                      hipMemcpyDeviceToHost),
                            "hipMemcpy failed");
            }

            //  Check output data
            for(unsigned i = 0; i < outfftBatchSize; ++i)
            {
                if(0 == (i % outfftVectorSizePadded))
                {
                    if(real[i] != outfftVectorSize)
                    {
                        checkflag = true;
                        break;
                    }
                }
                else
                {
                    if(real[i] != 0)
                    {
                        checkflag = true;
                        break;
                    }
                }

                if(imag[i] != 0)
                {
                    checkflag = true;
                    break;
                }
            }
        }
        break;
        case rocfft_array_type_real:
        {
            std::valarray<T> real(outfftBatchSize);

            if(place == rocfft_placement_inplace)
            {
                HIP_V_THROW(hipMemcpy(&real[0],
                                      input_device_buffers[0],
                                      size_of_input_buffers_in_bytes,
                                      hipMemcpyDeviceToHost),
                            "hipMemcpy failed");
            }
            else
            {
                HIP_V_THROW(hipMemcpy(&real[0],
                                      BuffersOut[0],
                                      size_of_output_buffers_in_bytes,
                                      hipMemcpyDeviceToHost),
                            "hipMemcpy failed");
            }

            ////check output data

            for(size_t b = 0; b < batchSize; b++)
            {
                size_t p3 = b * o_strides[3];
                for(size_t k = 0; k < lengths[2]; k++)
                {
                    size_t p2 = p3 + k * o_strides[2];
                    for(size_t j = 0; j < lengths[1]; j++)
                    {
                        size_t p1 = p2 + j * o_strides[1];
                        for(size_t i = 0; i < lengths[0]; i++)
                        {
                            size_t p0 = p1 + i * o_strides[0];

                            if(real[p0] != outfftVectorSize)
                            {
                                checkflag = true;
                                break;
                            }
                        }
                    }
                }
            }
        }
        break;
        default:
        {
            throw std::runtime_error("Input layout format not yet supported");
        }
        break;
        }

        if(checkflag)
        {
            std::cout << "\n\n\t\tRider Test *****FAIL*****" << std::endl;
        }
        else
        {
            std::cout << "\n\n\t\tRider Test *****PASS*****" << std::endl;
        }
    }

    LIB_V_THROW(rocfft_plan_destroy(plan), "rocfft_plan_destroy failed");
    LIB_V_THROW(rocfft_cleanup(), "rocfft_cleanup failed");

    clearBuffers(input_device_buffers, output_device_buffers);

    if(checkflag)
        return -1;

    return 0;
}

int main(int argc, char* argv[])
{
    //	This helps with mixing output of both wide and narrow characters to the
    //    screen
    std::ios::sync_with_stdio(false);

    int deviceId   = 0;
    int platformId = 0;

    //	FFT state

    rocfft_result_placement place         = rocfft_placement_inplace;
    rocfft_transform_type   transformType = rocfft_transform_type_complex_forward;
    rocfft_array_type       inArrType     = rocfft_array_type_complex_interleaved;
    rocfft_array_type       outArrType    = rocfft_array_type_complex_interleaved;
    rocfft_precision        precision     = rocfft_precision_single;

    size_t   lengths[3]    = {1, 1, 1};
    size_t   iStrides[4]   = {0, 0, 0, 0};
    size_t   oStrides[4]   = {0, 0, 0, 0};
    unsigned profile_count = 0;

    size_t batchSize  = 1;
    double scale      = 1.0;
    size_t iOffset[2] = {0, 0};
    size_t oOffset[2] = {0, 0};

    int tret = 0;

    // Number of trials
    int ntrial = 1;

    try
    {
        // clang-format off
        // clang-format doesn't handle boost program options very well

        // Declare the supported options.
        po::options_description desc("rocfft rider command line options");
        desc.add_options()("help,h", "produces this help message")
            ("version,v", "Print queryable version information from the rocfft library")
            ("info,i", "Print queryable information of all the runtimes and devices")
            ("printChosen", "Print queryable information of the selected runtime and device")
            ("platform", po::value<int>(&platformId)->default_value(0),
             "Select a specific platform id as it is reported by info")
            ("device", po::value<int>(&deviceId)->default_value(0),
             "Select a specific device id as it is reported by info")
            ("ntrial,N", po::value<int>(&ntrial)->default_value(1),
             "Trial size for the problem")
            ("notInPlace,o", "Not in-place FFT transform (default: in-place)")
            ("double", "Double precision transform (default: single)")
            ("transformType,t", po::value<rocfft_transform_type>(&transformType)
             ->default_value(rocfft_transform_type_complex_forward),
             "Type of transform:\n0) complex forward\n1) complex inverse\n2) real "
             "forward\n3) real inverse")
            ("lenX,x", po::value<size_t>(&lengths[0])->default_value(1024),
             "Specify the length of the 1st dimension of a test array")
            ("lenY,y", po::value<size_t>(&lengths[1])->default_value(1),
             "Specify the length of the 2nd dimension of a test array")
            ("lenZ,z", po::value<size_t>(&lengths[2])->default_value(1),
             "Specify the length of the 3rd dimension of a test array")
            ("isX", po::value<size_t>(&iStrides[0])->default_value(1),
             "Specify the input stride of the 1st dimension of a test array")
            ("isY", po::value<size_t>(&iStrides[1])->default_value(0),
             "Specify the input stride of the 2nd dimension of a test array")
            ( "isZ", po::value<size_t>(&iStrides[2])->default_value(0),
              "Specify the input stride of the 3rd dimension of a test array")
            ( "iD", po::value<size_t>(&iStrides[3])->default_value(0),
              "input distance between successive members when batch size > 1")
            ( "osX", po::value<size_t>(&oStrides[0])->default_value(1),
              "Specify the output stride of the 1st dimension of a test array")
            ( "osY", po::value<size_t>(&oStrides[1])->default_value(0),
              "Specify the output stride of the 2nd dimension of a test array")
            ( "osZ", po::value<size_t>(&oStrides[2])->default_value(0),
              "Specify the output stride of the 3rd dimension of a test array")
            ( "oD", po::value<size_t>(&oStrides[3])->default_value(0),
              "output distance between successive members when batch size > 1")
            ("scale", po::value<double>(&scale)->default_value(1.0), "Specify the scaling factor ")
            ( "iOff0", po::value<size_t>(&iOffset[0])->default_value(0),
              "Specify the offset for first/only input buffer")
            ( "iOff1", po::value<size_t>(&iOffset[1])->default_value(0),
              "Specify the offset for second input buffer")
            ( "oOff0", po::value<size_t>(&oOffset[0])->default_value(0),
              "Specify the offset for first/only output buffer")
            ( "oOff1", po::value<size_t>(&oOffset[1])->default_value(0),
              "Specify the offset for second output buffer")
            ( "batchSize,b", po::value<size_t>(&batchSize)->default_value(1),
              "If this value is greater than one, arrays will be used ")
            ( "profile,p", po::value<unsigned>(&profile_count)->default_value(1),
              "Time and report the kernel speed of the FFT (default: profiling off)")
            ( "inArrType", po::value<rocfft_array_type>(&inArrType)
              ->default_value(rocfft_array_type_complex_interleaved),
              "Array type of input data:\n0) interleaved\n1) planar\n2) real\n3) "
              "hermitian interleaved\n4) hermitian planar")
            ( "outArrType", po::value<rocfft_array_type>(&outArrType)
              ->default_value(rocfft_array_type_complex_interleaved),
              "Array type of output data:\n0) interleaved\n1) planar\n2) real\n3) "
              "hermitian interleaved\n4) hermitian planar");

        // clang-format on

        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);
        po::notify(vm);

        if(vm.count("version"))
        {
            char v[256];
            rocfft_get_version_string(v, 256);
            std::cout << "version " << v << std::endl;
            return 0;
        }

        if(vm.count("help"))
        {
            //	This needs to be 'cout' as program-options does not support
            //    wcout yet
            std::cout << desc << std::endl;
            return 0;
        }

        if(vm.count("info"))
        {
            return 0;
        }

        bool printInfo = false;
        if(vm.count("printChosen"))
        {
            printInfo = true;
        }

        if(vm.count("notInPlace"))
        {
            place = rocfft_placement_notinplace;
        }

        if(vm.count("double"))
        {
            precision = rocfft_precision_double;
        }

        if(profile_count > 1)
        {
        }

        if(transformType == rocfft_transform_type_real_forward)
            if((inArrType == rocfft_array_type_complex_interleaved)
               && (outArrType == rocfft_array_type_complex_interleaved))
            {
                inArrType  = rocfft_array_type_real;
                outArrType = rocfft_array_type_hermitian_interleaved;
            }

        if(transformType == rocfft_transform_type_real_inverse)
            if((inArrType == rocfft_array_type_complex_interleaved)
               && (outArrType == rocfft_array_type_complex_interleaved))
            {
                inArrType  = rocfft_array_type_hermitian_interleaved;
                outArrType = rocfft_array_type_real;
            }

        int inL = (int)inArrType;
        int otL = (int)outArrType;

        // input output array type support matrix
        int ioArrTypeSupport[5][5] = {
            {1, 1, 0, 0, 0},
            {1, 1, 0, 0, 0},
            {0, 0, 0, 1, 1},
            {0, 0, 1, 0, 0},
            {0, 0, 1, 0, 0},
        };

        if(inL > 4)
            throw std::runtime_error("Invalid Input array type format");
        if(otL > 4)
            throw std::runtime_error("Invalid Output array type format");

        if(ioArrTypeSupport[inL][otL] == 0)
            throw std::runtime_error("Invalid combination of Input/Output array type formats");

        bool packed = false;
        if((iStrides[0] == 1) && (iStrides[1] == 0) && (iStrides[2] == 0) && (iStrides[3] == 0)
           && (oStrides[0] == 1) && (oStrides[1] == 0) && (oStrides[2] == 0) && (oStrides[3] == 0))
            packed = true;

        if((transformType == rocfft_transform_type_complex_forward)
           || (transformType == rocfft_transform_type_complex_inverse)) // Complex-Complex cases
        {
            iStrides[1] = iStrides[1] ? iStrides[1] : lengths[0] * iStrides[0];
            iStrides[2] = iStrides[2] ? iStrides[2] : lengths[1] * iStrides[1];
            iStrides[3] = iStrides[3] ? iStrides[3] : lengths[2] * iStrides[2];

            if(place == rocfft_placement_inplace)
            {
                oStrides[0] = iStrides[0];
                oStrides[1] = iStrides[1];
                oStrides[2] = iStrides[2];
                oStrides[3] = iStrides[3];
            }
            else
            {
                oStrides[1] = oStrides[1] ? oStrides[1] : lengths[0] * oStrides[0];
                oStrides[2] = oStrides[2] ? oStrides[2] : lengths[1] * oStrides[1];
                oStrides[3] = oStrides[3] ? oStrides[3] : lengths[2] * oStrides[2];
            }
        }
        else // Real cases
        {
            size_t *rst, *cst;
            size_t  N      = lengths[0];
            size_t  Nt     = 1 + lengths[0] / 2;
            bool    iflag  = false;
            bool    rcFull = (inL == 0) || (inL == 1) || (otL == 0) || (otL == 1);

            if(inArrType == rocfft_array_type_real)
            {
                iflag = true;
                rst   = iStrides;
            }
            else
            {
                rst = oStrides;
            } // either in or out should be REAL

            // Set either in or out strides whichever is real
            if(place == rocfft_placement_inplace)
            {
                if(rcFull)
                {
                    rst[1] = rst[1] ? rst[1] : N * 2 * rst[0];
                }
                else
                {
                    rst[1] = rst[1] ? rst[1] : Nt * 2 * rst[0];
                }

                rst[2] = rst[2] ? rst[2] : lengths[1] * rst[1];
                rst[3] = rst[3] ? rst[3] : lengths[2] * rst[2];
            }
            else
            {
                rst[1] = rst[1] ? rst[1] : lengths[0] * rst[0];
                rst[2] = rst[2] ? rst[2] : lengths[1] * rst[1];
                rst[3] = rst[3] ? rst[3] : lengths[2] * rst[2];
            }

            // Set the remaining of in or out strides that is not real
            if(iflag)
            {
                cst = oStrides;
            }
            else
            {
                cst = iStrides;
            }

            if(rcFull)
            {
                cst[1] = cst[1] ? cst[1] : N * cst[0];
            }
            else
            {
                cst[1] = cst[1] ? cst[1] : Nt * cst[0];
            }

            cst[2] = cst[2] ? cst[2] : lengths[1] * cst[1];
            cst[3] = cst[3] ? cst[3] : lengths[2] * cst[2];
        }

        if(precision == rocfft_precision_single)
        {
            tret = transform<float>(lengths,
                                    iStrides,
                                    oStrides,
                                    batchSize,
                                    iOffset,
                                    oOffset,
                                    inArrType,
                                    outArrType,
                                    place,
                                    precision,
                                    transformType,
                                    scale,
                                    packed,
                                    deviceId,
                                    platformId,
                                    printInfo,
                                    profile_count,
                                    ntrial);
        }
        else
        {
            tret = transform<double>(lengths,
                                     iStrides,
                                     oStrides,
                                     batchSize,
                                     iOffset,
                                     oOffset,
                                     inArrType,
                                     outArrType,
                                     place,
                                     precision,
                                     transformType,
                                     scale,
                                     packed,
                                     deviceId,
                                     platformId,
                                     printInfo,
                                     profile_count,
                                     ntrial);
        }
    }
    catch(std::exception& e)
    {
        std::cerr << "rocfft error condition reported:" << std::endl << e.what() << std::endl;
        return 1;
    }

    return tret;
}


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#include <nt2/include/functions/polyval.hpp>
#include <nt2/include/functions/polyfit.hpp>
#include <nt2/include/functions/complexify.hpp>
#include <boost/type_traits/is_same.hpp>
#include <nt2/sdk/functor/meta/call.hpp>
#include <nt2/sdk/unit/tests/relation.hpp>
#include <nt2/sdk/unit/tests/ulp.hpp>
#include <nt2/sdk/unit/module.hpp>
#include <nt2/table.hpp>
#include <boost/array.hpp>

NT2_TEST_CASE_TPL ( polyva,  NT2_REAL_TYPES)
{
  typedef std::complex<T> cT;
  using nt2::polyval;
  using nt2::tag::polyval_;
  nt2::table<cT> x =  nt2::complexify(nt2::_(T(1), T(4)));
  nt2::table<cT> p =  nt2::complexify(nt2::_(T(1), T(4)));
  NT2_TEST_EQUAL(polyval(p, cT(1)), cT(10));
  NT2_TEST_EQUAL(polyval(p, cT(2)), cT(26));
  T cv[] = { 10, 26, 58, 112 };
  nt2::table<T> v(nt2::of_size(1, 4), &cv[0], &cv[4]);
  nt2::table<cT> vv =  nt2::complexify(v);
  NT2_TEST_ULP_EQUAL(vv, polyval(p,x), 0.5);
}

NT2_TEST_CASE_TPL ( polyval2,  NT2_REAL_TYPES)
{
  typedef std::complex<T> cT;
  using nt2::polyfit;
  using nt2::tag::polyfit_;
  nt2::table<cT> x =  nt2::complexify(nt2::reshape(nt2::_(T(1), T(4)), nt2::of_size(2, 2)));
  nt2::table<cT> p =  nt2::complexify(nt2::_(T(1), T(3)));
  nt2::table<cT> y =  nt2::polyval(p, x);
  y(nt2::end_) = cT(26);
  nt2::table<cT> r;
  size_t df;
  T normr;
  nt2::table<cT> mu;
  nt2::tie(p, r, df, normr, mu) = polyfit(x, y, 2);
  std::cout << 2 << std::endl;
  nt2::table<cT> delta;
  nt2::table<cT> xx =  nt2::complexify(nt2::reshape(nt2::_(T(1), T(0.5), T(4.5)), 2, 4));
  nt2::table<cT> yy(nt2::of_size(2, 2)); yy(1) = cT(6); yy(2) = cT(11);yy(3) = cT(18);yy(4) = cT(26);
  NT2_TEST_ULP_EQUAL(y, yy, 1.0);
  tie(y, delta) = polyval(p, xx, r, df, normr);
}


//==============================================================================
//         Copyright 2003 - 2013   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2013   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#include <nt2/include/functions/min.hpp>

#include <boost/dispatch/functor/meta/call.hpp>
#include <nt2/sdk/functor/meta/call.hpp>
#include <nt2/sdk/unit/tests/relation.hpp>
#include <nt2/sdk/unit/tests/type_expr.hpp>
#include <complex>
#include <nt2/sdk/complex/meta/as_dry.hpp>
#include <nt2/sdk/complex/complex.hpp>
#include <nt2/sdk/unit/tests/ulp.hpp>
#include <nt2/sdk/unit/tests/basic.hpp>
#include <nt2/sdk/meta/as_integer.hpp>
#include <nt2/sdk/unit/module.hpp>
#include <boost/simd/sdk/config.hpp>

#include <nt2/include/constants/one.hpp>
#include <nt2/include/constants/zero.hpp>
#include <nt2/include/constants/inf.hpp>

NT2_TEST_CASE_TPL ( min_real,  BOOST_SIMD_REAL_TYPES)
{
  using nt2::min;
  using nt2::tag::min_;
  typedef typename std::complex<T> cT;
  typedef typename nt2::meta::as_dry<T>::type dT;
  typedef typename boost::dispatch::meta::call<min_(cT, cT)>::type r_t;

  // return type conformity test
  NT2_TEST_TYPE_IS(r_t, cT);

  // specific values tests
  NT2_TEST_ULP_EQUAL(nt2::min(cT(nt2::Inf<T>()), cT(nt2::Inf<T>())), cT(nt2::Inf<T>()), 0);
  NT2_TEST_EQUAL(nt2::min(cT(nt2::One<T>()), cT(nt2::Zero<T>())), cT(nt2::Zero<T>()));
  NT2_TEST_EQUAL(nt2::min(cT(nt2::Zero<T>()), cT(nt2::Zero<T>())),cT(nt2::Zero<T>()));
  NT2_TEST_EQUAL(nt2::min(cT(0, 1), cT(1, 0)), cT(1, 0));
  NT2_TEST_EQUAL(nt2::min(cT(2, 1), cT(2, 2)), cT(2, 1));
  NT2_TEST_EQUAL(nt2::min(dT(1),dT(-1)), dT(1));
  NT2_TEST_EQUAL(nt2::min(dT(1),T(-1)), dT(1));
  NT2_TEST_EQUAL(nt2::min(T(1),dT(-1)), dT(1));
}


#include <shapes/Area.hpp>
#include <shapes/Circumference.hpp>

#include <visitor/DynamicVisitor.hpp>
#include <visitor/Visitable.hpp>

#include <boost/dll.hpp>
#include <boost/function.hpp>

#include <functional>
#include <iostream>
#include <vector>

struct NonShape
{
};

using ShapeVisitor         = DynamicVisitor<float( float )>;
using VisitableShapePtr    = VisitableUPtr<ShapeVisitor>;
using VisitableShapePtrVec = std::vector<VisitableShapePtr>;

ShapeVisitor makeAreaVisitor();
ShapeVisitor makeCircumferenceVisitor();
void         loadPlugin( char const * const     pluginFilename,
                         VisitableShapePtrVec & shapes,
                         ShapeVisitor &         areaVisitor,
                         ShapeVisitor &         circumferenceVisitor );

boost::dll::shared_library sharedLib;

int
main( int argc, char ** argv )
{
  std::cout << "Running Generic DynamicVisitor with Plugin Example\n\n";

  Rectangle rectangle { 2.0f, 3.0f };
  Circle    circle { 2.0f };
  NonShape  nonShape;
  float     scale = 2.0f;

  // Populate heterogeneous list of concrete Shape objects
  VisitableShapePtrVec shapes;
  shapes.push_back( makeUniqueVisitable<ShapeVisitor>( rectangle ) );
  shapes.push_back( makeUniqueVisitable<ShapeVisitor>( circle ) );
  // Note: Now we CAN insert a NonShape into the list of shapes. But if no
  // handler is registered for it, it will result in a runtime error/warning.
  shapes.push_back( makeUniqueVisitable<ShapeVisitor>( nonShape ) );

  ShapeVisitor areaVisitor          = makeAreaVisitor();
  ShapeVisitor circumferenceVisitor = makeCircumferenceVisitor();

  if ( argc >= 2 ) {
    loadPlugin( argv[1], shapes, areaVisitor, circumferenceVisitor );
  }

  // ==========================================================================
  std::cout << "Area Visitor:\n";

  // Visit the Shapes with the Area Visitor - checking valid accept result
  float totalArea = 0.0f;
  for ( auto const & shape : shapes ) {
    auto result = shape->accept( areaVisitor, scale );
    if ( result ) {
      std::cout << "  - Area for shape [" << shape->typeInfo().name()
                << "]: " << *result << '\n';
      totalArea += *result;
    }
    else {
      std::cerr << "WARNING: Unrecognized type: " << shape->typeInfo().name()
                << '\n';
    }
  }
  float expectedArea =
      scale * area( rectangle ) + scale * area( circle ) +
      scale * ( ( 3.0f * 4.0f ) / 2.0f ); // right triangle 3,4,5
  std::cout << "  Visited Area: " << totalArea << '\n'
            << "  Expected Area: " << expectedArea << '\n';

  // ==========================================================================
  std::cout << "\nCircumference Visitor:\n";

  // Visit the Shapes with the Circumference Visitor - checking valid result
  float totalCircumference = 0.0f;
  for ( auto const & shape : shapes ) {
    auto result = shape->accept( circumferenceVisitor, scale );
    if ( result ) {
      std::cout << "  - Circumference for shape [" << shape->typeInfo().name()
                << "]: " << *result << '\n';
      totalCircumference += *result;
    }
    else {
      std::cerr << "WARNING: Unrecognized type: " << shape->typeInfo().name()
                << '\n';
    }
  }
  float expectedCircumference =
      scale * circumference( rectangle ) + scale * circumference( circle ) +
      scale * ( 3.0f + 4.0f + 5.0f ); // Triangle{3,4,5}
  std::cout << "  Visited Circumference: " << totalCircumference << '\n'
            << "  Expected Circumference: " << expectedCircumference << '\n';
}

ShapeVisitor
makeAreaVisitor()
{
  // Add the area functions as handlers to the DynamicVisitor
  ShapeVisitor areaVisitor;
  areaVisitor.addHandler<Rectangle>(
      []( Rectangle const & r, float s ) -> float { return s * area( r ); } );
  areaVisitor.addHandler<Circle>(
      []( Circle const & c, float s ) -> float { return s * area( c ); } );

  return areaVisitor;
} // makeAreaVisitor

ShapeVisitor
makeCircumferenceVisitor()
{
  // Add the circumference functions as handlers to the DynamicVisitor
  ShapeVisitor circumferenceVisitor;
  circumferenceVisitor.addHandler<Rectangle>(
      []( Rectangle const & r, float s ) -> float {
        return s * circumference( r );
      } );
  circumferenceVisitor.addHandler<Circle>(
      []( Circle const & c, float s ) -> float {
        return s * circumference( c );
      } );

  return circumferenceVisitor;
} // makeCircumferenceVisitor

void
loadPlugin( char const * const     pluginFilename,
            VisitableShapePtrVec & shapes,
            ShapeVisitor &         areaVisitor,
            ShapeVisitor &         circumferenceVisitor )
{
  try {
    std::cout << "Attempting to load Shape plugin from: " << pluginFilename
              << '\n';
    sharedLib.load( pluginFilename );

    using ShapeSig   = void( VisitableShapePtrVec * );
    using VisitorSig = void( ShapeVisitor * );

    // Request plugin to fill the Shapes vector
    boost::function<ShapeSig> fillShapes =
        sharedLib.get<ShapeSig>( "fillShapes" );
    if ( fillShapes ) {
      fillShapes( &shapes );
    }

    // Request plugin to add handlers to the Area Visitor
    boost::function<VisitorSig> fillAreaVisitor =
        sharedLib.get<VisitorSig>( "fillAreaVisitor" );
    if ( fillAreaVisitor ) {
      fillAreaVisitor( &areaVisitor );
    }

    // Request plugin to add handlers to the Circumference Visitor
    boost::function<VisitorSig> fillCircumferenceVisitor =
        sharedLib.get<VisitorSig>( "fillCircumferenceVisitor" );
    if ( fillCircumferenceVisitor ) {
      fillCircumferenceVisitor( &circumferenceVisitor );
    }
  }
  catch ( std::exception & e ) {
    std::cerr << "EXCEPTION: " << e.what() << '\n';
  }
} // loadPlugin


// Re-used parts from BOOST sorting benchmark suite

#include <algorithm>
#include <iostream>
#include <iomanip>
#include <random>
#include <stdlib.h>
#include <vector>

#include <boost/sort/common/time_measure.hpp>
#include <boost/sort/common/file_vector.hpp>
#include "boost/sort/common/int_array.hpp"

#include <boost/sort/sort.hpp>

#ifndef NO_LLVM
extern "C" {
  #include "introsort.h"
}
#endif

using namespace std;
namespace bsort = boost::sort;
namespace bsc = boost::sort::common;

using bsc::time_point;
using bsc::now;
using bsc::subtract_time;
using bsc::fill_vector_uint64;
using bsc::write_file_uint64;

template<typename A> double time_op(A op) {
  auto start = now ();
  op();
  auto finish = now ();
  return subtract_time (finish, start);
}

template<typename A> void time_op(std::string name, A op) {
  auto start = now ();
  op();
  auto finish = now ();
  auto duration = subtract_time (finish, start);

  std::cout<<"  "<<name<<"."<<"time: "<<duration<<std::endl;
}

template<class IA, class compare>
void test_sorted(std::vector<IA> &A, compare comp)
{
  if (!std::is_sorted(A.begin (), A.end (), comp)) {
    std::cout<<"*** NOT SORTED *** "<<std::endl;
  }
}

template<class IA, class compare, class Op>
void sort_test(std::vector<IA> &A, std::vector<IA> &B, size_t NITER, compare comp, Op op, bool verify=true) {
//   std::cout<<name<<std::endl;

  vector<double> durations;

  for (size_t i=0;i<NITER;++i) {
    A=B;

    durations.push_back(time_op(op) * 1000.0);
    if (verify) test_sorted(A,comp);
  }

  std::sort(durations.begin(),durations.end());

  auto N=durations.size();
//   auto l=durations.begin()+N/10;
//   auto h=durations.end()-N/10;
  auto l=durations.begin();
  auto h=durations.end();

  N=h-l;

  double duration=std::accumulate(l,h,0.0) / (double)N;
  double min=*std::min_element(l,h);
  double max=*std::max_element(l,h);
//   double dev=max-min;

  std::cout<<": "<<std::setw(2)<<duration<<" ["<<min<<" .. "<<max<<"] ";

  for (auto i=l;i!=h;++i)
      std::cout<<std::setw(2)<<*i<<" ";

  std::cout<<std::endl;
}

class Int_Generator {
public:

  static vector<uint64_t> random(size_t NELEM) {
    vector<uint64_t> A;
    A.reserve (NELEM);
    A.clear ();
    if (fill_vector_uint64 ("input.bin", A, NELEM) != 0)
    {
        std::cout << "Error in the input file\n";
        exit(1);
    };
    return A;
  }

  static vector<uint64_t> random_dup (size_t NELEM, size_t nvals)
  {
    vector<uint64_t> A;
    A.reserve (NELEM);
    A.clear ();
    if (fill_vector_uint64 ("input.bin", A, NELEM) != 0)
    {
        std::cout << "Error in the input file\n";
        exit(1);
    };

    for (size_t i=0;i<A.size();++i) A[i]%=nvals;

    return A;
  }

  static vector<uint64_t> sorted (size_t NELEM)
  {
    vector<uint64_t> A;

    A.reserve (NELEM);
    A.clear ();
    for (size_t i = 0; i < NELEM; ++i)
        A.push_back (i);

    return A;
  }

  static vector<uint64_t> organ_pipe (size_t NELEM)
  {
    vector<uint64_t> A;

    A.reserve (NELEM);
    A.clear ();

    size_t n = NELEM/2;
    uint64_t v=0;

    for (size_t i=0;i<n;++i) A.push_back(v++);
    for (size_t i=n;i<2*n;++i) A.push_back(--v);
    return A;
  }

  static vector<uint64_t> almost_sorted (size_t NELEM, size_t nperm)
  {
    vector<uint64_t> A;

    A.reserve (NELEM);
    A.clear ();
    for (size_t i = 0; i < NELEM; ++i)
        A.push_back (i);

    vector<size_t> P;

    if (fill_vector_uint64 ("input.bin", P, 2*nperm) != 0)
    {
        std::cout << "Error in the input file\n";
        exit(1);
    };

    for (size_t i=0;i<P.size();++i) P[i]%=NELEM;

    for (size_t i=0;i+1<P.size();i+=2) {
      std::swap(A[P[i]],A[P[i+1]]);
    }

    return A;
  }

  static vector<uint64_t> alleq (size_t NELEM)
  {
    vector<uint64_t> A(NELEM,42);
    return A;
  }

  static vector<uint64_t> sorted_end (size_t NELEM,size_t n_last)
  {
    vector<uint64_t> A;
    A.reserve (NELEM);
    A.clear ();
    if (fill_vector_uint64 ("input.bin", A, NELEM + n_last) != 0)
    {
        std::cout << "Error in the input file\n";
        exit(1);
    };
    std::sort (A.begin (), A.begin () + NELEM);

    return A;
  }

  static vector<uint64_t> sorted_middle (size_t NELEM, size_t n_last)
  {
    vector<uint64_t> A, B, C;
    A.reserve (NELEM);
    A.clear ();
    if (fill_vector_uint64 ("input.bin", A, NELEM + n_last) != 0)
    {
        std::cout << "Error in the input file\n";
        exit(1);
    };
    for (size_t i = NELEM; i < A.size (); ++i)
        B.push_back (std::move (A[i]));
    A.resize ( NELEM);
    for (size_t i = 0; i < (NELEM >> 1); ++i)
        std::swap (A[i], A[NELEM - 1 - i]);

    std::sort (A.begin (), A.end ());
    size_t step = NELEM / n_last + 1;
    size_t pos = 0;

    for (size_t i = 0; i < B.size (); ++i, pos += step)
    {
        C.push_back (B[i]);
        for (size_t k = 0; k < step; ++k)
            C.push_back (A[pos + k]);
    };
    while (pos < A.size ())
        C.push_back (A[pos++]);
    A = C;
    return A;
  }

  static vector<uint64_t> reverse_sorted (size_t NELEM)
  {
    vector<uint64_t> A;

    A.reserve (NELEM);
    A.clear ();
    for (size_t i = NELEM; i > 0; --i)
        A.push_back (i);

    return A;
  }

  static vector<uint64_t> reverse_sorted_end (size_t NELEM, size_t n_last)
  {
    vector<uint64_t> A;
    A.reserve (NELEM);
    A.clear ();
    if (fill_vector_uint64 ("input.bin", A, NELEM + n_last) != 0)
    {
        std::cout << "Error in the input file\n";
        exit(1);
    };
    std::sort (A.begin (), A.begin () + NELEM);
    for (size_t i = 0; i < (NELEM >> 1); ++i)
        std::swap (A[i], A[NELEM - 1 - i]);

    return A;
  }

  static vector<uint64_t> reverse_sorted_middle (size_t NELEM, size_t n_last)
  {
    vector<uint64_t> A, B, C;
    A.reserve (NELEM);
    A.clear ();
    if (fill_vector_uint64 ("input.bin", A, NELEM + n_last) != 0)
    {
        std::cout << "Error in the input file\n";
        exit(1);
    };
    for (size_t i = NELEM; i < A.size (); ++i)
        B.push_back (std::move (A[i]));
    A.resize ( NELEM);
    for (size_t i = 0; i < (NELEM >> 1); ++i)
        std::swap (A[i], A[NELEM - 1 - i]);

    std::sort (A.begin (), A.end ());
    size_t step = NELEM / n_last + 1;
    size_t pos = 0;

    for (size_t i = 0; i < B.size (); ++i, pos += step)
    {
        C.push_back (B[i]);
        for (size_t k = 0; k < step; ++k)
            C.push_back (A[pos + k]);
    };
    while (pos < A.size ())
        C.push_back (A[pos++]);
    A = C;
    return A;
  };

  static void stats(vector<uint64_t> &A) {
    auto B=A;

    // Count different values, min,max,avg length of strings
    std::sort(B.begin(),B.end());
    auto i = std::unique(B.begin(),B.end());
    size_t n = i-B.begin();

    cout<<"# "<<A.size()<<" numbers "<<n<<" different"<<endl;
  }

public:
  static vector<uint64_t> generate_aux(size_t NELEM, string name) {
    if (name=="random") return random(NELEM);
    else if (name=="random-dup-10") return random_dup(NELEM,NELEM/10);
    else if (name=="random-boolean") return random_dup(NELEM,2);
    else if (name=="organ-pipe") return organ_pipe(NELEM);
    else if (name=="sorted") return sorted(NELEM);
    else if (name=="equal") return alleq(NELEM);
    else if (name=="almost-sorted-.1") return almost_sorted(NELEM,NELEM/1000);
    else if (name=="almost-sorted-1") return almost_sorted(NELEM,NELEM/100);
    else if (name=="almost-sorted-10") return almost_sorted(NELEM,NELEM/10);
    else if (name=="almost-sorted-50") return almost_sorted(NELEM,NELEM/2);
    else if (name=="sorted-end-.1") return sorted_end(NELEM,NELEM/1000);
    else if (name=="sorted-end-1") return sorted_end(NELEM,NELEM/100);
    else if (name=="sorted-end-10") return sorted_end(NELEM,NELEM/10);
    else if (name=="sorted-middle-.1") return sorted_middle(NELEM,NELEM/1000);
    else if (name=="sorted-middle-1") return sorted_middle(NELEM,NELEM/100);
    else if (name=="sorted-middle-10") return sorted_middle(NELEM,NELEM/10);
    else if (name=="rev-sorted") return reverse_sorted(NELEM);
    else if (name=="rev-sorted-end-.1") return reverse_sorted_end(NELEM,NELEM/1000);
    else if (name=="rev-sorted-end-1") return reverse_sorted_end(NELEM,NELEM/100);
    else if (name=="rev-sorted-end-10") return reverse_sorted_end(NELEM,NELEM/10);
    else if (name=="rev-sorted-middle-.1") return reverse_sorted_middle(NELEM,NELEM/1000);
    else if (name=="rev-sorted-middle-1") return reverse_sorted_middle(NELEM,NELEM/100);
    else if (name=="rev-sorted-middle-10") return reverse_sorted_middle(NELEM,NELEM/10);

    else {
      cout<<"No such integer generator "<<name<<endl;
      exit(1);
    }
  }

  static vector<uint64_t> generate(size_t NELEM, string name) {
    auto A = generate_aux(NELEM,name);
    stats(A);
    return A;
  }

};

#ifndef NO_LLVM

llstring cnv_str(string s) {
  llstring res;
  str_init(&res);

  for (auto c = s.begin(); c!=s.end();++c) { str_append(&res,*c); }
  return res;
}

std::vector<llstring> cnv_strvec(std::vector<string> v) {
  std::vector<llstring> res;
  res.reserve(v.size());

  for (auto i = v.begin();i!=v.end();++i) res.push_back(cnv_str(*i));

  return res;
}

string bcnv_str(const llstring &s) {
  string res="";

  for (size_t i=0;i<s.size;++i) { res = res + ((s.data[i])); }
  return res;
}

std::vector<string> bcnv_strvec(std::vector<llstring> v) {
  std::vector<string> res;
  res.reserve(v.size());

  for (auto i = v.begin();i!=v.end();++i) res.push_back(bcnv_str(*i));

  return res;
}

class String_Generator {

  static vector<string> random(size_t NELEM)
  {
    std::vector<std::string> A;
    A.reserve(NELEM);
    A.clear();
    if (bsc::fill_vector_string("input.bin", A, NELEM) != 0)
    {
      std::cout << "Error in the input file\n";
      exit(1);
    };
    return A;

  }

  static vector<string> sorted(size_t NELEM)
  {
    std::vector<std::string> A;
    A.reserve(NELEM);
    A.clear();
    if (bsc::fill_vector_string("input.bin", A, NELEM) != 0)
    {
      std::cout << "Error in the input file\n";
      exit(1);
    };
    std::sort( A.begin() , A.end() );
    return A;
  };

  static vector<string> almost_sorted (size_t NELEM, size_t nperm)
  {
    vector<string> A = sorted(NELEM);
    vector<size_t> P;

    if (fill_vector_uint64 ("input.bin", P, 2*nperm) != 0)
    {
        std::cout << "Error in the input file\n";
        exit(1);
    };

    for (size_t i=0;i<P.size();++i) P[i]%=NELEM;

    for (size_t i=0;i+1<P.size();i+=2) {
      std::swap(A[P[i]],A[P[i+1]]);
    }

    return A;
  }

  static vector<string> organ_pipe(size_t NELEM)
  {
    std::vector<std::string> A = sorted(NELEM);
    auto i = std::unique( A.begin() , A.end() );
    A.resize(i-A.begin());

    if (A.size()<NELEM/2) {
      std::cout << "Too many equal strings for organ-pipe distribution\n";
      exit(1);
    }
    A.resize(NELEM/2);
    A.insert(A.end(),A.begin(),A.end());
    std::reverse(A.begin()+NELEM/2,A.end());

    return A;
  };

  static vector<string> random_dup(size_t NELEM, size_t nval)
  {
    std::vector<std::string> D = sorted(nval);
    std::vector<size_t> S = Int_Generator::random_dup(NELEM,nval);

    std::vector<std::string> A;
    A.reserve(NELEM);
    A.clear();
    for (size_t i=0;i<NELEM;++i) {
      A.push_back(D[S[i]]);
    }

    return A;
  };

  static vector<string> alleq(size_t NELEM)
  {
    std::vector<std::string> D = sorted(1);

    std::vector<std::string> A;
    A.reserve(NELEM);
    A.clear();
    for (size_t i=0;i<NELEM;++i) {
      A.push_back(D[0]);
    }

    return A;
  };

  static vector<string> sorted_end(size_t NELEM, size_t n_last)
  {
    std::vector<std::string> A;
    A.reserve(NELEM);
    A.clear();
    if (bsc::fill_vector_string("input.bin", A, NELEM+ n_last) != 0)
    {
      std::cout << "Error in the input file\n";
      exit(1);
    };
    std::sort (A.begin() , A.begin() + NELEM );
    return A;
  };

  static vector<string> sorted_middle(size_t NELEM,size_t n_last)
  {
    std::vector<std::string> A,B,C;
    A.reserve(NELEM);
    A.clear();
    if (bsc::fill_vector_string("input.bin", A, NELEM + n_last) != 0)
    {
      std::cout << "Error in the input file\n";
      exit(1);
    };
    for ( size_t i = NELEM; i < A.size() ; ++i)
      B.push_back ( std::move ( A[i]));
    A.resize ( NELEM);
    std::sort (A.begin() , A.end() );
    size_t step = NELEM/n_last +1 ;
    size_t pos = 0 ;

    for ( size_t i =0 ; i < B.size() ; ++i, pos += step)
    {   C.push_back ( B[i]);
      for ( size_t k = 0 ; k < step ; ++k )
        C.push_back ( A[pos + k] );
    };
    while ( pos < A.size() ) C.push_back ( A[pos++]);
    A = C ;

    return A;
  };

  static vector<string> reverse_sorted(size_t NELEM)
  {

    std::vector<std::string> A;
    A.reserve(NELEM);
    {
      std::vector<std::string> B;
      B.reserve(NELEM);
      if (bsc::fill_vector_string("input.bin", B, NELEM) != 0)
      {
        std::cout << "Error in the input file\n";
        exit(1);
      };
      std::sort(B.begin(), B.end());
      A.clear();
      for (size_t i = 0; i < NELEM; ++i)
        A.push_back(B[NELEM - 1 - i]);
    };
    return A;
  };

  static vector<string> reverse_sorted_end(size_t NELEM,size_t n_last)
  {
    std::vector<std::string> A;
    A.reserve(NELEM);
    A.clear();
    if (bsc::fill_vector_string("input.bin", A, NELEM+ n_last) != 0)
    {
      std::cout << "Error in the input file\n";
      exit(1);
    };
    std::sort (A.begin() , A.begin() + NELEM );
    for ( size_t i =0 ; i< (NELEM >>1); ++i)
      std::swap ( A[i], A[NELEM - 1 - i]);

    return A;

  };

  static vector<string> reverse_sorted_middle(size_t NELEM,size_t n_last)
  {
    std::vector<std::string> A,B,C;
    A.reserve(NELEM);
    A.clear();
    if (bsc::fill_vector_string("input.bin", A, NELEM + n_last) != 0)
    {
      std::cout << "Error in the input file\n";
      exit(1);
    };
    for ( size_t i = NELEM ; i < A.size() ; ++i)
      B.push_back ( std::move ( A[i]));
    A.resize ( NELEM);

    std::sort (A.begin() , A.end() );
    for ( size_t i =0 ; i< (NELEM >>1); ++i)
      std::swap ( A[i], A[NELEM - 1 - i]);

    size_t step = NELEM /n_last +1 ;
    size_t pos = 0 ;

    for ( size_t i =0 ; i < B.size() ; ++i, pos += step)
    {   C.push_back ( B[i]);
      for ( size_t k = 0 ; k < step ; ++k )
        C.push_back ( A[pos + k] );
    };
    while ( pos < A.size() )
      C.push_back ( A[pos++]);
    A = C ;

    return A;
  };

public:

  static void stats(vector<string> &A) {
    auto B=A;

    // Sum up all string length
    size_t suml=0;
    for (auto i=B.begin();i<B.end();++i) suml+=i->size();

    // Count different values, min,max,avg length of strings
    std::sort(B.begin(),B.end());
    auto i = std::unique(B.begin(),B.end());
    B.resize(i-B.begin());

    size_t minl = B[0].size(),maxl = B[0].size();

    for (auto i=B.begin()+1;i!=B.end();++i) {
      minl=min(minl,i->size());
      maxl=max(maxl,i->size());
    }

    cout<<"# "<<A.size()<<" strings "<<B.size()<<" different. Lengths ["<<minl<<".."<<maxl<<"] avg: "<<suml/A.size()<<endl;
  }

  static vector<string> generate_aux(size_t NELEM, string name) {
    if (name=="random") return random(NELEM);
    else if (name=="random-dup-10") return random_dup(NELEM,NELEM/10);
    else if (name=="random-boolean") return random_dup(NELEM,2);
    else if (name=="organ-pipe") return organ_pipe(NELEM);
    else if (name=="sorted") return sorted(NELEM);
    else if (name=="equal") return alleq(NELEM);
    else if (name=="almost-sorted-.1") return almost_sorted(NELEM,NELEM/1000);
    else if (name=="almost-sorted-1") return almost_sorted(NELEM,NELEM/100);
    else if (name=="almost-sorted-10") return almost_sorted(NELEM,NELEM/10);
    else if (name=="almost-sorted-50") return almost_sorted(NELEM,NELEM/2);
    else if (name=="sorted-end-.1") return sorted_end(NELEM,NELEM/1000);
    else if (name=="sorted-end-1") return sorted_end(NELEM,NELEM/100);
    else if (name=="sorted-end-10") return sorted_end(NELEM,NELEM/10);
    else if (name=="sorted-middle-.1") return sorted_middle(NELEM,NELEM/1000);
    else if (name=="sorted-middle-1") return sorted_middle(NELEM,NELEM/100);
    else if (name=="sorted-middle-10") return sorted_middle(NELEM,NELEM/10);
    else if (name=="rev-sorted") return reverse_sorted(NELEM);
    else if (name=="rev-sorted-end-.1") return reverse_sorted_end(NELEM,NELEM/1000);
    else if (name=="rev-sorted-end-1") return reverse_sorted_end(NELEM,NELEM/100);
    else if (name=="rev-sorted-end-10") return reverse_sorted_end(NELEM,NELEM/10);
    else if (name=="rev-sorted-middle-.1") return reverse_sorted_middle(NELEM,NELEM/1000);
    else if (name=="rev-sorted-middle-1") return reverse_sorted_middle(NELEM,NELEM/100);
    else if (name=="rev-sorted-middle-10") return reverse_sorted_middle(NELEM,NELEM/10);

    else {
      cout<<"No such string generator "<<name<<endl;
      exit(1);
    }
  }

  static vector<llstring> generate(size_t NELEM, string name) {
    auto A = generate_aux(NELEM,name);
    stats(A);
    return cnv_strvec(A);
  }

};

namespace std {
template<>
struct less<llstring>
{
   inline bool operator()(const llstring& k1, const llstring& k2) const
   {
//       return less<string>()(bcnv_str(k1), bcnv_str(k2));
     return llstrcmp((llstring*)&k1,(llstring*)&k2);
   }
};
}

#endif

// Disable branch-prediction optimization for all iterators, to make it comparable with our impl!
template<class Iter, class Compare>
inline void boost_pdqsort(Iter first, Iter last, Compare comp) {
    if (first == last) return;
    boost::sort::pdqsort_detail::pdqsort_loop<Iter, Compare, false >( first, last, comp, boost::sort::pdqsort_detail::log2(last - first));
}

template<class Iter>
inline void boost_pdqsort(Iter first, Iter last) {
    typedef typename std::iterator_traits<Iter>::value_type T;
    boost_pdqsort(first, last, std::less<T>());
}

void test_uint(string name, size_t NITER, vector<uint64_t> B) {
  std::vector<uint64_t> A (B);

  auto comp = std::less<uint64_t>();
#ifndef NO_LLVM
  if (name=="isabelle::sort") sort_test(A,B,NITER,comp,[&]{ introsort(A.data (), 0, A.size());});
  else if (name=="isabelle::pdqsort") sort_test(A,B,NITER,comp,[&]{ pdqsort(A.data (), 0, A.size());});
  else
#endif
  if (name=="std::sort") sort_test(A,B,NITER,comp,[&]{ std::sort(A.begin (), A.end (), comp);});
  else if (name=="boost::pdqsort") sort_test(A,B,NITER,comp,[&]{ boost_pdqsort(A.begin (), A.end (), comp);});
  else if (name=="boost::pdqsort_bp") sort_test(A,B,NITER,comp,[&]{ boost::sort::pdqsort(A.begin (), A.end ());});
  else if (name=="boost::sample_sort") sort_test(A,B,NITER,comp,[&]{ boost::sort::sample_sort(A.begin (), A.end (), comp, 12);});
  else {
    cout<<"No such sorting algorithm "<<name<<endl;
    exit(1);
  }
}

#ifndef NO_LLVM
void test_llstring(string name, size_t NITER, vector<llstring> &B) {
  std::vector<llstring> A (B);

  auto comp = std::less<llstring>();

  if (name=="isabelle::sort") sort_test(A,B,NITER,comp,[&]{ str_introsort(A.data (), 0, A.size());});
  else if (name=="isabelle::pdqsort") sort_test(A,B,NITER,comp,[&]{ str_pdqsort(A.data (), 0, A.size());});
  else if (name=="boost::pdqsort") sort_test(A,B,NITER,comp,[&]{ boost_pdqsort(A.begin (), A.end (), comp);});
  else if (name=="std::sort") sort_test(A,B,NITER,comp,[&]{ std::sort(A.begin (), A.end (), comp);});
  else if (name=="boost::sample_sort") sort_test(A,B,NITER,comp,[&]{ boost::sort::sample_sort(A.begin (), A.end (), comp, 12);});
  else {
    cout<<"No such sorting algorithm "<<name<<endl;
    exit(1);
  }
}
#endif

int main(int argc, char** argv) {

  if (argc != 6) {
    cout<<"Usage: do_benchmark type algo-name data-name NELEM NITER"<<endl;
    exit(1);
  }

  string type_name=argv[1];
  string algo_name=argv[2];
  string data_name=argv[3];
  size_t NELEM=stoul(argv[4]);
  size_t NITER=stoul(argv[5]);

  if (type_name == "uint64") {
    auto A = Int_Generator::generate(NELEM,data_name);
#ifdef NO_LLVM
    cout<<"NO_LLVM ";
#endif
    cout<<type_name<<" "<<algo_name<<" "<<data_name<<" "<<NELEM<<"x"<<NITER<<std::flush;
    test_uint(algo_name,NITER,A);
#ifndef NO_LLVM
  } else if (type_name == "llstring") {
    auto A = String_Generator::generate(NELEM,data_name);
    cout<<type_name<<" "<<algo_name<<" "<<data_name<<" "<<NELEM<<"x"<<NITER<<std::flush;
    test_llstring(algo_name,NITER,A);
#endif
  } else {
    cout<<"No such data type "<<type_name<<endl;
    exit(1);
  }

  return 0;
}


//////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Alejandro Cabrera 2011.
// Distributed under the Boost
// Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or
// copy at http://www.boost.org/LICENSE_1_0.txt)
//
// See http://www.boost.org/libs/bloom_filter for documentation.
//
//////////////////////////////////////////////////////////////////////////////

#define BOOST_TEST_DYN_LINK 1
#define BOOST_TEST_MODULE "Boost Bloom Filter" 1
#include <iostream>

#include <boost/bloom_filter/basic_bloom_filter.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>

using boost::bloom_filters::basic_bloom_filter;
using boost::bloom_filters::boost_hash;

BOOST_AUTO_TEST_CASE(defaultConstructor) {
  typedef boost::mpl::vector<
    boost_hash<int, 13>,
    boost_hash<int, 17>,
    boost_hash<int, 19> > BoostHashFunctions;

  basic_bloom_filter<int, 8> bloom1;
  basic_bloom_filter<int, 8, BoostHashFunctions> bloom2;
}

BOOST_AUTO_TEST_CASE(rangeConstructor) {
  int elems[5] = {1,2,3,4,5};
  basic_bloom_filter<int, 8> bloom(elems, elems+5);

  BOOST_CHECK_EQUAL(bloom.count(), 5ul);
}

#ifndef BOOST_NO_0X_HDR_INITIALIZER_LIST
BOOST_AUTO_TEST_CASE(initListConstructor) {
  basic_bloom_filter<int, 8> bloom = {1,2,3,4,5};

  BOOST_CHECK_EQUAL(bloom.count(), 5ul);
}
#endif

BOOST_AUTO_TEST_CASE(copyConstructor) {
  int elems[5] = {1,2,3,4,5};
  basic_bloom_filter<int, 8> bloom1(elems, elems+5);
  basic_bloom_filter<int, 8> bloom2(bloom1);

  BOOST_CHECK_EQUAL(bloom1.count(), bloom2.count());
}

BOOST_AUTO_TEST_CASE(assignment)
{
  basic_bloom_filter<int, 8> bloom1;
  basic_bloom_filter<int, 8> bloom2;

  for (size_t i = 0; i < 200; ++i) {
    bloom1.insert(i);
    BOOST_CHECK_EQUAL(bloom1.probably_contains(i), true);
  }

  bloom2 = bloom1;

  for (size_t i = 0; i < 200; ++i) {
    BOOST_CHECK_EQUAL(bloom2.probably_contains(i), true);
  }
}

BOOST_AUTO_TEST_CASE(bit_capacity) {
  basic_bloom_filter<size_t, 8> bloom_8;
  basic_bloom_filter<size_t, 256> bloom_256;
  basic_bloom_filter<size_t, 2048> bloom_2048;

  BOOST_CHECK_EQUAL(bloom_8.bit_capacity(), 8ul);
  BOOST_CHECK_EQUAL(bloom_256.bit_capacity(), 256ul);
  BOOST_CHECK_EQUAL(bloom_2048.bit_capacity(), 2048ul);
}

BOOST_AUTO_TEST_CASE(empty) {
  basic_bloom_filter<size_t, 8> bloom;

  BOOST_CHECK_EQUAL(bloom.empty(), true);
  bloom.insert(1);
  BOOST_CHECK_EQUAL(bloom.empty(), false);
  bloom.clear();
  BOOST_CHECK_EQUAL(bloom.empty(), true);
}

BOOST_AUTO_TEST_CASE(numHashFunctions) {
  basic_bloom_filter<size_t, 8> bloom_3;
  basic_bloom_filter<size_t, 8, boost::mpl::vector<
    boost_hash<size_t, 1>,
    boost_hash<size_t, 2> > > bloom_2;
  basic_bloom_filter<size_t, 8, boost::mpl::vector<
    boost_hash<size_t, 1>,
    boost_hash<size_t, 2>,
    boost_hash<size_t, 3>,
    boost_hash<size_t, 4>,
    boost_hash<size_t, 5>,
    boost_hash<size_t, 6>,
    boost_hash<size_t, 7> > > bloom_7;

  BOOST_CHECK_EQUAL(bloom_3.num_hash_functions(), 1ul);
  BOOST_CHECK_EQUAL(bloom_2.num_hash_functions(), 2ul);
  BOOST_CHECK_EQUAL(bloom_7.num_hash_functions(), 7ul);
}

BOOST_AUTO_TEST_CASE(falsePositiveRate) {
  basic_bloom_filter<size_t, 64> bloom;

  BOOST_CHECK_EQUAL(bloom.false_positive_rate(), 0.0);

  bloom.insert(1);
  BOOST_CHECK_CLOSE(bloom.false_positive_rate(), 0.015504, .01);

  bloom.insert(2);
  BOOST_CHECK_CLOSE(bloom.false_positive_rate(), 0.030768, .01);

  bloom.insert(3);
  BOOST_CHECK_CLOSE(bloom.false_positive_rate(), 0.045794, .01);

  bloom.insert(4);
  BOOST_CHECK_CLOSE(bloom.false_positive_rate(), 0.060588, .01);

  bloom.insert(5);
  BOOST_CHECK_CLOSE(bloom.false_positive_rate(), 0.075151, .01);

  bloom.insert(6);
  BOOST_CHECK_CLOSE(bloom.false_positive_rate(), 0.089491, .01);

  for (size_t i = 7; i < 5000; ++i)
    bloom.insert(i);

  BOOST_CHECK_GE(bloom.false_positive_rate(), 0.6);
  BOOST_CHECK_LE(bloom.false_positive_rate(), 1.0);
}

BOOST_AUTO_TEST_CASE(probably_contains) {
  basic_bloom_filter<size_t, 8> bloom;

  bloom.insert(1);
  BOOST_CHECK_EQUAL(bloom.probably_contains(1), true);
  BOOST_CHECK_LE(bloom.count(), 3ul);
  BOOST_CHECK_GE(bloom.count(), 1ul);
}

BOOST_AUTO_TEST_CASE(doesNotContain) {
  basic_bloom_filter<size_t, 8> bloom;

  BOOST_CHECK_EQUAL(bloom.probably_contains(1), false);
}

BOOST_AUTO_TEST_CASE(insertNoFalseNegatives) {
  basic_bloom_filter<size_t, 2048> bloom;

  for (size_t i = 0; i < 100; ++i) {
    bloom.insert(i);
    BOOST_CHECK_EQUAL(bloom.probably_contains(i), true);
  }
}

BOOST_AUTO_TEST_CASE(rangeInsert) {
  int elems[5] = {1,2,3,4,5};
  basic_bloom_filter<size_t, 8> bloom;

  bloom.insert(elems, elems+5);
  BOOST_CHECK_EQUAL(bloom.count(), 5ul);
}

BOOST_AUTO_TEST_CASE(clear) {
  basic_bloom_filter<size_t, 8> bloom;

  for (size_t i = 0; i < 1000; ++i)
    bloom.insert(i);

  bloom.clear();
  BOOST_CHECK_EQUAL(bloom.probably_contains(1), false);
  BOOST_CHECK_EQUAL(bloom.count(), 0ul);
}

BOOST_AUTO_TEST_CASE(memberSwap) {
  size_t elems[5] = {1,2,3,4,5};
  basic_bloom_filter<size_t, 8> bloom1(elems, elems+2);
  basic_bloom_filter<size_t, 8> bloom2(elems+2, elems+5);

  bloom1.swap(bloom2);

  BOOST_CHECK_EQUAL(bloom1.count(), 3ul);
  BOOST_CHECK_EQUAL(bloom2.count(), 2ul);
}

BOOST_AUTO_TEST_CASE(testUnion) {
  basic_bloom_filter<size_t, 300> bloom_1;
  basic_bloom_filter<size_t, 300> bloom_2;
  basic_bloom_filter<size_t, 300> bloom_union;

  for (size_t i = 0; i < 100; ++i)
    bloom_1.insert(i);

  for (size_t i = 100; i < 200; ++i)
    bloom_2.insert(i);

  bloom_union = bloom_1 | bloom_2;

  for (size_t i = 0; i < 200; ++i)
    BOOST_CHECK_EQUAL(bloom_union.probably_contains(i), true);
  BOOST_CHECK_GE(bloom_union.count(), bloom_1.count());
  BOOST_CHECK_GE(bloom_union.count(), bloom_2.count());
}

BOOST_AUTO_TEST_CASE(testUnionAssign) {
  basic_bloom_filter<size_t, 300> bloom_1;
  basic_bloom_filter<size_t, 300> bloom_union;

  for (size_t i = 0; i < 100; ++i)
    bloom_1.insert(i);

  bloom_union |= bloom_1;

  for (size_t i = 0; i < 100; ++i)
    BOOST_CHECK_EQUAL(bloom_union.probably_contains(i), true);
  BOOST_CHECK_EQUAL(bloom_union.count(), bloom_1.count());
}

BOOST_AUTO_TEST_CASE(testIntersect) {
  basic_bloom_filter<size_t, 300> bloom_1;
  basic_bloom_filter<size_t, 300> bloom_2;
  basic_bloom_filter<size_t, 300> bloom_intersect;

  // overlap at 100
  for (size_t i = 0; i < 101; ++i)
    bloom_1.insert(i);

  for (size_t i = 100; i < 200; ++i)
    bloom_2.insert(i);

  bloom_intersect = bloom_1 & bloom_2;

  BOOST_CHECK_LE(bloom_intersect.count(), bloom_1.count());
  BOOST_CHECK_LE(bloom_intersect.count(), bloom_2.count());
  BOOST_CHECK_EQUAL(bloom_intersect.probably_contains(100), true);
}

BOOST_AUTO_TEST_CASE(testIntersectAssign) {
  basic_bloom_filter<size_t, 300> bloom_1;
  basic_bloom_filter<size_t, 300> bloom_intersect;

  for (size_t i = 0; i < 100; ++i)
    bloom_1.insert(i);

  bloom_intersect &= bloom_1;

  for (size_t i = 0; i < 100; ++i)
    BOOST_CHECK_EQUAL(bloom_intersect.probably_contains(i), false);
}

BOOST_AUTO_TEST_CASE(globalSwap) {
  size_t elems[5] = {1,2,3,4,5};
  basic_bloom_filter<size_t, 8> bloom1(elems, elems+2);
  basic_bloom_filter<size_t, 8> bloom2(elems+2, elems+5);

  swap(bloom1, bloom2);

  BOOST_CHECK_EQUAL(bloom1.count(), 3ul);
  BOOST_CHECK_EQUAL(bloom2.count(), 2ul);
}

BOOST_AUTO_TEST_CASE(equalityOperator) {
  basic_bloom_filter<int, 8> bloom1;
  basic_bloom_filter<int, 8> bloom2;

  BOOST_CHECK_EQUAL(bloom1 == bloom2, true);
  bloom1.insert(1);
  BOOST_CHECK_EQUAL(bloom1 == bloom2, false);
  bloom2.insert(1);
  BOOST_CHECK_EQUAL(bloom1 == bloom2, true);
}

BOOST_AUTO_TEST_CASE(inequalityOperator) {
  basic_bloom_filter<int, 8> bloom1;
  basic_bloom_filter<int, 8> bloom2;

  BOOST_CHECK_EQUAL(bloom1 != bloom2, false);
  bloom1.insert(1);
  BOOST_CHECK_EQUAL(bloom1 != bloom2, true);
  bloom2.insert(1);
  BOOST_CHECK_EQUAL(bloom1 != bloom2, false);
}


/* boost random/shuffle_output.hpp header file
 *
 * Copyright Jens Maurer 2000-2001
 * Permission to use, copy, modify, sell, and distribute this software
 * is hereby granted without fee provided that the above copyright notice
 * appears in all copies and that both that copyright notice and this
 * permission notice appear in supporting documentation,
 *
 * Jens Maurer makes no representations about the suitability of this
 * software for any purpose. It is provided "as is" without express or
 * implied warranty.
 *
 * See http://www.boost.org for most recent version including documentation.
 *
 * $Id: shuffle_output.hpp 12208 2002-01-03 22:21:34Z jmaurer $
 *
 * Revision history
 *  2001-02-18  moved to individual header files
 */

#ifndef BOOST_RANDOM_SHUFFLE_OUTPUT_HPP
#define BOOST_RANDOM_SHUFFLE_OUTPUT_HPP

#include <iostream>
#include <algorithm>     // std::copy
#include <cassert>
#include <boost/config.hpp>
#include <boost/limits.hpp>
#include <boost/static_assert.hpp>
#include <boost/cstdint.hpp>
#include <boost/random/linear_congruential.hpp>

namespace boost {
namespace random {

// Carter Bays and S.D. Durham 1979
template<class UniformRandomNumberGenerator, int k,
  class IntType = typename UniformRandomNumberGenerator::result_type,
#ifndef BOOST_NO_DEPENDENT_TYPES_IN_TEMPLATE_VALUE_PARAMETERS
  typename UniformRandomNumberGenerator::result_type
#else
  uint32_t
#endif
  val = 0>
class shuffle_output
{
public:
  typedef UniformRandomNumberGenerator base_type;
  typedef typename base_type::result_type result_type;

  BOOST_STATIC_CONSTANT(bool, has_fixed_range = false);
  BOOST_STATIC_CONSTANT(int, buffer_size = k);

  shuffle_output() : _rng() { init(); }
#if defined(BOOST_MSVC) && _MSC_VER <= 1200
  // MSVC does not implicitly generate the copy constructor here
  shuffle_output(const shuffle_output & x)
    : _rng(x._rng), y(x.y) { std::copy(x.v, x.v+k, v); }
#endif
  template<class T>
  explicit shuffle_output(T seed) : _rng(seed) { init(); }
  explicit shuffle_output(const base_type & rng) : _rng(rng) { init(); }
  template<class T>
  void seed(T s) { _rng.seed(s); init(); }

  result_type operator()() {
    // calculating the range every time may seem wasteful.  However, this
    // makes the information locally available for the optimizer.
    result_type range = max()-min()+1;
    int j = k*IntType(y-min())/range;
    // assert(0 <= j && j < k);
    y = v[j];
    v[j] = _rng();
    return y;
  }

  result_type min() const { return _rng.min(); }
  result_type max() const { return _rng.max(); }
  bool validation(result_type x) const { return val == x; }

#ifndef BOOST_NO_OPERATORS_IN_NAMESPACE
  friend std::ostream& operator<<(std::ostream& os, const shuffle_output& s)
  {
    os << s._rng << " " << s.y << " ";
    std::copy(s.v, s.v+k, std::ostream_iterator<result_type>(os, " "));
    return os;
  }
  friend std::istream& operator>>(std::istream& is, shuffle_output& s)
  {
    is >> s._rng >> std::ws >> s.y >> std::ws;
    for(int i = 0; i < s.buffer_size; ++i)
      is >> s.v[i] >> std::ws;
    return is;
  }
  friend bool operator==(const shuffle_output& x, const shuffle_output& y)
  { return x._rng == y._rng && x.y == y.y && std::equal(x.v, x.v+k, y.v); }
#else
  // Use a member function; Streamable concept not supported.
  bool operator==(const shuffle_output& rhs) const
  { return _rng == rhs._rng && y == rhs.y && std::equal(v, v+k, rhs.v); }
#endif
private:
  void init()
  {
#ifndef BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS
    BOOST_STATIC_ASSERT(std::numeric_limits<result_type>::is_integer);
#endif
    result_type range = max()-min();
    assert(range > 0);      // otherwise there would be little choice
    if(IntType(k * range) < IntType(range))  // not a sufficient condition
      // likely overflow with bucket number computation
      assert(!"overflow will occur");

    // we cannot use std::generate, because it uses pass-by-value for _rng
    for(result_type * p = v; p != v+k; ++p)
      *p = _rng();
    y = _rng();
  }

  base_type _rng;
  result_type v[k];
  result_type y;
};

#ifndef BOOST_NO_INCLASS_MEMBER_INITIALIZATION
//  A definition is required even for integral static constants
template<class UniformRandomNumberGenerator, int k,
  class IntType,
#ifndef BOOST_NO_DEPENDENT_TYPES_IN_TEMPLATE_VALUE_PARAMETERS
  typename UniformRandomNumberGenerator::result_type
#else
  uint32_t
#endif
  val>
const bool shuffle_output<UniformRandomNumberGenerator, k, IntType, val>::has_fixed_range;

template<class UniformRandomNumberGenerator, int k,
  class IntType,
#ifndef BOOST_NO_DEPENDENT_TYPES_IN_TEMPLATE_VALUE_PARAMETERS
  typename UniformRandomNumberGenerator::result_type
#else
  uint32_t
#endif
  val>
const int shuffle_output<UniformRandomNumberGenerator, k, IntType, val>::buffer_size;
#endif

} // namespace random

// validation by experiment from Harry Erwin's generator.h (private e-mail)
typedef random::shuffle_output<
    random::linear_congruential<uint32_t, 1366, 150889, 714025, 0>,
  97, uint32_t, 139726> kreutzer1986;

} // namespace boost

#endif // BOOST_RANDOM_SHUFFLE_OUTPUT_HPP