#!/usr/bin/env bash
# manage public directories
# WIP
# ------------------------------------------------------------------- #
#set -x
shopt -s extglob
#shopt -s failglob

# Defaults: the usage should be as simple as throwing some stuff into
# ~/pub and running $PROGNAME.
DIR=~/pub

PROGNAME="${0##*/}"

# Usage information / help message.
print_usage() {
 local text
 read -rd '' text << _EOF
 ${PROGNAME} - manage public directories
 usage: ${PROGNAME} [options] [command] [args...]
 options:
    -h: print this help message
    -d: define the public directory path (default ${DIR})
 commands:
   default: if no command is given, ensure the public directory exists and fix it
    fix: fixes permissions of args, recursing on directories (default is the -d option)
    add: copies the args into the public directory and fixes them.
   link: not implemented right now
   show: not implemented right now
 bugs and notes:
   add command does not understand nested directories right now
_EOF
 echo "${text}"
}

# Parse options. Takes $@, returns a number.
# Do shift $number and the $@ becomes just the arguments.
parse_options() {
 local opt
 local OPTARG
 local OPTIND

 while getopts 'hd:' opt
 do
   case "${opt}" in
     h)
       print_usage
       exit
     ;;
     d)
       if [[ -d "${OPTARG}" ]]
       then
         DIR="${OPTARG}"
       else
         echo "${PROGNAME}: ${OPTARG} is not a directory" >&2
         exit 31
       fi
     ;;
     *)
       echo "internal error" >&2
       exit 30
     ;;
   esac
 done
 return $(( OPTIND - 1 ))
}

# Read the command and arguments, dispatch runtime to another functions
# in sensible manner.
parse_command() {
 case "${1}" in
   # The default behaviour: make sure $DIR exists and fix that.
   ''|'default')
     ask_user \
       0 \
       "$PROGNAME default behaviour: Try to setup $DIR?" || exit 0

     if [[ -d "${DIR}" ]] || mkdir "${DIR}"
     then
       # I'm being absurdly clever here to save a few cycles. Since
       # the fix thing just below defaults to fixing $DIR, we just
       # fall into it using ;& instead of recursing parse_command.
       :
     else
       echo "${PROGNAME}: ${DIR} is not a directory" >&2
       exit 41
     fi
   ;&
   # fix: fix every arg, default to $DIR.
   'fix')
     if [[ -e "${2}" ]]
     then
       fix_permissions "${@: 2}"
     else
       fix_permissions "${DIR}"
     fi
   ;;
   # add: no defaults, raise error if no args
   'add')
     if [[ -e "${2}" ]]
     then
       copy_to_DIR "${@: 2}"
     else
       echo "${PROGNAME}: ${1}: no args" >&2
       exit 42
     fi
   ;;
   # * fallback: bad commands go to hell. Could try to guess what to do
   # if a path is given, but that's a very luxurious feature.
   *)
     echo "${PROGNAME}: ${1}: bad command" >&2
     exit 40
   ;;
 esac
}

# Helper function: print args, prompt for a [y/n] and return 0 or 1
# First arg is the default return value, which can be any integer
ask_user() {
 if [[ ! $1 =~ [[:digit:]] ]]
 then
   echo "internal error" >&2
   exit 21
 fi

 local REPLY
 local def
 if (( $1 )); then d='[N/y]'; else d='[Y/n]'; fi
 echo "${*: 2}"
 read -res -n 1 -p " $d>> "
 case "${REPLY,,}" in
   y) return 0  ;;
   n) return 1  ;;
   *) return $1 ;;
 esac
}

# ------------------------------------------------------------------- #
# Command code goes here.

# Fix permissions. Sets:
# files to 644,
# executable files to 755,
# files where path contains 'bin' to 755,
# directories to 755 and recurses on them.
fix_permissions() {
 for f
 do
   if [[ -f "${f}" ]]
   then
     [[ -x "${f}"     ]] && chmod 755 "${f}" && continue
     [[ "${f}" =~ bin ]] && chmod 755 "${f}" && continue
                            chmod 644 "${f}"
   elif [[ -d "${f}" ]]
   then
     chmod 755 "${f}"
     fix_permissions "${f}"/*
   fi
 done
}

# THIS IS TRASH AND CP IS TRASH
copy_to_DIR() {
 for f
 do
   if [[ ! -e "${DIR}"/"${f##*/}" ]]
   then
     /bin/cp -r "${f}" "${DIR}/" && fix_permissions "${DIR}"/"${f##*/}"
   fi
 done
}

# ------------------------------------------------------------------- #
# Main runtime
main() {
 parse_options "${@}"
 shift ${?}
 parse_command "${@}"
}

main "${@}"