#!/usr/bin/env bash

readonly BASE_PATH="${STYLER_DATA_PATH:-${XDG_DATA_HOME:-$HOME/.local/share}/styler}"
readonly CACHE_PATH="${STYLER_CACHE_PATH:-${XDG_CACHE_HOME:-$HOME/.cache}/styler}"

readonly PACKAGE_PATH="$BASE_PATH/packages"
readonly PROCESSOR_PATH="$BASE_PATH/processors"

readonly VERSION="0.4.1"

main() {
  local cmd=""
  local ret=0

  case "$1" in
  theme)
    cmd="switch_theme"
    ;;
  set)
    cmd="set_theme"
    ;;
  list)
    cmd="list"
    ;;
  download)
    cmd="download"
    ;;
  -v | --version | version)
    printf "Program theming script.\n\n©Marty Oehme\n\nVersion: %s\n" "$VERSION"
    exit 0
    ;;
  -h | --help | help | *)
    cmd="usage"
    ;;
  esac
  shift

  $cmd "$@"
  ret=$((ret + $?))
  exit $ret
}

usage() {
  printf "%s\n" \
    "" \
    " styler        Quickly switch your linux style." \
    "               Uses base16 themes to quickly set them for a variety of applications." \
    "" \
    " Usage: styler [-hv][set|theme base16-themename]" \
    "" \
    " Arguments:" \
    "" \
    "     theme [theme]                       Temporarily switch theme. Use any valid base16 theme name (without base16- prefix)." \
    "                                         Theme will be lost upon restart, or application restarts." \
    "" \
    "     set   [theme]                       Set the theme. Use any valid base16 theme name (without base16- prefix)." \
    "                                         Same as 'theme' option, but changes will be made permanent." \
    "" \
    "     list [processors|packages|themes]   Print out all currently installed processors, templates, or themes from selection. " \
    "     list themes                         Will print out themes which are available for all installed packages." \
    "     list themes all                     Will print out every single installed theme, regardless of its universal availability." \
    "" \
    "     download    [username/repository]   Download a base16 template into the package directory or download a processor16" \
    "                                         into the processor directory. Use user/repo format to automatically pull from github." \
    "" \
    "     help    | -h | --help                  Print out this help." \
    "" \
    "     version | -v | --version               Print out program information." \
    "" \
    ""
}

# base directory should always exist
base_dir_exists_or_create() {
  [[ -d "$BASE_PATH" ]] || mkdir "$BASE_PATH"
  [[ -d "$PACKAGE_PATH" ]] || mkdir "$PACKAGE_PATH"
  [[ -d "$PROCESSOR_PATH" ]] || mkdir "$BASE_PATH"
}

# retrieves all relevant packages from BASE_PATH/packages
# 'relevant' here means they follow github pattern of author/repository
get_packages() {
  for author in "$PACKAGE_PATH"/*; do
    # TODO should eventually be used to either distinguish between author/pkg and pkg packages
    # or to spit out a warning if they should not be used.
    # if grep -q -e '^base16-' <<<"$(basename -- "$author")"; then
    #   echo ERROR
    # fi
    for package in "$author"/*; do
      [[ -e "$author" ]] || break
      [[ -d "$package" ]] || break
      printf "%s/%s\n" "$(basename -- "$author")" "$(basename -- "$package")"
    done
  done
}

# retrieves all processors from BASE_PATH/processors
# 'relevant' here means they follow github pattern of author/repository
get_processors() {
  for author in "$PROCESSOR_PATH"/*; do
    for package in "$author"/*; do
      for processor in "$package"/*; do
        [[ -e "$processor" ]] || break
        [[ -f "$processor" ]] || break
        if grep -q -e '/theme_[[:alnum:]]\{1,\}$' <<<"$processor"; then
          printf "%s\n" "$(basename -- "$processor")"
        fi
      done
    done
  done
}

# retrieves all installed themes available to all packages
# can be called with `all` argument to return themes which only exist for some packages
get_themes() {
  local themes
  themes=$(find "$PACKAGE_PATH" -type f -name 'base16-*')

  local filtered
  if [[ "$1" = "all" ]]; then
    filtered="$(get_unique_themes "$themes")"
  else
    filtered="$(get_universal_themes "$themes")"
  fi

  echo "$filtered"
}

# filter down the list passed in to keep repeating base16 themes only once per name
# and remove surrounding path information (and file ending)
get_unique_themes() {
  if _cache_invalid "$CACHED_UNIQUE_THEMES" "$PACKAGE_PATH"; then
    notify-send "styler cache invalid" "rebuilding cache will take a moment..."
    _cache_rebuild "$CACHED_UNIQUE_THEMES" _fetch_unique_themes "$@"
  else
    cat "$CACHED_UNIQUE_THEMES"
  fi
}
readonly CACHED_UNIQUE_THEMES=${CACHE_PATH}/unique_themes

# returns themes which are available in all installed packages
# returns cached version or rebuilds cache
get_universal_themes() {
  if _cache_invalid "$CACHED_UNIVERSAL_THEMES" "$PACKAGE_PATH"; then
    notify-send "styler cache invalid" "rebuilding cache will take a moment..."
    _cache_rebuild "$CACHED_UNIVERSAL_THEMES" _fetch_universal_themes "$@"
  else
    cat "$CACHED_UNIVERSAL_THEMES"
  fi
}
readonly CACHED_UNIVERSAL_THEMES=${CACHE_PATH}/universal_themes

_fetch_unique_themes() {
  echo "$1" | sed "s/.*\\/base16-//;s/\\..*//" | sort | uniq
}

# only keep themes which are available in every installed package
_fetch_universal_themes() {
  local themes="$1"
  local unique
  unique="$(get_unique_themes "$themes")"

  local packages
  packages="$(get_packages)"

  local filtered
  for t in $unique; do
    for p in $packages; do
      if ! echo "$themes" | grep -qe "^.*$p.*$t.*$" -; then
        themes="$(echo "$themes" | sed "/$t/d")"
        unique="$(echo "$unique" | sed "/$t/d")"
        break
      fi
    done
  done
  echo "$unique"
}

# returns true if changes to $2 are newer than $1
# used to pass in cache file for $1, what it is caching for $2
_cache_invalid() {
  cache_upd="$(_last_update "$1")"
  live_upd="$(_last_update "$2")"
  if [ "${live_upd%.*}" -gt "${cache_upd%.*}" ]; then
    true
  else
    false
  fi
}

# recreates target cache with output of function
# arguments: "target_cache_file" "function_name" "arguments_for_function"
_cache_rebuild() {
  local target="$1"
  shift
  local fct="$1"
  shift
  local new
  new="$($fct "$@")"
  mkdir -p "$CACHE_PATH"
  echo "$new" | tee "$target"
}

# returns time of last update to argument passed in
# argument can be path to file or directory (searched recursively)
# if argument does not exist gives 0
_last_update() {
  [[ ! -e "$1" ]] && echo 0 && return
  find "$1" -type f -printf "%T@\n" | sort -n | tail -n 1
}

# temporarily switch theme, same thing as setting, only with permanence flag turned off for processors
switch_theme() {
  set_theme "$1" "false"
}

# call processors for all installed packages
set_theme() {
  local theme="$1"
  if [[ -z $theme ]]; then
    printf "Theme application requires an argument.\n"
    exit 1
  fi
  local permanent="${2:-true}"

  local packages
  packages="$(get_packages)"
  if [[ -z "$packages" ]]; then
    printf "ERROR: No base16 packages installed. Please install at least 1 base16 package in %s/.\n" "$PACKAGE_PATH" >&2
    exit 1
  fi

  local processors
  processors="$(get_processors)"
  if [[ -z "$processors" ]]; then
    printf "ERROR: No application processors installed. Please install at least one processor in %s/.\n" "$PROCESSOR_PATH" >&2
    exit 1
  fi

  for pkg in $packages; do
    local appext

    # filter the application a package targets, since base16 packages
    # carry standard names this removes everything before base16-
    # the result is the application it targets
    # shellcheck disable=SC2001
    appext=$(sed -e 's|^[A-Za-z0-9-]\{1,\}/base16-||' <<<"$pkg")

    # Compares application extension with existing processors and runs the appropriate processor if found
    processor=$(find "$PROCESSOR_PATH" -type f | grep -e "theme_$appext")
    if [[ -f "$processor" ]]; then
      "$processor" "$PACKAGE_PATH" "$pkg" "$theme" "$permanent"
    else
      printf "WARN: No processor found for application %s in %s. Make sure you install a processor for the application.\n" "$appext" "$PROCESSOR_PATH/" >&2
    fi

  done
}

list() {
  local selected="$1"
  shift

  case "$selected" in
  packages)
    get_packages
    ;;
  processors)
    get_processors
    ;;
  themes)
    get_themes "$@"
    ;;
  *)
    echo "Please select one of packages | processors | themes to list."
    ;;
  esac
}

download() {
  local pkg="$1"
  local page="https://github.com"
  local repo="$page/$pkg"

  [[ -z "$pkg" ]] && {
    echo "No package to download passed in. Please provide a package to download in the form user/repository."
    exit 1
  }

  type git >/dev/null 2>&1 || {
    echo "git is required to clone base16 package. Please install git."
    exit 1
  }

  base_dir_exists_or_create

  if ! git ls-remote --exit-code -h "$repo" >/dev/null; then
    echo "Repository $repo not found."
    exit 1
  fi

  # if package has patter name/base16-program, put it in packages; if name/process16-program put it in processors
  # if none of the above, assume it's a processor but warn the user
  if grep -q -e '^[0-9A-Za-z-]\{1,\}/base16-[0-9A-Za-z-]\{1,\}$' <<<"$pkg"; then
    git clone "$repo" "$PACKAGE_PATH/$pkg"
  elif grep -q -e '^[0-9A-Za-z-]\{1,\}/process16-[0-9A-Za-z-]\{1,\}$' <<<"$pkg"; then
    git clone "$repo" "$PROCESSOR_PATH/$pkg"
  else
    echo "Package does not fit default naming scheme of packages/processors. Assuming it is a processor but please check manually."
    git clone "$repo" "$PROCESSOR_PATH/$pkg"
  fi
}

main "$@"
