#! /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.
#

[ "${TRACE}" = "YES" ] && set -x && : "$0" "$@"


CMAKE_SOURCETREE_UPDATE_VERSION="0.0.2"


usage()
{
   [ $# -ne 0 ] && log_error "$1"

   cat <<EOF >&2
Usage:
   ${MULLE_USAGE_NAME} <action> <filename> <category>

   Let mulle-monitor run this for you. None of the arguments are actually
   used.

EOF
   exit 1
}


# https://cmake.org/Wiki/CMake_Checking_Platform
uname_to_cmake()
{
   log_entry "uname_to_cmake" "$@"

   local uname="$1"

   local systemname

   case "${uname}" in
      "")
         fail "uname is empty"
      ;;

      *)
         systemname="`tr 'a-z' 'A-Z' <<< "${uname:0:1}"`"
         systemname="${systemname}${uname:1}"
      ;;
   esac

   echo "\${CMAKE_SYSTEM_NAME} MATCHES \"${systemname}\""
}


osexclude_to_cmake_if()
{
   log_entry "osexclude_to_cmake_if" "$@"

   local marks="$1"

   local cmakevar

   local excludes
   local onlys

   set -o noglob ; IFS=","

   for osexclude in ${marks}
   do
      IFS="${DEFAULT_IFS}"; set +o noglob

      case "${osexclude}" in
         only-os-*)
            cmakevar="`uname_to_cmake "${osexclude:8}"`"
            onlys="`concat "${onlys}" "${cmakevar}" " OR " `"
         ;;

         no-os-*)
            cmakevar="`uname_to_cmake "${osexclude:6}"`"
            excludes="`concat "${excludes}" "${cmakevar}" " AND "`"
         ;;
      esac
   done


   IFS="${DEFAULT_IFS}"; set +o noglob

   if [ ! -z "${onlys}" ]
   then
      echo "if( ${onlys})"
      return
   fi

   if [ ! -z "${excludes}" ]
   then
      case "${excludes}" in
         *" OR "*)
            echo "if( NOT (${excludes}))"
         ;;

         *)
            echo "if( NOT ${excludes})"
         ;;
      esac
   fi
}


_emit_cmake_find_library()
{
   log_entry "_emit_cmake_find_library" "$@"

   local preference="$1"; shift

   local aliases="$1"
   local identifier="$2"
   local containername="$3"
   local marks="$4"
   local indent="$5"

   local libraries
   local i

   IFS=","; set -o noglob
   for i in ${aliases}
   do
      case "${preference}" in
         "static")
            libraries="`concat "${libraries}" "\\\${CMAKE_STATIC_LIBRARY_PREFIX}${i}\\\${CMAKE_STATIC_LIBRARY_SUFFIX}"`"
         ;;

         "shared")
            libraries="`concat "${libraries}" "\\\${CMAKE_SHARED_LIBRARY_PREFIX}${i}\\\${CMAKE_SHARED_LIBRARY_SUFFIX}"`"
         ;;
      esac

      # fallback to whatever linkage
      libraries="`concat "${libraries}" "${i}"`"
   done
   IFS="${DEFAULT_IFS}"; set -o noglob

   local failstring
   local failstatus

   case ",${marks}," in
      *,no-require-link,*)

         failstatus="STATUS"
         failstring="${identifier}_LIBRARY is missing but it\'s \"no-require-link\""
      ;;

      *)
         failstatus="FATAL_ERROR"
         failstring="${identifier}_LIBRARY was not found"
      ;;
   esac

#
# this is getting unwieldy, should probably use a function
#
   cat <<EOF
${indent}if( NOT ${identifier}_LIBRARY)
${indent}   find_library( ${identifier}_LIBRARY NAMES ${libraries})
${indent}   message( STATUS "${identifier}_LIBRARY is \${${identifier}_LIBRARY}")
${indent}
${indent}   # the order looks ascending, but due to the way this file is read
${indent}   # it ends up being descending, which is what we need
${indent}   if( ${identifier}_LIBRARY)
${indent}      set( ${containername}
${indent}         \${${containername}}
${indent}         \${${identifier}_LIBRARY}
${indent}         CACHE INTERNAL "need to cache this"
${indent}      )
${indent}      # temporarily expand CMAKE_MODULE_PATH
${indent}      get_filename_component( _TMP_${identifier}_ROOT "\${${identifier}_LIBRARY}" DIRECTORY)
${indent}      get_filename_component( _TMP_${identifier}_ROOT "\${_TMP_${identifier}_ROOT}" DIRECTORY)
${indent}
${indent}      # search for DependenciesAndLibraries.cmake to include
${indent}      foreach( _TMP_${identifier}_NAME in ${aliases})
${indent}         set( _TMP_${identifier}_DIR "\${_TMP_${identifier}_ROOT}/include/\${_TMP_${identifier}_NAME}/cmake")
${indent}         # use explicit path to avoid "surprises"
${indent}         if( EXISTS "\${_TMP_${identifier}_DIR}/DependenciesAndLibraries.cmake")
${indent}            unset( ${identifier}_DEFINITIONS)
${indent}            list( INSERT CMAKE_MODULE_PATH 0 "\${_TMP_${identifier}_DIR}")
${indent}            include( "\${_TMP_${identifier}_DIR}/DependenciesAndLibraries.cmake")
${indent}            list( REMOVE_ITEM CMAKE_MODULE_PATH "\${_TMP_${identifier}_DIR}")
${indent}            set( INHERITED_DEFINITIONS
${indent}               \${INHERITED_DEFINITIONS}
${indent}               \${${identifier}_DEFINITIONS}
${indent}               CACHE INTERNAL "need to cache this"
${indent}            )
${indent}            break()
${indent}         endif()
${indent}      endforeach()
EOF

   #
   # for objective-c we find objc-loader.inc in the public include files
   #
   case ",${marks}," in
      *,no-all-load,*)
      ;;

      *)
         cat <<EOF
${indent}
${indent}      # search for objc-loader.inc in include directory
${indent}      foreach( _TMP_${identifier}_NAME in ${aliases})
${indent}         set( _TMP_${identifier}_FILE "\${_TMP_${identifier}_ROOT}/include/\${_TMP_${identifier}_NAME}/objc-loader.inc")
${indent}         if( EXISTS "\${_TMP_${identifier}_FILE}")
${indent}            set( INHERITED_OBJC_LOADERS
${indent}               \${INHERITED_OBJC_LOADERS}
${indent}               \${_TMP_${identifier}_FILE}
${indent}               CACHE INTERNAL "need to cache this"
${indent}            )
${indent}            break()
${indent}         endif()
${indent}      endforeach()
EOF
      ;;
   esac

   cat <<EOF
${indent}   else()
${indent}      message( ${failstatus} "${failstring}")
${indent}   endif()
${indent}endif()
EOF
}


_emit_cmake_library()
{
   log_entry "_emit_cmake_library" "$@"

   _emit_cmake_find_library "any" "$@"
}


_emit_cmake_dependency()
{
   log_entry "_emit_cmake_dependency" "$@"

   _emit_cmake_find_library "static" "$@"
}


#
#
_emit_cmake_header_only()
{
   log_entry "_emit_cmake_header_only" "$@"

   local aliases="$1"
   local identifier="$2"
   local containername="$3"
   local marks="$4"
   local indent="$5"

   local headers
   local i

   IFS=","; set -o noglob
   for i in ${aliases}
   do
      headers="`concat "${headers}" "${i}.h"`"
      headers="`concat "${headers}" "${i}/${i}.h"`"
   done
   IFS="${DEFAULT_IFS}"; set +o noglob

   case ",${marks}," in
      *,no-require-link,*)

         failstatus="STATUS"
         failstring="${identifier}_HEADER is missing but it\'s \"no-require-link\""
      ;;

      *)
         failstatus="FATAL_ERROR"
         failstring="${identifier}_HEADER was not found"
      ;;
   esac
   #
   # the idea here is that cmake should generate the proper -I option
   #
   cat <<EOF
${indent}if( NOT ${identifier}_HEADER)
${indent}   find_file( ${identifier}_HEADER NAMES ${headers})
${indent}   message( STATUS "${identifier}_HEADER is \${${identifier}_HEADER}")
${indent}   set( ${containername}
${indent}      \${${identifier}_HEADER}
${indent}      \${${containername}}
${indent}      CACHE INTERNAL "need to cache this"
${indent}   )
${indent}   if( ${identifier}_HEADER)
${indent}      # search for DependenciesAndLibraries.cmake to include
${indent}      get_filename_component( _TMP_${identifier}_DIR "\${${identifier}_HEADER}" DIRECTORY)
${indent}      set( _TMP_${identifier}_DIR "\${_TMP_${identifier}_DIR}/cmake")
${indent}      # use explicit path to avoid "surprises"
${indent}      if( EXISTS "\${_TMP_${identifier}_DIR}/DependenciesAndLibraries.cmake")
${indent}         unset( ${identifier}_DEFINITIONS)
${indent}         list( INSERT CMAKE_MODULE_PATH 0 "\${_TMP_${identifier}_DIR}")
${indent}         include( "\${_TMP_${identifier}_DIR}/DependenciesAndLibraries.cmake")
${indent}         list( REMOVE_ITEM CMAKE_MODULE_PATH "\${_TMP_${identifier}_DIR}")
${indent}         set( INHERITED_DEFINITIONS
${indent}               \${INHERITED_DEFINITIONS}
${indent}               \${${identifier}_DEFINITIONS}
${indent}               CACHE INTERNAL "need to cache this"
${indent}         )
${indent}      endif()
${indent}   else()
${indent}      message( ${failstatus} "${failstring}")
${indent}   endif()
${indent}endif()
EOF
}


emit_cmake_dependency()
{
   log_entry "emit_cmake_dependency" "$@"

   local emitter="$1"
   local containername="$2"
   local address="$3"
   local marks="$4"
   local aliases="$5"

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

   local indent
   local ifstatement
   local endifstatement
   local name

   #
   # ALL_LOAD_ is the default for Objective-C static libraries and is the
   # default. "C" libraries are marked with no-all-load and remove the
   # prefix
   #
   name="ALL_LOAD_${containername}"

   if [ ! -z "${marks}" ]
   then
      ifstatement="`osexclude_to_cmake_if "${marks}" `"
      if [ ! -z "${ifstatement}" ]
      then
         indent="   "
      fi

      case ",${marks}," in
         *,no-all-load,*)
            name="${containername}"
         ;;
      esac
   fi

   if [ ! -z "${ifstatement}" ]
   then
      echo "${ifstatement}"
   fi

   local identifier
   local filename

   if [ -z "${aliases}" ]
   then
      aliases="${address}"
   fi

   # first alias determines the identifier
   filename="`fast_basename "${aliases%%,*}"`"

   if [ -z "${MULLE_CASE_SH}" ]
   then
      # shellcheck source=mulle-case.sh
      . "${MULLE_BASHFUNCTIONS_LIBEXEC_DIR}/mulle-case.sh" || return 1
   fi

   identifier="`tweaked_de_camel_case "${filename}"`"
   identifier="`printf "%s" "${identifier}" | tr -c 'a-zA-Z0-9' '_'`"
   identifier="`tr 'a-z' 'A-Z' <<< "${identifier}"`"

   "${emitter}" "${aliases}" \
                "${identifier}" \
                "${name}" \
                "${marks}" \
                "${indent}"

   if [ ! -z "${ifstatement}" ]
   then
      echo "endif()"
   fi

   echo
   echo
}


emit_cmake_dependencies()
{
   log_entry "emit_cmake_dependencies" "$@"

   local emitter="$1"
   local containername="$2"
   local dependencies="$3"

   local dependency

   set -o noglob ; IFS="
"
   for dependency in ${dependencies}
   do
      IFS="${DEFAULT_IFS}"; set +o noglob

      local address
      local marks
      local aliases

      log_debug "read \"${dependency}\""

      IFS=";" read address marks aliases include <<< "${dependency}"

      log_debug "address: ${address}"
      log_debug "marks:   ${marks}"
      log_debug "aliases: ${aliases}"

      if [ ! -z "${address}" ]
      then
         log_verbose "Emit cmake statements for ${C_MAGENTA}${C_BOLD}${address}"
         emit_cmake_dependency "${emitter}" \
                               "${containername}" \
                               "${address}" \
                               "${marks}" \
                               "${aliases}"
      fi
   done
   IFS="${DEFAULT_IFS}"; set +o noglob
}


emit_dependency_subproject()
{
   log_entry "emit_dependency_subproject" "$@"

   local dependency
   local header

   header="`exekutor "${MULLE_SOURCETREE}" -V \
                           ${MULLE_TECHNICAL_FLAGS} \
                           ${MULLE_SOURCETREE_FLAGS} \
                           list \
                              --format '%a;%m;%i={aliases,,-------};%i={include,,}\n' \
                              --marks 'dependency,header,no-link' \
                              --output-raw \
                              --no-output-header`" || return 1
   emit_cmake_dependencies "_emit_cmake_header_only" \
                           "HEADER_ONLY_LIBRARIES" \
                           "${header}"

   dependency="`exekutor "${MULLE_SOURCETREE}" -V \
                           ${MULLE_TECHNICAL_FLAGS} \
                           ${MULLE_SOURCETREE_FLAGS} \
                           list \
                              --format '%a;%m;%i={aliases,,};%i={include,,}\n' \
                              --marks 'dependency,link' \
                              --output-raw \
                              --no-output-header`" || return 1

   emit_cmake_dependencies "_emit_cmake_dependency" \
                           "DEPENDENCY_LIBRARIES" \
                           "${dependency}"
}


emit_library()
{
   log_entry "emit_library" "$@"

   local library
   local header

   header="`exekutor "${MULLE_SDE}" \
                        ${MULLE_TECHNICAL_FLAGS} \
                        ${MULLE_SDE_FLAGS} \
                        library list \
                           --marks header,no-link \
                           -- \
                           --output-raw \
                           --output-no-header`" || return 1
   emit_cmake_dependencies "_emit_cmake_header_only" \
                           "HEADER_ONLY_LIBRARIES" \
                           "${header}"

   library="`exekutor "${MULLE_SDE}" \
                        ${MULLE_TECHNICAL_FLAGS} \
                        ${MULLE_SDE_FLAGS} \
                        library list \
                           --marks link \
                           -- \
                           --output-raw \
                           --output-no-header`" || return 1
   emit_cmake_dependencies "_emit_cmake_library" \
                           "OS_SPECIFIC_LIBRARIES" \
                           "${library}"
}

#
# collect library and dependency, with routines in
# mulle-sde-updatesupport.sh for reuse with stuff other than cmake
#
write_libraries_file()
{
   log_entry "write_libraries_file" "$@"

   local text_lib

   if [ "${MULLE_SDE_LIBRARIES_FILE}" = "NONE" ]
   then
      MULLE_SDE_LIBRARIES_FILE=""

      text_lib="# library generation turned off by MULLE_SDE_LIBRARIES_FILE"
   else
      #
      text_lib="`emit_library`" || return 1
      if [ -z "${text_lib}" ]
      then
         text_lib="# there are no libraries in the sourcetree"
      fi
   fi
   MULLE_SDE_LIBRARIES_FILE="${MULLE_SDE_LIBRARIES_FILE:-cmake/_Libraries.cmake}"

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

${text_lib}"

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


write_dependencies_file()
{
   log_entry "write_dependencies_file" "$@"

   local text_dep

   if [ "${MULLE_SDE_DEPENDENCIES_FILE}" = "NONE" ]
   then
      MULLE_SDE_DEPENDENCIES_FILE=""
      text_dep="# dependency generation turned off by MULLE_SDE_DEPENDENCIES_FILE"
   else
      text_dep="`emit_dependency_subproject`"  || return 1
      if [ -z "${text_dep}" ]
      then
         text_dep="# there are no applicable dependencies in the sourcetree"
      fi
   fi

   MULLE_SDE_DEPENDENCIES_FILE="${MULLE_SDE_DEPENDENCIES_FILE:-cmake/_Dependencies.cmake}"

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

${text_dep}"

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

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


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

   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
         ;;

         -p|--projectname)
            [ "$#" -eq 1 ] && usage "Missing argument to \"$1\""
            shift

            PROJECT_NAME="$1"
         ;;

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

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

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

         *)
            break
         ;;
      esac

      shift
   done

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

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

   options_setup_trace "${MULLE_TRACE}"

   if [ "${OPTION_PARALLEL}" = "YES" ]
   then
      write_dependencies_file "${categorized_files}" &
      write_libraries_file "${categorized_files}" &

      log_fluff "waiting..."
      wait
      log_fluff 'done!'
   else
      write_dependencies_file "${categorized_files}"
      write_libraries_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 "$@"