import re

def best_hit_hmmer3(fname, alg, dbtype):
    """
    parser hmmer result file, return best hits. The best hit is the hit with the highest score for a specific
    query_accession. If two hits have the same score, the one with the lower bias is selected. If score and bias are
    equal, an error is raised.

    :param fname: path to hmmer result file
    :param alg: hmmsearch or hmmscan
    :param dbtype: database type. Based on the type, different columns of the result file are used as best hit.
        For hmmsearch, query_accessions are read from column 0, hits from column 3 (exception dbcan: column 2)
        For hmmscan, query_accessions are read from column 2, hits from column 1 (exception dbcan: column 0)
    :return: dict(query_accession: best_hit)
    """
    is_dbcan = (dbtype == 'dbcan')
    with open(fname, "r") as handle:
        if alg == "hmmsearch":
            col_acc = 0
            if is_dbcan:
                col_hit = 2
            else:
                col_hit = 3
        elif alg == "hmmscan":
            col_acc = 2
            if is_dbcan:
                col_hit = 0
            else:
                col_hit = 1
        col_score = 5
        col_bias = 6

        best_hits = {}
        for line in handle:
            if len(line.strip()) == 0 or line[0] == "#":
                continue
            fields = list(filter(None, line.split(" ")))
            acc = fields[col_acc].split(",")[0]
            score = fields[col_score]
            bias = fields[col_bias]
            if is_dbcan:
                hit = fields[col_hit].split(".hmm")[0]
            else:
                hit = fields[col_hit]

            if col_acc not in best_hits:
                best_hits[acc] = (hit, score, bias)
            elif score > best_hits[acc][1]:
                best_hits[acc] = (hit, score, bias)
            elif score == best_hits[acc][1] and bias < best_hits[acc][2]:
                best_hits[acc] = (hit, score, bias)
            elif score == best_hits[acc][1] and bias == best_hits[acc][2]:
                trace("error: equal best hits not yet considered in best_hit_hmmer3")

        for acc in best_hits:
            best_hits[acc] = best_hits[acc][0]

        return best_hits


def best_hit_blastp(fname, min_full_ident, db_obj):
    db_specific_acc_re_pattern = db_obj.get_acc_hit_regex_pattern()
    acc_pattern = re.compile("(?:^|;)" + db_specific_acc_re_pattern)
    with open(fname, "r") as handle:
        best_hits = {}
        for line in handle:
            fields = line.strip().split("\t")
            if len(fields) == 0:
                continue
            qacc = fields[0]
            hits = fields[1]
            qlen = int(fields[2])
            bitscore = float(fields[4])
            algnlen = int(fields[5])
            nident = int(fields[6])
            full_ident = algnlen / qlen * nident
            if full_ident >= min_full_ident:
                if qacc not in best_hits:
                    best_hits[qacc] = (hits, bitscore, full_ident)
                elif bitscore > best_hits[qacc][1]:
                    best_hits[qacc] = (hits, bitscore, full_ident)
                elif bitscore == best_hits[qacc][1] and full_ident > best_hits[qacc][2]:
                    best_hits[qacc] = (hits, bitscore, full_ident)
                elif bitscore == best_hits[qacc][1] and full_ident == best_hits[qacc][2]:
                    best_hits[qacc] = (best_hits[qacc][0] + ";" + hits, bitscore, full_ident)

        for acc in best_hits:
            hits = best_hits[acc][0]
            best_hits[acc] = []
            for match in  acc_pattern.finditer(hits):
                best_hits[acc].append(match.group(1))

        return best_hits


def best_hit_emapper(fname, db_type):
    with open(fname, "r") as handle:
        best_hits = {}
        for line in handle:
            line = line.strip()
            if len(line) == 0 or line[0] == "#":
                continue
            fields = line.split("\t")
            # fields[0] = query, fields[1] = seed_ortholog, bestOG|evalue|score removed, takes always most specified
            if db_type != 'eggnog_v5':
                best_hits[fields[0]] = fields[10].split("|")[0] + "." + fields[1]
    return best_hits
