# 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
            else:  # a.read_strand == '-'
                seg_num *= -1
                seg_start = a.ref_length - a.ref_end
                seg_end = a.ref_length - a.ref_start
            adjusted_seg_start = seg_start - a.read_start
            adjusted_seg_end = seg_end + a.read_end_gap
            if adjusted_seg_start < -min_overlap_amount:
                start_overlap_reads[seg_num].add(read_name)
            if adjusted_seg_end > a.ref_length + min_overlap_amount:
                end_overlap_reads[seg_num].add(read_name)
    return start_overlap_reads, end_overlap_reads


def remove_conflicting_alignments(alignments, allowed_overlap):
    """
    This function takes alignments for one read, removes conflicting and/or redundant alignments
    and returns the clean-up list of alignments.
    """
    alignments = sorted(alignments, reverse=True,
                        key=lambda x: (x.matching_bases, x.minimiser_count, x.ref_name))
    kept_alignments = []
    kept_alignment_ranges = []
    for a in alignments:
        this_range = (a.read_start, a.read_end)

        # Don't keep alignments for which their part of the read is already aligned.
        if range_is_contained(this_range, kept_alignment_ranges):
            continue

        # Don't keep alignments which overlap too much with existing alignments.

# 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

            else:  # a.read_strand == '-'
                seg_num *= -1
                seg_start = a.ref_length - a.ref_end
                seg_end = a.ref_length - a.ref_start
            adjusted_seg_start = seg_start - a.read_start
            adjusted_seg_end = seg_end + a.read_end_gap
            if adjusted_seg_start < -min_overlap_amount:
                start_overlap_reads[seg_num].add(read_name)
            if adjusted_seg_end > a.ref_length + min_overlap_amount:
                end_overlap_reads[seg_num].add(read_name)
    return start_overlap_reads, end_overlap_reads


def remove_conflicting_alignments(alignments, allowed_overlap):
    """
    This function takes alignments for one read, removes conflicting and/or redundant alignments
    and returns the clean-up list of alignments.
    """
    alignments = sorted(alignments, reverse=True,
                        key=lambda x: (x.matching_bases, x.minimiser_count, x.ref_name))
    kept_alignments = []
    kept_alignment_ranges = []
    for a in alignments:
        this_range = (a.read_start, a.read_end)

        # Don't keep alignments for which their part of the read is already aligned.
        if range_is_contained(this_range, kept_alignment_ranges):
            continue

        # Don't keep alignments which overlap too much with existing alignments.

        if range_overlap_size(this_range, kept_alignment_ranges) > allowed_overlap: