#===============================================================================
#     This file is part of TEMPy.
#     
#     TEMPy is a software designed to help the user in the manipulation 
#     and analyses of macromolecular assemblies using 3D electron microscopy maps. 
#     
#	  Copyright  2015 Birkbeck College University of London. 
#
#				Authors: Maya Topf, Daven Vasishtan, Arun Prasad Pandurangan,
#						Irene Farabella, Agnel-Praveen Joseph, Harpal Sahota
# 
#     This software is made available under GPL V3 license
#     http://www.gnu.org/licenses/gpl-3.0.html
#     
#     
#     Please cite your use of TEMPy in published work:
#     
#     Farabella, I., Vasishtan, D., Joseph, A.P., Pandurangan, A.P., Sahota, H. & Topf, M. (2015). J. Appl. Cryst. 48.
#
#===============================================================================

from numpy import linspace
import os

import TEMPy.Vector as Vector
from TEMPy.StructureParser import PDBParser


class EnsembleGeneration:
    """A class to create ensemble of structure instance"""
    
    def __init__(self):
        pass
    
    def loadEnsemble(self,path_dir,file_name_flag,hetatm=False,water=False,verbose=False,pdb=True):
        """
        Load an ensemble of Structure Instance from the directory path_dir.
        Arguments:
            *path_dir*
                directory name
            *file_name_flag*
                name or suffix of the files.
        """
        structure_list=[]
        list_rotate_models=[filein for filein in os.listdir(path_dir) if file_name_flag in filein and filein[-4:]=='.pdb' ]
        for pdbin in list_rotate_models:
            print pdbin
            if pdb==True:
                file_in=path_dir+'/'+pdbin
                #print file_in
                if verbose==True:
                    print "load file:",pdbin[:-4],file_in
                structure_instance=PDBParser.read_PDB_file(str(pdbin[:-4]),str(file_in),hetatm=hetatm,water=water)
                structure_list.append([pdbin[:-4],structure_instance])
        return structure_list

    def randomise_structs(self,structure_instance, no_of_structs, max_trans, max_rot, v_grain=30, rad=False,flag='mod',write=False):
        """
          
        Generate an ensemble of Structure Instance. 
                     
        Arguments:
            *structure_instance*
                Input Structure Instance
            *no_of_structs*
                int, number of structures to output
            *max_trans*
                Maximum translation permitted
            *max_rot*
                Maximum rotation permitted (in degree if rad=False)
            *v_grain*
                Graning Level for the generation of random vectors (default=30) 
            *write*
                True will write out each structure_instanceure Instance in the ensemble as single PDB.
        Return:
            list of Structure Instance in which each item is [structure_instance_name,structure_instance]
        """
        ensemble_list=[]
        file_name0=flag+'_0'
        ensemble_list.append([file_name0,structure_instance.copy()])
        if write==True:
            structure_instance.write_to_PDB(file_name0)
        count=0
        for x in range(0,no_of_structs-1):
            count+=1
            file_name=flag+'_'+str(count)+".pdb"
            structure_instance.randomise_position(float(max_trans), float(max_rot), v_grain, rad)
            if write==True:
                structure_instance.write_to_PDB(file_name)
            ensemble_list.append([file_name[:-4],structure_instance.copy()])
            structure_instance.reset_position() 
        return ensemble_list
   
    def anglar_sweep(self,structure_instance, axis, translation_vector, no_of_structs, loc_rotation_angle, flag='mod', atom_com_ind=False,write=False,filename=None):
        """
        Generate an ensemble of Structure Instance
        
        NOTE - Chose the number of structures for the ensemble accordingly with the angular increment step (loc_rotation_angle/no_of_structs) and
        translational increment step (translation_vector/no_of_structs) required. 
        Default setting is around the center of mass.
        
        Arguments:
            *structure_instance*
                Input Structure Instructure_instance
            *axis*
                3-tuple, axis for translation
            *translation_vector*
                3-ple, vector for translation
            *no_of_structs*
                int, number of structures to output
            *loc_rotation_angle*
                tuple, rotation angle for local rotation (degrees)
            *flag*
                string, prefix name for outputted pdb files
            *atom_com_ind*
                int, index of atom to rotate around. If False, rotates around centre of mass
            *write*
                True will write out each Structure Instance in the ensemble as single PDB.
        Return:
            list of Structure Instance in which each item is [structure_instance_name,structure_instance]
        """
        
            # Work out distance between adjacent structures
        ensemble_list=[]
        
        file_name0=flag+'_0'
        ensemble_list.append([file_name0,structure_instance])
        
        grain = loc_rotation_angle/no_of_structs
        if int(grain)<1:
            print "Warning: less then 1deg rotation"
        else:
            transl_x=linspace(0, translation_vector[0], num=no_of_structs)
            transl_y=linspace(0, translation_vector[1], num=no_of_structs)
            transl_z=linspace(0, translation_vector[2], num=no_of_structs)
            angle_rot=linspace(0,loc_rotation_angle,num=no_of_structs)
            count=-1    
            for x in angle_rot:
                count+=1
                file_name=flag+'_'+str(count+1)+".pdb"
                if atom_com_ind:
                    loc_point = structure_instance[atom_com_ind].get_pos_vector()
                    structure_instance.rotate_by_axis_angle(axis[0],axis[1],axis[2], x, com=loc_point)
                else:
                    structure_instance.rotate_by_axis_angle(axis[0],axis[1],axis[2],x)
                structure_instance.translate(transl_x[count],transl_y[count],transl_z[count])
                if write==True:
                    #print "model_num: ",(count+1),"angle: ",x,"tranls_increment: ",transl_x[count],transl_y[count],transl_z[count]
                    structure_instance.write_to_PDB(file_name)
                    ensemble_list.append([file_name[:-4],structure_instance])
                else:
                    ensemble_list.append([file_name[:-4],structure_instance])
                    #print "model_num: ",(count+1),"angle: ",x,"tranls_increment: ",transl_x[count],transl_y[count],transl_z[count]
                    structure_instance.reset_position()
        return ensemble_list
 
    def spiral_sweep(self, structure_instance, axis, dist, no_of_structs, loc_ang_range, loc_axis, flag, atom_com_ind=False,write=False):
        """
        Generate an ensemble of Structure Instance
        
        Arguments:
            *structure_instance*
                Input Structure Instance
           *axis*
               3-ple, axis for translation
           *dist*
               int, translation range (Angstroms)
           *no_of_structs*
               int, number of structures to output
           *loc_axis*
               3-ple, axis for local rotation around centre of mass
           *loc_ang_range*
                tuple, rotation range for local rotation (degrees)
           *flag*
               string, prefix name for outputted pdb files
           *atom_com_ind*
               int, index of atom to rotate around. If False, rotates around centre of mass.
            *write*
                True will write out each Structure Instance in the ensemble as single PDB.
         Return:
            list of Structure Instance in which each item is [structure_instance_name,structure_instance]
               
        """
        ensemble_list=[]
        file_name0='mod_0'
        ensemble_list.append([file_name0,structure_instance])
        count=0
        # Work out distance between adjacent structures
        grain = dist/no_of_structs
        # Make axis into vector of length 1
        axis = Vector.Vector(axis[0], axis[1], axis[2]).unit()

        for r in range(no_of_structs):
            file_name=flag+str(r+1)+".pdb"
            # Translate structure along axis
            structure_instance.translate(axis.x*r*grain,axis.y*r*grain,axis.z*r*grain)

            # Rotation around centre of mass
            loc_grain = (loc_ang_range[1]-loc_ang_range[0])/no_of_structs
            if atom_com_ind:
                loc_point = self[atom_com_ind].get_pos_vector()
                structure_instance.rotate_by_axis_angle(loc_axis[0],loc_axis[1],loc_axis[2], r*loc_grain, com=loc_point)
            else:
                structure_instance.rotate_by_axis_angle(loc_axis[0],loc_axis[1],loc_axis[2], r*loc_grain)

                if write==True:

                    structure_instance.write_to_PDB(file_name)
                    ensemble_list.append([file_name[:-4],structure_instance])
                else:
                    ensemble_list.append([file_name[:-4],structure_instance])
                structure_instance.reset_position()
        return ensemble_list


#TO ADD LOAD ENSEMBLE
#===============================================================================
# def load_dir_pdb(path_dir,file_name_flag,hetatm=False,water=False):
#     
#     structure_list=[]
#     list_rotate_models=[file for file in os.listdir(path_dir) if file_name_flag in file]
#     for pdb in list_rotate_models:
#         file_in=path_dir+'/'+pdb
#         print "*****",pdb[:-4],file_in
#         structure_instance=PDBParser.read_PDB_file(str(pdb[:-4]),str(file_in),hetatm=hetatm,water=water)
#         structure_list.append([pdb[:-4],structure_instance])
#     return structure_list
#===============================================================================
                
#NEED TESTING
#
#
#===============================================================================
#     def circular_sweep_with_fixed_spin(self,struct, axis, point, ang_range, no_of_structs, flag, loc_axis=True, loc_ang_range=[45,90], atom_com_ind=False,write=False):
#         """
#         Generate an ensemble of Structure Instance
#         
#         Arguments:
#             *struct*
#                 Input Structure Instance
#             *axis*
#                 3-ple, axis around which the large scale rotation will be done
#             *point*
#                 3-ple, point around which large scale rotation will be done
#             *ang_range*
#                 tuple, rotation range (degrees)
#             *no_of_structs*
#                 int, number of structures to output
#             *flag*
#                 string, prefix name for outputted pdb files
#             *loc_axis*
#                 3-ple, axis for local rotation around centre of mass
#            *loc_ang_range*
#                tuple, rotation range for local rotation (degrees)
#            *atom_com_ind*
#                int, index of atom to rotate around. If False, rotates around centre of mass.
#             *write*
#                True will write out each Structure Instance in the ensemble as single PDB.
#                 
#         """
#         ensemble_list=[]
#         file_name0='mod_0'
#         ensemble_list.append([file_name0,struct])
#         # Work out angle between adjacent structures
#         grain = (ang_range[1]-ang_range[0])/no_of_structs
#         # Make point into Vector object
#         point = Vector(point[0], point[1],point[2])
# 
#         for r in range(no_of_structs):
#             # Rotation around defined point
#             struct.rotate_by_axis_angle(axis[0],axis[1],axis[2], r*grain, com=point)
#             print r
#             if loc_axis and loc_ang_range:
#                 # Rotation around centre of mass or atom, if specified
#                 loc_grain = (loc_ang_range[1]-loc_ang_range[0])/no_of_structs
#                 if atom_com_ind:
#                     loc_point = a[atom_com_ind].get_pos_vector()
#                     struct.rotate_by_axis_angle(loc_axis[0],loc_axis[1],loc_axis[2], r*loc_grain, com=loc_point)
#                 else:
#                     struct.rotate_by_axis_angle(loc_axis[0],loc_axis[1],loc_axis[2], r*loc_grain)
#                 if write==True:
#                     struct.write_to_PDB(file_name)
#                     ensemble_list.append([file_name[:-4],struct])
#                 else:
#                     ensemble_list.append([file_name[:-4],struct])
#                     print "model_num: ",(count+1),"angle: ",x,"tranls_increment: ",transl_x[count],transl_y[count],transl_z[count]
#                     struct.reset_position()
#             else:
#                 print "print joke"
#         return ensemble_list
# 
# 
# 
# 
#     def circular_sweep_with_tangential_spin(self,struct, axis, point, ang_range, no_of_structs, flag, loc_ang_range=False, atom_com_ind=False,write=False):
#  
#         """
#         Generate an ensemble of Structure Instance
#         
#         Arguments:
#             *struct*
#                 Input Structure Instance
#             *axis*
#                 3-ple, axis around which the large scale rotation will be done
#             *point*
#                 3-ple, point around which large scale rotation will be done
#             *ang_range*
#                 tuple, rotation range (degrees)
#             *no_of_structs*
#                 int, number of structures to output
#             *flag*
#                 string, prefix name for outputted pdb files
#             *loc_ang_range*
#                tuple, rotation range for local rotation (degrees)
#             *atom_com_ind*
#                 int, index of atom to rotate around. If False, rotates around centre of mass.
#             *write*
#                 True will write out each Structure Instance in the ensemble as single PDB.
#                 
#         """
#         ensemble_list=[]
#         file_name0='mod_0'
#         ensemble_list.append([file_name0,struct])
#         
#         # Work out angle between adjacent structures
#         grain = (ang_range[1]-ang_range[0])/no_of_structs
#         # Make point and axis into Vector objects
#         point = Vector(point[0], point[1],point[2])
#         axis = Vector(axis[0], axis[1], axis[2])
# 
#         for r in range(no_of_structs):
#             # Rotation around defined point
#             self.rotate_by_axis_angle(axis[0],axis[1],axis[2], r*grain, com=point)
# 
#             if loc_ang_range:
#                 # Rotation around centre of mass or atom, if specified
#                 loc_grain = (loc_ang_range[1]-loc_ang_range[0])/no_of_structs
#                 if atom_com_ind:
#                     loc_point = self[atom_com_ind].get_pos_vector()
#                 else:
#                     loc_point = self.CoM    
#                 # Axis of local spin is cross product of axis and radius of rotation (ie. the tangent of the circular arc being traversed)
#                 rad = (loc_point-point).unit()
#                 loc_axis = rad.cross(axis)
#                 self.rotate_by_axis_angle(loc_axis[0],loc_axis[1],loc_axis[2], r*loc_grain)
# 
#                 if write==True:
#                     #print "model_num: ",(count+1),"angle: ",x,"tranls_increment: ",transl_x[count],transl_y[count],transl_z[count]
#                     struct.write_to_PDB(file_name)
#                     ensemble_list.append([file_name[:-4],struct])
#                 else:
#                     ensemble_list.append([file_name[:-4],struct])
#                     #print "model_num: ",(count+1),"angle: ",x,"tranls_increment: ",transl_x[count],transl_y[count],transl_z[count]
#                     struct.reset_position()
#         return ensemble_list
# 
#         
#===============================================================================