/*
 * blastParser.js
 * http://tasuke.dna.affrc.go.jp/
 *
 * Author: Norio Tabei
 *
 * Copyright (c) 2020 National Institute of Agrobiological Sciences   
 *
 */

///////////////////////////////////////////////////////////
// Run BLAST
///////////////////////////////////////////////////////////
function parseBlastPairwise(str) {
	return parseBlastPairwiseByArray(str.split(/\n/));
}
function parseBlastPairwiseByArray(response) {
	var hits = {};
	var sortEval = [];
	if ( !(response instanceof Array) ) return [hits, sortEval];

	var qnameSerial = 0;	// If there are multiple unnamed Sequences, serialize "No_definition_line"
	var qname = null;
	var chr = null;
	var hsp_num = 0;
	var bscore = null;
	var eval = null;
	var identity = null;
	var gaps = null;
	var alignQuery = "";
	var alignMatch = "";
	var alignSbjct = "";
	var q_from = null;	// Substitute only first
	var q_to = null;	// Always overwrite
	var s_from = null;	// Substitute only first
	var s_to = null;	// Always overwrite
	// Control variable 
	var flgMatchLine = false;
	var alignOffset = null;
	var alignOnerowLen = null;
	var setHsp = function() {
		hsp_num++;
		if( !(hsp_num in hits[qname][chr]) ) hits[qname][chr][hsp_num] = {};

		hits[qname][chr][hsp_num]['bitscr'] = bscore;
		hits[qname][chr][hsp_num]['eval'] = eval;
		hits[qname][chr][hsp_num]['identity'] = identity;
		hits[qname][chr][hsp_num]['gaps'] = gaps;
		hits[qname][chr][hsp_num]['qseq'] = alignQuery;
		hits[qname][chr][hsp_num]['midline']  = alignMatch;
		hits[qname][chr][hsp_num]['sseq'] = alignSbjct;
		hits[qname][chr][hsp_num]['qfrom'] = q_from;
		hits[qname][chr][hsp_num]['qto'] = q_to;
		hits[qname][chr][hsp_num]['sfrom'] = s_from;
		hits[qname][chr][hsp_num]['sto'] = s_to;
		hits[qname][chr][hsp_num]['alignlen'] = alignQuery.length;
		hits[qname][chr][hsp_num]['qstrand'] = getBlastStrand(parseInt(q_from), parseInt(q_to));
		hits[qname][chr][hsp_num]['sstrand'] = getBlastStrand(parseInt(s_from), parseInt(s_to));
		var q_len = Math.abs(q_to) - parseInt(q_from) + 1;
		var q_cover = q_len / hits[qname]['qlen'] * 100;
		hits[qname][chr][hsp_num]['qcover'] = q_cover;

		sortEval.push({'name':qname + "---SEv---" + chr + "---SEv---" + hsp_num, 'evalue':eval});
//console.log(qname + ", " + chr + ", " + hsp_num + ", " + alignQuery.length + ", " + eval + ", " + identity + ", " + gaps + ", " + q_from + "-" + q_to + ", " + s_from + "-" + s_to);
//console.log(alignQuery);
//console.log(alignMatch);
//console.log(alignSbjct);
	}
	var initHsp = function() {
		alignOffset = null;
		alignOnerowLen = null;
		alignQuery = "";
		alignMatch = "";
		alignSbjct = "";
		q_from = null;
		s_from = null;
		eval = null;
		bscore = null;
		identity = null;
		gaps = null;
	}
	for (var i=0, responseLen=response.length; i<responseLen; i++) {
		var bar;	// Array
		if(flgMatchLine) {
			var matchStr = response[i].substr(alignOffset);
			var matchStrLen = matchStr.length;
			if(matchStrLen < alignOnerowLen) matchStr += " ".repeat(alignOnerowLen - matchStrLen);
			alignMatch += matchStr;
			flgMatchLine = false;
		} else if( (bar = response[i].match(/^Query=\s*(\S*)/i)) ) {
			// set Previus HSP
			if(bscore) setHsp();

			// Initialize next HSP
			initHsp();
			qname = bar[1];
			if(!qname) {
				qname = 'No_definition_line';
				qnameSerial++;
				if(qnameSerial >= 2) qname += String(qnameSerial); 
			}
			if( !(qname in hits) ) hits[qname] = {};
			chr = null;
		} else if( !chr && (bar = response[i].match(/^Length=\s*(\d+)$/i)) ) {
			hits[qname]['qlen'] = bar[1];
		} else if( (bar = response[i].match(/^>(\S+)$/)) ) {
			// set Previus HSP
			if(bscore) setHsp();

			// Initialize next HSP
			hsp_num = 0;
			initHsp();
			chr = bar[1];
			if( !(chr in hits[qname]) ) hits[qname][chr] = {};
		} else if( (bar = response[i].match(/^\s*Score\s*=\s*(\S+).+Expect\s*=\s*(\S+)/i)) ) {
			// set Previus HSP
			if(bscore) setHsp();

			// Initialize next HSP
			initHsp();
			eval = bar[2];
			bscore = bar[1];
		} else if( /^\s*Identities/.test(response[i]) ) {
			if( (bar = response[i].match(/Identities\s*=\s*(\d+)/i)) ) identity = bar[1];
			if( (bar = response[i].match(/Gaps\s*=\s*(\d+)/i)) ) gaps = bar[1];
		} else if( (bar = response[i].match(/^(Query\s+(\d+)\s+)(\S+)\s+(\d+)$/i)) ) {
			alignQuery += bar[3];
			if(!alignOffset) {
				alignOffset = bar[1].length;
				alignOnerowLen = bar[3].length;
			}
			if(!q_from) q_from = bar[2];
			q_to = bar[4];

			flgMatchLine = true;
		} else if( (bar = response[i].match(/^Sbjct\s+(\d+)\s+(\S+)\s+(\d+)$/i)) ) {
			alignSbjct += bar[2];
			if(!s_from) s_from = bar[1];
			s_to = bar[3];
		}
	}
	if(bscore) setHsp();
	return [hits, sortEval];
}
function parseBlastXml(str){
	return parseBlastXmlByArray(str.split(/\n/));
}
function parseBlastXmlByArray(response){
//	console.log(response);
	var hits = {};
	var sortEval = [];
	if ( !(response instanceof Array) ) return [hits, sortEval];
//                var errMsg;
                var qname;
                var chr;
                var hsp_num;
                for (var i=0, responseLen=response.length; i<responseLen; i++) {
                        if(/<Iteration_message/.test(response[i])) {
//                                errMsg="<font size=\"4\" color=\"red\"><b>No hits found</b></font><br><br>";
                        }
                        if(/<Iteration_query-def/.test(response[i])) {
                                qname=response[i].replace(/\<Iteration_query-def\>/,"");
                                qname=qname.replace(/\<\/Iteration_query-def\>/,"");
                                qname = qname.trim();
                                if(qname in hits) {
                                } else {
                                        hits[qname] = {};
                                }
                        }
			if(/<Iteration_query-len/.test(response[i])) {
				qlen=response[i].replace(/\<Iteration_query-len\>/,"");
				qlen=qlen.replace(/\<\/Iteration_query-len\>/,"");
				qlen = qlen.trim();
				hits[qname]['qlen'] = parseInt(qlen);
			}
                        if(qname in hits) {
                        } else {
                                hits[qname] = {};
                        }
                        if(/<Hit_id/.test(response[i])) {
                                chr=response[i].replace(/\<Hit_id\>/,"");
                                chr=chr.replace(/\<\/Hit_id\>/,"");
                                chr=chr.trim();
                                if(chr in hits[qname]) {
                                } else {
                                        hits[qname][chr] = {};
                                }
                        }
/*
                        if(/<Hit_def/.test(response[i])) {
                                chr=response[i].replace(/\<Hit_def\>/,"");
                                chr=chr.replace(/\<\/Hit_def\>/,"");
                                chr=chr.trim();
                                if(chr in hits[qname]) {
                                } else {
                                        hits[qname][chr] = {};
                                }
                        }
*/
                        if(/<Hsp_num/.test(response[i])) {
                                hsp_num=response[i].replace(/\<Hsp_num\>/,"");
                                hsp_num=hsp_num.replace(/\<\/Hsp_num\>/,"");
                                hsp_num=hsp_num.trim();
                                if(hsp_num in hits[qname][chr]) {
                                } else {
                                        hits[qname][chr][hsp_num] = {};
                                }
                        }
                        if(/<Hsp_bit-score/.test(response[i])) {
                                bitscr=response[i].replace(/\<Hsp_bit-score\>/,"");
                                bitscr=bitscr.replace(/\<\/Hsp_bit-score\>/,"");
                                bitscr=bitscr.trim();
                                hits[qname][chr][hsp_num]['bitscr']=bitscr;
                        }
                        if(/<Hsp_evalue/.test(response[i])) {
                                eval=response[i].replace(/\<Hsp_evalue\>/,"");
                                eval=eval.replace(/\<\/Hsp_evalue\>/,"");
                                eval=eval.trim();
                                hits[qname][chr][hsp_num]['eval']=eval;
				sortEval.push({'name':qname+"---SEv---"+chr+"---SEv---"+hsp_num,'evalue':eval});
				//evalue[qname+":"+chr+":"+hsp_num]=eval;
                        }
                        if(/<Hsp_query-from/.test(response[i])) {
                                q_from=response[i].replace(/\<Hsp_query-from\>/,"");
                                q_from=q_from.replace(/\<\/Hsp_query-from\>/,"");
                                q_from=q_from.trim();
                                hits[qname][chr][hsp_num]['qfrom']=q_from;
                        }
                        if(/<Hsp_query-to/.test(response[i])) {
                                q_to=response[i].replace(/\<Hsp_query-to\>/,"");
                                q_to=q_to.replace(/\<\/Hsp_query-to\>/,"");
                                q_to=q_to.trim();
                                hits[qname][chr][hsp_num]['qto']=q_to;
				hits[qname][chr][hsp_num]['qstrand'] = getBlastStrand(parseInt(hits[qname][chr][hsp_num]['qfrom']), parseInt(hits[qname][chr][hsp_num]['qto']));
/*
				if(parseInt(hits[qname][chr][hsp_num]['qfrom']) < parseInt(hits[qname][chr][hsp_num]['qto'])) {
					hits[qname][chr][hsp_num]['qstrand'] = "+";
				} else if(parseInt(hits[qname][chr][hsp_num]['qfrom']) > parseInt(hits[qname][chr][hsp_num]['qto'])) {
					hits[qname][chr][hsp_num]['qstrand'] = "-";
				}
*/
				q_len = Math.abs(parseInt(hits[qname][chr][hsp_num]['qto']) - parseInt(hits[qname][chr][hsp_num]['qfrom'])) + 1;
				q_cover = q_len / hits[qname]['qlen'] * 100;
				hits[qname][chr][hsp_num]['qcover'] = q_cover;
                        }
                        if(/<Hsp_hit-from/.test(response[i])) {
                                s_from=response[i].replace(/\<Hsp_hit-from\>/,"");
                                s_from=s_from.replace(/\<\/Hsp_hit-from\>/,"");
                                s_from=s_from.trim();
                                hits[qname][chr][hsp_num]['sfrom']=s_from;
                        }
                        if(/<Hsp_hit-to/.test(response[i])) {
                                s_to=response[i].replace(/\<Hsp_hit-to\>/,"");
                                s_to=s_to.replace(/\<\/Hsp_hit-to\>/,"");
                                s_to=s_to.trim();
                                hits[qname][chr][hsp_num]['sto']=s_to;
				hits[qname][chr][hsp_num]['sstrand'] = getBlastStrand(parseInt(hits[qname][chr][hsp_num]['sfrom']), parseInt(hits[qname][chr][hsp_num]['sto']));
/*
				if(parseInt(hits[qname][chr][hsp_num]['sfrom']) < parseInt(hits[qname][chr][hsp_num]['sto'])) {
                                        hits[qname][chr][hsp_num]['sstrand'] = "+";
                                } else if(parseInt(hits[qname][chr][hsp_num]['sfrom']) > parseInt(hits[qname][chr][hsp_num]['sto'])) {
                                        hits[qname][chr][hsp_num]['sstrand'] = "-";
                                }
*/
			}
                        if(/<Hsp_align-len/.test(response[i])) {
                                alignlen=response[i].replace(/\<Hsp_align-len\>/,"");
                                alignlen=alignlen.replace(/\<\/Hsp_align-len\>/,"");
                                hits[qname][chr][hsp_num]['alignlen']=alignlen;
                        }
                        if(/<Hsp_identity/.test(response[i])) {
                                identity=response[i].replace(/\<Hsp_identity\>/,"");
                                identity=identity.replace(/\<\/Hsp_identity\>/,"");
                                hits[qname][chr][hsp_num]['identity']=identity;
                        }
                        if(/<Hsp_gaps/.test(response[i])) {
                                gaps=response[i].replace(/\<Hsp_gaps\>/,"");
                                gaps=gaps.replace(/\<\/Hsp_gaps\>/,"");
                                hits[qname][chr][hsp_num]['gaps']=gaps;
                        }
                        if(/<Hsp_qseq/.test(response[i])) {
                                qSeq=response[i].replace(/\<Hsp_qseq\>/,"");
                                qSeq=qSeq.replace(/\<\/Hsp_qseq\>/,"");
                                qSeq = qSeq.trim();
                                hits[qname][chr][hsp_num]['qseq']=qSeq;
                        }
                        if(/<Hsp_hseq/.test(response[i])) {
                                sSeq=response[i].replace(/\<Hsp_hseq\>/,"");
                                sSeq=sSeq.replace(/\<\/Hsp_hseq\>/,"");
                                sSeq = sSeq.trim();
                                hits[qname][chr][hsp_num]['sseq']=sSeq;
                        }
                        if(/<Hsp_midline/.test(response[i])) {
                                midline=response[i].replace(/\<Hsp_midline\>/,"");
                                midline=midline.replace(/\<\/Hsp_midline\>/,"");
                                midline = midline.trim();
                                hits[qname][chr][hsp_num]['midline']=midline;
                        }
                }
	return [hits, sortEval];
}
function getBlastStrand(from, to) {
	if(from <= to) {
		return "+";
	} else {
		return "-";
	}
}
