"""
Implementation of Nussinov's algorithm for finding maximum number of base-pair interactions.

The user can either specify an explicit strings, or a value n such that
a randomly generate string of length n is used, which by default will be length 15.
Examples:

   python secondary.py ACUAGGAAUGGAAGCC
   python secondary.py 15
   python secondary.py

a minimum separation gap can be specified optionally after an explicit first argument:

   python secondary.py ACUAGGAAUGGAAGCC 3
   python secondary.py 15 3

the default   
"""
import sys
from random import choice


def w(a,b):
    if {a,b} in ( {'A','U'}, {'C','G'} ):
        return 1   # complementary bases
    else:
        return 0

def buildTable(rna, gap):
    opt = {}
    for g in range(-1,len(rna)):
        # solve for all i,j with j = i + g
        for i in range(len(rna)-g):
            j = i + g
            if g <= gap:
                best = 0    # cannot make any pairs
            else:
                option1 = opt[i+1,j-1] + w(rna[i], rna[j])
                option2 = opt[i+1,j]
                option3 = opt[i,j-1]
                best = max(option1,option2,option3)
                for k in range(i+1,j):
                    if opt[i,k] + opt[k+1,j] > best:
                        best = opt[i,k] + opt[k+1,j]
            opt[i,j] = best
    return opt


def pairs(rna, gap, opt, i, j):
    """Return list of pairs for an optimal pairing."""
    if j - i <= gap:
        return []      # no pairs possible
    elif opt[i,j] == opt[i+1,j]:
        return pairs(rna, gap, opt, i+1, j)
    elif opt[i,j] == opt[i,j-1]:
        return pairs(rna, gap, opt, i, j-1)
    elif opt[i,j] == opt[i+1,j-1] + w(rna[i],rna[j]):
        return [ (i,j) ] + pairs(rna, gap, opt, i+1, j-1)
    else:
        for k in range(i+1,j):
            if opt[i,j] == opt[i,k] + opt[k+1,j]:
                return pairs(rna,gap,opt,i,k) + pairs(rna,gap,opt,k+1,j)


def parseArgs():
    # set defaults
    rna = genRandom(15)
    gap = 0

    if len(sys.argv) >= 2:
        if sys.argv[1].isdigit():
            n = int(sys.argv[1])
            rna = genRandom(n)
        else:
            rna = sys.argv[1]

        if len(sys.argv) >= 3:
            gap = max(0,int(sys.argv[2]))

    return rna,gap

def genRandom(n):
    return ''.join(choice('ACGU') for _ in range(n))


if __name__ == '__main__':
    rna,gap = parseArgs()
    print(rna)

    opt = buildTable(rna,gap)

    print('       ' + '  '.join(rna))
    for i in range(len(rna)):
        print('%2d '%i + rna[i],end=' ')
        for j in range(len(rna)):
            if j < i:
                msg = ''
            else:
                msg = opt[i,j]
            print('%3s' % msg,end='')
        print()
    

    print()
    print(opt[0,len(rna)-1])
    print(pairs(rna,gap,opt,0,len(rna)-1))
