#!/usr/bin/env python

"""
Typical Usage: 

coords2dihedrals_projected.py 180 < 12column_text_file.dat

This script reads a 12-column numeric text file representing the positions
of 4 atoms.  Then it calculates the "projected dihedral", which I define
as the difference in direction between two infinitely long lines:
-The first line passes through the first two atoms,
-The other line (the "last line") passes through the last two atoms.
The "projected dihedral" angle can be thought of as the largest possible
apparent angle between these two lines when viewed from all possible
viewing directions.  A pair of points (one on each line) is determined
that are closest to each other.  The "projected dihedral" angle is the angle
one sees when viewing these two lines from direction of the axis connecting
these two closest points. Note: If these two lines are parallel to each other
or if these 4 points are coplanar, the "projected" dihedral angle is undefined.

Example input file:

30.13 11.46 15.12 35.28 -2.32 12.61 30.68 -4.45 16.30 28.68 8.46 10.24
28.68 8.42 10.24 30.68 -4.45 16.30 25.22 -4.75 19.42 27.57 3.37 8.06
27.57 3.37 8.06 25.22 -4.75 19.42 20.45 -1.27 20.09 25.80 -2.26 6.61
25.80 -2.23 6.61 20.45 -1.27 20.09 15.47 1.36 18.12 21.67 -5.88 7.17
:
The 12 numbers on each line represent the x,y,z coordinates of 4 atoms.
(This is the format of the files generated by the "pdb2coords.py" script.)
For each line containing 12 numbers, this script returns a tuple
containing 4 numbers:
-The "projected dihedral" angle for these 4 atoms (in degrees)
-The distance from the first atom to the first closest point
 (IE The point on the first line that is closest to the last line)
-The distance between the pair of points on each line that are closest
 to each other,
-The distance from the last atom to the last closest point
 (IE The point on the last line that is closest to the first line)
(When a line contains the wrong number of numbers, 
 the script prints out a list of 4 impossibe negative values:
 "-720 -1 -1 -1" to let the caller know that 
 this particular angle could not be computed.)

Note:   
The "IUPAC/IUB" dihedral-angle convention is used:
4 atoms in the "trans" conformation have a dihedral angle of 180 degrees.
By default, dihedral angles are calculated in the range from 0 to 360.0 degrees.
(This means there is discontinuity in the angle at 0 degrees.)
However this may be a bad choice for polymers which can alternate between
helical conformations which are left and right handed.
In some cases, you may want to have the discontinuity appear at 180 degrees.
(or some other angle which is sparsely populated).

So you can supply an optional argument (the "branch_of_log") which indicates 
where the discontinuity in the dihedral angle will appear.
Dihedral angles returned by this program will lie in the range: 
[branch_of_log-360, branch_of_log)

"""

import sys
from math import sqrt, cos, sin, tan, acos, asin, atan, pi, floor
try:
    from .closest_line_points import ClosestLinePoints
    from .coords2dihedrals import Coords2DihedralsAnglesLengths,Coords2Dihedrals
except (ImportError, SystemError, ValueError):
    # not installed as a package
    from closest_line_points import ClosestLinePoints
    from coords2dihedrals import Coords2DihedralsAnglesLengths,Coords2Dihedrals


import signal
signal.signal(signal.SIGPIPE, signal.SIG_DFL)



def Coords2ProjectedDihedralsLengths(r0, r1, r2, r3, branch_of_log=pi):
    """
    Calculate the "projected" dihedral angle from the position of 4 atoms.  I
    define this as the difference in direction between two infinitely long lines
    -The first line passes through the first two atoms (r0,r1).
    -The other line (the "last line") passes through the last two atoms (r2,r3).
    The "projected dihedral" angle can be thought of as the largest possible
    apparent angle between these two lines when viewed from all possible
    viewing directions.  A pair of points (one on each line) is determined
    that are closest to each other.  The "projected dihedral" angle is the angle
    one sees when viewing these two lines from direction of the axis connecting
    these two closest points.  If these 4 points are coplanar, the 
    "projected" dihedral angle between them is undefined.
    """

    r10 = [r1[0]-r0[0],
           r1[1]-r0[1],
           r1[2]-r0[2]]
    r32 = [r3[0]-r2[0],
           r3[1]-r2[1],
           r3[2]-r2[2]]

    R1, R2 = ClosestLinePoints(r0, r2, r10, r32)

    phi,theta0,theta1,l10,l21,l32 = Coords2DihedralsAnglesLengths(r0,
                                                                  R1,
                                                                  R2,
                                                                  r3,
                                                                  branch_of_log)
    # (Note: theta0 and theta1 should both be approximately pi/2,
    #        so we don't bother to report these angles to the caller.)
    return (phi, l10, l21, l32)



def Coords2ProjectedDihedrals(r0, r1, r2, r3, branch_of_log=pi):
    phi,l01,l21,l32 = Coords2ProjectedDihedralsLengths(r0, r1, r2, r3)
    return phi



def main():

    branch_of_log = pi  # by default, dihedral angles lie in range: [-180,180.0)
    truncate_a    = 0
    truncate_b    = 0

    if (len(sys.argv) > 4):
        sys.stderr.write('Error (coords2dihedrals): number of arguments should not exceed 3.\n'\
                         '    If an odd-number of arguments are passed (1 or 3), then\n'
                         '    the first argument is assumed to be the branch-of-log, a number which is\n'
                         '    0 and 360.0.  This argument indicates where the discontinuity in the\n'
                         '    dihedral angle is located.  By default it is 0 degrees, which\n'
                         '    corresponds to 4 atoms in the "cis" conformation.)\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) == 4):
        branch_of_log = float(sys.argv[1])
        truncate_a    =   int(sys.argv[2])
        truncate_b    =   int(sys.argv[3])
    elif (len(sys.argv) == 3):
        truncate_a = int(sys.argv[1])
        truncate_b = int(sys.argv[2])
    elif (len(sys.argv) == 2):
        branch_of_log = float(sys.argv[1])
        branch_of_log *= pi/180.0


    coords_list = []
    # Read the file
    for line in sys.stdin:
        line = line.strip()
        # Each line should contain a list of 3 numbers separated by whitespace.
        # If so, store the 3 numbers in a list variable (named xyz), and append
        # it to the list of coordinates.
        # However some lines might also be blank, in which case we append the
        # empty list [] to the list of coordinates.
        if line == '':
            coords = []
        else:
            # Each line should contain a list of 3 numbers separated by whitespace.
            coords = list(map(float, line.split()))
            if len(coords) != 4*3:
                sys.stderr.write('Error(coords2dihedrals):\n'+'Each line should either contain 12 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*4:
            r0 = [coords_list[i][3*0+0],
                  coords_list[i][3*0+1],
                  coords_list[i][3*0+2]]
            r1 = [coords_list[i][3*1+0],
                  coords_list[i][3*1+1],
                  coords_list[i][3*1+2]]
            r2 = [coords_list[i][3*2+0],
                  coords_list[i][3*2+1],
                  coords_list[i][3*2+2]]
            r3 = [coords_list[i][3*3+0],
                  coords_list[i][3*3+1],
                  coords_list[i][3*3+2]]

            phi,l10,l21,l32 = Coords2ProjectedDihedralsLengths(r0, r1, r2, r3,
                                                               branch_of_log)

            sys.stdout.write(str(phi*180.0/pi) + ' ' +
                             str(l10) + ' ' +
                             str(l21) + ' ' +
                             str(l32) +
                             '\n')
        else:
            # Otherwise, we write out an impossible values to let the caller 
            # know that this particular dihedral angle could not be computed
            sys.stdout.write('-720 -1 -1 -1\n')


if __name__ == "__main__":
    main()
