import re
import os
import tempfile
import subprocess
from django.conf import settings
from Bio.Seq import Seq


PRIMER3_MIN_PRIMER = 18
PRIMER3_MAX_PRIMER = 25

PRIMER3_INT_DEFAULTS = {
    'PRIMER_OPT_SIZE': 20,
    'PRIMER_PICK_INTERNAL_OLIGO': 1,
    'PRIMER_INTERNAL_MAX_SELF_END': 8,
    'PRIMER_MIN_SIZE': PRIMER3_MIN_PRIMER,
    'PRIMER_MAX_SIZE': PRIMER3_MAX_PRIMER,
    'PRIMER_MAX_NS_ACCEPTED': 0,
    'PRIMER_MAX_SELF_ANY': 12,
    'PRIMER_MAX_SELF_END': 8,
    'PRIMER_PAIR_MAX_COMPL_ANY': 12,
    'PRIMER_PAIR_MAX_COMPL_END': 8,
    'PRIMER_MAX_POLY_X': 100,
    'PRIMER_INTERNAL_MAX_POLY_X': 100,
}

PRIMER3_FLOAT_DEFAULTS = {
    'PRIMER_OPT_TM': 60.0,
    'PRIMER_MIN_TM': 57.0,
    'PRIMER_MAX_TM': 63.0,
    'PRIMER_MIN_GC': 20.0,
    'PRIMER_MAX_GC': 80.0,
    'PRIMER_SALT_MONOVALENT': 50.0,
    'PRIMER_DNA_CONC': 5000.0,  # nM
}


def parse_primer3_output(d):
    primers = {}

    for k in d:
        m = re.search('\d+', k)
        if m:
            i = int(m.group(0))
            if i not in primers:
                primers[i] = {}
            k2 = re.sub('_%s_' % i, '_', k)
            primers[i][k2] = d[k]

    return [primers[k] for k in sorted(primers.keys())]


def primer3_run(opts):
    fn = None
    with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
        fn = f.name
        for k in opts:
            f.write('%s=%s\n' % (k, opts[k]))
        f.write('PRIMER_THERMODYNAMIC_PARAMETERS_PATH=%s/primer3_config/\n' % settings.PRIMER3_DIR)
        f.write('=')

    cmd = "%s/primer3_core %s" % (settings.PRIMER3_DIR, fn)
    out = subprocess.check_output(cmd.split(' '))
    os.unlink(fn)
    r = {}
    for l in out.split('\n'):
        if '=' in l:
            l = l.split('=')
            r[l[0]] = '='.join(l[1:])

    return parse_primer3_output(r)


def design_primers_from_template(template, roi_start, roi_len, junctions, primer3_opts):
    primer3_opts = {} if primer3_opts is None else primer3_opts
    min_primer_product_size = min(PRIMER3_MIN_PRIMER*2+roi_len, len(template))
    max_primer_product_size = len(template)

    opts = {}
    opts.update(PRIMER3_INT_DEFAULTS)
    opts.update(PRIMER3_FLOAT_DEFAULTS)
    for k, v in primer3_opts.iteritems():
        if k in PRIMER3_INT_DEFAULTS:
            opts[k] = int(v)
        elif k in PRIMER3_FLOAT_DEFAULTS:
            opts[k] = float(v)

    opts.update(dict(PRIMER_PRODUCT_SIZE_RANGE='%s-%s' % (min_primer_product_size,
                                                          max_primer_product_size),
                     SEQUENCE_ID='_',
                     SEQUENCE_TEMPLATE=template,
                     SEQUENCE_TARGET='%s,%s' % (roi_start, roi_len)))

    primers = primer3_run(opts)
    if junctions and len(junctions) > 0:
        for primer in primers:
            p1 = primer['PRIMER_LEFT_SEQUENCE']
            p2 = primer['PRIMER_RIGHT_SEQUENCE']
            p2_rc = str(Seq(p2).reverse_complement())
            p1_i = template.lower().index(p1.lower())
            p2_i = template.lower().index(p2_rc.lower())
            primer['PRIMER_LEFT_SEQUENCE_DISTANCE_TO_JUNCTION'] = junctions[0]-(p1_i+len(p1))
            primer['PRIMER_RIGHT_SEQUENCE_DISTANCE_TO_JUNCTION'] = p2_i-junctions[-1]

    return primers


def design_primers(fragment, roi_start_bp, roi_len, upstream_window, downstream_window, opts):
    """
    Design primer using primer3 pipeline. Returns list of designed primers.
    """

    opts = {} if opts is None else opts
    fragment = fragment.indexed_fragment()

    roi = fragment.get_sequence(bp_lo=roi_start_bp, bp_hi=roi_start_bp+roi_len-1)
    bp_lo = roi_start_bp-upstream_window
    bp_hi = roi_start_bp+roi_len-1+downstream_window
    template = fragment.get_sequence(bp_lo=bp_lo, bp_hi=bp_hi)

    roi_start = template.index(roi)+1
    return design_primers_from_template(template, roi_start, len(roi),
                                        [upstream_window, upstream_window+len(roi)-1], opts)
