#compdef make gmake pmake dmake

local prev="$words[CURRENT-1]" file expl tmp is_gnu dir incl

expandVars() {
    local open close var val tmp=$2 ret=$2
    if (( $1 == 0 )); then
	return
    fi
    while :; do
	var=${tmp#*\$}
	if [[ $var != $tmp ]]; then
	    tmp=$var
	    case $var in
	    (\(*)
		open='('
		close=')'
		;;
	    ({*)
		open='{'
		close='}'
		;;
	    ([[:alpha:]]*)
		open=''
		close=''
		var=${(s::)var[1]}
		;;
	    (\$*)
		# avoid parsing second $ in $$
		tmp=${tmp#\$}
		;&
	    (*)
		continue
		;;
	    esac
	    if [[ $open != '' ]]; then
		var=${var#$open}
		var=${var%%$close*}
	    fi
	    case $var in
	    ([[:alnum:]_]#)
		val=${(P)var}
		val=$(expandVars $(($1 - 1)) $val)
		ret=${ret//\$$open$var$close/$val}
		;;
	    esac
	else
	    print -- ${ret//\$\$/\$}
	    return
	fi
    done
}

# parseMakefile only runs inside $(...), so it doesn't matter that
# it pollutes the global namespace, setting zsh variables to
# make variables.  The difficult case is where a make variable
# is special in zsh; we use local -h to hide those.  This
# isn't a complete solution since it means variables defined in
# included Makefiles are undefined before returning to the parent.
parseMakefile() {
    local input var val TAB=$'\t' dir=$1

    while read input; do
	case "$input " in
	([[:alnum:]][[:alnum:]_]#[ $TAB]#=*)
	    var=${input%%[ $TAB]#=*}
	    val=${input#*=}
	    val=${val##[ $TAB]#}
	    [[ ${(tP)var} = *special ]] && local -h $var
	    eval $var=\$val
	    ;;
	([[:alnum:]][[:alnum:]_]#[ $TAB]#:=*)
	    var=${input%%[ $TAB]#:=*}
	    val=${input#*=}
	    val=${val##[ $TAB]#}
	    val=$(expandVars 10 $val)
	    [[ ${(tP)var} = *special ]] && local -h $var
	    eval $var=\$val
	    ;;
	([[:alnum:]][^$TAB:=]#:[^=]*)
	    input=${input%%:*}
	    print $(expandVars 10 $input)
	    ;;
	(${~incl} *)
	    local f=${input##${~incl} ##}
	    if [[ $incl = '.include' ]]; then
		f=${f#[\"<]}
		f=${f%[\">]}
	    fi
	    f=$(expandVars 10 $f)
	    case $f in
	    (/*) ;;
	    (*)  f=$dir/$f ;;
	    esac
	    if [ -r $f ]; then
		parseMakefile ${f%%/[^/]##} < $f
	    fi
	    ;;
	esac
    done
}

findBasedir () {
  local file index basedir
  basedir=$PWD
  for ((index=0; index<$#@; index++)); do
    if [[ $@[index] = -C ]]; then
      file=${~@[index+1]};
      if [[ -z $file ]]; then
	# make returns with an error if an empty arg is given
	# even if the concatenated path is a valid directory
	return
      elif [[ $file = /* ]]; then
	# Absolute path, replace base directory
	basedir=$file
      else
	# Relative, concatenate path
	basedir=$basedir/$file
      fi
    fi
  done
  print -- $basedir
}

_pick_variant -r is_gnu gnu=GNU unix -v -f

if [[ $is_gnu = gnu ]]; then
    incl="(-|)include"
else
    incl=.include
fi
if [[ "$prev" = -[CI] ]]; then
  _files -W ${(q)$(findBasedir ${words[1,CURRENT-1]})} -/
elif [[ "$prev" = -[foW] ]]; then
  _files -W ${(q)$(findBasedir $words)}
else
  file="$words[(I)-f]"
  if (( file )); then
    file=${~words[file+1]}
    [[ $file = [^/]* ]] && file=${(q)$(findBasedir $words)}/$file
    [[ -r $file ]] || file=
  else
    local basedir
    basedir=${(q)$(findBasedir $words)}
    if [[ $is_gnu = gnu && -r $basedir/GNUmakefile ]]; then
      file=$basedir/GNUmakefile
    elif [[ -r $basedir/makefile ]]; then
      file=$basedir/makefile
    elif [[ -r $basedir/Makefile ]]; then
      file=$basedir/Makefile
    else
      file=''
    fi
  fi

  if [[ -n "$file" ]] && _tags targets; then
    if [[ $is_gnu = gnu ]] &&
       zstyle -t ":completion:${curcontext}:targets" call-command; then
       tmp=( $(_call_program targets "$words[1]" -nsp --no-print-directory -f "$file" .PHONY 2> /dev/null | parseMakefile $PWD) )
    else
       tmp=( $(parseMakefile $PWD < $file) )
    fi
    _wanted targets expl 'make target' compadd -a tmp && return 0
  fi
  compstate[parameter]="${PREFIX%%\=*}"
  compset -P 1 '*='
  _value "$@"
fi
