#!/bin/bash
set -e

RED_COLOR="\e[1;31m"
PURPLE_COLOR="\e[1;35m"
GREEN_COLOR="\e[1;32m"
CLEAR_COLOR="\e[0m"

_DEF_PARALLEL_JOBS=4
_ENV_PARALLEL_JOBS="${PARALLEL_JOBS}"
source /etc/leaf.conf
if [ -n "${_ENV_PARALLEL_JOBS}" ]; then
  export PARALLEL_JOBS="${_ENV_PARALLEL_JOBS}"
elif [ -z "${PARALLEL_JOBS}" ]; then
  export PARALLEL_JOBS="${_DEF_PARALLEL_JOBS}"
else
  export PARALLEL_JOBS
fi
export MAKEFLAGS="-j${PARALLEL_JOBS}"
export NINJAJOBS="${PARALLEL_JOBS}"
export LC_ALL=C
export leaf_flags="CFLAGS CXXFLAGS FCFLAGS FFLAGS RUSTFLAGS"
for flag in ${leaf_flags}; do
  export $flag
done

declare -A BUILD_OPTIONS
BUILD_OPTIONS=([strip]="0" [libtool]="0" [zipman]="0")

main() {
  leaf_check_directories
  if [ $# -le 1 ]; then
    case $1 in
      clean)
        leaf_check_permission
        rm -rf {"${BUILD_DIR}","${TEMP_DIR}"}/*
        ;;
      list)
        cat "${INSTALLED_PACKAGES}"
        ;;
      *)
        leaf_print_usage
        ;;
    esac
  else
    local _item
    case $1 in
      prepare|build|install|force-install|remove|pack|dirct-install)
        leaf_check_permission
        for _item in "${@:2}"; do
          if [[ x"$1" == x"remove" ]]; then
            leaf_find_remove_pkgbuild "${_item}"
          else
            leaf_find_pkgbuild "${_item}"
          fi
          leaf_reset_state
          if [[ x"$1" == x"remove" ]]; then
            source "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/PKGBUILD"
          else
            source "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}".PKGBUILD
          fi
          leaf_parse_options
          srcdir="${BUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}"
          pkgdir="${srcdir}"/__pkgdir__
          if [[ x"$1" != x"dirct-install" ]]; then
            rm -rf "${srcdir}" && install -dm755 "${srcdir}" && cd "${srcdir}"
          fi
          leaf_${1}_package
        done
        ;;
      search)
        leaf_search_package "${@:2}"
        ;;
      show)
        for _item in "${@:2}"; do
          leaf_find_pkgbuild "${_item}"
          leaf_show_package
        done
        ;;
      unpack)
        for _item in "${@:2}"; do
          leaf_check_permission
          leaf_reset_state
          leaf_unpack_package "${_item}"
        done
        ;;
      *)
        leaf_print_usage
        ;;
    esac
    if [ -n "${MESSAGE}" ]; then
      echo -e "${MESSAGE}"
    fi
  fi
}

###########################################################
# Help functions that give PKGBUILD to use
###########################################################

leaf_clear_flags() {
  for flag in $leaf_flags; do
    unset $flag
  done
}

leaf_install_hook() {
  local _hook_file="$1"
  local _hook_dest="${pkgdir}${HOOK_DIR}"

  # check file name
  [[ -f "$_hook_file" ]] || leaf_error "Hook file '$_hook_file' does not exist"
  [[ -r "$_hook_file" ]] || leaf_error "Hook file '$_hook_file' is not readable"
  [[ "$(basename "$_hook_file")" == *.HOOK ]] || leaf_error "Hook file must end with .HOOK extension"

  # check hook file by load it in a sub shell
  (
    unset triggers target operation 2>/dev/null
    declare -a triggers
    declare -a target
    source "$_hook_file" || leaf_error "Source ${_hook_file} failed."
    [[ ${#triggers[@]} -gt 0 ]] || leaf_error "${_hook_file} missing 'triggers' array"
    [[ ${#target[@]} -gt 0 ]]    || leaf_error "${_hook_file} missing 'target' array"
    declare -f operation &>/dev/null || leaf_error "${_hook_file} missing 'operation' function"
  ) || leaf_error "Hook file ${_hook_file} is not valid. Please check your package ${PKG_NAME}."

  # install hook file
  install -Dm644 -- "$_hook_file" "${_hook_dest}/$(basename "$_hook_file")" || {
    leaf_error "Failed to install hook file to ${_hook_dest}"
  }

  echo -e "Installed hook: $(basename "$_hook_file")"
}

leaf_check_directories() {
  if [ -z "${BUILD_DIR}" ]; then
    leaf_error "Directory \${BUILD_DIR} must be existed"
  elif [ -z "${TEMP_DIR}" ]; then
    leaf_error "Directory \${TEMP_DIR} must be existed"
  fi
}

leaf_check_permission() {
  [ ${EUID} -eq 0 ] || leaf_error "Permission denied"
}

leaf_compress_man_pages() {
  pushd "${pkgdir}" > /dev/null 2>&1
  [ ! -d usr/share/man ] || {
    local _item
    find usr/share/man -type f,l | while read -r _item; do
      case $(file -bi "${_item}") in
        *text*)
          /usr/bin/gzip -c -9 "${_item}" > "${_item}".gz
          rm -fv "${_item}"
          ;;
        *symlink*)
          ln -srv "$(readlink -m "${_item}")".gz "$(realpath "${_item}".gz)"
          rm -fv "${_item}"
          ;;
        *)
          ;;
      esac
    done
  }
  popd > /dev/null 2>&1
}

leaf_error() {
  if [ -n "${MESSAGE}" ]; then
    echo -e "${MESSAGE}"
  fi
  echo -e "${RED_COLOR}* $1${CLEAR_COLOR}" && exit 1
}

leaf_find_pkgbuild() {
  local _location="$(find "${PKGBUILD_DIR}" -type f -wholename "*$1*.PKGBUILD" -printf "%P\n")"
  if [ -z "${_location}" ]; then
    leaf_error "Package $1's PKGBUILD does NOT exist"
  elif [[ $(echo "${_location}" | wc -l) == 1 ]]; then
    PKG_PREFIX="$(echo "${_location}" | awk -F'/' '{print $1}')"
    PKG_NAME="$(echo "${_location%.PKGBUILD}" | awk -F'/' '{print $2}')"
  else
    leaf_error "Ambiguous packages, the prefix or version of package must be specified"
  fi
}

leaf_find_remove_pkgbuild() {
  local _location="$(find "${TRACE_DIR}" -type d -wholename "*$1*" -printf "%P\n")"
  if [ -z "${_location}" ]; then
    leaf_error "Package $1 does NOT exist"
  elif [[ $(echo "${_location}" | wc -l) == 1 ]]; then
    PKG_PREFIX="$(echo "${_location}" | awk -F'/' '{print $1}')"
    PKG_NAME="$(echo "${_location%.PKGBUILD}" | awk -F'/' '{print $2}')"
  else
    leaf_error "Ambiguous packages, the prefix or version of package must be specified"
  fi
}

leaf_invoke_hooks() {
  local _hook _target _hook_dir
  local _trace_dir="${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}"
  case "$1" in
    install|remove)
      ;;
    *)
      leaf_error "leaf_invoke_hooks: $1 is invalid!"
      ;;
  esac
  if [ ! -d "${HOOK_DIR}" ]; then
    leaf_error "Hook dir ${HOOK_DIR} does not exist. Check your leaf installation."
  fi
  find "${HOOK_DIR}" -type f -name "*.HOOK" | while read -r _hook; do
    unset triggers target operation
    declare -a triggers
    declare -a target
    source "${_hook}" || leaf_error "Failed to source hook file: ${_hook}"
    [[ " ${triggers[*]} " =~ " $1 " ]] || continue
    for _target in "${target[@]}"; do
      if grep -qe "${_target}" "${_trace_dir}"/FILES 2>/dev/null; then
        echo -e "${GREEN_COLOR}*** Apply the hook: ${_hook}...${CLEAR_COLOR}"
        operation
        break
      fi
    done
  done
}

leaf_merge_package() {
  pushd $1 > /dev/null 2>&1
  local _trace_dir="${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}"
  local _item _item_conflict _mode _owner _group
  local _time="$(date +%Y%m%d%H%M%S)"
  cat "${_trace_dir}"/DIRS | while read -r _item; do
    _mode=$(stat -c %a ."${_item}")
    _owner=$(stat -c %u ."${_item}")
    _group=$(stat -c %g ."${_item}")
    install -d -m ${_mode} -o ${_owner} -g ${_group} "${_item}"
  done
  cat "${_trace_dir}"/FILES | while read -r _item; do
    _mode=$(stat -c %a ."${_item}")
    _owner=$(stat -c %u ."${_item}")
    _group=$(stat -c %g ."${_item}")
    if [[ "${_item}" == /etc/* && -e "${_item}" ]]; then
      _item_conflict="$(dirname "${_item}")/._$(basename "${_item}").conflict_${PKG_NAME}_${_time}"
      if (diff ."${_item}" "${_item}" > /dev/null); then
        install -D -m ${_mode} -o ${_owner} -g ${_group} ."${_item}" "${_item}"
      else
        install -D -m ${_mode} -o ${_owner} -g ${_group} ."${_item}" "${_item_conflict}"
        leaf_record_message "Config file confliction on ${_item}, the package provided version is installed as ${_item_conflict}."
      fi
    else
      install -D -m ${_mode} -o ${_owner} -g ${_group} ."${_item}" "${_item}"
    fi
  done
  cat "${_trace_dir}"/LINKS | while read -r _item; do
    cp -dp ."${_item}" "${_item}"
  done
  leaf_invoke_hooks install
  popd > /dev/null 2>&1
}

leaf_parse_options() {
  local _option_array=(${DEFAULT_BUILD_OPTIONS[@]} ${options[@]})
  for option in ${_option_array[@]}; do
    if [[ x"${option}" == x"strip" ]]; then
      BUILD_OPTIONS["strip"]="1"
    fi
    if [[ x"${option}" == x"!strip" ]]; then
      BUILD_OPTIONS["strip"]="0"
    fi
    if [[ x"${option}" == x"libtool" ]]; then
      BUILD_OPTIONS["libtool"]="1"
    fi
    if [[ x"${option}" == x"!libtool" ]]; then
      BUILD_OPTIONS["libtool"]="0"
    fi
    if [[ x"${option}" == x"zipman" ]]; then
      BUILD_OPTIONS["zipman"]="1"
    fi
    if [[ x"${option}" == x"!zipman" ]]; then
      BUILD_OPTIONS["zipman"]="0"
    fi
  done
}

leaf_print_usage() {
  cat << EOF
Usage: leaf [option] [packages]
Option:     prepare        prepare packages but do not build
            build          build packages but do not install
            install        install packages
            forece-install install packages even if test faild
            dirct-install  install packages directly from previous build
            remove         remove packages
            clean          clean all source folders
            list           list installed packages
            search         search packages by keyword
            show           show package details
            pack           build packages and pack
            unpack         unpack binary packages
                               This leaf does not have Super Cow Powers.
EOF
}

leaf_record_message() {
  MESSAGE+="\n${PURPLE_COLOR}* Message from ${PKG_PREFIX}/${PKG_NAME}${CLEAR_COLOR}\n"
  local _item
  for _item in "${@:1}"; do
    MESSAGE+="${GREEN_COLOR}* ${_item}${CLEAR_COLOR}\n"
  done
}

leaf_remove_libtool_files() {
  find "${pkgdir}" -name "*.la" -delete
}

leaf_reset_state() {
  options=()
  src_preinstall() {
    :
  }
  src_postinstall() {
    :
  }
  src_preremove() {
    :
  }
  src_postremove() {
    :
  }
  src_prepare() {
    :
  }
  src_build() {
    :
  }
  src_check() {
    :
  }
}

leaf_strip_files() {
  pushd "${pkgdir}" > /dev/null 2>&1
  local _file
  find . -type f | while read -r _file; do
    case $(file -bi "${_file}") in
      *application/x-sharedlib*)       # libraries(.so)
        strip --strip-unneeded "${_file}"
        ;;
      *application/x-pie-executable*)  # libraries(.so)
        strip --strip-unneeded "${_file}"
        ;;
      *application/x-archive*)         # libraries(.a)
        strip --strip-debug "${_file}"
        ;;
      *application/x-object*)
        case "${_file}" in
          *.ko)                        # kernel module
            strip --strip-unneeded "${_file}"
            ;;
          *)
            ;;
        esac
        ;;
      *application/x-executable*)      # binaries
        strip --strip-all "${_file}"
        ;;
      *)
        ;;
    esac
  done
  popd > /dev/null 2>&1
}

leaf_trace_package() {
  pushd "$1" > /dev/null 2>&1
  local _trace_dir="${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}"
  install -dm755 "${_trace_dir}"
  find . -type d | sort > "${_trace_dir}"/DIRS
  sed -i "1d" "$_trace_dir"/DIRS
  find . -type f | sort > "${_trace_dir}"/FILES
  find . -type l | sort > "${_trace_dir}"/LINKS
  sed -i "s/.//" "${_trace_dir}"/{DIRS,FILES,LINKS}
  install -vm644 "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}.PKGBUILD" "${_trace_dir}/PKGBUILD"
  popd > /dev/null 2>&1
}

leaf_update_package_database() {
  local _item="$(echo "$2" | awk 'BEGIN{FS="/";OFS="\\/"}{print $1,$2}')"
  case $1 in
    add)
      sed -i "/${_item}/d" "${INSTALLED_PACKAGES}"
      echo "$2" >> ${INSTALLED_PACKAGES}
      ;;
    delete)
      sed -i "/${_item}/d" "${INSTALLED_PACKAGES}"
      ;;
  esac
}

leaf_update_environment() {
  ldconfig
  [ ! -r /etc/profile ] || source /etc/profile
}

leaf_prepare_package() {
  local url sourcefile index _md5sum local_src pkg_src_root

  distdir="${DIST_DIR}"
  mkdir -pv -- "${distdir}"

  pkg_src_root="${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}"

  pushd "${distdir}" >/dev/null || leaf_error "cannot enter distdir: ${distdir}"

  for index in "${!sources[@]}"; do
    sourcefile="${sources[$index]}"
    url="${urls[$index]}"

    if [ -z "${url}" ]; then
      url="${sourcefile}"
    fi

    if [ -f "${sourcefile}" ]; then
      echo "${sourcefile} exists, checking..."
      _md5sum="$(md5sum -- "${sourcefile}" | awk '{print $1}')"
      if [ "${_md5sum}" = "${md5sums[$index]}" ]; then
        echo "md5sum match, skip ${sourcefile}."
        continue
      fi
      echo "md5sum does not match, refetching..."
      rm -f -- "${sourcefile}"
    fi

    case "${url}" in
      http://*|https://*|ftp://*)
        echo "Downloading ${sourcefile} from ${url}..."
        wget -O "${sourcefile}" -- "${url}" || leaf_error "wget failed: ${url}"
        ;;
      *)
        local_src="${pkg_src_root}/${url}"
        echo "Copying ${sourcefile} from local file ${local_src}..."
        [ -f "${local_src}" ] || leaf_error "local source not found: ${local_src}"
        cp -f -- "${local_src}" "${sourcefile}" || leaf_error "cp failed: ${local_src}"
        ;;
    esac

    _md5sum="$(md5sum -- "${sourcefile}" | awk '{print $1}')"
    if [ "${_md5sum}" != "${md5sums[$index]}" ]; then
      leaf_error "md5sums of ${sourcefile}(${_md5sum}) does not match with ${md5sums[$index]}!"
    fi
  done

  popd >/dev/null || true
  echo "Sources ready."
  src_prepare
}

leaf_build_package() {
  leaf_prepare_package
  src_build

  if ! src_check; then
    if [ "${FORCE_INSTALL}" == "1" ]; then
      echo -e "${RED_COLOR}* Tests failed, but is in force-install mode.${CLEAR_COLOR}"
    else
      leaf_error "Tests failed, please check. Aborting installation."
    fi
  fi

  src_install
  local _option
  for _option in $(echo ${!BUILD_OPTIONS[*]}); do
    if [[ x"${_option}" == x"strip" ]] && [[ x"${BUILD_OPTIONS[$_option]}" == x"1" ]]; then
      leaf_strip_files
    fi
    if [[ x"${_option}" == x"libtool" ]] && [[ x"${BUILD_OPTIONS[$_option]}" == x"0" ]]; then
      leaf_remove_libtool_files
    fi
    if [[ x"${_option}" == x"zipman" ]] && [[ x"${BUILD_OPTIONS[$_option]}" = x"1" ]]; then
      leaf_compress_man_pages
    fi
  done
  if [[ x"${ENABLE_DEBUG_TREE}" == x"true" ]]; then
    [ ! -x /usr/bin/tree ] || /usr/bin/tree "${pkgdir}"
  fi
}

leaf_install_package() {
  # install need to be in fakeroot
  leaf_build_package
  # trace need to to in the same fakeroot
  leaf_trace_package "${pkgdir}"
  # ROOT starts
  src_preinstall
  leaf_merge_package "${pkgdir}"
  src_postinstall
  leaf_update_package_database add "${PKG_PREFIX}/${PKG_NAME}"
  leaf_update_environment
  # ROOT ends
  echo -e "${GREEN_COLOR}* Package ${PKG_PREFIX}/${PKG_NAME} has been installed${CLEAR_COLOR}\n"
}

leaf_dirct-install_package() {
  # install need to be in fakeroot
  #leaf_build_package
  # trace need to to in the same fakeroot
  leaf_trace_package "${pkgdir}"
  # ROOT starts
  src_preinstall
  leaf_merge_package "${pkgdir}"
  src_postinstall
  leaf_update_package_database add "${PKG_PREFIX}/${PKG_NAME}"
  leaf_update_environment
  # ROOT ends
  echo -e "${GREEN_COLOR}* Package ${PKG_PREFIX}/${PKG_NAME} has been installed${CLEAR_COLOR}\n"
}

leaf_force-install_package(){
  FORCE_INSTALL=1
  leaf_install_package
}

leaf_remove_package() {
  [ -n "$(grep "${PKG_PREFIX}/${PKG_NAME}" ${INSTALLED_PACKAGES})" ] || {
    leaf_error "Package ${PKG_PREFIX}/${PKG_NAME} is NOT installed"
  }
  src_preremove
  local _file _link _directory _etc_backup_path _relative_path _backup_etc=false
  _etc_backup_path="/etc/.leaf_backup/${PKGNAME}_$(date +%Y%m%d%H%M%S)"
  cat "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/FILES" | while read -r _file; do
    if [[ "${_file}" == /etc/* && -f "${_file}" ]]; then
      _backup_etc=true
      _relative_path="${_file#/etc/}"
      mkdir -pv "${_etc_backup_path}/$(dirname "${_relative_path}")"
      mv -v "${_file}" "${_etc_backup_path}/${_relative_path}"
    else
      rm -fv "${_file}"
    fi
  done
  if [[ "${_backup_etc}" == true ]]; then
    leaf_record_message "Found files in /etc, backuped to ${_etc_backup_path}."
  fi
  cat "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/LINKS" | while read -r _link; do
    [ ! -L "${_link}" ] || rm -fv "${_link}"
  done
  cat "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}/DIRS" | while read -r _directory; do
    rmdir --ignore-fail-on-non-empty "${_directory}"
  done
  src_postremove
  leaf_invoke_hooks remove
  rm -rf "${TRACE_DIR}/${PKG_PREFIX}/${PKG_NAME}"
  leaf_update_package_database delete "${PKG_PREFIX}/${PKG_NAME}"
  leaf_update_environment
}

leaf_search_package() {
  local _item _regex
  for _item in "$@"; do
    _regex+="${_item}\|"
  done
  _regex="${_regex:0:${#_regex}-2}"
  find "${PKGBUILD_DIR}" -type f -regex ".*\(${_regex}\).*\.PKGBUILD" -printf "%P\n" | while read -r _item; do
    echo "${_item%.PKGBUILD}"
  done
}

leaf_show_package() {
  source "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}".PKGBUILD
  echo "Package: ${pkgname}"
  echo "Version: ${pkgver}"
  echo "Description: ${pkgdesc}"
  echo "Homepage: ${homepage}"
  echo -e "License: ${license[@]}\n"
}

leaf_pack_package() {
  leaf_build_package
  pushd "${srcdir}" > /dev/null 2>&1
  echo "${PKG_PREFIX}" > .PKG_PREFIX
  install -m644 "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}".PKGBUILD .PKGBUILD
  install -dm755 "${BINARY_DIR}"
  local _target="${BINARY_DIR}/${pkgname}-${pkgver}${BINARY_EXT}"
  rm -fv "${_target}"
  tar -I "${COMPRESS_PROG}" -cf "${_target}" "$(basename ${pkgdir})" .PKG{_PREFIX,BUILD}
  popd > /dev/null 2>&1
}

leaf_unpack_package() {
  srcdir="${TEMP_DIR}/$(basename ${1%.pkg.*})"
  pkgdir="${srcdir}"/__pkgdir__
  rm -rf "${srcdir}" && install -dm755 "${srcdir}" && cd "${srcdir}"
  tar -xf $1 -C "${srcdir}"
  source "${srcdir}"/.PKGBUILD
  PKG_PREFIX="$(cat "${srcdir}"/.PKG_PREFIX)"
  PKG_NAME="${pkgname}-${pkgver}"
  install -Dm644 "${srcdir}"/.PKGBUILD "${PKGBUILD_DIR}/${PKG_PREFIX}/${PKG_NAME}".PKGBUILD
  leaf_trace_package "${pkgdir}"
  src_preinstall
  leaf_merge_package "${pkgdir}"
  src_postinstall
  leaf_update_package_database add "${PKG_PREFIX}/${PKG_NAME}"
  leaf_update_environment
  echo -e "${GREEN_COLOR}* Package ${PKG_PREFIX}/${PKG_NAME} has been installed${CLEAR_COLOR}\n"
}

main $@
