# Path: unicycler/misc.py
# def get_nice_header(header):
#     """
#     For a header with a SPAdes/Velvet format, this function returns a simplified string that is
#     just NODE_XX where XX is the contig number.
#     For any other format, this function trims off everything following the first whitespace.
#     """
#     if is_header_spades_format(header):
#         return 'NODE_' + header.split('_')[1]
#     else:
#         return header.split()[0]
# 
# def dim(text):
#     return DIM + text + END_FORMATTING
# 
# def line_iterator(string_with_line_breaks):
#     """Iterates over a string containing line breaks, one line at a time."""
#     prev_newline = -1
#     while True:
#         next_newline = string_with_line_breaks.find('\n', prev_newline + 1)
#         if next_newline < 0:
#             break
#         yield string_with_line_breaks[prev_newline + 1:next_newline]
#         prev_newline = next_newline
# 
# def range_overlap(x1, x2, y1, y2):
#     """
#     Returns true if the range (x1, x2) overlaps with the range (y1, y2).
#     """
#     return x1 < y2 and y1 < x2
# 
# def range_is_contained(test_range, other_ranges):
#     """
#     Returns True if test_range is entirely contained within any range in other_ranges.
#     """
#     start, end = test_range
#     for other_range in other_ranges:
#         if other_range[0] <= start and other_range[1] >= end:
#             return True
#     return False
# 
# def range_overlap_size(test_range, other_ranges):
#     start, end = test_range
#     max_overlap = 0
#     for other_range in other_ranges:
#         max_overlap = max(max_overlap, min(end, other_range[1]) - max(start, other_range[0]))
#     return max_overlap
# 
# def simplify_ranges(ranges):
#     """
#     Collapses overlapping ranges together. Input ranges are tuples of (start, end) in the normal
#     Python manner where the end isn't included.
#     """
#     fixed_ranges = []
#     for int_range in ranges:
#         if int_range[0] > int_range[1]:
#             fixed_ranges.append((int_range[1], int_range[0]))
#         elif int_range[0] < int_range[1]:
#             fixed_ranges.append(int_range)
#     starts_ends = [(x[0], 1) for x in fixed_ranges]
#     starts_ends += [(x[1], -1) for x in fixed_ranges]
#     starts_ends.sort(key=lambda x: x[0])
#     current_sum = 0
#     cumulative_sum = []
#     for start_end in starts_ends:
#         current_sum += start_end[1]
#         cumulative_sum.append((start_end[0], current_sum))
#     prev_depth = 0
#     start = 0
#     combined = []
#     for pos, depth in cumulative_sum:
#         if prev_depth == 0:
#             start = pos
#         elif depth == 0:
#             combined.append((start, pos))
#         prev_depth = depth
#     return combined


import os
import sys
from collections import defaultdict
from .misc import get_nice_header, dim, line_iterator, range_overlap, range_is_contained, \
    range_overlap_size, simplify_ranges
from . import log
from . import settings
    from .cpp_wrappers import minimap_align_reads
    If filter_by_minimisers is True, it will remove low minimiser count hits.
    If filter_overlaps is True, it will exclude hits which overlap better hits.
    """
    alignments = defaultdict(list)
    for line in line_iterator(minimap_alignments_str):
        try:
            log.log(dim(line), 3)
            alignment = MinimapAlignment(line)
            read_alignments = alignments[alignment.read_name]
            read_alignments.append(alignment)
            read_alignments = sorted(read_alignments, key=lambda x: x.minimiser_count, reverse=True)
            if filter_by_minimisers:
                best_minimiser_count = read_alignments[0].minimiser_count
                min_minimiser_count = best_minimiser_count / minimiser_ratio
                read_alignments = [x for x in read_alignments
                                   if x.minimiser_count >= min_minimiser_count]
            if filter_overlaps:
                kept_alignments = []
                for alignment in read_alignments:
                    if not alignments_overlap(alignment, kept_alignments, allowed_overlap):
                        kept_alignments.append(alignment)
                read_alignments = kept_alignments
            alignments[alignment.read_name] = sorted(read_alignments, key=lambda x: x.read_start)
        except (IndexError, ValueError):
            pass
    return alignments


def alignments_overlap(a, other, allowed_overlap):
    adjusted_start = a.read_start + allowed_overlap

# Path: unicycler/misc.py
# def get_nice_header(header):
#     """
#     For a header with a SPAdes/Velvet format, this function returns a simplified string that is
#     just NODE_XX where XX is the contig number.
#     For any other format, this function trims off everything following the first whitespace.
#     """
#     if is_header_spades_format(header):
#         return 'NODE_' + header.split('_')[1]
#     else:
#         return header.split()[0]
# 
# def dim(text):
#     return DIM + text + END_FORMATTING
# 
# def line_iterator(string_with_line_breaks):
#     """Iterates over a string containing line breaks, one line at a time."""
#     prev_newline = -1
#     while True:
#         next_newline = string_with_line_breaks.find('\n', prev_newline + 1)
#         if next_newline < 0:
#             break
#         yield string_with_line_breaks[prev_newline + 1:next_newline]
#         prev_newline = next_newline
# 
# def range_overlap(x1, x2, y1, y2):
#     """
#     Returns true if the range (x1, x2) overlaps with the range (y1, y2).
#     """
#     return x1 < y2 and y1 < x2
# 
# def range_is_contained(test_range, other_ranges):
#     """
#     Returns True if test_range is entirely contained within any range in other_ranges.
#     """
#     start, end = test_range
#     for other_range in other_ranges:
#         if other_range[0] <= start and other_range[1] >= end:
#             return True
#     return False
# 
# def range_overlap_size(test_range, other_ranges):
#     start, end = test_range
#     max_overlap = 0
#     for other_range in other_ranges:
#         max_overlap = max(max_overlap, min(end, other_range[1]) - max(start, other_range[0]))
#     return max_overlap
# 
# def simplify_ranges(ranges):
#     """
#     Collapses overlapping ranges together. Input ranges are tuples of (start, end) in the normal
#     Python manner where the end isn't included.
#     """
#     fixed_ranges = []
#     for int_range in ranges:
#         if int_range[0] > int_range[1]:
#             fixed_ranges.append((int_range[1], int_range[0]))
#         elif int_range[0] < int_range[1]:
#             fixed_ranges.append(int_range)
#     starts_ends = [(x[0], 1) for x in fixed_ranges]
#     starts_ends += [(x[1], -1) for x in fixed_ranges]
#     starts_ends.sort(key=lambda x: x[0])
#     current_sum = 0
#     cumulative_sum = []
#     for start_end in starts_ends:
#         current_sum += start_end[1]
#         cumulative_sum.append((start_end[0], current_sum))
#     prev_depth = 0
#     start = 0
#     combined = []
#     for pos, depth in cumulative_sum:
#         if prev_depth == 0:
#             start = pos
#         elif depth == 0:
#             combined.append((start, pos))
#         prev_depth = depth
#     return combined


# Path: unicycler/minimap_alignment.py
import os
import sys
from collections import defaultdict
from .misc import get_nice_header, dim, line_iterator, range_overlap, range_is_contained, \
    range_overlap_size, simplify_ranges
from . import log
from . import settings
    from .cpp_wrappers import minimap_align_reads

    If filter_by_minimisers is True, it will remove low minimiser count hits.
    If filter_overlaps is True, it will exclude hits which overlap better hits.
    """
    alignments = defaultdict(list)
    for line in line_iterator(minimap_alignments_str):
        try:
            log.log(dim(line), 3)
            alignment = MinimapAlignment(line)
            read_alignments = alignments[alignment.read_name]
            read_alignments.append(alignment)
            read_alignments = sorted(read_alignments, key=lambda x: x.minimiser_count, reverse=True)
            if filter_by_minimisers:
                best_minimiser_count = read_alignments[0].minimiser_count
                min_minimiser_count = best_minimiser_count / minimiser_ratio
                read_alignments = [x for x in read_alignments
                                   if x.minimiser_count >= min_minimiser_count]
            if filter_overlaps:
                kept_alignments = []
                for alignment in read_alignments:
                    if not alignments_overlap(alignment, kept_alignments, allowed_overlap):
                        kept_alignments.append(alignment)
                read_alignments = kept_alignments
            alignments[alignment.read_name] = sorted(read_alignments, key=lambda x: x.read_start)
        except (IndexError, ValueError):
            pass
    return alignments


def alignments_overlap(a, other, allowed_overlap):
    adjusted_start = a.read_start + allowed_overlap

    return any(range_overlap(adjusted_start, a.read_end, x.read_start, x.read_end) for x in other)