#!/usr/bin/env python

"""
Usage:

coords2angles.py < 9column_text_file.dat

This script reads a 9-column numeric text file.  For example:

30.13 11.46 15.12 35.28 -2.32 12.61 30.68 -4.45 16.30
28.68 8.42 10.24 30.68 -4.45 16.30 25.22 -4.75 19.42
27.57 3.37 8.06 25.22 -4.75 19.42 20.45 -1.27 20.09
25.80 -2.23 6.61 20.45 -1.27 20.09 15.47 1.36 18.12
:
The 9 numbers on each line represent the x,y,z coordinates of 3 atoms.
(This is the format of the files generated by the "pdb2coords.py" script.)
For each line, the program calculates the angle between these atoms
followed by distances between atoms 1,2 and 2,3 (in that order)
to the standard out.  (3 numbers total, followed by a newline.)
(When a line contains the wrong number of numbers, 
 the script prints out a list of 3 impossibe negative values: "-360 -1 -1"
 to let the caller know that this particular angle could not be computed

"""

import sys
from math import sqrt, cos, sin, tan, acos, asin, atan, pi
# Sometimes this program pipes its output to other programs which halt early.
# Below we silently suppress the ugly "Broken pipe" message this generates:
import signal
signal.signal(signal.SIGPIPE, signal.SIG_DFL)


def length_v(r):
    lsqd = 0.0
    for d in range(0,len(r)):
        lsqd += r[d]*r[d]
    return sqrt(lsqd)


def inner_prod_v(r1,r2):
    result = 0.0
    for d in range(0,len(r1)):
        result += r1[d]*r2[d]
    return result


def main():
    if (len(sys.argv) > 3):
        sys.stderr.write('Error (coords2angles): number of arguments should not exceed 2.\n'\
                          '    (The two arguments correspond to the number of lines of\n'\
                          '     text to omit from the beginning and end of the file, respectively.)\n'\
                          '     If one argument is passed, then both are assumed to be the same.\n'\
                          '     If no argument is passed, then by default, no data is ignored.\nExiting...\n\n')
        sys.exit(-1)

    # NOTE: The "truncate" arguments are not really supported any more.  Instead
    #       use other scripts to post-process the results printed by this program.
    elif (len(sys.argv) == 3):
        truncate_a = int(sys.argv[1])
        truncate_b = int(sys.argv[2])
    elif (len(sys.argv) == 2):
        truncate_a = int(sys.argv[1])
        truncate_b = truncate_a
    else:
        truncate_a = 0
        truncate_b = 0


    coords_list = []
    # Read the file
    for line in sys.stdin:
        line = line.strip()
        if line == '':
            coords = []
        else:
            # Each line should contain a list of 9 numbers separated by whitespace.
            coords = list(map(float, line.split()))
            if len(coords) != 3*3:
                sys.stderr.write('Error(coords2angles.py):\n'+'Each line should either contain 9 numbers or be blank.\n')
                sys.exit(-1)
        coords_list.append(coords)


    # Truncate the data we don't want.
    # (Why?  The residues at the beginning and ending of helices 
    #  are less trustworthy then the residues in the middle.)
    coords_list = coords_list[truncate_a:len(coords_list)-truncate_b]

    N = len(coords_list)
    for i in range(0,N):
        if len(coords_list[i]) == 3*3:
            r10 = [0.0, 0.0, 0.0]
            r21 = [0.0, 0.0, 0.0]
            for d in range(0,3):
                r10[d] = coords_list[i][3*1+d] - coords_list[i][3*0+d]
                r21[d] = coords_list[i][3*2+d] - coords_list[i][3*1+d]
            l10 = length_v(r10)
            l21 = length_v(r21)

            # The bond angle is the angle between r10 and r21
            cos_theta = -inner_prod_v(r10, r21) /(length_v(r10)*length_v(r21))

            if (cos_theta > 1.0):
                cos_theta = 1.0
            elif (cos_theta < -1.0):
                cos_theta = -1.0

            theta = acos(cos_theta)

            sys.stdout.write(str(theta*180.0/pi)+" "+str(l10)+" "+str(l21)+'\n')

        else:
            # Otherwise, we write out impossible values to let the caller 
            # know that this particular angle could not be computed
            sys.stdout.write('-360 -1 -1\n')


if __name__ == "__main__":
    main()
