# vim: set fileencoding=utf-8 :
#
# Copyright (C) 2012 Daniyal Kazempour
# University of Tuebingen
# Interfaculty Institute of Microbiology and Infection Medicine
# Div. of Microbiology/Biotechnology
#
# Copyright (C) 2012,2013 Kai Blin
# University of Tuebingen
# Interfaculty Institute of Microbiology and Infection Medicine
# Div. of Microbiology/Biotechnology
#
# Copyright (C) 2017 Marnix Medema
# Wageningen University
# Bioinformatics Group
#
# License: GNU Affero General Public License v3 or later
# A copy of GNU AGPL v3 should have been included in this software package in LICENSE.txt.
'''
More detailed thiopeptide analysis using HMMer-based leader peptide
cleavage site prediction as well as prediction of molcular mass.
'''

import logging
from Bio.SeqFeature import SeqFeature, FeatureLocation
from antismash import utils
from antismash.generic_modules.genefinding.all_orfs import Orf, scan_orfs, sort_orfs, get_reverse_complement
from antismash.specific_modules.lassopeptides.specific_analysis import find_all_orfs
from antismash.generic_modules.hmm_detection import HmmSignature
import re
from helperlibs.wrappers.io import TemporaryFile
import numpy as np
import os
from svm_thio import svm_classify


class Thiopeptide(object):
    '''
    Class to calculate and store thiopeptide information
    '''
    def __init__(self, start, end, score, rodeo_score):
        self.start = start
        self.end = end
        self.score = score
        self.rodeo_score = rodeo_score
        self.thio_type = ''
        self._leader = ''
        self._core = ''
        self._weight = -1
        self._monoisotopic_weight = -1
        self._alt_weights = []
        self._macrocycle = ''
        self._amidation = False
        self._mature_alt_weights = []
        self._mature_features = ''
        self._c_cut = ''


    @property
    def core(self):
        return self._core

    @core.setter
    def core(self, seq):
        self.core_analysis_monoisotopic = utils.RobustProteinAnalysis(seq, monoisotopic=True)
        self.core_analysis = utils.RobustProteinAnalysis(seq, monoisotopic=False)
        self._core = seq

    @property
    def leader(self):
        return self._leader

    @leader.setter
    def leader(self, seq):
        self._leader = seq

    @property
    def macrocycle(self):
        self._predict_macrocycle()
        return self._macrocycle

    @macrocycle.setter
    def macrocycle(self, macro):
        self._macrocycle = macro

    def _predict_macrocycle(self):
        '''
        Prediction of macrocycle ring
        '''
        
        if not self._core:
            raise ValueError()


        aux = self._core
        if len(self._core) == 17 and self._core[0] in "TIV":
            aux = self._core[4:]

        if aux[0] == "S":
            if "SC" in aux[9:12]:
                         
                if aux[9] == "S":
                    self._macrocycle = "26-member"
            
                if aux[10] == "S":
                    self._macrocycle = "29-member"

            if re.search(r"S{6,8}?",aux[(len(aux)/2):]):
                if aux[12] =="S":
                    self._macrocycle = "35-member"
                
    @property
    def amidation(self):
        return self._amidation

    @amidation.setter
    def amidation(self, value):
        self._amidation = value

    @property
    def mature_features(self):
        self._predict_mature_core_features()
        return self._mature_features

    @mature_features.setter
    def mature_features(self, feats):
        self._mature_features = feats

        
    def _predict_mature_core_features(self):
        '''
        Prediction of some features of the mature peptide
        '''
        if not self.thio_type:
            raise ValueError()

        self._mature_features = ''
        if self.thio_type == 'Type-I':
            self._mature_features = "Central ring- pyridine tetrasubstituted (hydroxyl group present); second macrocycle"
        
        if self.thio_type == 'Type-III':
            self._mature_features = "Central ring- pyridine trisubstituted"
        
        if self.thio_type == 'Type-II':
            self._mature_features = "Central ring- piperidine; second macrocycle containing a quinaldic acid moiety"
    
        return self._mature_features

    @property
    def c_cut(self):
        return self._c_cut

    @c_cut.setter
    def c_cut(self, ccut):
        self._c_cut = ccut

    
    def __repr__(self):
        return "Thiopeptide(%s..%s, %s, %r, %r, %s(%s), %s, %s, %s, %s)" % (self.start, self.end, self.score, self._core, self.thio_type, self._monoisotopic_weight, self._weight, self.macrocycle, self.amidation, self._mature_features, self.c_cut)
        
   
    def _calculate_mw(self):
        '''
        (re)calculate the monoisotopic mass and molecular weight
        '''
        if not self._core:
            raise ValueError()

       
        aas = self.core_analysis.count_amino_acids()
        no_thr_ser = aas['T'] + aas['S']
        no_cys = aas['C']

        mol_mass = self.core_analysis.molecular_weight()
        monoisotopic_mass = self.core_analysis_monoisotopic.molecular_weight()
        alt_weights = []
        self._weight = mol_mass 
        self._monoisotopic_weight = monoisotopic_mass
        
        dhs = 18.02 * (no_thr_ser-1)

        # every unbridged Ser or Thr might not be dehydrated
        for i in range(1, no_thr_ser + 1):
            self._alt_weights.append(mol_mass + 18.02 * i)

        if self.thio_type != "Type-III":
        # maturation reactions:
            mol_mass -= dhs
            
            #Ycao > cys/ser to azole ~20 Da for ring
            mol_mass -=  (20 * (no_cys - 1))
        
            #cycloaddition of 2 Dha residues
            mol_mass -= 35
        
            if self.thio_type == 'Type-I':
                #indolic acid (172) + cyclization
                mol_mass += 172 - 3

            if self.thio_type == 'Type-II':
                #quinaldic acid + cyclization
                mol_mass += 215 
            
            #considering 2 oxidations
            mol_mass += 32

        if self.amidation == True:
            mol_mass -= 70


        mass_diff = self._weight - mol_mass
        mod_mol_mass = mol_mass
        mod_monoisotopic_mass = monoisotopic_mass - mass_diff
 
        self._mature_alt_weights.append (mod_mol_mass)
        self._mature_alt_weights.append(mod_monoisotopic_mass)

       
        # every unbridged Ser or Thr might not be dehydrated
        for i in range(1, no_thr_ser + 1):
            self._mature_alt_weights.append(mod_mol_mass + 18.02 * i)

            
    @property
    def monoisotopic_mass(self):
        '''
        function determines the weight of the core peptide and substracts
        the weight which is reduced, due to dehydratation
        '''
        if self._monoisotopic_weight > -1:
            return self._monoisotopic_weight

        self._calculate_mw()
        return self._monoisotopic_weight

    @property
    def molecular_weight(self):
        '''
        function determines the weight of the core peptide
        '''
        if self._weight > -1:
            return self._weight

        self._calculate_mw()
        return self._weight

    @property
    def alternative_weights(self):
        '''
        function determines the possible alternative weights assuming one or
        more of the Ser/Thr residues aren't dehydrated
        '''
        if self._alt_weights != []:
            return self._alt_weights

        self._calculate_mw()
        return self._alt_weights

    @property
    def mature_alt_weights(self):
        '''
        function determines the possible mature alternative weights which includes mw,
        monoisotopic mass and alternative weightss due to different hydrated residues
    
        '''
        if self._mature_alt_weights != []:
            return self._mature_alt_weights

        self._calculate_mw()
        return self._mature_alt_weights

    
def predict_amidation(found_domains):
    amidation = False
    # nosA homologs nocA, tpdK nocA berI pbt
    if 'thio_amide' in found_domains:
        amidation = True

    return amidation
    
    
def predict_cleavage_site(query_hmmfile, target_sequence, threshold):
    '''
    Function extracts from HMMER the start position, end position and score 
    of the HMM alignment
    '''
    hmmer_res = utils.run_hmmpfam2(query_hmmfile, target_sequence)
    resvec = [None, None, None]
    
    for res in hmmer_res:
        for hits in res:
            thio_type = hits.description
            for hsp in hits:
                if hsp.bitscore > threshold:
                    resvec = [hsp.query_start, hsp.query_end-14, hsp.bitscore]
                    return resvec
    
    return resvec

        
def predict_type_from_gene_cluster(found_domains):
    '''
    Predict the thiopeptide type from the gene cluster
    '''

    if 'PF06968' in found_domains:
        return 'Type-I'
        
    if 'PF04055' in found_domains or 'PF00733' in found_domains:
        return 'Type-II'
    
    return 'Type-III'


def get_detected_domains(seq_record, cluster):
    
    found_domains = []
    #Gather biosynthetic domains
    for feature in utils.get_cluster_cds_features(cluster, seq_record):
      
        if not 'sec_met' in feature.qualifiers:
            continue

        for entry in feature.qualifiers['sec_met']:
            if entry.startswith('Domains detected:'):
                entry = entry[17:]
                domains = entry.split(';')
                for domain in domains:
                    found_domains.append(domain.split()[0])

    #Gather non-biosynthetic domains
    cluster_features = utils.get_cluster_cds_features(cluster, seq_record)
    cluster_fasta = utils.get_specific_multifasta(cluster_features)
    non_biosynthetic_hmms_by_id = run_non_biosynthetic_phmms(cluster_fasta)
    non_biosynthetic_hmms_found = []
    for id in non_biosynthetic_hmms_by_id.keys():
        hsps_found_for_this_id = non_biosynthetic_hmms_by_id[id]
        for hsp in hsps_found_for_this_id:
            if hsp.query_id not in non_biosynthetic_hmms_found:
                non_biosynthetic_hmms_found.append(hsp.query_id)
    found_domains += non_biosynthetic_hmms_found

    return found_domains

def run_non_biosynthetic_phmms(fasta):
    """Try to identify cleavage site using pHMM"""
    hmmdetails = [line.split("\t") for line in open(utils.get_full_path(__file__, "non_biosyn_hmms" + os.sep + "hmmdetails.txt"),"r").read().split("\n") if line.count("\t") == 3]
    _signature_profiles = [HmmSignature(details[0], details[1], int(details[2]), details[3]) for details in hmmdetails]
    non_biosynthetic_hmms_by_id = {}
    for sig in _signature_profiles:
        sig.path = utils.get_full_path(__file__, "non_biosyn_hmms" + os.sep + sig.path.rpartition(os.sep)[2])
        runresults = utils.run_hmmsearch(sig.path, fasta)
        for runresult in runresults:
            #Store result if it is above cut-off
            for hsp in runresult.hsps:
                if hsp.bitscore > sig.cutoff:
                    if not non_biosynthetic_hmms_by_id.has_key(hsp.hit_id):
                        non_biosynthetic_hmms_by_id[hsp.hit_id] = [hsp]
                    else:
                        non_biosynthetic_hmms_by_id[hsp.hit_id].append(hsp)
    return non_biosynthetic_hmms_by_id

def run_cleavage_site_phmm(fasta, hmmer_profile, threshold):
    """Try to identify cleavage site using pHMM"""
    profile = utils.get_full_path(__file__, hmmer_profile)
    return predict_cleavage_site(profile, fasta, threshold)

def run_cleavage_site_regex(fasta):
    """Try to identify cleavage site using regular expressions"""
    #Regular expressions; try 1 first, then 2, etc.
    rex1 = re.compile('([I|V]AS)')
    rex2 = re.compile('([G|A|S]AS)')

    #For each regular expression, check if there is a match that is <10 AA from the end
    if re.search(rex1,fasta) and len(re.split(rex1,fasta)[-1]) > 10:
        start, end = [m.span() for m in rex1.finditer(fasta)][-1]
        end -= 5
    elif re.search(rex2,fasta) and len(re.split(rex1,fasta)[-1]) > 10:
        start, end = [m.span() for m in rex2.finditer(fasta)][-1]
        end -= 5
    else:
        return [None, None, None]

    return start, end, 0


def thioscout(core):
    """ThioScout function from Chris Schwalen to count repeat blocks"""
    # rex1 repeating Cys Ser Thr residues
    rex1 = re.compile('[C]{2,}|[S]{2,}|[T]{2,}')

    # rex2 contiguous cyclizable residues
    rex2 = re.compile('[C|S|T]{2,}')

    rexout1 = re.findall(rex1, core)
    number_of_repeat_blocks = len(rexout1)

    temp = "".join(rexout1)
    number_of_repeating_CST = str([temp.count("C"), temp.count("S"), temp.count("T")]).strip("[]")

    rexout2 = re.findall(rex2,core)
    number_of_heteroblocks = len(rexout2)

    if rexout2:
        rexout2 = np.mean([len(x) for x in rexout2])
        avg_heteroblock_length = str(rexout2).strip("[]")
    else:
        avg_heteroblock_length = 0.0

    return number_of_repeating_CST, number_of_repeat_blocks, avg_heteroblock_length, number_of_heteroblocks


def acquire_rodeo_heuristics(seq_record, cluster, query, leader, core, domains):
    """Calculate heuristic scores for RODEO"""
    tabs = []
    score = 0
    #Contains TOMM YcaO (PF02624)
    if "YcaO" in domains:
        score += 2
        tabs.append(1)
    else:
        tabs.append(0)
    #Contains LanB N-terminal domain (PF04738)
    if "Lant_dehyd_N" in domains or "PF04738" in domains or "tsrC" in domains:
        score += 2
        tabs.append(1)
    else:
        tabs.append(0)
    #Contains LanB C-terminal domain (PF14028)
    if "Lant_dehyd_C" in domains:
        score += 2
        tabs.append(1)
    else:
        tabs.append(0)
    #Contains TOMM dehydrogenase (PF00881)
    if "PF00881" in domains:
        score += 2
        tabs.append(1)
    else:
        tabs.append(0)
    #Contains rSAM methyltransferase (PF04055)
    if "PF04055" in domains:
        score += 2
        tabs.append(1)
    else:
        tabs.append(0)
    #Contains P450 (PF00067)
    if "p450" in domains:
        score += 1
        tabs.append(1)
    else:
        tabs.append(0)
    #Contains ABC transporter or abhydrolase
    abc_transp_abhydrolases = ["PF00005", "PF01061", "PF12698", "PF12697", "PF00561"]
    for dom in abc_transp_abhydrolases:
        if dom in domains:
            score += 1
            tabs.append(1)
        else:
            tabs.append(0)
    #CSS/CTT, SS/SSS/SSS, CC/CCC/CCCC, TT/TT/TTTT motifs
    motifs = (('[C][S]{2,}', 1), ('[C][T]{2,}', 1), ('[S]{2,}', 1), ('[S]{3,}', 1), \
       ('[S]{4,}', 2), ('[C]{2,}', 1), ('[C]{3,}', 1), ('[C]{4,}', 2), \
       ('[T]{2,}', 1), ('[T]{3,}', 1), ('[T]{4,}', 2))
    for motif in motifs:
        if re.search(motif[0], core) != None:
            score += motif[1]
            tabs.append(1)
        else:
            tabs.append(0)
    #No Cys/Ser/Thr core residues
    for aa in "CST":
        if aa not in core:
            score -= 2
            tabs.append(1)
        else:
            tabs.append(0)
    #Mass of core peptide (unmodified) < 2100
    core_analysis = utils.RobustProteinAnalysis(core, monoisotopic=True, invalid='average')
    if core_analysis.molecular_weight() < 2100:
        score += 1
        tabs.append(1)
    else:
        tabs.append(0)
    #Sum of repeating Cys/Ser/Thr > 4
    number_of_repeating_CST, number_of_repeat_blocks, avg_heteroblock_length, number_of_heteroblocks = thioscout(core)
    if sum([int(nr) for nr in number_of_repeating_CST.split(", ")]) > 4:
        score += 2
        tabs.append(1)
    else:
        tabs.append(0)
    #Avg heterocycle block length > 3
    if avg_heteroblock_length != "nan" and avg_heteroblock_length > 3:
        score += 2
        tabs.append(1)
    else:
        tabs.append(0)
    #Leader net charge < 5
    charge_dict = {"E": -1, "D": -1, "K": 1, "R": 1}
    leader_charge = sum([charge_dict[aa] for aa in leader if aa in charge_dict])
    if leader_charge < 5:
        score += 2
        tabs.append(1)
    else:
        tabs.append(0)
    #Leader net charge > 0
    if leader_charge > 0:
        score -= 2
        tabs.append(1)
    else:
        tabs.append(0)
    #Leader contains a Cys
    if "C" in leader:
        score -= 1
        tabs.append(1)
    else:
        tabs.append(0)
    #Peptide terminates Cys/Ser/Thr
    if core[-1] in ["C", "S", "T"]:
        score += 1
        tabs.append(1)
    else:
        tabs.append(0)
    #Core contains >= 2 positive residues
    if sum([core.count(aa) for aa in "RK"]) >= 2:
        score -= 1
        tabs.append(1)
    else:
        tabs.append(0)
    #Number of heterocyclizable residues to core ratio > 0.4
    if float(sum([core.count(aa) for aa in "CST"])) / len(core) >= 0.4:
        score += 2
        tabs.append(1)
    else:
        tabs.append(0)
    return score, tabs

def generate_rodeo_svm_csv(seq_record, cluster, query, leader, core, previously_gathered_tabs):
    """Generates all the items for one candidate precursor peptide"""
    precursor = leader + core
    columns = []
    #Precursor Index
    columns.append(1)
    #classification
    columns.append(0)
    columns += previously_gathered_tabs
    #Number repeating blocks of heterocyclizable residues in core
    number_of_repeating_CST, number_of_repeat_blocks, avg_heteroblock_length, number_of_heteroblocks = thioscout(core)
    columns.append(number_of_repeat_blocks)
    #Number of core repeating Cys
    columns.append(int(number_of_repeating_CST.split(", ")[0]))
    #Number of core repeating Ser
    columns.append(int(number_of_repeating_CST.split(", ")[1]))
    #Number of core repeating Thr
    columns.append(int(number_of_repeating_CST.split(", ")[2]))
    #Number of blocks of heterocyclizable residues in core
    columns.append(number_of_heteroblocks)
    #Average core heterocycle block length
    if avg_heteroblock_length == "nan":
        columns.append(0)
    else:
        columns.append(avg_heteroblock_length)
    #Precursor peptide mass (unmodified)
    columns.append(utils.RobustProteinAnalysis(leader+core, monoisotopic=True, invalid='average').molecular_weight())
    #Unmodified leader peptide mass
    columns.append(utils.RobustProteinAnalysis(leader, monoisotopic=True, invalid='average').molecular_weight())
    #Unmodified core peptide mass
    columns.append(utils.RobustProteinAnalysis(core, monoisotopic=True, invalid='average').molecular_weight())
    #Length of Precursor
    columns.append(len(precursor))
    #Length of Leader
    columns.append(len(leader))
    #Length of Core
    columns.append(len(core))
    #Ratio of length of leader / length of core
    columns.append(float(len(core)) / float(len(leader)))
    #Ratio of heterocyclizable  residues / length of core
    columns.append(float(sum([core.count(aa) for aa in "CST"])) / len(core))
    #Estimated core charge at neutral pH
    #charge_dict = {"E": -1, "D": -1, "K": 1, "H": 1, "R": 1}
    #columns.append(sum([charge_dict[aa] for aa in core if aa in charge_dict]))
    #Estimated leader charge at neutral pH
    #columns.append(sum([charge_dict[aa] for aa in leader if aa in charge_dict]))
    #Estimated precursor charge at neutral pH
    #columns.append(sum([charge_dict[aa] for aa in leader+core if aa in charge_dict]))
    #Absolute value of core charge at neutral pH
    #columns.append(abs(sum([charge_dict[aa] for aa in core if aa in charge_dict])))
    #Absolute value of leader charge at neutral pH
    #columns.append(abs(sum([charge_dict[aa] for aa in leader if aa in charge_dict])))
    #Absolute value of precursor charge at neutral pH
    #columns.append(abs(sum([charge_dict[aa] for aa in leader+core if aa in charge_dict])))
    #Number in leader of each amino acid
    columns += [leader.count(aa) for aa in "ARDNCQEGHILKMFPSTWYV"]
    #Aromatics in leader
    columns.append(sum([leader.count(aa) for aa in "FWY"]))
    #Neg charged in leader
    columns.append(sum([leader.count(aa) for aa in "DE"]))
    #Pos charged in leader
    columns.append(sum([leader.count(aa) for aa in "RK"]))
    #Charged in leader
    columns.append(sum([leader.count(aa) for aa in "RKDE"]))
    #Aliphatic in leader
    columns.append(sum([leader.count(aa) for aa in "GAVLMI"]))
    #Hydroxyl in leader
    columns.append(sum([leader.count(aa) for aa in "ST"]))
    #Counts of AAs in core
    columns += [core.count(aa) for aa in "ARDNCQEGHILKMFPSTWYV"]
    #Aromatics in core
    columns.append(sum([core.count(aa) for aa in "FWY"]))
    #Neg charged in core
    columns.append(sum([core.count(aa) for aa in "DE"]))
    #Pos charged in core
    columns.append(sum([core.count(aa) for aa in "RK"]))
    #Charged in core
    columns.append(sum([core.count(aa) for aa in "RKDE"]))
    #Aliphatic in core
    columns.append(sum([core.count(aa) for aa in "GAVLMI"]))
    #Hydroxyl in core
    columns.append(sum([core.count(aa) for aa in "ST"]))
    #Counts of AAs in entire precursor (leader+core)
    columns += [precursor.count(aa) for aa in "ARDNCQEGHILKMFPSTWYV"]
    #Aromatics in precursor
    columns.append(sum([precursor.count(aa) for aa in "FWY"]))
    #Neg charged in precursor
    columns.append(sum([precursor.count(aa) for aa in "DE"]))
    #Pos charged in precursor
    columns.append(sum([precursor.count(aa) for aa in "RK"]))
    #Charged in precursor
    columns.append(sum([precursor.count(aa) for aa in "RKDE"]))
    #Aliphatic in precursor
    columns.append(sum([precursor.count(aa) for aa in "GAVLMI"]))
    #Hydroxyl in precursor
    columns.append(sum([precursor.count(aa) for aa in "ST"]))
    return columns

def run_rodeo_svm(csv_columns):
    """Run RODEO SVM"""
    input_training_file = utils.get_full_path(__file__, 'svm_thio' + os.sep + 'training_set.csv')         # the CSV containing the training set
    with TemporaryFile() as input_fitting_file:
        out_file = open(input_fitting_file.name, "w")
        out_file.write('PK,Classification,Contains TOMM YcaO PF02624,Contains LanB Nterm PF04738,Contains LanB Cterm PF14028,Contains TOMM dehy PF00881,Contains rSAM MTase PF04055,Contains P450 PF00067,Contains ABC trans1 PF00005,Contains ABC trans2 PF01061,Contains ABC trans3 PF12698,Contains abhydrolase1 PF12697,Contains abhydrolase2 PF00561,CSS motif,CTT motif,SS motif,SSS motif,SSSS motif,CC motif,CCC motif,CCCC motif,TT motif,TTT motif,TTTT motif,No Cys core residues,No Ser core residues,No Thr core residues,Core mass < 2100,Sum of repeating Cys/Ser/Thr > 4,Avg heterocycle block length > 3,Leader net charge < 5,Leader net charge > 0,Leader contains a Cys?,Peptide terminates Cys/Ser/Thr,Core contains >= 2 positive residues,Heterocycle ratio > 0.4,Number of core repeating blocks,Number of core repeating Cys,Number of core repeating Ser,Number of core repeating Thr,Number of core heterocycle blocks,avg core heterocycle block length,Precursor peptide mass (unmodified),Leader peptide mass (unmodified),Core peptide mass (unmodified),Length of Precursor,Length of Leader,Length of Core,Leader / core ratio,Heterocycle residues/lenth of core,A,R,D,N,C,Q,E,G,H,I,L,K,M,F,P,S,T,W,Y,V,Aromatics,Neg charged,Pos charged,Charged,Aliphatic,Hydroxyl,A,R,D,N,C,Q,E,G,H,I,L,K,M,F,P,S,T,W,Y,V,Aromatics,Neg charged,Pos charged,Charged,Aliphatic,Hydroxyl,A,R,D,N,C,Q,E,G,H,I,L,K,M,F,P,S,T,W,Y,V,Aromatics,Neg charged,Pos charged,Charged,Aliphatic,Hydroxyl\n')
        out_file.write(",".join([str(item) for item in csv_columns]))
        out_file.close()
        with TemporaryFile() as output_filename:
            svm_classify.classify_peptide(input_training_file, input_fitting_file.name, output_filename.name)
            output = open(output_filename.name, "r").read()
            if output.strip().partition(",")[2] == "1":
                return 10
            else:
                return 0

def run_rodeo(seq_record, cluster, query, leader, core, domains):
    """Run RODEO heuristics + SVM to assess precursor peptide candidate"""
    rodeo_score = 0
    
    #Incorporate heuristic scores
    heuristic_score, gathered_tabs_for_csv = acquire_rodeo_heuristics(seq_record, cluster, query, leader, core, domains)
    rodeo_score += heuristic_score

    #Incorporate SVM scores
    csv_columns = generate_rodeo_svm_csv(seq_record, cluster, query, leader, core, gathered_tabs_for_csv)
    rodeo_score +=  run_rodeo_svm(csv_columns)

    if rodeo_score >= 20:
        return True, rodeo_score
    else:
        return False, rodeo_score

def determine_precursor_peptide_candidate(seq_record, cluster, query, query_sequence, domains):
    """Identify precursor peptide candidates and split into two"""

    #Skip sequences with >200 AA
    if len(query_sequence) > 200 or len(query_sequence) < 40:
        return

    #Create FASTA sequence for feature under study
    thio_a_fasta = ">%s\n%s" % (utils.get_gene_id(query), query_sequence)

    #Run sequence against pHMM; if positive, parse into a vector containing START, END and SCORE
    start, end, score = run_cleavage_site_phmm(thio_a_fasta, 'thio_cleave.hmm', -3.00)

    #If no pHMM hit, try regular expression
    if score is None:
        start, end, score = run_cleavage_site_regex(thio_a_fasta)
        if score is None or end > len(query_sequence) - 5:
            start, end, score = 0, int(len(query_sequence)*0.60) - 14, 0

    #Run RODEO to assess whether candidate precursor peptide is judged real
    rodeo_result = run_rodeo(seq_record, cluster, query, query_sequence[:end], query_sequence[end:], domains)
    if rodeo_result[0] is False:
        #logging.debug('%r: No cleavage site predicted' % utils.get_gene_id(query))
        return
    else:
        thiopeptide = Thiopeptide(start, end + 1, score, rodeo_result[1])

    #Determine the leader and core peptide
    thiopeptide.leader = query_sequence[:end]
    thiopeptide.core = query_sequence[end:]

    return thiopeptide

#thresh_hit = -2.00

def run_thiopred(seq_record, cluster, query, thio_type, domains):
   
    query_sequence = utils.get_aa_sequence(query, to_stop=True)
    thio_a_fasta = ">%s\n%s" % (utils.get_gene_id(query), query_sequence)

    #Run checks to determine whether an ORF encodes a precursor peptide
    result = determine_precursor_peptide_candidate(seq_record, cluster, query, query_sequence, domains)
    if result is None:
        return
    
    #Determine thiopeptide type
    result.thio_type = thio_type
   
    if not 'sec_met' in query.qualifiers:
        query.qualifiers['sec_met'] = []


    if ";".join(query.qualifiers['sec_met']).find(';Kind: biosynthetic') < 0:
        query.qualifiers['sec_met'].append('Kind: biosynthetic')

    # leader cleavage "validation"
    pep_hmmer_profile = 'thiopep2.hmm'     
    thresh_pep_hit = -2

    core_a_fasta = ">%s\n%s" % (utils.get_gene_id(query), result.core)
 
    profile_pep = utils.get_full_path(__file__, pep_hmmer_profile)
    hmmer_res_pep = utils.run_hmmpfam2(profile_pep, core_a_fasta)

    filter_out = True
    for res in hmmer_res_pep:
        for hits in res:
            for seq in hits:
                if seq.bitscore > thresh_pep_hit:
                    filter_out = False

    if filter_out:
        return

    # additional filter(s) for peptide prediction
    aux = ""
    if re.search("[ISTV][SACNTW][STNCVG][ATCSGM][SVTFC][CGSTEAV][TCGVY]", result.core):
        aux = re.search("[ISTV][SACNTW][STNCVG][ATCSGM][SVTFC][CGSTEAV][TCGVY].*"\
                        ,result.core).group()
    else:
        return
    
    diff = len(result.core)-len(aux)
    
    if aux != "" and (len(aux)<20 and len(aux)>10):
        result.leader = result.leader+result.core[:diff]
        result.core = aux

        
    # prediction of cleavage in C-terminal based on thiopeptide's core sequence
    # if last core residue != S or T or C > great chance of a tail cut
    if result.core[-1] not in "SCT":
        C_term_hmmer_profile = 'thio_tail.hmm'
        thresh_C_hit = -9

        temp = result.core[-10:]
        core_a_fasta = ">%s\n%s" % (utils.get_gene_id(query),temp)
 
        profile_C = utils.get_full_path(__file__, C_term_hmmer_profile)
        hmmer_res_C = utils.run_hmmpfam2(profile_C, core_a_fasta)

        for res in hmmer_res_C:
            for hits in res:
                for seq in hits:
                    if seq.bitscore > thresh_C_hit:
                        result.c_cut = temp[seq.query_end-1:]
       
    
    if result is None:
        logging.debug('%r: No C-terminal cleavage site predicted' % utils.get_gene_id(query))
        return


    if not 'sec_met' in query.qualifiers:
        query.qualifiers['sec_met'] = []


    if ";".join(query.qualifiers['sec_met']).find(';Kind: biosynthetic') < 0:
        query.qualifiers['sec_met'].append('Kind: biosynthetic')

    return result
     

def result_vec_to_features(orig_feature, res_vec):
    start = orig_feature.location.start
    end = orig_feature.location.start + (res_vec.end * 3)
    strand = orig_feature.location.strand
    loc = FeatureLocation(start, end, strand=strand)
    leader_feature = SeqFeature(loc, type='CDS_motif')
    leader_feature.qualifiers['note'] = ['leader peptide']
    leader_feature.qualifiers['note'].append('thiopeptide')
    leader_feature.qualifiers['note'].append('predicted leader seq: %s' % res_vec.leader)
    leader_feature.qualifiers['locus_tag'] = [ utils.get_gene_id(orig_feature) ]
    start = end
    end = orig_feature.location.end
    loc = FeatureLocation(start, end, strand=strand)
    core_feature = SeqFeature(loc, type='CDS_motif')
    core_feature.qualifiers['note'] = ['core peptide']
    core_feature.qualifiers['note'].append('thiopeptide')

           
    #resets .. to recalculate weights considering C-terminal putative cut
    res_vec._weight = -1
    res_vec._monoisotopic_weight = -1
    oldcore = res_vec.core
    res_vec.core = oldcore[:(len(res_vec.core)-len(res_vec.c_cut))]
 

    core_feature.qualifiers['note'].append('monoisotopic mass: %0.1f' % res_vec.monoisotopic_mass)
    core_feature.qualifiers['note'].append('molecular weight: %0.1f' % res_vec.molecular_weight)
    if res_vec.alternative_weights:
        weights = map(lambda x: "%0.1f" % x, res_vec.alternative_weights)
        core_feature.qualifiers['note'].append('alternative weights: %s' % "; ".join(weights))

    if res_vec.thio_type != 'Type-III':
    #weights considering maturation reaction
        weights = map(lambda x: "%0.1f" % x, res_vec.mature_alt_weights)
        core_feature.qualifiers['note'].append('alternative weights considering maturation: %s' % "; ".join(weights))

       
    core_feature.qualifiers['note'].append('predicted core seq: %s' % res_vec.core)
    core_feature.qualifiers['note'].append('predicted type: %s' % res_vec.thio_type)
    core_feature.qualifiers['note'].append('score: %0.2f' % res_vec.score)
    core_feature.qualifiers['note'].append('RODEO score: %i' % res_vec.rodeo_score)

    
    core_feature.qualifiers['note'].append('predicted macrocycle: %s' %res_vec.macrocycle)
    core_feature.qualifiers['note'].append('putative cleaved off residues: %s' %res_vec.c_cut)


    if res_vec.amidation:
        core_feature.qualifiers['note'].append('predicted tail reaction: dealkylation of C-Terminal residue; amidation')
    core_feature.qualifiers['note'].append('predicted core features: %s' %res_vec.mature_features)    
    core_feature.qualifiers['locus_tag'] = [ utils.get_gene_id(orig_feature) ]

    return [leader_feature, core_feature]


def specific_analysis(seq_record, options):

 
    clusters = utils.get_cluster_features(seq_record)
    for cluster in clusters:
        if 'product' not in cluster.qualifiers or \
           'thiopeptide' not in cluster.qualifiers['product'][0]:
            continue

        #Find candidate ORFs that are not yet annotated
        new_orfs = find_all_orfs(seq_record, cluster)

        thio_fs = utils.get_cluster_cds_features(cluster, seq_record) + new_orfs
        domains = get_detected_domains(seq_record, cluster)
        thio_type = predict_type_from_gene_cluster(domains)
        amidation = predict_amidation(domains)
       
        if thio_type is None:
            return
    
        for thio_f in thio_fs:
            result_vec = run_thiopred(seq_record, cluster, thio_f, thio_type, domains)
            
            if result_vec is None:
                continue

            if amidation:
                result_vec.amidation = True
                
            new_features = result_vec_to_features(thio_f, result_vec)
            if "allorf" in utils.get_gene_id(thio_f):
                new_features.append(thio_f)
            seq_record.features.extend(new_features)
