#! /usr/bin/env bash
#
#   Copyright (c) 2018 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.
#
# Create fresh HeadersAndSources.cmake from filesystem information
#
[ "${TRACE}" = "YES" ] && set -x && : "$0" "$@"


CMAKE_SOURCE_UPDATE_VERSION="0.0.1"


usage()
{
   if [ ! -z "$1" ]
   then
      log_error "$1"
   fi

   cat <<EOF >&2
Usage:
   mulle-sde-cmake-source-update

   Let mulle-monitor or mulle-sde run this for you.

EOF
   exit 1
}


emit_cmake_var()
{
   log_entry "emit_cmake_var" "$@"

   local name="$1"
   local contents="$2"

   if [ -z "${contents}" ]
   then
      return
   fi

   echo "set( ${name}"
   LC_ALL="C" sed '/ /s/\(.*\)/"\1"/' <<< "${contents}"
   echo ")"
   echo
}


emit_common_directories()
{
   log_entry "emit_common_directories" "$@"

   local items="$1"
   local emitter="$2"
   local parameter="$3"

   [ -z "${emitter}" ] && internal_fail "emitter is empty"

   local collection

   collection="`rexekutor sed -n -e 's|^[^;]*;\(.*\)/[^/]*\.h|\1|p' <<< "${items}" | LC_ALL=C sort -u`"

   if [ ! -z "${collection}" ]
   then
      if [ "${PROJECT_SOURCE_DIR}" = "." ]
      then
         collection=".
${collection}"
      fi

      "${emitter}" "${parameter}" "${collection}"
   fi
}


emit_by_category()
{
   log_entry "emit_by_category" "$@"

   local items="$1"
   local emitter="$2"

   [ -z "${emitter}" ] && internal_fail "emitter is empty"

   local collectname
   local collection
   local remainder

   remainder="${items}"

   while [ ! -z "${remainder}" ]
   do
      # https://stackoverflow.com/questions/1773939/how-to-use-sed-to-return-something-from-first-line-which-matches-and-quit-early
      collectname="`sed -n -e '/\(^[^;]*\).*/{s//\1/p;q;}' <<< "${remainder}" `"
      collection="`egrep "^${collectname};" <<< "${remainder}" | cut -d ';' -f 2-`"

      "${emitter}" "${collectname}" "${collection}"

      remainder="`egrep -v "^${collectname};" <<< "${remainder}" `"
   done

   :
}


create_headers_file()
{
   log_entry "create_headers_file" "$@"

   local categorized_files="$1"

   local text_hdr

   case "${MULLE_SDE_CMAKE_HEADERS_FILE}" in
      "DISABLED")
         log_verbose "Header generation disabled by MULLE_SDE_CMAKE_HEADERS_FILE=DISABLED"
      ;;

      "NONE")
         text_hdr="# Header generation disabled by MULLE_SDE_CMAKE_HEADERS_FILE=NONE"
         MULLE_SDE_CMAKE_HEADERS_FILE=""
      ;;

      *)
         local categorized_headers
         local text_hdr1
         local text_hdr2

         categorized_headers="`rexekutor egrep '^[^;]*HEADERS;' <<< "${categorized_files}"`"

         text_hdr1="`emit_common_directories "${categorized_headers}" emit_cmake_var INCLUDE_DIRS` "  || return 1
         text_hdr2="`emit_by_category "${categorized_headers}" emit_cmake_var` "  || return 1

         if [ -z "${text_hdr2}" ]
         then
            text_hdr2="# no headers"
         fi

         text_hdr="${text_hdr1}

${text_hdr2}"

      ;;
   esac

   MULLE_SDE_CMAKE_HEADERS_FILE="${MULLE_SDE_CMAKE_HEADERS_FILE:-cmake/_Headers.cmake}"

   text_hdr="# ${MULLE_SDE_CMAKE_HEADERS_FILE} is generated by \`mulle-sde\`. Edits will be lost.
#
if( MULLE_TRACE_INCLUDE)
   MESSAGE( STATUS \"# Include \\\"\${CMAKE_CURRENT_LIST_FILE}\\\"\" )
endif()

${text_hdr}
"

   log_fluff "Create cmake header files (${PWD})"

   log_verbose "Writing ${C_RESET_BOLD}${MULLE_SDE_CMAKE_HEADERS_FILE}"
   exekutor mkdir -p  "`fast_dirname "${MULLE_SDE_CMAKE_HEADERS_FILE}"`"
   exekutor chmod a+w "${MULLE_SDE_CMAKE_HEADERS_FILE}"  2> /dev/null
   redirect_exekutor "${MULLE_SDE_CMAKE_HEADERS_FILE}" echo "${text_hdr}"
   exekutor chmod a-w "${MULLE_SDE_CMAKE_HEADERS_FILE}"
}


create_sources_file()
{
   log_entry "create_sources_file" "$@"

   local categorized_files="$1"

   local text_src

   case "${MULLE_SDE_CMAKE_SOURCES_FILE}" in
      "DISABLED")
         log_verbose "Source generation disabled by MULLE_SDE_CMAKE_SOURCES_FILE=DISABLED"
      ;;

      "NONE")
         text_src="# Source generation disabled by MULLE_SDE_CMAKE_SOURCES_FILE=NONE"
         MULLE_SDE_CMAKE_SOURCES_FILE=""
      ;;

      *)
         local categorized_sources

         categorized_sources="`rexekutor egrep -v '^[^;]*HEADERS;' <<< "${categorized_files}"`"

         text_src="`emit_by_category "${categorized_sources}" emit_cmake_var`"   || return 1

         if [ -z "${text_src}" ]
         then
            #
            # header only libraries are tricky to do portably
            #
            text_src="message( ERROR \" No sources found. \`mulle-sde environment set MULLE_SDE_CMAKE_SOURCES_FILE NONE\`, if this is intentional\")"
         fi

      ;;
   esac

   MULLE_SDE_CMAKE_SOURCES_FILE="${MULLE_SDE_CMAKE_SOURCES_FILE:-cmake/_Sources.cmake}"
   text_src="# ${MULLE_SDE_CMAKE_SOURCES_FILE} is generated by \`mulle-sde\`. Edits will be lost.
#
if( MULLE_TRACE_INCLUDE)
   MESSAGE( STATUS \"# Include \\\"\${CMAKE_CURRENT_LIST_FILE}\\\"\" )
endif()

${text_src}"

   log_fluff "Create cmake source files (${PWD})"

   log_verbose "Writing ${C_RESET_BOLD}${MULLE_SDE_CMAKE_SOURCES_FILE}"
   exekutor mkdir -p  "`fast_dirname "${MULLE_SDE_CMAKE_SOURCES_FILE}"`" 2> /dev/null
   exekutor chmod a+w "${MULLE_SDE_CMAKE_SOURCES_FILE}"  2> /dev/null
   redirect_exekutor "${MULLE_SDE_CMAKE_SOURCES_FILE}" echo "${text_src}"
   exekutor chmod a-w "${MULLE_SDE_CMAKE_SOURCES_FILE}"
}


main()
{
   log_entry "main" "$@"

   # technical flags
   local MULLE_TRACE
   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_MERGE="NO"
   local MULLE_FLAG_LOG_SCRIPTS="NO"
   local MULLE_FLAG_LOG_SETTINGS="NO"
   local MULLE_FLAG_LOG_VERBOSE="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"

   local OPTION_PARALLEL="YES"

   while [ $# -ne 0 ]
   do
      if options_technical_flags "$1"
      then
         shift
         continue
      fi

      case "$1" in
         -h*|--help|help)
            usage
         ;;

         --no-parallel)
            OPTION_PARALLEL="NO"
         ;;

         --version)
            echo "${CMAKE_SOURCE_UPDATE_VERSION}"
            return 0
         ;;

         -*)
            usage "Unknown option \"$1\""
         ;;

         *)
            break
         ;;
      esac

      shift
   done

   options_setup_trace "${MULLE_TRACE}"

   [ $# -ne 0 ] && usage "superflous arguments $*"

   MULLE_MATCH="${MULLE_MATCH:-`command -v mulle-match`}"
   [ -z "${MULLE_MATCH}" ] && fail "mulle-match not in PATH"

   #
   # With 1000 header and 1000 source files, a full update takes ~7s on
   # my machine. It certainly would be interesting to make this properly
   # incremental.
   #
   # Foreseen problems:
   #
   #     missed events ?
   #
   local categorized_files

   if ! categorized_files="`exekutor "${MULLE_MATCH}" \
                                 ${MULLE_TECHNICAL_FLAGS} \
                                 ${MULLE_MATCH_FLAGS} \
                                 find --format "%C;%f\\n" \
                                      --match-filter "source"`"
   then
      return 1
   fi

   if [ -z "${categorized_files}" ]
   then
      log_warning "No matching source files found. " >&2
      # exit 0 # but still create empty files, otherwise cmake is unhappy
   fi

   categorized_files="`LC_ALL=C rexekutor sort -d -t';' -k 1,2 <<< "${categorized_files}" `"

   if [ "${OPTION_PARALLEL}" = "YES" ]
   then
      create_headers_file "${categorized_files}" &
      create_sources_file "${categorized_files}" &

      log_fluff "waiting..."
      wait
      log_fluff 'done!'
   else
      create_headers_file "${categorized_files}"
      create_sources_file "${categorized_files}"
   fi
}


_init()
{
   if [ -z "${MULLE_BASHFUNCTIONS_LIBEXEC_DIR}" ]
   then
      MULLE_BASHFUNCTIONS_LIBEXEC_DIR="`mulle-bashfunctions-env "libexec-dir" 2> /dev/null`"
      [ -z "${MULLE_BASHFUNCTIONS_LIBEXEC_DIR}" ] && \
         echo "mulle-bashfunctions-env not installed" >&2 && \
         exit 1
   fi

   . "${MULLE_BASHFUNCTIONS_LIBEXEC_DIR}/mulle-bashfunctions.sh" "minimal" || exit 1
}

_init "$@"
main "$@"