// Copyright (c) 2015-2019 Daniel Cooke
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

#ifndef read_stats_hpp
#define read_stats_hpp

#include <vector>
#include <iterator>
#include <utility>
#include <algorithm>
#include <numeric>
#include <type_traits>

#include <boost/range/algorithm.hpp>
#include <boost/optional.hpp>

#include "basics/aligned_read.hpp"
#include "basics/mappable_reference_wrapper.hpp"
#include "containers/mappable_map.hpp"
#include "mappable_algorithms.hpp"
#include "maths.hpp"
#include "type_tricks.hpp"

namespace octopus {

namespace detail {

template <typename T>
constexpr bool is_aligned_read = std::is_same<std::decay_t<T>, AlignedRead>::value;
template <typename T>
constexpr bool is_aligned_read_mref = std::is_same<std::decay_t<T>, MappableReferenceWrapper<AlignedRead>>::value;
template <typename T>
constexpr bool is_aligned_read_mcref = std::is_same<std::decay_t<T>, MappableReferenceWrapper<const AlignedRead>>::value;

template <typename Container>
constexpr bool is_aligned_read_container = is_aligned_read<typename Container::value_type>
                                        || is_aligned_read_mref<typename Container::value_type>
                                        || is_aligned_read_mcref<typename Container::value_type>;

struct IsForward
{
    bool operator()(const AlignedRead& read) const
    {
        return is_forward_strand(read);
    }
};

struct IsReverse
{
    bool operator()(const AlignedRead& read) const
    {
        return is_reverse_strand(read);
    }
};

struct IsMappingQualityZero
{
    bool operator()(const AlignedRead& read) const noexcept
    {
        return read.mapping_quality() == 0;
    }
};

template <typename Map>
bool all_empty(const Map& map) noexcept
{
    return std::all_of(std::cbegin(map), std::cend(map), [] (const auto& p) { return p.second.empty(); });
}

template <typename T>
bool has_coverage(const T& reads, NonMapTag)
{
    using octopus::has_coverage;
    return has_coverage(reads);
}

template <typename T>
bool has_coverage(const T& reads, const GenomicRegion& region, NonMapTag)
{
    using octopus::has_coverage;
    return has_coverage(reads, region);
}

template <typename T>
unsigned min_coverage(const T& reads, NonMapTag)
{
    using octopus::min_coverage;
    return min_coverage(reads);
}

template <typename T>
unsigned min_coverage(const T& reads, const GenomicRegion& region, NonMapTag)
{
    using octopus::min_coverage;
    return min_coverage(reads, region);
}

template <typename T>
unsigned max_coverage(const T& reads, NonMapTag)
{
    using octopus::max_coverage;
    return max_coverage(reads);
}

template <typename T>
unsigned max_coverage(const T& reads, const GenomicRegion& region, NonMapTag)
{
    using octopus::max_coverage;
    return max_coverage(reads, region);
}

template <typename T>
double mean_coverage(const T& reads, const GenomicRegion& region, NonMapTag)
{
    if (reads.empty() || is_empty(region)) return 0;
    return maths::mean(calculate_positional_coverage(reads, region));
}

template <typename T>
double mean_coverage(const T& reads, NonMapTag)
{
    if (reads.empty()) return 0;
    return mean_coverage(reads, encompassing_region(reads), NonMapTag {});
}

template <typename T>
double stdev_coverage(const T& reads, const GenomicRegion& region, NonMapTag)
{
    if (reads.empty() || is_empty(region)) return 0;
    return maths::stdev(calculate_positional_coverage(reads, region));
}

template <typename T>
double stdev_coverage(const T& reads, NonMapTag)
{
    if (reads.empty()) return 0;
    return stdev_coverage(reads, encompassing_region(reads), NonMapTag {});
}

template <typename T>
std::size_t count_reads(const T& reads, NonMapTag)
{
    return reads.size();
}

template <typename T>
std::size_t count_reads(const T& reads, const GenomicRegion& region, NonMapTag)
{
    return count_overlapped(reads, region);
}

template <typename T>
std::size_t count_forward(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    return std::count_if(std::cbegin(reads), std::cend(reads), IsForward {});
}

template <typename T>
std::size_t count_forward(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    return std::count_if(std::cbegin(overlapped), std::cend(overlapped), IsForward {});
}

template <typename T>
std::size_t count_reverse(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    return std::count_if(std::cbegin(reads), std::cend(reads), IsReverse {});
}

template <typename T>
std::size_t count_reverse(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    return std::count_if(std::cbegin(overlapped), std::cend(overlapped), IsReverse {});
}

template <typename T>
std::pair<std::size_t, std::size_t> count_directions(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto num_forward = std::count_if(std::cbegin(reads), std::cend(reads), IsForward {});
    return std::make_pair(num_forward, reads.size() - num_forward);
}

template <typename T>
std::pair<std::size_t, std::size_t> count_directions(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    const auto num_forward = std::count_if(std::cbegin(overlapped), std::cend(overlapped), IsForward {});
    return std::make_pair(num_forward, size(overlapped) - num_forward);
}

struct IsSupplementary
{
    bool operator()(const AlignedRead& read) const noexcept
    {
        return read.is_marked_supplementary_alignment();
    }
};

template <typename T>
std::size_t count_supplementary(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    return std::count_if(std::cbegin(reads), std::cend(reads), IsSupplementary {});
}

template <typename T>
std::size_t count_supplementary(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    return std::count_if(std::cbegin(overlapped), std::cend(overlapped), IsSupplementary {});
}

struct IsSecondary
{
    bool operator()(const AlignedRead& read) const noexcept
    {
        return read.is_marked_secondary_alignment();
    }
};

template <typename T>
std::size_t count_secondary(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    return std::count_if(std::cbegin(reads), std::cend(reads), IsSecondary {});
}

template <typename T>
std::size_t count_secondary(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    return std::count_if(std::cbegin(overlapped), std::cend(overlapped), IsSecondary {});
}

struct IsShorter
{
    bool operator()(const AlignedRead& lhs, const AlignedRead& rhs) const noexcept
    {
        return sequence_size(lhs) < sequence_size(rhs);
    }
};

template <typename T>
AlignedRead::NucleotideSequence::size_type min_read_length(const T& reads, NonMapTag)
{
    if (reads.empty()) return 0;
    return sequence_size(*std::min_element(std::cbegin(reads), std::cend(reads), IsShorter {}));
}

template <typename T>
AlignedRead::NucleotideSequence::size_type min_read_length(const T& reads, const GenomicRegion& region, NonMapTag)
{
    const auto overlapped = overlap_range(reads, region);
    if (empty(overlapped)) return 0;
    return sequence_size(*std::min_element(std::cbegin(overlapped), std::cend(overlapped), IsShorter {}));
}

template <typename T>
AlignedRead::NucleotideSequence::size_type max_read_length(const T& reads, NonMapTag)
{
    if (reads.empty()) return 0;
    return sequence_size(*std::min_element(std::cbegin(reads), std::cend(reads), IsShorter {}));
}

template <typename T>
AlignedRead::NucleotideSequence::size_type max_read_length(const T& reads, const GenomicRegion& region, NonMapTag)
{
    const auto overlapped = overlap_range(reads, region);
    if (empty(overlapped)) return 0;
    return sequence_size(*std::max_element(std::cbegin(overlapped), std::cend(overlapped), IsShorter {}));
}

template <typename T>
AlignedRead::NucleotideSequence::size_type mean_read_length(const T& reads, NonMapTag)
{
    if (reads.empty()) return 0;
    return maths::mean(std::cbegin(reads), std::cend(reads), [] (const auto& read) { return sequence_size(read); });
}

template <typename T>
AlignedRead::NucleotideSequence::size_type mean_read_length(const T& reads, const GenomicRegion& region, NonMapTag)
{
    const auto overlapped = overlap_range(reads, region);
    if (empty(overlapped)) return 0;
    return maths::mean(std::cbegin(overlapped), std::cend(overlapped), [] (const auto& read) { return sequence_size(read); });
}

template <typename T>
AlignedRead::NucleotideSequence::size_type median_read_length(const T& reads, NonMapTag)
{
    if (reads.empty()) return 0;
    std::vector<AlignedRead::NucleotideSequence::size_type> lengths(reads.size());
    std::transform(std::cbegin(reads), std::cend(reads), std::begin(lengths), [] (const auto& read) { return sequence_size(read); });
    return maths::median(lengths);
}

template <typename T>
AlignedRead::NucleotideSequence::size_type median_read_length(const T& reads, const GenomicRegion& region, NonMapTag)
{
    const auto overlapped = overlap_range(reads, region);
    if (empty(overlapped)) return 0;
    std::vector<AlignedRead::NucleotideSequence::size_type> lengths(size(overlapped));
    std::transform(std::cbegin(overlapped), std::cend(overlapped), std::begin(lengths), [] (const auto& read) { return sequence_size(read); });
    return maths::median(lengths);
}

template <typename T>
std::size_t count_base_pairs(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [] (const auto curr, const auto& read) {
                               return curr + static_cast<std::size_t>(sequence_size(read));
                           });
}

template <typename T>
std::size_t count_base_pairs(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    return std::accumulate(std::cbegin(overlapped), std::cend(overlapped), std::size_t {0},
                           [&region] (const auto curr, const auto& read) {
                               return curr + sequence_size(read, region);
                           });
}

template <typename T>
std::size_t count_forward_base_pairs(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [] (const auto curr, const auto& read) {
                               return curr + ((IsForward()(read)) ? static_cast<std::size_t>(sequence_size(read)) : 0);
                           });
}

template <typename T>
std::size_t count_forward_base_pairs(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    return std::accumulate(std::cbegin(overlapped), std::cend(overlapped), std::size_t {0},
                           [&region] (const auto curr, const auto& read) {
                               return curr + ((IsForward()(read)) ? static_cast<std::size_t>(sequence_size(read, region)) : 0);
                           });
}

template <typename T>
std::size_t count_reverse_base_pairs(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {},
                           [] (const auto curr, const auto& read) {
                               return curr + ((IsReverse()(read)) ? static_cast<std::size_t>(sequence_size(read)) : 0);
                           });
}

template <typename T>
std::size_t count_reverse_base_pairs(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    return std::accumulate(std::cbegin(overlapped), std::cend(overlapped), std::size_t {0},
                           [&region] (const auto curr, const auto& read) {
                               return curr + ((IsReverse()(read)) ? static_cast<std::size_t>(sequence_size(read, region)) : 0);
                           });
}

template <typename T>
std::size_t count_mapq_zero(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    return std::count_if(std::cbegin(reads), std::cend(reads), IsMappingQualityZero {});
}

template <typename T>
std::size_t count_mapq_zero(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    return std::count_if(std::cbegin(overlapped), std::cend(overlapped), IsMappingQualityZero {});
}

struct MappingQualityLess
{
    bool operator()(const AlignedRead& lhs, const AlignedRead& rhs) const noexcept
    {
        return lhs.mapping_quality() < rhs.mapping_quality();
    }
};

template <typename T>
auto min_mapping_quality(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    auto result_itr = std::min_element(std::cbegin(reads), std::cend(reads), MappingQualityLess {});
    return result_itr->mapping_quality();
}

template <typename T>
auto min_mapping_quality(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    auto result_itr = std::min_element(std::cbegin(overlapped), std::cend(overlapped), MappingQualityLess {});
    return result_itr->mapping_quality();
}

template <typename T>
auto max_mapping_quality(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    auto result_itr = std::max_element(std::cbegin(reads), std::cend(reads), MappingQualityLess {});
    return result_itr->mapping_quality();
}

template <typename T>
auto max_mapping_quality(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    auto result_itr = std::max_element(std::cbegin(overlapped), std::cend(overlapped), MappingQualityLess {});
    return result_itr->mapping_quality();
}

template <typename T>
double median_mapping_quality(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    std::vector<double> qualities(reads.size());
    std::transform(std::cbegin(reads), std::cend(reads), std::begin(qualities),
                   [] (const auto& read) { return static_cast<double>(read.mapping_quality()); });
    return maths::median<double>(qualities);
}

template <typename T>
double median_mapping_quality(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    std::vector<double> qualities(size(overlapped));
    std::transform(std::cbegin(overlapped), std::cend(overlapped), std::begin(qualities),
                   [] (const auto& read) { return static_cast<double>(read.mapping_quality()); });
    return maths::median<double>(qualities);
}

template <typename T>
double rmq_mapping_quality(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    std::vector<double> qualities(reads.size());
    std::transform(std::cbegin(reads), std::cend(reads), std::begin(qualities),
                   [] (const auto& read) { return static_cast<double>(read.mapping_quality()); });
    return maths::rmq<double>(qualities);
}

template <typename T>
double rmq_mapping_quality(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    std::vector<double> qualities(size(overlapped));
    std::transform(std::cbegin(overlapped), std::cend(overlapped), std::begin(qualities),
                   [] (const auto& read) { return static_cast<double>(read.mapping_quality()); });
    return maths::rmq<double>(qualities);
}

template <typename T>
double median_base_quality(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    std::vector<double> qualities {};
    qualities.reserve(count_base_pairs(reads, NonMapTag {}));
    for (const auto& read : reads) {
        for (const auto quality : read.base_qualities()) {
            qualities.push_back(static_cast<double>(quality));
        }
    }
    return maths::median<double>(qualities);
}

template <typename T>
double median_base_quality(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    std::vector<double> qualities {};
    qualities.reserve(count_base_pairs(reads, region, NonMapTag {}));
    std::for_each(std::cbegin(overlapped), std::cend(overlapped), [&qualities] (const auto& read) {
        for (const auto quality : read.base_qualities()) {
            qualities.push_back(static_cast<double>(quality));
        }
    });
    return maths::median<double>(qualities);
}

template <typename T>
double rmq_base_quality(const T& reads, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    std::vector<double> qualities {};
    qualities.reserve(count_base_pairs(reads, NonMapTag {}));
    for (const auto& read : reads) {
        for (const auto quality : read.base_qualities()) {
            qualities.push_back(static_cast<double>(quality));
        }
    }
    return maths::rmq<double>(qualities);
}

template <typename T>
double rmq_base_quality(const T& reads, const GenomicRegion& region, NonMapTag)
{
    static_assert(is_aligned_read_container<T>, "T must be a container of AlignedReads");
    const auto overlapped = overlap_range(reads, region);
    std::vector<double> qualities {};
    qualities.reserve(count_base_pairs(reads, region, NonMapTag {}));
    std::for_each(std::cbegin(overlapped), std::cend(overlapped), [&qualities] (const auto& read) {
        for (const auto quality : read.base_qualities()) {
            qualities.push_back(static_cast<double>(quality));
        }
    });
    return maths::rmq<double>(qualities);
}

template <typename ReadMap>
bool has_coverage(const ReadMap& reads, MapTag)
{
    return std::any_of(std::cbegin(reads), std::cend(reads),
                       [] (const auto& sample_reads) {
                           return has_coverage(sample_reads.second, NonMapTag {});
                       });
}

template <typename ReadMap>
bool has_coverage(const ReadMap& reads, const GenomicRegion& region, MapTag)
{
    return std::any_of(std::cbegin(reads), std::cend(reads),
                       [&region] (const auto& sample_reads) {
                           return has_coverage(sample_reads.second, region, NonMapTag {});
                       });
}

template <typename ReadMap>
unsigned min_coverage(const ReadMap& reads, MapTag)
{
    if (reads.empty()) return 0;
    std::vector<unsigned> sample_min_coverages(reads.size(), 0);
    std::transform(std::cbegin(reads), std::cend(reads), std::begin(sample_min_coverages),
                   [] (const auto& sample_reads) {
                       return min_coverage(sample_reads.second, NonMapTag {});
                   });
    return *std::min_element(cbegin(sample_min_coverages), std::cend(sample_min_coverages));
}

template <typename ReadMap>
unsigned min_coverage(const ReadMap& reads, const GenomicRegion& region, MapTag)
{
    if (reads.empty()) return 0;
    std::vector<unsigned> sample_min_coverages(reads.size(), 0);
    std::transform(std::cbegin(reads), std::cend(reads), std::begin(sample_min_coverages),
                   [&region] (const auto& sample_reads) {
                       return min_coverage(sample_reads.second, region, NonMapTag {});
                   });
    return *std::min_element(cbegin(sample_min_coverages), std::cend(sample_min_coverages));
}

template <typename ReadMap>
unsigned max_coverage(const ReadMap& reads, MapTag)
{
    if (reads.empty()) return 0;
    std::vector<unsigned> sample_max_coverages(reads.size(), 0);
    std::transform(std::cbegin(reads), std::cend(reads), std::begin(sample_max_coverages),
                   [] (const auto& sample_reads) {
                       return max_coverage(sample_reads.second, NonMapTag {});
                   });
    return *std::max_element(cbegin(sample_max_coverages), std::cend(sample_max_coverages));
}

template <typename ReadMap>
unsigned max_coverage(const ReadMap& reads, const GenomicRegion& region, MapTag)
{
    if (reads.empty()) return 0;
    std::vector<unsigned> sample_max_coverages(reads.size(), 0);
    std::transform(std::cbegin(reads), std::cend(reads), std::begin(sample_max_coverages),
                   [&region] (const auto& sample_reads) {
                       return max_coverage(sample_reads.second, region, NonMapTag {});
                   });
    return *std::max_element(std::cbegin(sample_max_coverages), std::cend(sample_max_coverages));
}

template <typename T>
auto sum_positional_coverages(const T& reads, const GenomicRegion& region)
{
    const auto num_bases = size(region);
    std::vector<std::size_t> result(num_bases);
    for (const auto& p : reads) {
        const auto sample_coverages = calculate_positional_coverage(p.second, region);
        assert(sample_coverages.size() == num_bases);
        for (std::size_t i {0}; i < num_bases; ++i) {
            result[i] += sample_coverages[i];
        }
    }
    return result;
}

template <typename T>
double mean_coverage(const T& reads, const GenomicRegion& region, MapTag)
{
    if (reads.empty() || is_empty_region(region)) return 0.0;
    if (reads.size() == 1) return mean_coverage(std::cbegin(reads)->second, region, NonMapTag {});
    return maths::mean(sum_positional_coverages(reads, region));
}

template <typename T>
double mean_coverage(const T& reads, MapTag)
{
    if (reads.empty() || all_empty(reads)) return 0.0;
    return mean_coverage(reads, encompassing_region(reads), MapTag {});
}

template <typename T>
double stdev_coverage(const T& reads, const GenomicRegion& region, MapTag)
{
    if (reads.empty() || is_empty_region(region)) return 0.0;
    if (reads.size() == 1) return stdev_coverage(std::cbegin(reads)->second, region, NonMapTag {});
    return maths::stdev(sum_positional_coverages(reads, region));
}

template <typename T>
double stdev_coverage(const T& reads, MapTag)
{
    if (reads.empty() || all_empty(reads)) return 0.0;
    return stdev_coverage(reads, encompassing_region(reads), MapTag {});
}

template <typename T>
std::size_t count_reads(const T& reads, MapTag)
{
    return maths::sum_sizes(reads);
}

template <typename T>
std::size_t count_reads(const T& reads, const GenomicRegion& region, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [&region] (const auto curr, const auto& sample_reads) {
                               return curr + count_reads(sample_reads.second, region, NonMapTag {});
                           });
}

template <typename T>
std::size_t count_forward(const T& reads, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [] (const auto curr, const auto& sample_reads) {
                               return curr + count_forward(sample_reads.second, NonMapTag {});
                           });
}

template <typename T>
std::size_t count_forward(const T& reads, const GenomicRegion& region, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [&region] (const auto curr, const auto& sample_reads) {
                               return curr + count_forward(sample_reads.second, region, NonMapTag {});
                           });
}

template <typename T>
std::size_t count_reverse(const T& reads, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [] (const auto curr, const auto& sample_reads) {
                               return curr + count_reverse(sample_reads.second, NonMapTag {});
                           });
}

template <typename T>
std::size_t count_reverse(const T& reads, const GenomicRegion& region, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [&region] (const auto curr, const auto& sample_reads) {
                               return curr + count_reverse(sample_reads.second, region, NonMapTag {});
                           });
}

template <typename T>
std::pair<std::size_t, std::size_t> count_directions(const T& reads, MapTag)
{
    std::pair<std::size_t, std::size_t> result {};
    for (const auto& p : reads) {
        const auto sample_counts = count_directions(p.second, NonMapTag {});
        result.first += sample_counts.first; result.second += sample_counts.second;
    }
    return result;
}

template <typename T>
std::pair<std::size_t, std::size_t> count_directions(const T& reads, const GenomicRegion& region, MapTag)
{
    std::pair<std::size_t, std::size_t> result {};
    for (const auto& p : reads) {
        const auto sample_counts = count_directions(p.second, region, NonMapTag {});
        result.first += sample_counts.first; result.second += sample_counts.second;
    }
    return result;
}

template <typename T>
std::size_t count_supplementary(const T& reads, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [] (const auto curr, const auto& sample_reads) {
                               return curr + count_supplementary(sample_reads.second, NonMapTag {});
                           });
}

template <typename T>
std::size_t count_supplementary(const T& reads, const GenomicRegion& region, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [&region] (const auto curr, const auto& sample_reads) {
                               return curr + count_supplementary(sample_reads.second, region, NonMapTag {});
                           });
}

template <typename T>
std::size_t count_secondary(const T& reads, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [] (const auto curr, const auto& sample_reads) {
                               return curr + count_secondary(sample_reads.second, NonMapTag {});
                           });
}

template <typename T>
std::size_t count_secondary(const T& reads, const GenomicRegion& region, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [&region] (const auto curr, const auto& sample_reads) {
                               return curr + count_secondary(sample_reads.second, region, NonMapTag {});
                           });
}

template <typename T>
AlignedRead::NucleotideSequence::size_type min_read_length(const T& reads, MapTag)
{
    boost::optional<AlignedRead::NucleotideSequence::size_type> min_length {};
    for (const auto& p : reads) {
        if (!p.second.empty()) {
            auto l = min_read_length(p.second, NonMapTag {});
            if (min_length) {
                min_length = std::min(*min_length, l);
            } else {
                min_length = l;
            }
        }
    }
    if (min_length) {
        return *min_length;
    } else {
        return 0;
    }
}

template <typename T>
AlignedRead::NucleotideSequence::size_type min_read_length(const T& reads, const GenomicRegion& region, MapTag)
{
    boost::optional<AlignedRead::NucleotideSequence::size_type> min_length {};
    for (const auto& p : reads) {
        if (!p.second.empty()) {
            auto l = min_read_length(p.second, region, NonMapTag {});
            if (min_length) {
                min_length = std::min(*min_length, l);
            } else {
                min_length = l;
            }
        }
    }
    if (min_length) {
        return *min_length;
    } else {
        return 0;
    }
}

template <typename T>
AlignedRead::NucleotideSequence::size_type max_read_length(const T& reads, MapTag)
{
    boost::optional<AlignedRead::NucleotideSequence::size_type> max_length {};
    for (const auto& p : reads) {
        if (!p.second.empty()) {
            auto l = max_read_length(p.second, NonMapTag {});
            if (max_length) {
                max_length = std::min(*max_length, l);
            } else {
                max_length = l;
            }
        }
    }
    if (max_length) {
        return *max_length;
    } else {
        return 0;
    }
}

template <typename T>
AlignedRead::NucleotideSequence::size_type max_read_length(const T& reads, const GenomicRegion& region, MapTag)
{
    boost::optional<AlignedRead::NucleotideSequence::size_type> max_length {};
    for (const auto& p : reads) {
        if (!p.second.empty()) {
            auto l = max_read_length(p.second, region, NonMapTag {});
            if (max_length) {
                max_length = std::min(*max_length, l);
            } else {
                max_length = l;
            }
        }
    }
    if (max_length) {
        return *max_length;
    } else {
        return 0;
    }
}

template <typename T>
AlignedRead::NucleotideSequence::size_type mean_read_length(const T& reads, MapTag)
{
    if (reads.empty()) return 0;
    if (reads.size() == 1) return mean_read_length(std::cbegin(reads)->second, NonMapTag {});
    double total_length {}; std::size_t num_reads {};
    for (const auto& p : reads) {
        for (const auto& read : p.second) {
            total_length += sequence_size(read);
            ++num_reads;
        }
    }
    return total_length / num_reads;
}

template <typename T>
AlignedRead::NucleotideSequence::size_type mean_read_length(const T& reads, const GenomicRegion& region, MapTag)
{
    if (reads.empty()) return 0;
    if (reads.size() == 1) return mean_read_length(std::cbegin(reads)->second, region, NonMapTag {});
    double total_length {}; std::size_t num_reads {};
    for (const auto& p : reads) {
        const auto overlapped = overlap_range(p.second, region);
        for (const auto& read : overlapped) {
            total_length += sequence_size(read);
            ++num_reads;
        }
    }
    return total_length / num_reads;
}

template <typename T>
AlignedRead::NucleotideSequence::size_type median_read_length(const T& reads, MapTag)
{
    if (reads.empty()) return 0;
    if (reads.size() == 1) return median_read_length(std::cbegin(reads)->second, NonMapTag {});
    std::vector<AlignedRead::NucleotideSequence::size_type> lengths(count_reads(reads));
    auto length_itr = std::begin(lengths);
    for (const auto& p : reads) {
        length_itr = std::transform(std::cbegin(p.second), std::cend(p.second), length_itr,
                                    [] (const auto& read) { return sequence_size(read); });
    }
    return maths::median(lengths);
}

template <typename T>
AlignedRead::NucleotideSequence::size_type median_read_length(const T& reads, const GenomicRegion& region, MapTag)
{
    if (reads.empty()) return 0;
    if (reads.size() == 1) return median_read_length(std::cbegin(reads)->second, region, NonMapTag {});
    std::vector<AlignedRead::NucleotideSequence::size_type> lengths(count_reads(reads, region));
    auto length_itr = std::begin(lengths);
    for (const auto& p : reads) {
        const auto overlapped = overlap_range(p.second, region);
        length_itr = std::transform(std::cbegin(overlapped), std::cend(overlapped), length_itr,
                                    [] (const auto& read) { return sequence_size(read); });
    }
    return maths::median(lengths);
}

template <typename T>
std::size_t count_base_pairs(const T& reads, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [] (const auto curr, const auto& sample_reads) {
                               return curr + count_base_pairs(sample_reads.second, NonMapTag {});
                           });
}

template <typename T>
std::size_t count_base_pairs(const T& reads, const GenomicRegion& region, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [&region] (const auto curr, const auto& sample_reads) {
                               return curr + count_base_pairs(sample_reads.second, region, NonMapTag {});
                           });
}

template <typename T>
std::size_t count_forward_base_pairs(const T& reads, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [] (const auto curr, const auto& sample_reads) {
                               return curr + count_forward_base_pairs(sample_reads.second, NonMapTag {});
                           });
}

template <typename T>
std::size_t count_forward_base_pairs(const T& reads, const GenomicRegion& region, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [&region] (const auto curr, const auto& sample_reads) {
                               return curr + count_forward_base_pairs(sample_reads.second, region, NonMapTag {});
                           });
}

template <typename T>
std::size_t count_reverse_base_pairs(const T& reads, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [] (const auto curr, const auto& sample_reads) {
                               return curr + count_reverse_base_pairs(sample_reads.second, NonMapTag {});
                           });
}

template <typename T>
std::size_t count_reverse_base_pairs(const T& reads, const GenomicRegion& region, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [&region] (const auto curr, const auto& sample_reads) {
                               return curr + count_reverse_base_pairs(sample_reads.second, region, NonMapTag {});
                           });
}

template <typename T>
std::size_t count_mapq_zero(const T& reads, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [] (const auto curr, const auto& sample_reads) {
                               return curr + count_mapq_zero(sample_reads.second, NonMapTag {});
                           });
}

template <typename T>
std::size_t count_mapq_zero(const T& reads, const GenomicRegion& region, MapTag)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), std::size_t {0},
                           [&region] (const auto curr, const auto& sample_reads) {
                               return curr + count_mapq_zero(sample_reads.second, region, NonMapTag {});
                           });
}

template <typename T>
auto min_mapping_quality(const T& reads, MapTag)
{
    auto result = min_mapping_quality(std::cbegin(reads)->second, NonMapTag {});
    std::for_each(std::next(std::cbegin(reads)), std::cend(reads), [&] (const auto& p) {
        result = std::min(result, min_mapping_quality(p.second, NonMapTag {}));
    });
    return result;
}

template <typename T>
auto min_mapping_quality(const T& reads, const GenomicRegion& region, MapTag)
{
    auto result = min_mapping_quality(std::cbegin(reads)->second, region, NonMapTag {});
    std::for_each(std::next(std::cbegin(reads)), std::cend(reads), [&] (const auto& p) {
        result = std::min(result, min_mapping_quality(p.second, region, NonMapTag {}));
    });
    return result;
}

template <typename T>
auto max_mapping_quality(const T& reads, MapTag)
{
    auto result = max_mapping_quality(std::cbegin(reads)->second, NonMapTag {});
    std::for_each(std::next(std::cbegin(reads)), std::cend(reads), [&] (const auto& p) {
        result = std::max(result, max_mapping_quality(p.second, NonMapTag {}));
    });
    return result;
}

template <typename T>
auto max_mapping_quality(const T& reads, const GenomicRegion& region, MapTag)
{
    auto result = max_mapping_quality(std::cbegin(reads)->second, region, NonMapTag {});
    std::for_each(std::next(std::cbegin(reads)), std::cend(reads), [&] (const auto& p) {
        result = std::max(result, max_mapping_quality(p.second, region, NonMapTag {}));
    });
    return result;
}

template <typename T>
double median_mapping_quality(const T& reads, MapTag)
{
    std::vector<double> qualities {};
    qualities.reserve(maths::sum_sizes(reads));
    for (const auto& sample_reads : reads) {
        std::transform(std::cbegin(sample_reads.second), std::cend(sample_reads.second),
                       std::back_inserter(qualities), [] (const auto& read) {
            return static_cast<double>(read.mapping_quality());
        });
    }
    return maths::median<double>(qualities);
}

template <typename T>
double median_mapping_quality(const T& reads, const GenomicRegion& region, MapTag)
{
    std::vector<double> qualities {};
    qualities.reserve(maths::sum_sizes(reads));
    for (const auto& sample_reads : reads) {
        const auto overlapped = overlap_range(sample_reads.second, region);
        std::transform(std::cbegin(overlapped), std::cend(overlapped),
                       std::back_inserter(qualities), [] (const auto& read) {
            return static_cast<double>(read.mapping_quality());
        });
    }
    return maths::median<double>(qualities);
}

template <typename T>
double rmq_mapping_quality(const T& reads, MapTag)
{
    std::vector<double> qualities {};
    qualities.reserve(maths::sum_sizes(reads));
    for (const auto& sample_reads : reads) {
        std::transform(std::cbegin(sample_reads.second), std::cend(sample_reads.second),
                       std::back_inserter(qualities), [] (const auto& read) {
                           return static_cast<double>(read.mapping_quality());
                       });
    }
    return maths::rmq<double>(qualities);
}

template <typename T>
double rmq_mapping_quality(const T& reads, const GenomicRegion& region, MapTag)
{
    std::vector<double> qualities {};
    qualities.reserve(maths::sum_sizes(reads));
    for (const auto& sample_reads : reads) {
        const auto overlapped = overlap_range(sample_reads.second, region);
        std::transform(std::cbegin(overlapped), std::cend(overlapped),
                       std::back_inserter(qualities), [] (const auto& read) {
                           return static_cast<double>(read.mapping_quality());
                       });
    }
    return maths::rmq<double>(qualities);
}

template <typename T>
double median_base_quality(const T& reads, MapTag)
{
    std::vector<double> qualities {};
    qualities.reserve(count_base_pairs(reads, MapTag {}));
    for (const auto& sample_reads : reads) {
        for (const auto& read : sample_reads.second) {
            for (const auto quality : read.base_qualities()) {
                qualities.push_back(static_cast<double>(quality));
            }
        }
    }
    return maths::median<double>(qualities);
}

template <typename T>
double median_base_quality(const T& reads, const GenomicRegion& region, MapTag)
{
    std::vector<double> qualities {};
    qualities.reserve(count_base_pairs(reads, region, MapTag {}));
    for (const auto& sample_reads : reads) {
        const auto overlapped = overlap_range(sample_reads.second, region);
        std::for_each(std::cbegin(overlapped), std::cend(overlapped),
                      [&qualities] (const auto& read) {
                          for (const auto quality : read.base_qualities()) {
                              qualities.push_back(static_cast<double>(quality));
                          }
                      });
    }
    return maths::median<double>(qualities);
}

template <typename T>
double rmq_base_quality(const T& reads, MapTag)
{
    std::vector<double> qualities {};
    qualities.reserve(count_base_pairs(reads, MapTag {}));
    for (const auto& sample_reads : reads) {
        for (const auto& read : sample_reads.second) {
            for (const auto quality : read.base_qualities()) {
                qualities.push_back(static_cast<double>(quality));
            }
        }
    }
    return maths::rmq<double>(qualities);
}

template <typename T>
double rmq_base_quality(const T& reads, const GenomicRegion& region, MapTag)
{
    std::vector<double> qualities {};
    qualities.reserve(count_base_pairs(reads, region, MapTag {}));
    for (const auto& sample_reads : reads) {
        const auto overlapped = overlap_range(sample_reads.second, region);
        std::for_each(std::cbegin(overlapped), std::cend(overlapped),
                      [&qualities] (const auto& read) {
                          for (const auto quality : read.base_qualities()) {
                              qualities.push_back(static_cast<double>(quality));
                          }
                      });
    }
    return maths::rmq<double>(qualities);
}

} // namespace detail

template <typename T, typename = enable_if_map<T>>
bool has_coverage(const T& reads)
{
    return detail::has_coverage(reads, MapTag {});
}

template <typename T, typename = enable_if_map<T>>
bool has_coverage(const T& reads, const GenomicRegion& region)
{
    return detail::has_coverage(reads, region, MapTag {});
}

template <typename T, typename = enable_if_map<T>>
unsigned min_coverage(const T& reads)
{
    return detail::min_coverage(reads, MapTag {});
}

template <typename T, typename = enable_if_map<T>>
unsigned min_coverage(const T& reads, const GenomicRegion& region)
{
    return detail::min_coverage(reads, region, MapTag {});
}

template <typename T, typename = enable_if_map<T>>
unsigned max_coverage(const T& reads)
{
    return detail::max_coverage(reads, MapTag {});
}

template <typename T, typename = enable_if_map<T>>
unsigned max_coverage(const T& reads, const GenomicRegion& region)
{
    return detail::max_coverage(reads, region, MapTag {});
}

template <typename T>
double mean_coverage(const T& reads)
{
    return detail::mean_coverage(reads, MapTagType<T> {});
}

template <typename T>
double mean_coverage(const T& reads, const GenomicRegion& region)
{
    return detail::mean_coverage(reads, region, MapTagType<T> {});
}

template <typename T>
double stdev_coverage(const T& reads)
{
    return detail::stdev_coverage(reads, MapTagType<T> {});
}

template <typename T>
double stdev_coverage(const T& reads, const GenomicRegion& region)
{
    return detail::stdev_coverage(reads, region, MapTagType<T> {});
}

template <typename T>
std::size_t count_reads(const T& reads)
{
    return detail::count_reads(reads, MapTagType<T> {});
}

template <typename T>
std::size_t count_reads(const T& reads, const GenomicRegion& region)
{
    return detail::count_reads(reads, region, MapTagType<T> {});
}

template <typename T>
std::size_t count_forward(const T& reads)
{
    return detail::count_forward(reads, MapTagType<T> {});
}

template <typename T>
std::size_t count_forward(const T& reads, const GenomicRegion& region)
{
    return detail::count_forward(reads, region, MapTagType<T> {});
}

template <typename T>
std::size_t count_reverse(const T& reads)
{
    return detail::count_reverse(reads, MapTagType<T> {});
}

template <typename T>
std::size_t count_reverse(const T& reads, const GenomicRegion& region)
{
    return detail::count_reverse(reads, region, MapTagType<T> {});
}

template <typename T>
std::pair<std::size_t, std::size_t> count_directions(const T& reads)
{
    return detail::count_directions(reads, MapTagType<T> {});
}

template <typename T>
std::pair<std::size_t, std::size_t> count_directions(const T& reads, const GenomicRegion& region)
{
    return detail::count_directions(reads, region, MapTagType<T> {});
}

template <typename T>
std::size_t count_supplementary(const T& reads)
{
    return detail::count_supplementary(reads, MapTagType<T> {});
}

template <typename T>
std::size_t count_supplementary(const T& reads, const GenomicRegion& region)
{
    return detail::count_supplementary(reads, region, MapTagType<T> {});
}

template <typename T>
std::size_t count_secondary(const T& reads)
{
    return detail::count_secondary(reads, MapTagType<T> {});
}

template <typename T>
std::size_t count_secondary(const T& reads, const GenomicRegion& region)
{
    return detail::count_secondary(reads, region, MapTagType<T> {});
}

template <typename T>
AlignedRead::NucleotideSequence::size_type min_read_length(const T& reads)
{
    return detail::min_read_length(reads, MapTagType<T> {});
}

template <typename T>
AlignedRead::NucleotideSequence::size_type min_read_length(const T& reads, const GenomicRegion& region)
{
    return detail::min_read_length(reads, region, MapTagType<T> {});
}

template <typename T>
AlignedRead::NucleotideSequence::size_type max_read_length(const T& reads)
{
    return detail::max_read_length(reads, MapTagType<T> {});
}

template <typename T>
AlignedRead::NucleotideSequence::size_type max_read_length(const T& reads, const GenomicRegion& region)
{
    return detail::max_read_length(reads, region, MapTagType<T> {});
}

template <typename T>
AlignedRead::NucleotideSequence::size_type mean_read_length(const T& reads)
{
    return detail::mean_read_length(reads, MapTagType<T> {});
}

template <typename T>
AlignedRead::NucleotideSequence::size_type mean_read_length(const T& reads, const GenomicRegion& region)
{
    return detail::mean_read_length(reads, region, MapTagType<T> {});
}

template <typename T>
AlignedRead::NucleotideSequence::size_type median_read_length(const T& reads)
{
    return detail::median_read_length(reads, MapTagType<T> {});
}

template <typename T>
AlignedRead::NucleotideSequence::size_type median_read_length(const T& reads, const GenomicRegion& region)
{
    return detail::median_read_length(reads, region, MapTagType<T> {});
}

template <typename T>
double strand_bias(const T& reads)
{
    const auto num_forward_reads = static_cast<double>(count_forward(reads));
    const auto num_reverse_reads = static_cast<double>(count_reverse(reads));
    const auto total             = num_forward_reads + num_reverse_reads;
    return (total > 0) ? (num_forward_reads / total) : 0.0;
}

template <typename T>
double strand_bias(const T& reads, const GenomicRegion& region)
{
    const auto num_forward_reads = static_cast<double>(count_forward(reads, region));
    const auto num_reverse_reads = static_cast<double>(count_reverse(reads, region));
    const auto total             = num_forward_reads + num_reverse_reads;
    return (total > 0) ? (num_forward_reads / total) : 0.0;
}

template <typename T>
std::size_t count_base_pairs(const T& reads)
{
    return detail::count_base_pairs(reads, MapTagType<T> {});
}

template <typename T>
std::size_t count_base_pairs(const T& reads, const GenomicRegion& region)
{
    return detail::count_base_pairs(reads, region, MapTagType<T> {});
}

template <typename T>
std::size_t count_forward_base_pairs(const T& reads)
{
    return detail::count_forward_base_pairs(reads, MapTagType<T> {});
}

template <typename T>
std::size_t count_forward_base_pairs(const T& reads, const GenomicRegion& region)
{
    return detail::count_forward_base_pairs(reads, region, MapTagType<T> {});
}

template <typename T>
std::size_t count_reverse_base_pairs(const T& reads)
{
    return detail::count_reverse_base_pairs(reads, MapTagType<T> {});
}

template <typename T>
std::size_t count_reverse_base_pairs(const T& reads, const GenomicRegion& region)
{
    return detail::count_reverse_base_pairs(reads, region, MapTagType<T> {});
}

template <typename T>
std::size_t count_mapq_zero(const T& reads)
{
    return detail::count_mapq_zero(reads, MapTagType<T> {});
}

template <typename T>
std::size_t count_mapq_zero(const T& reads, const GenomicRegion& region)
{
    return detail::count_mapq_zero(reads, region, MapTagType<T> {});
}

template <typename T>
auto min_mapping_quality(const T& reads)
{
    return detail::min_mapping_quality(reads, MapTagType<T> {});
}

template <typename T>
auto min_mapping_quality(const T& reads, const GenomicRegion& region)
{
    return detail::min_mapping_quality(reads, region, MapTagType<T> {});
}

template <typename T>
auto max_mapping_quality(const T& reads)
{
    return detail::max_mapping_quality(reads, MapTagType<T> {});
}

template <typename T>
auto max_mapping_quality(const T& reads, const GenomicRegion& region)
{
    return detail::max_mapping_quality(reads, region, MapTagType<T> {});
}

template <typename T>
double median_mapping_quality(const T& reads)
{
    return detail::median_mapping_quality(reads, MapTagType<T> {});
}

template <typename T>
double median_mapping_quality(const T& reads, const GenomicRegion& region)
{
    return detail::median_mapping_quality(reads, region, MapTagType<T> {});
}

template <typename T>
double rmq_mapping_quality(const T& reads)
{
    return detail::rmq_mapping_quality(reads, MapTagType<T> {});
}

template <typename T>
double rmq_mapping_quality(const T& reads, const GenomicRegion& region)
{
    return detail::rmq_mapping_quality(reads, region, MapTagType<T> {});
}

template <typename T>
double median_base_quality(const T& reads)
{
    return detail::median_base_quality(reads, MapTagType<T> {});
}

template <typename T>
double median_base_quality(const T& reads, const GenomicRegion& region)
{
    return detail::median_base_quality(reads, region, MapTagType<T> {});
}

template <typename T>
double rmq_base_quality(const T& reads)
{
    return detail::rmq_base_quality(reads, MapTagType<T> {});
}

template <typename T>
double rmq_base_quality(const T& reads, const GenomicRegion& region)
{
    return detail::rmq_base_quality(reads, region, MapTagType<T> {});
}

template <typename ReadMap>
std::size_t count_samples_with_coverage(const ReadMap& reads)
{
    return std::count_if(std::cbegin(reads), std::cend(reads),
                         [] (const auto& sample_reads) {
                             return has_coverage(sample_reads.second);
                         });
}

template <typename ReadMap>
std::size_t count_samples_with_coverage(const ReadMap& reads, const GenomicRegion& region)
{
    return std::count_if(std::cbegin(reads), std::cend(reads),
                         [&region] (const auto& sample_reads) {
                             return has_coverage(sample_reads.second, region);
                         });
}

template <typename ReadMap>
unsigned sum_min_coverages(const ReadMap& reads)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), 0,
                           [] (auto curr, const auto& sample_reads) {
                               return curr + min_coverage(sample_reads.second);
                           });
}

template <typename ReadMap>
unsigned sum_min_coverages(const ReadMap& reads, const GenomicRegion& region)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), 0,
                           [&region] (auto curr, const auto& sample_reads) {
                               return curr + min_coverage(sample_reads.second, region);
                           });
}

template <typename ReadMap>
unsigned sum_max_coverages(const ReadMap& reads)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), 0,
                           [] (auto curr, const auto& sample_reads) {
                               return curr + max_coverage(sample_reads.second);
                           });
}

template <typename ReadMap>
unsigned sum_max_coverages(const ReadMap& reads, const GenomicRegion& region)
{
    return std::accumulate(std::cbegin(reads), std::cend(reads), 0,
                           [&region] (auto curr, const auto& sample_reads) {
                               return curr + max_coverage(sample_reads.second, region);
                           });
}

template <typename ReadMap>
std::size_t max_sample_read_count(const ReadMap& reads)
{
    if (reads.empty()) return 0;
    return std::max_element(std::cbegin(reads), std::cend(reads),
                            [] (const auto& lhs, const auto& rhs) {
                                return lhs.second.size() < rhs.second.size();
                            })->second.size();
}

} // namespace octopus

#endif


#ifdef WITH_AD3

//#define GraphicalModelDecomposition LOC_Inference_GraphicalModelDecomposition
#include <boost/python.hpp>
#include <string>
#include "inf_def_visitor.hxx"

#include <opengm/inference/loc.hxx>
# include <param/loc_param.hxx>

using namespace boost::python;

template<class GM,class ACC>
void export_loc(){
   using namespace boost::python;
   import_array();
   append_subnamespace("solver");

   // setup
   InfSetup setup;
   setup.cite       = "K. Jung, P. Kohli and D. Shah:\"`Local Rules for Global MAP: When Do They Work? "
                      "<http://research.microsoft.com/en-us/um/people/pkohli/papers/jks_nips09_TR.pdf>`_\","
                      "NIPS 2009.\n\n";
   setup.algType    = "movemaking";
   setup.guarantees = "epsilon approximation for planar graphical models";
   setup.examples   = ">>> parameter = opengm.InfParam(phi=0.3,maxRadius=20)\n"
                      ">>> inference = opengm.inference.Loc(gm=gm,accumulator='minimizer',parameter=parameter)\n\n"
                      "\n\n";
   setup.dependencies = "needs AD3 / WITH_AD3";

   // export parameter
   typedef opengm::LOC<GM, ACC>  PyLOC;
   exportInfParam<PyLOC>("_Loc");
   // export inference
   class_< PyLOC>("_Loc",init<const GM & >())
   .def(InfSuite<PyLOC>(std::string("Loc"),setup))
   ;
}

template void export_loc<opengm::python::GmAdder, opengm::Minimizer>();
template void export_loc<opengm::python::GmAdder, opengm::Maximizer>();
//template void export_loc<GmMultiplier, opengm::Minimizer>();
//template void export_loc<GmMultiplier, opengm::Maximizer>();

#endif


//==============================================================================
//         Copyright 2003 & onward LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 & onward LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_CONSTANT_REGISTER_HPP_INCLUDED
#define BOOST_SIMD_CONSTANT_REGISTER_HPP_INCLUDED

/*!
 * \file
 * \brief Defines the BOOST_SIMD_CONSTANT_REGISTER_VALUE macro
 */

#include <boost/simd/meta/int_c.hpp>
#include <boost/simd/meta/float.hpp>
#include <boost/simd/meta/double.hpp>

//==============================================================================
/*!
 * Generate a named constant from an integral, float and double value set
 * and a default type
 * \param TAG    ID of the constant to generate
 * \param TYPE   Default type of the constant
 * \param INT    Integral value of the constant
 * \param FLOAT  Float value of the constant
 * \param DOUBLE Double value of the constant
 */
//==============================================================================
#if defined(DOXYGEN_ONLY)
#define BOOST_SIMD_CONSTANT_REGISTER(TAG,TYPE,INT,FLOAT,DOUBLE) \
struct TAG  : boost::simd::ext::pure_constant_<TAG>             \
{                                                               \
  typedef boost::simd::ext::pure_constant_<TAG> parent;         \
  typedef TYPE default_type;                                    \
};                                                              \
/**/
#else
#define BOOST_SIMD_CONSTANT_REGISTER(TAG,TYPE,INT,FLOAT,DOUBLE)             \
struct TAG : boost::simd::ext::pure_constant_<TAG>                          \
{                                                                           \
  typedef boost::simd::ext::pure_constant_<TAG> parent;                     \
  typedef TYPE default_type;                                                \
  template<class T, class D=void> struct apply                              \
    : boost::simd::meta::int_c<typename T::type,typename T::type(INT)> {};  \
                                                                            \
  template<class... Args>                                                   \
  static BOOST_FORCEINLINE BOOST_AUTO_DECLTYPE dispatch(Args&&... args)     \
  BOOST_AUTO_DECLTYPE_BODY( BOOST_PP_CAT(dispatching_, TAG)( ext::adl_helper(), static_cast<Args&&>(args)... ) ) \
};                                                                          \
template<class T, class D>                                                  \
struct  TAG::apply<boost::dispatch::meta::single_<T>,D>                     \
      : boost::simd::meta::single_<FLOAT> {};                               \
template<class T, class D>                                                  \
struct  TAG::apply<boost::dispatch::meta::double_<T>,D>                     \
      : boost::simd::meta::double_<DOUBLE> {};                              \
/**/
#endif

#endif


//==============================================================================
//         Copyright 2003 - 2012 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_SWAR_FUNCTIONS_DETAILS_RANDOM_PERMUTE_HPP_INCLUDED
#define BOOST_SIMD_SWAR_FUNCTIONS_DETAILS_RANDOM_PERMUTE_HPP_INCLUDED

#include <boost/mpl/at.hpp>

namespace boost{ namespace simd{ namespace details
{
  template<class IndexMap>
  struct random_permute
  {
    template<class Index, class Cardinal>
    struct apply : mpl::at<IndexMap,Index>
    {};
  };
} } }

#endif


#pragma once

//the generated shapes are properly centered and rotated

#include <TopoDS_Shape.hxx>

inline const TopoDS_Shape &GenericShapeToOCCT( const TopoDS_Shape &s ){
	return s;
}

#include <Meta/entity.hpp>
#include <boost/utility/enable_if.hpp>

template<typename E>
typename boost::enable_if<GenericSim::Meta::IsEntity<E>,const TopoDS_Shape>::type
GenericShapeToOCCT( const E &e ){
	return ShapeEntityToOCCTByConcept( e, typename GenericSim::Meta::GetEntityConcept<E>::Type() );
}

#include <BRepPrimAPI_MakeBox.hxx>
template< typename E >
const TopoDS_Shape ShapeEntityToOCCTByConcept( const E &e, EC::Box ){
	gp_XYZ size = boost::fusion::invoke( boost::value_factory<gp_XYZ>(), e.at_e<EC::Box::Size>() );
	return BRepPrimAPI_MakeBox(gp_Pnt(size*-0.5),gp_Pnt(size*0.5));
}

#include <BRepPrimAPI_MakeCylinder.hxx>
#include <gp_Ax2.hxx>
#include <gp_XYZ.hxx>
template< typename E >
const TopoDS_Shape ShapeEntityToOCCTByConcept( const E &e, EC::Cylinder ){
	return BRepPrimAPI_MakeCylinder(
		gp_Ax2(gp_Pnt(0,0,-e.at_e<EC::Cylinder::Length>()*0.5),gp_XYZ(0,0,1)),
		e.at_e<EC::Cylinder::Radius>(),
		e.at_e<EC::Cylinder::Length>()
	);
}

#include <BRepPrimAPI_MakeSphere.hxx>
template< typename E >
const TopoDS_Shape ShapeEntityToOCCTByConcept( const E &e, EC::Sphere ){
	return BRepPrimAPI_MakeSphere( e.at_e<EC::Sphere::Radius>() );
}