#!/usr/bin/env python
"""The BuildInitSugar class for building sugar coordinates given base and C1' coordinates"""

# Copyright 2010 Kevin Keating
# 
# Licensed under the Educational Community License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
# 
# http://www.osedu.org/licenses/ECL-2.0
# 
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an "AS IS"
# BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
# or implied. See the License for the specific language governing
# permissions and limitations under the License.

from strucCalc import plus, minus, torsion, rotateAtoms, crossProd, magnitude

STARTING_CHI  = -150 #anti
SYN_CHI       = 70
HIGH_ANTI_CHI = -85

class BuildInitSugar:
    """Given base and C1' coordinates, build a ribose sugar in an anti configuration"""
    
    def __init__(self, c3pStruc, c2pStruc):
        """Initialize a BuildInitSugar object.
        
        ARGUMENTS:
            c3pstruc - the filename for a PDB file containing a C3'-endo sugar
            c2pstruc - the filename for a PDB file containing a C2'-endo sugar
        RETURNS:
            an initialized BuildInitSugar object
        """
        
        self.c3pStrucFilename = c3pStruc
        self.c2pStrucFilename = c2pStruc
        self.__c3pAtoms = readSugarPDB(c3pStruc)
        self.__c2pAtoms = readSugarPDB(c2pStruc)
        
        #translate the sugars so that the C1' atom is at the origin (so we don't have to do it every time we align a sugar)
        for curSugar in (self.__c3pAtoms, self.__c2pAtoms):
            c1p = curSugar["C1'"]
            del curSugar["C1'"] #delete the C1' atom, since it's going to be zeroed anyway
                                #(and we don't want to return it since it doesn't need to be added to the pseudoMolecule object)
            for (curAtom, curCoords) in curSugar.iteritems():
                curSugar[curAtom] = minus(curCoords, c1p)
            
            
        
    
    def buildSugar(self, baseAtoms, pucker):
        """Build a sugar of the specified pucker onto a base
        
        ARGUMENTS:
            baseAtoms - a dictionary of base atoms in the form atomName:[x, y, z]
                        Note that this dictionary MUST contain the C1' atom
            pucker    - the pucker of the sugar to be built (passed as an integer, either 2 or 3)
        RETURNS:
            coordinates for a sugar of the specified pucker in anti configuration with the base
        """
        
        #fetch the appropriate sugar structure
        if pucker == 3:
            sugarAtoms = self.__c3pAtoms
        elif pucker == 2:
            sugarAtoms = self.__c2pAtoms
        else:
            raise "BuildInitSugar called with unrecognized pucker: " + str(pucker)
        #I don't have to worry about accidentally modifying the original atom dictionaries, since
        #rotateAtoms effectively makes a deep copy
        
        #figure out which base atoms to use for alignment
        if baseAtoms.has_key("N9"):
            Natom = "N9"
            Catom = "C4"
        else:
            Natom = "N1"
            Catom = "C2"
        
        
        #rotate the sugar so the glycosidic bond is at the appropriate angle
        #first, calculate an axis for this rotation
        translatedBaseN = minus(baseAtoms[Natom], baseAtoms["C1'"])
        sugarN = sugarAtoms[Natom]
        
        axis = crossProd(sugarN, translatedBaseN)
        angle = torsion(translatedBaseN, axis, (0,0,0), sugarN)
        
        #if either angle or magnitude(axis) is 0, then the glycosidic bond is already oriented appropriately
        if not(angle == 0 or magnitude(axis) == 0):
            sugarAtoms = rotateAtoms(sugarAtoms, axis, angle)
        
        
        #next, rotate the sugar so that chi is appropriate
        translatedBaseC = minus(baseAtoms[Catom], baseAtoms["C1'"])
        curChi = torsion(translatedBaseC, translatedBaseN, [0,0,0], sugarAtoms["O4'"])
        sugarAtoms = rotateAtoms(sugarAtoms, translatedBaseN, curChi - STARTING_CHI)
        
        #remove the unnecessary atoms from the sugarAtoms dict
        del sugarAtoms["N1"]
        del sugarAtoms["N9"]
        
        #translate the sugar to the C1' atom of the base
        sugarAtoms = dict([(atom, plus(coords, baseAtoms["C1'"])) for (atom, coords) in sugarAtoms.iteritems()])
        
        return sugarAtoms


def rotateSugar(antiSugarCoords, atoms, newChi = "syn"):
    """Given a sugar in anti configuration, rotate it to a new configuration (such as syn or high-anti).
    
    ARGUMENTS:
        antiSugarCoords - a dictionary containing a sugar in anti configuration in the format atomName: [x, y, z]
                          typically, this dictionary is generated by a BuildInitSugar object
        atoms           - a dictionary containing base coordinates in the format atomName: [x, y, z]
    OPTIONAL ARGUMENTS:
        newChi          - the chi value to rotate the sugar to
                          may be "syn", "high-anti", or a number (in degrees)
                          defaults to "syn"
    RETURNS:
        synSugarCoords  - a dictionary containing a sugar in syn configuration in the format atomName: [x, y, z]
    """
    
    if newChi == "syn":
        newChi = SYN_CHI
    elif newChi == "high-anti":
        newChi = HIGH_ANTI_CHI
    
    #translate the sugar coordinates to the origin
    synSugarCoords = dict([(atom, minus(coords, atoms["C1'"])) for (atom, coords) in antiSugarCoords.iteritems()])
    
    #rotate the sugar
    if atoms.has_key("N9"):
        baseN = "N9"
    else:
        baseN = "N1"
    axis = minus(atoms[baseN], atoms["C1'"])
    
    synSugarCoords = rotateAtoms(synSugarCoords, axis, newChi - STARTING_CHI, atoms["C1'"])
    
    return synSugarCoords


def readSugarPDB(filename):
    """Read in a simple PDB containing the atoms of a sugar ring.
    
    ARGUMENTS:
        filename - the name of the PDB file
    RETURNS:
        a dictionary of atomName: [x, y, z]
    """
    
    input = open(filename, 'r')
    
    #a dictionary of atoms names -> coordinates
    struc = {}
    
    for curline in input:
        if curline[0:6].strip() == "ATOM":
            atomName = curline[12:17].strip()
            x  = float(curline[30:39])
            y  = float(curline[38:47])
            z  = float(curline[46:55])
            
            #if necessary convert the atom names to PDB version 3.0
            atomName.replace('*', "'")
            
            struc[atomName] = (x, y, z)
    
    input.close()
    return struc
