#!/bin/bash

# ===========================================================================
#
#                            PUBLIC DOMAIN NOTICE
#            National Center for Biotechnology Information (NCBI)
#
#  This software/database is a "United States Government Work" under the
#  terms of the United States Copyright Act.  It was written as part of
#  the author's official duties as a United States Government employee and
#  thus cannot be copyrighted.  This software/database is freely available
#  to the public for use. The National Library of Medicine and the U.S.
#  Government do not place any restriction on its use or reproduction.
#  We would, however, appreciate having the NCBI and the author cited in
#  any work or product based on this material.
#
#  Although all reasonable efforts have been taken to ensure the accuracy
#  and reliability of the software and data, the NLM and the U.S.
#  Government do not and cannot warrant the performance or results that
#  may be obtained by using this software or data. The NLM and the U.S.
#  Government disclaim all warranties, express or implied, including
#  warranties of performance, merchantability or fitness for any particular
#  purpose.
#
# ===========================================================================
#
# File Name:  elink
#
# Author:  Jonathan Kans, Aaron Ucko
#
# Version Creation Date:   06/03/2020
#
# ==========================================================================

pth=$( dirname "$0" )

case "$pth" in
  /* )
    ;; # already absolute
  *  )
    pth=$(cd "$pth" && pwd)
    ;;
esac

case ":$PATH:" in
  *:"$pth":* )
    ;;
  * )
    PATH="$PATH:$pth"
    export PATH
    ;;
esac

# handle common flags - dot command is equivalent of "source"

if [ ! -f "$pth"/ecommon.sh ]
then
  echo "ERROR: Unable to find '$pth/ecommon.sh' file" >&2
  exit 1
fi

. "$pth"/ecommon.sh

# initialize specific flags

internal=false

target=""

name=""
cmmd=""
mode=""
filter=""

idtype=""
related=false

cited=false
cites=false

chunk=100
default_chunk=true

elink_debug=false
if [ -n "${ELINK_DEBUG}" ] && [ "${ELINK_DEBUG}" = true ]
then
  elink_debug=true
fi

# read command-line arguments

while [ $# -gt 0 ]
do
  tag="$1"
  rem="$#"
  case "$tag" in
    -internal )
      internal=true
      shift
      ;;
    -newmode | -oldmode )
      shift
      ;;
    -db )
      CheckForArgumentValue "$tag" "$rem"
      shift
      dbase="$1"
      shift
      ;;
    -id )
      CheckForArgumentValue "$tag" "$rem"
      shift
      ids="$1"
      shift
      while [ $# -gt 0 ]
      do
        case "$1" in
          -* )
            break
            ;;
          * )
            # concatenate run of UIDs with commas
            ids="$ids,$1"
            shift
            ;;
        esac
      done
      ;;
    -format )
      shift
      if [ $# -gt 0 ]
      then
        shift
        if [ "$1" = "acc" ] || [ "$1" = "accn" ]
        then
          idtype=acc
        fi
      else
        DisplayError "Missing -format argument"
        exit 1
      fi
      ;;
    -target )
      shift
      if [ $# -gt 0 ]
      then
        if [ -n "$target" ]
        then
          if [ "$target" = "$1" ]
          then
            DisplayWarning "Redundant -target '$1' argument"
          else
            DisplayError "Colliding -target '$target' and '$1' arguments"
            exit 1
          fi
        fi
        target="$1"
        shift
      else
        DisplayError "Missing -target argument"
        exit 1
      fi
      ;;
    -name | -linkname )
      CheckForArgumentValue "$tag" "$rem"
      shift
      name="$1"
      shift
      ;;
    -cmd )
      CheckForArgumentValue "$tag" "$rem"
      shift
      cmmd="$1"
      shift
      ;;
    -mode )
      CheckForArgumentValue "$tag" "$rem"
      shift
      mode="$1"
      shift
      ;;
    -filter )
      CheckForArgumentValue "$tag" "$rem"
      shift
      # set term for filtering after link -related (undocumented)
      filter="$1"
      shift
      ;;
    -related )
      related=true
      shift
      ;;
    -neighbor )
      related=true
      shift
      ;;
    -cited )
      cited=true
      shift
      ;;
    -cites )
      cites=true
      shift
      ;;
    -elink_debug | -elink-debug )
      elink_debug=true
      shift
      ;;
    -chunk )
      CheckForArgumentValue "$tag" "$rem"
      shift
      # override default chunk value (undocumented)
      chunk=$(( $1 ))
      default_chunk=false
      shift
      ;;
    -batch )
      # accept -batch flag from old scripts - now standard behavior
      shift
      ;;
    -h | -help | --help | help )
      echo "elink $version"
      echo ""
      newVersion=$( NewerEntrezDirectVersion )
      if [ -n "$newVersion" ]
      then
        DisplayNote "EDirect version ${newVersion} is now available"
        echo "" >&2
        cat "$pth/help/elink-help.txt"
        echo ""
        DisplayNote "EDirect version ${newVersion} is now available"
        echo "" >&2
      else
        cat "$pth/help/elink-help.txt"
        echo ""
      fi
      exit 0
      ;;
    -* )
      ParseCommonArgs "$@"
      if [ "$argsConsumed" -gt 0 ]
      then
        shift "$argsConsumed"
      else
        DisplayError "Unrecognized option $1"
        exit 1
      fi
      ;;
    * )
      DisplayError "Unrecognized argument $1"
      shift
      ;;
  esac
done

FinishSetup

# check for ENTREZ_DIRECT message or piped UIDs unless database and UIDs provided in command line

if [ -z "$db" ]
then
  ParseStdin
elif [ -z "$ids" ] && [ -z "$input" ]
then
  ParseStdin
fi

# additional argument reality checks

if [ -n "$db" ] && [ -n "$dbase" ] && [ "$db" != "$dbase" ]
then
  DisplayError "Colliding -db '$db' and ENTREZ_DIRECT Db '$dbase' arguments"
  exit 1
fi

if [ "$related" = true ]
then
  if [ -n "$db" ] && [ -n "$target" ] && [ "$db" != "$target" ]
  then
    DisplayError "-related -db '$db' incompatible with -target '$target'"
    exit 1
  elif [ -n "$dbase" ] && [ -n "$target" ] && [ "$dbase" != "$target" ]
  then
    DisplayError "-related and ENTREZ_DIRECT Db '$dbase' incompatible with -target '$target'"
    exit 1
  fi
fi

if [ -z "$ids$rest$qury$input" ]
then
  needHistory=true
fi

# take database from dbase value or -db argument

if [ -z "$dbase" ]
then
  dbase="$db"
fi

if [ "$dbase" = "nucleotide" ]
then
  dbase="nuccore"
fi

# check for missing required arguments

if [ -z "$dbase" ]
then
  DisplayError "Missing -db argument"
  exit 1
fi

# normalize to lower-case (e.g., SRA -> sra)

dbase=$( echo "$dbase" | tr '[:upper:]' '[:lower:]' )

# take optional days and datetype arguments from message

if [ -z "$reldate" ] && [ -n "$reldatex" ]
then
  reldate="$reldatex"
fi
if [ -z "$mindate" ] && [ -n "$mindatex" ]
then
  mindate="$mindatex"
fi
if [ -z "$maxdate" ] && [ -n "$maxdatex" ]
then
  maxdate="$maxdatex"
fi
if [ -z "$datetype" ] && [ -n "$datetypex" ]
then
  datetype="$datetypex"
fi

# normalize date arguments

FixDateConstraints

# convert spaces between UIDs to commas

ids=$( echo "$ids" | sed -e "s/ /,/g; s/,,*/,/g" )

# cmd aliases

case "$cmmd" in
  history )
    cmmd="neighbor_history"
    ;;
  neighbors )
    # silently convert known typo in existing scripts
    cmmd="neighbor"
    ;;
  entrez )
    # alias for edirect
    cmmd="edirect"
    ;;
  uids )
    # alias for uid
    cmmd="uid"
    ;;
  score )
    cmmd="neighbor_score"
    if [ -z "$target" ]
    then
      target="$dbase"
    fi
    ;;
  llibs )
    cmmd="llinkslib"
    ;;
esac

# special cases for target, cmd, and linkname

case "$cmmd" in
  acheck )
    ;;
  ncheck | lcheck | llinks | llinkslib | prlinks )
    target=""
    ;;
  neighbor | neighbor_score | neighbor_history )
    ;;
  edirect | uid )
    if [ -z "$target" ]
    then
      target="$dbase"
    fi

    if [ -z "$name" ]
    then
      # set default name
      name="${dbase}_${target}"
    fi
    ;;
  * )
    if [ -n "$cmmd" ]
    then
      DisplayWarning "Unrecognized -cmd option $cmmd, ignoring for now"
      cmmd=""
    fi
    if [ -z "$target" ] && [ "$related" = false ] && [ "$cited" = false ] && [ "$cites" = false ]
    then
      DisplayError "Must supply -target or -related on command line"
      exit 1
    fi
    if [ -z "$target" ]
    then
      target="$dbase"
    fi

    if [ -z "$name" ]
    then
      # set default name
      name="${dbase}_${target}"
      # special case for pubmed_pmc - commented out now that the link has returned
      # if [ $name = "pubmed_pmc" ]
      # then
        # name="pubmed_pmc_local"
      # fi
    fi
    ;;
esac

if [ -z "$cmmd" ]
then
  cmmd="neighbor_history"
fi

if [ "$dbase" = "nlmcatalog" ]
then
  DisplayError "Entrez Direct does not support links for the nlmcatalog database"
  exit 1
fi

# input reality checks

if [ "$needHistory" = true ]
then

  if [ -t 0 ]
  then
    DisplayError "ENTREZ_DIRECT message not piped from stdin"
    exit 1
  fi
  if [ -z "$web_env" ]
  then
    DisplayError "WebEnv value not found in elink input"
    exit 1
  fi
  if [ -z "$qry_key" ]
  then
    DisplayError "QueryKey value not found in elink input"
    exit 1
  fi
  if [ -z "$num" ] || [ "$num" -lt 1 ]
  then
    # print message with count of 0 if no results to process
    WriteEDirect "$target" "$web_env" "$qry_key" "0" "$stp" "$err"
    exit 0
  fi
fi

if [ "$cited" = true ] || [ "$cites" = true ]
then
  if [ "$dbase" != "pubmed" ]
  then
    DisplayError "-cited or -cites can only be used with -db pubmed"
    exit 1
  fi
fi

# lookup accessions in -id argument or piped from stdin

if [ "$elink_debug" = true ]
then
  echo "LookupSpecialAccessions" >&2
fi

LookupSpecialAccessions

# -cited or -cites access the NIH Open Citation Collection dataset (see PMID 31600197)

LinkInIcite() {

  iciteElement="$1"
  GetUIDs |
  join-into-groups-of "$chunk" |
  while read uids
  do
    nquire -get https://icite.od.nih.gov/api/pubs -pmids "$uids" |
    transmute -j2x |
    xtract -pattern opt -sep "\n" -element "$iciteElement"
  done |
  accn-at-a-time |
  sort -n | uniq
}

QueryIcite() {

  cits=$( LinkInIcite "$1" )

  if [ -n "$cits" ]
  then
    # post to history appears to be broken for large sets, instantiate in message instead
    num=$( echo "$cits" | wc -l | tr -d ' ' )
    echo "<ENTREZ_DIRECT>"
    echo "  <Db>${dbase}</Db>"
    echo "  <Count>${num}</Count>"
    echo "$cits" |
    accn-at-a-time |
    while read uid
    do
      echo "  <Id>${uid}</Id>"
    done
    echo "</ENTREZ_DIRECT>"
  else
    echo "<ENTREZ_DIRECT>"
    echo "  <Db>pubmed</Db>"
    echo "  <Count>0</Count>"
    echo "</ENTREZ_DIRECT>"
  fi
}

if [ "$cited" = true ]
then
  # equivalent of -name pubmed_pubmed_citedin (for pubmed records also in pmc)
  QueryIcite "cited_by"

  exit 0
fi

if [ "$cites" = true ]
then
  # equivalent of -name pubmed_pubmed_refs (for pubmed records also in pmc)
  QueryIcite "references"

  exit 0
fi

# helper function adds link-specific arguments (if set)

RunWithLinkArgs() {

  if [ "$log" = true ]
  then
    printf "." >&2
  fi

  AddIfNotEmpty -dbfrom "$dbase" \
  AddIfNotEmpty -db "$target" \
  AddIfNotEmpty -cmd "$cmmd" \
  AddIfNotEmpty -linkname "$name" \
  AddIfNotEmpty -retmode "$mode" \
  AddIfNotEmpty -idtype "$idtype" \
  AddIfNotEmpty -term "$filter" \
  RunWithCommonArgs "$@"
}

# explicitly-set non-history link requests generate XML results

if [ "$cmmd" != "neighbor_history" ] && [ "$cmmd" != "edirect" ] && [ "$cmmd" != "uid" ]
then

  GetUIDs |
  join-into-groups-of "$chunk" |
  while read uids
  do
    uids=$( echo "$uids" | tr ',' ' ' )
    set nquire -url "$base" elink.fcgi
    # $uids is unquoted so the shell will perform word splitting on it
    for uid in $uids
    do
      # individual -id arguments get a separate set of link results for each uid
      set "$@" -id "$uid"
    done
    RunWithLinkArgs "$@" |
    transmute -format indent -doctype ""
  done

  exit 0
fi

# -quick uses history for elink without trying to circumvent truncation limits

if [ "$quick" = true ] && [ "$needHistory" = true ]
then

  err=""
  cmmd="neighbor_history"
  res=$( RunWithLinkArgs nquire -url "$base" elink.fcgi \
          -WebEnv "$web_env" -query_key "$qry_key" )

  if [ -n "$res" ]
  then
    dt=""
    ParseMessage "$res" eLinkResult dt DbTo web_env WebEnv qry_key QueryKey

    if [ -n "$err" ]
    then
      DisplayError "elink failed - $err"
      exit 1
    fi
    if [ -z "$web_env" ]
    then
      echo "WebEnv value not found in elink output - WebEnv1 $wb"
      exit 1
    fi
    if [ -n "$wb" ] && [ "$web_env" != "$wb" ]
    then
      echo "WebEnv mismatch in elink output - WebEnv1 $wb, WebEnv2 $web_env"
      exit 1
    fi
    if [ -z "$qry_key" ]
    then
      lst=$( echo "$res" | tail -n 8 )
      echo "QueryKey value not found in elink output - unable to retrieve count"
      echo "$lst"
      exit 1
    fi

    # need to call esearch to get count of results stored in history - this will also increment the QueryKey
    num="0"
    nbr=$( RunWithCommonArgs nquire -url "$base" esearch.fcgi \
           -WebEnv "$web_env" -query_key "$qry_key" -retmax 0 -db "$target" )
    if [ -n "$nbr" ]
    then
      num=$( echo "$nbr" |
             sed -e 's|<TranslationStack>.*</TranslationStack>||' |
             sed -e 's|<QueryTranslation>.*</QueryTranslation>||' |
             xtract -pattern eSearchResult -element Count )
    fi

    WriteEDirect "$dt" "$web_env" "$qry_key" "$num" "$stp" "$err"
  fi

  exit 0
fi

# special case originally for PubMed with new SOLR server

LinkAllInChunks() {

  # use neighbor command instead of history mechanism
  cmmd="neighbor"

  # default chunk of 100 avoids overflow of elink.fcgi server or backend database
  GetUIDs |
  join-into-groups-of "$chunk" |
  while read uids
  do
    RunWithLinkArgs nquire -url "$base" elink.fcgi -id "$uids" "$@" |
    xtract -pattern LinkSet -sep "\n" -element Link/Id
  done |
  sort -V | uniq -i | grep '.'
}

# query in small chunks for efficiency, return sorted and uniqued UID list

if [ "$cmmd" = "uid" ]
then

  if [ "$log" = true ]
  then
    printf "ELink\n" >&2
  fi
  flt=""
  raw=$( LinkAllInChunks )
  if [ "$log" = true ]
  then
    printf "\n" >&2
  fi

  echo "$raw"

  exit 0
fi

if [ "$target" = "pubmed" ] || [ "$cmmd" = "edirect" ]
then

  if [ "$log" = true ]
  then
    printf "ELink\n" >&2
  fi
  flt=""
  raw=$( LinkAllInChunks )
  if [ "$log" = true ]
  then
    printf "\n" >&2
  fi
  if [ -n "$raw" ]
  then
    flt=$( echo "$raw" | sed -e 's/^/  <Id>/' -e 's/$/<\/Id>/' )
    num=$( echo "$raw" | wc -l | tr -d ' ' )
  else
    # clear values if results are empty
    flt=""
    num="0"
  fi

  seconds_end=$(date "+%s")
  seconds_elapsed=$((seconds_end - seconds_start))

  # create -format xids output
  echo "<ENTREZ_DIRECT>"
  if [ -n "$target" ]
  then
    echo "  <Db>${target}</Db>"
  fi
  if [ -n "$num" ]
  then
    echo "  <Count>${num}</Count>"
  fi
  if [ -n "$stp" ]
  then
    # increment step value
    stp=$(( stp + 1 ))
    echo "  <Step>${stp}</Step>"
  fi
  if [ -n "$flt" ]
  then
    echo "$flt"
  fi
  if [ "$quick" = true ] || [ "$quickx" = "Y" ]
  then
    echo "  <Quick>Y</Quick>"
  fi
  if [ "$debug" = true ] || [ "$debugx" = "Y" ]
  then
    echo "  <Debug>Y</Debug>"
  fi
  if [ "$log" = true ] || [ "$logx" = "Y" ]
  then
    echo "  <Log>Y</Log>"
  fi
  if [ "$timer" = true ] && [ -n "$seconds_elapsed" ]
  then
    echo "  <Elapsed>${seconds_elapsed}</Elapsed>"
  fi
  echo "</ENTREZ_DIRECT>"

  exit 0
fi

# helper function adds web environment argument for history (if set)

RunWithLinkHistoryArgs() {

  AddIfNotEmpty -WebEnv "$web_env" \
  RunWithLinkArgs "$@"
}

# -cmd neighbor_history now passes UID list to external elink if input is a history reference

# esearch -db gene -query "Pancreatic Cancer Biomarker AND human [ORGN]" | tee /dev/tty |
# elink -target protein -log | tee /dev/tty |
# efetch -format docsum > refseq.txt

# internally converted to:

# esearch -db gene -query "Pancreatic Cancer Biomarker AND human [ORGN]" | tee /dev/tty |
# efetch -format uid | tee /dev/tty |
# elink -db gene -target protein -log | tee /dev/tty |
# efetch -format docsum > refseq.txt

MakeXIDMessage() {

  echo "<ENTREZ_DIRECT>"
  if [ -n "$dbase" ]
  then
    echo "  <Db>${dbase}</Db>"
  fi
  # instantiate UIDs within ENTREZ_DIRECT message

  res=$( GetUIDs )

  nm=$( echo "$res" | wc -l | tr -d ' ' )
  if [ -n "$nm" ]
  then
    echo "  <Count>${nm}</Count>"
  fi
  if [ -n "$stp" ]
  then
    # increment step value
    stp=$(( stp + 1 ))
    echo "  <Step>${stp}</Step>"
  fi
  echo "$res" |
  while read uid
  do
    echo "  <Id>${uid}</Id>"
  done
  if [ -n "$err" ]
  then
    echo "  <Error>${err}</Error>"
  fi

  seconds_end=$(date "+%s")
  seconds_elapsed=$((seconds_end - seconds_start))

  if [ "$log" = true ] || [ "$logx" = "Y" ]
  then
    echo "  <Log>Y</Log>"
  fi

  if [ "$timer" = true ] && [ -n "$seconds_elapsed" ]
  then
    echo "  <Elapsed>${seconds_elapsed}</Elapsed>"
  fi

  echo "</ENTREZ_DIRECT>"
}

# workaround for history server limitations

if [ "$needHistory" = true ]
then

  # instantiate UIDs into ENTREZ_DIRECT message
  xids=$( MakeXIDMessage )

  lg=""
  ck=""
  ed=""

  if [ "$log" = true ]
  then
    lg="ok"
  fi
  if [ "$default_chunk" = false ]
  then
    ck="ok"
  fi
  if [ "$elink_debug" = true ]
  then
    ed="ok"
  fi

  if [ "$elink_debug" = true ]
  then
    echo "Launching External Elink" >&2
    echo "Launching elink -db $dbase -target $target " ${ck:+"-chunk"} ${ck:+"${chunk}"} ${lg:+"-log"} ${ed:+"-elink_debug"} >&2
  fi

  # shell parameter expansion populates optional arguments sent to separate instance of elink,
  # execution logic just below in LinkInGroups function
  ( echo "$xids" | elink -db "$dbase" -target "$target" ${ck:+"-chunk"} ${ck:+"${chunk}"} ${lg:+"-log"} ${ed:+"-elink_debug"} )

  if [ "$elink_debug" = true ]
  then
    echo "External Elink Launched" >&2
  fi

  exit 0
fi

# -cmd neighbor_history

# in new instance called from above, GetUIDs will obtain the uid list from instantiated message,
# the previous web_env is abandoned, and the first call to RunWithLinkHistoryArgs will assign a new
# web_env value for individual link subset results and the final merge

wb="$web_env"

LinkInGroups() {

  if [ "$log" = true ]
  then
    printf "ELink\n" >&2
  fi

  if [ "$elink_debug" = true ]
  then
    echo "Enter LinkInGroups" >&2
  fi

  # calculate num if using raw uids or -id argument
  if [ -z "$num" ] || [ "$num" = 0 ]
  then
    if [ -n "$rest" ]
    then
      num=$( echo "$rest" | wc -l | tr -d ' ' )
    elif [ -n "$ids" ]
    then
      num=$( echo "$ids" | wc -l | tr -d ' ' )
    fi
  fi

  if [ -n "$num" ] && [ "$num" -gt 100 ] && [ "$default_chunk" = true ]
  then
    # integer division of ( num + 97 ) / 98 allows up to 99 groups to be loaded into memory,
    # fusion of result components with "(#1) OR (#2) ... OR (#99)" will create the 100th (maximum) entry
    numer=$(( $num + 97 ))
    denom=$(( 98 ))
    chunk=$(( $numer / $denom ))
  fi

  loop_count=0

  # limit to 99 history elements storing link results - any more will fail once combined into another entry,
  # use awk instead of head -n 99 to avoid prematurely closed (broken) pipe error
  GetUIDs |
  join-into-groups-of "$chunk" |
  awk 'NR < 100' |
  while read uids
  do
    # limit to first 100 to avoid backend server error
    firstuids=$( echo "$uids" | tr ',' '\n' | head -n 100 | join-into-groups-of 100 )
    loop_count=$(( loop_count + 1 ))
    err=""
    res=$( RunWithLinkHistoryArgs nquire -url "$base" elink.fcgi -id "$firstuids" )

    if [ -n "$res" ]
    then
      dt=""
      ParseMessage "$res" eLinkResult dt DbTo web_env WebEnv qry_key QueryKey

      if [ "$elink_debug" = true ]
      then
        echo "WebEnv $web_env, QueryKey $qry_key, Count $loop_count" >&2
        if [ "$qry_key" == "" ]
        then
          echo "RES" >&2
          echo "$res" | grep -v "<Id>" >&2
        fi
      fi

      if [ -n "$err" ]
      then
        DisplayError "elink failed - $err"
        exit 1
      fi
      if [ -z "$web_env" ]
      then
        echo "WebEnv value not found in elink output - WebEnv1 $wb"
        exit 1
      fi
      if [ -n "$wb" ] && [ "$web_env" != "$wb" ]
      then
        echo "WebEnv mismatch in elink output - WebEnv1 $wb, WebEnv2 $web_env"
        exit 1
      fi

      WriteEDirectStep "$dt" "$web_env" "$qry_key" "$err"
    fi
  done

  if [ "$elink_debug" = true ]
  then
    echo "Leave LinkInGroups" >&2
  fi

  if [ "$log" = true ]
  then
    printf "\n" >&2
  fi
}

lnks=$( LinkInGroups )

if [ -n "$lnks" ]
then

  # extract first database and webenv values, and all key numbers
  comps=$( echo "$lnks" | xtract -wrp Set,Rec -pattern ENTREZ_DIRECT \
           -wrp Web -element WebEnv -wrp Key -element QueryKey )

  wbnv=$( echo "$comps" | xtract -pattern Set -first Web )
  qrry=$( echo "$comps" | xtract -pattern Set -block Rec -pfx "(#" -sfx ")" -tab " OR " -element Key )

  if [ "$elink_debug" = true ]
  then
    echo "QRRY $qrry" >&2
  fi

  err=""
  num=""
  if [ -z "$qrry" ]
  then
    # no neighbors or links can be a normal response,
    # e.g., elink -db gene -id 496376 -target medgen
    WriteEDirect "$target" "$web_env" "$qry_key" "0" "$stp" "$err"
    exit 0
  fi

  # send search command, e.g, "(#1) OR (#2)", along with database and web environment
  srch=$( RunWithCommonArgs nquire -url "$base" esearch.fcgi -db "$target" \
          -WebEnv "$wbnv" -term "$qrry" -retmax 0 -usehistory y )

  if [ -n "$srch" ]
  then
    res=$( echo "$srch" | sed -e 's|<TranslationStack>.*</TranslationStack>||' )
    ParseMessage "$res" eSearchResult web_env WebEnv qry_key QueryKey num Count

    if [ "$elink_debug" = true ]
    then
      echo "SRCH $res" >&2
    fi
  fi

  if [ -n "$num" ] && [ "$num" -lt 1 ]
  then
    tmp=$( GetUIDs )
    uids=$( echo "$tmp" | head -n "$chunk" | join-into-groups-of "$chunk" )
    res=$( RunWithCommonArgs nquire -url "$base" elink.fcgi \
           -dbfrom "$dbase" -id "$uids" -cmd "acheck" )

    if [ -n "$res" ]
    then
      ParseMessage "$res" eLinkResult ignore DbFrom

      if [ -z "$err" ]
      then
        tst=$( echo "$res" | xtract -pattern LinkInfo -if LinkName -equals "$name" -element LinkName )
        if [ -n "$tst" ]
        then
          DisplayError "UNEXPECTED EMPTY LINK RESULT FOR ${name}"
        fi
      else
        DisplayError "-cmd acheck TEST FAILED - ${err}"
      fi
    fi
  fi

  WriteEDirect "$target" "$web_env" "$qry_key" "$num" "$stp" "$err"

  exit 0
fi

# warn on error

DisplayError "ELink failure"
exit 1
