#!/bin/sh

############################################################
# Copyright (c) 2009-2014 by Aleksey Cheusov
#
# See LICENSE file in the distribution.
############################################################

set -e

LC_ALL=C
export LC_ALL

: ${CHECK_COMMON_SH_DIR:=/usr/pkg/libexec/mk-configure}

if test -d /usr/xpg4/bin; then
    # We cannot work with Solaris' default usercrap
    PATH=/usr/xpg4/bin:$PATH
    export PATH
fi

##################################################
# options
usage (){
    cat <<EOF
mkc_check_decl detects presense of define, variable, function or type
in system header files by compiling a test program.

Usage:
 mkc_check_decl [OPTIONS] <CHECKTYPE> <what> [includes...]
where CHECKTYPE is either of the following: "define", "variable",
"func[0-9]", "funcordefine[0-9]", "type", "member" or "prototype".

OPTIONS:
   -h             display this help
   -d             delete cache files and temporaty files
   -D             delete cache files

Examples:
   mkc_check_decl define __GNUC__
   mkc_check_decl define RTLD_LAZY dlfcn.h
   mkc_check_decl variable sys_errlist errno.h
   mkc_check_decl variable __malloc_hook malloc.h
   mkc_check_decl func3 poll poll.h
   mkc_check_decl func2 fgetln stdio.h
   mkc_check_decl funcordefine1 htobe32 sys/endian.h
   mkc_check_decl type mbstate_t wchar.h
   mkc_check_decl type long-long
   mkc_check_decl member tm.tm_isdst time.h
   mkc_check_decl member ifreq.ifr_addr.sa_len net/if.h
   mkc_check_decl prototype 'int connect(int __fd, const struct sockaddr * __addr, socklen_t __len)' sys/socket.h
   mkc_check_decl prototype 'int connect(int __fd, struct sockaddr * __addr, socklen_t __len)' sys/socket.h
EOF
}

if test $# -eq 0; then
    usage
    exit 1
fi
if test "_$1" = '_-h'; then
    usage
    exit 0
fi
if test "_$1" = '_-d'; then
    delcache=all
    shift
fi
if test "_$1" = '_-D'; then
    delcache=result
    shift
fi

##################################################
# initializing

decltype=`echo "$1" | sed -e 's/[0-9]//g'`
argscnt=`echo "$1" | sed 's/[^0-9]//g'`
shift

declwhat=`echo "$1" | /usr/bin/awk '{gsub(/-/, " "); gsub(/ +[(]/, "("); $1=$1; print}'`
shift

typemsg="$decltype $declwhat"
case "$decltype" in
    type)
	pathpart=`echo "${decltype}_${declwhat}_$*" | tr '/. ' '__~'`
	;;
    prototype)
	pathpart=`echo "${decltype}_${declwhat}_$*" | tr '/(). *' '____~8'`
	funcname=`echo "$declwhat" | sed 's,^[^(]*[^a-zA-Z0-9_]\([a-zA-Z0-9_]*\) *[(].*$,\1,'`
	;;
    *)
	pathpart=`echo "$decltype$argscnt $declwhat" $* | tr '/. ' '___'`
	;;
esac

. ${CHECK_COMMON_SH_DIR}/mkc_check_common.sh

##################################################
# functions

compile (){
    if $CC -c -o "$tmpo" $CPPFLAGS $CFLAGS "$tmpc" 2>"$tmperr"
    then
	return 0
    else
	return 1
    fi
}

##############################
is_define (){
    get_includes "$@" > "$tmpc"

    cat >> "$tmpc" <<EOF
#if defined($declwhat)
int func(void)
{
   return 0;
}
#else
#error "$declwhat is not a define"
#endif
EOF

    #
    compile
}

##############################
is_variable (){
    get_includes "$@" > "$tmpc"

    cat >> "$tmpc" <<EOF
int func(void)
{
   return sizeof (($declwhat)) && (&$declwhat != 0);
}
EOF
    #
    compile
}

##############################
has_size (){
    get_includes "$@" > "$tmpc"

    cat >> "$tmpc" <<EOF
int func(void)
{
   return sizeof ($declwhat);
}
EOF

    #
    compile
}

is_type (){
    has_size "$@" || return 1
    is_variable "$@" && return 1
    return 0
}

##############################
is_func_basic (){
    func_or_define="$1"
    shift

    get_includes "$@" > "$tmpc"

    printf '%s\n' 'void func (void){' >> "$tmpc"
    if test "$func_or_define" = 0; then
	printf '#ifndef %s\n' "$declwhat" >> "$tmpc"
    fi
    printf '%s\n' "    if (${declwhat}) return;" >> "$tmpc"
    if test "$func_or_define" = 0; then
	printf '#endif\n' >> "$tmpc"
    fi
    printf '%s' "    ${declwhat}(" >> "$tmpc"

    /usr/bin/awk -v N="$argscnt" '
BEGIN {
   for (i=0; i < N; ++i){
      if (i)
         printf ","
      printf "0"
   }
}
' >> "$tmpc"

    printf ');\n}\n' >> "$tmpc"

    #
    compile
}

is_func (){
    is_func_basic 1 "$@"
}

is_funcordefine (){
    is_func_basic 0 "$@"
}

##############################
is_prototype (){
    get_includes "$@" > "$tmpc"

    cat >> "$tmpc" <<EOF
void func2 (void);
void func (void);
void func2 (void)
{
   if (${funcname}) return;
}
${declwhat};
void func (void)
{
   if (${funcname}) return;
}
EOF

    #
    compile
}


##############################
is_member (){
    get_includes "$@" > "$tmpc"

    type_t=`echo $declwhat | sed 's/[.].*$//'`
    member=`echo $declwhat | sed 's/^[^.]*[.]//'`

    cat >> "$tmpc" <<EOF
int func(void)
{
   $type_t var;
   return sizeof (var.$member);
}
EOF

    #
    compile
}

##################################################
# test

if test $# -gt 0; then
    incs_msg=" ( $* )"
fi

check_itself (){
    if is_${decltype} "$@"
    then
	echo 1
    else
	echo 0
    fi
}

check_and_cache "checking for ${typemsg}${incs_msg}" "$cache" "$@"

##################################################
# clean-ups

cleanup

##################################################
# finishing

if test "$ret" -eq 1; then
    printme 'yes\n' 1>&2
else
    printme 'no\n' 1>&2
fi

echo $ret
