#!/usr/bin/env bash
# scriptbox: Creates a multi-binary script that self-contains the input scripts.
#         Symlinking to the resulting binary with the name of one of the original scripts will trigger
#         said script. The idea is similar to `busybox`.

LD_INTERNAL=1
. $(dirname $(realpath $0))/daisy.source

args=$@

function help()
{
  echo "$LD_BIN is a utility that allows you to generate busybox-style combined binaries."
  echo "It only supports shell scripts compatible with \`bash\`."
  echo "To access the original functionality of an input binary, you can either use a symlink or"
  echo "call the function like so: 'combi-bin input-bin <input-bin args>."
  echo ""
  echo "> Usage:"
  echo "Creating boxed binary:        $LD_BIN -o <BOXED_BIN> <-s source files> <-p include files verbatim> -i INPUT_BINS ..."
  echo "<X> Unpacking a boxed binary: $LD_BIN -e <BOXED_BIN>"
  echo "View this screen:             $LD_BIN <noargs>"
  exit 0
}

if [[ $@ == '' ]]; then
  help
fi

# Define some building blocks
case_p1="case $BINARY in"
case_pm1="  $OPTION)"
case_pm2="  exec $OTPION_fn"
case_pm3="  ;;"
case_cm1="  *)"
case_cm2="  exec help_fn"
case_cm3="  ;;"
case_p2="esac"

func_p1="function $OPTION_fn() {"
func_p2="  exit($?)"
func_p3="}"

basic_p1="BINARY=$0"

# Start parsing args
inputs=()
output=""

inputs=()
output=""
includes=()
sources=()

args=("$@")
i=0
b=0
s=0
p=0

while [ $i -lt $# ]; do
  case "${args[$i]}" in
    -i)
      ((i++))
      while [ $i -lt $# ] && [[ ! "${args[$i]}" =~ ^- ]]; do
        file="${args[$i]}"
        ((i++))
        if [[ -e "$file" ]]; then
          inputs+=("$file")
          ((b++))
        else
          echo "WARNING: Missing input binary: \"$file\"."
          echo "Module will NOT be available!"
        fi
      done
      continue
      ;;
    -s)
      ((i++))
      while [ $i -lt $# ] && [[ ! "${args[$i]}" =~ ^- ]]; do
        file="${args[$i]}"
        ((i++))
        if [[ -e "$file" ]]; then
          sources+=("$file")
          ((s++))
        else
          echo "WARNING: Missing input source: \"$file\"."
          echo "File will NOT be sourced!"
        fi
      done
      continue
      ;;
    -p)
      ((i++))
      while [ $i -lt $# ] && [[ ! "${args[$i]}" =~ ^- ]]; do
        file="${args[$i]}"
        ((i++))
        if [[ -e "$file" ]]; then
          includes+=("$file")
          ((p++))
        else
          echo "WARNING: Missing input include: \"$file\"."
          echo "File will NOT be included!"
        fi
      done
      continue
      ;;
    -o)
      ((i++))
      output="${args[$i]}"
      ;;
  esac
  ((i++))
done

echo "Input binaries: ${inputs[*]}"
echo "Include files: ${includes[*]}"
echo "Source files: ${sources[*]}"
echo "Output binary: $output"

if [[ "$output" == "" ]]; then
  echo "Missing output file!"
  exit 1
fi

function add()
{
  echo "$@" >> "$output"
}

function add_nn()
{
  echo -n "$@" >> "$output"
}

rm -rf "$output"

# Now to construct the binary
# >>> Section 1, includes
add "#!/usr/bin/env bash"
add "# Multi-call binary generated by LACKADAISICAL binbox"
add "# $output information:"
add "# Contained modules: ${inputs[*]}"
add "# Files included verbatim: ${includes[*]}"
add "# Files sourced: ${sources[*]}"
add "# Symlink to this binary with the module name to invoke it, or"
add "# use '$output <func>' to do the same."

for f in "${sources[@]}"; do
  add ". $f"
done

for f in "${includes[@]}"; do
  add ""
  add "# Included file $f"
  add "$(cat "$f")"
done

add "readarray -t all_funcs < <(declare -F | awk '{print \$3}')"

# >>> Section 2: Modules
for f in "${inputs[@]}"; do
    add "# Module '$f':"
    add "function $f()"
    add "{"
    add "$(cat "$f" | grep -v "#!")"
    add "}"
done

# >>> Section 3: Module selection
add ""
add "################################################################################"
add "# END OF INCLUDED MODULES ######################################################"
add "################################################################################"
add ""
add "# Array of modules as well as array of functions."
# Add a static list of modules with a no-newline add
add_nn "modules=("
  for f in "${inputs[@]}"; do
    add_nn "\"$f\" "
  done
add ")"
add 'mapfile -t funcs < <(printf "%s\n" "${all_funcs[@]}" "${modules[@]}" "${modules[@]}" | sort | uniq -u)'
add ""
add "# Check the export switches (-m and -f)"
add ""

add "symed=1"
add "binself=\$(basename \$0)"
add "boxfile=\"$(basename $output)\""
add ""
add "if [[ \$binself == \$boxfile  ]]; then"
add "  symed=0"
add "  if [[ \$# -eq 0 ]]; then"
add "    echo '$(basename $output): Multi-call binary generated by lackadaisical binbox.'"
add "    echo 'Use switch \"-m\" to generate symlinks of modules in the current directory,'"
add "    echo 'or use switch \"-f\" to generate symlinks of all available functions.'"
add "    echo ''"
add "    echo 'Exported modules:'"
add "    for mod in \"\${modules[@]}\"; do"
add "      echo \"- \$mod\""
add "    done"
add "    echo ''"
add "    echo 'Other functions:'"
add "    for func in \"\${funcs[@]}\"; do"
add "      echo \"- \$func\""
add "    done"
add "    exit 0"
add "  fi"
add "fi"
add ""
add "if [[ \$symed -eq 0 ]]; then"
add "  eval \$@"
add "  exit \$?"
add "fi"
add ""
add "if [[ \$symed -eq 1 ]]; then"
add "  eval \$(basename \$0) \$@"
add "fi"


chmod +x "$output"
