#!/usr/bin/env python3
'''
AUTHOR: Advait Balaji
DATE: 07/07/2020

DESC: This script takes in the centrifuge and diamond outputs 
and produces a tab separated file of form as required by seqscreen

#query taxid source confidence

consolidatedtax_2_taxreport_fast.py --diamond=/Path/to/diamondoutput.txt --centrifuge=/Path/to/centrifugeoutput
                                     --out=/Path/to/output.txt  --cutoff=1 [--help] 

'''

'''
imports
'''
import argparse
import sys
import csv
import numpy as np



top_hits = {} #top hit in diamond
results = {} #final results dict
source = {} #source of classifiation centrifuge or diamond
confidence = {} #confidence score for each protein
centrifuge_results_dict = {} #store centrifuge results
combined_dict = {} #combined results

def usage():
    print(f"consolidatedtax_2_taxreport_fast.py \
        --diamond=/Path/to/diamondoutput.txt \
        --centrifuge=/Path/to/centrifugeoutput \
        --out=/Path/to/output.txt \
        --cutoff=1 [--help] ")

'''
processes the centrifuge output file
    @params: centrifuge_f -> centrifuge output file (string)
'''

def processCentrifuge(centrifuge_f):
    with open(centrifuge_f,'r') as inpf:
        inpf_reader = csv.DictReader(inpf,delimiter="\t")
        for row in inpf_reader:
            query = base_query(row["readID"]) #read classified
            taxid = row["taxID"] #tax id
            score = row["score"] #centrifuge score
            seqid = row["seqID"] #seqid for hit in DB
            hit_length = row["hitLength"]
            query_length = row["queryLength"]
            if taxid != "0":
                centrifuge_conf = int(hit_length)/int(query_length) #get confidence score
                if query in centrifuge_results_dict:
                    if centrifuge_results_dict[query][1][0] < score: #found a better match update
                        centrifuge_results_dict[query] = ([taxid],[score],[str(centrifuge_conf)],["centrifuge"])
                    elif centrifuge_results_dict[query][1][0] == score: #equally good match add to multi-tax
                        centrifuge_results_dict[query][0].append(taxid)
                        centrifuge_results_dict[query][1].append(score)
                        centrifuge_results_dict[query][2].append(str(centrifuge_conf))
                        centrifuge_results_dict[query][3].append("centrifuge")
                    else:
                        continue #don't take a lower score
                else:
                    centrifuge_results_dict[query] = ([taxid],[score],[str(centrifuge_conf)],["centrifuge"])

    for k,v in centrifuge_results_dict.items(): 
        results[k]  =  (v[0],v[1])
        source[k] = (v[0],v[3])
        confidence[k] = (v[0],v[2]) #assign confidence for centrifuge



'''
processes the diamond output file
    @params: diamond_f -> diamond output file (string)
    @params: cutoff -> cutoff value to consider (int)
'''
def processDiamond(diamond_f,cutoff):
    global combined_dict
    combined_dict = results
    with open(diamond_f,'r') as inpf:
        for line in inpf:
            tokens = line.split('\t')
            query = base_query(tokens[0])
            max_bit_score = float(tokens[7])
            bitscore = float(tokens[11])
            taxid = tokens[15].split("ID=")[1].split(" ")[0]
            bitscore_cutoff = cutoff * top_hits[query]
            if bitscore > bitscore_cutoff:
                if query in results:
                    if int(results[query][1][0]) < bitscore:
                        results[query][0].append(taxid)
                        results[query][1].append(bitscore)
                        #results[query] = (results[query][0], [bitscore])
                        #results[query][1] = bitscore # can't assign bitscore directly, need to do the above modification!
                        source[query][0].append(taxid)
                        source[query][1].append("diamond")
                        confidence[query][0].append(taxid)
                        confidence[query][1].append(str(float(tokens[2])/100))
                    combined_dict[query][0].append(taxid)
                    combined_dict[query][1].append(bitscore)
                else:
                    source[query] = ([taxid],["diamond"])
                    results[query] = ([taxid],[bitscore])    
                    confidence[query] = ([taxid],[str(float(tokens[2])/100)])
                    combined_dict[query] = ([taxid],[bitscore]) 


'''
finds the top hits in diamond output file and stores  in top_hit
    @params: diamond_f -> diamond output file (string)
'''

def findTopHitsDiamond(diamond_f):
    with open(diamond_f,'r') as inpf:
        for line in inpf:
            tokens = line.split('\t')
            query = base_query(tokens[0])
            bitscore = float(tokens[11])
            if bitscore < 0:
                print(f"Bitscore was found to be less that zero, might be the wrong column")
                exit(1)
            if query in top_hits:
                if top_hits[query] < bitscore:
                    top_hits[query] = bitscore
            else:
                top_hits[query] = bitscore

'''
write the output file that can be read by the report generation workflow
    @params: out_f -> output file (string)
'''
def writeResults(out_f):
    with open(out_f,'w') as outf:
        outf.write("#query\ttaxid\tsource\tconfidence\tcentrifuge_top_priority\tcombined_taxid\n")
        for k in results:
            centrifuge_top_priority = ""
            if k in centrifuge_results_dict:
                centrifuge_top_priority = centrifuge_results_dict[k][0][0]
            else:
                centrifuge_top_priority = ",".join(results[k][0])
            outf.write(k+"\t"+",".join(results[k][0])+"\t"+",".join(source[k][1])+"\t"+",".join(confidence[k][1])+"\t"+centrifuge_top_priority+"\t"+",".join(set(combined_dict[k][0]))+"\n")

'''
fixes readID in diamond and remove __unambig__ token
    @params: readID from diamond file
'''
def base_query(id):
    return id.split("_unambig_")[0]


def main():

    if len(sys.argv) < 4 or len(sys.argv) > 5 :
        print(f"Number of arguments {len(sys.argv)-1} is incorrect")
        usage()
        exit(1)

    parser = argparse.ArgumentParser()

    parser.add_argument("-d","--diamond",type=str,required=True,help="Input: /Path/to/Diamondfile.txt")
    parser.add_argument("-c","--centrifuge",type=str,required=True,help="Input: /Path/to/Centrifugefile.txt")
    parser.add_argument("-o","--out",type=str,required=True,help="Output: /Path/to/Outputfile.txt")
    parser.add_argument("-t","--cutoff",type=int,default=1,help="Cutoff for top n bit scores from max bit score (for ex: 1,5,10...)")

    args = parser.parse_args() #save args

    cutoff = (100-args.cutoff)/100

    findTopHitsDiamond(args.diamond)
    processCentrifuge(args.centrifuge)
    processDiamond(args.diamond,cutoff)
    #processCentrifuge(args.centrifuge)
    writeResults(args.out)

if __name__  == "__main__":
    main()

