#!/bin/bash
# Queries the ABS
# License: GPL3

## TODO
# * Add license text
# * Create symlinks from pkgbase to pkgname[@] for easy package finding

## GOALS
# * Have a searchable database of PKGBUILD metadata
# * Have an interface for source-only builds
# * Possibility to hook up ABS dirs besides ABSROOT (low priority)
# * Tell updates and non available binary packages (working on this)

source $(dirname $(command -v $0))/toru-utils

# Saves contents on a named cache
# $1 cache name (repo)
# $2+ contents
function store_cache {
  cache=$1; shift

  [ -z "$cache" ] && return 1

  cat $@ > ${TORUPATH}/${cache}.cache

  return $?
}

# Return cache contents
# $1 cache name
read_cache() {
  cat ${TORUPATH}/${1}.cache 2>/dev/null

  return $?
}

##
#  usage : get_full_version( $epoch, $pkgver, $pkgrel )
# return : full version spec, including epoch (if necessary), pkgver, pkgrel
##
get_full_version() {
   if [[ $1 -eq 0 ]]; then
      # zero epoch case, don't include it in version
       echo $2-$3
   else
       echo $1:$2-$3
   fi
}

# Outputs an ordered package-fullpkgver array
print_package_array() {
   echo "$@" | tr " " "\n" | sort -u
}


# Gets repo.db contents (unordered)
# $1 repo
get_db_contents() {
  [ ! -r /var/lib/pacman/sync/$1.db ] && return 0

  bsdtar -tf /var/lib/pacman/sync/$1.db | cut -d'/' -f1 | sort -u
}

# Get the pkgname
# pkgname from pkgver separator can be either '-' or ' '
extract_pkgname() {
  echo "$@" | tr " " "\n" | sed "s/^\(.\+\)[- ][^-]\+-[^-]\+$/\1/"
}

# Get all the pkgnames from a file
# pkgname from pkgver separator can be either '-' or ' '
extract_pkgname_from_file() {
  sed "s/^\(.\+\)[- ][^-]\+-[^-]\+$/\1/" $1
}

# Split pkgnames from pkgvers
split_pkgname_from_pkgver() {
  sed "s/^\(.\+\)-\([^-]\+-[^-]\+\)$/\1 \2/" $1
}

# Get the fullpkgver
# pkgname from pkgver separator can be either '-' or ' '
extract_fullpkgver() {
  echo "$@" | tr " " "\n" | sed "s/^.\+[ -]\([^-]\+-[^-]\+\)$/\1/"
}

# Checks if $1 is a valid repo
is_repo() {
  if ! in_array ${1} ${REPOS[@]}; then
    $quiet || warning "${1} is not a valid repo (check REPOS array at libretools.conf)"
    return 1
  fi
}

# Updates the database by finding all PKGBUILDS
# Workflow:
# * Find all PKGBUILDs on the ABS repo specified
# * Get all packages already on package repos
# * Compare them
# Args:
update() {
  local update_sync_file=false
# The PKGBUILDs found
  local -a pkgbuilds=()
# The list of pkgname-fullpkgver
  local -a packages_in_abs=()
  local -a pkg_updates=()
  local -a package_paths=()

# Traverse all specified repos
  for __repo in $@; do
# Check if the repo is set as such, otherwise skip
    is_repo ${__repo} || continue

# Fullpath of the repo
    _repopath=$(readlink -f ${__repo})

# This is the syncfile, stores the last date as content and mtime
    local lastsyncfile=${TORUPATH}/${__repo}.lastsync

# Find all the PKGBUILDs newer than the last update
# Update newer, otherwise everything
    if [[ $force = true || ! -e ${lastsyncfile} ]]; then

      $quiet || warning "Forcing upgrade"
# Get all PKGBUILDs
      pkgbuilds=($(find ${_repopath} -maxdepth 2 -type f -name 'PKGBUILD'))

    else

# Only find newer than lastsyncfile and read everything else from cache
      pkgbuilds=($(find ${_repopath} -maxdepth 2 -type f -name 'PKGBUILD' -newer ${lastsyncfile}))
      packages_in_abs=($(read_cache ${__repo}))

      $quiet || msg2 "Getting ${#packages_in_abs[@]} packages from cache"

    fi

    package_paths=($(read_cache ${__repo}.paths || true))

# Inform how many PKGBUILDS were found and quit immediately if none
    $quiet || msg "Found $((${#pkgbuilds[*]}-1)) PKGBUILDs to update"

# Traverse all found PKGBUILDs
    for _pkgbuild in ${pkgbuilds[@]}; do
# Update the sync file because there are pkgbuilds to update
      update_sync_file=true

# Load PKGBUILD's metadata
      source ${_pkgbuild} || continue

# Guess pkgbase from PKGBUILD's basedir
      _pkgpath=$(dirname "${_pkgbuild}")
      _pkgbase=${pkgbase:-${pkgname[0]}}

# We won't need this (all unsets are for memory efficiency)
      unset build package url md5sums install pkgdesc backup options
# TODO fill a license list
      unset license
# TODO create source tarballs?
      unset mksource
# TODO solve dependency tree?
      unset depends makedepends

      for _pkg in ${pkgname[@]}; do
# Keep removing unneeded stuff
        unset package_${_pkg} >/dev/null 2>&1 || true
# Fill the list of packages to find
        packages_in_abs+=($_pkg-$(get_full_version ${epoch:-0} $pkgver $pkgrel))
        package_paths+=($_pkg:$_pkgpath)
      done # end pkgnames

      unset pkgbase pkgname pkgver pkgrel source epoch
    done # end pkgbuilds

# Sync! (Only if there was an actual sync)
    ${update_sync_file} && lastsync ${lastsyncfile}

    if [ "${lastsyncfile}" -nt "${TORUPATH}/${__repo}.paths.cache" ]; then
      print_package_array "${package_paths[@]}" > $TMPDIR/paths
      store_cache ${__repo}.paths $TMPDIR/paths
    fi

# If there isn't an update cache or it's older than the last update, we check
    if [ "${lastsyncfile}" -nt "${TORUPATH}/${__repo}.updates.cache" ]; then

# Get repo database contents
      packages_in_sync=($(get_db_contents ${__repo}))
# Drops arrays into files
      print_package_array "${packages_in_abs[@]}" > ${TMPDIR}/packages_in_abs
      print_package_array "${packages_in_sync[@]}" > ${TMPDIR}/packages_in_sync

# Work with files
      unset packages_in_abs package_in_sync

# Use a different separator for pkgnames and pkgvers
# so we can join them by pkgname (first field)
      split_pkgname_from_pkgver ${TMPDIR}/packages_in_abs | sort -k1b,1 > ${TMPDIR}/in_abs
      split_pkgname_from_pkgver ${TMPDIR}/packages_in_sync | sort -k1b,1 > ${TMPDIR}/in_sync

      $quiet || msg "These packages are available to update"
# Join both files by pkgname, the end result is:
# pkgname syncver absver
      join ${TMPDIR}/in_sync ${TMPDIR}/in_abs | \
      while read need_line; do
        _pkg=$(echo "${need_line}" | cut -d' ' -f1)
        _syncver=$(echo "${need_line}" | cut -d' ' -f2)
        _absver=$(echo "${need_line}" | cut -d' ' -f3)

# If the versions differ we need an update
# TODO move this to update query
        if [ "${_syncver}" != "${_absver}" ]; then
          $quiet || msg2 "$_pkg update from $_syncver to $_absver"
          $quiet && echo "$_pkg"

# FIXME this works all right but it's unset once the while ends
          #pkg_updates+=("$_pkg")

# Fix for the above problem, but it access the file every time instead of
# puting all packages together once
          echo $_pkg >> ${TMPDIR}/updates

        fi
      done # end need_line

      unset _pkg _syncver _absver need_line

# Save the cache
      store_cache ${__repo} ${TMPDIR}/packages_in_abs

# See above FIXME
  #    print_package_array "${updates[@]}" > ${TMPDIR}/updates
      if [ -r ${TMPDIR}/updates ]; then
        store_cache ${__repo}.updates ${TMPDIR}/updates
      fi

    else
      $quiet || msg "Reading updates from cache..."
      read_cache ${__repo}.updates
    fi

  done # end repos
}

# Find all the packages that are missing from the repo dbs (aka not built)
missing() {
  true
}

## Finds a PKGBUILD on toru's path cache
## usage: where_is <pkgname>
# Look in all caches but pick the first one
where_is() {
  local __repo
  local _path
  for __repo in ${REPOS[@]}; do
    _path=$(grep "^${1}:" "${TORUPATH}/${__repo}.paths.cache" 2>/dev/null |
        cut -d: -f2)

    [ -n "${_path}" ] && break
  done

  [ -z "$_path" ] && return 1

  echo ${_path}
}

# TODO: clean usage instructions
function usage {
    echo ""
    echo "$0 [options] repo1 ... repon"
    echo ""
    echo "Make a db containing PKGBUILD metadata."
    echo ""
    echo "-h : this message"
#    echo "-a : update all repos at once"
    echo "-u : update repo information"
    echo "-q : quiet"
    echo "-f : rebuild the db even if it is updated"
    echo "-p <pkgname>: return the path for pkgname"
    echo ""
    exit 1
}

## MAIN
commands=()
repos=()
quiet=false
force=false
while getopts 'haqfpum' arg; do
  case $arg in
      h) usage; exit 0 ;;
# TODO: Update all repos on $REPOS array
#      a) update_all_repos ;;
      q) quiet=true ;;
      f) force=true ;;
      u) commands+=(update);;
      p) shift $(( OPTIND - 1 ))
          where_is "$1" || exit 1;;
      m) commands+=(missing);;
  esac

  shift $(( OPTIND - 1 ))
done


TMPDIR=$(mktemp -d)

[[ -z ${TMPDIR} ]] && exit 1

${commands[0]} ${@}

exit $?