#!/usr/bin/env bash
if [[ "${LOADED_ORTHOMAPPER_BUSCO_SH+1}" == "" ]]; then
#------------------------------------------------------------------------------
# Run BUSCO part of mapping
# * estimate lineage using --auto-lineage
# * compute busco
#------------------------------------------------------------------------------
set -u

#------------------------------------------------------------------------------
# Variables that need to be set
#------------------------------------------------------------------------------
#export BUSCO_DOCKER_IMAGE=${BUSCO_DOCKER_IMAGE:="ezlabgva/busco:v5.6.1_cv1"}
export BUSCO_DOCKER_IMAGE=${BUSCO_DOCKER_IMAGE:="ezlabgva/busco:v5.4.7_cv1"}
export BUSCO_CMD=${BUSCO_CMD:=$(/usr/bin/which busco 2>/dev/null)}
export BUSCO_WD_ROOT=${BUSCO_WD_ROOT:=$(pwd)}
export BUSCO_DATA=${BUSCO_DATA:="./busco"}

# variables that has a valid default

# these are set later
export BUSCO_OUTPUT=


#------------------------------------------------------------------------------
# Variables with sensible defaults
#------------------------------------------------------------------------------
# nr of CPU's to use; if 0 it will use a conservative nr of threads (not all)
export BUSCO_NCPUS=${BUSCO_NCPUS:=0}

# min BUSCO score (completeness [0,100])
export BUSCO_MINSCORE=${BUSCO_MINSCORE:=0}

# flag to run busco offline
# this implies run fails if data does not exist in BUSCO_DATA
export BUSCO_OFFLINE=${BUSCO_OFFLINE:=0}

# enabled flag
export BUSCO_ENABLED=${BUSCO_ENABLED:=1}

# busco ODB version
export BUSCO_ODB_VERSION=${BUSCO_ODB_VERSION:="odb10"}

# BUSCO output dir in the docker environment
export BUSCO_DOCKER_WD=${BUSCO_DOCKER_WD:=/busco_wd}
declare -xr BUSCO_DOCKER_OUTPUT=output

#------------------------------------------------------------------------------
# Variables with result information
#------------------------------------------------------------------------------
# metadata to be set
export BUSCO_LINEAGE=""
export BUSCO_LINEAGE_TAXID=""
export BUSCO_INPUT_FASTA=""
export BUSCO_INPUT_LINEAGE=""

# BUSCO results
export BUSCO_RES_AUTO=0
export BUSCO_RES_FILE=""
export BUSCO_RES_STRING=""
export BUSCO_RES_COMPLETE=""
export BUSCO_RES_SINGLECOPY=""
export BUSCO_RES_MULTICOPY=""
export BUSCO_RES_FRAGMENTED=""
export BUSCO_RES_MISSING=""
export BUSCO_RES_TOTAL=""

# Fixed name for metadata file
declare -r _BUSCO_METADATA=.busco_metadata

#set_busco_dirs() {
map_busco_set_dirs() {
    BUSCO_WD_ROOT=$DIR_PROJECT/busco/$(map_cleanup_taxid "$1")
    ###BUSCO_WD="$BUSCO_WD_ROOT/$BUSCO_DOCKER_WD"
    BUSCO_OUTPUT="$BUSCO_WD_ROOT/$BUSCO_DOCKER_OUTPUT"
}

CHK_MSG=""
chk_dir() {
    CHK_MSG=""
    local dvar="$1"
    local d="${!dvar}"
    local rval=0
    if [[ -z $d ]]; then
        CHK_MSG=$(printf "%-30s %s" "$dvar" "is not set")
        rval=1
    else
        if [[ ! -d $d ]]; then
            CHK_MSG=$(printf "%-30s %s" "$dvar" "is set but not readable at $d")
            rval=1
        fi
    fi
    return $rval
}

chk_exe() {
    CHK_MSG=""
    local dvar="$1"
    local d="${!dvar}"
    local rval=0
    if [[ -z $d ]]; then
        CHK_MSG=$(printf "%-30s %s" "$dvar" "is not set")
        rval=1
    else
        if [[ ! -x $d ]]; then
            CHK_MSG=$(printf "%-30s %s" "$dvar" "is set but not executable (or readable) at $d")
            rval=1
        fi
    fi
    return $rval
}

print_busco_version() {
    local vbusco=""
    if [[ -x $BUSCO_CMD ]]; then
        vbusco="$BUSCO_CMD"
    elif [[ -n $BUSCO_DOCKER_IMAGE ]]; then
        vbusco="$BUSCO_DOCKER_IMAGE"
    fi
    printf "%-40s : %s\n" "busco" "$vbusco"
}

export BUSCO_SETUP_MSG=""
export BUSCO_SETUP_OK=0
map_busco_setup() {
    BUSCO_SETUP_MSG=""
    BUSCO_SETUP_OK=1
    (( BUSCO_ENABLED == 0 )) && {
        BUSCO_SETUP_MSG="BUSCO is disabled"
        BUSCO_SETUP_OK=0
        return 2
    }

    # make run user taxid is defined
    [[ -n "$_RUN_SINGLE_TAXID" ]] || {
        BUSCO_SETUP_MSG="BUSCO setup failed - user taxid id not set ( parse_run_args() not called? )\n"
        BUSCO_SETUP_OK=1
        return 255
    }

    # set directories related to this taxid
    map_info_msg "setup BUSCO with taxid = $_RUN_SINGLE_TAXID"
    map_busco_set_dirs "$_RUN_SINGLE_TAXID" || { BUSCO_SETUP_MSG="failed setting BUSCO dirs"; BUSCO_SETUP_OK=1; return 255; }

    mkdir -p $BUSCO_WD_ROOT || { BUSCO_SETUP_OK=0; BUSCO_SETUP_MSG="${BUSCO_SETUP_MSG}failed to create BUSCO_WD_ROOT at $BUSCO_WD_ROOT\n"; return 255; }
    mkdir -p $BUSCO_DATA    || { BUSCO_SETUP_OK=0; BUSCO_SETUP_MSG="${BUSCO_SETUP_MSG}failed to create BUSCO_DATA at $BUSCO_DATA\n"; return 255; }
    ###mkdir -p $BUSCO_WD      || { BUSCO_SETUP_OK=0; BUSCO_SETUP_MSG="${BUSCO_SETUP_MSG}failed to create BUSCO_WD at $BUSCO_WD\n";           return 255; }
    
    # give priority to BUSCO_CMD - local install
    [[ -n $BUSCO_CMD ]] && BUSCO_DOCKER_IMAGE=""
    
    # check directories
    local dir_setup_ok=1
    chk_dir BUSCO_WD_ROOT || { dir_setup_ok=0; BUSCO_SETUP_MSG="${BUSCO_SETUP_MSG}${CHK_MSG}\n"; }
    ###chk_dir BUSCO_WD      || { dir_setup_ok=0; BUSCO_SETUP_MSG="${BUSCO_SETUP_MSG}${CHK_MSG}\n"; }
    chk_dir BUSCO_DATA    || { dir_setup_ok=0; BUSCO_SETUP_MSG="${BUSCO_SETUP_MSG}${CHK_MSG}\n"; }
    if chk_exe BUSCO_CMD; then
        BUSCO_SETUP_OK=$dir_setup_ok
    else
        # if no busco command, check if docker image is available
        CHK_MSG=""
        if (( dir_setup_ok == 1 )); then
            if [[ -n $BUSCO_DOCKER_IMAGE ]]; then
                if /usr/bin/which docker >& /dev/null; then
                    local d=""
                    d=$(docker images -q "$BUSCO_DOCKER_IMAGE")
                    if [[ $? -eq 0 ]]; then
                        if [[ -n "$d"  ]]; then
                            # docker image exists -> OK!
                            BUSCO_SETUP_OK=1
                            BUSCO_CMD=""
                        else
                            CHK_MSG=$(printf "%-30s %s" "BUSCO_DOCKER_IMAGE" "does not exist locally")
                        fi
                    else
                        CHK_MSG=$(printf "%-30s %s" "docker service" "is not running")
                    fi
                    
                else
                    CHK_MSG=$(printf "%-30s %s" "docker" "is not installed")
                fi
            else
                CHK_MSG=$(printf "%-30s %s" "BUSCO_DOCKER_IMAGE" "is not set")
            fi
        fi
    fi
    if (( BUSCO_SETUP_OK == 0 )); then
        if (( dir_setup_ok == 1 )); then
            CHK_MSG=$(printf "%-30s %s" "busco" "neither local install nor docker image exists")
        fi
    else
        # ALL ok!!
        return 0
    fi
    [[ -z $CHK_MSG ]] || BUSCO_SETUP_MSG="${BUSCO_SETUP_MSG}${CHK_MSG}\n"
    return 255
}

map_busco_load_levels_map() {
    if [[ -d $BUSCO_DATA ]]; then
        if [[ ! -f $BUSCO_DATA/levels2busco_${BUSCO_ODB_VERSION}.json ]]; then
            curl --silent $URL_ORTHODB_API/download/mapping/levels2busco_${BUSCO_ODB_VERSION}.json.gz -o $BUSCO_DATA/levels2busco_${BUSCO_ODB_VERSION}.json.gz || {
                echo "error: failed downloading from $URL_ORTHODB_API/download/mapping/levels2busco_${BUSCO_ODB_VERSION}.json.gz"
                return 255
            }
	    gunzip $BUSCO_DATA/levels2busco_${BUSCO_ODB_VERSION}.json.gz || return 255
            echo "installed levels2busco_${BUSCO_ODB_VERSION}.json"
        fi
    else
        echo "error: unable to install levels2busco_${BUSCO_ODB_VERSION}.json in BUSCO_DATA"
        if [[ -n $BUSCO_DATA ]]; then
            echo "error: BUSCO_DATA set but not readable/writeable"
            echo "error: BUSCO_DATA = $BUSCO_DATA"
        else
            echo "error: BUSCO_DATA not set"
        fi
        return 255
    fi
    return 0
}

#------------------------------------------------------------------------------
# Save and read metadata
#------------------------------------------------------------------------------
map_busco_save_metadata() {
    [[ -d $BUSCO_WD_ROOT ]] || {
        map_error_msg "unable to save busco metadata - missing dir $BUSCO_WD_ROOT"
        return 255
    }
    local fout="$BUSCO_WD_ROOT/$_BUSCO_METADATA"
    local fout_auto=""
    if [[ -z $BUSCO_LINEAGE_TAXID ]]; then
        fout="${fout}_auto"
        BUSCO_RES_AUTO=1
    else
        fout_auto="${fout}_auto"
        fout="${fout}_$BUSCO_LINEAGE_TAXID"
        # do not set BUSCO_RES_AUTO=0 here
    fi
    map_info_msg "save metadata to $fout"
    {
        echo "BUSCO_ODB_VERSION=\"$BUSCO_ODB_VERSION\""
        echo "BUSCO_LINEAGE=\"$BUSCO_LINEAGE\""
        echo "BUSCO_LINEAGE_TAXID=\"$BUSCO_LINEAGE_TAXID\""
        echo "BUSCO_INPUT_FASTA=\"$BUSCO_INPUT_FASTA\""
        echo "BUSCO_INPUT_LINEAGE=\"$BUSCO_INPUT_LINEAGE\""
        echo "BUSCO_RES_AUTO=\"$BUSCO_RES_AUTO\""
        echo "BUSCO_RES_FILE=\"$BUSCO_RES_FILE\""
        echo "BUSCO_RES_STRING=\"$BUSCO_RES_STRING\""
        echo "BUSCO_RES_COMPLETE=\"$BUSCO_RES_COMPLETE\""
        echo "BUSCO_RES_SINGLECOPY=\"$BUSCO_RES_SINGLECOPY\""
        echo "BUSCO_RES_MULTICOPY=\"$BUSCO_RES_MULTICOPY\""
        echo "BUSCO_RES_FRAGMENTED=\"$BUSCO_RES_FRAGMENTED\""
        echo "BUSCO_RES_MISSING=\"$BUSCO_RES_MISSING\""
        echo "BUSCO_RES_TOTAL=\"$BUSCO_RES_TOTAL\""

    } > "$fout"
    
    # if it was originally auto lineage, copy result to 'auto' metadata
    if [[ -n $fout_auto ]]; then
        if (( BUSCO_RES_AUTO == 1 )); then
            map_info_msg "copy metadata to $fout_auto"
            {
                echo "# copy from $(basename fout)"
                cat "$fout"
            } > $fout_auto
        fi
    fi
    return $?
}

map_busco_reset_metadata() {
    ###BUSCO_ODB_VERSION=""
    BUSCO_LINEAGE=""
    BUSCO_LINEAGE_TAXID=""
    BUSCO_INPUT_FASTA=""
    BUSCO_INPUT_LINEAGE=""
    
    # BUSCO results
    # DO NOT RESET THIS - ALWAYS SET ONCE
    ### ONE BUSCO_RES_AUTO=
    BUSCO_RES_FILE=""
    BUSCO_RES_STRING=""
    BUSCO_RES_COMPLETE=""
    BUSCO_RES_SINGLECOPY=""
    BUSCO_RES_MULTICOPY=""
    BUSCO_RES_FRAGMENTED=""
    BUSCO_RES_MISSING=""
    BUSCO_RES_TOTAL=""
}

map_busco_set_if_empty() {
    BUSCO_RES_STRING=${BUSCO_RES_STRING:="<empty>"}
    BUSCO_RES_COMPLETE=${BUSCO_RES_COMPLETE:="0"}
    BUSCO_RES_SINGLECOPY=${BUSCO_RES_SINGLECOPY:="0"}
    BUSCO_RES_MULTICOPY=${BUSCO_RES_MULTICOPY:="0"}
    BUSCO_RES_FRAGMENTED=${BUSCO_RES_FRAGMENTED:="0"}
    BUSCO_RES_MISSING=${BUSCO_RES_MISSING:="0"}
    BUSCO_RES_TOTAL=${BUSCO_RES_TOTAL:="-1"}
}

#------------------------------------------------------------------------------
# get BUSCO metadata file
# 0 -> file(s) found, stored in FILES_BUSCO_METADATA array
# The first element in array is the one with the best COMPLETE score (percentage)
#------------------------------------------------------------------------------
FILES_BUSCO_METADATA=()
map_busco_get_metadata() {
    [[ -d $BUSCO_WD_ROOT ]] || { map_error_msg "no BUSCO working directory found (BUSCO_WD_ROOT=$BUSCO_WD_ROOT)"; return 255; }
    FILES_BUSCO_METADATA=()
    local nodeid=""
    [[ $# -eq 1 ]] && nodeid="$1"
    if [[ -n $nodeid ]]; then
        FILES_BUSCO_METADATA=( $(find $BUSCO_WD_ROOT -mindepth 1 -maxdepth 1 -name "${_BUSCO_METADATA}_${nodeid}") )
    else
        FILES_BUSCO_METADATA=( $(find $BUSCO_WD_ROOT -mindepth 1 -maxdepth 1 -name "${_BUSCO_METADATA}*") )
        local sbest=-1
        local fbest=-1
        local ibest=-1
        local i=0
        # find the best result
        for f in ${FILES_BUSCO_METADATA[@]}; do
            # ignore files where lineage taxid is not set
            eval "x$(grep -h BUSCO_LINEAGE_TAXID= $f)"
            [[ -n $xBUSCO_LINEAGE_TAXID ]] || continue

            # evaluate busco result and select the best one
            eval "x$(grep -h BUSCO_RES_COMPLETE= $f)"
            i=$((i+1))
            [[ -n $xBUSCO_RES_COMPLETE ]] || continue
            if [[ $(echo "$xBUSCO_RES_COMPLETE > $sbest" | bc -l) == "1" ]]; then
                fbest="$f"
                sbest="$xBUSCO_RES_COMPLETE"
                ibest=$i
		        xBUSCO_RES_COMPLETE=
            fi
        done
        if (( ibest != -1 )); then
            # swap best with the first one
            FILES_BUSCO_METADATA[$ibest]=${FILES_BUSCO_METADATA[0]}
            FILES_BUSCO_METADATA[0]=$fbest
        fi
    fi
    [[ ${#FILES_BUSCO_METADATA[@]} -gt 0 ]] || return 1
    [[ -n "${FILES_BUSCO_METADATA[0]}"   ]] || return 1
    [[ -f "${FILES_BUSCO_METADATA[0]}"   ]] || return 1
    return 0
}

#------------------------------------------------------------------------------
# find the BUSCO metadata file(s)
# and sources the best one
#------------------------------------------------------------------------------
map_busco_read_metadata() {
    [[ -d $BUSCO_WD_ROOT ]] || {
        map_error_msg "unable to read busco metadata - missing dir $BUSCO_WD_ROOT"
        return 255
    }
    map_busco_reset_metadata
    local ec=0
    local f=""
    local nodeid=""
    [[ $# -eq 1 ]] && nodeid="$1"
    map_busco_get_metadata $nodeid || return $?
    info_msg "read from busco metadata ${FILES_BUSCO_METADATA[0]}"
    source "${FILES_BUSCO_METADATA[0]}"
    ec=$?
    
    # set any empty variables to default
    map_busco_set_if_empty
    
    verbose_info_msg "got busco lineage ${BUSCO_LINEAGE} (${BUSCO_LINEAGE_TAXID})"
    return $ec
}

#------------------------------------------------------------------------------
# Check if metadata is filled
#------------------------------------------------------------------------------
map_busco_is_filled_metadata() {
    local nempty=0
    [[ -n $BUSCO_ODB_VERSION    ]] || { nempty=$((nempty+1)); }
    [[ -n $BUSCO_LINEAGE        ]] || { nempty=$((nempty+1)); }
    [[ -n $BUSCO_LINEAGE_TAXID  ]] || { nempty=$((nempty+1)); }
    #[[ -n $BUSCO_INPUT_FASTA    ]] || { nempty=$((nempty+1)); }
    #[[ -n $BUSCO_INPUT_LINEAGE  ]] || { nempty=$((nempty+1)); }
    [[ -n $BUSCO_RES_AUTO       ]] || { nempty=$((nempty+1)); }
    [[ -n $BUSCO_RES_FILE       ]] || { nempty=$((nempty+1)); }
    [[ -n $BUSCO_RES_STRING     ]] || { nempty=$((nempty+1)); }
    [[ -n $BUSCO_RES_COMPLETE   ]] || { nempty=$((nempty+1)); }
    [[ -n $BUSCO_RES_SINGLECOPY ]] || { nempty=$((nempty+1)); }
    [[ -n $BUSCO_RES_MULTICOPY  ]] || { nempty=$((nempty+1)); }
    [[ -n $BUSCO_RES_FRAGMENTED ]] || { nempty=$((nempty+1)); }
    [[ -n $BUSCO_RES_MISSING    ]] || { nempty=$((nempty+1)); }
    [[ -n $BUSCO_RES_TOTAL      ]] || { nempty=$((nempty+1)); }
    (( nempty == 0 )) && return 0
    return 1
}

#------------------------------------------------------------------------------
# Convert NCBI taxid to BUSCO lineage string
# Requires the following
# * $DIR_ORTHOLOGER/bin/busco_get_level.py      script that handles the conv
# * $BUSCO_DATA/levels2busco.json             maps all clades in OrthoDB
#                                             to BUSCO lineages (JSON)
# The JSON format is
#   {"2": ["2", "bacteria", 0], "1146883": ["1760", "actinobacteria-class", 1], ... }
#------------------------------------------------------------------------------
map_busco_taxid_to_busco_lineage() {
    [[ $# -ge 1 ]] || { map_error_msg "map_busco_taxid_to_busco_lineage: requires at least one argument"; return 255; }
    BUSCO_LINEAGE=""
    BUSCO_LINEAGE_TAXID=""

    local taxid=$1
    local buscolvl="$DIR_ORTHOLOGER/bin/busco_get_level.py"
    local lvldata="$BUSCO_DATA/levels2busco_${BUSCO_ODB_VERSION}.json"
    [[ -f $lvldata ]] || { map_error_msg "levels to busco name map missing at $lvldata"; return 255; }
    [[ -x $buscolvl ]] || { map_error_msg "missing script busco_get_level at $buscolvl"; return 255; }
                            
    local level=()
    level=( $($buscolvl -i "$taxid" -d "$lvldata" 2>/dev/null) )
    if [[ ${#level} -eq 0 ]]; then
        map_error_msg "no busco level available relative $taxid"
        return 255
    fi
    # return data
    # busco lineage name
    BUSCO_LINEAGE="${level[0]}"
    
    # corresponding NCBI taxid
    BUSCO_LINEAGE_TAXID="${level[1]}"
}

#------------------------------------------------------------------------------
# Convert BUSCO lineage to NCBI taxid
#------------------------------------------------------------------------------
map_busco_lineage_to_taxid() {
    [[ $# -ge 1 ]] || { map_error_msg "map_busco_taxid_to_busco_lineage: requires at least one argument"; return 255; }
    BUSCO_LINEAGE=""
    BUSCO_LINEAGE_TAXID=""

    local lineage=$1
    local buscolvl="$DIR_ORTHOLOGER/bin/busco_get_level.py"
    local lvldata="$BUSCO_DATA/levels2busco_${BUSCO_ODB_VERSION}.json"
    [[ -f $lvldata ]] || { map_error_msg "levels to busco name map missing at $lvldata"; return 255; }
    [[ -x $buscolvl ]] || { map_error_msg "missing script busco_get_level at $buscolvl"; return 255; }
                            
    local level=()

    level=( $($buscolvl -i "$lineage" -d "$lvldata" 2>/dev/null) )
    if [[ ${#level[@]} -eq 0 ]]; then
        map_error_msg "no busco taxid available relative $lineage"
        return 255
    fi
    # return data
    # busco lineage name
    BUSCO_LINEAGE="${level[0]}"
    
    # corresponding NCBI taxid
    BUSCO_LINEAGE_TAXID="${level[1]}"    
}

#------------------------------------------------------------------------------
# Run BUSCO
#------------------------------------------------------------------------------
map_busco_run() {
    [[ $# -ge 1 ]] || { map_error_msg "map_busco_run: requires at least one argument"; return 255; }
    local inputFasta="$1"
    local lineage=""
    [[ $# -gt 1 ]] && lineage="$2"

    # check input fasta
    [[ -f "$inputFasta" ]] || { map_error_msg "unable to read input fasta $inputFasta"; return 255; }
    local fasta=$(basename "$inputFasta")
    
    # create dir where busco data is to be mounted
    mkdir -p $BUSCO_WD_ROOT/busco_downloads || {
        map_error_msg "unable to create busco downloads dir at $BUSCO_WD_ROOT/busco_downloads"
        return 255
    }
    
    # check required directories
    [[ -d $BUSCO_DATA ]] || { mkdir -p $BUSCO_DATA || map_error_msg "unable to create BUSCO_DATA at $BUSCO_DATA"; return 255; }
    [[ -d $BUSCO_DATA ]] || { map_error_msg "missing busco data dir at $BUSCO_DATA"; return 255; }
    
    ###[[ -d $BUSCO_WD   ]] || { map_error_msg "missing busco work dir at $BUSCO_WD";   return 255; }

    # get levels map
    map_busco_load_levels_map || return 255
    

    # copy input fasta file - make sure no line starts with '#'
    grep -v '^#' $inputFasta > "$BUSCO_WD_ROOT/$fasta" || {
        map_error_msg "failed creating new fasta at $BUSCO_WD_ROOT/$fasta"
        return 255
    }

    # set input - to be saved in metadata
    
    
    #-------------------------------------------
    # set BUSCO options
    #-------------------------------------------
    local lineageOpt="--auto-lineage"
    if [[ -n $lineage ]]; then
        if (( BUSCO_OFFLINE == 1 )); then
            local dlineage="$BUSCO_DATA/lineages/${lineage}_${BUSCO_ODB_VERSION}"
            [[ -d $dlineage ]] || {
                local flin=$(find "$BUSCO_DATA/lineages" -name "${lineage}_${BUSCO_ODB_VERSION}.*.tar.gz")
                if [[ -f $flin ]]; then
	                cd "$BUSCO_DATA/lineages"
                    tar -xzf $(basename $flin) || {
                        map_error_msg "unable to untar BUSCO lineage $flin"
                        return 255
                    }
		            cd -
                fi
                [[ -d $dlineage ]] || {
                    map_error_msg "BUSCO runs in offline mode - missing data for lineage $lineage at $dlineage"
                    return 255
                }
            }
        fi
        lineageOpt="--lineage_dataset $lineage"
    fi
    
    # DOCKER: mount BUSCO work directory
    local mount_opts="-v $BUSCO_WD_ROOT:$BUSCO_DOCKER_WD -w $BUSCO_DOCKER_WD"

    # DOCKER: mount BUSCO data directory where lineage files are to be found
    mount_opts="$mount_opts -v $BUSCO_DATA:$BUSCO_DOCKER_WD/busco_downloads"

    # Input data file relative to workdir
    local busco_opts=""

    # Output directory
    if [[ -x $BUSCO_CMD ]]; then
        busco_opts="$busco_opts -i $BUSCO_WD_ROOT/$fasta --out_path $BUSCO_WD_ROOT -o $BUSCO_DOCKER_OUTPUT -f"
    else
        busco_opts="$busco_opts -i $fasta  --out_path $BUSCO_DOCKER_WD -o $BUSCO_DOCKER_OUTPUT -f"
    fi

    # Lineage data set
    busco_opts="$busco_opts $lineageOpt"

    # BUSCO mode
    busco_opts="$busco_opts --mode proteins"

    # Tell BUSCO not to try to find files offline
    (( BUSCO_OFFLINE == 1 )) && busco_opts="$busco_opts --offline"

    # Nr of CPUs - set the nr of threads to a conervative value
    local nthreads=$(get_nthreads_conservative $BUSCO_NCPUS)
    map_info_msg "run BUSCO with $nthreads threads (BUSCO_NCPUS)."
    busco_opts="$busco_opts --cpu $nthreads"

    local ec=0
    if [[ -x $BUSCO_CMD ]]; then
        map_info_msg "run BUSCO with command:"
        map_info_msg "$BUSCO_CMD $busco_opts -f"
        $BUSCO_CMD $busco_opts -f >& busco.log
        ec=$?
    else
        map_info_msg "run BUSCO using docker with command:"
        map_info_msg "docker run -u $(id -u) $mount_opts $BUSCO_DOCKER_IMAGE busco $busco_opts"
        docker run -u $(id -u) $mount_opts $BUSCO_DOCKER_IMAGE busco $busco_opts >& /dev/null
        ec=$?
        map_info_msg "docker run finished with error code $ec"
    fi
    return $ec
}

# temp variables
export xBUSCO_RES_FILE=""
export xBUSCO_RES_STRING=""
export xBUSCO_RES_COMPLETE=""
export xBUSCO_RES_SINGLECOPY=""
export xBUSCO_RES_MULTICOPY=""
export xBUSCO_RES_FRAGMENTED=""
export xBUSCO_RES_MISSING=""
export xBUSCO_RES_TOTAL=""
map_busco_extract_results_from_summary() {
    local fsum="$1"
    local taxid="$2"
    [[ -f $fsum ]] || { map_error_msg "missing BUSCO summary file at $fsum"; return 255; }
    
    xBUSCO_RES_FILE=$fsum
    
    # extract lineage and version from dir name
    local label=$(basename $fsum)
    local ifsSave=$IFS
    IFS="."
    local elements=( $(echo "$label") )
    IFS=$ifsSave
    [[ ${#elements[@]} -eq 5 ]] || {
        map_error_msg "map_extract_busco_from_summary: got bad label $label , taxid=$taxid, Nelements=${#elements[@]} ${elements[0]}"
        return 4
    }

    # extract ODB version, lineage and taxid from 'archaea_odb10'
    local lineage="${elements[2]}"
    IFS="_"
    elements=( $(echo "$lineage") )
    IFS=$ifsSave
    [[ ${#elements[@]} -eq 2 ]] || {
        map_error_msg "map_extract_busco_from_summary: got bad lineage label $lineage"
        return 4
    }
    [[ "${BUSCO_ODB_VERSION}" == "${elements[1]}" ]] || {
        map_warn_msg "downloaded ODB version <${elements[1]}> does not match the requested version <${BUSCO_ODB_VERSION}>"
    }
    map_busco_lineage_to_taxid "${elements[0]}" || {
        map_error_msg "failed extracting lineage (${elements[0]}) and taxid ($BUSCO_LINEAGE_TAXID)"
        return 255
    }
    # extract BUSCO result
    # parse string
    #   C:93.9%[S:92.6%,D:1.3%],F:1.4%,M:4.7%,n:13780
    result=$(grep $'\tC:' "$xBUSCO_RES_FILE" | sed 's/\[/,/; s/\],/,/' | tr -d '\t ' | tr ',' ' ')
    xBUSCO_RES_STRING="$result"
    local x
    for x in $result; do
        local vx=($(echo $x | tr ':' ' '))
        local bsvar="${vx[0]}"
        local val="${vx[1]}"
        local sval=${val//%/}
        if [[ $bsvar == "C" ]]; then
            xBUSCO_RES_COMPLETE="${sval}"
        elif [[ $bsvar == "S" ]]; then
            xBUSCO_RES_SINGLECOPY=:"${sval}"
        elif [[ $bsvar == "D" ]]; then
            xBUSCO_RES_MULTICOPY="${sval}"
        elif [[ $bsvar == "F" ]]; then
            xBUSCO_RES_FRAGMENTED="${sval}"
        elif [[ $bsvar == "M" ]]; then
            xBUSCO_RES_MISSING="${sval}"
        elif [[ $bsvar == "n" ]]; then
            xBUSCO_RES_TOTAL="${sval}"
        fi
    done
}

# will compare current BUSCO_RES with xBUSCO_RES
map_busco_set_if_better() {
    [[ -n $BUSCO_RES_COMPLETE ]] || BUSCO_RES_COMPLETE="-1.0"
    if [[ $(echo "$xBUSCO_RES_COMPLETE > $BUSCO_RES_COMPLETE" | bc -l) == "1" ]]; then
        BUSCO_RES_FILE="$xBUSCO_RES_FILE"
        BUSCO_RES_STRING="$xBUSCO_RES_STRING"
        BUSCO_RES_COMPLETE="$xBUSCO_RES_COMPLETE"
        BUSCO_RES_SINGLECOPY="$xBUSCO_RES_SINGLECOPY"
        BUSCO_RES_MULTICOPY="$xBUSCO_RES_MULTICOPY"
        BUSCO_RES_FRAGMENTED="$xBUSCO_RES_FRAGMENTED"
        BUSCO_RES_MISSING="$xBUSCO_RES_MISSING"
        BUSCO_RES_TOTAL="$xBUSCO_RES_TOTAL"
    fi
}

map_busco_extract_results() {
    [[ $# -ge 1 ]] || {
        map_error_msg "map_busco_extract_results: requires at least one argument"
        return 255
    }

    map_info_msg "extract busco results"
    
    map_busco_reset_metadata

    local taxid="$1"
    map_busco_set_dirs "$taxid"
    [[ -d $BUSCO_OUTPUT ]] || {
        map_error_msg "missing docker output directory at BUSCO_OUTPUT=$BUSCO_OUTPUT"
        return 1
    }

    # get result dir
    # Note if auto lineage was used, there will be 2 run_* directories.
    # However the one relevant will be an actual dir and not a symlink.
    #short_summary.specific.archaea_${BUSCO_ODB_VERSION}.output.txt
    local result=($(find $BUSCO_OUTPUT  -maxdepth 1 -name "short_summary.specific.*.output.txt" -type f))
    if [[ ${#result[@]} -eq 0 ]]; then
        map_error_msg "map_busco_extract_results: no result found for taxid $taxid"
        map_error_msg "search dir: $BUSCO_OUTPUT"
        return 2
    fi

    map_info_msg "select best BUSCO result from ${#result[@]} summaries"
    local fsum=""
    for fsum in ${result[@]}; do
        map_busco_extract_results_from_summary "$fsum" "$taxid" || continue
        map_busco_set_if_better
    done
    if [[ -z $BUSCO_LINEAGE_TAXID ]]; then
        if [[ -z $BUSCO_LINEAGE ]]; then
            map_error_msg "autolineage failed"
        else
            map_error_msg "retrieved lineage $BUSCO_LINEAGE is not supported"
        fi
        return 255
    fi
    
    map_info_msg "------------------------------------------------------------------------------------------"
    map_info_msg "Lineage     : $BUSCO_LINEAGE ($BUSCO_LINEAGE_TAXID)"
    map_info_msg "OrthoDB ver : $BUSCO_ODB_VERSION"
    map_info_msg "BUSCO comp  : $BUSCO_RES_COMPLETE"
    map_info_msg "Summary     : $BUSCO_RES_FILE"
    map_info_msg "Log         : $BUSCO_OUTPUT/logs/busco.log"
    map_info_msg "------------------------------------------------------------------------------------------"
    
    verbose_info_msg "saving updated metadata"
    map_busco_save_metadata || return 255

    #map_error_msg "======> FORCE QUIT HERE <======="
    #return 255
    
    return 0
}
LOADED_ORTHOMAPPER_BUSCO_SH=0
fi
