from __future__ import print_function
import logging
import multiprocessing
from collections import defaultdict
import pybedtools

from defaults import MIN_INV_SUBALIGN_LENGTH, MIN_DEL_SUBALIGN_LENGTH

logger = logging.getLogger(__name__)

def get_insertion_breakpoints(age_records, intervals, expected_bp_pos, window=20, start=0, dist_to_expected_bp=50):
    func_logger = logging.getLogger("%s-%s" % (get_insertion_breakpoints.__name__, multiprocessing.current_process()))
    bedtools_intervals = [pybedtools.Interval("1", interval[0], interval[1]) for interval in sorted(intervals)]
    func_logger.info("bedtools_intervals %s" % (str(bedtools_intervals)))
    if not bedtools_intervals:
        return []

    potential_breakpoints = sorted(list(set(
        [interval.start for interval in bedtools_intervals] + [interval.end for interval in bedtools_intervals])))

    breakpoints = []
    for breakpoint in potential_breakpoints[1:-1]:
        # Check if the breakpoint is within window distance of a validated breakpoint
        if min([window + 1] + [abs(b[0] - breakpoint) for b in breakpoints]) <= window:
            continue
        func_logger.info("\tExamining potential breakpoint %d for support" % breakpoint)
        left_support = [interval[0] for interval in intervals if abs(interval[0] - breakpoint) <= window]
        right_support = [interval[1] for interval in intervals if abs(interval[1] - breakpoint) <= window]
        counter_examples = [age_record for age_record in age_records if age_record.has_long_ref_flanks() and (
            age_record.has_ref_deletion(window) or age_record.has_insertion(min_diff=1,
                                                                            max_diff=49)) and age_record.breakpoint_match(
            breakpoint, window)]
        if counter_examples:
            counter_example_ends = [age_record.start1_end1s for age_record in counter_examples]
            func_logger.info("\t\tSkipping breakpoint %d due to %s" % (breakpoint, str(counter_example_ends)))
            continue

        if left_support:
            func_logger.info("\t\tLeft support %s" % (str(left_support)))
        if right_support:
            func_logger.info("\t\tRight support %s" % (str(right_support)))

        if (left_support and right_support) and min(
                        [window + 1] + [abs(b[0] - breakpoint) for b in breakpoints]) > window:
            both_support = [age_record for age_record in age_records if
                            age_record.has_insertion(min_diff=50, max_diff=1000000000) and age_record.breakpoint_match(
                                breakpoint, window)]
            if both_support:
                func_logger.info("\t\tboth_support = %s" % (str(both_support)))
                func_logger.info("\t\tinsertion lengths = %s" % (
                str([age_record.insertion_length() for age_record in both_support])))
            insertion_length = max([0] + [age_record.insertion_length() for age_record in both_support])
            func_logger.info("\t\tInsertion length = %d" % insertion_length)
            breakpoints.append((breakpoint, insertion_length))

    func_logger.info("Nonfiltered breakpoints as %s" % (str(breakpoints)))

    if len(breakpoints)>1:
        breakpoints=filter(lambda x: min(abs(x[0]-expected_bp_pos[0]),abs(expected_bp_pos[1]-x[0]))<dist_to_expected_bp,breakpoints)

    func_logger.info("Gathered breakpoints as %s" % (str(breakpoints)))

    return [(start + b[0], b[1]) for b in breakpoints]


def get_deletion_breakpoints(age_records, window=20, min_flank_length=50, start=0):
    func_logger = logging.getLogger("%s-%s" % (get_deletion_breakpoints.__name__, multiprocessing.current_process()))

    potential_breakpoints = sorted(
        [age_record.start1_end1s[0][1] for age_record in age_records] + [age_record.start1_end1s[1][0] for age_record in
                                                                         age_records])
    breakpoints = []
    for breakpoint in potential_breakpoints:
        left_support = [age_record for age_record in age_records if
                        abs(age_record.start1_end1s[0][1] - breakpoint) < window]
        right_support = [age_record for age_record in age_records if
                         abs(age_record.start1_end1s[1][0] - breakpoint) < window]

        if (left_support or right_support) and min([window + 1] + [abs(b - breakpoint) for b in breakpoints]) >= window:
            breakpoints.append(breakpoint)

    func_logger.info("Gathered breakpoints as %s" % (str(breakpoints)))

    return [start + breakpoint for breakpoint in breakpoints]

def check_closeness_to_bp(pos,pad,dist_to_expected_bp,LR_bp,seq_length=0):
    if LR_bp == 'L':
        return abs(pos-pad)<dist_to_expected_bp
    else:
        return abs(pos-(seq_length-pad))<dist_to_expected_bp
    


def get_inversion_breakpoints(age_records, window=20, min_endpoint_dist=10, start=0, pad=500, dist_to_expected_bp=400, min_inv_subalign_len=MIN_INV_SUBALIGN_LENGTH):
    func_logger = logging.getLogger("%s-%s" % (get_deletion_breakpoints.__name__, multiprocessing.current_process()))

    potential_breakpoints = []
    for age_record in age_records:
    
        polarities=[abs(age_record.polarities1[i]-age_record.polarities2[i]) for i in range(age_record.nfrags)]               
        good_intervals=[i for i in range(age_record.nfrags) if abs(age_record.start1_end1s[i][1]-age_record.start1_end1s[i][0]) > min_inv_subalign_len and abs(age_record.start2_end2s[i][1]-age_record.start2_end2s[i][0]) > min_inv_subalign_len]
        good_intervals=[i for i in good_intervals if abs(age_record.start1_end1s[i][1]-age_record.start1_end1s[i][0]) <= max(age_record.inputs[0].length-2*(pad-dist_to_expected_bp),pad+dist_to_expected_bp)]
        func_logger.info('Good intervals: %s'%str(good_intervals))
        if len(good_intervals)<2:
            func_logger.info('Not enough good interval for this age record: %s'%str(age_record))
            continue
        candidate_inv_intervals=[]
        inv_interval=-1
        long_inversion=False
        
        left_end_near_l_bp=filter(lambda x: check_closeness_to_bp(min(age_record.start1_end1s[x]),pad,dist_to_expected_bp,"L"), good_intervals)
        right_end_near_r_bp=filter(lambda x: check_closeness_to_bp(max(age_record.start1_end1s[x]),pad,dist_to_expected_bp,"R",age_record.inputs[0].length), good_intervals)
        
        right_end_near_l_bp=filter(lambda x: check_closeness_to_bp(max(age_record.start1_end1s[x]),pad,dist_to_expected_bp,"L"), good_intervals)
        left_end_near_r_bp=filter(lambda x: check_closeness_to_bp(min(age_record.start1_end1s[x]),pad,dist_to_expected_bp,"R",age_record.inputs[0].length), good_intervals)

        candidate_inv_intervals=list(set(left_end_near_l_bp)&set(right_end_near_r_bp))
        candidate_norm_intervals=list(set(left_end_near_r_bp)|set(right_end_near_l_bp))
        
        if len(candidate_inv_intervals)>1 and len(candidate_norm_intervals)<=1: 
            candidate_inv_intervals=list(set(candidate_inv_intervals)-set(candidate_norm_intervals))
        
        if len(candidate_inv_intervals)>1:
            dist_to_exp_bps=map(lambda x: abs(min(age_record.start1_end1s[x])-pad)+abs(max(age_record.start1_end1s[x])-(age_record.inputs[0].length-pad)),candidate_inv_intervals)
            inv_interval=min(enumerate(dist_to_exp_bps),key=lambda x:x[1])[0]
        elif len(candidate_inv_intervals)==1 :
            inv_interval=candidate_inv_intervals[0]
        
        
        if inv_interval==-1:
            #Potentially long inversion
            candidate_inv_intervals=[i for i in left_end_near_l_bp if ((set(candidate_norm_intervals)&set(left_end_near_r_bp))-set([i]))] + \
                                    [i for i in right_end_near_r_bp if ((set(candidate_norm_intervals)&set(right_end_near_l_bp))-set([i]))]
            
            if len(candidate_inv_intervals)>1:
                candidate_inv_intervals=[i for i in set(candidate_inv_intervals)&set(left_end_near_l_bp) if (pad< (sum(age_record.start1_end1s[i])/2.0))] + \
                                        [i for i in set(candidate_inv_intervals)&set(right_end_near_r_bp) if ((age_record.inputs[0].length-pad) > (sum(age_record.start1_end1s[i])/2.0))]