#!/bin/sh
# author polikuo
# version 1.7
# June 27 2018

. /etc/init.d/tc-functions
useBusybox

showhelp() {
  [ "$C" ] && echo -n ${YELLOW} 1>&2
  local BN=$(basename $0)
  echo "${BN}: A script that seeks dependencies in a given directory" 1>&2
  echo "Usage: ${BN} [-option] directory [append your list here ...]" 1>&2
  echo 'Available Options (default -ns)
  -c     colorful output
  -d     show only the detected tczs, without custom extensions (blue)
  -g     graph .tcz.tree (magenta)
  -h     show this helpinfo (yellow)
  -n     show dependencies that can not be found (red)
  -p     scan provides.db for dependencies
  -r     show recursively scanned tczs (green)
  -s     show shortened tczs (cyan)
  -t DIR set tcedir to DIR (default /etc/sysconfig/tcedir)' 1>&2
  [ "$C" ] && echo -n ${NORMAL} 1>&2
  exit 1
}

copy2fs() {
  local COPY2FS
  [ "$C" ] && echo -n ${RED} 1>&2
  if [ -f "${TCEDIR}/copy2fs.lst" ]; then
    echo -n "WARNING! copy2fs.lst found in ${TCEDIR}" 1>&2
    echo ", please disable it and reboot" 1>&2
    COPY2FS="true"
  fi
  if [ -f "${TCEDIR}/copy2fs.flg" ]; then
    echo -n "WARNING! copy2fs.flg found in ${TCEDIR}" 1>&2
    echo ", please disable it and reboot" 1>&2
    COPY2FS="true"
  fi
  [ "$C" ] && echo -n ${NORMAL} 1>&2
  [ "$COPY2FS" ] && exit 1
}

# additional custom extensions
extra() {
  local EXT SHEBANG
  for EXT in $@; do
    EXT=${EXT##*/}
    EXT=${EXT%.tcz}
    EXT=${EXT//KERNEL/$KERNELVER}
    APPEND="${APPEND}${EXT} "
  done
  # check script
  # shebang limit is set to 128, head -n1 is slow with binary files
  SHEBANG=$(find -type f -exec head -c128 {} + 2> /dev/null | grep '^#!')
  if [ "$SHEBANG" ]; then
    case "$SHEBANG" in *bash* ) APPEND="${APPEND}bash " ;; esac
    case "$SHEBANG" in *perl* ) APPEND="${APPEND}perl5 " ;; esac
    case "$SHEBANG" in
      *php* ) echo "${RED}WARNING! php script detected${NORMAL}" 1>&2 ;;
    esac
    case "$SHEBANG" in
      *python* ) echo "${RED}WARNING! python script detected${NORMAL}" 1>&2 ;;
    esac
  fi
}

unique() {
  local A BUFFER=' '
  for A in "$@"; do
    case "$BUFFER" in
      *" ${A} "* ) :;;
      * ) BUFFER="${BUFFER}${A} ";;
    esac
  done
  echo "$BUFFER"
}

rm_matched_in_list() {
  local B BUFFER="$1" REMOVE OUT=' '
  shift
  REMOVE=" $(echo $@) "
  for B in $BUFFER; do
    case "$REMOVE" in
      *" ${B} "* ) :;;
      * ) OUT="${OUT}${B} ";;
    esac
  done
  echo "$OUT"
}

# check if TCZS existed
chkexist() {
  local FILE QUIT
  [ "$C" ] && echo -n ${RED}
  for FILE in $@; do
    # $KERNELVER already replaced
    [ -s "${TCEDIR}/optional/${FILE}.tcz" ] || {
      echo "${FILE}.tcz not found or size is zero" 1>&2
      QUIT="1"
    }
  done
  if [ "$QUIT" ]; then
    echo "Please move the tczs and the dep files into your" 1>&2
    echo "${TCEDIR}/optional/${NORMAL}" 1>&2
    exit 1
  fi
  [ "$C" ] && echo -n ${NORMAL}
}

# check for redundant
del_redundant() {
  local LIST RPT REMOVE GRAPH
  # $KERNELVER already replaced
  LIST=" $(echo $TCZS) "
  for RPT in $TCZS; do
    if [ -s "${TCEDIR}/optional/${RPT}.tcz.dep" ]; then
      for REMOVE in $(cat "${TCEDIR}/optional/${RPT}.tcz.dep"); do
        REMOVE=${REMOVE##*/}
        REMOVE=${REMOVE%.tcz}
        REMOVE=${REMOVE//KERNEL/$KERNELVER}
        LIST="${LIST/ ${REMOVE} / }"
      done
    fi
  done
  LIST=$(echo ${LIST//$KERNELVER/KERNEL} | tr ' ' '\n' | sort)
  [ "$C" ] && echo -n ${CYAN}
  [ "$S" ] && echo "$LIST" | sed 's:$:.tcz:g'
  [ "$S" ] && [ "$G" ] && echo ''
  if [ "$G" ]; then
    [ "$C" ] && echo -n ${MAGENTA}
    # use for loop to prevent early quitting in awk
    for GRAPH in $LIST; do
      graph $GRAPH
    done
  fi
  [ "$C" ] && echo -n ${NORMAL}
}

# recursively scan dependencies
rscan() {
  cd "${TCEDIR}/optional" || exit 1
  echo $@ | awk -v KERNELVER="$KERNELVER" '
  function awk_scan(name, depfile, line) {
    gsub(/ /, "", name)
    if (name) {
      sub(/\.tcz/, "", name)
      sub(/KERNEL/, KERNELVER, name)
      depfile = name ".tcz.dep"
      print name
      while (getline line < depfile > 0)
        awk_scan(line)
      close(depfile)
    }
  }
  {for(i=1;i<=NF;i++) awk_scan($i)}'
  cd - &> /dev/null
}

scandir() {
  # global DETECTED NOTFOUND
  local FIND_EXEC LDD_OUT SELF LIBS S NF

  FIND_EXEC=$(for A in $(find . -not -type d); do [ -x $A ] && echo $A; done)
  LDD_OUT=$(ldd $FIND_EXEC 2> /dev/null)
  SELF=$(echo "$FIND_EXEC" | sed 's:.::')
  LIBS=$(
    echo "$LDD_OUT" | awk '/=>/ && !/not found$/{U[$3]=1}
    END{for (var in U) printf var " "}'
  )
  LIBS=$(rm_matched_in_list "$LIBS" $SELF)

  DETECTED=$(realpath $LIBS 2> /dev/null | grep '^/tmp/tcloop/' | cut -d '/' -f 4)
  DETECTED=$(unique $DETECTED)

  if [ "$N" ]; then
    SELF=$(echo "$FIND_EXEC" | sed 's:.*/::')
    NF=$(
      echo "$LDD_OUT" | awk '/not found$/{U[$1]=1}
      END{for (var in U) printf var " "}'
    )
    NF=$(rm_matched_in_list "$NF " $SELF)
    NOTFOUND=$(echo $NF | tr ' ' '\n')
  fi
}

searchDB() {
  local NF=$NOTFOUND TARGET PROVIDES ADDED NOTDOWNLOADED CLUSTER NOWHERE
  for TARGET in $NF; do
    PROVIDES=$(
      awk 'BEGIN {FS="\n";RS=""}
      /\/'${TARGET}'\n/ || /\/'${TARGET}'$/ {print $1}' "${TCEDIR}/${DB}"
    )
    if [ "$PROVIDES" ]; then
      if [ $(echo "$PROVIDES" | wc -l) -eq 1 ]; then
        if [ -s "${TCEDIR}/optional/${PROVIDES}" ]; then
          APPEND="${APPEND}${PROVIDES} "
          ADDED=$(
            echo "$ADDED"
            echo "${PROVIDES} provides ${TARGET}, list appended"
          )
          NOTFOUND=$(echo "$NOTFOUND" | grep -v "^${TARGET}$")
        else
          NOTDOWNLOADED=$(
            echo "$NOTDOWNLOADED"
            echo "${TARGET} can be found in: ${PROVIDES}"
          )
        fi
      else
        CLUSTER=$(
          echo "$CLUSTER"
          echo $TARGET can be found in: $PROVIDES
        )
      fi
    else
      NOWHERE=$(
        echo "$NOWHERE"
        echo "$TARGET"
      )
    fi
  done
  if [ "$ADDED" ]; then
    echo "Note: These dependencies are detected in ${DB}:${ADDED}" 1>&2
    echo '' 1>&2
  fi
  if [ "$N" ]; then
    if [ "$NOWHERE" ]; then
      echo "These dependencies can not be found anywhere:${NOWHERE}" 1>&2
      echo '' 1>&2
    fi
    if [ "$NOTDOWNLOADED" ]; then
      echo "These dependencies are not presented in ${TCEDIR}/optional" 1>&2
      echo "Please download these extensions:${NOTDOWNLOADED}" 1>&2
      echo '' 1>&2
    fi
    if [ "$CLUSTER" ]; then
      echo "Found potential deps in ${DB}, but not proccessed:${CLUSTER}" 1>&2
      echo '' 1>&2
    fi
  fi
}

graph() {
  cd "${TCEDIR}/optional"
  echo $1 | awk -v KERNELVER="$KERNELVER" '
  BEGIN{layer = 0}
  function dep_tree(name, depfile, line) {
    depfile = name ".tcz.dep"
    layer += 1
    while(getline line < depfile > 0) {
      gsub(/ /, "", line)
      sub(/\.tcz$/, "", line)
      sub(/KERNEL/, KERNELVER, line)
      if(line) {
        for(i=0;i<layer;i++) printf "   "
        print line ".tcz"
        dep_tree(line)
      }
    }
    close(depfile)
    layer -= 1
  }
  {
    sub(/KERNEL/, KERNELVER)
    print $0 ".tcz"
    layer = 0
    dep_tree($0)
  }'
  cd - &> /dev/null
}

# Options
C='' D='' G='' N='' P='' R='' S=''
while getopts cdghnprst: option 2> /dev/null
do
  case "$option" in
    c ) C="true";;
    d ) D="true";;
    g ) G="true";;
    h ) showhelp;;
    n ) N="true";;
    p ) P="true";;
    r ) R="true";;
    s ) S="true";;
    t ) TCEDIR=$(realpath $OPTARG) || echo "Using default directory" 1>&2;;
    * ) echo "Invalid options"; showhelp;;
  esac
done
[ "$C" ] || RED='' GREEN='' YELLOW='' BLUE='' MAGENTA='' CYAN='' NORMAL=''
[ "$D" ] || [ "$G" ] || [ "$N" ] || [ "$R" ] || [ "$S" ] || N="true" S="true"
shift $((OPTIND-1))

# directory to scan
[ "$(echo $1)" ] || ! echo "${RED}Where to search ?${NORMAL}" 1>&2 || showhelp
[ -d "$1" ] || ! echo "${RED}'${1}' is not a directory${NORMAL}" 1>&2 || showhelp
cd $1
shift

# main
[ "$TCEDIR" ] || TCEDIR=$(realpath /etc/sysconfig/tcedir)
readonly TCEDIR
[ -d "$TCEDIR" ] || ! echo "${RED}${TCEDIR} is not a directory${NORMAL}" 1>&2 || exit 1
copy2fs

readonly KERNELVER=$(uname -r)
APPEND=' '
extra $@

DETECTED=''
NOTFOUND=''
scandir

if [ "$P" ]; then
  [ "$C" ] && echo -n ${RED}
  readonly DB="provides.db"
  getMirror
  cd "$TCEDIR"
  if zsync -i "${TCEDIR}/${DB}" -q "${MIRROR}/${DB}.zsync"
  then
    rm -f "${DB}.zs-old"
  else
    wget -O "${TCEDIR}/${DB}" "${MIRROR}/${DB}" &> /dev/null
  fi
  cd - &> /dev/null
  searchDB
  [ "$C" ] && echo -n ${NORMAL}
fi

SCAN="${APPEND}${DETECTED}"

if [ "$G" ] || [ "$R" ] || [ "$S" ]; then
  if [ "$(echo $SCAN)" ]; then
    TCZS=$(rscan $SCAN)
    TCZS=$(unique $TCZS)
    chkexist $TCZS
  else
    TCZS=''
  fi
fi

# output
if [ "$D" ] && [ "$(echo $DETECTED)" ]; then
  chkexist $DETECTED
  [ "$C" ] && echo -n ${BLUE}
  echo ${DETECTED//$KERNELVER/KERNEL} | tr ' ' '\n' | sort | sed 's:$:.tcz:g'
  [ "$N" ] && [ "$NOTFOUND" ] || [ "$R" ] || [ "$S" ] && echo ''
  [ "$C" ] && echo -n ${NORMAL}
fi

if [ "$N" ] && [ "$(echo $NOTFOUND)" ]; then
  [ "$C" ] && echo -n ${RED} 1>&2
  echo 'Missing dependencies:' 1>&2
  echo "$NOTFOUND" 1>&2
  [ "$R" ] || [ "$S" ] || [ "$G" ] || [ "$(echo $TCZS)" ] && echo '' 1>&2
  [ "$C" ] && echo -n ${NORMAL} 1>&2
fi

if [ "$R" ] && [ "$(echo $TCZS)" ]; then
  [ "$C" ] && echo -n ${GREEN}
  echo ${TCZS//$KERNELVER/KERNEL} | tr ' ' '\n' | sort | sed 's:$:.tcz:g'
  [ "$S" ] || [ "$G" ] || [ "$(echo $TCZS)" ] && echo ''
  [ "$C" ] && echo -n ${NORMAL}
fi

if [ "$(echo $TCZS)" ]; then
  [ "$S" ] || [ "$G" ] && del_redundant
fi

exit 0