#! /usr/bin/env bash
#
#   Copyright (c) 2015-2017 Nat! - Mulle kybernetiK
#   All rights reserved.
#
#   Redistribution and use in source and binary forms, with or without
#   modification, are permitted provided that the following conditions are met:
#
#   Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
#   Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
#   Neither the name of Mulle kybernetiK nor the names of its contributors
#   may be used to endorse or promote products derived from this software
#   without specific prior written permission.
#
#   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
#   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
#   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
#   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
#   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
#   POSSIBILITY OF SUCH DAMAGE.
#
MULLE_EXECUTABLE_VERSION_MAJOR=3
MULLE_EXECUTABLE_VERSION_MINOR=15
MULLE_EXECUTABLE_VERSION_PATCH=0

MULLE_EXECUTABLE_VERSION="${MULLE_EXECUTABLE_VERSION_MAJOR}.${MULLE_EXECUTABLE_VERSION_MINOR}.${MULLE_EXECUTABLE_VERSION_PATCH}"


MULLE_EXECUTABLE_FUNCTIONS_MIN="3.12"
MULLE_EXECUTABLE_FUNCTIONS_MAX="4"


#
# This is the main user interface to mulle-bootstrap
# sorta like git
#

trap_fail()
{
   exit 1
}


SHOWN_COMMANDS="\
   bootstrap     : does install and build recursively [default]
   build         : builds fetched repositories
   clean         : cleans mulle-bootstrap produced files.
   defer         : join a master mulle-bootstrap project
   emancipate    : emancipate from a master mulle-bootstrap project
   paths         : get include and library paths
   init          : initializes a bootstrap project
   update        : updates repositories (fetch).
   upgrade       : updates and checks out repositories (pull).
   show          : show repositories and brews"

DARWIN_COMMANDS="\
   xcode         : sets up xcodeproj settings"
HIDDEN_COMMANDS="\
   config        : read or edit config settings
   expansion     : read or edit fetch expansions
   git           : run git commands on fetched repositories
   library-path  : print path to mulle-bootstrap libexec
   log           : show build logs
   project-path  : print path to folder containing .bootstrap
   run           : run command with proper dependencies PATHs
   setting       : read or edit build settings
   status        : show status of repositories and brews
   systeminstall : install dependencies as system libraries/headers
   tag           : tag fetched repositories
   uname         : mulle-bootstraps simplified uname(1)
   version       : print mulle-bootstrap version"


print_commands()
{
   local  show_all="${1:-NO}"

   echo "${SHOWN_COMMANDS}"

   if [ "${UNAME}" = 'darwin' ]
   then
      echo "${DARWIN_COMMANDS}"
   fi

   if [ "${show_all}" != "NO" ]
   then
      echo "${HIDDEN_COMMANDS}"
   fi
}


mulle_bootstrap_usage()
{
   local  verbose="${1:-NO}"

   cat <<EOF
Usage:
   ${MULLE_BOOTSTAP_EXECUTABLE} [flags] [command] [options]

Flags:
   -a -y     : default answer to questions (scripts wont be checked)
               -a (clone preferred) -y (local copy/symlink preferred)
   -f        : force operation
   -n        : do nothing creative or destructive
   --no-defer: do not defer to master, if project is a minion
   -v        : -v to be more verbose (-vv or -vvv for more verbosity)
   -V        : verbose build
   -DKEY=VAL : define local expansion key/value C style
EOF

   bootstrap_technical_option_usage

   cat <<EOF

Commands:
EOF

   print_commands "${verbose}" | sort


   if [ "${verbose}" = "NO" ]
   then
      cat <<EOF
      (use mulle-bootstrap -v help to show more commands)
EOF
   fi

   cat <<EOF
Options are command specific. Use mulle-bootstrap <command> -h for help.
EOF

   exit 1
}


_bootstrap_main()
{
   local command

   log_debug "::: bootstrap begin :::"

   . mulle-bootstrap-fetch.sh
   . mulle-bootstrap-build.sh
   . mulle-bootstrap-warn-scripts.sh

   # used for option handling only
   MULLE_BOOTSTRAP_WILL_BUILD="YES"

   warn_scripts_main "${BOOTSTRAP_DIR}" || exit 1

   if fetch_needed
   then
      MULLE_EXECUTABLE_FAIL_PREFIX="mulle-bootstrap fetch"
      fetch_main "$@" || exit 1
      # used for option handling only
      MULLE_BOOTSTRAP_DID_FETCH="YES"
   else
      log_verbose "No fetch needed"
   fi

   if build_needed
   then
      MULLE_EXECUTABLE_FAIL_PREFIX="mulle-bootstrap build"
      MULLE_BOOTSTRAP_DID_BUILD="YES"
      build_main "$@" || exit 1
   else
      log_verbose "No build needed"
   fi

   #
   # only show for default "bootstrap" action
   #
   if [ "${MULLE_BOOTSTRAP_DID_FETCH}" = "YES" ]
   then
      if [ -f "${BOOTSTRAP_DIR}.auto/motd" ]
      then
         log_fluff "Show motd"

         cat "${BOOTSTRAP_DIR}.auto/motd" >&2
      fi
   fi

   log_debug "::: bootstrap end :::"
}


#
# stolen from:
# http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac
# ----
#
_prepend_path_if_relative()
{
   case "$2" in
      /*)
         echo "$2"
      ;;
      *)
         echo "$1/$2"
      ;;
   esac
}


resolve_symlinks()
{
   local dir_context
   local linkpath

   if linkpath="`readlink "$1"`"
   then
      dir_context=`dirname -- "$1"`
      resolve_symlinks "`_prepend_path_if_relative "${dir_context}" "${linkpath}"`"
   else
      echo "$1"
   fi
}


bootstrap_libexec_path()
{
   local executablepath="$1"

   local exedir
   local exedirpath
   local prefix
   local libexecpath

   case "${executablepath}" in
      \.*|/*|~*)
      ;;

      *)
         executablepath="`command -v "${executablepath}"`"
      ;;
   esac

   executablepath="`resolve_symlinks "${executablepath}"`"
   exedirpath="`dirname "${executablepath}"`"
   prefix="`dirname "${exedirpath}"`"
   libexecpath="${prefix}/libexec/mulle-bootstrap"

   if [ ! -x "${libexecpath}/mulle-bootstrap-functions.sh" ]
   then
      libexecpath="${exedirpath}/src"
   fi

   case "$libexecpath" in
      /*|~*)
      ;;

      *)
         libexecpath="$PWD/$libexecpath"
      ;;
   esac

   if [ -x "${libexecpath}/mulle-bootstrap-functions.sh" ]
   then
      echo "${libexecpath}"
   fi
}


bootstrap_init()
{
   MULLE_LIBEXEC_PATH="`bootstrap_libexec_path "$0"`"
   if [ -z "${MULLE_LIBEXEC_PATH}" ]
   then
      echo "Fatal Error: Could not find libexec for ${MULLE_EXECUTABLE} ($PWD)" >&2
      exit 1
   fi

   PATH="${MULLE_LIBEXEC_PATH}:$PATH"
   export PATH

   if [ ! -z "${MULLE_BOOTSTRAP_LIBEXEC_TRACE}" ]
   then
      echo "PATH=$PATH" >&2
   fi

   #
   # shell export commands with minimal
   # trap setup
   #
   case "${1}" in
      library-path)
         echo "${MULLE_LIBEXEC_PATH}"
         exit 0
      ;;

      version)
         echo "${MULLE_EXECUTABLE_VERSION}"
         exit 0
      ;;

      shell)
         fail "shell has moved to mulle-sde"
      ;;
   esac

   #  set -e # more pain then gain in the end
   #  set -u # doesn't work with my style

   # now include this first to get
   # logging and UNAME

   . mulle-bootstrap-logging.sh
   . mulle-bootstrap-local-environment.sh || fail "not loaded"

   trap trap_fail INT
   set -o pipefail
}


bootstrap_cd_projectpath()
{
   local  orgpwd

   orgpwd="$PWD"

   while ! is_bootstrap_project
   do
      local old

      old="$PWD"
      cd ..

      if [ "$PWD" = "${old}" ]
      then
         if [ "${command}" = "seting" ]
         then
            cd "${orgpwd}"
         else
            return 1
         fi
      fi
   done
}


bootstrap_main()
{
   local command
   local ps4string

   local MULLE_FLAG_ANSWER="ASK"
   local MULLE_FLAG_MAGNUM_FORCE="NONE"
   local MULLE_FLAG_IGNORE_GRAVEYARD="NO"
   local MULLE_FLAG_VERBOSE_BUILD="NO"
   local MULLE_FLAG_LENIENT="NO"

   # technical flags
   local MULLE_FLAG_DONT_DEFER="NO"
   local MULLE_FLAG_EXEKUTOR_DRY_RUN="NO"
   local MULLE_FLAG_FOLLOW_SYMLINKS="YES"
   local MULLE_FLAG_LOG_CACHE="NO"
   local MULLE_FLAG_LOG_DEBUG="NO"
   local MULLE_FLAG_LOG_EXEKUTOR="NO"
   local MULLE_FLAG_LOG_FLUFF="NO"
   local MULLE_FLAG_LOG_SCRIPTS="NO"
   local MULLE_FLAG_LOG_SETTINGS="NO"
   local MULLE_FLAG_LOG_VERBOSE="NO"
   local MULLE_FLAG_LOG_MERGE="NO"
   local MULLE_TRACE_PATHS_FLIP_X="NO"
   local MULLE_TRACE_POSTPONE="NO"
   local MULLE_TRACE_RESOLVER_FLIP_X="NO"
   local MULLE_TRACE_SETTINGS_FLIP_X="NO"

   #
   # simple option handling
   #
   while [ $# -ne 0 ]
   do
      if bootstrap_technical_flags "$1"
      then
         shift
         continue
      fi

      case "$1" in
         -a|--prefer-origin)
            MULLE_FLAG_ANSWER="NO"
          ;;

         -y|--prefer-local)
            MULLE_FLAG_ANSWER="YES"
         ;;

         --lenient)
            MULLE_FLAG_LENIENT="YES"
         ;;

         --no-defer)
            MULLE_FLAG_DONT_DEFER="YES"
         ;;

         # used persistently throughout so it's global
         --follow-symlinks)
            MULLE_FLAG_FOLLOW_SYMLINKS="YES"
         ;;

         -f|--force)
            MULLE_FLAG_MAGNUM_FORCE="BOTH"
         ;;

         --ignore-graveyard)
            MULLE_FLAG_IGNORE_GRAVEYARD="YES"
         ;;

         -h|--help)
            mulle_bootstrap_usage "${MULLE_TRACE}"
         ;;

         -D*)  # just like C
            # define key values (by putting them into .bootstrap.local)
            bootstrap_define_expansion "`echo "$1" | sed s'/^-D[ ]*//'`"
         ;;

         -V|--verbose-build)
            MULLE_FLAG_VERBOSE_BUILD="YES"
         ;;

         --version)
            echo "${MULLE_EXECUTABLE_VERSION}"
            exit 0
         ;;

         -*)
            log_error "${MULLE_EXECUTABLE_FAIL_PREFIX}: Unknown option \"$1\""
            mulle_bootstrap_usage
         ;;

         *)
            break
         ;;
      esac

      shift
   done

   bootstrap_setup_trace "${MULLE_TRACE}"

   # source in environment now

   local_environment_main

   #
   # some commands only run when we have a .bootstrap folder
   # we do it somewhat like git, and progress up the directory
   # hierarchy until we find a .bootstrap or .bootstrap.local folder
   # this is useful for `mulle-bootstrap paths` (with absolute paths)
   #
   command="${1:-bootstrap}"

   case "${command}" in
      help|init|uname)
      ;;

      project-path)
         if ! bootstrap_cd_projectpath
         then
            exit 1
         fi
         echo "`pwd -P`"
         exit 0
      ;;

      *)
         if ! bootstrap_cd_projectpath
         then
            fail "There is no ${BOOTSTRAP_DIR} or ${BOOTSTRAP_DIR}.local folder here ($orgpwd), can't continue"
         fi
      ;;
   esac

   if bootstrap_should_defer_to_master "$@"
   then
      return 1
   fi

   #
   # some commands are only valid, if the fetch went through
   # defer/emancipate clean, so that's ok
   #
   case "${MULLE_FLAG_MAGNUM_FORCE}" in
      "BOTH")
         set_fetch_needed
      ;;

      "NONE")
         case "${command}" in
            bootstrap|build|systeminstall|tag|update|upgrade)
               bootstrap_ensure_consistency
            ;;
         esac
      ;;
   esac

   MULLE_EXECUTABLE_FAIL_PREFIX="${MULLE_EXECUTABLE} ${command}"
   [ $# -eq 0 ] || shift

   MULLE_EXECUTABLE_OPTIONS="$@"

   case "${command}" in
      bootstrap)
         _bootstrap_main "$@"
      ;;

      build)
         . mulle-bootstrap-build.sh

         build_main "$@" || exit 1
      ;;

      clean)
         . mulle-bootstrap-clean.sh

         clean_main "$@" || exit 1
      ;;

      config|configs|configurations)
         . mulle-bootstrap-settings.sh

         config_main "$@" || exit 1
      ;;

      defer)
         . mulle-bootstrap-defer.sh

         defer_main "$@" || exit 1
      ;;

      dist)
         . mulle-bootstrap-clean.sh

         clean_main "dist" || exit 1
      ;;

      emancipate)
         . mulle-bootstrap-defer.sh

         emancipate_main "$@" || exit 1
      ;;

      expansion|expansions)
         . mulle-bootstrap-settings.sh

         expansion_main "$@" || exit 1
      ;;

      path|paths)
         . mulle-bootstrap-paths.sh

         paths_main "$@" || exit 1
      ;;

      git)
         . mulle-bootstrap-scm.sh

         git_main "$@" || exit 1
      ;;

      help)
         mulle_bootstrap_usage "${MULLE_TRACE}" || exit 1
      ;;

      init)
         . mulle-bootstrap-init.sh

         init_main "$@" || exit 1
      ;;

      install|fetch)
         . mulle-bootstrap-fetch.sh

         DONT_ASK_AFTER_WARNING=YES fetch_main "$@" || exit 1
      ;;

      library-path)
         echo "$PATH" | tr ':' '\012' | head -1
         return 0
      ;;

      log|logs)
         [ -z "${MULLE_BOOTSTRAP_LOG_SH}" ] && . mulle-bootstrap-log.sh

         log_main "$@" || exit 1
      ;;

      master-path)
         echo "${PWD}"
         return 0
      ;;

      run)
         . mulle-bootstrap-paths.sh

         run_main "$@" || exit 1
      ;;

      setting|settings)
         . mulle-bootstrap-settings.sh

         setting_main "$@" || exit 1
      ;;

      systeminstall)
         . mulle-bootstrap-systeminstall.sh

         systeminstall_main "$@" || exit 1
      ;;

      show)
         [ -z "${MULLE_BOOTSTRAP_SHOW_SH}" ] && . mulle-bootstrap-show.sh

         show_main "$@" || exit 1
      ;;

      status)
         . mulle-bootstrap-status.sh

         local rval

         status_main "$@"
         rval="$?"

         [ "$rval" -eq 0 ] || exit $rval
      ;;

      tag)
         . mulle-bootstrap-tag.sh

         tag_main "$@" || exit 1
      ;;

      type)
         if is_minion_bootstrap_project
         then
            echo "Minion"
         else
            if is_master_bootstrap_project
            then
               echo "Master"
            else
               echo "Freeman"
            fi
         fi
      ;;

      uname)
         echo "${UNAME}"
         exit 0
      ;;

      update)
         . mulle-bootstrap-fetch.sh

         update_main "$@" || exit 1
      ;;

      upgrade)
         . mulle-bootstrap-fetch.sh

         upgrade_main "$@" || exit 1
      ;;

      version)
         echo "${MULLE_EXECUTABLE_VERSION}"
         return 0
      ;;

      xcode|setup-xcode)
         . mulle-bootstrap-xcode.sh

         MULLE_EXECUTABLE_FAIL_PREFIX="mulle-bootstrap xcode"
         xcode_main "$@" || exit 1
      ;;

      *)
         log_error "${MULLE_EXECUTABLE_FAIL_PREFIX}: Unknown command \"${command}\""
         mulle_bootstrap_usage
      ;;
   esac
   log_debug ":bootstrap_main done:"
}


#
# leading backslash ? looks like we're getting called from
# mingw via a .BAT or so
#
case "$PATH" in
   '\\'*)
      PATH="`tr '\\' '/' <<< "${PATH}"`"
   ;;
esac

#
# service both names
#
MULLE_EXECUTABLE_PATH="$0"
case "$MULLE_EXECUTABLE_PATH" in
   /*|~*)
   ;;

   *)
      MULLE_EXECUTABLE_PATH="$PWD/$MULLE_EXECUTABLE_PATH"
   ;;
esac


MULLE_EXECUTABLE="`basename -- "$0"`"
MULLE_ARGUMENTS="$@"
MULLE_EXECUTABLE_ENV_PATH="$PATH"
MULLE_EXECUTABLE_FAIL_PREFIX="${MULLE_EXECUTABLE}"
MULLE_EXECUTABLE_PID="$$"
export MULLE_EXECUTABLE_PID


bootstrap_init "$@" # needs params


main()
{
   if ! bootstrap_main "$@"
   then
      # just do it again, but cd has been set differently
      main "$@"
      exit $?
   fi
}

main "$@"

trap - TERM EXIT