/*
 * Copyright (C) 2015 Hamza Merzić
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/
#include "pqp_environment.h"

#include <Eigen/Dense>
#include <Eigen/Geometry>
#include <fstream>
#include <sstream>
#include <string>
#include <algorithm>

PqpEnvironment::PqpEnvironment(const std::vector<std::string>&
                                   robot_model_files,
                               const std::string& dh_table_file,
                               const std::string& obstacles_model_file,
                               RandomSpaceGeneratorInterface *random_generator,
                               const int sample_space_size)
    : obstacles_(new PQP_Model), conf_sample_space_(nullptr),
      sample_space_size_(sample_space_size), bubble_counter_(0),
      collision_counter_(0) {
  if (!LoadRobotParameters(dh_table_file)) throw "DH table problem!";
  if (!LoadRobotModel(robot_model_files)) throw "Robot model problem!";
  if (!LoadObstacles(obstacles_model_file)) throw "Obstacles problem!";
  if (!GenerateSampleSpace(random_generator, sample_space_size))
    throw "Sample space not generated!";

  dimension_ = segments_.size();
}

bool PqpEnvironment::LoadRobotModel(
      const std::vector<std::string>& robot_model_files) {
  try {
    EMatrix R = EMatrix::Identity();
    EVector3f T (0.0, 0.0, 0.0);

    int segments_index = 0;
    for (const auto& model_file : robot_model_files) {
      if (segments_index >= 1)
        dh_table_[segments_index - 1].InverseTransform(R, T);

      double axis_length, radius;
      EVector3f axis = dh_table_[segments_index].translation();
      axis.normalize();

      segments_.emplace_back(parser_.GetTransformModel(model_file, R, T,
          axis, &axis_length, &radius));
      capsules_.emplace_back(axis * axis_length, radius);
      ++segments_index;
    }

    return true;
  } catch (...) {
    throw;
  }
  return false;  // Input file not present
}

// TODO(hamza): Add limits parsing
bool PqpEnvironment::LoadRobotParameters(const std::string& parameters_file) {
  std::ifstream input_file (parameters_file.c_str());
  if (input_file) {
    try {
      input_file.seekg(0, std::ios::end);            // End of file
      std::streampos length (input_file.tellg());    // Read the size
      input_file.seekg(0, std::ios::beg);            // Return to beginning

      std::vector<char> buffer (length);
      input_file.read(&buffer[0], length);

      // Move buffer to stringstream parser
      std::stringstream parser;
      parser.rdbuf()->pubsetbuf(&buffer[0], length);

      double theta, d, a, alpha;
      while (!parser.eof()) {
        parser >> theta >> d >> a >> alpha;
        dh_table_.emplace_back(theta, d, a, alpha);
      }

      return true;
    }
    catch(...) {
      throw "File error!";
    }
  }
  return false;  // Input file not present
}

bool PqpEnvironment::LoadObstacles(const std::string& obstacles_model_file) {
  try {
    obstacles_ = std::unique_ptr<PQP_Model>(
      parser_.GetModel(obstacles_model_file));

    return true;
  }
  catch (...) {
    throw;
  }
  return false;  // Input file not present
}

bool PqpEnvironment::GenerateSampleSpace(
    RandomSpaceGeneratorInterface* random_generator,
    const int sample_space_size) {
  try {
    // Create configuration sample space
    conf_sample_space_ = std::unique_ptr<FlannPointArray> (new FlannPointArray (
        flann::Matrix<double> (
        random_generator->CreateSampleSpace(sample_space_size).release(),
        sample_space_size, static_cast<int>(segments_.size())),
        flann::KDTreeIndexParams(4)));
    conf_sample_space_->buildIndex();

    return true;
  } catch (...) {
    return false;
  }
}

size_t PqpEnvironment::AddPoint(EVectorXd& q) {
  conf_sample_space_->addPoints(flann::Matrix<double> (q.data(), 1, q.size()));
  return conf_sample_space_->size() - 1;
}

void PqpEnvironment::RemovePoint(int point_index) {
  conf_sample_space_->removePoint(point_index);
}

bool PqpEnvironment::MakeBubble(const EVectorXd& coordinates,
    std::shared_ptr<Bubble>& bubble) {
  ++bubble_counter_;
  EMatrix R = EMatrix::Identity();
  EVector3f T (0.0, 0.0, 0.0);
  // Static environment/cylinder transform
  EMatrix R_temp = EMatrix::Identity();
  EVector3f T_temp (0.0, 0.0, 0.0);

  PQP_DistanceResult distance_res;
  bubble = std::shared_ptr<Bubble>(new Bubble(coordinates));

  double axis_distance = 0;

  EVector3f endpoint, prev_endpoint;
  prev_endpoint.setZero();

  // Finding minimal distance to obstacles and updating bubble's first
  // dimension
  for (size_t i = 0; i < dimension_; ++i) {
    R = R * Eigen::AngleAxisf(coordinates[i], EVector::UnitZ());
    PQP_Distance(&distance_res, reinterpret_cast<PQP_REAL(*)[3]>(R.data()),
        T.data(), segments_.at(i).get(),
        reinterpret_cast<PQP_REAL(*)[3]>(R_temp.data()), T_temp.data(),
        obstacles_.get(), 0.0, 0.0);

    if (distance_res.Distance() < kMinDistanceToObstacles) {
      bubble.reset();
      return false;  // Too close to obstacle
    }
    if (distance_res.Distance() < bubble->distance())
      bubble->distance() = distance_res.Distance();

    endpoint = T + R * capsules_[i].first;
    axis_distance = std::max(axis_distance, capsules_[i].second +
        std::max(sqrt(endpoint(0) * endpoint(0) + endpoint(1) * endpoint(1)),
          sqrt(prev_endpoint(0) * prev_endpoint(0) +
            prev_endpoint(1) * prev_endpoint(1))));
    prev_endpoint = endpoint;

    dh_table_.at(i).Transform(R, T);
  }

  bubble->SetDimension(0, bubble->distance() / axis_distance);

  // Update bubble dimensions
  for (size_t i = 1; i < dimension_; ++i) {
    R = EMatrix::Identity();
    T << 0.0, 0.0, 0.0;

    axis_distance = 0.0;
    prev_endpoint.setZero();
    for (size_t k = i; k < dimension_; ++k) {
      R = R * Eigen::AngleAxisf(coordinates[k], EVector::UnitZ());

      endpoint = T + R * capsules_[i].first;

      axis_distance = std::max(axis_distance, capsules_[i].second +
        std::max(sqrt(endpoint(0) * endpoint(0) + endpoint(1) * endpoint(1)),
          sqrt(prev_endpoint(0) * prev_endpoint(0) +
            prev_endpoint(1) * prev_endpoint(1))));
      prev_endpoint = endpoint;
      if (bubble->distance() / axis_distance < bubble->GetDimension(i)) {
        bubble->SetDimension(i, bubble->distance() / axis_distance);
      }
      dh_table_.at(k).Transform(R, T);
    }
  }
  return true;
}

double PqpEnvironment::DistanceQuery(EVectorXd& q) {
  EMatrix R = EMatrix::Identity();
  EVector3f T (0.0, 0.0, 0.0);
  // Static environment transform
  EMatrix R_temp = EMatrix::Identity();
  EVector3f T_temp (0.0, 0.0, 0.0);

  PQP_DistanceResult distance_res;
  double min_distance = INFINITY;  // Initialized at infinity

  for (size_t i = 0; i < dimension_; ++i) {
    R = R * Eigen::AngleAxisf(q[i], EVector::UnitZ());
    PQP_Distance(&distance_res, reinterpret_cast<PQP_REAL(*)[3]>(R.data()),
      T.data(), segments_.at(i).get(),
      reinterpret_cast<PQP_REAL(*)[3]>(R_temp.data()), T_temp.data(),
      obstacles_.get(), 0.0, 0.0);

    if (distance_res.Distance() < kMinDistanceToObstacles)
      return 0;  // Too close to obstacle, return 0
    if (distance_res.Distance() < min_distance)
      min_distance = distance_res.Distance();

    dh_table_.at(i).Transform(R, T);
  }

  return min_distance;
}

bool PqpEnvironment::CollisionQuery(EVectorXd& q) {
  ++collision_counter_;
  EMatrix R = EMatrix::Identity();
  EVector3f T (0.0, 0.0, 0.0);
  // Static environment transform
  EMatrix R_temp = EMatrix::Identity();
  EVector3f T_temp (0.0, 0.0, 0.0);

  PQP_CollideResult collision_res;

  for (size_t i = 0; i < dimension_; ++i) {
    R = R * Eigen::AngleAxisf(q[i], EVector::UnitZ());
    PQP_Collide(&collision_res, reinterpret_cast<PQP_REAL(*)[3]>(R.data()),
      T.data(), segments_.at(i).get(),
      reinterpret_cast<PQP_REAL(*)[3]>(R_temp.data()), T_temp.data(),
      obstacles_.get(), PQP_FIRST_CONTACT);

    if (collision_res.NumPairs())
      return false;  // Collision

    dh_table_.at(i).Transform(R, T);
  }

  return true;
}

std::vector<int> PqpEnvironment::KnnQuery(EVectorXd& q, int k) {
  flann::Matrix<double> query (q.data(), 1, q.size());

  std::vector<int> vector_indices (k);
  flann::Matrix<int> indices (vector_indices.data(), 1, k);
  flann::Matrix<double> dists (new double[k], 1, k);

  conf_sample_space_->knnSearch(query, indices, dists, k,
                                flann::SearchParams(128));

  delete[] dists.ptr();
  return vector_indices;
}


// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2014 Benoit Steiner <benoit.steiner.goog@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

#define EIGEN_TEST_NO_LONGDOUBLE
#define EIGEN_TEST_NO_COMPLEX
#define EIGEN_TEST_FUNC cxx11_tensor_random_cuda
#define EIGEN_DEFAULT_DENSE_INDEX_TYPE int
#define EIGEN_USE_GPU

#include "main.h"
#include <Eigen/CXX11/Tensor>

static void test_default()
{
  Tensor<std::complex<float>, 1> vec(6);
  vec.setRandom();

  // Fixme: we should check that the generated numbers follow a uniform
  // distribution instead.
  for (int i = 1; i < 6; ++i) {
    VERIFY_IS_NOT_EQUAL(vec(i), vec(i-1));
  }
}

void test_cxx11_tensor_random_cuda()
{
  CALL_SUBTEST(test_default());
}


#include <stan/math/rev.hpp>
#include <boost/mp11.hpp>
#include <gtest/gtest.h>
#include <test/unit/math/rev/functor/test_fixture_ode.hpp>
#include <test/unit/math/rev/functor/test_fixture_ode_sho_ctl.hpp>
#include <test/unit/math/rev/functor/ode_test_functors.hpp>

/**
 *
 * Use same solver functor type for both w & w/o tolerance control
 */
template <typename solve_type, typename... Ts>
using ode_test_tuple = std::tuple<solve_type, solve_type, Ts...>;

/**
 * Outer product of test types
 */
using harmonic_oscillator_ctl_test_types = boost::mp11::mp_product<
    ode_test_tuple, ::testing::Types<ode_adjoint_functor>,
    ::testing::Types<double, stan::math::var_value<double>>,  // t
    ::testing::Types<double, stan::math::var_value<double>>,  // y0
    ::testing::Types<double, stan::math::var_value<double>>   // theta
    >;

TYPED_TEST_SUITE_P(harmonic_oscillator_ctl_test);
TYPED_TEST_P(harmonic_oscillator_ctl_test, no_error) { this->test_good(); }
TYPED_TEST_P(harmonic_oscillator_ctl_test, error_conditions) {
  this->test_bad();
}
TYPED_TEST_P(harmonic_oscillator_ctl_test, value) {
  stan::math::nested_rev_autodiff nested;

  this->test_value(0.0);
  this->test_value(1.0);
  this->test_value(-1.0);

  if (std::is_same<std::tuple_element_t<2, TypeParam>, double>::value
      && std::is_same<std::tuple_element_t<3, TypeParam>, double>::value
      && std::is_same<std::tuple_element_t<4, TypeParam>, double>::value) {
    EXPECT_EQ(stan::math::nested_size(), 0);
  } else {
    EXPECT_GT(stan::math::nested_size(), 0);
  }
}

REGISTER_TYPED_TEST_SUITE_P(harmonic_oscillator_ctl_test, no_error,
                            error_conditions, value);
INSTANTIATE_TYPED_TEST_SUITE_P(StanOde, harmonic_oscillator_ctl_test,
                               harmonic_oscillator_ctl_test_types);


/* Copyright © 2018 Apple Inc. All rights reserved.
 *
 * Use of this source code is governed by a BSD-3-clause license that can
 * be found in the LICENSE.txt file or at https://opensource.org/licenses/BSD-3-Clause
 */

#define BOOST_TEST_MODULE test_neural_nets_model_exporter

#include <unity/toolkits/coreml_export/neural_net_models_exporter.hpp>

#include <map>
#include <memory>
#include <string>
#include <vector>

#include <boost/test/unit_test.hpp>
#include <unity/toolkits/coreml_export/mlmodel_include.hpp>
#include <util/test_macros.hpp>

namespace turi {
namespace object_detection {
namespace {

using turi::neural_net::model_spec;

BOOST_AUTO_TEST_CASE(test_object_detector_export_coreml_with_nms) {

    const std::string test_annotations_name = "test_annotations";
    const std::string test_image_name = "test_image";
    const std::vector<std::string> test_class_labels = { "label1", "label2" };
    static constexpr size_t test_max_iterations = 4;
    double test_iou_threshold = 0.55;
    double test_confidence_threshold = 0.15;

    std::map<std::string, flexible_type> options;
    options["include_non_maximum_suppression"] = 1;
    options["iou_threshold"] = test_iou_threshold;
    options["confidence_threshold"] = test_confidence_threshold;

    flex_dict user_defined_metadata;
    user_defined_metadata.emplace_back("model", "model");
    user_defined_metadata.emplace_back("max_iterations", test_max_iterations);
    user_defined_metadata.emplace_back("training_iterations", test_max_iterations);
    user_defined_metadata.emplace_back("include_non_maximum_suppression", "True");
    user_defined_metadata.emplace_back("feature", test_image_name);
    user_defined_metadata.emplace_back("annotations", test_annotations_name);
    user_defined_metadata.emplace_back("classes", "label1, label2");
    user_defined_metadata.emplace_back("type", "object_detector");
    user_defined_metadata.emplace_back("confidence_threshold", options["confidence_threshold"]);
    user_defined_metadata.emplace_back("iou_threshold", options["iou_threshold"]);

    flex_list t_class_labels = flex_list(test_class_labels.begin(), test_class_labels.end());

    model_spec yolo_nn_spec;
    yolo_nn_spec.add_convolution("test_layer", "image", 16, 16, 3, 3, 1, 1,
                                 model_spec::padding_type::SAME,
                                 /* weight_init_fn */ [](float*w , float* w_end) {
                                     for (int i = 0; i < w_end - w; ++i) {
                                         w[i] = static_cast<float>(i);
                                     }
                                 });

    std::shared_ptr<coreml::MLModelWrapper> model_wrapper =
    export_object_detector_model(yolo_nn_spec,
                                 13 * 32,
                                 13 * 32,
                                 test_class_labels.size(),
                                 13 * 13 * 15,
                                 std::move(user_defined_metadata), std::move(t_class_labels),
                                 std::move(options));

    std::shared_ptr<CoreML::Model> c_model = model_wrapper->coreml_model();
    auto p_model = c_model->getProto();

    const ::CoreML::Specification::Pipeline &pipeline = p_model.pipeline();

    const auto &model_nms = pipeline.models(1).nonmaximumsuppression();

    auto labels = model_nms.stringclasslabels();
    for (int i = 0; i < labels.vector_size(); i++) {
        TS_ASSERT_EQUALS(labels.vector(i), test_class_labels[i]);
    }
    TS_ASSERT_EQUALS(model_nms.iouthreshold(), test_iou_threshold);
    TS_ASSERT_EQUALS(model_nms.confidencethreshold(), test_confidence_threshold);
    TS_ASSERT_EQUALS(model_nms.confidenceinputfeaturename(), "raw_confidence");
    TS_ASSERT_EQUALS(model_nms.coordinatesinputfeaturename(), "raw_coordinates");
    TS_ASSERT_EQUALS(model_nms.iouthresholdinputfeaturename(), "iouThreshold");
    TS_ASSERT_EQUALS(model_nms.confidencethresholdinputfeaturename(), "confidenceThreshold");
    TS_ASSERT_EQUALS(model_nms.confidenceoutputfeaturename(), "confidence");
    TS_ASSERT_EQUALS(model_nms.coordinatesoutputfeaturename(), "coordinates");

}

}  // namespace
}  // namespace object_detection
}  // namespace turi


//---------------------------------------------------------------------------//
//  MIT License
//
//  Copyright (c) 2020-2021 Mikhail Komarov <nemo@nil.foundation>
//  Copyright (c) 2020-2021 Nikita Kaskov <nemo@nil.foundation>

//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.
//---------------------------------------------------------------------------//

#ifndef FILECOIN_STORAGE_PROOFS_CORE_MERKLE_PROOF_HPP
#define FILECOIN_STORAGE_PROOFS_CORE_MERKLE_PROOF_HPP

#include <algorithm>
#include <vector>

#include <boost/variant.hpp>

#include <nil/crypto3/hash/algorithm/hash.hpp>

#include <nil/filecoin/storage/proofs/core/merkle/merkle.hpp>
#include <nil/filecoin/storage/proofs/core/proof/proof.hpp>
#include <nil/filecoin/storage/proofs/core/crypto/feistel.hpp>
#include <nil/filecoin/storage/proofs/core/path_element.hpp>

namespace nil {
    namespace filecoin {
        namespace merkletree {
            template<std::size_t A, std::size_t B, std::size_t C>
            std::size_t base_path_length(std::size_t leaves) {
                std::size_t l;
                if (C > 0) {
                    l = leaves / C / B;
                } else if (B > 0) {
                    l = leaves / B;
                } else {
                    l = leaves;
                }

                return graph_height<A>(l) - 1;
            }

            template<std::size_t A, std::size_t B, std::size_t C>
            std::size_t compound_path_length(std::size_t leaves) {
                std::size_t len = base_path_length<A, B, C>(leaves);
                if (B > 0) {
                    len += 1;
                }

                if (C > 0) {
                    len += 1;
                }

                return len;
            }

            template<std::size_t A, std::size_t B, std::size_t C>
            std::size_t compound_tree_height(std::size_t leaves) {
                // base layer
                std::size_t a = graph_height<A>(leaves) - 1;

                // sub tree layer
                std::size_t b;
                if (B > 0) {
                    b = B - 1;
                } else {
                    b = 0;
                }

                // top tree layer
                std::size_t c;
                if (C > 0) {
                    c = C - 1;
                } else {
                    c = 0;
                }

                return a + b + c;
            }

            template<typename Hash>
            struct Proof_basic_policy {
                typedef std::array<uint8_t, Hash::digest_size> hash_result_type;
                constexpr static const std::size_t hash_digest_size = Hash::digest_size;
            };

            template<typename Hash, std::size_t BaseTreeArity = 2>
            struct Proof {

                typedef typename MerkleTree_basic_policy<Hash>::hash_result_type element;
                constexpr static const std::size_t element_size = MerkleTree_basic_policy<Hash>::hash_digest_size;

                // Optional proofs at immediate lower level from current.  Should
                // be None at the base layer.
                std::shared_ptr<Proof<element, BaseTreeArity>> sub_tree_proof;
                std::size_t top_layer_nodes;         // arity of top layer
                std::size_t sub_tree_layer_nodes;    // arity of sub-tree
                std::vector<element> lemma; // layer
                std::vector<std::size_t> path; // branch index

                /// Creates new MT inclusion proof
                template<std::size_t TopLayerArity, std::size_t SubTreeArity>
                Proof(std::shared_ptr<Proof<element, BaseTreeArity>> sub_tree_proof, const std::vector<element> &lemma, const
                      std::vector<std::size_t> &path) : sub_tree_proof(sub_tree_proof), top_layer_nodes(TopLayerArity),
                    sub_tree_layer_nodes(SubTreeArity), lemma(lemma), path(path){
                    if (TopLayerArity == 0 && SubTreeArity == 0) {
                        BOOST_ASSERT_MSG(lemma.size() > 2, "Invalid lemma length (short)");
                        BOOST_ASSERT_MSG(lemma.size() == utilities::get_merkle_proof_lemma_len(path.size() + 1, BaseTreeArity),
                            "Invalid lemma length");
                    }
                }

                /// Return proof target leaf
                element item() {
                    return *lemma.begin();
                }

                /// Return sub tree root
                element sub_tree_root() {
                    assert(sub_tree_layer_nodes > 0 && sub_tree_proof.is_some());
                    return sub_tree_proof.root();
                }

                /// Return tree root
                element root() {
                    return *(lemma.end() - 1);
                }

                /// Validates sub-tree proofs with the specified arity.
                bool validate_sub_tree_proof(std::size_t arity) {
                    // Ensure that the sub_tree validates to the root of that
                    // sub_tree.
                    bool valid = sub_tree_proof.unwrap().validate::<Algorithm<element>>();
                    if (!valid) {
                            return valid;
                        }

                    // Validate top-most/current layer
                    //
                    // Check that the remaining proof matches the tree root (note
                    // that Proof::validate at the base layer cannot handle a
                    // proof this small, so this is a version specific for what we
                    // know we have in this case).
                    auto a = Algorithm<T>::default();
                    a.reset();
                    const auto node_count = arity;
                    const auto h = {
                        std::vector<T> nodes;
                        nodes.reserve(node_count);

                        auto cur_index = 0;
                        for (const auto j = 0; j < node_count; ++j) {
                            if j == self.path()[0] {
                                nodes.push(self.sub_tree_root().clone());
                            } else {
                                nodes.push(self.lemma()[cur_index].clone());
                                cur_index += 1;
                            }
                        }

                        if cur_index != node_count - 1 {
                            return false;
                        }

                        a.multi_node(&nodes, 0)
                    };

                    return h == root();
                }

                /// Verifies MT inclusion proof
                bool validate() {
                    if (top_layer_nodes > 0) {
                        // Special Top layer handling here.
                        BOOST_ASSERT_MSG(sub_tree_proof,
                                "Sub tree proof must be present for validation");

                        return validate_sub_tree_proof<Algorithm>(top_layer_nodes);
                    }

                    if (sub_tree_layer_nodes > 0) {
                        // Sub-tree layer handling here.
                        BOOST_ASSERT_MSG(sub_tree_proof,
                                "Sub tree proof must be present for validation");

                        return validate_sub_tree_proof<Algorithm>(sub_tree_layer_nodes);
                    }

                    // Base layer handling here.
                    BOOST_ASSERT_MSG(sub_tree_layer_nodes == 0, "Base layer proof must have 0 as sub-tree layer node count");
                    BOOST_ASSERT_MSG(top_layer_nodes == 0, "Base layer proof must have 0 as top layer node count");
                    BOOST_ASSERT_MSG(!sub_tree_proof, "Sub tree proof must be None");

                    std::size_t size = lemma.size();
                    if (size < 2) {
                        return false;
                    }

                    std::size_t branches = BaseTreeArity;
                    auto a = Algorithm<element>::default();
                    auto h = this->item();
                    auto path_index = 1;

                    for (size_t i = 1; i < size - 1; i += branches - 1) {
                        a.reset();
                        h = {
                            std::vector<element> nodes;
                            nodes.reserve(branches);
                            auto cur_index = 0;
                            for (j = 0; j < branches; ++j) {
                                if j == self.path[path_index - 1] {
                                    nodes.push(h.clone());
                                } else {
                                    nodes.push(self.lemma[i + cur_index].clone());
                                    cur_index += 1;
                                }
                            }

                            if cur_index != branches - 1 {
                                return false;
                            }

                            path_index += 1;
                            a.multi_node(&nodes, i - 1)
                        };
                    }

                    return h == root();
                }

                    /// Verifies MT inclusion proof and that leaf_data is the original leaf data for which proof was generated.
                template<template<typename> class Algorithm>
                bool validate_with_data(leaf_data: &dyn Hashable<A>) {
                    auto a = Algorithm<T>::default();
                    leaf_data.hash(&a);
                    const auto item = a.hash();
                    a.reset();
                    const auto leaf_hash = a.leaf(item);

                    if (leaf_hash == item()) {
                        return validate<Algorithm>();
                    } else {
                        return false;
                    }
                }
            };

//            /// Interface to abstract over the concept of Merkle Proof.
//            template<typename Hash, std::size_t BaseArity, std::size_t SubTreeArity, std::size_t TopTreeArity,
//                     typename FieldType = typename crypto3::algebra::curves::bls12<381>::scalar_field_type>
//            struct BasicMerkleProof {
//                typedef Hash hash_type;
//                typedef FieldType field_type;
//                typedef typename field_type::value_type fr_value_type;
//
//                constexpr static const std::size_t base_arity = BaseArity;
//                constexpr static const std::size_t sub_tree_arity = SubTreeArity;
//                constexpr static const std::size_t top_tree_arity = TopTreeArity;
//
//                /// Try to convert a merkletree proof into this structure.
//                static BasicMerkleProof<Hash, BaseArity, SubTreeArity, TopTreeArity>
//                    try_from_proof(const Proof<typename Hash::digest_type, BaseArity> &p) {
//                }
//
//                std::vector<std::pair<std::vector<fr_value_type>, std::size_t>> as_options() {
//                    return path()
//                        .iter()
//                        .map(| v | {(v .0.iter().copied().map(Into::into).map(Some).collect(), Some(v .1), )})
//                        .collect::<Vec<_>>();
//                }
//
//                std::pair<fr_value_type, std::vector<std::pair<std::vector<fr_value_type>, std::size_t>>>
//                    into_options_with_leaf() {
//                    const auto leaf = leaf();
//                    const auto path = path();
//                    (Some(leaf.into()),
//                     path.into_iter()
//                         .map(| (a, b) | {(a.iter().copied().map(Into::into).map(Some).collect(), Some(b), )})
//                         .collect::<Vec<_>>(), )
//                }
//
//                std::vector<std::pair<std::vector<fr_value_type>, std::size_t>> as_pairs() {
//                    for (int i = 0; i < path().size(); i++) {
//
//                    }
//                        .iter()
//                        .map(| v | (v .0.iter().copied().map(Into::into).collect(), v .1))
//                        .collect::<Vec<_>>();
//                }
//
//                virtual bool verify() const = 0;
//
//                /// Validates the MerkleProof and that it corresponds to the supplied node.
//                ///
//                /// TODO: audit performance and usage in case verification is
//                /// unnecessary based on how it's used.
//                virtual bool validate(std::size_t node) {
//                    if (!verify()) {
//                        return false;
//                    }
//
//                    return node == path_index();
//                }
//
//                virtual bool validate_data(const typename Hash::digest_type &data) {
//                    if (!verify()) {
//                        return false;
//                    }
//
//                    return leaf() == data;
//                }
//
//                virtual typename Hash::digest_type leaf() = 0;
//                virtual typename Hash::digest_type root() = 0;
//                virtual std::size_t size() = 0;
//                virtual std::vector<std::pair<std::vector<typename Hash::digest_type>, std::size_t>> path() = 0;
//
//                std::size_t path_index() {
//                    return std::accumulate(
//                        path().begin(), path().end(), 0,
//                        [&](std::size_t acc, typename std::vector<std::pair<std::vector<typename Hash::digest_type>,
//                                                                            std::size_t>>::value_type &val) -> std::size_t {
//                            return (acc + BaseArity) + val.second;
//                        });
//                }
//
//                bool proves_challenge(std::size_t challenge) {
//                    path_index() == challenge;
//                }
//
//                /// Calcluates the exected length of the full path, given the number of leaves in the base layer.
//                std::size_t expected_len(std::size_t leaves) {
//                    return compound_path_length<BaseArity, SubTreeArity, TopTreeArity>(leaves);
//                }
//            };
//
//            template<typename Hash, std::size_t BaseArity>
//            struct InclusionPath {
//                /// Calculate the root of this path, given the leaf as input.
//                typename Hash::digest_type root(const typename Hash::digest_type &leaf) {
//                    using namespace nil::crypto3;
//                    accumulator_set<Hash> acc;
//                    std::accumulate(path.begin(), path.end(), leaf,
//                                    [&](typename Hash::digest_type acc,
//                                        typename std::vector<PathElement<Hash, BaseArity>>::value_type &v) {
//
//                                    });
//                    auto a = H::Function::default();
//                    (0..self.path.len())
//                        .fold(
//                            leaf, | h, height | {
//                                a.reset();
//
//                                const auto index = self.path[height].index;
//                                auto nodes = self.path[height].hashes.clone();
//                                nodes.insert(index, h);
//
//                                a.multi_node(&nodes, height)
//                            })
//                }
//
//                std::size_t size() {
//                    return path.size();
//                }
//
//                bool empty() {
//                    return path.empty();
//                }
//
//                std::size_t path_index() {
//                    return std::accumulate(
//                        path.begin(), path.end(), 0,
//                        [&](std::size_t acc, typename std::vector<PathElement<Hash, BaseArity>>::value_type &v) {
//                            return (acc * BaseArity) + v.index;
//                        });
//                }
//
//                std::vector<PathElement<Hash, BaseArity>> path;
//            };
//
//            template<typename Hash, std::size_t BaseArity>
//            struct SingleProof {
//                template<template<typename, std::size_t> class Proof>
//                static SingleProof<Hash, BaseArity> try_from_proof(const Proof<typename Hash::digest_type, BaseArity> &p) {
//                    return proof_to_single(p, 1);
//                }
//
//                bool verify() {
//                    return root == path.root(leaf);
//                }
//
//                std::size_t size() {
//                    return path.size() * (BaseArity - 1) + 2;
//                }
//
//                std::vector<std::pair<std::vector<typename Hash::digest_type>, std::size_t>> path() {
//                    return path.iter().map(| x | (x.hashes.clone(), x.index)).collect::<Vec<_>>();
//                }
//
//                std::size_t path_index() {
//                    return path.path_index();
//                }
//
//                /// Root of the merkle tree.
//                typename Hash::digest_type root;
//                /// The original leaf data for this prof.
//                typename Hash::digest_type leaf;
//                /// The path from leaf to root.
//                InclusionPath<Hash, BaseArity> path;
//            };
//
//            template<typename Hash, std::size_t BaseArity, std::size_t SubTreeArity>
//            struct SubProof {
//                static SubProof<Hash, BaseArity, SubTreeArity>
//                    try_from_proof(const Proof<typename Hash::digest_type, BaseArity> &p) {
//                    BOOST_ASSERT_MSG(p.sub_layer_nodes() == SubTreeArity, "sub arity mismatch");
//                    BOOST_ASSERT_MSG(p.sub_tree_proof, "Cannot generate sub proof without a base-proof");
//                    std::shared_ptr<Proof<typename Hash::digest_type, BaseArity>> base_p = p.sub_tree_proof;
//
//                    // Generate SubProof
//                    typename Hash::digest_type root = p.root();
//                    typename Hash::digest_type leaf = base_p.item();
//                    InclusionPath<Hash, BaseArity> base_proof =
//                        extract_path<typename Hash::digest_type, BaseArity>(base_p.lemma(), base_p.path(), 1);
//                    InclusionPath<Hash, SubTreeArity> sub_proof =
//                        extract_path<typename Hash::digest_type, SubTreeArity>(p.lemma(), p.path(), 0);
//
//                    return {base_proof, sub_proof, root, leaf};
//                }
//
//                bool verify() {
//                    root == sub_proof.root(base_proof.root(leaf));
//                }
//
//                std::size_t size() {
//                    return SubTreeArity;
//                }
//
//                std::vector<std::pair<std::vector<typename Hash::digest_type>, std::size_t>> path() {
//                    return base_proof.iter()
//                        .map(| x | (x.hashes.clone(), x.index))
//                        .chain(self.sub_proof.iter().map(| x | (x.hashes.clone(), x.index)))
//                        .collect();
//                }
//
//                std::size_t path_index() {
//                    std::size_t base_proof_leaves = 1;
//                    for (int i = 0; i < base_proof.size(); i++) {
//                        base_proof_leaves *= BaseArity;
//                    }
//
//                    std::size_t sub_proof_index = sub_proof.path_index();
//
//                    return (sub_proof_index * base_proof_leaves) + base_proof.path_index();
//                }
//
//                InclusionPath<Hash, BaseArity> base_proof;
//
//                InclusionPath<Hash, SubTreeArity> sub_proof;
//
//                typename Hash::digest_type root;
//                /// The original leaf data for this prof.
//
//                typename Hash::digest_type leaf;
//            };
//
//            template<typename Hash, std::size_t BaseArity, std::size_t SubTreeArity, std::size_t TopTreeArity>
//            struct TopProof {
//                TopProof<Hash, BaseArity, SubTreeArity, TopTreeArity>
//                    try_from_proof(const Proof<typename Hash::digest_type, BaseArity> &p) {
//                    BOOST_ASSERT_MSG(p.top_layer_nodes() == TopTreeArity, "top arity mismatch");
//                    BOOST_ASSERT_MSG(p.sub_layer_nodes() == SubTreeArity, "sub arity mismatch");
//
//                    BOOST_ASSERT_MSG(p.sub_tree_proof, "Cannot generate top proof without a sub-proof");
//                    const auto sub_p = p.sub_tree_proof;
//
//                    BOOST_ASSERT_MSG(sub_p.sub_tree_proof, "Cannot generate top proof without a base-proof");
//                    const auto base_p = sub_p.sub_tree_proof;
//
//                    const auto root = p.root();
//                    const auto leaf = base_p.item();
//
//                    return {extract_path<Hash, BaseArity>(base_p.lemma(), base_p.path(), 1), extract_path<Hash, SubTreeArity>(sub_p.lemma(), sub_p.path(), 0), extract_path<Hash, TopTreeArity>(p.lemma(), p.path(), 0), root, leaf};
//                }
//
//                bool verify() {
//                    root == top_proof.root(sub_proof.root(base_proof.root(leaf)));
//                }
//
//                std::size_t size() {
//                    return TopTreeArity;
//                }
//
//                std::vector<std::pair<std::vector<typename Hash::digest_type>, std::size_t>> path() {
//                    return base_proof.iter()
//                        .map(| x | (x.hashes.clone(), x.index))
//                        .chain(self.sub_proof.iter().map(| x | (x.hashes.clone(), x.index)))
//                        .chain(self.top_proof.iter().map(| x | (x.hashes.clone(), x.index)))
//                        .collect();
//                }
//
//                std::size_t path_index() {
//                    std::size_t base_proof_leaves = 1;
//                    for (int i = 0; i < base_proof.size(); i++) {
//                        base_proof_leaves *= BaseArity;
//                    }
//
//                    return (sub_proof.path_index() * base_proof_leaves) +
//                           (top_proof.path_index() * base_proof_leaves * SubTreeArity) + base_proof.path_index();
//                }
//
//                InclusionPath<Hash, BaseArity> base_proof;
//
//                InclusionPath<Hash, SubTreeArity> sub_proof;
//
//                InclusionPath<Hash, TopTreeArity> top_proof;
//                /// Root of the merkle tree.
//
//                typename Hash::digest_type root;
//                /// The original leaf data for this prof.
//                typename Hash::digest_type leaf;
//            };
//
//            template<typename Hash, std::size_t BaseArity, std::size_t SubTreeArity, std::size_t TopTreeArity>
//            using ProofData = boost::variant<SingleProof<Hash, BaseArity>, SubProof<Hash, BaseArity, SubTreeArity>,
//                                             TopProof<Hash, BaseArity, SubTreeArity, TopTreeArity>>;
//
//            template<typename Hash, std::size_t BaseArity, std::size_t SubTreeArity, std::size_t TopTreeArity>
//            struct MerkleProof : public BasicMerkleProof<Hash, BaseArity, SubTreeArity, TopTreeArity> {
//                typedef typename Hash::digest_type digest_type;
//                MerkleProof(std::size_t n) :
//                    data(SingleProof<Hash, BaseArity>(std::vector<PathElement<Hash, BaseArity>>(n), root, leaf)) {
//                }
//
//                virtual bool verify() const override {
//                    return false;
//                }
//                virtual bool validate(std::size_t node) override {
//                }
//                virtual bool validate_data(const digest_type &data) override {
//                }
//                virtual digest_type leaf() override {
//                    return nullptr;
//                }
//                virtual digest_type root() override {
//                    return nullptr;
//                }
//                virtual std::vector<std::pair<std::vector<typename Hash::digest_type>, std::size_t>> path() override {
//                    return std::vector<std::pair<std::vector<typename Hash::digest_type>, std::size_t>>();
//                }
//
//                ProofData<Hash, BaseArity, SubTreeArity, TopTreeArity> data;
//            };
//
//            /// 'lemma_start_index' is required because sub/top proofs start at
//            /// index 0 and base proofs start at index 1 (skipping the leaf at the
//            /// front)
//            template<typename Hash, std::size_t BaseArity>
//            InclusionPath<Hash, BaseArity> extract_path(const std::vector<typename Hash::digest_type> &lemma,
//                                                        const std::vector<std::size_t> &path,
//                                                        std::size_t lemma_start_index) {
//                std::vector<PathElement<Hash, BaseArity>> res;
//
//                for (int i = 0; i < path.size(); i++) {
//                    res.emplace_back(
//                        std::vector<typename Hash::digest_type>(lemma.begin() + lemma_start_index + BaseArity * i,
//                                                                lemma.begin() + lemma_start_index + BaseArity * (i + 1)),
//                        index);
//                }
//
//                return InclusionPath<Hash, BaseArity>(res);
//            }
//
//            /// Converts a merkle_light proof to a SingleProof
//            template<typename Hash, std::size_t BaseArity, std::size_t TargetArity,
//                     template<typename, std::size_t> class Proof>
//            SingleProof<Hash, TargetArity>
//                proof_to_single(const Proof<Hash, BaseArity> &proof, std::size_t lemma_start_index,
//                                typename Hash::digest_type &sub_root = typename Hash::digest_type()) {
//                typename Hash::digest_type root = proof.root();
//                typename Hash::digest_type leaf = sub_root.emplty() ? sub_root : proof.item();
//
//                InclusionPath<Hash, TargetArity> path =
//                    extract_path<Hash, TargetArity>(proof.lemma(), proof.path(), lemma_start_index);
//
//                return {path, root, leaf};
//            }
        }    // namespace merkletree
    }    // namespace filecoin
}    // namespace nil

#endif


#define BOOST_TEST_MODULE Sort
#include <algorithm>
#include <boost/iterator/counting_iterator.hpp>
#include <boost/test/unit_test.hpp>
#include <vexcl/vector.hpp>
#include <vexcl/sort.hpp>
#include "context_setup.hpp"

BOOST_AUTO_TEST_CASE(sort_keys)
{
    const size_t n = 1000 * 1000;

    std::vector<float> k = random_vector<float>(n);
    vex::vector<float> keys(ctx, k);

    vex::sort(keys);
    vex::copy(keys, k);

    BOOST_CHECK( std::is_sorted(k.begin(), k.end()) );
}

BOOST_AUTO_TEST_CASE(sort_keys_vals_default)
{
    const size_t n = 1000 * 1000;

    std::vector<int  > k = random_vector<int  >(n);
    std::vector<float> v = random_vector<float>(n);
    std::vector<int>   p(n);

    vex::vector<int  > keys(ctx, k);
    vex::vector<float> vals(ctx, v);

    for(size_t i = 0; i < p.size(); ++i) p[i] = static_cast<int>(i);
    std::stable_sort(p.begin(), p.end(), [&](int i, int j) { return k[i] < k[j]; });

    vex::sort_by_key(keys, vals);

    check_sample(keys, [&](size_t pos, int val) {
            BOOST_CHECK_EQUAL(val, k[p[pos]]);
            });

    check_sample(vals, [&](size_t pos, float val) {
            BOOST_CHECK_EQUAL(val, v[p[pos]]);
            });
}

BOOST_AUTO_TEST_CASE(sort_keys_vals_custom_op)
{
    const size_t n = 1000 * 1000;

    std::vector<int  > k = random_vector<int  >(n);
    std::vector<float> v = random_vector<float>(n);
    std::vector<int>   p(n);

    vex::vector<int  > keys(ctx, k);
    vex::vector<float> vals(ctx, v);

    for(size_t i = 0; i < p.size(); ++i) p[i] = static_cast<int>(i);

    struct even_first_t {
        typedef bool result_type;
        even_first_t() {}

        VEX_DUAL_FUNCTOR(result_type, (int, a)(int, b),
            char bit1 = 1 & a;
            char bit2 = 1 & b;
            if (bit1 == bit2) return a < b;
            return bit1 < bit2;
            )
    } even_first;

    std::stable_sort(p.begin(), p.end(), [&](int i, int j) -> bool {
            int a = k[i];
            int b = k[j];
            return even_first(a, b);
            });

    vex::sort_by_key(keys, vals, even_first);

    check_sample(keys, [&](size_t pos, int val) {
            BOOST_CHECK_EQUAL(val, k[p[pos]]);
            });

    check_sample(vals, [&](size_t pos, float val) {
            BOOST_CHECK_EQUAL(val, v[p[pos]]);
            });
}

BOOST_AUTO_TEST_CASE(sort_keys_tuple)
{
    const size_t n = 1000 * 1000;

    std::vector<int>   k1 = random_vector<int>  (n);
    std::vector<float> k2 = random_vector<float>(n);

    vex::vector<int>   keys1(ctx, k1);
    vex::vector<float> keys2(ctx, k2);

    struct less_t {
        typedef bool result_type;
        less_t() {}

        VEX_DUAL_FUNCTOR(result_type, (int, a1)(float, a2)(int, b1)(float, b2),
            return (a1 == b1) ? (a2 < b2) : (a1 < b1);
            )
    } less;

    vex::sort(boost::fusion::vector_tie(keys1, keys2), less );
    vex::copy(keys1, k1);
    vex::copy(keys2, k2);

    BOOST_CHECK( std::is_sorted(
                boost::counting_iterator<size_t>(0),
                boost::counting_iterator<size_t>(n),
                [&](size_t i, size_t j) {
                    return std::make_tuple(k1[i], k2[i]) < std::make_tuple(k1[j], k2[j]);
                } ) );
}

BOOST_AUTO_TEST_CASE(sort_keys_vals_tuple)
{
    const size_t n = 1000 * 1000;

    std::vector<int>     k1 = random_vector<int>    (n);
    std::vector<float>   k2 = random_vector<float>  (n);
    std::vector<cl_long> v1 = random_vector<cl_long>(n);
    std::vector<short>   v2 = random_vector<short>  (n);

    vex::vector<int>     keys1(ctx, k1);
    vex::vector<float>   keys2(ctx, k2);
    vex::vector<cl_long> vals1(ctx, v1);
    vex::vector<short>   vals2(ctx, v2);

    std::vector<int> p(n);
    for(size_t i = 0; i < p.size(); ++i) p[i] = static_cast<int>(i);

    struct less_t {
        typedef bool result_type;
        less_t() {}

        VEX_DUAL_FUNCTOR(result_type, (int, a1)(float, a2)(int, b1)(float, b2),
            return (a1 == b1) ? (a2 < b2) : (a1 < b1);
            )
    } less;

    std::stable_sort(p.begin(), p.end(), [&](int i, int j) {
            return less(k1[i], k2[i], k1[j], k2[j]);
            });

    vex::sort_by_key(
            boost::fusion::vector_tie(keys1, keys2),
            boost::fusion::vector_tie(vals1, vals2),
            less
            );

    check_sample(keys1, [&](size_t pos, int val) {
            BOOST_CHECK_EQUAL(val, k1[p[pos]]);
            });

    check_sample(keys2, [&](size_t pos, float val) {
            BOOST_CHECK_EQUAL(val, k2[p[pos]]);
            });

    check_sample(vals1, [&](size_t pos, cl_long val) {
            BOOST_CHECK_EQUAL(val, v1[p[pos]]);
            });

    check_sample(vals2, [&](size_t pos, short val) {
            BOOST_CHECK_EQUAL(val, v2[p[pos]]);
            });
}

BOOST_AUTO_TEST_SUITE_END()