#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <armadillo>
#include <carma>
#include <catch2/catch.hpp>

namespace py = pybind11;

typedef py::array_t<double, py::array::f_style | py::array::forcecast> fArr;

TEST_CASE("Version check", "[version_check]") {
    std::cout << "carma v" << carma::carma_version().as_string() << "\n";
}

TEST_CASE("Test arr_to_mat", "[arr_to_mat]") {
    py::module np = py::module::import("numpy");
    py::module np_rand = py::module::import("numpy.random");

    SECTION("F-contiguous; no copy") {
        int copy = 0;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2)));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0.0;
        auto* _ptr = reinterpret_cast<double*>(info.ptr);
        auto ptr = arr.unchecked<2>();
        for (size_t ic = 0; ic < arr_S1; ic++) {
            for (size_t ir = 0; ir < arr_S0; ir++) {
                arr_sum += ptr(ir, ic);
            }
        }

        // call function to be tested
        arma::Mat<double> M = carma::arr_to_mat<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(_ptr == M.memptr());
    }

    SECTION("F-contiguous; copy") {
        bool copy = true;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2)));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0.0;
        auto* _ptr = reinterpret_cast<double*>(info.ptr);
        auto ptr = arr.unchecked<2>();
        for (size_t ic = 0; ic < arr_S1; ic++) {
            for (size_t ir = 0; ir < arr_S0; ir++) {
                arr_sum += ptr(ir, ic);
            }
        }

        // call function to be tested
        arma::Mat<double> M = carma::arr_to_mat<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(_ptr != M.memptr());
    }

    SECTION("F-contiguous; steal") {
        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2)));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0.0;
        auto* _ptr = reinterpret_cast<double*>(info.ptr);
        auto ptr = arr.unchecked<2>();
        for (size_t ic = 0; ic < arr_S1; ic++) {
            for (size_t ir = 0; ir < arr_S0; ir++) {
                arr_sum += ptr(ir, ic);
            }
        }

        // call function to be tested
        arma::Mat<double> M = carma::arr_to_mat<double>(std::move(arr));

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(_ptr == M.memptr());
    }

    SECTION("F-contiguous; const") {
        const py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2)));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0.0;
        auto* _ptr = reinterpret_cast<double*>(info.ptr);
        auto ptr = arr.unchecked<2>();
        for (size_t ic = 0; ic < arr_S1; ic++) {
            for (size_t ir = 0; ir < arr_S0; ir++) {
                arr_sum += ptr(ir, ic);
            }
        }

        // call function to be tested
        arma::Mat<double> M = carma::arr_to_mat<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(_ptr != M.memptr());
    }

    SECTION("C-contiguous; no copy") {
        int copy = 0;

        py::array_t<double> arr = np_rand.attr("normal")(0, 1, py::make_tuple(100, 2));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        auto arr_p = arr.unchecked<2>();

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        auto ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0;
        for (size_t ci = 0; ci < arr_S1; ci++) {
            for (size_t ri = 0; ri < arr_S0; ri++) {
                arr_sum += arr_p(ri, ci);
            }
        }

        // call function to be tested
        arma::Mat<double> M = carma::arr_to_mat<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        INFO("is c-contiguous " << carma::is_c_contiguous(arr));
        INFO("is f-contiguous " << carma::is_f_contiguous(arr));
        INFO("is aligned " << carma::is_aligned(arr));
        INFO("mat_sum is  " << mat_sum);
        INFO("arr_sum is  " << arr_sum);
        INFO("M " << M);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(ptr != M.memptr());
    }

    SECTION("C-contiguous; copy") {
        bool copy = true;

        py::array_t<double> arr = np_rand.attr("normal")(0, 1, py::make_tuple(100, 2));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        auto arr_p = arr.unchecked<2>();

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        auto* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0;
        for (size_t ci = 0; ci < arr_S1; ci++) {
            for (size_t ri = 0; ri < arr_S0; ri++) {
                arr_sum += arr_p(ri, ci);
            }
        }

        // call function to be tested
        arma::Mat<double> M = carma::arr_to_mat<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        INFO("is c-contiguous " << carma::is_c_contiguous(arr));
        INFO("is f-contiguous " << carma::is_f_contiguous(arr));
        INFO("is aligned " << carma::is_aligned(arr));
        INFO("mat_sum is  " << mat_sum);
        INFO("arr_sum is  " << arr_sum);
        INFO("M " << M);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(ptr != M.memptr());
    }

    SECTION("C-contiguous; steal") {
        py::array_t<double> arr = np_rand.attr("normal")(0, 1, py::make_tuple(100, 2));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        auto arr_p = arr.unchecked<2>();

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0;
        for (size_t ci = 0; ci < arr_S1; ci++) {
            for (size_t ri = 0; ri < arr_S0; ri++) {
                arr_sum += arr_p(ri, ci);
            }
        }

        // call function to be tested
        arma::Mat<double> M = carma::arr_to_mat<double>(std::move(arr));

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        INFO("is c-contiguous " << carma::is_c_contiguous(arr));
        INFO("is f-contiguous " << carma::is_f_contiguous(arr));
        INFO("is aligned " << carma::is_aligned(arr));
        INFO("mat_sum is  " << mat_sum);
        INFO("arr_sum is  " << arr_sum);
        INFO("M " << M);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }

    SECTION("C-contiguous; const") {
        const py::array_t<double> arr = np_rand.attr("normal")(0, 1, py::make_tuple(100, 2));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        auto arr_p = arr.unchecked<2>();

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0;
        for (size_t ci = 0; ci < arr_S1; ci++) {
            for (size_t ri = 0; ri < arr_S0; ri++) {
                arr_sum += arr_p(ri, ci);
            }
        }

        // call function to be tested
        arma::Mat<double> M = carma::arr_to_mat<double>(std::move(arr));

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        INFO("is c-contiguous " << carma::is_c_contiguous(arr));
        INFO("is f-contiguous " << carma::is_f_contiguous(arr));
        INFO("is aligned " << carma::is_aligned(arr));
        INFO("mat_sum is  " << mat_sum);
        INFO("arr_sum is  " << arr_sum);
        INFO("M " << M);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
        M.insert_cols(0, 2, true);
    }

    SECTION("F-contiguous; no copy; change") {
        int copy = 0;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2)));

        // call function to be tested
        arma::Mat<double> M = carma::arr_to_mat<double>(arr, copy);

        REQUIRE_THROWS(M.insert_cols(0, 2, true));
    }

    SECTION("F-contiguous; copy; change") {
        int copy = 1;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2)));

        // call function to be tested
        arma::Mat<double> M = carma::arr_to_mat<double>(arr, copy);
        M.insert_cols(0, 2, true);
    }

    SECTION("F-contiguous; const; change") {
        const py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2)));

        // call function to be tested
        arma::Mat<double> M = carma::arr_to_mat<double>(arr);
        M.insert_cols(0, 2, true);
    }

    SECTION("F-contiguous; steal; change") {
        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2)));

        // call function to be tested
        arma::Mat<double> M = carma::arr_to_mat<double>(std::move(arr));
        M.insert_cols(0, 2, true);
    }

    SECTION("C-contiguous; no copy; change") {
        py::array_t<double> arr = np_rand.attr("normal")(0, 1, py::make_tuple(100, 2));

        // call function to be tested
        arma::Mat<double> M = carma::arr_to_mat<double>(arr, false);

        REQUIRE_THROWS(M.insert_cols(0, 2, true));
    }

    SECTION("dimension exception") {
        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2)));

        REQUIRE_THROWS_AS(carma::arr_to_mat<double>(arr, false), carma::ConversionError);
    }
} /* TEST_CASE ARR_TO_MAT */

TEST_CASE("Test arr_to_row", "[arr_to_row]") {
    py::module np = py::module::import("numpy");
    py::module np_rand = py::module::import("numpy.random");

    SECTION("F-contiguous; no copy") {
        bool copy = false;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, 100));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Row<double> M = carma::arr_to_row<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("F-contiguous; copy") {
        bool copy = true;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, 100));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Row<double> M = carma::arr_to_row<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }

    SECTION("F-contiguous; steal") {
        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, 100));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Row<double> M = carma::arr_to_row<double>(std::move(arr));

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("F-contiguous; const") {
        const py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, 100));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Row<double> M = carma::arr_to_row<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }

    SECTION("2D; no copy") {
        bool copy = false;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(1, 100)));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Row<double> M = carma::arr_to_row<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("C-contiguous 2D; no copy") {
        bool copy = false;

        py::array_t<double> arr = np_rand.attr("normal")(0, 1, py::make_tuple(1, 100));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Row<double> M = carma::arr_to_row<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("C-contiguous; no copy") {
        bool copy = false;

        py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Row<double> M = carma::arr_to_row<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("C-contiguous; copy") {
        bool copy = true;

        py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Row<double> M = carma::arr_to_row<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }

    SECTION("C-contiguous; steal") {
        py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Row<double> M = carma::arr_to_row<double>(std::move(arr));

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("C-contiguous; const") {
        const py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Row<double> M = carma::arr_to_row<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }

    SECTION("no copy; change") {
        bool copy = false;

        py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        //  call function to be tested
        arma::Row<double> M = carma::arr_to_row<double>(arr, copy);
        REQUIRE_THROWS(M.insert_cols(0, 2, true));
    }

    SECTION("copy; change") {
        bool copy = true;

        py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        //  call function to be tested
        arma::Row<double> M = carma::arr_to_row<double>(arr, copy);
        M.insert_cols(0, 2, true);
    }

    SECTION("const; change") {
        const py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        //  call function to be tested
        arma::Row<double> M = carma::arr_to_row<double>(arr);
        M.insert_cols(0, 2, true);
    }

    SECTION("steal; change") {
        py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        //  call function to be tested
        arma::Row<double> M = carma::arr_to_row<double>(std::move(arr));
        M.insert_cols(0, 2, true);
    }

    SECTION("dimension exception") {
        bool copy = false;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(2, 100)));

        REQUIRE_THROWS_AS(carma::arr_to_row<double>(arr, copy), carma::ConversionError);
    }
} /* TEST_CASE ARR_TO_ROW */

TEST_CASE("Test arr_to_col", "[arr_to_col]") {
    py::module np = py::module::import("numpy");
    py::module np_rand = py::module::import("numpy.random");

    SECTION("F-contiguous; no copy") {
        bool copy = false;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, 100));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Col<double> M = carma::arr_to_col<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("F-contiguous; copy") {
        bool copy = true;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, 100));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Col<double> M = carma::arr_to_col<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }

    SECTION("F-contiguous; steal") {
        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, 100));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Col<double> M = carma::arr_to_col<double>(std::move(arr));

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("F-contiguous; const") {
        const py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, 100));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Col<double> M = carma::arr_to_col<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }

    SECTION("2D; no copy") {
        bool copy = false;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 1)));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Col<double> M = carma::arr_to_col<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("C-contiguous 2D; no copy") {
        bool copy = false;

        py::array_t<double> arr = np_rand.attr("normal")(0, 1, py::make_tuple(100, 1));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Col<double> M = carma::arr_to_col<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("C-contiguous; no copy") {
        bool copy = false;

        py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Col<double> M = carma::arr_to_col<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("C-contiguous; copy") {
        bool copy = true;

        py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Col<double> M = carma::arr_to_col<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }

    SECTION("C-contiguous; steal") {
        py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Col<double> M = carma::arr_to_col<double>(std::move(arr));

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("C-contiguous; const") {
        const py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        arma::Col<double> M = carma::arr_to_col<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }

    SECTION("no copy; change") {
        bool copy = false;

        py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        // call function to be tested
        arma::Col<double> M = carma::arr_to_col<double>(arr, copy);

        REQUIRE_THROWS(M.insert_rows(0, 2, true));
    }

    SECTION("copy; change") {
        bool copy = true;

        py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        // call function to be tested
        arma::Col<double> M = carma::arr_to_col<double>(arr, copy);
        M.insert_rows(0, 2, true);
    }

    SECTION("steal; change") {
        py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        //  call function to be tested
        arma::Col<double> M = carma::arr_to_col<double>(std::move(arr));
        M.insert_rows(0, 2, true);
    }

    SECTION("const; change") {
        const py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        //  call function to be tested
        arma::Col<double> M = carma::arr_to_col<double>(arr);
        M.insert_rows(0, 2, true);
    }

    SECTION("dimension exception") {
        bool copy = false;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2)));

        REQUIRE_THROWS_AS(carma::arr_to_col<double>(arr, copy), carma::ConversionError);
    }
} /* TEST_CASE ARR_TO_COL */

TEST_CASE("Test arr_to_cube", "[arr_to_cube]") {
    py::module np = py::module::import("numpy");
    py::module np_rand = py::module::import("numpy.random");

    SECTION("F-contiguous; no copy") {
        bool copy = false;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2)));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        size_t arr_S2 = arr.shape(2);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0.0;
        auto ptr = arr.unchecked<3>();
        for (size_t is = 0; is < arr_S2; is++) {
            for (size_t ic = 0; ic < arr_S1; ic++) {
                for (size_t ir = 0; ir < arr_S0; ir++) {
                    arr_sum += ptr(ir, ic, is);
                }
            }
        }

        // call function to be tested
        arma::Cube<double> M = carma::arr_to_cube<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(arr_S2 == M.n_slices);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("F-contiguous; copy") {
        bool copy = true;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2)));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        size_t arr_S2 = arr.shape(2);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0.0;
        auto ptr = arr.unchecked<3>();
        for (size_t is = 0; is < arr_S2; is++) {
            for (size_t ic = 0; ic < arr_S1; ic++) {
                for (size_t ir = 0; ir < arr_S0; ir++) {
                    arr_sum += ptr(ir, ic, is);
                }
            }
        }

        // call function to be tested
        arma::Cube<double> M = carma::arr_to_cube<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(arr_S2 == M.n_slices);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }

    SECTION("F-contiguous; steal") {
        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2)));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        size_t arr_S2 = arr.shape(2);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0.0;
        auto ptr = arr.unchecked<3>();
        for (size_t is = 0; is < arr_S2; is++) {
            for (size_t ic = 0; ic < arr_S1; ic++) {
                for (size_t ir = 0; ir < arr_S0; ir++) {
                    arr_sum += ptr(ir, ic, is);
                }
            }
        }

        // call function to be tested
        arma::Cube<double> M = carma::arr_to_cube<double>(std::move(arr));

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(arr_S2 == M.n_slices);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("F-contiguous; const") {
        const py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2)));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        size_t arr_S2 = arr.shape(2);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0.0;
        auto ptr = arr.unchecked<3>();
        for (size_t is = 0; is < arr_S2; is++) {
            for (size_t ic = 0; ic < arr_S1; ic++) {
                for (size_t ir = 0; ir < arr_S0; ir++) {
                    arr_sum += ptr(ir, ic, is);
                }
            }
        }

        // call function to be tested
        arma::Cube<double> M = carma::arr_to_cube<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(arr_S2 == M.n_slices);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }

    SECTION("C-contiguous; no copy") {
        bool copy = false;

        py::array_t<double> arr = np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        size_t arr_S2 = arr.shape(2);
        auto arr_p = arr.unchecked<3>();

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        auto ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0;
        for (size_t si = 0; si < arr_S2; si++) {
            for (size_t ci = 0; ci < arr_S1; ci++) {
                for (size_t ri = 0; ri < arr_S0; ri++) {
                    arr_sum += arr_p(ri, ci, si);
                }
            }
        }

        // call function to be tested
        arma::Cube<double> M = carma::arr_to_cube<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(arr_S2 == M.n_slices);
        INFO("is c-contiguous " << carma::is_c_contiguous(arr));
        INFO("is f-contiguous " << carma::is_f_contiguous(arr));
        INFO("is aligned " << carma::is_aligned(arr));
        INFO("mat_sum is  " << mat_sum);
        INFO("arr_sum is  " << arr_sum);
        INFO("M " << M);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(ptr != M.memptr());
    }

    SECTION("C-contiguous; copy") {
        bool copy = true;

        py::array_t<double> arr = np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        size_t arr_S2 = arr.shape(2);
        auto arr_p = arr.unchecked<3>();

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0;
        for (size_t si = 0; si < arr_S2; si++) {
            for (size_t ci = 0; ci < arr_S1; ci++) {
                for (size_t ri = 0; ri < arr_S0; ri++) {
                    arr_sum += arr_p(ri, ci, si);
                }
            }
        }

        // call function to be tested
        arma::Cube<double> M = carma::arr_to_cube<double>(arr, copy);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(arr_S2 == M.n_slices);
        INFO("is c-contiguous " << carma::is_c_contiguous(arr));
        INFO("is f-contiguous " << carma::is_f_contiguous(arr));
        INFO("is aligned " << carma::is_aligned(arr));
        INFO("mat_sum is  " << mat_sum);
        INFO("arr_sum is  " << arr_sum);
        INFO("M " << M);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(ptr != M.memptr());
    }

    SECTION("C-contiguous; steal") {
        py::array_t<double> arr = np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        size_t arr_S2 = arr.shape(2);
        auto arr_p = arr.unchecked<3>();

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0;
        for (size_t si = 0; si < arr_S2; si++) {
            for (size_t ci = 0; ci < arr_S1; ci++) {
                for (size_t ri = 0; ri < arr_S0; ri++) {
                    arr_sum += arr_p(ri, ci, si);
                }
            }
        }

        // call function to be tested
        arma::Cube<double> M = carma::arr_to_cube<double>(std::move(arr));

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(arr_S2 == M.n_slices);
        INFO("is c-contiguous " << carma::is_c_contiguous(arr));
        INFO("is f-contiguous " << carma::is_f_contiguous(arr));
        INFO("is aligned " << carma::is_aligned(arr));
        INFO("mat_sum is  " << mat_sum);
        INFO("arr_sum is  " << arr_sum);
        INFO("M " << M);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }

    SECTION("C-contiguous; const") {
        const py::array_t<double> arr = np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        size_t arr_S2 = arr.shape(2);
        auto arr_p = arr.unchecked<3>();

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0;
        for (size_t si = 0; si < arr_S2; si++) {
            for (size_t ci = 0; ci < arr_S1; ci++) {
                for (size_t ri = 0; ri < arr_S0; ri++) {
                    arr_sum += arr_p(ri, ci, si);
                }
            }
        }

        // call function to be tested
        arma::Cube<double> M = carma::arr_to_cube<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(arr_S2 == M.n_slices);
        INFO("is c-contiguous " << carma::is_c_contiguous(arr));
        INFO("is f-contiguous " << carma::is_f_contiguous(arr));
        INFO("is aligned " << carma::is_aligned(arr));
        INFO("mat_sum is  " << mat_sum);
        INFO("arr_sum is  " << arr_sum);
        INFO("M " << M);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }

    SECTION("F-contiguous; steal; -- change") {
        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2)));

        // attributes of the numpy array
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        size_t arr_S2 = arr.shape(2);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0.0;
        auto ptr = arr.unchecked<3>();
        for (size_t is = 0; is < arr_S2; is++) {
            for (size_t ic = 0; ic < arr_S1; ic++) {
                for (size_t ir = 0; ir < arr_S0; ir++) {
                    arr_sum += ptr(ir, ic, is);
                }
            }
        }

        // call function to be tested
        arma::Cube<double> M = carma::arr_to_cube<double>(std::move(arr));

        M.insert_cols(0, 2, true);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_S0 == M.n_rows);
        CHECK((arr_S1 + 2) == M.n_cols);
        CHECK((arr_S1) == M.n_slices);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }

#ifndef WIN32
    SECTION("F-contiguous; no copy; -- change") {
        bool copy = false;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2)));

        // call function to be tested
        arma::Cube<double> M = carma::arr_to_cube<double>(arr, copy);

        REQUIRE_THROWS(M.insert_slices(0, 2, true));
    }
#endif

#ifndef WIN32
    SECTION("F-contiguous; const; -- change") {
        const py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2)));

        // call function to be tested
        arma::Cube<double> M = carma::arr_to_cube<double>(arr);
        M.insert_cols(0, 2, true);
        M.insert_slices(0, 2, true);
    }
#endif

#ifndef WIN32
    SECTION("F-contiguous; steal; -- change") {
        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2)));

        // call function to be tested
        arma::Cube<double> M = carma::arr_to_cube<double>(std::move(arr));
        M.insert_cols(0, 2, true);
        M.insert_slices(0, 2, true);
    }
#endif

    SECTION("dimension exception") {
        bool copy = false;

        py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2)));

        REQUIRE_THROWS_AS(carma::arr_to_cube<double>(arr, copy), carma::ConversionError);
    }
} /* TEST_CASE ARR_TO_CUBE */

TEST_CASE("Test arr_to_mat_view", "[arr_to_mat_view]") {
    py::module np = py::module::import("numpy");
    py::module np_rand = py::module::import("numpy.random");

    SECTION("F-contiguous") {
        const py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2)));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0.0;
        auto* _ptr = reinterpret_cast<double*>(info.ptr);
        auto ptr = arr.unchecked<2>();
        for (size_t ic = 0; ic < arr_S1; ic++) {
            for (size_t ir = 0; ir < arr_S0; ir++) {
                arr_sum += ptr(ir, ic);
            }
        }

        // call function to be tested
        const arma::Mat<double> M = carma::arr_to_mat_view<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(_ptr == M.memptr());
    }

    SECTION("C-contiguous") {
        const py::array_t<double> arr = np_rand.attr("normal")(0, 1, py::make_tuple(100, 2));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0.0;
        double* _ptr = reinterpret_cast<double*>(info.ptr);
        auto ptr = arr.unchecked<2>();
        for (size_t ic = 0; ic < arr_S1; ic++) {
            for (size_t ir = 0; ir < arr_S0; ir++) {
                arr_sum += ptr(ir, ic);
            }
        }

        // call function to be tested
        const arma::Mat<double> M = carma::arr_to_mat_view<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(_ptr != M.memptr());
    }
}  // TEST_CASE ARR_TO_MAT_VIEW

TEST_CASE("Test arr_to_row_view", "[arr_to_row_view]") {
    py::module np = py::module::import("numpy");
    py::module np_rand = py::module::import("numpy.random");

    SECTION("F-contiguous") {
        const py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, 100));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        const arma::Row<double> M = carma::arr_to_row_view<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("C-contiguous") {
        const py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        const arma::Row<double> M = carma::arr_to_row_view<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_cols);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }
}  // TEST_CASE ARR_TO_ROW_VIEW

TEST_CASE("Test arr_to_col_view", "[arr_to_col_view]") {
    py::module np = py::module::import("numpy");
    py::module np_rand = py::module::import("numpy.random");

    SECTION("F-contiguous") {
        const py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, 100));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        const arma::Col<double> M = carma::arr_to_col_view<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("C-contiguous") {
        const py::array_t<double> arr = np_rand.attr("normal")(0, 1, 100);

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();
        const double* ptr = reinterpret_cast<double*>(info.ptr);

        // compute sum of array
        double arr_sum = 0.0;
        for (size_t i = 0; i < static_cast<size_t>(arr.size()); i++)
            arr_sum += ptr[i];

        //  call function to be tested
        const arma::Col<double> M = carma::arr_to_col_view<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }
}  // TEST_CASE ARR_TO_COL_VIEW

TEST_CASE("Test arr_to_cube_view", "[arr_to_cube_view]") {
    py::module np = py::module::import("numpy");
    py::module np_rand = py::module::import("numpy.random");

    SECTION("F-contiguous") {
        const py::array_t<double> arr = fArr(np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2)));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        size_t arr_S2 = arr.shape(2);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0.0;
        auto ptr = arr.unchecked<3>();
        for (size_t is = 0; is < arr_S2; is++) {
            for (size_t ic = 0; ic < arr_S1; ic++) {
                for (size_t ir = 0; ir < arr_S0; ir++) {
                    arr_sum += ptr(ir, ic, is);
                }
            }
        }

        // call function to be tested
        const arma::Cube<double> M = carma::arr_to_cube_view<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(arr_S2 == M.n_slices);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr == M.memptr());
    }

    SECTION("C-contiguous") {
        const py::array_t<double> arr = np_rand.attr("normal")(0, 1, py::make_tuple(100, 2, 2));

        // attributes of the numpy array
        size_t arr_N = arr.size();
        size_t arr_S0 = arr.shape(0);
        size_t arr_S1 = arr.shape(1);
        size_t arr_S2 = arr.shape(2);

        // get buffer for raw pointer
        py::buffer_info info = arr.request();

        // compute sum of array
        double arr_sum = 0.0;
        auto ptr = arr.unchecked<3>();
        for (size_t is = 0; is < arr_S2; is++) {
            for (size_t ic = 0; ic < arr_S1; ic++) {
                for (size_t ir = 0; ir < arr_S0; ir++) {
                    arr_sum += ptr(ir, ic, is);
                }
            }
        }

        // call function to be tested
        const arma::Cube<double> M = carma::arr_to_cube_view<double>(arr);

        double mat_sum = arma::accu(M);

        // variable for test status
        CHECK(arr_N == M.n_elem);
        CHECK(arr_S0 == M.n_rows);
        CHECK(arr_S1 == M.n_cols);
        CHECK(arr_S2 == M.n_slices);
        CHECK(std::abs(arr_sum - mat_sum) < 1e-6);
        CHECK(info.ptr != M.memptr());
    }
}  // TEST_CASE ARR_TO_CUBE_VIEW


/*
@copyright Louis Dionne 2014
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#include <boost/hana/detail/assert.hpp>
#include <boost/hana/detail/constexpr.hpp>
#include <boost/hana/functional.hpp>
using namespace boost::hana;

int main() {
    //! [main]
    BOOST_HANA_CONSTEXPR_LAMBDA auto minus = [](int x, int y, int z = 0) {
        return x - y - z;
    };

    BOOST_HANA_CONSTEXPR_ASSERT(minus(3, 0) == 3 - 0);
    BOOST_HANA_CONSTEXPR_ASSERT(flip(minus)(3, 0) == 0 - 3);

    BOOST_HANA_CONSTEXPR_ASSERT(minus(3, 0, 1) == 3 - 0 - 1);
    BOOST_HANA_CONSTEXPR_ASSERT(flip(minus)(3, 0, 1) == 0 - 3 - 1);
    //! [main]
}


#ifndef MYPLANARFACETRAVERSAL_HPP
#define MYPLANARFACETRAVERSAL_HPP

#include <GraphLayoutLibrary_global.h>
#include <iostream>

#include <vector>
#include <set>
#include <map>
#include <boost/next_prior.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/properties.hpp>

#include <boost/graph/adjacency_list.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/graph/lookup_edge.hpp>
#include <boost/ref.hpp>

using namespace boost;

  struct my_planar_face_traversal_visitor
  {
    void begin_traversal()
    {}

    void begin_face()
    {}

    template <typename Edge, typename adjFaces, typename dualEdges>
    void next_edge(Edge&, adjFaces&, dualEdges&)
    {}

    template <typename Vertex>
    void next_vertex(Vertex)
    {}

    void end_face()
    {}

    void end_traversal()
    {}

  };

  template<typename Graph,
           typename PlanarEmbedding,
           typename Visitor,
           typename EdgeIndexMap,
           typename adjFaces,
           typename dualEdges
           >
  void planar_face_traversal(const Graph& g,
                             PlanarEmbedding embedding,
                             Visitor& visitor, EdgeIndexMap em,
                             adjFaces& adjecentFaces,
                             dualEdges& dualE
                             )
  {
    typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
    typedef typename graph_traits<Graph>::edge_descriptor edge_t;
    typedef typename graph_traits<Graph>::vertex_iterator vertex_iterator_t;
    typedef typename graph_traits<Graph>::edge_iterator edge_iterator_t;
    typedef typename
      property_traits<PlanarEmbedding>::value_type embedding_value_t;
    typedef typename embedding_value_t::const_iterator embedding_iterator_t;

    typedef typename
      std::vector< std::set<vertex_t> > distinguished_edge_storage_t;
    typedef typename
      std::vector< std::map<vertex_t, edge_t> >
      distinguished_edge_to_edge_storage_t;

    typedef typename
      boost::iterator_property_map
        <typename distinguished_edge_storage_t::iterator, EdgeIndexMap>
      distinguished_edge_map_t;

    typedef typename
      boost::iterator_property_map
        <typename distinguished_edge_to_edge_storage_t::iterator, EdgeIndexMap>
      distinguished_edge_to_edge_map_t;

    distinguished_edge_storage_t visited_vector(num_edges(g));
    distinguished_edge_to_edge_storage_t next_edge_vector(num_edges(g));

    distinguished_edge_map_t visited(visited_vector.begin(), em);
    distinguished_edge_to_edge_map_t next_edge(next_edge_vector.begin(), em);

    vertex_iterator_t vi, vi_end;
    typename std::vector<edge_t>::iterator ei, ei_end;
    edge_iterator_t fi, fi_end;
    embedding_iterator_t pi, pi_begin, pi_end;

    visitor.begin_traversal();

    // Initialize the next_edge property map. This map is initialized from the
    // PlanarEmbedding so that get(next_edge, e)[v] is the edge that comes
    // after e in the clockwise embedding around vertex v.

    for(boost::tie(vi,vi_end) = vertices(g); vi != vi_end; ++vi)
      {
        vertex_t v(*vi);
        pi_begin = embedding[v].begin();
        pi_end = embedding[v].end();
        for(pi = pi_begin; pi != pi_end; ++pi)
          {
            edge_t e(*pi);
            std::map<vertex_t, edge_t> m = get(next_edge, e);
            m[v] = boost::next(pi) == pi_end ? *pi_begin : *boost::next(pi);
            put(next_edge, e, m);
          }
      }

    // Take a copy of the edges in the graph here, since we want to accomodate
    // face traversals that add edges to the graph (for triangulation, in
    // particular) and don't want to use invalidated edge iterators.
    // Also, while iterating over all edges in the graph, we single out
    // any self-loops, which need some special treatment in the face traversal.

    std::vector<edge_t> self_loops;
    std::vector<edge_t> edges_cache;
    std::vector<vertex_t> vertices_in_edge;

    for(boost::tie(fi,fi_end) = edges(g); fi != fi_end; ++fi)
      {
        edge_t e(*fi);
        edges_cache.push_back(e);
        if (source(e,g) == target(e,g))
          self_loops.push_back(e);
      }

    // Iterate over all edges in the graph
    ei_end = edges_cache.end();
    for(ei = edges_cache.begin(); ei != ei_end; ++ei)
      {

        edge_t e(*ei);
        vertices_in_edge.clear();
        vertices_in_edge.push_back(source(e,g));
        vertices_in_edge.push_back(target(e,g));

        typename std::vector<vertex_t>::iterator vi, vi_end;
        vi_end = vertices_in_edge.end();

        //Iterate over both vertices in the current edge
        for(vi = vertices_in_edge.begin(); vi != vi_end; ++vi)
          {

            vertex_t v(*vi);
            std::set<vertex_t> e_visited = get(visited, e);
            typename std::set<vertex_t>::iterator e_visited_found
              = e_visited.find(v);

            if (e_visited_found == e_visited.end())
              visitor.begin_face();

            while (e_visited.find(v) == e_visited.end())
              {
                visitor.next_vertex(v);
                visitor.next_edge(e, adjecentFaces, dualE);
                e_visited.insert(v);
                put(visited, e, e_visited);
                v = source(e,g) == v ? target(e,g) : source(e,g);
                e = get(next_edge, e)[v];
                e_visited = get(visited, e);
              }

            if (e_visited_found == e_visited.end())
              visitor.end_face();

          }

      }

    // Iterate over all self-loops, visiting them once separately
    // (they've already been visited once, this visitation is for
    // the "inside" of the self-loop)

    ei_end = self_loops.end();
    for(ei = self_loops.begin(); ei != ei_end; ++ei)
      {
        visitor.begin_face();
        visitor.next_edge(*ei, adjecentFaces, dualE);
        visitor.next_vertex(source(*ei,g));
        visitor.end_face();
      }

    visitor.end_traversal();
  }

  template<typename Graph, typename PlanarEmbedding, typename Visitor, typename adjFaces, typename dualEdges>
  inline void planar_face_traversal(const Graph& g,
                                    PlanarEmbedding embedding,
                                    Visitor& visitor,
                                    adjFaces& adjecentFaces,
                                    dualEdges& dualE
                                    )
  {
    planar_face_traversal(g, embedding, visitor, get(edge_index, g), adjecentFaces, dualE);
  }

#endif // MYPLANARFACETRAVERSAL_HPP


//######################################################################
//#   Refiner Module
//#
//#   Copyright (C) 2020 Siemens AG
//#   SPDX-License-Identifier: MIT
//#   Author 2020: This module has been developed by
//#                Roman Kaskman under supervision of Slobodan Ilic
//#######################################################################

#ifndef REFINER_OCCLUSION_HANDLER_HPP
#define REFINER_OCCLUSION_HANDLER_HPP

#include "rgbd_types.hpp"
#include <Eigen/Dense>
#include "configuration.h"
#include <opencv2/core/core.hpp>

class OcclusionHandler {

public:

  OcclusionHandler(Configuration configuration,
      std::vector<RgbdFile> rgbd_image_files, std::vector<Eigen::Matrix4d> camera_poses);

  std::vector<size_t> get_frame_ids_with_visible_model(int model_id, const Eigen::Matrix4d &model_pose);

private:

  bool is_object_visible(const cv::Mat& gt_depth, const cv::Mat& rendered_depth);

  Configuration configuration;
  std::vector<RgbdFile> rgbd_image_files;
  std::vector<Eigen::Matrix4d> camera_poses;

  float object_visibility_threshold;
  float occlusion_threshold;

};

#endif //REFINER_OCCLUSION_HANDLER_HPP


#include <iostream>
#include <vector>
#include <boost/filesystem.hpp>
#include <opencv4/opencv2/opencv.hpp>
#include <opencv4/opencv2/core/core.hpp>
#include <opencv4/opencv2/highgui/highgui.hpp>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/filters/statistical_outlier_removal.h>
#include <pcl/filters/voxel_grid.h>
#include <math.h>
#include <chrono>
#include <random>

using namespace std;
namespace fs = boost::filesystem;

template <class T>
std::string IntToStr(T n);

std::vector<cv::Mat> diff_depth(std::vector<cv::Mat> images, int reference);
int select_reference(std::vector<cv::Mat> images, std::vector<string> imageNames);
int select_reference_min(std::vector<cv::Mat> images, std::vector<string> imageNames);
std::vector<cv::Mat> interference_remove(std::vector<cv::Mat> images, std::vector<cv::Mat> interferenceROI);
cv::Mat medianImages(std::vector<cv::Mat> images,std::vector<cv::Mat> uninterferenceROI);
pcl::PointCloud<pcl::PointXYZRGB>::Ptr generatePointCloud(cv::Mat medianImage, string, bool saveFilterInfo, std::vector<bool> * filterPointIdent, int);
float medianPixel(std::vector<float> values);
cv::Mat medianRowsCols(cv::Mat image, int frameBufferSize);
float excludeExtreme(cv::Mat image,int i,bool type);
cv::Mat interpolation(cv::Mat image, cv::Mat binary);
cv::Mat repairInputData(cv::Mat image, cv::Mat interfROI,  cv::Mat median, std::vector<bool> * filterPointIdent, int count);
void saveLUT(cv::Mat depth, std::string path);
cv::Mat generateIslands(cv::Mat image);

string path;
float percentage;
float replace_thresh;
int rgbRows=0;
cv::Mat reference_rgb;
cv::Mat _importanceMap, _interferenceBinary;
cv::Rect myROI(0, 0, 512, 424);
pcl::PointCloud<pcl::PointXYZRGB>::Ptr medianCloud=nullptr;
int main(int argc, char **argv)
{

    std::chrono::system_clock::time_point then, now;

    path=argv[1];
    reference_rgb=cv::imread(path+argv[2],cv::IMREAD_COLOR);
    cv::Mat image=cv::Mat::zeros(reference_rgb.rows,reference_rgb.cols,CV_32FC1);
    rgbRows=reference_rgb.rows;
    reference_rgb=reference_rgb(myROI);
    cv::transpose(reference_rgb,reference_rgb);
    cvtColor(reference_rgb, reference_rgb, cv::COLOR_RGBA2RGB);

    percentage=std::stof(argv[3]);
    replace_thresh=std::stof(argv[4]);
    std::vector<cv::Mat> images;
    std::vector<string> imageNames;

    string ext{".txt"};
    int frameBufferSize=0;

    for(auto & p : fs::directory_iterator( path ))
    {
        if(p.path().extension() == ext)
        {

            ifstream depthTxt;
            depthTxt.open(p.path().string());
            std::cout<<p.path().string()<<std::endl;
            string line;
            double counter;
            int rows=0;
            int cols=0;
            while(getline(depthTxt,line))
            {
                if(rows==rgbRows)
                {
                    cols++;
                    rows=0;
                }
                float pixel_value= std::stof(line);
                image.at<float>(rows, cols, 0) = pixel_value;
                rows++;
            }
            depthTxt.close();

//            std::cout << p << std::endl;After thresholding
//            cv::Mat image=cv::imread(p.path().string(),cv::IMREAD_ANYCOLOR | cv::IMREAD_ANYDEPTH);
//            cv::Rect myROI(255, 125, 100, 175);
//            cv::Rect myROI(170, 65, 90, 175);
//            cvtColor(image, image, cv::COLOR_BGR2GRAY);
//            image = generateIslands(image);
            (void)generatePointCloud(image,path+"/data/original"+IntToStr(frameBufferSize), false, nullptr, frameBufferSize);

            cv::Mat tmpBinary;
            cv::Mat tmpDepth;
            cv::normalize(image, tmpDepth, 0, 255,cv::NORM_MINMAX);
//            cv::imwrite(path + "/data/origos"+IntToStr(frameBufferSize) + ".png",tmpDepth(myROI));

//            cv::inRange(image,80,750,tmpBinary);

            cv::Mat tmpImage;

            image.copyTo(tmpImage,tmpBinary);
//            (void)generatePointCloud(tmpImage,path+"/data/original"+IntToStr(frameBufferSize));

            images.emplace_back(tmpImage(myROI));
            imageNames.emplace_back(p.path().string());

            cv::normalize(tmpImage, tmpDepth, 0, 255,cv::NORM_MINMAX);
//            cv::imwrite(path + "/data/original"+IntToStr(frameBufferSize) + ".png",tmpDepth);
            frameBufferSize++;
        }
    }
    std::vector<bool>* filterPointIdent = new std::vector<bool>(1);

    then=std::chrono::system_clock::now();

//    auto reference = select_reference_min(images, imageNames);
    auto reference = select_reference(images, imageNames);
    auto interferenceROI = diff_depth(images, reference);
    auto uninterferenceROI = interference_remove(images,interferenceROI);

    auto medianImage = medianImages(images,images);

    auto filtered = medianRowsCols(medianImage,frameBufferSize);
    auto interpolated = interpolation(medianImage, _interferenceBinary);

//    cv::imwrite(path + "/data/interp_mask.hdr",interpolated);

    interpolated = interpolated+filtered;

//    medianCloud = generatePointCloud(interpolated,path+"/data/median");

        //    cv::normalize(interpolated, interpolated, 0, 255,cv::NORM_MINMAX);
//    cv::imwrite(path + "/data/median.hdr",interpolated);
//    saveLUT(interpolated,path);

    int i=0;
    for(auto image : images)
    {
        repairInputData(image,_interferenceBinary,interpolated,filterPointIdent,i);

//        cv::Mat tmpUnfilterMask, notBin;
//        cv::bitwise_not(_interferenceBinary,notBin);
//        image.copyTo(tmpUnfilterMask, notBin);
        (void)generatePointCloud(image,path+"/data/uninterfer_"+IntToStr(i),true, filterPointIdent, i);

        i++;
    }

    now=std::chrono::system_clock::now();
    std::cout <<std::chrono::duration_cast<std::chrono::milliseconds>(now - then).count() << " ms" << std::endl;

//    pcl::visualization::CloudViewer viewer ("Simple Cloud Viewer");
//    viewer.showCloud(cloud);
//    while (!viewer.wasStopped ())
//    {
//    }
}

int select_reference(std::vector<cv::Mat> images, std::vector<string> imageNames){
    //how many pixels are non zero. select image with biggest ROI
    int nonZero[images.size()];
    int interator=0;
    for(auto & image : images){
        nonZero[interator]=cv::countNonZero(image);
//        std::cout<<nonZero[interator]<<" "<<imageNames[interator]<<std::endl;
        interator++;
    }
    int N = sizeof(nonZero) / sizeof(int);
    int position = distance(nonZero, max_element(nonZero, nonZero + (sizeof(nonZero) / sizeof(int))));
//    std::cout<<"Reference: " << nonZero[position]<<" "<<imageNames[position]<<std::endl;
    return position;
}

int select_reference_min(std::vector<cv::Mat> images, std::vector<string> imageNames){
    //select image, where the ROI is minimal
    // count of zero pixels in image
    int nonZero[images.size()];
    int interator=0;
    for(auto & image : images){
        nonZero[interator]=(image.cols*image.rows)-cv::countNonZero(image);
//        std::cout<<nonZero[interator]<<" "<<imageNames[interator]<<std::endl;
        interator++;
    }
    int N = sizeof(nonZero) / sizeof(int);
    int position = distance(nonZero, max_element(nonZero, nonZero + (sizeof(nonZero) / sizeof(int))));
//    std::cout<<"Reference: " << nonZero[position]<<" "<<imageNames[position]<<std::endl;
    return position;
}

std::vector<cv::Mat> diff_depth(std::vector<cv::Mat> images, int reference){
    int i=0;
    std::vector<cv::Mat> interferenceROI;
    auto referenceImage=images[reference];
    for(auto & image : images){
        cv::Mat diffImage = cv::Mat::zeros(referenceImage.rows,referenceImage.cols,referenceImage.type());
        cv::absdiff(referenceImage,image,diffImage);
        cv::Mat tmp;
        cv::normalize(diffImage, tmp, 0, 255,cv::NORM_MINMAX);
        cv::Mat element = cv::getStructuringElement( cv::MORPH_RECT,cv::Size( 1*1 + 1, 1*1+1 ),cv::Point( 1, 1 ) );
        cv::dilate( tmp, tmp, element );
//        cv::imwrite(path + "/data/interferenceROI"+IntToStr(i) + ".png",tmp);
        interferenceROI.emplace_back(diffImage);
        i++;
    }
    return interferenceROI;
}

std::vector<cv::Mat> interference_remove(std::vector<cv::Mat> images,std::vector<cv::Mat> interferenceROI){
    int i=0;
    std::vector<cv::Mat> uninterferenceROI;
    for(auto & image : images){
        cv::Mat tmpBinary;
        cv::threshold(interferenceROI[i], tmpBinary, 100, 255, cv::THRESH_BINARY_INV);
        cv::Mat tmp;
        cv::Mat tmpDepth;
        tmpBinary.convertTo(tmpBinary, CV_8U);
//        cv::imwrite(path +"/data/binary"+IntToStr(i) + ".png",interferenceROI[i]);
        image.copyTo(tmpDepth, tmpBinary);
        cv::normalize(tmpDepth, tmp, 0, 255,cv::NORM_MINMAX);
//        cv::imwrite(path +"/data/uninterferenceROI"+IntToStr(i) + ".png",tmp);
        uninterferenceROI.emplace_back(tmpDepth);
        i++;
    }
    return uninterferenceROI;
}

cv::Mat medianImages(std::vector<cv::Mat> images,std::vector<cv::Mat> uninterferenceROI){
    cv::Mat medianImage = cv::Mat::zeros(uninterferenceROI[0].rows,uninterferenceROI[0].cols,uninterferenceROI[0].type());
    cv::Mat importanceMap = cv::Mat::zeros(uninterferenceROI[0].rows,uninterferenceROI[0].cols,CV_8U);
    for(int i=0; i<uninterferenceROI[0].cols;i++){  //riadky
        for(int j=0; j<uninterferenceROI[0].rows;j++){ //stlpce
            std::vector<float> values;
            for(auto & image : uninterferenceROI){ //image
                if(image.at<float>(j,i)>1) {
                    values.emplace_back(image.at<float>(j,i));
                }
            }
            if(values.size()>1) {
                importanceMap.at<uchar>(j, i)=static_cast<uint>(values.size());
                sort(values.begin(), values.end());
                if(values.size() % 2 !=0)
                    medianImage.at<float>(j, i)=values[(values.size()-1)/2];
                else
                    medianImage.at<float>(j, i)=(values[floor((values.size()-1)/2)] + values[ceil((values.size()-1)/2)])/2;
            }
        }
    }
    importanceMap.copyTo(_importanceMap);

    cv::Mat tmp;
    cv::normalize(importanceMap, tmp, 0, 255,cv::NORM_MINMAX);
    cv::imwrite(path+"/data/importanceMap.png",tmp);
    cv::normalize(medianImage, tmp, 0, 255,cv::NORM_MINMAX);
    cv::imwrite(path+"/data/median.png",tmp);
    (void)generatePointCloud(medianImage,"median",false,nullptr,0);
    return medianImage;
}

cv::Mat medianRowsCols(cv::Mat image, int frameBufferSize){
    struct median_value{
        float value;
        int position;
    };
    cv::Mat filtered;
    std::vector<median_value> col_median;
    std::vector<median_value> row_median;

    for(int i=0; i<image.cols;i++){  //riadky
        median_value value;
        value.value = excludeExtreme(image,i,true);
        value.position = i;
        col_median.emplace_back(value);
    }
    for(int j=0; j<image.rows;j++){
        median_value value;
        value.value = excludeExtreme(image,j,false);
        value.position = j;
        row_median.emplace_back(value);
    }

    cv::Mat tmpBinary, tmpImage;
    cv::inRange(_importanceMap,1,round(frameBufferSize*percentage),_interferenceBinary);
//    cv::imwrite(path+"/data/binary.png",_interferenceBinary);
    cv::bitwise_not(_interferenceBinary,tmpBinary);
//    cv::imwrite(path+"/data/notBinary.png",tmpBinary);
    image.copyTo(filtered,tmpBinary);
    auto cloud = generatePointCloud(filtered,path+"/data/filtered", false, nullptr, 0);

//    for(int i=0;i<image.cols;i++){
//        for(int j=0;j<image.rows;j++){
//            auto value=image.at<float>(j,i);
//            std::cout<<i<<" "<<j<<" "<<col_median[i].value<<" "<<row_median[j].value<<" "<<value<<std::endl;
//            if(col_median[i].value-value>15 || row_median[i].value-value>15 ) {
//                filtered.at<float>(j,i)= 0;
//            }
//        }
//    }

//    for (auto median : col_median) {
//        std::cout<<median.value<<" ";
//    }
//    std::cout<<std::endl;

//    for (auto median : row_median) {
//        std::cout<<median.value<<" ";
//    }
//    std::cout<<std::endl;
    cv::imwrite(path+"/data/filtered.png",filtered);
    return filtered;
}

float excludeExtreme(cv::Mat image,int x,bool type){
    std::vector<float> values;
    if(type==false){
        for(int i=0; i<image.rows;i++){
            if(image.at<float>(x,i)>1) {
                values.emplace_back(image.at<float>(x,i));
            }
        }
    } else {
        for(int i=0; i<image.cols;i++){
            if(image.at<float>(i,x)>1) {
                values.emplace_back(image.at<float>(i,x));
            }
        }
    }
    if(values.size()>1)
        return medianPixel(values);
    else
        return 0;
}

float medianPixel(std::vector<float> values){
    sort(values.begin(), values.end());

    if(values.size() % 2 !=0)
        return values[(values.size()-1)/2];
    else
        return (values[floor((values.size()-1)/2)] + values[ceil((values.size()-1)/2)])/2;
}

cv::Mat interpolation(cv::Mat image, cv::Mat binary){

    struct pixelInfo{
        float value;
        cv::Point point;
    };

    std::vector<std::vector<pixelInfo>> points;
    std::vector<pixelInfo> vektorik;

    cv::Mat tmpImageHorizontal = cv::Mat::zeros(image.rows,image.cols,image.type());
    cv::Mat tmpImageVertical = cv::Mat::zeros(image.rows,image.cols,image.type());
    cv::Mat tmpImage = cv::Mat::zeros(image.rows,image.cols,image.type());

    for(int i=0;i<image.rows;i++){
        for(int j=0;j<image.cols;j++){
            if(binary.at<uchar>(i,j)==255){
                pixelInfo data;
                data.value=255;
                data.point.x=j;
                data.point.y=i;
                vektorik.push_back(data);

                if(binary.at<uchar>(i,j+1)==0){
                    points.push_back(vektorik);
                    vektorik.clear();
                }
            }
        }
    }
    std::vector<std::vector<pixelInfo>> interPoints;
    auto size = points.size();
    for(int i=0; i<size; i++){
        auto leftPositionValue = image.at<float>(points[i].front().point.y,points[i].front().point.x-1);
        auto rightPositionValue = image.at<float>(points[i].back().point.y,points[i].back().point.x+1);
        if((int)leftPositionValue!=0 && (int)rightPositionValue!=0) {
            interPoints.push_back(points[i]);
        }
    }
    for(int i=0; i<interPoints.size();i++){
        auto leftPositionValue = image.at<float>(interPoints[i].front().point.y,interPoints[i].front().point.x-1);
        auto rightPositionValue = image.at<float>(interPoints[i].back().point.y,interPoints[i].back().point.x+1);
        auto differenceValue = leftPositionValue - rightPositionValue;
        auto diffX = interPoints[i].back().point.x-interPoints[i].front().point.x+1;
        auto accValue = differenceValue/diffX;
//        std::cout<<interPoints[i].front().point.x<<" "<< interPoints[i].front().point.y<<" "<< interPoints[i].back().point.x<<" "<< interPoints[i].back().point.y <<" "
//                <<leftPositionValue<<" "<<rightPositionValue<<" "<< differenceValue <<" "<<" "<<diffX<<" "<<accValue<<std::endl;
        int k=1;
        for(auto point : interPoints[i]){
            tmpImageHorizontal.at<float>(point.point.y,point.point.x)=round(leftPositionValue-accValue*k);
            k++;
        }
    }
//    cv::imwrite(path+"/data/horizontal.png",tmpImageHorizontal);

    points.clear();
    vektorik.clear();
    interPoints.clear();

    for(int i=0;i<image.cols;i++){
        for(int j=0;j<image.rows;j++){
            if(binary.at<uchar>(j,i)==255){
                pixelInfo data;
                data.value=255;
                data.point.x=i;
                data.point.y=j;
                vektorik.push_back(data);

                if(binary.at<uchar>(j+1,i)==0){
                    points.push_back(vektorik);
                    vektorik.clear();
                }
            }
        }
    }

    size = points.size();
    for(int i=0; i<size; i++){
        auto leftPositionValue = image.at<float>(points[i].front().point.y-1,points[i].front().point.x);
        auto rightPositionValue = image.at<float>(points[i].back().point.y+1,points[i].back().point.x);
        if((int)leftPositionValue!=0 && (int)rightPositionValue!=0) {
            interPoints.push_back(points[i]);
        }
    }

    for(int i=0; i<interPoints.size();i++){
        auto leftPositionValue = image.at<float>(interPoints[i].front().point.y-1,interPoints[i].front().point.x);
        auto rightPositionValue = image.at<float>(interPoints[i].back().point.y+1,interPoints[i].back().point.x);
        auto differenceValue = leftPositionValue - rightPositionValue;
        auto diffY = interPoints[i].back().point.y-interPoints[i].front().point.y+1;
        auto accValue = differenceValue/diffY;
//        std::cout<<interPoints[i].front().point.x<<" "<< interPoints[i].front().point.y<<" "<< interPoints[i].back().point.x<<" "<< interPoints[i].back().point.y <<" "
//                <<leftPositionValue<<" "<<rightPositionValue<<" "<< differenceValue <<" "<<" "<<diffX<<" "<<accValue<<std::endl;
        int k=1;
        if(accValue<10){
            for(auto point : interPoints[i]){
                tmpImageVertical.at<float>(point.point.y,point.point.x)=round(leftPositionValue-accValue*k);
                k++;
            }
        }
    }

    cv::imwrite(path+"/data/vertical.png",tmpImageVertical);

    for(int i=0;i<image.rows;i++){
        for(int j=0;j<image.cols;j++){
            auto leftPositionValue = tmpImageHorizontal.at<float>(i,j);
            auto rightPositionValue = tmpImageVertical.at<float>(i,j);
            if((int)rightPositionValue != 0 && (int)leftPositionValue!=0){
                tmpImage.at<float>(i,j)=round((leftPositionValue+rightPositionValue)/2);
            } else if ((int)rightPositionValue == 0 || (int)leftPositionValue == 0 ){
                tmpImage.at<float>(i,j)=round((leftPositionValue+rightPositionValue));
            }
        }
    }

    return tmpImage;
}

pcl::PointCloud<pcl::PointXYZRGB>::Ptr generatePointCloud(cv::Mat medianImage, std::string name, bool saveFilterInfo, std::vector<bool> *filterPointIdent, int count){

   cv::transpose(medianImage, medianImage);
   cv::Mat tmpImage;
//    transpose(reference_rgb, tmpImage);
//    cv::imwrite("depth_med.png",medianImage);
//    cv::imwrite("ref.png",tmpImage);

    pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGB>);
    cloud->width = static_cast<uint32_t>(medianImage.cols);
    cloud->height = static_cast<uint32_t>(medianImage.rows);
    cloud->is_dense = false;
    cloud->points.resize( cloud->width* cloud->height);

    uint32_t n=0;
    double x=0, y=0, z=0;
    const float fx=363.709,fy=363.709,cx=254.799,cy=209.699;
//    cvtColor(reference_rgb, reference_rgb, cv::COLOR_RGBA2RGB);
//    flip(reference_rgb, reference_rgb, +1);
    ofstream myfile;

    if(saveFilterInfo){
        myfile.open( name + ".txt");
    }
    for (int x_side = 0; x_side < medianImage.cols; x_side++)
    {
        for (int y_side = 0; y_side < medianImage.rows; y_side++)
        {
            const float icx(cx), icy(cy);
            const float ifx(1/fx), ify(1/fy);
            double Z = medianImage.at<float>(x_side, y_side)/1000.0f;
 //           auto Z=medianImage.data[512*y_side+x_side]/1000.0f;

            if(saveFilterInfo){
              if(Z>0 || filterPointIdent->at(n)){
                bool a = filterPointIdent->at(n);
                myfile<<static_cast<int>(a)<<"\n";
              }
            }

            if(Z>0 )
            {
                x=(x_side + 0.5 - icx)*ifx*Z;
                y=(y_side + 0.5 - icy)*ify*Z;
                z=Z;

            }
            else
            {
                x=NAN;
                y=NAN;
                z=NAN;
            }

            cloud->points[n].x=x;
            cloud->points[n].y=y;
            cloud->points[n].z=z;
//            cloud->points[n].r=maskRGB.at<cv::Vec3b>(x_side,y_side)[2];
//            cloud->points[n].g=maskRGB.at<cv::Vec3b>(x_side,y_side)[1];
//            cloud->points[n].b=maskRGB.at<cv::Vec3b>(x_side,y_side)[0];

            cv::Vec3b tmp_rgb=reference_rgb.at<cv::Vec3b>(x_side,y_side);
            if(tmp_rgb[2]==0 && tmp_rgb[1]==0 && tmp_rgb[0]==0)
            {
              tmp_rgb[2]=255;
              tmp_rgb[1]=255;
              tmp_rgb[0]=255;
            }

            cloud->points[n].r=tmp_rgb[2];
            cloud->points[n].g=tmp_rgb[1];
            cloud->points[n].b=tmp_rgb[0];
            n++;
        }
    }

    if(saveFilterInfo){
        myfile.close();
    }

    std::vector<int> removedPoints;
    pcl::removeNaNFromPointCloud(*cloud,*cloud,removedPoints);
    pcl::io::savePLYFileBinary(name+".ply",*cloud);
    return cloud;
}

cv::Mat repairInputData(cv::Mat image, cv::Mat interfROI,  cv::Mat median, std::vector<bool> *filterPointIdent, int count){

//    cv::imwrite(path+"/data/image"+ IntToStr(count) + ".png",image);
//    cv::imwrite(path+"/data/interf"+ IntToStr(count) + ".png",interfROI);
    filterPointIdent->clear();

    for(int i=0;i<image.cols;i++){
        for(int j=0;j<image.rows;j++){
//            if(interfROI.at<uint>(i,j)>0){
//                std::cout<<interfROI.at<uint>(i,j)<<std::endl;
                auto imagePixel = image.at<float>(i,j);
                auto medianPixel = median.at<float>(i,j);

                auto var = abs(imagePixel-medianPixel);

                if(var>replace_thresh){
//                    image.at<float>(i,j)=medianPixel;
                    filterPointIdent->emplace_back(true);
                    image.at<float>(i,j)=0;

                } else {
                    filterPointIdent->emplace_back(false);
                }

//            }
        }
    }
    (void)generatePointCloud(image,path+"/data/cloud"+IntToStr(count),true,filterPointIdent,count);
    return image;
}

void saveLUT(cv::Mat depth, std::string path)
{
#define ir_depth_width 512
#define ir_depth_height 424
    int i,j;

    ofstream myfile;
    myfile.open(path + "/data/median.txt");

    for(i = 0; i < depth.cols; i++)
    {
        for(j = 0; j < depth.rows; j++)
        {
            double orig = depth.at<float>(j,i);
            myfile<<orig<<"\n";

        }
    }

    myfile.close();
}

cv::Mat generateIslands(cv::Mat image){
    float lower_bound = 650;
    float upper_bound = 653;
    std::uniform_real_distribution<float> unif(lower_bound,upper_bound);
    std::default_random_engine re;
    for(int i=0; i < 20; i++){
        for(int j=0; j<20; j++){
            image.at<float>(100+i,100+j)=unif(re);
        }
    }
    return image;
}

template <class T>
std::string IntToStr(T n){
    std::stringstream result;
    result << n;
    return result.str();
}


#pragma once

#include <mbgl/text/collision_feature.hpp>
#include <mbgl/text/placement_config.hpp>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wshadow"
#ifdef __clang__
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#endif
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wdeprecated-register"
#pragma GCC diagnostic ignored "-Wshorten-64-to-32"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/index/rtree.hpp>
#pragma GCC diagnostic pop

namespace mbgl {

namespace bg = boost::geometry;
namespace bgm = bg::model;
namespace bgi = bg::index;
typedef bgm::point<float, 2, bg::cs::cartesian> CollisionPoint;
typedef bgm::box<CollisionPoint> Box;
typedef std::tuple<Box, CollisionBox, IndexedSubfeature> CollisionTreeBox;
typedef bgi::rtree<CollisionTreeBox, bgi::linear<16, 4>> Tree;

class IndexedSubfeature;

class CollisionTile {
public:
    explicit CollisionTile(PlacementConfig);

    float placeFeature(const CollisionFeature&, const bool allowOverlap, const bool avoidEdges);
    void insertFeature(CollisionFeature&, const float minPlacementScale, const bool ignorePlacement);

    std::vector<IndexedSubfeature> queryRenderedSymbols(const mapbox::geometry::box<int16_t>&, const float scale);

    const PlacementConfig config;

    const float minScale = 0.5f;
    const float maxScale = 2.0f;
    float yStretch;

private:
    float findPlacementScale(float minPlacementScale,
            const Point<float>& anchor, const CollisionBox& box,
            const Point<float>& blockingAnchor, const CollisionBox& blocking);
    Box getTreeBox(const Point<float>& anchor, const CollisionBox& box, const float scale = 1.0);

    Tree tree;
    Tree ignoredTree;
    std::array<float, 4> rotationMatrix;
    std::array<float, 4> reverseRotationMatrix;
    std::array<CollisionBox, 4> edges;
};

} // namespace mbgl