#!/bin/sh

set -eu

script_path=$(cd "$(dirname "${0}")" && pwd)
top_srcdir=$(realpath "${script_path}/..")

# extract all includes found in source files found in the same folder as specified Makefile.am
#
# param ${1} path to a Makefile.am
# return all dependencies libs in the order of compilation
extract_includes() {
  # extract folder name from current library Makefile.am
  CURRENT_FOLDER=$(dirname "${1}")"/"
  # select only files in current folder
  SEARCH_FILES=$(echo "${FILE_LIST}" | grep "${CURRENT_FOLDER}")
  # select all lines containing '#include ' directive
  RAW_INCLUDES_LIST=$(echo "${SEARCH_FILES}" | xargs grep "^#include " 2>/dev/null)
  # filter only included dependencies found in other libraries by using the form 'other_lib_name/header_file.h'
  # to do this it is required to select the string between '<' and '>', searching for '/' character and returning the name until last '/'
  RAW_INCLUDES_LIST=$(echo "${RAW_INCLUDES_LIST}" | cut -d "#" -f 2 | tr "\"" " " | cut -d "<" -f 2 | cut -d ">" -f 1 | grep "/" | rev | cut -d "/" -f 2 | rev | sort -u)
  # filter includes that are not compiled by the project's Makefiles
  INCLUDES_LIST=
  for i in ${LIBRARIES_LIST}; do
    for j in ${RAW_INCLUDES_LIST}; do
      if test "${j}" = "${i}"; then
        INCLUDES_LIST="${i} ${INCLUDES_LIST}"
        break
      fi
    done
  done
  # remove empty spaces
  INCLUDES_LIST=$(echo "${INCLUDES_LIST}" | tr -s " ")
  # order dependencies in the order of compilation
  FILTERED_INCLUDES_LIST=
  for i in ${LIBRARIES_LIST}; do
    if test "$(echo "${INCLUDES_LIST}" | grep -c "\b${i}\b")" -ne 0; then
      FILTERED_INCLUDES_LIST="${i} ${FILTERED_INCLUDES_LIST}"
    fi
  done
  echo "${FILTERED_INCLUDES_LIST}"
}

# extract all header only files and headers and source files found in the external library required by specified library
# param ${1} name of the current library
# param ${2} name of the external dependency library required by current library
# return the list of header only files as 'HEADERS: heaser1.h header2.h' and header and source files as 'HEADERS_AND_SOURCES: source1.h source1.cc source2.h source2.cpp'
extract_non_include_files() {
  # extract folder name for current library Makefile.am
  CURRENT_FOLDER=$(dirname "src/lib/${1}/Makefile.am")"/"
  # extract folder name for external dependency library Makefile.am
  EXTERNAL_FOLDER=$(dirname "src/lib/${2}/Makefile.am")"/"
  # select only files in current folder
  SEARCH_FILES=$(echo "${FILE_LIST}" | grep "${CURRENT_FOLDER}")
  HEADERS_LIST=
  NON_HEADERS_LIST=
  # select all lines containing '#include ' directive
  RAW_INCLUDES_LIST=$(echo "${SEARCH_FILES}" | xargs grep "^#include " 2>/dev/null)
  # filter only included headers found in other libraries by using the form 'other_lib_name/header_file.h'
  # to do this it is required to select the string between '<' and '>', searching for '/' character, search for the extension marker '.' and returning the name after last '/'
  RAW_INCLUDES_LIST=$(echo "${RAW_INCLUDES_LIST}" | cut -d "#" -f 2 | tr "\"" " " | cut -d "<" -f 2 | cut -d ">" -f 1 | grep "/" | grep "\b${2}\b" | cut -d "/" -f 2 | grep "\." | sort -u)
  # select only files in dependency library folder and strip full path
  RELATIVE_SEARCH_FILES=$(echo "${FILE_LIST}" | grep "${EXTERNAL_FOLDER}" | sed -e "s#${EXTERNAL_FOLDER}##g")
  # search for the header file but also for source files
  for i in ${RAW_INCLUDES_LIST}; do
    # filter by name only (no extension)
    FILTER=$(echo "${i}" | cut -d "." -f 1)
    # filter non header files with exact name of the header file without the extension
    NON_HEADER=$(echo "${RELATIVE_SEARCH_FILES}" | grep "\b${FILTER}\." | grep -v "${i}")
    if test "$(echo "${NON_HEADER}" | wc -w)" -ne 0; then
      # append header and source file names
      NON_HEADERS_LIST="${i} ${NON_HEADER} ${NON_HEADERS_LIST}"
    else
      # append header only file name
      HEADERS_LIST="${i} ${HEADERS_LIST}"
    fi
  done
  # sort header only files
  HEADERS_LIST=$(echo "${HEADERS_LIST}" | tr -s " " | sort -u)
  # sort header and source files
  NON_HEADERS_LIST=$(echo "${NON_HEADERS_LIST}" | tr -s " " | sort -u)
  echo "HEADERS_AND_SOURCES:${NON_HEADERS_LIST}"
  echo "HEADERS:${HEADERS_LIST}"
}

# extract all valid dependencies of a specified library
#
# param ${1} list of all libraries in the reverse compilation order
# param ${2} library name for which the dependency list is computed
# return the list of dependencies for specified library in the reverse compilation order
extract_dependencies() {
  echo "${1}" | grep -Eo "\b${2}\b.*$"
}

# extract computed dependency for specified library
#
# param ${1} library name for which the dependency list is retrieved
# param ${2} library path for which the dependency list is retrieved
# return stored value of computed dependencies or 'NONE' if dependencies have not been computed yet
extract_computed_dependencies() {
  PATH_TO_NAME=$(echo "${2}" | tr -s "/" "_")
  NAME="COMPUTED_DEPENDENCIES_${PATH_TO_NAME}_${1}"
  if test -n "$(eval "echo \"\${$NAME+x}\"")"; then
    eval "echo \"\${$NAME}\""
  else
    echo "NONE"
  fi
}

# extract library directive
#
# param ${1} artifact path
extract_library_directive() {
  ARTIFACT_PATH="${1}"
  grep 'LIBADD\|LDADD' "${ARTIFACT_PATH}/Makefile.am" | sort | tr -s ' ' | cut -d " " -f 1 | sort -u | tr '\n' ' ' | sed 's/ *$//'
}

# extract library name
#
# param ${1} artifact path
extract_library_name() {
  ARTIFACT_PATH="${1}"
  grep 'LIBRARIES' "${ARTIFACT_PATH}/Makefile.am" | grep "LIBRARIES" | tr -s ' ' | cut -d " " -f 3
}

# compute artifact dependencies
#
# param ${1} artifact name
# param ${2} artifact path
compute_dependencies() {
  ARTIFACT="${1}"
  ARTIFACT_PATH="${2}"
  echo ""
  echo "########################################"
  echo "### ${ARTIFACT_PATH}/${ARTIFACT}"
  echo "########################################"
  echo ""
  # all valid dependencies that can be added by each dependency library
  echo "${ARTIFACT_PATH}/${ARTIFACT} valid dependencies:"
  echo "${VALID_LIST}"
  # detect dependencies errors by searching for dependencies that are compiled after the current library and can generate missing symbols
  NON_RECURSIVE_BASE_DEPENDENCIES=
  for j in ${BASE_DEPENDENCIES}; do
    # only add the dependency if it is in the valid dependencies list to prevent infinite recursion and log the error otherwise
    if test "$(echo "${VALID_LIST}" | grep -c "\b${j}\b")" -eq 0; then
      # search for external header and source files
      INVALID_EXTERNAL_DEPENDENCIES=$(extract_non_include_files "${ARTIFACT}" "${j}") || true
      # filter header only external files
      EXTERNAL_HEADERS=$(echo "${INVALID_EXTERNAL_DEPENDENCIES}" | grep "HEADERS:" | cut -d ":" -f 2)
      # filter header and source external files
      EXTERNAL_ALL=$(echo "${INVALID_EXTERNAL_DEPENDENCIES}" | grep "HEADERS_AND_SOURCES:" | cut -d ":" -f 2)
      echo "### ERROR ### dependencies ERROR for ${ARTIFACT_PATH}/${ARTIFACT} on ${j} with:"
      # if there are any header only external files
      if test "$(echo "${EXTERNAL_ALL}" | wc -w)" -ne 0; then
        echo "non header only files: ${EXTERNAL_ALL}"
      fi
      # if there are any header and source external files
      if test "$(echo "${EXTERNAL_HEADERS}" | wc -w)" -ne 0; then
        echo "header only files: ${EXTERNAL_HEADERS}"
      fi
    else
      # don't add current library to it's dependencies list
      if test "${j}" != "${ARTIFACT}"; then
        NON_RECURSIVE_BASE_DEPENDENCIES="${NON_RECURSIVE_BASE_DEPENDENCIES} ${j}"
      fi
    fi
  done
  # all found dependencies in the reverse compilation order
  BASE_DEPENDENCIES=$(echo "${BASE_DEPENDENCIES}" | sed 's/ *$//')
  # all found and valid dependencies in the reverse compilation order
  NON_RECURSIVE_BASE_DEPENDENCIES=$(echo "${NON_RECURSIVE_BASE_DEPENDENCIES}" | sed 's/^ *//;s/ *$//')
  echo "${ARTIFACT_PATH}/${ARTIFACT} base dependencies:"
  echo "${BASE_DEPENDENCIES}"
  echo "${ARTIFACT_PATH}/${ARTIFACT} non recursive dependencies:"
  echo "${NON_RECURSIVE_BASE_DEPENDENCIES}"
  # minimum set of dependencies for current library
  DEPENDENCIES=
  for j in ${NON_RECURSIVE_BASE_DEPENDENCIES}; do
    NEW_DEPENDENCIES=$(extract_computed_dependencies "${j}" "src/lib")
    if test "${NEW_DEPENDENCIES}" == "NONE"; then
      echo "### ERROR ### computed dependency not found for ${j}"
    else
      DEPENDENCIES="${NEW_DEPENDENCIES} ${DEPENDENCIES}"
    fi
  done
  DEPENDENCIES=$(echo "${DEPENDENCIES} ${NON_RECURSIVE_BASE_DEPENDENCIES}" | tr -s " " "\n" | sort -u | sed 's/ *$//')
  # order dependencies in the order of compilation
  SORTED_DEPENDENCIES=
  for j in ${LIBRARIES_LIST}; do
    if test "$(echo "${DEPENDENCIES}" | grep -c "\b${j}\b")" -ne 0; then
      SORTED_DEPENDENCIES="${j} ${SORTED_DEPENDENCIES}"
    fi
  done
  SORTED_DEPENDENCIES=$(echo "${SORTED_DEPENDENCIES}" | sed 's/ *$//g')
  echo "${ARTIFACT_PATH}/${ARTIFACT} minimum dependencies:"
  echo "${SORTED_DEPENDENCIES}"
  echo ""
  echo "++++++++++++++++++++++++++++++++++++++++"
  ARTIFACT_DIRECTIVE=$(extract_library_directive "${ARTIFACT_PATH}/${ARTIFACT}")
  for j in ${SORTED_DEPENDENCIES}; do
    DEPENDENCY_LIBRARY_NAME=$(extract_library_name "src/lib/${j}")
    echo "${ARTIFACT_DIRECTIVE} += \$(top_builddir)/src/lib/${j}/${DEPENDENCY_LIBRARY_NAME}"
  done
  echo "++++++++++++++++++++++++++++++++++++++++"
  echo "########################################"
  echo ""
}

# Folder containing full repo. Default is "tools/.."
REPO_FOLDER="${1-${top_srcdir}}"
cd "${REPO_FOLDER}"

# filter all Makefile.am files
MAKEFILES_LIST=$(find . -type f -wholename '*src/*Makefile.am' | sed 's#\./##g' | sort)

# if no Makefile.am found exit
if test -z "${MAKEFILES_LIST}"; then
  echo "invalid repo path: no Makefile.am file found"
  exit
fi

echo "list of Makefile.am:"
echo "${MAKEFILES_LIST}"

# base Makefile.am for all sources is in src/lib/Makefile.am
BASE_MAKEFILE=$(echo "${MAKEFILES_LIST}" | grep "src/lib/Makefile.am")

# if no src/lib/Makefile.am found exit
if test -z "${BASE_MAKEFILE}"; then
  echo "invalid repo path: no src/lib/Makefile.am file found"
  exit
fi

echo "base Makefile.am:"
echo "${BASE_MAKEFILE}"

# generate the list of libraries in the compilation order
LIBRARIES_LIST=
RAW_LIBRARIES_LIST=$(grep 'SUBDIRS' "${BASE_MAKEFILE}")
LIBRARIES_LIST=$(echo "${RAW_LIBRARIES_LIST}" | tr ' ' '\n' | grep -v SUBDIRS | grep -v '=' | tr '\n' ' ' | sed 's/ *$//')

# generate the list of libraries in the reverse compilation order
REVERSE_LIBRARIES_LIST=$(echo "${LIBRARIES_LIST}" | tr ' ' '\n' | tac | tr '\n' ' ' | sed 's/ *$//')

echo "list of libraries:"
echo "${LIBRARIES_LIST}"

echo "reverse list of libraries:"
echo "${REVERSE_LIBRARIES_LIST}"

# filter all files of interest ignoring irrelevant ones
# ignore .git, .libs, .deps doc folders and .o .lo .Plo .Po .gcno .gcda .m4 .dox .json .mes files
FILE_LIST=$(find . 2>/dev/null | grep -v "\.git" | grep -v "/\.libs/" | grep -v "\.o$" | grep -v "/\.deps/" | grep -v "\.lo$" | grep -v "\.Plo$" | grep -v "\.Po$" | grep -v "\.gcno$" | grep -v "gcda" | grep -v "\.m4$" | grep -v "\.dox$" | grep -v "\.json$" | grep -v "/doc/" | grep -v "\.mes$" | sort)

#echo "files:"
#echo "${FILE_LIST}"

BASE_LIBRARIES_MAKEFILES=

# generate the list of dependencies for all libraries in src/lib
for i in ${LIBRARIES_LIST}; do
  # generate current library Makefile.am path
  BASE_LIBRARIES_MAKEFILES="${BASE_LIBRARIES_MAKEFILES} src/lib/${i}/Makefile.am"
  # extract dependencies found in the library folder
  BASE_DEPENDENCIES=$(extract_includes "src/lib/${i}/Makefile.am") || true
  # generate the list of valid dependencies for the current library (take compilation order into account)
  VALID_LIST=$(extract_dependencies "${REVERSE_LIBRARIES_LIST}" "${i}")
  compute_dependencies "${i}" "src/lib"
  PATH_TO_NAME=$(echo "src/lib" | tr -s "/" "_")
  export "COMPUTED_DEPENDENCIES_${PATH_TO_NAME}_${i}=${SORTED_DEPENDENCIES}"
done

# remove empty spaces
BASE_LIBRARIES_MAKEFILES=$(echo "${BASE_LIBRARIES_MAKEFILES}" | sed 's/ *$//' | tr ' ' '\n')

echo "base Makefile.am files:"
echo "${BASE_LIBRARIES_MAKEFILES}"

OTHER_MAKEFILES=$(echo "${MAKEFILES_LIST}" | tr -s " " "\n" | grep -v "src/lib/" | grep -v "src/share/" | grep -v "src/Makefile.am")

echo "remaining Makefile.am files:"
echo "${OTHER_MAKEFILES}"

for i in ${OTHER_MAKEFILES}; do
  # extract dependencies found in the artifact folder
  BASE_DEPENDENCIES=$(extract_includes "${i}") || true
  # generate the list of valid dependencies for the current artifact (take compilation order into account)
  VALID_LIST="${REVERSE_LIBRARIES_LIST}"
  ARTIFACT=$(echo "${i}" | rev | cut -d "/" -f 2 | rev)
  ARTIFACT_PATH=$(echo "${i}" | rev | cut -d "/" -f 3- | rev)
  compute_dependencies "${ARTIFACT}" "${ARTIFACT_PATH}"
  PATH_TO_NAME=$(echo "${ARTIFACT_PATH}" | tr -s "/" "_")
  export "COMPUTED_DEPENDENCIES_${PATH_TO_NAME}_${ARTIFACT}=${SORTED_DEPENDENCIES}"
done
