# Student(s): 

############################################################################
# Implementation of the Needleman-Wunsch Global Pairwise Alignment algorithm
# (to be modified to be the Smith-Waterman Local Pairwise Alignment algorithm)
############################################################################

def buildTable(X, Y, score, gap):
    """This is responsible for building and returning the table of values."""

    # create table of zeros with dimension (1+|X|)-by-(1+|Y|)
    opt = [ [0] * (1+len(Y)) for _ in range(1+len(X))]
    
    for k in range(1+len(Y)):
        opt[0][k] = k*gap       # initialize top row
    for j in range(1+len(X)):
        opt[j][0] = j*gap       # initialize left column
    
    for j in range(1,1+len(X)):
        for k in range(1, 1+len(Y)):
            option1 = opt[j-1][k-1] + score(X[j-1],Y[k-1])  # align last chars
            option2 = opt[j-1][k] + gap                     # last of X with gap
            option3 = opt[j][k-1] + gap                     # last of Y with gap
            opt[j][k] = max(option1, option2, option3)
    return opt

def optScore(X, Y, table):
    """This is responsible for returning the maximum score."""
    return table[len(X)][len(Y)]   # bottom-right corner

def reconstructSolution(X, Y, table, score, gap):
    """This is responsible for returning strings that describe the optimal alignment."""
    first = ''        # alignment for X
    second = ''       # alignment for Y
    glue = ''         # line showing matches/mismatches

    # start reconstruction at bottom-right of table
    j = len(X)
    k = len(Y)
    
    while j>0 or k>0:

        if j>0 and k>0 and table[j][k] == table[j-1][k-1] + score(X[j-1],Y[k-1]):
            # option1 above; match X[j-1] with Y[k-1]
            first =  X[j-1] + first
            second = Y[k-1] + second
            if X[j-1] == Y[k-1]:
                glue = '|' + glue   # designate match
            else:
                glue = '.' + glue   # designate mismatch
            j = j-1
            k = k-1

        elif j > 0 and table[j][k] == table[j-1][k] + gap:
            # option2 above; match X[j-1] with a gap in Y
            first  = X[j-1] + first
            second = '-' + second
            glue   = ' ' + glue
            j = j-1

        else: 
            # option3 above; match gap in X with Y[k-1]
            first  = '-'  + first
            second = Y[k-1] + second
            glue   = ' '  + glue
            k = k-1

    return first,glue,second


############################################################################
# Do NOT change (or even read?) anything below this line
############################################################################

def displayTable(table):
    for row in table:
        print(' '.join('%3d' % val for val in row))

def createScoreFunc(match,mismatch):
    def s(u,v):
        if u == v:
            return match
        else:
            return mismatch
    s.__name__ = "match=%d, mismatch=%d" % (match,mismatch)

    return s   # return the newly defined function

def performTest(X,Y,score,gap):
    print('='*30)
    print('Computing alignment with gap=%d score=%s' % (gap,score.__name__))
    table = buildTable(X,Y,score,gap)
    opt = optScore(X,Y,table)
    print('Optimal score=%d' % opt)
    first,glue,second = reconstructSolution(X, Y, table, score, gap)
    print('')
    print('Optimal Alignment:')
    print(first)
    print(glue)
    print(second)
    if max(len(X),len(Y)) <= 100:
        print('')
        print('Intermediate table of values:')
        displayTable(table)
    print('')
    

if __name__ == '__main__':
    import sys

    try:
        tests = open('tests.txt')
        lines = tests.readlines()
        for k in range(len(lines)):
            line = lines[k]
            pieces = line.split()
            if pieces:
                if len(pieces) != 5:
                    print("Error at line %d; should be five arguments" % (1+k))
                else:
                    try:
                        match = int(pieces[0])
                        mismatch = int(pieces[1])
                        gap = int(pieces[2])
                        X = pieces[3]
                        Y = pieces[4]
                        s = createScoreFunc(match,mismatch)
                        performTest(X,Y,s,gap)
                    except ValueError:
                        print("Test line should be of form: [matchVal] [mismatchVal] [gapVal] X Y")
    except IOError:
        print('Unable to open tests.txt file')
