#!/bin/bash
#
# Load configuration
#
CONF=${CONF:-/etc/sboui/sboui-backend.conf}
. $CONF

# Build location
TMP=${TMP:-/tmp/SBo}

# Where to drop completed package
OUTPUT=${OUTPUT:-/tmp}

################################################################################
# Removes quotes around string
function remove_quotes ()
{
  local STRING=$1
  STRING=${STRING#\"}
  STRING=${STRING%\"}
  echo $STRING
}

################################################################################
# Determines number of elements in a list. Note that the input list must be
# in quotes for this to work properly (otherwise it will only return 1 or 0).
function listlen ()
{
  local LIST=$1
  local LEN=0
  local ELEM=""

  for ELEM in $LIST; do
    let "LEN+=1"
  done

  echo $LEN
}

################################################################################
# Gets name of package
function get_package_name ()
{
  local PKG=$1

  # Get rid of trailing stuff
  local NAME=${PKG%*-*} # Remove build number / tag
  NAME=${NAME%*-*}      # Remove arch
  NAME=${NAME%*-*}      # Remove version
  echo $NAME
}

################################################################################
# Gets SlackBuild version from VERSION string in *.info file
function get_current_version ()
{
  local BUILD=$1

  # Make sure we are at the top of the git tree
  cd $REPO_DIR

  # Get path of SlackBuild.info
  local INFILE=$(find . -maxdepth 3 -mindepth 3 -name "$BUILD".info)

  # Get version number from SlackBuild.info
  . $INFILE

  echo $VERSION
}

################################################################################
# Checks if Ponce's git repo is being used
function check_ponce_repo ()
{
  if [ "$REPO" == "https://github.com/Ponce/slackbuilds.git" ]; then
    echo 1
  else
    echo 0
  fi
}

################################################################################
# Returns the git branch name that is currently being used
function get_git_branch ()
{
  # Go to top-level git tree
  cd $REPO_DIR
 
  # Parse git status string
  local STAT=$(git status | head -1)
  local BRANCH=$(echo "$STAT" | cut -d' ' -f3)
  echo $BRANCH
}

################################################################################
# Switches to specified git branch
function checkout_git_branch ()
{
  local GITBRANCH="$1"
  local CHECK=0

  # Go to top-level git tree
  cd $REPO_DIR

  # Get branch currently being used
  local CURRBRANCH=$(get_git_branch)

  # Checkout new branch if needed
  if [ "$CURRBRANCH" != "$GITBRANCH" ]; then
    git checkout $GITBRANCH > /dev/null
    CHECK=$?
  fi

  echo $CHECK
}

################################################################################
# Syncs git repo with local copy
function update ()
{
  # Check if Ponce's git repo is being used
  local PONCEREPO=$(check_ponce_repo)
  local BASEDIR=$(basename $REPO_DIR)
  local CHECK=0
  
  # Create local git directory if needed
  if [ ! -d "$REPO_DIR"/.git ]; then
    mkdir -p $REPO_DIR
    cd $REPO_DIR/../
    rm -rf $BASEDIR
    git clone $REPO $BASEDIR
    CHECK=$?
    if [ $CHECK -ne 0 ]; then
      exit $CHECK
    fi
    if [ $PONCEREPO -eq 0 ]; then
      cd $BASEDIR
      CHECK=$(checkout_git_branch $BRANCH)
      if [ $CHECK -ne 0 ]; then
        exit $CHECK
      fi
    fi

  # Master branch can just use git pull, but Ponce's must be re-downloaded
  # http://www.linuxquestions.org/questions/slackware-14/what-is-the-correct-url-and-command-to-git-clone-slackbuilds-current-4175578557/#post5537842
  else
    if [ $PONCEREPO -eq 0 ]; then
      cd $REPO_DIR
      CHECK=$(checkout_git_branch $BRANCH)
      if [ $CHECK -ne 0 ]; then
        exit $CHECK
      fi
      git clean -df
      git checkout -- .
      git pull
      CHECK=$?
      if [ $CHECK -ne 0 ]; then
        exit $CHECK
      fi
    else
      cd $REPO_DIR/../
      rm -rf $BASEDIR
      git clone $REPO $BASEDIR 
      CHECK=$?
      if [ $CHECK -ne 0 ]; then
        exit $CHECK
      fi
    fi
  fi
}

################################################################################
# Checks if a SlackBuild is actually installed. Returns package name if so.
function check_installed ()
{
  local BUILD=$1
  local PKGLIST=$(find /var/lib/pkgtools/packages -maxdepth 1 -name "$BUILD*")
  local INSTALLED=0
  local PKG BUILDNAME

  # There can be multiple packages fitting the pattern, so loop through them
  # and check against requested
  if [ -n "$PKGLIST" ]; then
    for PKG in $PKGLIST
    do
      PKG=$(basename "$PKG")
      BUILDNAME=$(get_package_name "$PKG")
      if [ "$BUILDNAME" == "$BUILD" ]; then
        INSTALLED=1
        break
      fi
    done
  fi

  if [ $INSTALLED -eq 1 ]; then
    echo $PKG
  else
    echo "Not installed"
  fi
}

################################################################################
# Gets y/n choice
function user_choice ()
{
  local __MSG=$1
  local __CHOICE=$2
  local TEMPCHOICE

  local VALIDCHOICE=0
  while [ $VALIDCHOICE -eq 0 ]
  do
    echo -ne "$__MSG (y/n): "
    read TEMPCHOICE
    if [[ "$TEMPCHOICE" == "y" || "$TEMPCHOICE" == "Y" ]]; then
      TEMPCHOICE="y"
      VALIDCHOICE=1
    elif [[ "$TEMPCHOICE" == "n" || "$TEMPCHOICE" == "N" ]]; then
      TEMPCHOICE="n"
      VALIDCHOICE=1
    else
     echo "Please enter y or n."
    fi
  done

  # This trick is needed to echo to stdout. See usage in install_slackbuild.
  eval $__CHOICE="'$TEMPCHOICE'"
}

################################################################################
# Gets list of SlackBuilds specified on the command line
function get_buildlist
{
  local ARG BUILDLIST
  BUILDLIST=""

  for ARG in $@
  do
    if [ "${ARG:0:1}" == "-" ]; then
      continue
    elif [ "${ARG%=*}" == "sourcedir" ]; then
      continue
    else
      BUILDLIST="$BUILDLIST $ARG"
    fi
  done

  echo $BUILDLIST
}

################################################################################
# Parses command-line options for install
function parse_install_opts ()
{
  local ARG
  for ARG in $@
  do
    if [ "${ARG:0:1}" == "-" ]; then
      case $ARG in
      "--force" | "-f")
          FORCE=1
          ;;
      *)
          echo "Unrecognized install option $ARG."
          exit 1
          ;;
      esac
    elif [ "${ARG%=*}" == "sourcedir" ]; then
      SOURCEOPT=1
      SOURCEDIR=${ARG#*=}
    fi
  done
}

################################################################################
# Extracts URLs of source files to download
function sources_from_info ()
{
  local INFILE=$1
  if [ $# -eq 2 ]; then
    local FORCE32=$2
  else
    local FORCE32="noforce-32-bit"
  fi
  local FORCE32=$2
  local ARCH=$(uname -m)
  local SOURCES

  # Read variables from .info file
  . $INFILE

  # First try to use DOWNLOAD_x86_64 link for 64-bit
  if [ "$ARCH" == "x86_64" ]; then
    SOURCES=$DOWNLOAD_x86_64
  fi

  # Use regular DOWNLOAD= line for x86 architecture, if there's no 64-bit source
  # download, or if forcing 32-bit
  if [[ "$ARCH" != "x86_64" || -z "$SOURCES" || "$FORCE32" == "force-32-bit" ]]; then
    SOURCES=$DOWNLOAD
  fi

  echo $SOURCES
}

################################################################################
# Extracts MD5sums of source files to download
function md5sums_from_info ()
{
  local INFILE=$1
  if [ $# -eq 2 ]; then
    local FORCE32=$2
  else
    local FORCE32="noforce-32-bit"
  fi
  local ARCH=$(uname -m)
  local SOURCES MD5SUMS

  # Read variables from .info file
  . $INFILE

  # First try to use MD5SUM_x86_64 link for 64-bit
  if [ "$ARCH" == "x86_64" ]; then
    SOURCES=$DOWNLOAD_x86_64
    MD5SUMS=$MD5SUM_x86_64
  fi

  # Use regular MD5SUM= line for x86 architecture, if there's no 64-bit source
  # download, or if forcing 32-bit
  if [[ "$ARCH" != "x86_64" || -z "$SOURCES" || "$FORCE32" == "force-32-bit" ]]; then
    MD5SUMS=$MD5SUM
  fi

  echo $MD5SUMS
}

################################################################################
# Removes source files
function remove_sources ()
{
  local SOURCES="$@"
  local SOURCE
  for SOURCE in $SOURCES
  do
    SOURCE=$(basename "$SOURCE")
    if [ -f "$SOURCE" ]; then
      rm $SOURCE
    fi
  done
}

################################################################################
# Installs or reinstalls SlackBuild
function install_slackbuild ()
{
  local BUILD=$1
  local MSG CHOICE MD5ARRAY ITEM MD5CHK

  # Get SlackBuild path
  cd $REPO_DIR
  local BUILDPATH=$(find . -maxdepth 2 -mindepth 2 -name "$BUILD")
  if [ -z "$BUILDPATH" ]; then
    echo "Error: there is no SlackBuild named $BUILD."
    exit 1
  fi

  # Check if SlackBuild is installed on system and get package name
  local INSTALLEDPKG=$(check_installed "$BUILD")

  # Offer reinstallation if it is already installed
  local CURRENTVERSION=$(get_current_version "$BUILD")
  if [ "$INSTALLEDPKG" != "Not installed" ]; then

    # Ask about reinstallation
    echo "$BUILD is already installed."
    echo "Installed package: $INSTALLEDPKG"
    echo "Available version: $CURRENTVERSION"
    MSG="Do you want to remove the existing package and reinstall?"

    # Return if user chooses to
    if [ $FORCE -eq 0 ]; then
      user_choice "$MSG" CHOICE
      if [ "$CHOICE" != "y" ]; then
        return
      fi
    fi
  fi

  # Go to SlackBuild directory
  cd $BUILDPATH

  # Determine source download strings
  local SOURCES=$(sources_from_info "$BUILD".info)

  # Some SlackBuilds have an UNSUPPORTED string if it's not supported on
  # 64-bit
  local FORCE32="no-force-32-bit"
  local ARCH=$(uname -m)
  if [[ "$ARCH" == "x86_64" && "$SOURCES" == "UNSUPPORTED" ]]; then
    MSG="$BUILD is unsupported on x86_64, but it may work with mutlilib.\nTry installing 32-bit version?"
    user_choice "$MSG" CHOICE
    if [ "$CHOICE" == "y" ]; then
      FORCE32="force-32-bit"
      SOURCES=$(sources_from_info "$BUILD".info $FORCE32)
    else
      exit 0
    fi
  fi

  # Get MD5sums for source downloads and put them into an array
  local MD5SUMS=$(md5sums_from_info "$BUILD".info $FORCE32)
  local COUNT=0
  for ITEM in $MD5SUMS
  do
    MD5ARRAY["$COUNT"]=$ITEM
    let "COUNT+=1"
  done

  # Remove any existing source files
  remove_sources $SOURCES

  # Create file to mark time
  local TMPFILE=$(mktemp /tmp/sboui.XXXXXX)

  # Download and check MD5SUMs
  local SOURCERR=0
  local MD5ERR=0
  COUNT=0
  for SOURCE in $SOURCES
  do
    # Copy sources from specified directory
    if [ $SOURCEOPT -eq 1 ]; then
      SOURCE=$(basename "$SOURCE")
      if [ -z "$(find "$SOURCEDIR" -maxdepth 1 -name "$SOURCE")" ]; then
        echo "Error: source file $SOURCE not present in $SOURCEDIR."
        exit 1
      else
        if [ "$(readlink -f $SOURCEDIR)" != "$REPO_DIR" ]; then
          cp $SOURCEDIR/$SOURCE .
        fi
      fi

    # Download and check for error
    else
      wget "$SOURCE"
      if [ $? != 0 ]; then         
        echo "There was an error downloading the source file."
        SOURCERR=1
      fi
    fi

    # Check MD5sum
    SOURCE=$(basename "$SOURCE")
    MD5CHK=$(md5sum "$SOURCE")
    MD5CHK=$(echo $MD5CHK | cut -d' ' -f1)
    if [ "$MD5CHK" != "${MD5ARRAY[$COUNT]}" ]; then
      MSG="Error: md5sum check failed on $SOURCE. Continue anyway?"
      user_choice "$MSG" CHOICE
      if [ "$CHOICE" == "n" ]; then
        MD5ERR=1
      fi
    fi
 
    let "COUNT+=1"
  done

  # Exit if something went wrong
  if [[ $SOURCERR -eq 1 || $MD5ERR -eq 1 ]]; then
    if [ $SOURCERR -eq 1 ]; then
      echo "One or more sources failed to download."
    fi
    if [ $MD5ERR -eq 1 ]; then
      echo "One or more sources failed the md5sum check."
    fi

    # Remove source code
    if [ "$CLEAN_SOURCE" == "yes" ]; then
      if [[ $SOURCEOPT -eq 0 || "$(readlink -f $SOURCEDIR)" != "$REPO_DIR" ]]; then
        remove_sources $SOURCES
        exit 1
      fi
    fi
  fi

  # Execute install script
  local PERM=$(stat -c '%a %n' $BUILD.SlackBuild)
  chmod +x $BUILD.SlackBuild
  ./$BUILD.SlackBuild

  # Check if package was built successfully. Note version string can contain
  # trailing stuff like the kernel version.
  if [ -f $TMPFILE ]; then
    local PKG=$(find "$OUTPUT" -maxdepth 1 -cnewer $TMPFILE -name "${BUILD}-${CURRENTVERSION}*.t?z")
  else
    local PKG=$(find "$OUTPUT" -maxdepth 1 -name "${BUILD}-${CURRENTVERSION}*.t?z")
  fi
  local NPKG=$(listlen "$PKG")

  if [ $NPKG -eq 0 ]; then
    echo "Error: build failed."
    local PKGERR=1

  elif [ $NPKG -gt 1 ]; then
    # This is very unlikely to happen if $TMPFILE exists
    echo "Error: more than one package in $OUTPUT matching ${BUILD}-${CURRENTVERSION} pattern."
    echo "Please remove unneeded packages and try again."
    local PKGERR=2

  else
    local PKGERR=0
  fi

  # Exit on error
  if [ $PKGERR -ne 0 ]; then

    # Remove source tarballs
    if [[ $SOURCEOPT -eq 0 || "$(readlink -f $SOURCEDIR)" != "$REPO_DIR" ]]; then
      if [ "$CLEAN_SOURCE" == "yes" ]; then
        remove_sources $SOURCES
      fi
  
      # Restore permissions of SlackBuild script and exit
      chmod $PERM $BUILD.SlackBuild
      exit 1
    fi
  fi

  # Install compiled package
  upgradepkg --reinstall --install-new $PKG

  # Remove temporary files and source code
  if [ "$CLEAN_PACKAGE" == "yes" ]; then
    rm $PKG
  fi
  if [[ "$CLEAN_TMP" == "yes" && -f $TMPFILE ]]; then
    find $TMP -mindepth 1 -maxdepth 1 -type d \
              -cnewer $TMPFILE -exec rm -rf {} \;
    rm $TMPFILE
  fi
  if [ "$CLEAN_SOURCE" == "yes" ]; then
    if [[ $SOURCEOPT -eq 0 || "$(readlink -f $SOURCEDIR)" != "$REPO_DIR" ]]; then
      remove_sources $SOURCES
    fi
  fi

  # Restore original permissions of SlackBuild script
  chmod $PERM $BUILD.SlackBuild

  # Notify of any special Slackware instructions
  if [[ -f 'README.SLACKWARE' || -f 'README.Slackware' ]]; then
    echo "Note: Slackware notes found. You should read them with 'sboui-backend info $BUILD'"
    echo "or with the Browse Files function of sboui."
  fi
}

################################################################################
# Installs, reinstalls, or upgrades one or more SlackBuilds
function install_multiple ()
{
  local BUILD

  for BUILD in $@
  do
    install_slackbuild $BUILD
  done
}

################################################################################
# Shows info from README and README.SLACKWARE
function show_info ()
{
  local BUILD=$1

  # Go to the top of the git tree
  cd $REPO_DIR

  # Get the directory for the requested SlackBuild
  echo "Searching for $BUILD ..."
  local BUILDPATH=$(find . -maxdepth 2 -mindepth 2 -name "$BUILD")
  if [ -z "$BUILDPATH" ]; then
    echo "There is no SlackBuild named $BUILD."
    return
  fi

  # Go the the SlackBuild directory
  cd $BUILDPATH
  BUILDPATH=${BUILDPATH#./*}
  local NAME=${BUILDPATH#*/*}

  # Get current version
  local VERSION=$(get_current_version "$NAME")
  echo "In local git repository:"
  echo "$NAME-$VERSION"
  echo ""
  echo "-----------------------------Description-----------------------------"
  cat 'README'
  if [ -f 'README.Slackware' ]; then
    echo ""
    echo "---------------------------Slackware notes---------------------------"
    cat 'README.Slackware'
  elif [ -f 'README.SLACKWARE' ]; then
    echo ""
    echo "---------------------------Slackware notes---------------------------"
    cat 'README.SLACKWARE'
  fi
}

################################################################################
# Searches for SlackBuilds with given pattern
function search ()
{
  local PATTERN=$1
  local BUILD STATUS

  # Go to the top of the git tree
  cd $REPO_DIR

  echo "Searching for $PATTERN ..."
  local BUILDS=$(find . -maxdepth 2 -mindepth 2 -type d | grep -i "$PATTERN")

  if [ -z "$BUILDS" ]; then
    echo "There is no SlackBuild matching the pattern $PATTERN."
  else
    # Get GROUP/NAME/STATUS
    for BUILD in $BUILDS
    do
      BUILD=${BUILD#./*}
      local GROUP=${BUILD%/*}
      local NAME=${BUILD#*/*}
      if [ "$(check_installed $NAME)" != "Not installed" ]; then
        STATUS="installed"
      else
        STATUS="not installed"
      fi
      echo "$GROUP/$NAME  [$STATUS]"
    done
  fi
}

################################################################################
# Searches README files for given pattern
function search_readmes ()
{
  local BUILD

  # Make sure spaces in the search string are resolved
  local PATTERN="$*"

  # Go to the top of the git tree
  cd $REPO_DIR

  echo "Searching README files for $PATTERN ..."
  local PTRNFILES=$(find . -maxdepth 3 -mindepth 3 -name README | xargs grep -il "$PATTERN" | sort -t "/" -k 3)

  # Display list of SlackBuilds
  if [ -n "$PTRNFILES" ]; then
    echo "The following SlackBuilds have $PATTERN in the README file:"
    for BUILD in $PTRNFILES
    do
      BUILD=${BUILD#./*/*}  # Remove leading ./ and group
      BUILD=${BUILD%*/*}    # Remove trailing /README
      echo $BUILD
    done
  else
    echo "No SlackBuilds have $PATTERN in the README file."
  fi
}      

################################################################################
# Lists groups in git tree
function list_groups ()
{
  local GROUP

  # Go to top-level git tree
  cd $REPO_DIR

  # Print list of groups
  local GROUPLIST=$(find . -maxdepth 1 -type d | sort)
  for GROUP in $GROUPLIST
  do
    if [[ "$GROUP" != "." && "$GROUP" != "./.git" ]]; then
      echo ${GROUP#./*}
    fi
  done
}

################################################################################
# Browses SlackBuilds in requested group
function browse_group ()
{
  local GROUP=$1
  local PKG

  # Go to top-level git tree
  cd $REPO_DIR

  # Check if group exists
  if [ ! -d $GROUP ]; then
    echo "No group called $GROUP."
    return
  fi

  # List packages in GROUP
  cd $GROUP
  local PKGLIST=$(find . -maxdepth 1 -type d | sort)
  for PKG in $PKGLIST
  do
    if [ "$PKG" != "." ]; then
      echo ${PKG#./*}
    fi
  done
}  

################################################################################
# Provides info on program usage
function print_usage ()
{
  echo ""
  if [ $# -eq 1 ]; then
    echo "Error: $1"
  fi
  echo "Usage: sboui-backend COMMAND [OPTIONS] [SLACKBUILDS]"
  echo
  echo "COMMANDS:"
  echo
  echo "update"
  echo "  Syncs git repo with local repo. This command should be run on first"
  echo "  use and periodically afterwards. Be sure to set the correct git"
  echo "  branch for your Slackware version in /etc/sboui/sboui-backend.conf."
  echo
  echo "install"
  echo "  Installs, reinstalls, or upgrades SlackBuilds listed on the command"
  echo "  line after the install command. Options:"
  echo "  --force, -f: if package is already installed, reinstalls without"
  echo "               asking for confirmation first."
  echo "  sourcedir=DIRECTORY: looks for source files in the specified"
  echo "               directory instead of downloading them from the internet."
  echo
  echo "search"
  echo "  Searches for SlackBuilds in the repository whose name includes the"
  echo "  pattern listed on the command line after the search command."
  echo
  echo "search-readmes"
  echo "  Searches README files in the repository for the pattern listed on the"
  echo "  command line after the search-readmes command."
  echo
  echo "info"
  echo "  Displays the contents of the README* files for the SlackBuild listed"
  echo "  on the command line after the info command."
  echo
  echo "list-groups"
  echo "  Displays groups in the repository."
  echo
  echo "browse-group"
  echo "  Displays SlackBuilds in the group specified on the command line after"
  echo "  the browse-group command."
  echo
  echo "--help, -h"
  echo "  Shows this usage information."
  echo 
}

################################################################################
# Main program

# Defaults for CLOs
FORCE=0
SOURCEOPT=0
SOURCEDIR=""

# Not enough command line arguments
if [ $# -lt 1 ]; then
  print_usage "must specify a command."
  exit 1

# update
elif [ "$1" == "update" ]; then
  update 

# install
elif [ "$1" == "install" ]; then
  if [ $# -eq 1 ]; then
    print_usage "must specify SlackBuild with install option."
    exit 1
  else
    parse_install_opts ${@:2:$#}
    install_multiple $(get_buildlist ${@:2:$#})
  fi

# search
elif [ "$1" == "search" ]; then
  if [ $# -lt 2 ]; then
    print_usage "must specify PATTERN with search option."
    exit 1
  else
    search $2
  fi

# search-readmes
elif [ "$1" == "search-readmes" ]; then
  if [ $# -lt 2 ]; then
    print_usage "must specify PATTERN with search-readmes option."
    exit 1
  else
    search_readmes $2
  fi

# info
elif [ "$1" == "info" ]; then
  if [ $# -lt 2 ]; then
    print_usage "must specify a SlackBuild with info option."
    exit 1
  else
    show_info $2
  fi

# list-groups
elif [ "$1" == "list-groups" ]; then
  list_groups

# browse-group
elif [ "$1" == "browse-group" ]; then
  if [ $# -lt 2 ]; then
    print_usage "must specify a SlackBuild with browse-group option."
    exit 1
  else
    browse_group $2
  fi

# --help
elif [[ "$1" == "--help" || "$1" == "-h" ]]; then
  print_usage

# Unrecognized option
else
  print_usage "unrecognized option."
  exit 1
fi
