#! /bin/bash
#
# cygcheck-dep, GNU General Public License
# Copyright (c) 2013 Mikhail Usenko <mikeus@nm.ru>
#
### michaelus /*


name="cygcheck-dep"
version="2.0"

tag_cygwin="cygwin/"
tag_cygwinports="cygwinports/"
tag_extra="extra/"

####### version(), help() /*
#######
version()
{
  echo "$name, version $version"
}

help()
{
  version
  cat << $
Show information on dependencies for installed Cygwin packages.

Usage: $name [-v] [-c] [-s FILE | -S] [-p] [-l] [-i | -I]
       $name [-v] [-c] [-s FILE | -S] [-p] [[-r] [-R] [-n] [-N] [PACKAGE ...]]
       $name [-v] [-c] [-s FILE | -S] [-o] [-b] [-f]
       $name [-h | --help]
       $name [-V | --version]

Options:
 (Setup diagnostic)
  -l              Check for installed packages that are not required by any
                   other installed packages (that is "leaves" in the package
                   dependency tree).
  -i              Check for package "islands". "An island" is a group of
                   interdependent packages that require each other (with
                   circular or all-to-all relationships) and any part of the
                   group or all together they are not needed by any other
                   installed packages.
  -I              Show groups of interdependent packages with circular or
                   all-to-all relationships. Supersedes -i.
  -b              Show list of installed packages that have broken
                   or unknown dependencies. Implies -q.
  -f              Show list of installed packages that have phantom
                   dependencies (nonpresent packages likely renamed/retired
                   /etc.) specified in the setup.ini file. Implies -q.
 (Package information)
  -r              Show packages that required by the PACKAGE.
                   (This is the same list that specified in "requires:" line
                   in the setup.ini file for the PACKAGE except for broken
                   and phantom dependencies.)
  -R              Recursively resolve a list of packages that required by the
                   PACKAGE.
  -n              Show packages that need the PACKAGE.
  -N              Recursively resolve a list of packages that need the PACKAGE.
 (Cygwin Ports setup support)
  -p              Turn on the Cygwin Ports collection support.
  -o              List installed packages that have overridden versions in
                   the Cygwin Ports collection. Implies -p, -q.
 (Modifiers)
  -c              Normally every time $name runs it downloads
                   latest Cygwin package database file (setup.ini) with
                   information on package dependencies from Cygwin ftp site.
                   This option prevents from unnecessary downloads and
                   forces to use the file cached from the previous download.
  -s              $name shows information on installed packages
                   for the Cygwin installation you are working in (the list
                   of installed packages is /etc/setup/installed.db). You may
                   specify a different source for a database FILE of installed
                   packages (e.g. the file from another Cygwin installation
                   on your system).
  -S              Instead of checking through only installed packages use
                   a list of all available packages from the setup.ini file
                   from the Cygwin standard distribution (and from the Cygwin
                   Ports collection if its support has been turned on).
                   In other words it will be treated as if all the available
                   packages from the package database are installed.
                   Supersedes -s. (Be aware it may take long time with other
                   options: more than 50 sec.)
 (Other)
  -x              Print package names prefixed with the tag of their
                   distribution: '$tag_cygwin', '$tag_cygwinports', or '$tag_extra'.
  -v              Be more verbose: turn on Wget's output (to stderr) while
                   downloading setup.ini file.
  -q              Be more quiet: suppress the warnings on phantom dependencies
                   (nonpresent packages likely renamed/retired/etc.) specified
                   in the setup.ini file and on broken/unknown dependencies.
  -h, --help      Print this help to stdout and exit.
  -V, --version   Print the program version to stdout and exit.
$
}
####### */

####### processing of the command-line options /*
#######
opt_print_src_tag=""
opt_quiet_wget="-q"
opt_suppress_unneeded_warns=""
opt_use_cached=""
opt_installed_db=""
opt_treat_all_packages=""
opt_show_leaves=""
opt_show_islands=""
opt_show_interdependent=""
opt_show_broken=""
opt_show_phantom=""
opt_show_required=""
opt_resolve_required=""
opt_show_dependent=""
opt_resolve_dependent=""
opt_with_ports=""
opt_show_overridden=""
f_dependencies_be_necessary=""
f_nonleaves_be_necessary=""
while getopts "xvqcs:SpoliIbfrRnN-:hV" OPT; do
  case "$OPT" in
    x)  opt_print_src_tag="yes"
        ;;
    v)  opt_quiet_wget=""
        ;;
    q)  opt_suppress_unneeded_warns="yes"
        ;;
    c)  opt_use_cached="yes"
        ;;
    s)  opt_installed_db="$OPTARG"
        ;;
    S)  opt_treat_all_packages="yes"
        ;;
    p)  opt_with_ports="yes"
        ;;
    o)  opt_show_overridden="yes"
        opt_with_ports="yes"
        opt_suppress_unneeded_warns="yes"
        ;;
    l)  opt_show_leaves="yes"
        f_dependencies_be_necessary="yes"
        f_nonleaves_be_necessary="yes"
        ;;
    i)  opt_show_islands="yes"
        f_dependencies_be_necessary="yes"
        f_nonleaves_be_necessary="yes"
        ;;
    I)  opt_show_interdependent="yes"
        f_dependencies_be_necessary="yes"
        f_nonleaves_be_necessary="yes"
        ;;
    b)  opt_show_broken=yes
        opt_suppress_unneeded_warns="yes"
        f_dependencies_be_necessary="yes"
        ;;
    f)  opt_show_phantom="yes"
        opt_suppress_unneeded_warns="yes"
        f_dependencies_be_necessary="yes"
        ;;
    r)  opt_show_required="yes"
        f_dependencies_be_necessary="yes"
        ;;
    R)  opt_resolve_required="yes"
        f_dependencies_be_necessary="yes"
        ;;
    n)  opt_show_dependent="yes"
        f_dependencies_be_necessary="yes"
        ;;
    N)  opt_resolve_dependent="yes"
        f_dependencies_be_necessary="yes"
        ;;
    -)  case "$OPTARG" in
             help)  help
                    exit 0
                    ;;
          version)  version
                    exit 0
                    ;;
                *)  echo >&2 "$0: illegal option -- -$OPTARG"
                    exit 1;
                    ;;
        esac
        ;;
    h)  help
        exit 0
        ;;
    V)  version
        exit 0;
        ;;
   \?)  exit 1
        ;;
  esac
done
####### */

####### checking for the cache directory /*
#######
cache_dir="/var/cache/$name"
[ -d "$cache_dir" ] ||
if ! mkdir -p "$cache_dir"; then
  echo >&2 "$0: unable to create cache directory"
  echo >&2 "$0: ($cache_dir)"
  exit 2
fi
####### */

####### checking for the installed.db file /*
#######
installed_db="${opt_installed_db:-/etc/setup/installed.db}"
if ! [ "$opt_treat_all_packages" ]; then
  if ! [ -r "$installed_db" ]; then
    echo >&2 "$0: installed.db file is not exist or is not readable"
    echo >&2 "$0: ($installed_db)"
    exit 3
  fi
fi
####### */

####### getting the target architecture /*
#######
  mach="${MACHTYPE%%-*}"
# mach="$(uname -m)"
[ "$mach" = "x86_64" ] || mach="x86"
####### */

####### downloading the setup.ini files /*
# cygwin /*
cw_setup_ini="$cache_dir/cygwin/$mach/setup.ini"
if ! [ "$opt_use_cached" ]; then
  cw_setup_bz2_url="ftp://sourceware.org/pub/cygwin/$mach/setup.bz2"
  if ! wget >&2 $opt_quiet_wget -r -nH --cut-dirs 1 -P "$cache_dir" "$cw_setup_bz2_url"; then
    echo >&2 "$0: Cygwin standard distribution:"
    echo >&2 "$0: failed to download setup.ini"
    echo >&2 "$0: check your internet connection"
    echo >&2 "$0: or try -c option to use cached file from previous download"
    exit 4
  fi
  cw_setup_bz2="$cache_dir/cygwin/$mach/setup.bz2"
  if ! bzip2 -t "$cw_setup_bz2"; then
    echo >&2 "$0: Cygwin standard distribution:"
    echo >&2 "$0: failed to check integrity of downloaded setup.ini"
    echo >&2 "$0: ($cw_setup_bz2)"
    echo >&2 "$0: you may try -c option to use cached file from previous download"
    exit 5
  fi
  if ! bzcat "$cw_setup_bz2" > "$cw_setup_ini"; then
    echo >&2 "$0: Cygwin standard distribution:"
    echo >&2 "$0: failed to decompress downloaded setup.ini"
    echo >&2 "$0: ($cw_setup_bz2 -> $cw_setup_ini)"
    exit 6
  fi
fi
if ! [ -r "$cw_setup_ini" ]; then
  echo >&2 "$0: Cygwin standard distribution:"
  echo >&2 "$0: setup.ini file is not exist or is not readable"
  echo >&2 "$0: ($cw_setup_ini)"
  [ "$opt_use_cached" ] &&
  echo >&2 "$0: try to run without -c option to download the file"
  exit 7
fi
# */
# cigwinports /*
if [ "$opt_with_ports" ]; then
  cwp_setup_ini="$cache_dir/cygwinports/$mach/setup.ini"
  if ! [ "$opt_use_cached" ]; then
    cwp_setup_bz2_url="ftp://sourceware.org/pub/cygwinports/$mach/setup.bz2"
    if ! wget >&2 $opt_quiet_wget -r -nH --cut-dirs 1 -P "$cache_dir" "$cwp_setup_bz2_url"; then
      echo >&2 "$0: Cygwin Ports collection:"
      echo >&2 "$0: failed to download setup.ini"
      echo >&2 "$0: check your internet connection"
      echo >&2 "$0: or try -c option to use cached file from previous download"
      exit 8
    fi
    cwp_setup_bz2="$cache_dir/cygwinports/$mach/setup.bz2"
    if ! bzip2 -t "$cwp_setup_bz2"; then
      echo >&2 "$0: Cygwin Ports collection:"
      echo >&2 "$0: failed to check integrity of downloaded setup.ini"
      echo >&2 "$0: ($cwp_setup_bz2)"
      echo >&2 "$0: you may try -c option to use cached file from previous download"
      exit 9
    fi
    if ! bzcat "$cwp_setup_bz2" > "$cwp_setup_ini"; then
      echo >&2 "$0: Cygwin Ports collection:"
      echo >&2 "$0: failed to decompress downloaded setup.ini"
      echo >&2 "$0: ($cwp_setup_bz2 -> $cwp_setup_ini)"
      exit 10
    fi
  fi
  if ! [ -r "$cwp_setup_ini" ]; then
    echo >&2 "$0: Cygwin Ports collection:"
    echo >&2 "$0: setup.ini file is not exist or is not readable"
    echo >&2 "$0: ($cwp_setup_ini)"
    [ "$opt_use_cached" ] &&
    echo >&2 "$0: try to run without -c option to download the file"
    exit 11
  fi
fi
# */
####### */

####### declaring global variables /*
#######
declare L P R V F k n kk nn pp
declare -i i j ports0 extras0

declare -a pkg_name
declare -A pkg_index pkg_index_override
declare -a pkg_version

declare -a pkg_drequisites pkg_ddependants
declare -a pkg_rrequisites pkg_rdependants

declare -a pkg_installed pkg_version_installed
declare -a pkg_drequisites_discarded pkg_drequisites_phantom
declare extra_pkgs nonleaf_pkgs

declare chances_for_misleading warnings
####### */

####### reading off the setup.ini files; populating pkg_{name,index,version}, pkg_drequisites (preparatory) variables /*
#######
L=""; let i=0; let j=0
while IFS='' read -r L || [ "$L" ]; do
  case "$L" in
            @\ *) j=$i; let ++i
                  P="${L#@ }"
                  pkg_name[$j]="$P"
                  pkg_index["$P"]=$j
                  [ "$opt_with_ports" ] && pkg_version[$j]=""
                  pkg_drequisites[$j]=""
                  ;;
    requires:\ *) R="${L#requires: }"
                  pkg_drequisites[$j]="$R"
                  ;;
     version:\ *) V="${L#version: }"
                  pkg_version[$j]="$V"
                  ;;
  esac
done < <(
          if [ "$opt_with_ports" ]; then
            sed -n '/^@ /p; /^requires: /p; /^version: /p' "$cw_setup_ini"
          else
            sed -n '/^@ /p; /^requires: /p' "$cw_setup_ini"
          fi
        )

ports0=${#pkg_name[@]}

if [ "$opt_with_ports" ]; then
  L=""; let i=$ports0; let j=$ports0
  while IFS='' read -r L || [ "$L" ]; do
    case "$L" in
              @\ *) j=$i; let ++i
                    P="${L#@ }"
                    pkg_name[$j]="$P"
                    k="${pkg_index[$P]}"
                    [ "$k" ] && pkg_index_override["$P"]=$j || pkg_index["$P"]=$j
                    pkg_version[$j]=""
                    pkg_drequisites[$j]=""
                    ;;
      requires:\ *) R="${L#requires: }"
                    pkg_drequisites[$j]="$R"
                    ;;
       version:\ *) V="${L#version: }"
                    pkg_version[$j]="$V"
                    ;;
    esac
  done < <(sed -n '/^@ /p; /^requires: /p; /^version: /p' "$cwp_setup_ini")
fi

extras0=${#pkg_name[@]}
####### */

####### reading off the installed.db file; adding to pkg_{name,index,drequisites} for extra packages; populating {pkg,pkg_version}_installed variables /*
#######
if [ "$opt_treat_all_packages" ]; then
  pkg_installed=(${!pkg_name[@]})
  
  let j=${#pkg_installed[@]}-1
  if [ $j -ne ${pkg_installed[$j]} ]; then
    echo >&2 "$0: internal error (12)"
    exit 12
  fi

  [ "$opt_with_ports" ] && for P in "${!pkg_index_override[@]}"; do unset -v pkg_installed\[${pkg_index["$P"]}\]; done
else
  P=""; F=""; L=""
  i=$extras0
  while read -r P F L || [ "$P" ]; do
    if [ "$opt_with_ports" ]; then
      psfx="${F#$P-}"; pver="${psfx%-[0-9]*}"; prel="${psfx#$pver-}"; prel="${prel%%.*}"
      V="$pver-$prel"
    fi

    k="${pkg_index[$P]}"
    if ! [ "$k" ]; then  # extra package
      pkg_name[$i]="$P"
      pkg_index["$P"]=$i
      # [ "$opt_with_ports" ] && pkg_version[$i]="$V"
      pkg_drequisites[$i]=""

      pkg_installed[$i]=$i
      # [ "$opt_with_ports" ] && pkg_version_installed[$i]="$V"
      let ++i
      continue
    fi

    [ "$opt_with_ports" ] && n="${pkg_index_override[$P]}" || n=""
    if [ "$n" ]; then  # overridden package
      if [ "$V" == "${pkg_version[$k]}" ] && [ "${pkg_version[$k]}" = "${pkg_version[$n]}" ]; then
        pkg_drequisites[$k]="$(tr -s ' ' '\n' <<<"${pkg_drequisites[$k]}" | sort -u | tr '\n' ' ')"
        pkg_drequisites[$n]="$(tr -s ' ' '\n' <<<"${pkg_drequisites[$n]}" | sort -u | tr '\n' ' ')"
        if [ "${pkg_drequisites[$k]}" != "${pkg_drequisites[$n]}" ]; then
          if [ "$f_dependencies_be_necessary" ]; then
            echo "$0: WARNING: ambiguous installation source:"
            echo "$0:   unable to determine the installation source for"
            echo "$0:   the package '$P' which has the same versions"
            echo "$0:   in the Cygwin standard distribution and in the"
            echo "$0:   Cygwin Ports collection and the installed one;"
            echo "$0: $P: version installed=${V:-?} cygwin=${pkg_version[$k]:-#N/A} cygwinports=${pkg_version[$n]:-#N/A}"
            echo "$0:   lists of the package dependencies are different"
            echo "$0:   in the both sources:"
            echo "$0: $tag_cygwin$P: requires ( ${pkg_drequisites[$k]})"
            echo "$0: $tag_cygwinports$P: requires ( ${pkg_drequisites[$n]})"
            echo "$0:   the version from the Cygwin Ports collection"
            echo "$0:   will be preferred"
            chances_for_misleading="yes"
          fi
        fi
        j=$n
      else
        L="$(sort -V <<<"$V a"$'\n'"${pkg_version[$k]} c"$'\n'"${pkg_version[$n]} p" | sed -n 's/^.* //; H; ${ g; s/\n//gp; }')"
        let j=-1
        case "$L" in
          *ac*) j=$k;;
          *ap*) j=$n;;
            *a) L="${L: -2:1}"
                case "$L" in
                  c)  j=$k;;
                  p)  j=$n;;
                esac
                if [ "$f_dependencies_be_necessary" ]; then
                  echo       "$0: WARNING: unknown installed package version:"
                  echo       "$0:   unable to determine the installation source"
                  echo       "$0:   for the package '$P' which has overridden"
                  echo       "$0:   versions in the Cygwin standard distribution"
                  echo       "$0:   and in the Cygwin Ports collection and the"
                  echo       "$0:   installed version greater than in the both"
                  echo       "$0:   installation sources;"
                  echo       "$0: $P: version installed=${V:-?} cygwin=${pkg_version[$k]:-#N/A} cygwinports=${pkg_version[$n]:-#N/A}"
                  echo       "$0:   dependencies for this package will be taken"
                  echo       "$0:   from the source with the nearest package version"
                  echo -n    "$0:   number: "
                  case "$L" in
                    c)  echo "Cygwin standard distribution";;
                    p)  echo "Cygwin Ports collection (preferred)";;
                  esac
                  chances_for_misleading="yes"
                fi
                ;;
        esac
      fi
      pkg_installed[$j]=$j
      pkg_version_installed[$j]="$V"
    else
      pkg_installed[$k]=$k
      # [ "$opt_with_ports" ] && pkg_version_installed[$k]="$V"
    fi
  done < <(sed '1d' "$installed_db")
fi
####### */

####### nout() /*
#######
nout()
{
  local pp

  if [ $# -gt 1 ]; then
    local k
    local -a t

    for k; do [ "$k" ] && t["$k"]="$k"; done

    for k in ${!t[@]}; do
      if [ "$opt_print_src_tag" ]; then
        [ $k -lt $ports0 ] && pp="$tag_cygwin" || { [ $k -lt $extras0 ] && pp="$tag_cygwinports" || pp="$tag_extra"; }
      else
        pp=""
      fi
      [ "$k" -ge $extras0 ] && pp="${prefix}?"
      [ "${pkg_drequisites_discarded[$k]}" ] && pp="${pp}*"
      #[ "${pkg_drequisites_phantom[$k]}" ] && pp="${pp}!"
      echo -n " ${pp}${pkg_name[$k]}"
    done
  else
    [ "$1" ] || return

    if [ "$opt_print_src_tag" ]; then
      [ "$1" -lt $ports0 ] && pp="$tag_cygwin" || { [ "$1" -lt $extras0 ] && pp="$tag_cygwinports" || pp="$tag_extra"; }
    else
      pp=""
    fi
    [ "$1" -ge $extras0 ] && pp="${pp}?"
    [ "${pkg_drequisites_discarded[$1]}" ] && pp="${pp}*"
    #[ "${pkg_drequisites_phantom[$1]}" ] && pp="${pp}!"
    echo -n " ${pp}${pkg_name[$1]}"
  fi
}
####### */

####### resolving necessary pkg_drequisites and nonleaf_pkgs /*
if [ "$f_dependencies_be_necessary" ]; then
  for i in ${!pkg_name[@]}; do
    if ! [ "${pkg_installed[$i]}" ]; then
      unset -v pkg_drequisites\[$i\]
      continue
    fi

    R="${pkg_drequisites[$i]}"
    pkg_drequisites[$i]=""

    L=""; F=""; V=""
    for P in $R; do
      k="${pkg_index[$P]}"
      if ! [ "$k" ]; then
        F="$F $P"
        continue
      fi
      kk=$k; nn=$k

      [ "$opt_with_ports" ] && n="${pkg_index_override[$P]}" || n=""
      if [ "$n" ]; then
        nn=$n
        k=${pkg_installed[$k]}
        n=${pkg_installed[$n]}
        if [ "$k" ] && [ "$n" ]; then
          echo >&2 "$0: internal error (13)"
          exit 13
        fi
        if [ "$opt_treat_all_packages" ] && { [ ! "$k" ] && [ ! "$n" ]; }; then
          echo >&2 "$0: internal error (14)"
          exit 14
        fi
        [ "$n" ] && k=$n
      else
        k=${pkg_installed[$k]}
      fi

      [ "$k" ] && L="$L"$'\n'$k || { [ $i -lt $ports0 ] && V="$V $kk" || V="$V $nn"; }
    done

    if [ "$F" ]; then
      pkg_drequisites_phantom[$i]="$F"
      if [ ! "$opt_suppress_unneeded_warns" ] && [ ! "$opt_show_phantom" ]; then
        [ $i -lt $ports0 ] && pp="$tag_cygwin" || { [ $i -lt $extras0 ] && pp="$tag_cygwinports" || pp="$tag_extra"; }
        echo "$0: WARNING: phantom dependencies:"
        echo "$0:   installed package '${pp}${pkg_name[$i]}'"
        echo "$0:   requires the following phantom dependencies"
        echo "$0:   (nonpresent packages likely renamed/retired/etc.)"
        echo "$0: ($F )"
        warnings="yes"
      fi
    fi

    pkg_drequisites[$i]="$L"

    if [ "$V" ]; then
      pkg_drequisites_discarded[$i]="$V"
      if [ ! "$opt_suppress_unneeded_warns" ] && [ ! "$opt_show_broken" ]; then
        [ $i -lt $ports0 ] && pp="$tag_cygwin" || { [ $i -lt $extras0 ] && pp="$tag_cygwinports" || pp="$tag_extra"; }
        echo    "$0: WARNING: broken dependencies:"
        echo    "$0:   installed package '${pp}${pkg_name[$i]}'"
        echo    "$0:   depends on the following required packages that are"
        echo    "$0:   not installed; these dependencies will be discarded"
        echo -n "$0: ("; nout $V; echo " )"
        chances_for_misleading="yes"
      fi
    fi
  done
fi

if [ "$f_nonleaves_be_necessary" ]; then
  nonleaf_pkgs="$(IFS=$'\n' sort -n <<<"${pkg_drequisites[*]}" | sed '/^$/d' | uniq -c | sort -r | sed 's/^.* //')"
fi
####### */

####### printing out the warning about extra packages /*
#######
if ! [ "$opt_treat_all_packages" ]; then
  extra_pkgs="${pkg_installed[*]:$extras0}"
  if [ "$extra_pkgs" ] && [ ! "$opt_suppress_unneeded_warns" ]; then
    echo    "$0: WARNING: extra packages with unknown dependencies:"
    echo    "$0:   the following installed packages are not"
    if [ "$opt_with_ports" ]; then
      echo  "$0:   present in the Cygwin standard distribution"
      echo  "$0:   or in the Cygwin Ports collection;"
    else
      echo  "$0:   present in the Cygwin standard distribution;"
    fi
    echo    "$0:   these packages will be prefixed with a question mark (?)"
    echo -n "$0: ("
    for i in $extra_pkgs; do echo -n " ${pkg_name[$i]}"; done
    echo " )"
    [ "$f_dependencies_be_necessary" ] && chances_for_misleading="yes" || warnings="yes"
  fi
fi
####### */

####### printing out the warning about misleading results and/or '\n' if there are warnings printed out /*
#######
if [ "$chances_for_misleading" ]; then
      echo    "$0: WARNING: possible misleading results:"
      echo    "$0:   due to reasons of the above warning(s) the results"
      echo -n "$0:   of dependency resolution may appear to be misleading"
    if [ ${#pkg_drequisites_discarded[@]} -gt 0 ]; then
      echo ";"
      echo    "$0:   packages with discarded broken dependencies"
      echo    "$0:   will be prefixed with an asterisk (*)"
    else
      echo
    fi
fi
{ [ "$chances_for_misleading" ] || [ "$warnings" ]; } && echo
####### */

####### processing for opt_show_overridden /*
#######
if [ "$opt_show_overridden" ]; then
  for P in "${!pkg_index_override[@]}"; do
    k=${pkg_index["$P"]}
    n=${pkg_index_override["$P"]}
    [ "${pkg_installed[$k]}" ] && echo " $P: version installed=${pkg_version_installed[$k]:-?} cygwin=${pkg_version[$k]:-#N/A} cygwinports=${pkg_version[$n]:-#N/A}"
    [ "${pkg_installed[$n]}" ] && echo " $P: version installed=${pkg_version_installed[$n]:-?} cygwin=${pkg_version[$k]:-#N/A} cygwinports=${pkg_version[$n]:-#N/A}"
  done | sort
fi
####### */

####### processing for opt_show_phantom /*
if [ "$opt_show_phantom" ]; then
  for i in ${!pkg_drequisites_phantom[@]}; do
    nout $i; echo ": requires the phantom packages (${pkg_drequisites_phantom[$i]} )"
  done
fi
####### */

####### processing for opt_show_broken /*
if [ "$opt_show_broken" ]; then
  for i in ${!pkg_drequisites_discarded[@]}; do
    nout $i; echo -n ": broken required dependencies ("; nout ${pkg_drequisites_discarded[$i]}; echo " )"
  done
  for i in $extra_pkgs; do
    nout $i; echo ": extra package with unknown dependencies"
  done
fi
####### */

####### processing for opt_show_leaves /*
#######
if [ "$opt_show_leaves" ]; then
  ( # this is a subshell
    for i in $nonleaf_pkgs; do unset -v pkg_installed\[$i\]; done
    for j in ${!pkg_installed[@]}; do nout $j; echo; done
  )
fi
####### */

####### rrequisites_add(), rrequisites_resolve() /*
#######
rrequisites_add()
{
  local -i t="$1"
  local p k
  local -a r

  for p in ${pkg_rrequisites[$1]}; do r["$p"]="$p"; done

  #while shift; [ $# -gt 0 ]; do r["$1"]="$1"; done
  shift; for k; do r["$k"]="$k"; done

  pkg_rrequisites[$t]="${!r[*]}"
}

rrequisites_resolve()
{
  local -a pp

  rr()
  {
    local p e

    for p in ${pkg_drequisites["$1"]}; do
      if ! test "${pp[$p]}"; then
        pp["$p"]="$p"
        if [ "${pkg_rrequisites[$p]+set}" ]; then
          for e in ${pkg_rrequisites[$p]}; do pp["$e"]="$e"; done
        else
          rr "$p"
        fi
      fi
    done
  }

  if ! test "${pkg_rrequisites[$1]+set}"; then
    rr "$1"
    pkg_rrequisites["$1"]="${!pp[*]}"
  fi
}
####### */

####### processing for opt_show_islands, opt_show_interdependent /*
#######
if [ "$opt_show_islands" ] || [ "$opt_show_interdependent" ]; then
  for i in $nonleaf_pkgs; do
    rrequisites_resolve $i
  done
  for i in ${!pkg_installed[@]}; do
    rrequisites_resolve $i
  done

  L=""
  while IFS='' read -r -d $'\x00' L || [ "$L" ]; do
    if ! [ "$opt_show_interdependent" ]; then
      for P in $L; do
        for i in ${!pkg_installed[@]}; do
          [[ "${pkg_drequisites[$i]}" =~ (^|$'\n')"$P"($'\n'|$) ]] && { [[ "$L" =~ (^|$'\n')$i($'\n'|$) ]] || continue 3; }
        done
      done
    fi
    echo -n "("; nout $L; echo " )"
  done <\
  <( # this is a subshell
    for i in ${!pkg_installed[@]}; do
      rrequisites_add $i $i
      echo "$i ${pkg_rrequisites[$i]}"
    done | sort -k 2 | uniq -f 1 --all-repeated=separate | sed 's/ .*$//; s/^$/\x00/'
   )
fi
####### */

####### ddependants_prep(), rdependants_resolve() /*
#######
ddependants_prep()
{
  test "${pkg_ddependants[$1]+set}" && return

  local d

  pkg_ddependants["$1"]=""
  for d in ${!pkg_installed[@]}; do
    [[ "${pkg_drequisites[$d]}" =~ (^|$'\n')"$1"($'\n'|$) ]] && pkg_ddependants["$1"]="${pkg_ddependants[$1]}"$'\n'$d
  done
}

rdependants_resolve()
{
  local -a pp

  rr()
  {
    local p e

    ddependants_prep "$1"
    for p in ${pkg_ddependants["$1"]}; do
      if ! test "${pp[$p]}"; then
        pp["$p"]="$p"
        if [ "${pkg_rdependants[$p]+set}" ]; then
          for e in ${pkg_rdependants[$p]}; do pp["$e"]="$e"; done
        else
          rr "$p"
        fi
      fi
    done
  }

  if ! test "${pkg_rdependants[$1]+set}"; then
    rr "$1"
    pkg_rdependants["$1"]="${!pp[*]}"
  fi
}
####### */

####### processing for opt_show_required, opt_resolve_required, opt_show_dependent, opt_resolve_dependent /*
#######
if [ "$opt_show_required" ] || [ "$opt_resolve_required" ] || [ "$opt_show_dependent" ] || [ "$opt_resolve_dependent" ]; then
  for P in "${@:$OPTIND}"; do
    k=${pkg_index["$P"]}
    if [ "$k" ]; then
      [ "$opt_with_ports" ] && n="${pkg_index_override[$P]}" || n=""
      if [ "$n" ]; then
        k=${pkg_installed[$k]}
        n=${pkg_installed[$n]}
        if [ "$k" ] && [ "$n" ]; then
          echo >&2 "$0: internal error (13)"
          exit 13
        fi
        if [ "$opt_treat_all_packages" ] && { [ ! "$k" ] && [ ! "$n" ]; }; then
          echo >&2 "$0: internal error (14)"
          exit 14
        fi
        [ "$n" ] && k=$n
      else
        k=${pkg_installed[$k]}
      fi
      if ! [ "$k" ]; then
        echo " $P: package is not installed"
        continue
      fi
    else
      echo " $P: package does not exist"
      continue
    fi

    if [ "$opt_show_required" ]; then
      nout $k; echo -n ": requires ("; nout ${pkg_drequisites[$k]}; echo " )"
    fi

    if [ "$opt_resolve_required" ]; then
      rrequisites_resolve $k
      nout $k; echo -n ": recursively requires ("; nout ${pkg_rrequisites[$k]}; echo " )"
    fi

    if [ "$opt_show_dependent" ]; then
      ddependants_prep $k
      nout $k; echo -n ": is needed for ("; nout ${pkg_ddependants[$k]}; echo " )"
    fi

    if [ "$opt_resolve_dependent" ]; then
      rdependants_resolve $k
      nout $k; echo -n ": is recursively needed for ("; nout ${pkg_rdependants[$k]}; echo " )"
    fi
  done
fi
####### */

### michaelus */

# vim: et ts=2 sw=2 sts=0
# vim: fdc=4 fdl=1 fdm=marker fmr=/*,*/
