"""Detect structural variation in genomes using high-throughput sequencing data.
"""
import collections
import copy

from bcbio.structural import lumpy

_CALLERS = {"lumpy": lumpy.run}

def _get_svcallers(data):
    svs = data["config"]["algorithm"].get("svcaller")
    if svs is None:
        svs = []
    elif isinstance(svs, basestring):
        svs = [svs]
    return svs

def _handle_multiple_svcallers(data):
    """Retrieve configured structural variation caller, handling multiple.
    """
    svs = _get_svcallers(data)
    out = []
    for svcaller in svs:
        base = copy.deepcopy(data)
        base["config"]["algorithm"]["svcaller_active"] = svcaller
        out.append(base)
    return out

def _combine_multiple_svcallers(samples):
    """
    """
    by_bam = collections.defaultdict(list)
    for x in samples:
        by_bam[x[0]["work_bam"]].append(x[0])
    out = []
    for grouped_calls in by_bam.itervalues():
        def orig_svcaller_order(x):
            return _get_svcallers(x).index(x["config"]["algorithm"]["svcaller_active"])
        sorted_svcalls = sorted([x for x in grouped_calls if "sv" in x],
                                key=orig_svcaller_order)
        final_calls = [x["sv"] for x in sorted_svcalls]
        final = grouped_calls[0]
        final["sv"] = final_calls
        del final["config"]["algorithm"]["svcaller_active"]
        out.append([final])
    return out

def run(samples, run_parallel):
    """Run structural variation detection using configured methods.
    """
    to_process = []
    extras = []
    for data in (xs[0] for xs in samples):
        ready_data = _handle_multiple_svcallers(data)
        if len(ready_data) > 0:
            for x in ready_data:
                to_process.append([x])
        else:
            extras.append([data])
    processed = run_parallel("detect_sv", to_process)
    return extras + _combine_multiple_svcallers(processed)

def detect_sv(data):
    """Top level parallel target for examining structural variation.
    """
    svcaller = data["config"]["algorithm"].get("svcaller_active")
    if svcaller:
        data["sv"] = _CALLERS[svcaller](data)
    return [[data]]

@@ -177,16 +177,51 @@
 mpy.run%7D
+%0A_BATCH_CALLERS = %7B%22cn.mops%22: None%7D
 %0A%0Adef _g
@@ -1567,26 +1567,53 @@
 o_process = 
-%5B%5D
+collections.defaultdict(list)
 %0A    extras 
@@ -1779,16 +1779,211 @@
 y_data:%0A
+                svcaller = x%5B%22config%22%5D%5B%22algorithm%22%5D.get(%22svcaller_active%22)%0A                batch = x.get(%22metadata%22, %7B%7D).get(%22batch%22)%0A                if svcaller in _BATCH_CALLERS and batch:%0A    
         
@@ -2000,24 +2000,31 @@
 _process
+%5Bbatch%5D
 .append(
 %5Bx%5D)%0A   
@@ -2019,12 +2019,84 @@
 end(
+x)%0A                else:%0A                    to_process%5Bx%5B%22name%22%5D%5B-1%5D%5D = 
 %5Bx%5D
-)
 %0A   
@@ -2182,26 +2182,73 @@
 ct_sv%22, 
-to_process
+(%5Bxs, xs%5B0%5D%5B%22config%22%5D%5D for xs in to_process.itervalues())
 )%0A    re
@@ -2315,20 +2315,29 @@
 tect_sv(
-data
+items, config
 ):%0A    %22
@@ -2423,30 +2423,22 @@
 aller = 
-data%5B%22
 config
-%22%5D
 %5B%22algori
@@ -2462,24 +2462,37 @@
 er_active%22)%0A
+    out = %5B%5D%0A
     if svcal
@@ -2508,62 +2508,569 @@
     
-data%5B%22sv%22%5D = _CALLERS%5Bsvcaller%5D(data)%0A    return %5B%5Bdata%5D%5D
+if svcaller in _CALLERS:%0A            assert len(items) == 1%0A            data = items%5B0%5D%0A            data%5B%22sv%22%5D = _CALLERS%5Bsvcaller%5D(data)%0A            out.append(%5Bdata%5D)%0A        elif svcaller in _BATCH_CALLERS:%0A            sv = _BATCH_CALLERS%5Bsvcaller%5D(items)%0A            svitems = %5B%5D%0A            for data in items:%0A                data%5B%22sv%22%5D = sv%0A                svitems.append(data)%0A            out.append(svitems)%0A        else:%0A            raise ValueError(%22Unexpected structural variant caller: %25s%22 %25 svcaller)%0A    else:%0A        out.append(items)%0A    return out
 %0A

Handle batch calling of structural variants, explicitly sorting batches for sv callers that support them