#include <string>
#include<iostream>
#include <algorithm>    // std::partial_sort

//Used for time measurement
#include <chrono>

//Support for messages in the cluster
#include <mpi.h>

//Used for interaction with the user
#include <boost/program_options.hpp>

//Used for image manipulation
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>

namespace po = boost::program_options;

//sort the window using insertion sort
//insertion sort is best for this sorting
void insertionSort(int window[], int len)
{
    int temp, i , j;
    for(i = 0; i < len; i++){
        temp = window[i];
        for(j = i-1; j >= 0 && temp < window[j]; j--){
            window[j+1] = window[j];
        }
        window[j+1] = temp;
    }
}

void display(std::string imgAdd, int windowSize=2){
	int windowElements = (windowSize*2 + 1)*(windowSize*2 + 1);
	cv::Mat src, dst;

	// Load an image
	src = cv::imread(imgAdd, CV_LOAD_IMAGE_GRAYSCALE);
	int imgSize = src.rows*src.cols;
	int imgRows = src.rows;
	int imgCols = src.cols;

	if( !src.data )
	{ return; }

	dst = src.clone();

	int* window = (int*) malloc(windowElements*sizeof(int));
	for(int i = 0; i < src.rows*src.cols ; i++){

		int cnt = 0;
		// Pick up window element
		for(int u = -windowSize; u <= windowSize; u++){
			for(int v = -windowSize; v <= windowSize; v++){

				int row = (i + v + u*src.rows) % imgRows;
				int col = (i + v + u*src.rows) / imgRows;

				if( (row > 0) && (row < imgRows) && (col > 0) && (col < imgCols) ){
					window[cnt] = src.data[i + v + u*src.rows];
				}
				else{
					assert(cnt < windowsElements);
					window[cnt] = 128;
				}
				cnt++;
			}
		}

		// sort the window to find median
		std::partial_sort (window, window + windowSize/2 + 1, window + windowSize);

		// assign the median to centered element of the matrix
		dst.data[i] = window[windowSize/2];
	}

	cv::namedWindow("final");
	cv::imshow("final", dst);

	cv::namedWindow("initial");
	cv::imshow("initial", src);

	cv::waitKey();
}

int main(int ac, char* av[]){
    try{
        po::options_description desc("Allowed Options");
        desc.add_options()
                ("help,h", "Produce help message")
                ("file,f", po::value<std::string>(), "File to be processed")
		("kernelSize,k", po::value<int>()->default_value(1), "Size of the kernel to apply")
        ;

        po::variables_map vm;
        po::store(po::parse_command_line(ac, av, desc), vm);
        po::notify(vm);

        if(vm.count("file")){
            display(vm["file"].as<std::string>(), vm["kernelSize"].as<int>());
        }
        else {//Includes help message
            std::cout << "This program applies a median filter to an image, and distributes the job between several processors and machines\n" << desc << "\n";
            return 0;
        }

    }
    catch(std::exception& e) {
        std::cerr << "error: " << e.what() << "\n";
        return 1;
    }
    catch(...) {
        std::cerr << "Exception of unknown type!\n";
    }

    return 0;
}


//==================================================================================================
/**
  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
**/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_DEFINITION_SPLIT_HIGH_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_DEFINITION_SPLIT_HIGH_HPP_INCLUDED

#include <boost/simd/config.hpp>
#include <boost/simd/detail/dispatch/function/make_callable.hpp>
#include <boost/simd/detail/dispatch/hierarchy/functions.hpp>
#include <boost/simd/detail/dispatch.hpp>

namespace boost { namespace simd
{
  namespace tag
  {
    BOOST_DISPATCH_MAKE_TAG(ext, split_high_, boost::dispatch::abstract_<split_high_>);
  }

  namespace ext
  {
    BOOST_DISPATCH_FUNCTION_DECLARATION(tag, split_high_);
  }

  BOOST_DISPATCH_CALLABLE_DEFINITION(tag::split_high_,split_high);
} }

#endif


#include <boost/program_options.hpp>

#include "main.hxx"

#include "gfx/context.hxx"
#include "platform/window.hxx"

#include "input/input_manager.hxx"

#include "loaders/image_loader.hxx"

#include "math/perlin.hxx"

#include "gfx/framebuffer.hxx"
#include "gfx/render_pass.hxx"
#include "gfx/shader.hxx"
#include "gfx/image.hxx"
#include "gfx/shader_storage.hxx"
#include "gfx/vertex_array.hxx"

#include "camera/camera.hxx"
#include "camera/camera_controller.hxx"

#include "raytracer/bvh.hxx"
#include "raytracer/material.hxx"
#include "raytracer/physics.hxx"
#include "raytracer/primitives.hxx"

auto constexpr kVERTEX_SEMANTIC_POSITION = 0u;

auto constexpr kBVH_TREE_BINDING = 1u;
auto constexpr kOUT_IMAGE_BINDING = 2u;
auto constexpr kLAMBERTIAN_BUFFER_BINDING = 2u;
auto constexpr kMETAL_BUFFER_BINDING = 3u;
auto constexpr kDIELECTRIC_BUFFER_BINDING = 4u;
auto constexpr kEMISSIVE_BUFFER_BINDING = 5u;
auto constexpr kPRIMITIVES_BINDING = 6u;
auto constexpr kCAMERA_BINDING = 7u;
auto constexpr kPERLIN_NOISE_BINDING = 8u;

auto constexpr kDEBUG_SPHERICAL_FIBONACCI_LATTICE = false;
auto constexpr kUNIT_VECTORS_NUMBER = 8'192u;
auto constexpr kUNIT_VECTORS_BUFFER_BINDING = 5u;

auto constexpr kGROUP_SIZE = glm::uvec2{8, 8};

namespace app {
struct state final {
    std::array<std::int32_t, 2> window_size{0, 0};

    scene::camera_system camera_system;
    std::shared_ptr<scene::camera> camera;

    std::unique_ptr<camera::orbit_controller> camera_controller;

    gfx::shader_storage_buffer<scene::camera::gpu_data> camera_buffer;

    gfx::render_pass render_pass;

    gfx::program compute_program;
    gfx::program blit_program;

    gfx::program sphere_debug_program;
    gfx::vertex_array<3, float> sphere_debug_vao;

    gfx::shader_storage_buffer<primitives::sphere> spheres_buffer;
    std::vector<primitives::sphere> spheres;

    // std::unique_ptr<physics::system> physics_system;
};

class window_event_handler final : public platform::event_handler {
public:

    window_event_handler(app::state &app_state) noexcept : app_state{app_state} { }

    void on_resize(std::int32_t width, std::int32_t height) override
    {
        if (width * height == 0)
            return;

        app_state.window_size = std::array{width, height};

        app_state.camera->aspect = static_cast<float>(width) / static_cast<float>(height);
    }

private:

    app::state &app_state;
};

void update(app::state &app_state, [[maybe_unused]] float delta_time)
{
    app_state.camera_controller->update();
    app_state.camera_system.update();

    gfx::update_shader_storage_buffer(app_state.camera_buffer, &app_state.camera->data);

    // app_state.physics_system->update(delta_time);
    // gfx::update_shader_storage_buffer(app_state.spheres_buffer, std::data(app_state.spheres));
}

void render(app::state const &app_state, glm::uvec2 groups_number)
{
    glUseProgram(app_state.compute_program.handle);

    glDispatchCompute(groups_number.x, groups_number.y, 1);

    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

void render_spherical_fibonacci_lattice(app::state const &app_state)
{
    glUseProgram(app_state.sphere_debug_program.handle);

    auto [width, height] = app_state.window_size;

    glViewport(0, 0, width, height);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glBindVertexArray(app_state.sphere_debug_vao.handle);
    glDrawArrays(GL_POINTS, 0, kUNIT_VECTORS_NUMBER);
}

void blit_framebuffer(app::state const &app_state)
{
    glUseProgram(app_state.blit_program.handle);

    auto &&[vao, framebuffer] = app_state.render_pass;

    auto [app_width, app_height] = app_state.window_size;
    auto [fbo_width, fbo_height] = framebuffer.size;

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer.handle);

    glViewport(0, 0, fbo_width, fbo_height);

    glBindVertexArray(vao);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    glBlitNamedFramebuffer(framebuffer.handle, 0, 0, 0, fbo_width, fbo_height,
                           0, 0, app_width, app_height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
}

int main(int argc, char *argv[])
{
#ifdef _DEBUG
    #ifdef _MSC_VER
        _CrtSetDbgFlag(_CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    #else
        std::signal(SIGSEGV, posix_signal_handler);
        std::signal(SIGTRAP, posix_signal_handler);
    #endif
#endif

    app::state app_state;
    app_state.window_size = std::array{800, 600};

    try {
        namespace po = boost::program_options;

        po::options_description desc("Allowed options");

        desc.add_options()
            ("help", "produce help message")
            ("size", po::value<std::vector<int>>()->multitoken(), "window size");

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

        if (vm.count("help")) {
            std::cout << desc << std::endl;
            return 1;
        }

        if (vm.count("size")) {
            auto window_size = vm["size"].as<std::vector<int>>();

            std::copy_n(std::begin(window_size), std::size(app_state.window_size), std::begin(app_state.window_size));
        }

    } catch (...) {
        std::cerr << "Error: unexpected exception at program options parsing.";
        return 1;
    }

    if (auto result = glfwInit(); result != GLFW_TRUE)
        throw std::runtime_error("failed to init GLFW"s);

    auto [width, height] = app_state.window_size;

    auto const groups_number = glm::uvec2{glm::ceil(glm::vec2{width, height} / glm::vec2{kGROUP_SIZE})};

    std::cout << fmt::format("groups number: {}x{}\n"s, groups_number.x, groups_number.y);

    platform::window window{"Raytracer"sv, width, height};

    auto app_window_event_handler = std::make_shared<app::window_event_handler>(app_state);
    window.connect_handler(app_window_event_handler);

    auto input_manager = std::make_shared<input::input_manager>();
    window.connect_handler(input_manager);

    gfx::context context{window};

    auto image = gfx::create_image2D(width, height, GL_RGBA32F);

    glBindImageTexture(kOUT_IMAGE_BINDING, image.handle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
    glBindTextureUnit(4, image.handle);

    {
        auto attachment = gfx::create_image2D(width, height, GL_SRGB8_ALPHA8);
        auto framebuffer = gfx::create_framebuffer(width, height, attachment);

        app_state.render_pass = gfx::create_render_pass(std::move(framebuffer));
    }

    {
        auto compute_shader_stage = gfx::create_shader_stage<gfx::shader::compute>("shader.comp.spv"sv, "main"sv);
        app_state.compute_program = gfx::create_program(std::vector{compute_shader_stage});
    }

    {
        auto vertex_stage = gfx::create_shader_stage<gfx::shader::vertex>("shader.vert.spv"sv, "main"sv);
        auto fragment_stage = gfx::create_shader_stage<gfx::shader::fragment>("shader.frag.spv"sv, "main"sv);

        app_state.blit_program = gfx::create_program(std::vector{vertex_stage, fragment_stage});
    }

    {
        auto aspect = static_cast<float>(width) / static_cast<float>(height);
        app_state.camera = app_state.camera_system.create_camera(42.f, aspect);

        app_state.camera_controller = std::make_unique<camera::orbit_controller>(app_state.camera, *input_manager);
        app_state.camera_controller->look_at(glm::vec3{6.4, 1.6, 2.7}, glm::vec3{0, 1, 0});

        app_state.camera_buffer = gfx::create_shader_storage_buffer<scene::camera::gpu_data>(kCAMERA_BINDING, 1);

        gfx::update_shader_storage_buffer(app_state.camera_buffer, &app_state.camera->data);
    }

    {
        {
            std::vector<material::lambertian> lambertian;

            lambertian.push_back({glm::vec3{.2, .4, .5}});
            lambertian.push_back({glm::vec3{.6, .8, .8}});

            auto length = static_cast<std::uint32_t>(std::size(lambertian));

            gfx::create_shader_storage_buffer<material::lambertian>(kLAMBERTIAN_BUFFER_BINDING, length, std::data(lambertian));
        }

        {
            std::vector<material::metal> metal;

            metal.push_back({glm::vec3{.8, .6, .2}, 0});

            auto length = static_cast<std::uint32_t>(std::size(metal));

            gfx::create_shader_storage_buffer<material::metal>(kMETAL_BUFFER_BINDING, length, std::data(metal));
        }

        {
            std::vector<material::dielectric> dielectric;

            dielectric.push_back({glm::vec3{1}, 1.5f});

            auto length = static_cast<std::uint32_t>(std::size(dielectric));

            gfx::create_shader_storage_buffer<material::dielectric>(kDIELECTRIC_BUFFER_BINDING, length, std::data(dielectric));
        }

        {
            std::vector<material::emissive> emissive;

            emissive.push_back({glm::vec3{1}, 4.f});

            auto length = static_cast<std::uint32_t>(std::size(emissive));

            gfx::create_shader_storage_buffer<material::emissive>(kEMISSIVE_BUFFER_BINDING, length, std::data(emissive));
        }
    }

    {
        auto &&spheres = app_state.spheres;

        spheres.emplace_back(primitives::sphere{glm::vec3{0, 1, 0}, 1, 3, 0});
        spheres.emplace_back(primitives::sphere{glm::vec3{0, -1000, 0}, 1000, 0, 1});

        spheres.emplace_back(primitives::sphere{glm::vec3{+2.1, 1, 0}, 1, 1, 0});

        spheres.emplace_back(primitives::sphere{glm::vec3{-2.1, 1, 0}, 1, 2, 0});
        spheres.emplace_back(primitives::sphere{glm::vec3{-2.1, 1, 0}, -.98f, 2, 0});

        spheres.emplace_back(primitives::sphere{glm::vec3{0, 1, 2}, 0.2f, 2, 0});

        auto length = static_cast<std::uint32_t>(std::size(spheres));

        app_state.spheres_buffer = gfx::create_shader_storage_buffer<primitives::sphere>(kPRIMITIVES_BINDING, length);
        gfx::update_shader_storage_buffer(app_state.spheres_buffer, std::data(app_state.spheres));
    }

    // app_state.physics_system = std::make_unique<physics::system>(app_state.spheres);

    if constexpr (false) {
        auto unit_vectors_image = gfx::create_image2D(width, height, GL_RGBA32F);

        std::vector<glm::vec4> unit_vectors(static_cast<std::size_t>(width) * static_cast<std::size_t>(height));

        std::random_device random_device;
        std::mt19937 generator{random_device()};

    #ifdef _MSC_VER
        std::generate(std::execution::par_unseq, std::begin(unit_vectors), std::end(unit_vectors), [&generator]
    #else
        std::generate(std::begin(unit_vectors), std::end(unit_vectors), [&generator]
    #endif
        {
            return glm::vec4(glm::normalize(math::random_on_unit_sphere(generator)), 1);
        });

        glTextureSubImage2D(unit_vectors_image.handle, 0, 0, 0, width, height, GL_RGBA, GL_FLOAT, std::data(unit_vectors));
        glBindImageTexture(kUNIT_VECTORS_BUFFER_BINDING, unit_vectors_image.handle, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
    }

    if constexpr (false) {
#if 1
        auto unit_vectors = math::spherical_fibonacci_lattice(kUNIT_VECTORS_NUMBER);
#else
        std::vector<glm::vec3> unit_vectors(kUNIT_VECTORS_NUMBER);

        std::random_device random_device;
        std::mt19937 generator{random_device()};

    #ifdef _MSC_VER
        std::generate(std::execution::par_unseq, std::begin(unit_vectors), std::end(unit_vectors), [&generator]
    #else
        std::generate(std::begin(unit_vectors), std::end(unit_vectors), [&generator]
    #endif
        {
            return glm::normalize(math::random_on_unit_sphere(generator));
        });
#endif

        auto buffer = gfx::create_shader_storage_buffer<glm::vec3>(kUNIT_VECTORS_BUFFER_BINDING, kUNIT_VECTORS_NUMBER, std::data(unit_vectors));

        if constexpr (kDEBUG_SPHERICAL_FIBONACCI_LATTICE) {
            auto vertex_stage = gfx::create_shader_stage<gfx::shader::vertex>("debug-sphere.vert.spv"sv, "main"sv);
            auto fragment_stage = gfx::create_shader_stage<gfx::shader::fragment>("debug-sphere.frag.spv"sv, "main"sv);

            app_state.sphere_debug_program = gfx::create_program(std::vector{vertex_stage, fragment_stage});
            app_state.sphere_debug_vao = gfx::create_vertex_array<3, float>(kVERTEX_SEMANTIC_POSITION, buffer);
        }
    }

    {
        auto perlin = math::create_perlin_noise();

        gfx::create_shader_storage_buffer<math::perlin>(kPERLIN_NOISE_BINDING, 1, &perlin);
    }

    auto texture_image = loader::load_image("earth-sphere.jpg"sv);
    glBindTextureUnit(12, texture_image.handle);

    glMemoryBarrier(GL_ALL_BARRIER_BITS);
    glFinish();

    if (auto result = glGetError(); result != GL_NO_ERROR)
        throw std::runtime_error("OpenGL error: "s + std::to_string(result));

    auto last_time = std::chrono::high_resolution_clock::now();

    window.update([&] (auto &&window)
    {
        auto start = std::chrono::high_resolution_clock::now();

        auto delta_time = static_cast<float>(std::chrono::duration_cast<std::chrono::microseconds>(start - last_time).count()) * 1e-6f;

        last_time = start;

        glfwPollEvents();

        app::update(app_state, delta_time);

        if constexpr (kDEBUG_SPHERICAL_FIBONACCI_LATTICE) {
            app::render_spherical_fibonacci_lattice(app_state);
        }

        else {
            app::render(app_state, groups_number);

            glFinish();

            app::blit_framebuffer(app_state);
        }

        glfwSwapBuffers(window.handle());

        if (auto result = glGetError(); result != GL_NO_ERROR)
            throw std::runtime_error("OpenGL error: "s + std::to_string(result));

        glfwSetWindowTitle(window.handle(), ("Raytracer "s + std::to_string(delta_time * 1e+3f)).c_str());
    });

    glfwTerminate();
}


#include <iostream>
#include <boost/asio/thread_pool.hpp>
#include <boost/asio/post.hpp>

#include "BarcodeProcessingHandler.hpp"
#include "helper.hpp"

struct VectorHasher {
    int operator()(const std::vector<int> &V) const {
        int hash = V.size();
        for(auto &i : V) {
            hash ^= i + 0x9e3779b9 + (hash << 6) + (hash >> 2);
        }
        return hash;
    }
};

class umiQualityStat
{
    public:
        // adds data to the errorMap
        //input is following map: barcodeID(AB,BC1, bC2, ...) => vector of different barcodes found
        void add_value(const std::unordered_map<std::string, std::vector<std::string> >& barcodesForUmi);
        std::vector<std::string> get_keys_in_order()
        {
            return(barcodeOrder);
        }
        std::unordered_map< std::vector<int>, unsigned long long, VectorHasher> get_error_map()
        {
            return(errorMap);
        }

    private:
        //Map to store where we have several barcodes for a UMI
        // BC1 | BC2 | ABname | treatmentname => count
        // 1   | 1   | 1      | 1             => 92236
        // 1   | 2   | 1      | 1             => 100
        //e.g. we have many UMIs (92236) where the Sc-AB-treatment is truly unique
        //and 100 UMIs that have two different barcodes in BC-round 2
        std::unordered_map< std::vector<int>, unsigned long long, VectorHasher> errorMap;
        std::vector<std::string> barcodeOrder; // order of barcode types (AB, BC1, BC2, ...) in which the counts are stored in vector<int>
        //above in the errorMap
        std::mutex errorMapUpdateLock;
};

class UmiQuality
{
    public:

        UmiQuality(const BarcodeProcessingHandler& handler)
        {
            rawData = handler.getRawData();
        }
        //run the quality check: calls 1.) checkUniquenessOfUmis 2.) writeUmiQualityData
        void runUmiQualityCheck(const int& thread, const std::string& output);

    private:
    //private functions called in runUmiQualityCheck
        void checkUniquenessOfUmis(const std::vector<umiDataLinePtr>& uniqueUmis);
        void writeUmiQualityData(std::string output);

        //Statistic about the UMI quality
        umiQualityStat umiQualStat;
        //raw data: storing all demultiplexed dataLines
        UnprocessedDemultiplexedData rawData;
};


//
// Created by Davide  Caroselli on 27/09/16.
//

#include <algorithm>
#include <boost/math/distributions/binomial.hpp>
#include <suffixarray/SuffixArray.h>
#include <util/hashutils.h>

#include "PhraseTable.h"
#include "UpdateManager.h"
#include "TranslationOptionBuilder.h"

using namespace mmt;
using namespace mmt::sapt;

struct PhraseTable::pt_private {
    SuffixArray *index;
    UpdateManager *updates;
    Aligner *aligner;

    size_t numberOfSamples;
};

PhraseTable::PhraseTable(const string &modelPath, const Options &options, Aligner *aligner) {
    self = new pt_private();
    self->index = new SuffixArray(modelPath, options.prefix_length, options.gc_timeout, options.gc_buffer_size);
    self->updates = new UpdateManager(self->index, options.update_buffer_size, options.update_max_delay);
    self->aligner = aligner;
    self->numberOfSamples = options.samples;
}

PhraseTable::~PhraseTable() {
    delete self->updates;
    delete self->index;
    delete self;
}

void PhraseTable::Add(const updateid_t &id, const domain_t domain, const std::vector<wid_t> &source,
                      const std::vector<wid_t> &target, const alignment_t &alignment) {
    self->updates->Add(id, domain, source, target, alignment);
}

void PhraseTable::Delete(const updateid_t &id, const domain_t domain) {
    self->updates->Delete(id, domain);
}

unordered_map<stream_t, seqid_t> PhraseTable::GetLatestUpdatesIdentifier() {
    const vector<seqid_t> &streams = self->index->GetStreams();

    unordered_map<stream_t, seqid_t> result;
    result.reserve(streams.size());

    for (size_t i = 0; i < streams.size(); ++i) {
        if (streams[i] >= 0)
            result[(stream_t) i] = streams[i];
    }

    return result;
}

/* Translation Options scoring */

static void GetLexicalScores(Aligner *aligner, const vector<wid_t> &phrase, const TranslationOption &option,
                             float &fwdScore, float &bwdScore) {
    vector<vector<float>> fwdWordProb(option.targetPhrase.size());
    vector<vector<float>> bwdWordProb(phrase.size());
    size_t sSize = phrase.size();
    size_t tSize = option.targetPhrase.size();

    // Computes the lexical probabilities on the best alignment only
    for (auto a = option.alignment.begin(); a != option.alignment.end(); ++a) {
        wid_t sWord = phrase[a->first];
        wid_t tWord = option.targetPhrase[a->second];
        fwdWordProb[a->second].push_back(aligner->GetForwardProbability(sWord, tWord));  // P(tWord | sWord)
        bwdWordProb[a->first].push_back(aligner->GetBackwardProbability(sWord, tWord));  // P(sWord | tWord)

    }
    fwdScore = 0.0;
    for (size_t ti = 0; ti < tSize; ++ti) {
        float tmpProb = 0.0;
        size_t tmpSize = fwdWordProb[ti].size();

        if (tmpSize > 0) {
            for (size_t i = 0; i < tmpSize; ++i) {
                tmpProb += fwdWordProb[ti][i];
            }
            tmpProb /= tmpSize;
        } else {
            tmpProb = aligner->GetTargetNullProbability(option.targetPhrase[ti]);
        }
        // should never happen that tmpProb <= 0
        fwdScore += (tmpProb <= 0.0) ? -9 : log(tmpProb);
    }

    bwdScore = 0.0;
    for (size_t si = 0; si < sSize; ++si) {
        float tmpProb = 0.0;
        size_t tmpSize = bwdWordProb[si].size();

        if (tmpSize > 0) {
            for (size_t i = 0; i < tmpSize; ++i) {
                tmpProb += bwdWordProb[si][i];
            }
            tmpProb /= tmpSize;
        } else {
            tmpProb = aligner->GetSourceNullProbability(phrase[si]);
        }

        // should never happen that tmpProb <= 0
        bwdScore += (tmpProb <= 0.0) ? -9 : log(tmpProb);
    }
}

static float lbop(float succ, float tries, float confidence) {
    if (confidence == 0)
        return succ / tries;
    else
        return (float) boost::math::binomial_distribution<>::find_lower_bound_on_p(tries, succ, confidence);
}

static void MakeTranslationOptions(SuffixArray *index, Aligner *aligner,
                                   const vector<wid_t> &phrase, const vector<sample_t> &samples,
                                   vector<TranslationOption> &output) {

    size_t validSamples = 0;
    vector<TranslationOptionBuilder> builders;
    TranslationOptionBuilder::Extract(phrase, samples, builders, validSamples);

    static constexpr float confidence = 0.01;

    // Compute frequency-based and (possibly) lexical-based scores for all options
    // create the actual Translation option objects, setting the "best" alignment.
    size_t SampleSourceFrequency = validSamples;
    size_t GlobalSourceFrequency = index->CountOccurrences(true, phrase);

    for (auto entry = builders.begin(); entry != builders.end(); ++entry) {
        size_t GlobalTargetFrequency = index->CountOccurrences(false, entry->GetPhrase());

        float fwdScore = log(lbop(entry->GetCount(),
                                  std::max(entry->GetCount(), SampleSourceFrequency),
                                  confidence));
        float bwdScore = log(lbop(entry->GetCount(),
                                  std::max(entry->GetCount(), (size_t) round((float) SampleSourceFrequency * GlobalTargetFrequency / GlobalSourceFrequency)),
                                  confidence));

        float fwdLexScore = 0.f;
        float bwdLexScore = 0.f;

        TranslationOption option;
        option.alignment = entry->GetBestAlignment();
        option.targetPhrase = entry->GetPhrase();
        option.orientations = entry->GetOrientations();

        if (aligner)
            GetLexicalScores(aligner, phrase, option, fwdLexScore, bwdLexScore);

        option.scores[ForwardProbabilityScore] = fwdScore;
        option.scores[BackwardProbabilityScore] = min(0.f, bwdScore);
        option.scores[ForwardLexicalScore] = fwdLexScore;
        option.scores[BackwardLexicalScore] = bwdLexScore;

        output.push_back(option);
    }
}

/* SAPT methods */

vector<TranslationOption> PhraseTable::GetTranslationOptions(const vector<wid_t> &phrase, context_t *context) {
    vector<sample_t> samples;
    self->index->GetRandomSamples(phrase, self->numberOfSamples, samples, context);

    vector<TranslationOption> result;
    MakeTranslationOptions(self->index, self->aligner, phrase, samples, result);

    return result;
}

translation_table_t PhraseTable::GetAllTranslationOptions(const vector<wid_t> &sentence, context_t *context) {
    translation_table_t ttable;

    for (size_t start = 0; start < sentence.size(); ++start) {
        Collector *collector = self->index->NewCollector(context);

        vector<wid_t> phrase;
        vector<wid_t> phraseDelta;

        for (size_t end = start; end < sentence.size(); ++end) {
            wid_t word = sentence[end];
            phrase.push_back(word);
            phraseDelta.push_back(word);

            if (ttable.find(phrase) == ttable.end()) {
                vector<sample_t> samples;
                collector->Extend(phraseDelta, self->numberOfSamples, samples);
                phraseDelta.clear();

                if (samples.empty())
                    break;

                vector<TranslationOption> options;
                MakeTranslationOptions(self->index, self->aligner, phrase, samples, options);

                ttable[phrase] = options;
            }
        }
       delete collector;
    }

    return ttable;
}


// Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, version 2.0,
// as published by the Free Software Foundation.
//
// This program is also distributed with certain software (including
// but not limited to OpenSSL) that is licensed under separate terms,
// as designated in a particular file or component or in included license
// documentation.  The authors of MySQL hereby grant you an additional
// permission to link the program and your derivative works with the
// separately licensed software that they have included with MySQL.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License, version 2.0, for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.

/// @file
///
/// This file implements the covered_by functor and mbr_covered_by function.

#include <boost/geometry.hpp>

#include "sql/dd/types/spatial_reference_system.h"  // dd::Spatial_reference_system
#include "sql/gis/box.h"
#include "sql/gis/box_traits.h"
#include "sql/gis/covered_by_functor.h"
#include "sql/gis/geometries.h"
#include "sql/gis/geometries_traits.h"
#include "sql/gis/mbr_utils.h"
#include "sql/gis/relops.h"
#include "sql/sql_exception_handler.h"  // handle_gis_exception

namespace bg = boost::geometry;

namespace gis {

Covered_by::Covered_by(double semi_major, double semi_minor)
    : m_semi_major(semi_major), m_semi_minor(semi_minor) {}

bool Covered_by::operator()(const Geometry *g1, const Geometry *g2) const {
  return apply(*this, g1, g2);
}

bool Covered_by::operator()(const Box *b1, const Box *b2) const {
  DBUG_ASSERT(b1->coordinate_system() == b2->coordinate_system());
  switch (b1->coordinate_system()) {
    case Coordinate_system::kCartesian:
      return eval(down_cast<const Cartesian_box *>(b1),
                  down_cast<const Cartesian_box *>(b2));
    case Coordinate_system::kGeographic:
      return eval(down_cast<const Geographic_box *>(b1),
                  down_cast<const Geographic_box *>(b2));
  }

  DBUG_ASSERT(false);
  return false;
}

bool Covered_by::eval(const Geometry *g1, const Geometry *g2) const {
  // Currently only implemented for boxes (MBRs).
  DBUG_ASSERT(false);
  throw not_implemented_exception::for_non_projected(*g1, *g2);
}

//////////////////////////////////////////////////////////////////////////////

// covered_by(Box, Box)

bool Covered_by::eval(const Cartesian_box *b1, const Cartesian_box *b2) const {
  return bg::covered_by(*b1, *b2);
}

bool Covered_by::eval(const Geographic_box *b1,
                      const Geographic_box *b2) const {
  return bg::covered_by(*b1, *b2);
}

//////////////////////////////////////////////////////////////////////////////

bool mbr_covered_by(const dd::Spatial_reference_system *srs, const Geometry *g1,
                    const Geometry *g2, const char *func_name, bool *covered_by,
                    bool *null) noexcept {
  try {
    DBUG_ASSERT(g1->coordinate_system() == g2->coordinate_system());
    DBUG_ASSERT(srs == nullptr ||
                ((srs->is_cartesian() &&
                  g1->coordinate_system() == Coordinate_system::kCartesian) ||
                 (srs->is_geographic() &&
                  g1->coordinate_system() == Coordinate_system::kGeographic)));

    if ((*null = (g1->is_empty() || g2->is_empty()))) return false;

    Covered_by covered_by_func(srs ? srs->semi_major_axis() : 0.0,
                               srs ? srs->semi_minor_axis() : 0.0);

    switch (g1->coordinate_system()) {
      case Coordinate_system::kCartesian: {
        Cartesian_box mbr1;
        box_envelope(g1, srs, &mbr1);
        Cartesian_box mbr2;
        box_envelope(g2, srs, &mbr2);
        *covered_by = covered_by_func(&mbr1, &mbr2);
        break;
      }
      case Coordinate_system::kGeographic: {
        Geographic_box mbr1;
        box_envelope(g1, srs, &mbr1);
        Geographic_box mbr2;
        box_envelope(g2, srs, &mbr2);
        *covered_by = covered_by_func(&mbr1, &mbr2);
        break;
      }
    }
  } catch (...) {
    handle_gis_exception(func_name);
    return true;
  }

  return false;
}

}  // namespace gis


/*===================================================================

The Medical Imaging Interaction Toolkit (MITK)

Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.

This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.

See LICENSE.txt or http://www.mitk.org for details.

===================================================================*/

#ifndef __itkFiberExtractionFilter_cpp
#define __itkFiberExtractionFilter_cpp

#include "itkFiberExtractionFilter.h"

#define _USE_MATH_DEFINES
#include <cmath>
#include <boost/progress.hpp>
#include <mitkDiffusionFunctionCollection.h>
#include <boost/lexical_cast.hpp>

namespace itk{

template< class PixelType >
FiberExtractionFilter< PixelType >::FiberExtractionFilter()
  : m_DontResampleFibers(false)
  , m_Mode(MODE::OVERLAP)
  , m_InputType(INPUT::SCALAR_MAP)
  , m_BothEnds(true)
  , m_OverlapFraction(0.8)
  , m_NoNegatives(false)
  , m_NoPositives(false)
  , m_Interpolate(false)
  , m_Threshold(0.5)
  , m_Labels()
  , m_SkipSelfConnections(false)
  , m_OnlySelfConnections(false)
  , m_SplitByRoi(false)
  , m_SplitLabels(false)
  , m_MinFibersPerTract(0)
  , m_PairedStartEndLabels(false)
{
  m_Interpolator = itk::LinearInterpolateImageFunction< itk::Image< PixelType, 3 >, float >::New();
}

template< class PixelType >
FiberExtractionFilter< PixelType >::~FiberExtractionFilter()
{

}

template< class PixelType >
void FiberExtractionFilter< PixelType >::SetRoiImages(const std::vector<ItkInputImgType*> &rois)
{
  for (auto roi : rois)
  {
    if (roi==nullptr)
    {
      MITK_INFO << "ROI image is NULL!";
      return;
    }
  }
  m_RoiImages = rois;
}

template< class PixelType >
void FiberExtractionFilter< PixelType >::SetRoiImageNames(const std::vector< std::string > roi_names)
{
  m_RoiImageNames = roi_names;
}

template< class PixelType >
mitk::FiberBundle::Pointer FiberExtractionFilter< PixelType >::CreateFib(std::vector< long >& ids)
{
  vtkSmartPointer<vtkFloatArray> weights = vtkSmartPointer<vtkFloatArray>::New();
  vtkSmartPointer<vtkPolyData> pTmp = m_InputFiberBundle->GeneratePolyDataByIds(ids, weights);
  mitk::FiberBundle::Pointer fib = mitk::FiberBundle::New(pTmp);
  fib->SetFiberWeights(weights);
  return fib;
}

template< class PixelType >
bool FiberExtractionFilter< PixelType >::IsPositive(const itk::Point<float, 3>& itkP)
{
  if( m_InputType == INPUT::SCALAR_MAP )
    return mitk::imv::IsInsideMask<PixelType>(itkP, m_Interpolate, m_Interpolator, m_Threshold);
  else if( m_InputType == INPUT::LABEL_MAP )
  {
    auto val = mitk::imv::GetImageValue<PixelType>(itkP, m_Interpolate, m_Interpolator);
    for (auto l : m_Labels)
      if (l==val)
        return true;
  }
  else
    mitkThrow() << "No valid input type selected!";
  return false;
}

template< class PixelType >
std::vector< std::string > FiberExtractionFilter< PixelType >::GetPositiveLabels() const
{
  return m_PositiveLabels;
}

template< class PixelType >
void FiberExtractionFilter< PixelType >::ExtractOverlap(mitk::FiberBundle::Pointer fib)
{
  MITK_INFO << "Extracting fibers (min. overlap " << m_OverlapFraction << ")";
  vtkSmartPointer<vtkPolyData> polydata = fib->GetFiberPolyData();

  std::vector< std::vector< long > > positive_ids;  // one ID vector per ROI
  positive_ids.resize(m_RoiImages.size());

  std::vector< long > negative_ids; // fibers not overlapping with ANY mask

  boost::progress_display disp(m_InputFiberBundle->GetNumFibers());
  for (int i=0; i<m_InputFiberBundle->GetNumFibers(); i++)
  {
    ++disp;
    vtkCell* cell = polydata->GetCell(i);
    int numPoints = cell->GetNumberOfPoints();
    vtkPoints* points = cell->GetPoints();

    float best_ol = 0;
    int best_ol_idx = -1;
    for (unsigned int m=0; m<m_RoiImages.size(); ++m)
    {
      auto roi = m_RoiImages.at(m);
      m_Interpolator->SetInputImage(roi);
      PixelType inside = 0;
      PixelType outside = 0;
      for (int j=0; j<numPoints-1; j++)
      {
        itk::Point<float, 3> startVertex = mitk::imv::GetItkPoint(points->GetPoint(j));
        itk::Index<3> startIndex;
        itk::ContinuousIndex<float, 3> startIndexCont;
        roi->TransformPhysicalPointToIndex(startVertex, startIndex);
        roi->TransformPhysicalPointToContinuousIndex(startVertex, startIndexCont);

        itk::Point<float, 3> endVertex = mitk::imv::GetItkPoint(points->GetPoint(j + 1));
        itk::Index<3> endIndex;
        itk::ContinuousIndex<float, 3> endIndexCont;
        roi->TransformPhysicalPointToIndex(endVertex, endIndex);
        roi->TransformPhysicalPointToContinuousIndex(endVertex, endIndexCont);

        std::vector< std::pair< itk::Index<3>, double > > segments = mitk::imv::IntersectImage(roi->GetSpacing(), startIndex, endIndex, startIndexCont, endIndexCont);
        for (std::pair< itk::Index<3>, double > segment : segments)
        {
          if (!roi->GetLargestPossibleRegion().IsInside(segment.first))
            continue;
          if (roi->GetPixel(segment.first)>=m_Threshold)
            inside += segment.second;
          else
            outside += segment.second;
        }
      }

      float overlap = (float)inside/(inside+outside);
      if (overlap > best_ol && overlap >= m_OverlapFraction)
      {
        best_ol_idx = m;
        best_ol = overlap;
      }
    }
    if (best_ol_idx<0)
      negative_ids.push_back(i);
    else
      positive_ids.at(best_ol_idx).push_back(i);
  }

  if (!m_NoNegatives)
    m_Negatives.push_back(CreateFib(negative_ids));
  if (!m_NoPositives)
  {
    for (unsigned int i=0; i<positive_ids.size(); ++i)
    {
      std::string name;
      if (i<m_RoiImageNames.size())
        name = m_RoiImageNames.at(i);
      else
        name = "Roi" + boost::lexical_cast<std::string>(i);

      if (positive_ids.at(i).size()>=m_MinFibersPerTract)
      {
        m_Positives.push_back(CreateFib(positive_ids.at(i)));
        m_PositiveLabels.push_back(name);
      }
    }
    if (!m_SplitByRoi)
    {
      mitk::FiberBundle::Pointer output = mitk::FiberBundle::New(nullptr);
      output = output->AddBundles(m_Positives);
      m_Positives.clear();
      m_Positives.push_back(output);
      m_PositiveLabels.clear();
      m_PositiveLabels.push_back("");
    }
  }
}

template< class PixelType >
void FiberExtractionFilter< PixelType >::ExtractEndpoints(mitk::FiberBundle::Pointer fib)
{
  MITK_INFO << "Extracting fibers (endpoints in mask)";
  vtkSmartPointer<vtkPolyData> polydata = fib->GetFiberPolyData();

  std::vector< std::vector< long > > positive_ids;  // one ID vector per ROI
  positive_ids.resize(m_RoiImages.size());

  std::vector< long > negative_ids; // fibers not overlapping with ANY mask

  boost::progress_display disp(m_InputFiberBundle->GetNumFibers());
  for (int i=0; i<m_InputFiberBundle->GetNumFibers(); i++)
  {
    ++disp;
    vtkCell* cell = polydata->GetCell(i);
    int numPoints = cell->GetNumberOfPoints();
    vtkPoints* points = cell->GetPoints();

    bool positive = false;
    if (numPoints>1)
      for (unsigned int m=0; m<m_RoiImages.size(); ++m)
      {
        auto roi = m_RoiImages.at(m);
        m_Interpolator->SetInputImage(roi);

        int inside = 0;

        // check first fiber point
        {
          double* p = points->GetPoint(0);
          itk::Point<float, 3> itkP;
          itkP[0] = p[0]; itkP[1] = p[1]; itkP[2] = p[2];

          if ( IsPositive(itkP) )
            inside++;
        }

        // check second fiber point
        {
          double* p = points->GetPoint(numPoints-1);
          itk::Point<float, 3> itkP;
          itkP[0] = p[0]; itkP[1] = p[1]; itkP[2] = p[2];

          if ( IsPositive(itkP) )
            inside++;
        }

        if (inside==2 || (inside==1 && !m_BothEnds))
        {
          positive = true;
          positive_ids[m].push_back(i);
        }
      }
    if (!positive)
      negative_ids.push_back(i);
  }

  if (!m_NoNegatives)
    m_Negatives.push_back(CreateFib(negative_ids));
  if (!m_NoPositives)
  {
    for (unsigned int i=0; i<positive_ids.size(); ++i)
    {
      std::string name;
      if (i<m_RoiImageNames.size())
        name = m_RoiImageNames.at(i);
      else
        name = "Roi" + boost::lexical_cast<std::string>(i);

      if (positive_ids.at(i).size()>=m_MinFibersPerTract)
      {
        m_Positives.push_back(CreateFib(positive_ids.at(i)));
        m_PositiveLabels.push_back(name);
      }
    }
    if (!m_SplitByRoi)
    {
      mitk::FiberBundle::Pointer output = mitk::FiberBundle::New(nullptr);
      output = output->AddBundles(m_Positives);
      m_Positives.clear();
      m_Positives.push_back(output);
      m_PositiveLabels.clear();
      m_PositiveLabels.push_back("");
    }
  }
}

template< class PixelType >
void FiberExtractionFilter< PixelType >::ExtractLabels(mitk::FiberBundle::Pointer fib)
{
  MITK_INFO << "Extracting fibers by labels";
  vtkSmartPointer<vtkPolyData> polydata = fib->GetFiberPolyData();

  std::map< std::string, std::vector< long > > positive_ids;

  std::vector< long > negative_ids; // fibers not overlapping with ANY label

  boost::progress_display disp(m_InputFiberBundle->GetNumFibers());
  for (int i=0; i<m_InputFiberBundle->GetNumFibers(); i++)
  {
    ++disp;
    vtkCell* cell = polydata->GetCell(i);
    int numPoints = cell->GetNumberOfPoints();
    vtkPoints* points = cell->GetPoints();

    bool positive = false;
    if (numPoints>1)
      for (unsigned int m=0; m<m_RoiImages.size(); ++m)
      {
        auto roi = m_RoiImages.at(m);
        m_Interpolator->SetInputImage(roi);

        int inside = 0;

        double* p1 = points->GetPoint(0);
        itk::Point<float, 3> itkP1;
        itkP1[0] = p1[0]; itkP1[1] = p1[1]; itkP1[2] = p1[2];
        short label1 = mitk::imv::GetImageValue<PixelType>(itkP1, m_Interpolate, m_Interpolator);

        double* p2 = points->GetPoint(numPoints-1);
        itk::Point<float, 3> itkP2;
        itkP2[0] = p2[0]; itkP2[1] = p2[1]; itkP2[2] = p2[2];
        short label2 = mitk::imv::GetImageValue<PixelType>(itkP2, m_Interpolate, m_Interpolator);

        if (!m_Labels.empty())  // extract fibers from all pairwise label combinations
        {
          for (auto l : m_Labels)
          {
            if (l==label1)
              inside++;
            if (l==label2)
              inside++;
            if (inside==2)
              break;
          }
        }
        else if (!m_StartLabels.empty() || !m_EndLabels.empty()) // extract fibers between start and end labels
        {
          if (!m_StartLabels.empty() && !m_EndLabels.empty())
            m_BothEnds = true;  // if we have start and end labels it does not make sense to not use both endpoints
          if (m_PairedStartEndLabels)
          {
            if (m_StartLabels.size()!=m_EndLabels.size())
              mitkThrow() << "Start and end label lists must have same size if paired labels are used";
            for (unsigned int ii=0; ii<m_StartLabels.size(); ++ii)
              if ( (m_StartLabels.at(ii)==label1 && m_EndLabels.at(ii)==label2) || (m_StartLabels.at(ii)==label2 && m_EndLabels.at(ii)==label1) )
              {
                inside = 2;
                break;
              }
          }
          else
          {
            for (unsigned int ii=0; ii<m_StartLabels.size(); ++ii)
            {
              if ( m_StartLabels.at(ii)==label1 || m_StartLabels.at(ii)==label2 )
              {
                ++inside;
                break;
              }
            }
            for (unsigned int jj=0; jj<m_EndLabels.size(); ++jj)
            {
              if ( m_EndLabels.at(jj)==label1 || m_EndLabels.at(jj)==label2 )
              {
                ++inside;
                break;
              }
            }
          }
        }
        else  // use all labels
        {
          if (label1!=0)
            ++inside;
          if (label2!=0)
            ++inside;
        }

        std::string key = "";

        if (m_SplitLabels)
        {
          if (label1<label2)
            key = boost::lexical_cast<std::string>(label1) + "-" + boost::lexical_cast<std::string>(label2);
          else
            key = boost::lexical_cast<std::string>(label2) + "-" + boost::lexical_cast<std::string>(label1);
        }

        if (m_SplitByRoi)
        {
          if (m<m_RoiImageNames.size())
            key = m_RoiImageNames.at(m) + "_" + key;
          else
            key = "Roi" + boost::lexical_cast<std::string>(m) + "_" + key;
        }

        if (m_BothEnds)
        {
          if ( (inside==2 && (!m_SkipSelfConnections || label1!=label2)) || (inside==2 && m_OnlySelfConnections && label1==label2) )
          {
            positive = true;
            if ( positive_ids.count(key)==0 )
              positive_ids.insert( std::pair< std::string, std::vector< long > >( key, {i}) );
            else
              positive_ids[key].push_back(i);
          }
        }
        else
        {
          if ( (inside>=1 && (!m_SkipSelfConnections || label1!=label2)) || (inside==2 && m_OnlySelfConnections && label1==label2) )
          {
            positive = true;
            if ( positive_ids.count(key)==0 )
              positive_ids.insert( std::pair< std::string, std::vector< long > >( key, {i}) );
            else
              positive_ids[key].push_back(i);
          }
        }
      }
    if (!positive)
      negative_ids.push_back(i);
  }

  if (!m_NoNegatives)
    m_Negatives.push_back(CreateFib(negative_ids));
  if (!m_NoPositives)
  {
    for (auto label : positive_ids)
    {
      if (label.second.size()>=m_MinFibersPerTract)
      {
        m_Positives.push_back(CreateFib(label.second));
        m_PositiveLabels.push_back(label.first);
      }
    }
  }
}

template< class PixelType >
void FiberExtractionFilter< PixelType >::SetLabels(const std::vector<unsigned short> &Labels)
{
  m_Labels = Labels;
}

template< class PixelType >
void FiberExtractionFilter< PixelType >::SetStartLabels(const std::vector<unsigned short> &Labels)
{
  m_StartLabels = Labels;
}

template< class PixelType >
void FiberExtractionFilter< PixelType >::SetEndLabels(const std::vector<unsigned short> &Labels)
{
  m_EndLabels = Labels;
}

template< class PixelType >
std::vector<mitk::FiberBundle::Pointer> FiberExtractionFilter< PixelType >::GetNegatives() const
{
  return m_Negatives;
}

template< class PixelType >
std::vector<mitk::FiberBundle::Pointer> FiberExtractionFilter< PixelType >::GetPositives() const
{
  return m_Positives;
}

template< class PixelType >
void FiberExtractionFilter< PixelType >::GenerateData()
{
  mitk::FiberBundle::Pointer fib = m_InputFiberBundle;
  if (fib->GetNumFibers()<=0)
  {
    MITK_INFO << "No fibers in tractogram!";
    return;
  }

  if (m_Mode == MODE::OVERLAP)
    ExtractOverlap(fib);
  else if (m_Mode == MODE::ENDPOINTS)
  {
    if (m_InputType==INPUT::LABEL_MAP)
      ExtractLabels(fib);
    else
      ExtractEndpoints(fib);
  }
}

}

#endif // __itkFiberExtractionFilter_cpp


// License: BSD
// https://raw.githubusercontent.com/samiamlabs/dyno/master/LICENCE

#include <cmath>

#include <tf/transform_datatypes.h>

#include <boost/assign.hpp>

#include <tricycle_controller/tricycle_controller.h>
#include <urdf_geometry_parser/urdf_geometry_parser.h>

namespace tricycle_controller {

TricycleController::TricycleController()
    : command_struct_(), wheel_radius_(0.0), wheel_base_(0.0),
      cmd_vel_timeout_(0.5), base_frame_id_("base_footprint"),
      odom_frame_id_("odom"), enable_odom_tf_(true), angle_tolerance_(0.2),
      post_date_odom_tf_(true) {}

bool TricycleController::init(hardware_interface::RobotHW *robot_hw,
                              ros::NodeHandle &root_nh,
                              ros::NodeHandle &controller_nh) {
  const std::string complete_ns = controller_nh.getNamespace();
  std::size_t id = complete_ns.find_last_of("/");
  name_ = complete_ns.substr(id + 1);

  // Get joint name from the parameter server
  std::string drive_wheel_joint_name, steer_joint_name;

  if (!getJointName(controller_nh, "drive_wheel_joint",
                    drive_wheel_joint_name) ||
      !getJointName(controller_nh, "steer_joint", steer_joint_name)) {
    ROS_ERROR_STREAM("Get joint names from param failed");
    return false;
  }

  // Odometry related:
  double publish_rate;
  controller_nh.param("publish_rate", publish_rate, 50.0);
  ROS_INFO_STREAM_NAMED(name_, "Controller state will be published at "
                                   << publish_rate << "Hz.");
  publish_period_ = ros::Duration(1.0 / publish_rate);

  controller_nh.param("open_loop", open_loop_, open_loop_);

  int velocity_rolling_window_size = 10;
  controller_nh.param("velocity_rolling_window_size",
                      velocity_rolling_window_size,
                      velocity_rolling_window_size);
  ROS_INFO_STREAM_NAMED(name_, "Velocity rolling window size of "
                                   << velocity_rolling_window_size << ".");

  odometry_.setVelocityRollingWindowSize(velocity_rolling_window_size);

  // Twist command related:
  controller_nh.param("cmd_vel_timeout", cmd_vel_timeout_, cmd_vel_timeout_);
  ROS_INFO_STREAM_NAMED(
      name_, "Velocity commands will be considered old if they are older than "
                 << cmd_vel_timeout_ << "s.");

  controller_nh.param("base_frame_id", base_frame_id_, base_frame_id_);
  ROS_INFO_STREAM_NAMED(name_, "Base frame_id set to " << base_frame_id_);

  controller_nh.param("enable_odom_tf", enable_odom_tf_, enable_odom_tf_);
  ROS_INFO_STREAM_NAMED(name_,
                        "Publishing to tf is "
                            << (enable_odom_tf_ ? "enabled" : "disabled"));

  // Velocity and acceleration limits:
  controller_nh.param("linear/x/has_velocity_limits",
                      limiter_lin_.has_velocity_limits,
                      limiter_lin_.has_velocity_limits);
  controller_nh.param("linear/x/has_acceleration_limits",
                      limiter_lin_.has_acceleration_limits,
                      limiter_lin_.has_acceleration_limits);
  controller_nh.param("linear/x/max_velocity", limiter_lin_.max_velocity,
                      limiter_lin_.max_velocity);
  controller_nh.param("linear/x/min_velocity", limiter_lin_.min_velocity,
                      -limiter_lin_.max_velocity);
  controller_nh.param("linear/x/max_acceleration",
                      limiter_lin_.max_acceleration,
                      limiter_lin_.max_acceleration);
  controller_nh.param("linear/x/min_acceleration",
                      limiter_lin_.min_acceleration,
                      -limiter_lin_.max_acceleration);

  controller_nh.param("angular/z/has_velocity_limits",
                      limiter_ang_.has_velocity_limits,
                      limiter_ang_.has_velocity_limits);
  controller_nh.param("angular/z/has_acceleration_limits",
                      limiter_ang_.has_acceleration_limits,
                      limiter_ang_.has_acceleration_limits);
  controller_nh.param("angular/z/max_velocity", limiter_ang_.max_velocity,
                      limiter_ang_.max_velocity);
  controller_nh.param("angular/z/min_velocity", limiter_ang_.min_velocity,
                      -limiter_ang_.max_velocity);
  controller_nh.param("angular/z/max_acceleration",
                      limiter_ang_.max_acceleration,
                      limiter_ang_.max_acceleration);
  controller_nh.param("angular/z/min_acceleration",
                      limiter_ang_.min_acceleration,
                      -limiter_ang_.max_acceleration);

  // If either parameter is not available, we need to look up the value in the
  // URDF
  bool lookup_wheel_radius =
      !controller_nh.getParam("wheel_radius", wheel_radius_);
  bool lookup_wheel_base = !controller_nh.getParam("wheel_base", wheel_base_);

  urdf_geometry_parser::UrdfGeometryParser uvk(root_nh, base_frame_id_);

  if (lookup_wheel_radius)
    if (!uvk.getJointRadius(drive_wheel_joint_name, wheel_radius_)) {
      ROS_ERROR_STREAM("Failed to get wheel radius, aborting.");
      return false;
    } else
      controller_nh.setParam("wheel_radius", wheel_radius_);

  if (lookup_wheel_base)
    // Does not take z into account
    // TODO: make more general, use getTransformVector maybe?
    if (!uvk.getDistanceBetweenJoints("base_footprint_joint", steer_joint_name,
                                      wheel_base_)) {
      ROS_ERROR_STREAM("Failed to get position of steer joint, aborting.");
      return false;
    } else
      controller_nh.setParam("wheel_base", wheel_base_);

  // Regardless of how we got the separation and radius, use them
  // to set the odometry parameters
  odometry_.setWheelParams(wheel_radius_, wheel_base_);
  ROS_INFO_STREAM_NAMED(name_, "Odometry params : "
                                   << ", wheel radius " << wheel_radius_
                                   << ", wheel base " << wheel_base_);

  drive_wheel_kinematics_.setMountPose(wheel_base_, 0);
  drive_wheel_kinematics_.setRadius(wheel_radius_);

  setOdomPubFields(root_nh, controller_nh);

  hardware_interface::VelocityJointInterface *const vel_joint_hw =
      robot_hw->get<hardware_interface::VelocityJointInterface>();

  hardware_interface::PositionJointInterface *const pos_joint_hw =
      robot_hw->get<hardware_interface::PositionJointInterface>();

  ROS_INFO_STREAM_NAMED(
      name_, "Adding drive wheel with joint name: " << drive_wheel_joint_name);
  drive_wheel_joint_ =
      vel_joint_hw->getHandle(drive_wheel_joint_name); // throws on failure

  ROS_INFO_STREAM_NAMED(
      name_, "Adding steering with joint name: " << steer_joint_name);

  steer_joint_ = pos_joint_hw->getHandle(steer_joint_name); // throws on failure

  sub_command_ = controller_nh.subscribe(
      "cmd_vel", 1, &TricycleController::cmdVelCallback, this);

  return true;
}

void TricycleController::update(const ros::Time &time,
                                const ros::Duration &period) {
  updateOdometry(time);
  updateCommand(time, period);
}

void TricycleController::starting(const ros::Time &time) {
  brake();

  // Register starting time used to keep fixed rate
  last_state_publish_time_ = time;

  odometry_.init(time);
}

void TricycleController::stopping(const ros::Time & /*time*/) { brake(); }

void TricycleController::updateOdometry(const ros::Time &time) {
  // Compute odometry
  if (open_loop_) {
    odometry_.updateOpenLoop(last0_cmd_.lin, last0_cmd_.ang, time);
  }

  const double steer_pos = steer_joint_.getPosition();
  const double drive_wheel_pos = drive_wheel_joint_.getPosition();

  odometry_.update(steer_pos, drive_wheel_pos, time);

  ROS_DEBUG("Odom: x: %f, y: %f, heading: %f", odometry_.getX(),
            odometry_.getY(), odometry_.getHeading());

  // Publish odometry message
  if (last_state_publish_time_ + publish_period_ < time) {
    last_state_publish_time_ += publish_period_;
    // Compute and store orientation info
    const geometry_msgs::Quaternion orientation(
        tf::createQuaternionMsgFromYaw(odometry_.getHeading()));

    ros::Time tf_stamp = time;
    if (post_date_odom_tf_) {
      tf_stamp -= ros::Duration(0.1);
    }

    // Populate odom message and publish
    if (odom_pub_->trylock()) {
      odom_pub_->msg_.header.stamp = tf_stamp;
      odom_pub_->msg_.pose.pose.position.x = odometry_.getX();
      odom_pub_->msg_.pose.pose.position.y = odometry_.getY();
      odom_pub_->msg_.pose.pose.orientation = orientation;
      odom_pub_->msg_.twist.twist.linear.x = odometry_.getLinear();
      odom_pub_->msg_.twist.twist.angular.z = odometry_.getAngular();
      odom_pub_->unlockAndPublish();
    }

    // Publish tf /odom frame
    if (enable_odom_tf_ && tf_odom_pub_->trylock()) {
      geometry_msgs::TransformStamped &odom_frame =
          tf_odom_pub_->msg_.transforms[0];
      odom_frame.header.stamp = tf_stamp;
      odom_frame.transform.translation.x = odometry_.getX();
      odom_frame.transform.translation.y = odometry_.getY();
      odom_frame.transform.rotation = orientation;
      tf_odom_pub_->unlockAndPublish();
    }
  }
}

void TricycleController::updateCommand(const ros::Time &time,
                                       const ros::Duration &period) {
  // Retreive current velocity command and time step:
  Command curr_cmd = *(command_.readFromRT());
  const double dt = (time - curr_cmd.stamp).toSec();

  // Brake if cmd_vel has timeout:
  if (dt > cmd_vel_timeout_) {
    curr_cmd.lin = 0.0;
    curr_cmd.ang = 0.0;
  }

  const double cmd_dt(period.toSec());

  const double angular_speed = odometry_.getAngular();

  // Limit velocities and accelerations:
  limiter_lin_.limit(curr_cmd.lin, last0_cmd_.lin, last1_cmd_.lin, cmd_dt);
  limiter_ang_.limit(curr_cmd.ang, last0_cmd_.ang, last1_cmd_.ang, cmd_dt);

  last1_cmd_ = last0_cmd_;
  last0_cmd_ = curr_cmd;

  drive_wheel_kinematics_.setCommand(curr_cmd.lin, curr_cmd.ang);
  drive_wheel_kinematics_.update();

  double steer_angle = drive_wheel_kinematics_.getSteer();
  double steer_angle_state = steer_joint_.getPosition();

  double drive_wheel_vel = drive_wheel_kinematics_.getAngularVelocity();

  if(fabs(steer_angle_state - steer_angle) > angle_tolerance_)
  {
    drive_wheel_vel = 0;
  }

  drive_wheel_joint_.setCommand(drive_wheel_vel);
  steer_joint_.setCommand(steer_angle);
}

void TricycleController::brake() {
  const double vel = 0.0;
  drive_wheel_joint_.setCommand(vel);

  // const double pos = 0.0;
  // steer_joint_.setCommand(pos);
}

void TricycleController::cmdVelCallback(const geometry_msgs::Twist &command) {
  if (isRunning()) {
    // check that we don't have multiple publishers on the command topic
    if (!allow_multiple_cmd_vel_publishers_ &&
        sub_command_.getNumPublishers() > 1) {
      ROS_ERROR_STREAM_THROTTLE_NAMED(
          1.0, name_,
          "Detected "
              << sub_command_.getNumPublishers()
              << " publishers. Only 1 publisher is allowed. Going to brake.");
      brake();
      return;
    }

    command_struct_.ang = command.angular.z;
    command_struct_.lin = command.linear.x;
    command_struct_.stamp = ros::Time::now();
    command_.writeFromNonRT(command_struct_);
    ROS_DEBUG_STREAM_NAMED(name_, "Added values to command. "
                                      << "Ang: " << command_struct_.ang << ", "
                                      << "Lin: " << command_struct_.lin << ", "
                                      << "Stamp: " << command_struct_.stamp);
  } else {
    ROS_ERROR_NAMED(name_,
                    "Can't accept new commands. Controller is not running.");
  }
}

bool TricycleController::getJointName(ros::NodeHandle &controller_nh,
                                      const std::string &wheel_param,
                                      std::string &wheel_name) {
  XmlRpc::XmlRpcValue wheel_list;
  if (!controller_nh.getParam(wheel_param, wheel_list)) {
    ROS_ERROR_STREAM_NAMED(name_, "Couldn't retrieve wheel param '"
                                      << wheel_param << "'.");
    return false;
  }

  if (wheel_list.getType() == XmlRpc::XmlRpcValue::TypeArray) {
    if (wheel_list.size() == 0) {
      ROS_ERROR_STREAM_NAMED(name_, "Wheel param '" << wheel_param
                                                    << "' is an empty list");
      return false;
    }

    for (int i = 0; i < wheel_list.size(); ++i) {
      if (wheel_list[i].getType() != XmlRpc::XmlRpcValue::TypeString) {
        ROS_ERROR_STREAM_NAMED(name_, "Wheel param '" << wheel_param << "' #"
                                                      << i
                                                      << " isn't a string.");
        return false;
      }
    }
    wheel_name = static_cast<std::string>(wheel_list[0]);
  } else if (wheel_list.getType() == XmlRpc::XmlRpcValue::TypeString) {
    wheel_name = static_cast<std::string>(wheel_list);
  } else {
    ROS_ERROR_STREAM_NAMED(
        name_, "Wheel param '"
                   << wheel_param
                   << "' is neither a list of strings nor a string.");
    return false;
  }

  return true;
}

void TricycleController::setOdomPubFields(ros::NodeHandle &root_nh,
                                          ros::NodeHandle &controller_nh) {
  // Get and check params for covariances
  XmlRpc::XmlRpcValue pose_cov_list;
  if (!controller_nh.getParam("pose_covariance_diagonal", pose_cov_list)) {
    ROS_ERROR_STREAM("Param pose_covariance_diagonal not found");
  }

  ROS_ASSERT(pose_cov_list.getType() == XmlRpc::XmlRpcValue::TypeArray);
  ROS_ASSERT(pose_cov_list.size() == 6);
  for (int i = 0; i < pose_cov_list.size(); ++i)
    ROS_ASSERT(pose_cov_list[i].getType() == XmlRpc::XmlRpcValue::TypeDouble);

  XmlRpc::XmlRpcValue twist_cov_list;
  if (!controller_nh.getParam("twist_covariance_diagonal", twist_cov_list)) {
    ROS_ERROR_STREAM("Param twist_covariance_diagonal not found");
  }
  ROS_ASSERT(twist_cov_list.getType() == XmlRpc::XmlRpcValue::TypeArray);
  ROS_ASSERT(twist_cov_list.size() == 6);
  for (int i = 0; i < twist_cov_list.size(); ++i)
    ROS_ASSERT(twist_cov_list[i].getType() == XmlRpc::XmlRpcValue::TypeDouble);

  // Setup odometry realtime publisher + odom message constant fields
  odom_pub_.reset(new realtime_tools::RealtimePublisher<nav_msgs::Odometry>(
      controller_nh, "odom", 100));
  odom_pub_->msg_.header.frame_id = odom_frame_id_;
  odom_pub_->msg_.child_frame_id = base_frame_id_;
  odom_pub_->msg_.pose.pose.position.z = 0;
  odom_pub_->msg_.pose.covariance =
      boost::assign::list_of(static_cast<double>(pose_cov_list[0]))(0)(0)(0)(0)(
          0)(0)(static_cast<double>(pose_cov_list[1]))(0)(0)(0)(0)(0)(0)(
          static_cast<double>(pose_cov_list[2]))(0)(0)(0)(0)(0)(0)(
          static_cast<double>(pose_cov_list[3]))(0)(0)(0)(0)(0)(0)(
          static_cast<double>(pose_cov_list[4]))(0)(0)(0)(0)(0)(0)(
          static_cast<double>(pose_cov_list[5]));
  odom_pub_->msg_.twist.twist.linear.y = 0;
  odom_pub_->msg_.twist.twist.linear.z = 0;
  odom_pub_->msg_.twist.twist.angular.x = 0;
  odom_pub_->msg_.twist.twist.angular.y = 0;
  odom_pub_->msg_.twist.covariance =
      boost::assign::list_of(static_cast<double>(twist_cov_list[0]))(0)(0)(0)(
          0)(0)(0)(static_cast<double>(twist_cov_list[1]))(0)(0)(0)(0)(0)(0)(
          static_cast<double>(twist_cov_list[2]))(0)(0)(0)(0)(0)(0)(
          static_cast<double>(twist_cov_list[3]))(0)(0)(0)(0)(0)(0)(
          static_cast<double>(twist_cov_list[4]))(0)(0)(0)(0)(0)(0)(
          static_cast<double>(twist_cov_list[5]));
  tf_odom_pub_.reset(new realtime_tools::RealtimePublisher<tf::tfMessage>(
      root_nh, "/tf", 100));
  tf_odom_pub_->msg_.transforms.resize(1);
  tf_odom_pub_->msg_.transforms[0].transform.translation.z = 0.0;
  tf_odom_pub_->msg_.transforms[0].child_frame_id = base_frame_id_;
  tf_odom_pub_->msg_.transforms[0].header.frame_id = odom_frame_id_;
}

} // namespace tricycle_controller


#include <scorum/chain/database/block_tasks/comments_cashout_impl.hpp>
#include <boost/range/combine.hpp>

namespace scorum {
namespace chain {
namespace database_ns {

process_comments_cashout_impl::process_comments_cashout_impl(block_task_context& ctx)
    : _ctx(ctx)
    , dgp_service(ctx.services().dynamic_global_property_service())
    , account_service(ctx.services().account_service())
    , account_blogging_statistic_service(ctx.services().account_blogging_statistic_service())
    , comment_service(ctx.services().comment_service())
    , comment_statistic_scr_service(ctx.services().comment_statistic_scr_service())
    , comment_statistic_sp_service(ctx.services().comment_statistic_sp_service())
    , comment_vote_service(ctx.services().comment_vote_service())
    , hardfork_service(ctx.services().hardfork_property_service())
{
}

void process_comments_cashout_impl::close_comment_payout(const comment_object& comment)
{
    comment_service.update(comment, [&](comment_object& c) {

        // TODO: remove abs_* fields (it is used only in tests)
        c.children_abs_rshares = 0;
        c.abs_rshares = 0;

        // to reset next payout for curators
        c.total_vote_weight = 0;

        c.cashout_time = fc::time_point_sec::maximum();
        c.last_payout = dgp_service.head_block_time();
    });

    _ctx.push_virtual_operation(comment_payout_update_operation(comment.author, fc::to_string(comment.permlink)));

#ifdef CLEAR_VOTES
    auto comment_votes = comment_vote_service.get_by_comment(comment.id);
    for (const comment_vote_object& vote : comment_votes)
    {
        comment_vote_service.remove(vote);
    }
#endif
}

fc::uint128_t process_comments_cashout_impl::get_total_claims(const comment_refs_type& comments,
                                                              curve_id reward_curve,
                                                              fc::uint128_t recent_claims) const
{
    shares_vector_type total_rshares;
    for (const comment_object& comment : comments)
    {
        total_rshares.push_back(comment.net_rshares);
    }

    fc::uint128_t total_claims = rewards_math::calculate_total_claims(recent_claims, reward_curve, total_rshares);

    return total_claims;
}

template <typename TFundService>
void process_comments_cashout_impl::reward(TFundService& fund_service, const comment_refs_type& comments)
{
    const auto& fund = fund_service.get();
    if (fund.activity_reward_balance.amount < 1 || comments.empty())
        return;

    auto total_claims = get_total_claims(comments, fund.author_reward_curve, fund.recent_claims);

    auto fund_rewards
        = calculate_comments_payout(comments, fund.activity_reward_balance, total_claims, fund.author_reward_curve);

    auto total_reward = hardfork_service.has_hardfork(SCORUM_HARDFORK_0_3)
        ? pay_for_comments(comments, fund_rewards)
        : pay_for_comments_legacy(comments, fund_rewards);

    fund_service.update([&](typename TFundService::object_type& rfo) {
        rfo.recent_claims = total_claims;
        rfo.activity_reward_balance -= total_reward;
        rfo.last_update = dgp_service.head_block_time();
    });
}

asset process_comments_cashout_impl::pay_for_comments(const comment_refs_type& comments,
                                                      const std::vector<asset>& fund_rewards)
{
    FC_ASSERT(comments.size() == fund_rewards.size(), "comments count and comments' rewards count should be equal");
    FC_ASSERT(!fund_rewards.empty(), "collection cannot be empty");

    auto reward_symbol = fund_rewards[0].symbol();
    auto total_claimed_reward = asset(0, reward_symbol);

    for (const auto& item : boost::combine(comments, fund_rewards))
    {
        const auto& comment = item.get<0>().get();
        const auto& fund_reward = item.get<1>();

        auto rewards = pay_curators(comment, fund_reward);
        auto beneficiaries_reward = pay_beneficiaries(comment, rewards.author_reward);

        auto curators_reward = rewards.curators_reward;
        auto author_reward = rewards.author_reward - beneficiaries_reward;

        const auto& author = account_service.get_account(comment.author);
        pay_account(author, author_reward);

        auto claimed_reward = curators_reward + author_reward + beneficiaries_reward;
        total_claimed_reward += claimed_reward;

        comment_service.update(comment, [&](comment_object& c) { c.last_payout = dgp_service.head_block_time(); });
        if (author_reward.amount > 0)
            comment_service.set_rewarded_flag(comment);

        _ctx.push_virtual_operation(
            author_reward_operation(comment.author, fc::to_string(comment.permlink), author_reward));

        // clang-format off
        _ctx.push_virtual_operation(comment_reward_operation(
                comment.author,
                fc::to_string(comment.permlink),
                fund_reward,
                claimed_reward,
                author_reward,
                curators_reward,
                asset(0, reward_symbol),
                asset(0, reward_symbol),
                beneficiaries_reward));

        accumulate_statistic(comment,
                             author,
                             fund_reward,
                             author_reward,
                             curators_reward,
                             asset(0, reward_symbol),
                             asset(0, reward_symbol),
                             beneficiaries_reward,
                             reward_symbol);
        // clang-format on
    }

    return total_claimed_reward;
}

asset process_comments_cashout_impl::pay_for_comments_legacy(const comment_refs_type& comments,
                                                             const std::vector<asset>& fund_rewards)
{
    FC_ASSERT(comments.size() == fund_rewards.size(), "comments count and comments' rewards count should be equal");
    FC_ASSERT(fund_rewards.size() > 0, "collection cannot be empty");

    asset_symbol_type reward_symbol = fund_rewards[0].symbol();

    struct comment_reward
    {
        share_type fund; // reward accrued from fund
        share_type commenting; // reward accrued from children comments
    };
    struct comment_key
    {
        account_name_type author;
        fc::shared_string permlink;
    };
    auto less = [](const comment_key& l, const comment_key& r) {
        return std::tie(l.author, l.permlink) < std::tie(r.author, r.permlink);
    };

    std::map<comment_key, comment_reward, decltype(less)> comment_rewards(less);
    for (auto i = 0u; i < comments.size(); ++i)
    {
        comment_rewards.emplace(comment_key{ comments[i].get().author, get_permlink(comments[i].get().permlink) },
                                comment_reward{ fund_rewards[i].amount, 0 });
    }

    asset total_reward = asset(0, reward_symbol);

    // newest, with bigger depth comments first
    comment_refs_type comments_with_parents = collect_parents(comments);

    for (const comment_object& comment : comments_with_parents)
    {
        const auto& reward = comment_rewards[comment_key{ comment.author, get_permlink(comment.permlink) }];

        asset fund_reward = asset(reward.fund, reward_symbol);
        asset parent_payout_value = asset(reward.commenting, reward_symbol);

        comment_payout_result payout_result = pay_for_comment_legacy(comment, fund_reward, parent_payout_value);

        total_reward += payout_result.total_claimed_reward;

        // save payout for the parent comment
        comment_rewards[comment_key{ comment.parent_author, get_permlink(comment.parent_permlink) }].commenting
            += payout_result.parent_comment_reward.amount;
    }

    return total_reward;
}

process_comments_cashout_impl::comment_payout_result process_comments_cashout_impl::pay_for_comment_legacy(
    const comment_object& comment, const asset& fund_reward, const asset& payout_from_children)
{
    try
    {
        auto reward_symbol = fund_reward.symbol();
        comment_payout_result payout_result{ asset(0, reward_symbol), asset(0, reward_symbol) };
        if (fund_reward.amount < 1 && payout_from_children.amount < 1)
            return payout_result;

        auto rewards = pay_curators(comment, fund_reward);
        auto author_reward = rewards.author_reward;
        auto curators_reward = rewards.curators_reward;

        auto payout_to_parent = asset(0, reward_symbol);
        if (comment.depth != 0)
        {
            auto fraction = utils::make_fraction(SCORUM_PARENT_COMMENT_REWARD_PERCENT, SCORUM_100_PERCENT);
            payout_to_parent = (author_reward + payout_from_children) * fraction;
        }

        author_reward = (author_reward + payout_from_children) - payout_to_parent;

        auto beneficiaries_reward = pay_beneficiaries(comment, author_reward);
        author_reward -= beneficiaries_reward;

        const auto& author = account_service.get_account(comment.author);
        pay_account(author, author_reward);

        payout_result.total_claimed_reward = curators_reward + author_reward + beneficiaries_reward;
        payout_result.parent_comment_reward = payout_to_parent;

        comment_service.update(comment, [&](comment_object& c) { c.last_payout = dgp_service.head_block_time(); });
        if (author_reward.amount > 0 || payout_from_children.amount > 0)
            comment_service.set_rewarded_flag(comment);

        _ctx.push_virtual_operation(
            author_reward_operation(comment.author, fc::to_string(comment.permlink), author_reward));

        // clang-format off
        _ctx.push_virtual_operation(comment_reward_operation(
                             comment.author,
                             fc::to_string(comment.permlink),
                             fund_reward,
                             payout_result.total_claimed_reward,
                             author_reward,
                             curators_reward,
                             payout_from_children,
                             payout_to_parent,
                             beneficiaries_reward));

        accumulate_statistic(comment,
                             author,
                             fund_reward,
                             author_reward,
                             curators_reward,
                             payout_from_children,
                             payout_to_parent,
                             beneficiaries_reward,
                             reward_symbol);
        // clang-format on

        return payout_result;
    }
    FC_CAPTURE_AND_RETHROW((comment))
}

process_comments_cashout_impl::curators_author_rewards
process_comments_cashout_impl::pay_curators(const comment_object& comment, const asset& fund_reward)
{
    try
    {
        auto reward_symbol = fund_reward.symbol();
        auto potential_reward = fund_reward * utils::make_fraction(SCORUM_CURATION_REWARD_PERCENT, SCORUM_100_PERCENT);

        if (!comment.allow_curation_rewards)
            return { asset(0, reward_symbol), fund_reward - potential_reward };
        else if (comment.total_vote_weight <= 0)
            return { asset(0, reward_symbol), fund_reward };

        auto rewarded = asset(0, reward_symbol);
        auto comment_votes = comment_vote_service.get_by_comment_weight_voter(comment.id);
        for (const comment_vote_object& vote : comment_votes)
        {
            auto claim = potential_reward * utils::make_fraction(vote.weight, comment.total_vote_weight);
            if (claim.amount > 0)
            {
                rewarded += claim;

                const auto& voter = account_service.get(vote.voter);
                pay_account(voter, claim);

                _ctx.push_virtual_operation(
                    curation_reward_operation(voter.name, claim, comment.author, fc::to_string(comment.permlink)));

                accumulate_statistic(voter, claim);
            }
        }

        return { rewarded, fund_reward - rewarded };
    }
    FC_CAPTURE_AND_RETHROW((comment.author)(comment.permlink)(fund_reward))
}

asset process_comments_cashout_impl::pay_beneficiaries(const comment_object& comment, const asset& author_reward)
{
    auto reward_symbol = author_reward.symbol();

    auto beneficiaries_reward = asset(0, reward_symbol);
    for (auto& beneficiary : comment.beneficiaries)
    {
        auto beneficiary_reward = author_reward * utils::make_fraction(beneficiary.weight, SCORUM_100_PERCENT);
        pay_account(account_service.get_account(beneficiary.account), beneficiary_reward);
        beneficiaries_reward += beneficiary_reward;

        _ctx.push_virtual_operation(comment_benefficiary_reward_operation(
            beneficiary.account, comment.author, fc::to_string(comment.permlink), beneficiary_reward));
    }

    return beneficiaries_reward;
}

void process_comments_cashout_impl::pay_account(const account_object& recipient, const asset& reward)
{
    if (SCORUM_SYMBOL == reward.symbol())
    {
        account_service.increase_balance(recipient, reward);
    }
    else if (SP_SYMBOL == reward.symbol())
    {
        account_service.create_scorumpower(recipient, reward);
    }
}

std::vector<asset> process_comments_cashout_impl::calculate_comments_payout(const comment_refs_type& comments,
                                                                            const asset& reward_fund_balance,
                                                                            fc::uint128_t total_claims,
                                                                            curve_id reward_curve) const
{
    std::vector<asset> rewards;
    rewards.reserve(comments.size());

    for (const comment_object& comment : comments)
    {
        share_type payout = rewards_math::calculate_payout(
            comment.net_rshares, total_claims, reward_fund_balance.amount, reward_curve,
            comment.max_accepted_payout.amount, SCORUM_MIN_COMMENT_PAYOUT_SHARE);

        rewards.emplace_back(payout, reward_fund_balance.symbol());
    }

    return rewards;
}

void process_comments_cashout_impl::accumulate_statistic(const comment_object& comment,
                                                         const account_object& author,
                                                         const asset& fund_reward,
                                                         const asset& author_payout,
                                                         const asset& curation_payout,
                                                         const asset& payout_from_children,
                                                         const asset& payout_to_parent,
                                                         const asset& beneficiary_payout,
                                                         asset_symbol_type reward_symbol)
{
    FC_ASSERT(author_payout.symbol() == reward_symbol);
    FC_ASSERT(curation_payout.symbol() == reward_symbol);
    FC_ASSERT(beneficiary_payout.symbol() == reward_symbol);

    asset total_payout = author_payout;
    total_payout += curation_payout;
    total_payout += beneficiary_payout;

    // clang-format off
    if (SCORUM_SYMBOL == reward_symbol)
    {
        accumulate_comment_statistic(comment_statistic_scr_service, comment,
                                     fund_reward,
                                     total_payout,
                                     author_payout,
                                     curation_payout,
                                     payout_from_children,
                                     payout_to_parent,
                                     beneficiary_payout);
    }
    else if (SP_SYMBOL == reward_symbol)
    {
        accumulate_comment_statistic(comment_statistic_sp_service, comment,
                                     fund_reward,
                                     total_payout,
                                     author_payout,
                                     curation_payout,
                                     payout_from_children,
                                     payout_to_parent,
                                     beneficiary_payout);
    }
// clang-format on

#ifndef IS_LOW_MEM
    {
        const auto& author_stat = account_blogging_statistic_service.obtain(author.id);
        account_blogging_statistic_service.increase_posting_rewards(author_stat, author_payout);
    }
#endif
}

void process_comments_cashout_impl::accumulate_statistic(const account_object& voter, const asset& curation_payout)
{
#ifndef IS_LOW_MEM
    const auto& voter_stat = account_blogging_statistic_service.obtain(voter.id);
    account_blogging_statistic_service.increase_curation_rewards(voter_stat, curation_payout);
#endif
}

comment_refs_type process_comments_cashout_impl::collect_parents(const comment_refs_type& comments)
{
    struct by_depth_greater
    {
        bool operator()(const comment_object& lhs, const comment_object& rhs)
        {
            return std::tie(lhs.depth, lhs.id) > std::tie(rhs.depth, rhs.id);
        }
    };

    using comment_refs_set = std::set<comment_refs_type::value_type, by_depth_greater>;
    comment_refs_set ordered_comments(comments.begin(), comments.end());

    // 'ordered_comments' set is sorted by depth in desc order.
    for (auto it = ordered_comments.begin(); it != ordered_comments.end() && it->get().depth != 0; ++it)
    {
        const comment_object& comment = it->get();

        const auto& parent_comment = comment_service.get(comment.parent_author, fc::to_string(comment.parent_permlink));

        // insert parent if it doesn't exist or do nothing if exists. Insertable parent will be always 'after' current
        // comment because of the set ordering
        ordered_comments.insert(parent_comment);
    }

    comment_refs_type comments_with_parents(ordered_comments.begin(), ordered_comments.end());

    return comments_with_parents;
}

fc::shared_string process_comments_cashout_impl::get_permlink(const fc::shared_string& str) const
{
    if (hardfork_service.has_hardfork(SCORUM_HARDFORK_0_1))
        return str;
    else
        return fc::shared_string("", str.get_allocator());
}

// Explicit template instantiation
// clang-format off
template void process_comments_cashout_impl::reward<content_reward_fund_scr_service_i>(content_reward_fund_scr_service_i&, const comment_refs_type&);
template void process_comments_cashout_impl::reward<content_reward_fund_sp_service_i>(content_reward_fund_sp_service_i&, const comment_refs_type&);
template void process_comments_cashout_impl::reward<content_fifa_world_cup_2018_bounty_reward_fund_service_i>( content_fifa_world_cup_2018_bounty_reward_fund_service_i&, const comment_refs_type&);
// clang-format on
}
}
}


#include <boost/multiprecision/cpp_bin_float.hpp>
#include <cassert>
#include <chrono>
#include <clamp_cast/clamp_cast.hpp>
#include <cstddef>
#include <cstdio>
#include <gtest/gtest.h>
#include <random>
#include <tuple>

#define test_hi_to_lo(T, U) \
	{ \
		T t = std::numeric_limits<T>::lowest(); \
		U u = clamp_cast<U>(t); \
		EXPECT_EQ(u, std::numeric_limits<U>::lowest()); \
\
		t = std::numeric_limits<T>::max(); \
		u = clamp_cast<U>(t); \
		EXPECT_EQ(u, std::numeric_limits<U>::max()); \
	}

#define test_lo_to_hi(U, T) \
	{ \
		T t = std::numeric_limits<T>::lowest(); \
		U u = clamp_cast<U>(t); \
		EXPECT_EQ(u, std::numeric_limits<U>::lowest()); \
\
		t = std::numeric_limits<T>::max(); \
		u = clamp_cast<U>(t); \
		EXPECT_EQ(u, std::numeric_limits<U>::max()); \
	}

#define test_lo_to_hi_unsigned(U, T) \
	{ \
		T t = std::numeric_limits<T>::lowest(); \
		U u = clamp_cast<U>(t); \
		EXPECT_EQ(u, U{ 0 }); \
\
		t = std::numeric_limits<T>::max(); \
		u = clamp_cast<U>(t); \
		EXPECT_EQ(u, std::numeric_limits<U>::max()); \
	}

#define test_hi_unsigned_to_lo(T, U) \
	{ \
		U u = std::numeric_limits<U>::lowest(); \
		T t = clamp_cast<T>(u); \
		EXPECT_EQ(t, T{ 0 }); \
\
		t = std::numeric_limits<T>::max(); \
		u = clamp_cast<U>(t); \
		EXPECT_EQ(u, std::numeric_limits<U>::max()); \
	}

namespace {
template <class... Args>
void unused(Args...) noexcept {
}

template <class Tuple, class Func>
void for_each(Tuple& tuple, Func&& func) noexcept {
	std::apply([&](auto... args) { (std::invoke(func, args), ...); }, tuple);
}

template <class T, class U>
void debug_hi_to_lo() {
	T t = std::numeric_limits<T>::lowest();
	U u = clamp_cast<U>(t);
	EXPECT_EQ(u, std::numeric_limits<U>::lowest());

	t = std::numeric_limits<T>::max();
	u = clamp_cast<U>(t);
	EXPECT_EQ(u, std::numeric_limits<U>::max());
}

template <class T, class U>
void debug_hi_unsigned_to_lo() {
	U u = std::numeric_limits<U>::lowest();
	T t = clamp_cast<T>(u);
	EXPECT_EQ(t, T{ 0 });

	t = std::numeric_limits<T>::max();
	u = clamp_cast<U>(t);
	EXPECT_EQ(u, std::numeric_limits<U>::max());
}

template <class U, class T>
void debug_lo_to_hi() {
	T t = std::numeric_limits<T>::lowest();
	U u = clamp_cast<U>(t);
	EXPECT_EQ(u, std::numeric_limits<U>::lowest());

	t = std::numeric_limits<T>::max();
	u = clamp_cast<U>(t);
	EXPECT_EQ(u, std::numeric_limits<U>::max());
}

template <class U, class T>
void debug_lo_to_hi_unsigned() {
	U u = std::numeric_limits<U>::lowest();
	T t = clamp_cast<T>(u);
	EXPECT_EQ(t, T{ 0 });

	t = std::numeric_limits<T>::max();
	u = clamp_cast<U>(t);
	EXPECT_EQ(u, std::numeric_limits<U>::max());
}

TEST(clamp_cast, int_to_int) {
	test_hi_to_lo(int64_t, int32_t);
	test_hi_to_lo(int64_t, int16_t);
	test_hi_to_lo(int64_t, int8_t);

	test_lo_to_hi(int32_t, int64_t);
	test_hi_to_lo(int32_t, int16_t);
	test_hi_to_lo(int32_t, int8_t);

	test_lo_to_hi(int16_t, int64_t);
	test_lo_to_hi(int16_t, int32_t);
	test_hi_to_lo(int16_t, int8_t);

	test_lo_to_hi(int8_t, int64_t);
	test_lo_to_hi(int8_t, int32_t);
	test_lo_to_hi(int8_t, int16_t);
}

TEST(clamp_cast, float_to_float) {
	test_hi_to_lo(long double, double);
	test_hi_to_lo(long double, float);

	test_lo_to_hi(double, long double);
	test_hi_to_lo(double, float);

	test_lo_to_hi(float, long double);
	test_lo_to_hi(float, double);
}

TEST(clamp_cast, float_to_int) {
	test_hi_to_lo(long double, int64_t);
	test_hi_to_lo(long double, int32_t);
	test_hi_to_lo(long double, int16_t);
	test_hi_to_lo(long double, int8_t);

	test_hi_to_lo(double, int64_t);
	test_hi_to_lo(double, int32_t);
	test_hi_to_lo(double, int16_t);
	test_hi_to_lo(double, int8_t);

	test_hi_to_lo(float, int64_t);
	test_hi_to_lo(float, int32_t);
	test_hi_to_lo(float, int16_t);
	test_hi_to_lo(float, int8_t);
}

TEST(clamp_cast, int_to_float) {
	test_lo_to_hi(int64_t, long double);
	test_lo_to_hi(int64_t, double);
	test_lo_to_hi(int64_t, float);

	test_lo_to_hi(int32_t, long double);
	test_lo_to_hi(int32_t, double);
	test_lo_to_hi(int32_t, float);

	test_lo_to_hi(int16_t, long double);
	test_lo_to_hi(int16_t, double);
	test_lo_to_hi(int16_t, float);

	test_lo_to_hi(int8_t, long double);
	test_lo_to_hi(int8_t, double);
	test_lo_to_hi(int8_t, float);
}

TEST(clamp_cast, uint_to_uint) {
	test_hi_to_lo(uint64_t, uint32_t);
	test_hi_to_lo(uint64_t, uint16_t);
	test_hi_to_lo(uint64_t, uint8_t);

	test_lo_to_hi(uint32_t, uint64_t);
	test_hi_to_lo(uint32_t, uint16_t);
	test_hi_to_lo(uint32_t, uint8_t);

	test_lo_to_hi(uint16_t, uint64_t);
	test_lo_to_hi(uint16_t, uint32_t);
	test_hi_to_lo(uint16_t, uint8_t);

	test_lo_to_hi(uint8_t, uint64_t);
	test_lo_to_hi(uint8_t, uint32_t);
	test_lo_to_hi(uint8_t, uint16_t);
}

TEST(clamp_cast, int_to_uint) {
	test_lo_to_hi_unsigned(int64_t, uint64_t);
	test_hi_to_lo(int64_t, uint32_t);
	test_hi_to_lo(int64_t, uint16_t);
	test_hi_to_lo(int64_t, uint8_t);

	test_lo_to_hi_unsigned(int32_t, uint64_t);
	test_lo_to_hi_unsigned(int32_t, uint32_t);
	test_hi_to_lo(int32_t, uint16_t);
	test_hi_to_lo(int32_t, uint8_t);

	test_lo_to_hi_unsigned(int16_t, uint64_t);
	test_lo_to_hi_unsigned(int16_t, uint32_t);
	test_lo_to_hi_unsigned(int16_t, uint16_t);
	test_hi_to_lo(int16_t, uint8_t);

	test_lo_to_hi_unsigned(int8_t, uint64_t);
	test_lo_to_hi_unsigned(int8_t, uint32_t);
	test_lo_to_hi_unsigned(int8_t, uint16_t);
	test_lo_to_hi_unsigned(int8_t, uint8_t);
}

TEST(clamp_cast, float_to_uint) {
	test_hi_to_lo(long double, uint64_t);
	test_hi_to_lo(long double, uint32_t);
	test_hi_to_lo(long double, uint16_t);
	test_hi_to_lo(long double, uint8_t);

	test_hi_to_lo(double, uint64_t);
	test_hi_to_lo(double, uint32_t);
	test_hi_to_lo(double, uint16_t);
	test_hi_to_lo(double, uint8_t);

	test_hi_to_lo(float, uint64_t);
	test_hi_to_lo(float, uint32_t);
	test_hi_to_lo(float, uint16_t);
	test_hi_to_lo(float, uint8_t);
}

TEST(clamp_cast, uint_to_int) {
	test_hi_unsigned_to_lo(uint64_t, int64_t);
	test_hi_unsigned_to_lo(uint64_t, int32_t);
	test_hi_unsigned_to_lo(uint64_t, int16_t);
	test_hi_unsigned_to_lo(uint64_t, int8_t);

	test_lo_to_hi(uint32_t, int64_t);
	test_hi_unsigned_to_lo(uint32_t, int32_t);
	test_hi_unsigned_to_lo(uint32_t, int16_t);
	test_hi_unsigned_to_lo(uint32_t, int8_t);

	test_lo_to_hi(uint16_t, int64_t);
	test_lo_to_hi(uint16_t, int32_t);
	test_hi_unsigned_to_lo(uint16_t, int16_t);
	test_hi_unsigned_to_lo(uint16_t, int8_t);

	test_lo_to_hi(uint8_t, int64_t);
	test_lo_to_hi(uint8_t, int32_t);
	test_lo_to_hi(uint8_t, int16_t);
	test_hi_unsigned_to_lo(uint8_t, int8_t);
}

TEST(clamp_cast, uint_to_float) {
	test_lo_to_hi(uint64_t, long double);
	test_lo_to_hi(uint64_t, double);
	test_lo_to_hi(uint64_t, float);

	test_lo_to_hi(uint32_t, long double);
	test_lo_to_hi(uint32_t, double);
	test_lo_to_hi(uint32_t, float);

	test_lo_to_hi(uint16_t, long double);
	test_lo_to_hi(uint16_t, double);
	test_lo_to_hi(uint16_t, float);

	test_lo_to_hi(uint8_t, long double);
	test_lo_to_hi(uint8_t, double);
	test_lo_to_hi(uint8_t, float);
}

TEST(clamp_cast, all_permutations) {
	std::tuple<uint64_t, uint32_t, uint16_t, uint8_t, int64_t, int32_t, int16_t,
			int8_t, long double, double, float>
			all_types{};

	for_each(all_types, [&](auto val1) {
		for_each(all_types, [&](auto val2) {
			unused(val1, val2);
			using val_t1 = std::decay_t<decltype(val1)>;
			using val_t2 = std::decay_t<decltype(val2)>;

			// Check signed / unsigned clamping
			{
				for (int8_t i = std::numeric_limits<int8_t>::lowest();
						i < std::numeric_limits<int8_t>::max(); ++i) {
					val_t1 v1 = clamp_cast<val_t1>(i);
					val_t2 v2 = clamp_cast<val_t2>(v1);
					if constexpr (std::is_unsigned_v<val_t1>) {
						if (i < 0) {
							EXPECT_EQ(v1, val_t1{ 0 });
						}
					} else {
						EXPECT_EQ(v1, static_cast<val_t1>(i));
					}

					if constexpr (std::is_unsigned_v<val_t2>) {
						if (v1 < val_t1{ 0 }) {
							EXPECT_EQ(v2, val_t2{ 0 });
						}
					} else {
						EXPECT_EQ(v2, static_cast<val_t2>(v1));
					}
				}
			}

			// Thorough checks
			{
				val_t1 v1_low = std::numeric_limits<val_t1>::lowest();
				val_t1 v1_hi = std::numeric_limits<val_t1>::max();
				val_t2 v2_low = std::numeric_limits<val_t2>::lowest();
				val_t2 v2_hi = std::numeric_limits<val_t2>::max();

				using namespace boost::multiprecision;
				using d128 = cpp_bin_float_quad;

				d128 v1_low128 = static_cast<d128>(v1_low);
				d128 v1_hi128 = static_cast<d128>(v1_hi);
				d128 v2_low128 = static_cast<d128>(v2_low);
				d128 v2_hi128 = static_cast<d128>(v2_hi);

				val_t1 v1 = clamp_cast<val_t1>(v2_low);
				if (v1_low128 <= v2_low128) {
					EXPECT_EQ(v1, static_cast<val_t1>(v2_low));
				} else {
					EXPECT_EQ(v1, v1_low);
				}
				v1 = clamp_cast<val_t1>(v2_hi);
				if (v1_hi128 >= v2_hi128) {
					EXPECT_EQ(v1, static_cast<val_t1>(v2_hi));
				} else {
					EXPECT_EQ(v1, v1_hi);
				}

				val_t2 v2 = clamp_cast<val_t2>(v1_low);
				if (v2_low128 <= v1_low128) {
					EXPECT_EQ(v2, static_cast<val_t2>(v1_low));
				} else {
					EXPECT_EQ(v2, v2_low);
				}
				v2 = clamp_cast<val_t2>(v1_hi);
				if (v2_hi128 >= v1_hi128) {
					EXPECT_EQ(v2, static_cast<val_t2>(v1_hi));
				} else {
					EXPECT_EQ(v2, v2_hi);
				}
			}
		});
	});
}

TEST(clamp_cast, edges) {
	std::tuple<uint64_t, uint32_t, uint16_t, uint8_t, int64_t, int32_t, int16_t,
			int8_t, long double, double, float>
			all_types{};

	for_each(all_types, [&](auto val1) {
		for_each(all_types, [&](auto val2) {
			unused(val1, val2);
			using val_t1 = std::decay_t<decltype(val1)>;
			using val_t2 = std::decay_t<decltype(val2)>;

			val_t1 v1_low = std::numeric_limits<val_t1>::lowest();
			val_t1 v1_hi = std::numeric_limits<val_t1>::max();
			val_t2 v2_low = std::numeric_limits<val_t2>::lowest();
			val_t2 v2_hi = std::numeric_limits<val_t2>::max();

			using namespace boost::multiprecision;
			using d128 = cpp_bin_float_quad;

			d128 v1_low128 = static_cast<d128>(v1_low);
			d128 v1_hi128 = static_cast<d128>(v1_hi);
			d128 v2_low128 = static_cast<d128>(v2_low);
			d128 v2_hi128 = static_cast<d128>(v2_hi);

			constexpr val_t1 edges{ 20 };

			if constexpr (std::is_floating_point_v<val_t1>) {
				val_t1 stop_iter = std::nextafter(v1_low, v1_hi);
				for (size_t i = 0; i < edges; ++i) {
					stop_iter = std::nextafter(stop_iter, v1_hi);
				}

				for (val_t1 i = v1_low; i < stop_iter;
						i = std::nextafter(i, v1_hi)) {
					val_t2 v2 = clamp_cast<val_t2>(i);
					if (v2_low128 <= v1_low128) {
						EXPECT_EQ(v2, static_cast<val_t2>(i));
					} else {
						EXPECT_EQ(v2, v2_low);
					}
				}

				stop_iter = std::nextafter(v1_hi, v1_low);
				for (size_t i = 0; i < edges; ++i) {
					stop_iter = std::nextafter(stop_iter, v1_low);
				}

				for (val_t1 i = v1_hi; i > stop_iter;
						i = std::nextafter(i, v1_low)) {
					val_t2 v2 = clamp_cast<val_t2>(i);
					if (v2_hi128 >= v1_hi128) {
						EXPECT_EQ(v2, static_cast<val_t2>(i));
					} else {
						EXPECT_EQ(v2, v2_hi);
					}
				}
			} else {
				for (val_t1 i = v1_low; i < v1_low + edges; ++i) {
					val_t2 v2 = clamp_cast<val_t2>(i);
					if (v2_low128 <= v1_low128) {
						EXPECT_EQ(v2, static_cast<val_t2>(i));
					} else {
						EXPECT_EQ(v2, v2_low);
					}
				}

				for (val_t1 i = v1_hi; i > v1_hi - edges; --i) {
					val_t2 v2 = clamp_cast<val_t2>(i);
					if (v2_hi128 >= v1_hi128) {
						EXPECT_EQ(v2, static_cast<val_t2>(i));
					} else {
						EXPECT_EQ(v2, v2_hi);
					}
				}
			}
		});
	});
}
} // namespace

int main(int argc, char** argv) {
	double ld = -42.0;
	unsigned char uc = clamp_cast<unsigned char>(ld);
	printf("%u\n", uc);

	float f = 500000.f;
	char c = clamp_cast<char>(f);
	printf("%d\n", c);

	::testing::InitGoogleTest(&argc, argv);
	return RUN_ALL_TESTS();
}