# 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 sactipeptide analysis using HMMer-based leader peptide
cleavage sites prediction as well as prediction of number of  dissulfide
bridges, molcular mass and macrolactam ring.
'''

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 distance_to_pfam, find_all_orfs
from antismash.generic_modules.hmm_detection import HmmSignature
import re
from helperlibs.wrappers.io import TemporaryFile
import os
import numpy as np
from svm_sacti import svm_classify

class Sactipeptide(object):
    '''
    Class to calculate and store sactipeptide information
    '''
    def __init__(self, start, end, score, rodeo_score):
        self.start = start
        self.end = end
        self.score = score
        self.rodeo_score = rodeo_score
        self._leader = ''
        self._core = ''


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

    @core.setter
    def core(self, seq):
        seq = seq.replace('X', '')
        self._core = seq

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

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

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

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

def get_detected_domains(seq_record, cluster):
    
    found_domains = []
    domain_counts = {}
    #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 'Domains detected:' in entry:
                entry = entry.partition('Domains detected: ')[2]
                domains = entry.split(';')
                for domain in domains:
                    if domain.split()[0] not in found_domains:
                        found_domains.append(domain.split()[0])
                        domain_counts[domain.split()[0]] = 1
                    else:
                        domain_counts[domain.split()[0]] += 1

    #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)
                domain_counts[hsp.query_id] = 1
            else:
                domain_counts[hsp.query_id] += 1
    found_domains += non_biosynthetic_hmms_found

    return found_domains, domain_counts

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 cds_has_domain(cds, query_domain):
    """Function to test whether a cds has a certain domain"""
    if not 'sec_met' in cds.qualifiers:
        return False
    for entry in cds.qualifiers['sec_met']:
        if 'Domains detected:' in entry:
            entry = entry.partition('Domains detected: ')[2]
            domains = entry.split(';')
            for domain in domains:
                domain = domain.split()[0]
                if domain == query_domain:
                    return True
    return False

def acquire_rodeo_heuristics(seq_record, cluster, query, leader, core, domains):
    """Calculate heuristic scores for RODEO"""
    tabs = []
    score = 0
    precursor = leader+core
    #Calcd. precursor peptide mass (Da)
    precursor_analysis = utils.RobustProteinAnalysis(precursor, monoisotopic=True, invalid='average')
    tabs.append(float(precursor_analysis.molecular_weight()))
    score += 0
    #Calcd. leader peptide mass (Da)
    leader_analysis = utils.RobustProteinAnalysis(leader, monoisotopic=True, invalid='average')
    tabs.append(float(leader_analysis.molecular_weight()))
    score += 0
    #Calcd. core peptide mass (Da)
    core_analysis = utils.RobustProteinAnalysis(core, monoisotopic=True, invalid='average')
    tabs.append(float(core_analysis.molecular_weight()))
    score += 0
    #Distance to any biosynthetic protein (E, B, C)
    hmmer_profiles = ['PF04055']
    distance = distance_to_pfam(seq_record, query, hmmer_profiles)
    tabs.append(distance)
    #rSAM within 500 nt?
    if distance_to_pfam(seq_record, query, ['PF04055']) < 500:
        score += 1
        tabs.append(1)
    else:
        tabs.append(0)
    #rSAM within 150 nt?
    if distance_to_pfam(seq_record, query, ['PF04055']) < 150:
        score += 1
        tabs.append(1)
    else:
        tabs.append(0)
    #rSAM further than 1000 nt?
    if distance_to_pfam(seq_record, query, ['PF04055']) == -1 or \
       distance_to_pfam(seq_record, query, ['PF04055']) > 10000:
        score -= 2
        tabs.append(1)
    else:
        tabs.append(0)
    #Ratio of N-term to 1st Cys 0.25<x<0.60; Ratio of N-term to 1st Cys <0.25 or >0.60
    if "C" not in precursor:
        score -= 2
        tabs += [0,1]
    elif 0.25 <= precursor.find("C") / float(len(precursor)) <= 0.60:
        score += 2
        tabs += [1,0]
    else:
        score -= 2
        tabs += [0,1]
    #Three or more Cys; Less than 3 Cys
    if precursor.count("C") >= 3:
        score += 4
        tabs += [1,0]
    else:
        score -= 4
        tabs += [0,1]
    #CxC/CxxC/CxxxC/CxxxxxC; #CC/CCC
    motifs = (('C[ARNDBCEQZGHILKMFPSTWYV]{5}C', 2), ('C[ARNDBCEQZGHILKMFPSTWYV]{3}C', 1), \
       ('C[ARNDBCEQZGHILKMFPSTWYV]{2}C', 1),('C[ARNDBCEQZGHILKMFPSTWYV]{1}C', 2), \
       ('CC', -2), ('CCC', -2))
    for motif in motifs:
        if re.search(motif[0], core) != None:
            score += motif[1]
            tabs.append(1)
        else:
            tabs.append(0)
    #No Cys in last 1/4th?
    quarter_length = (len(precursor) / 4) * -1
    if not "C" in precursor[quarter_length:]:
        score += 1
        tabs.append(1)
    else:
        score -= 1
        tabs.append(0)
    #2 Cys in first 2/3rds of precursor, 1 Cys in last 1/3rd of precursor
    two_thirds = (len(precursor) / 3) * 2
    if precursor[:two_thirds].count("C") == 2 and precursor[two_thirds:].count("C") == 1:
        score += 1
        tabs.append(1)
    else:
        tabs.append(0)
    #Peptide matches SboA hmm
    if cds_has_domain(query, "Subtilosin_A"):
        score += 3
        tabs.append(1)
    else:
        tabs.append(0)
    #Peptide matches SkfA hmm
    if cds_has_domain(query, "TIGR04404"):
        score += 3
        tabs.append(1)
    else:
        tabs.append(0)
    #Peptide matches SCIFF hmm
    if cds_has_domain(query, "TIGR03973"):
        score += 2
        tabs.append(1)
    else:
        tabs.append(0)
    #Cluster has PqqD/RRE (PF05402)
    if "PF05402" in domains:
        score += 1
        tabs.append(1)
    else:
        tabs.append(0)
    #Cluster has SPASM domain (PF13186)
    if "PF13186" in domains:
        score += 1
        tabs.append(1)
    else:
        tabs.append(0)
    #PF04055 (rSAM) domain start
    domainpath = utils.get_full_path(__file__, "").rpartition(os.sep)[0].rpartition(os.sep)[0].rpartition(os.sep)[0] + os.sep + "generic_modules" + os.sep + "hmm_detection" 
    details = [line.split("\t") for line in open(domainpath + os.sep + "hmmdetails.txt","r").read().split("\n") if line.count("\t") == 3 and "PF04055" in line][0]
    sig = HmmSignature(details[0], details[1], int(details[2]), details[3])
    sig.path = domainpath + os.sep + "PF04055.hmm"
    cluster_features = utils.get_cluster_cds_features(cluster, seq_record)
    fasta = utils.get_specific_multifasta(cluster_features)
    runresults = utils.run_hmmsearch(sig.path, fasta)
    hitstarts = []
    hitends = []
    for runresult in runresults:
        #Store result if it is above cut-off
        for hsp in runresult.hsps:
            if hsp.bitscore > sig.cutoff:
                hitstarts.append(hsp.hit_start)
                hitstarts.append(hsp.hit_end)
    if len(hitstarts) > 0 and max(hitstarts) > 80:
        score += 1
        tabs.append(1)
    else:
        tabs.append(0)
    #Cluster has peptidase
    peptidase_domains = ["Peptidase_M16_C", "Peptidase_S8", "Peptidase_M16", "Peptidase_S41"]
    no_peptidase = True
    for pepdom in peptidase_domains:
        if pepdom in domains:
            score += 1
            tabs.append(1)
            no_peptidase = False
        else:
            tabs.append(0)
    #Cluster has transporter
    transport_domains = ["PF00005", "PF00664"]
    for transpdom in transport_domains:
        if transpdom in domains:
            score += 1
            tabs.append(1)
        else:
            tabs.append(0)
    #Cluster has response regulator (PF00072)
    if "PF00072" in domains:
        score += 1
        tabs.append(1)
    else:
        tabs.append(0)
    #Cluster has major facilitator (PF07690)
    if "PF07690" in domains:
        score += 1
        tabs.append(1)
    else:
        tabs.append(0)
    #Cluster has ATPase (PF13304)
    if "PF13304" in domains:
        score += 1
        tabs.append(1)
    else:
        tabs.append(0)
    #Cluster has Fer4_12 (PF13353)
    if "PF13353" in domains:
        score += 1
        tabs.append(1)
    else:
        tabs.append(0)
    #Cluster has rSAM (PF04055)
    if "PF04055" in domains or "TIGR03975" in domains:
        score += 2
        tabs.append(1)
    else:
        tabs.append(0)
    #Cluster has no recognized peptidase
    if no_peptidase:
        score -= 2
        tabs.append(1)
    else:
        tabs.append(0)
    #C-terminal portion is < 0.35 or > 0.65; C-terminal portion is defined as the part from the last cysteine in the last identified Cx(n)C motif to the C-terminus
    #And criterion "C-terminal portion is > 0.35 and < 0.65"
    if "C" not in precursor:
        score -= 2
        tabs += [1,0]
    else:
        last_motif_C = 0
        index = -1
        for aa in reversed(precursor):
            if aa == "C" and "C" in precursor[index-6:index]:
                last_motif_C = len(precursor[:index]) + 1
            index -= 1
        if not (0.35 <= last_motif_C / float(len(precursor)) <= 0.65):
            score -= 2
            tabs += [1,0]
        else:
            score += 3
            tabs += [1,0]
    #SS profile sum > 1; The "SS profile" variables are calculated by the sactiscout.py script, namely the last regex search done using the rex4. This provides a count for each number in the range
    lanlower = 1
    lanupper = 6
    cysrex = '(?=(C.{%d,%d}C))' % (lanlower, lanupper)
    rex4 = re.compile(cysrex)
    totrings = rex4.findall(core)
    if len(totrings) > 1:
        score += 2
        tabs.append(1)
    else:
        tabs.append(0)
    return score, tabs, hitends

def lanscout(seq):
    lanlower = 1
    lanupper = 6
    #nis = 'ITSISLCTPGCKTGALMGCNMKTATCHCSIHVSK'
    #define lanthionine ring with a c-terminal Cys
    #lan = '(?=([T|S].{%d,%d}C))' % (lanlower, lanupper)
    cysrex = '(?=(C.{%d,%d}C))' % (lanlower, lanupper)
    
    #db = []
    strings = []
    #numringlist = []

    db = []
    avgs = []
    numrings = []
    cterm = []
    for n in range(0, len(seq)):
        core = str(seq[n][:])
        strings.append(core)

        rex1 = re.compile(r'([G|A]{1,2}C)')
        rex2 = re.compile(r'(C[G|A]{1,2})')
        rex3 = re.compile(r'(C.{2,5})(?=C)')
        rex4 = re.compile(cysrex)
        
        loc1 = []
        match1 = []
        for county in rex1.finditer(core):
            loc1.append(county.start())
            match1.append(county.group())
            
        for county in rex2.finditer(core):
            loc1.append(county.start())
            match1.append(county.group())
        
        
        tempy = []
        for m in range(len(loc1[:-1])):
            if (loc1[m+1]-loc1[m]) > 0:
                tempy.append(loc1[m+1]-loc1[m])
            else: 
                tempy.append(1)
        
        if tempy:
            avgs.append(np.mean(tempy))
        else:
            avgs.append(0)

        numrings.append(len(match1))
        #print loc1
        #print match1
        cterm.append(len(rex3.split(core)[-1])/float(len(core)))
        #print cterm

        numringlist = []
        totrings = rex4.findall(core)
        size = []
        for i in range(0, len(totrings)):
            size.append(len(totrings[i]))
        db.append(size)
        rings = len(totrings)
        numringlist.append(numrings)
    
    profile = []
    for i in range(0,len(db)):
        #print db[i]
        temp = []
        for j in range(lanlower+2,lanupper+3):
            temp.append(db[i].count(j))
        profile.append(temp)
    #print profile

    for i in range(0,len(profile)):
        #profile[i] = str(profile[i])
        profile[i]=str(profile[i]).strip('[]')
    #print profile
    
    return numrings, strings, avgs, cterm, profile

def generate_rodeo_svm_csv(seq_record, cluster, query, leader, core, previously_gathered_tabs, hitends, domains, domain_counts):
    """Generates all the items for one candidate precursor peptide"""
    columns = []
    precursor = leader + core
    #Precursor Index
    columns.append(1)
    #classification
    columns.append(0)
    columns += previously_gathered_tabs
    #Length of leader peptide
    columns.append(len(leader))
    #Length of precursor peptide
    columns.append(len(precursor))
    #Length of core peptide
    columns.append(len(core))
    #Length of core / length of precursor ratio
    columns.append(float(len(core)) / float(len(precursor)))
    #Length of core / length of leader ratio
    columns.append(float(len(core)) / float(len(leader)))
    #Ratio of length of N-terminus to first Cys / length of precursor
    columns.append(precursor.count("C") / float(len(precursor)))
    #Number of occurrences of CxNC motifs
    numrings, strings, avgs, cterm, profile = lanscout([precursor])
    if np.isnan(avgs[0]):
        avgs = [0]
    columns.append(numrings[0])
    #Average distance between CxNC motifs
    columns.append(avgs[0])
    #Ratio of length from last CxNC to C-terminus / length of core
    columns.append(cterm[0])
    #Number of instances of CxNC where N = 1
    columns.append(profile[0].split(",")[0])
    #Number of instances of CxNC where N = 2
    columns.append(profile[0].split(",")[1])
    #Number of instances of CxNC where N = 3
    columns.append(profile[0].split(",")[2])
    #Number of instances of CxNC where N = 4
    columns.append(profile[0].split(",")[3])
    #Number of instances of CxNC where N = 5
    columns.append(profile[0].split(",")[4])
    #Number of instances of CxNC where N = 6
    columns.append(profile[0].split(",")[5])
    #Estimated core charge at neutral pH
    #charge_dict = {"E": -1, "D": -1, "K": 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 precursor 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 entire precursor of each amino acid
    columns += [precursor.count(aa) for aa in "ARDNCQEGHILKMFPSTWYV"]
    #Number in entire precursor of each amino acid type (Aromatics, Neg charged, Pos charged, Charged, Aliphatic, Hydroxyl)
    columns.append(sum([precursor.count(aa) for aa in "FWY"]))
    columns.append(sum([precursor.count(aa) for aa in "DE"]))
    columns.append(sum([precursor.count(aa) for aa in "RK"]))
    columns.append(sum([precursor.count(aa) for aa in "RKDE"]))
    columns.append(sum([precursor.count(aa) for aa in "GAVLMI"]))
    columns.append(sum([precursor.count(aa) for aa in "ST"]))
    #Number in leader of each amino acid
    columns += [leader.count(aa) for aa in "ARDNCQEGHILKMFPSTWYV"]
    #Number in leader of each amino acid type (Aromatics, Neg charged, Pos charged, Charged, Aliphatic, Hydroxyl)
    columns.append(sum([leader.count(aa) for aa in "FWY"]))
    columns.append(sum([leader.count(aa) for aa in "DE"]))
    columns.append(sum([leader.count(aa) for aa in "RK"]))
    columns.append(sum([leader.count(aa) for aa in "RKDE"]))
    columns.append(sum([leader.count(aa) for aa in "GAVLMI"]))
    columns.append(sum([leader.count(aa) for aa in "ST"]))
    #Number in core of each amino acid
    columns += [core.count(aa) for aa in "ARDNCQEGHILKMFPSTWYV"]
    #Number in core of each amino acid type (Aromatics, Neg charged, Pos charged, Charged, Aliphatic, Hydroxyl)
    columns.append(sum([core.count(aa) for aa in "FWY"]))
    columns.append(sum([core.count(aa) for aa in "DE"]))
    columns.append(sum([core.count(aa) for aa in "RK"]))
    columns.append(sum([core.count(aa) for aa in "RKDE"]))
    columns.append(sum([core.count(aa) for aa in "GAVLMI"]))
    columns.append(sum([core.count(aa) for aa in "ST"]))
    #Number of each peptidase Pfam hit (PF05193/PF00082/PF03572/PF00675/PF02517/PF02163/PF00326)
    peptidase_domains = ["PF05193", "PF00082", "PF03572", "PF00675", "PF02517", "PF02163", "PF00326"]
    for pepdom in peptidase_domains:
        if pepdom in domain_counts:
            columns.append(domain_counts[pepdom])
        else:
            columns.append(0)
    #Number of each ABC transporter Pfam hit (PF00005/PF00664)
    transp_domains = ["PF00005", "PF00664"]
    for transp_dom in transp_domains:
        if transp_dom in domain_counts:
            columns.append(domain_counts[transp_dom])
        else:
            columns.append(0)
    #Number of each response regulator Pfam hit (PF00072)
    if "PF00072" in domain_counts:
        columns.append(domain_counts["PF00072"])
    else:
        columns.append(0)
    #Number of each major facilitator Pfam hit (PF07690)
    if "PF07690" in domain_counts:
        columns.append(domain_counts["PF07690"])
    else:
        columns.append(0)
    #Number of each ATPase Pfam hit (PF02518/PF13304)
    atpase_domains = ["PF02518", "PF13304"]
    for atpase_dom in atpase_domains:
        if atpase_dom in domain_counts:
            columns.append(domain_counts[atpase_dom])
        else:
            columns.append(0)
    #Number of each Fer4_12 Pfam hit (PF13353)
    if "PF13353" in domain_counts:
        columns.append(domain_counts["PF13353"])
    else:
        columns.append(0)
    #Number of each rSAM Pfam hit (PF04055)
    if "PF04055" in domain_counts:
        columns.append(domain_counts["PF04055"])
    else:
        columns.append(0)
    #Length of rSAM including PqqD domain
    if len(hitends) == 0:
        columns.append(0)
    else:
        columns.append(max(hitends))
    return columns

def run_rodeo_svm(csv_columns):
    """Run RODEO SVM"""
    input_training_file = utils.get_full_path(__file__, 'svm_sacti' + 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('Precursor Index,classification,Distance,Within 500 nt?,Within 150 nt?,Further than 1000 nt?,Core has 2 or 4 Cys?,Leader longer than core?,Plausible sacti ring?,Leader has GxxxxxT motif?,Core starts with G?,Core and BGC in same direction?,Raito leader/core < 2 and > 0.5,Core starts with Cys and even number of Cys?,No Gly in core?,Core has at least 1 aromatic aa?,Core has at least 2 aromatic aa?,Core has odd number of Cys?,Leader has Trp?,Leader has Lys?,Leader has Cys?,Cluster has PF00733?,Cluster has PF05402?,Cluster has PF13471?,Leader has LxxxxxT motif?,Core has adjacent identical aas (doubles)?,Core length (aa),Leader length (aa),Precursor length (aa),Leader/core ratio,Number of Pro in first 9 aa of core?,Estimated core charge,Estimated leader charge,Estimated precursor charge,Absolute value of core charge,Absolute value of leader charge,Absolute value of precursor charge,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,Motif1?,Motif2?,Motif3?,Motif4?,Motif5?,Motif6?,Motif7?,Motif8?,Motif9?,Motif10?,Motif11?,Motif12?,Motif13?,Motif14?,Motif15?,Motif16?,Total motifs hit,"Score, Motif1","Score, Motif2","Score, Motif3","Score, Motif4","Score, Motif5","Score, Motif6","Score, Motif7","Score, Motif8","Score, Motif9","Score, Motif10","Score, Motif11","Score, Motif12","Score, Motif13","Score, Motif14","Score, Motif15","Score, Motif16",Sum of MEME scores,No Motifs?,Alternate Start Codon?\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, domain_counts):
    """Run RODEO heuristics + SVM to assess precursor peptide candidate"""
    rodeo_score = 0
    
    #Incorporate heuristic scores
    heuristic_score, gathered_tabs_for_csv, hitends = 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, hitends, domains, domain_counts)
    rodeo_score +=  run_rodeo_svm(csv_columns)

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


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

    #Skip sequences with >100 AA
    if len(query_sequence) > 100 or len(query_sequence) < 20:
        return

    #Create FASTA sequence for feature under study
    sacti_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(sacti_a_fasta, 'precursor_2637.hmm', 1.00)

    #If no pHMM hit, try regular expression
    #if score is None:
    #    start, end, score = run_cleavage_site_regex(sacti_a_fasta)
    #        if score is None:
    start, end, score = 0, int(len(query_sequence)*0.25), 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, domain_counts)
    if rodeo_result[0] is False:
        #logging.debug('%r: No cleavage site predicted' % utils.get_gene_id(query))
        return
    else:
        sacti_peptide = Sactipeptide(start, end + 1, score, rodeo_result[1])

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

    return sacti_peptide


def run_sactipred(seq_record, cluster, query, domains, domain_counts):
    """General function to predict and analyse sacti peptides"""

    query_sequence = utils.get_aa_sequence(query, to_stop=True)

    #Run checks to determine whether an ORF encodes a precursor peptide
    result = determine_precursor_peptide_candidate(seq_record, cluster, query, query_sequence, domains, domain_counts)
    if result is None:
        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('sactipeptide')
    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('predicted core seq: %s' % res_vec.core)
    core_feature.qualifiers['note'].append('sactipeptide')
    core_feature.qualifiers['note'].append('RODEO score: %i' % res_vec.rodeo_score)
    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 \
           'sactipeptide' not in cluster.qualifiers['product'][0]:
            continue
        
        #Find candidate ORFs that are not yet annotated
        new_orfs = find_all_orfs(seq_record, cluster)
        
        #Get all CDS features to evaluate for RiPP-likeness
        sacti_fs = utils.get_cluster_cds_features(cluster, seq_record) + new_orfs
        domains, domain_counts = get_detected_domains(seq_record, cluster)
      
        #Evaluate each candidate precursor peptide
        for sacti_f in sacti_fs:
            result_vec = run_sactipred(seq_record, cluster, sacti_f, domains, domain_counts)
            if result_vec is None:
                continue
 
            new_features = result_vec_to_features(sacti_f, result_vec)
            if "allorf" in utils.get_gene_id(sacti_f):
                new_features.append(sacti_f)
            seq_record.features.extend(new_features)
