#!/bin/sh
# Fortran の USE から .lo 依存関係を生成します。
# Generate .lo dependencies from Fortran USE statements.
# 使用方法 / Usage:
#   gendepsf90.sh [--external REGEX]... [--map REGEX=PATH]... [--map-exclude REGEX]... files...
#   gendepsf90.sh --in-place Makefile.am [--external REGEX]... [--map REGEX=PATH]... [--map-exclude REGEX]... files...
# 例 / Example:
#   ./gendepsf90.sh --external 'netcdf|mpi' \
#     --map '^dc_'='$(top_builddir)/src/dc_utils/libdc_utils.la' \
#     --map '^gdnc'='$(top_builddir)/src/gtdata/gtdata_netcdf/libgtdata_netcdf.la' \
#     *.f90

set -eu

script_dir=$(cd "$(dirname "$0")" && pwd -P)
repo_root=$(cd "$script_dir/.." && pwd -P)
cwd=$(pwd -P)

make_topbuild_path() {
  # Convert a relative path to $(top_builddir)/<path-from-repo-root>
  # Leave absolute paths or Makefile variables as-is.
  p="$1"
  case "$p" in
    /*) printf '%s' "$p"; return ;;
    *'$('*')'*) printf '%s' "$p"; return ;;
  esac
  # Resolve to absolute
  dir=$(cd "$cwd" && cd "$(dirname "$p")" && pwd -P)
  abs="$dir/$(basename "$p")"
  case "$abs" in
    "$repo_root"/*)
      rel="${abs#$repo_root/}"
      printf '$(top_builddir)/%s' "$rel"
      ;;
    *)
      printf '%s' "$p"
      ;;
  esac
}

lc() {
  printf '%s' "$1" | tr 'A-Z' 'a-z'
}

usage() {
  cat <<'USAGE' 1>&2
Usage: gendepsf90.sh [--external REGEX]... [--map REGEX=PATH]... [--map-exclude REGEX]... files...
       gendepsf90.sh --in-place Makefile.am [--external REGEX]... [--map REGEX=PATH]... [--map-exclude REGEX]... files...

  --external REGEX   Regex for modules to ignore (case-insensitive)
  --map REGEX=PATH   Regex for modules handled by another dir (case-insensitive)
  --map-exclude REGEX  Regex to exclude from --map matches (case-insensitive)
  --in-place FILE    Replace marker block in FILE with generated deps

Output: dependency blocks for Makefile.am
USAGE
  exit 2
}

externals="^mpi;^omp_lib"
maps=""
files=""
in_place=""
map_excludes=""

while [ $# -gt 0 ]; do
  case "$1" in
    --external)
      [ $# -ge 2 ] || usage
      ext_re=$(lc "$2")
      if [ -z "$externals" ]; then
        externals="$ext_re"
      else
        externals="$externals;$ext_re"
      fi
      shift 2
      ;;
    --map)
      [ $# -ge 2 ] || usage
      case "$2" in
        *=*) : ;; 
        *) usage ;;
      esac
      re=$(lc "${2%%=*}")
      path="${2#*=}"
      path="$(make_topbuild_path "$path")"
      mapped="${re}=${path}"
      if [ -z "$maps" ]; then
        maps="$mapped"
      else
        maps="$maps;$mapped"
      fi
      shift 2
      ;;
    --map-exclude)
      [ $# -ge 2 ] || usage
      ex_re=$(lc "$2")
      if [ -z "$map_excludes" ]; then
        map_excludes="$ex_re"
      else
        map_excludes="$map_excludes;$ex_re"
      fi
      shift 2
      ;;
    --in-place)
      [ $# -ge 2 ] || usage
      in_place="$2"
      shift 2
      ;;
    --help|-h)
      usage
      ;;
    --)
      shift
      files="$files $*"
      break
      ;;
    -* )
      usage
      ;;
    * )
      files="$files $1"
      shift
      ;;
  esac
done

# shellcheck disable=SC2086
set -- $files
[ $# -gt 0 ] || usage

mapfile=$(mktemp)
depsfile=$(mktemp)
trap 'rm -f "$mapfile" "$depsfile"' EXIT

# Build module -> file map from local sources
awk '
function lcase(s,   i,c,r){r="";for(i=1;i<=length(s);i++){c=substr(s,i,1);r=r tolower(c)}return r}
function stripcomment(s){sub(/!.*/,"",s);return s}
{
  line=stripcomment($0)
  if (line ~ /^[ \t]*module[ \t]+/ && line !~ /^[ \t]*module[ \t]+procedure/ && line !~ /^[ \t]*end[ \t]+module/) {
    split(line, a, /[ \t]+/)
    if (a[2] != "") {
      print lcase(a[2]) " " FILENAME
    }
  }
}
' "$@" | awk '!seen[$1]++' > "$mapfile"

EXTERNALS="$externals" MAPS="$maps" MAP_EXCLUDES="$map_excludes" \
awk -v mapfile="$mapfile" '
function lcase(s,   i,c,r){r="";for(i=1;i<=length(s);i++){c=substr(s,i,1);r=r tolower(c)}return r}
function stripcomment(s){sub(/!.*/,"",s);return s}
function split_maps(s,   n,i,kv){
  n=split(s, arr, /;/)
  for(i=1;i<=n;i++){
    if(arr[i]=="") continue
    split(arr[i], kv, /=/)
    map_re[i]=kv[1]
    map_la[i]=substr(arr[i], length(kv[1])+2)
  }
  map_n=n
}
function split_excludes(s,   n,i){
  n=split(s, ex, /;/)
  for(i=1;i<=n;i++){
    if(ex[i]=="") continue
    map_ex[i]=ex[i]
  }
  map_ex_n=n
}
BEGIN{
  split_maps(ENVIRON["MAPS"])
  split_excludes(ENVIRON["MAP_EXCLUDES"])
  ext_n=split(ENVIRON["EXTERNALS"], ext_re, /;/)
  while((getline < mapfile)>0){
    mod2file[$1]=$2
  }
  missing_n = 0
}
function is_external(mod,   i){
  for(i=1;i<=ext_n;i++) if(ext_re[i]!="" && mod ~ ext_re[i]) return 1
  return 0
}
function mapped_la(mod,   i){
  for(i=1;i<=map_ex_n;i++) if(map_ex[i]!="" && mod ~ map_ex[i]) return ""
  for(i=1;i<=map_n;i++) if(map_re[i]!="" && mod ~ map_re[i]) return map_la[i]
  return ""
}
function add_dep(dep){
  if(dep=="" || dep==target) return
  if(!(dep in deps)) {deps[dep]=1; dep_list[++dep_n]=dep}
}
function flush(){
  if(dep_n==0) return
  print target ": \\";
  for(i=1;i<=dep_n;i++){
    if(i<dep_n) print "\t" dep_list[i] " \\";
    else print "\t" dep_list[i];
  }
}

FNR==1{
  # flush previous file
  if(NR>1) flush()
  # reset state
  delete deps; delete dep_list; dep_n=0; cont=0; buf=""
  file=FILENAME
  base=file
  sub(/.*\//,"",base)
  sub(/\.f90$/,"",base)
  target=base ".lo"
}
{
  line=stripcomment($0)
  if(cont){ buf=buf line } else { buf=line }
  if(buf ~ /&[ \t]*$/){ sub(/&[ \t]*$/, "", buf); cont=1; next } else { cont=0 }

  if(buf ~ /^[ \t]*use[ \t,]/){
    if(buf ~ /use[ \t]*,[ \t]*(intrinsic|non_intrinsic)/){ next }
    # normalize: allow optional :: and only-clause
    if(match(tolower(buf), /^[ \t]*use[ \t]*([,:]|::)?[ \t]*([a-z0-9_]+)/, m)){
      mod=m[2]
      mod=lcase(mod)
      if(is_external(mod)) next
      la=mapped_la(mod)
      if(la!=""){ add_dep(la); next }
      if(mod in mod2file){
        dep=mod2file[mod]
        sub(/.*\//,"",dep)
        sub(/\.f90$/,"",dep)
        add_dep(dep ".lo")
      } else {
        # missing local module mapping: ignore but warn once
        if(!(mod in missing)){
          missing[mod]=1
          missing_n++
          print "gendepsf90.sh: warning: module not found: " mod " (in " file ")" > "/dev/stderr"
        }
      }
    }
  }
}
END{
  flush()
  if (missing_n > 0) {
    print "gendepsf90.sh: error: unresolved modules: " missing_n > "/dev/stderr"
    exit 1
  }
}
' "$@" > "$depsfile"

if [ -z "$in_place" ]; then
  cat "$depsfile"
  exit 0
fi

if [ ! -f "$in_place" ]; then
  echo "gendepsf90.sh: error: file not found: $in_place" >&2
  exit 1
fi

begin_marker="# BEGIN AUTO-DEPS by gendepsf90"
end_marker="# END AUTO-DEPS by gendepsf90"

tmpfile=$(mktemp)
trap 'rm -f "$mapfile" "$depsfile" "$tmpfile"' EXIT

awk -v begin="$begin_marker" -v end="$end_marker" -v deps="$depsfile" '
BEGIN{inblk=0; done=0}
{
  if ($0 == begin) {
    print $0
    while ((getline line < deps) > 0) print line
    inblk=1
    done=1
    next
  }
  if ($0 == end) {
    inblk=0
    print $0
    next
  }
  if (!inblk) print $0
}
END{
  if (!done) {
    print "gendepsf90.sh: error: marker block not found" > "/dev/stderr"
    exit 1
  }
}
' "$in_place" > "$tmpfile"

mv "$tmpfile" "$in_place"
