#!/bin/sh

# Copyright (c) 2016-2017, Carsten Kunze <carsten.kunze@arcor.de>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.

usage () {
	echo "Usage: $0 [-s]"
	echo "	-s	Silence output"
	exit $1
}

SFLAG=
MAKE=
DEFS=
LIB_LEX=
LIB_CURSES=
cat /dev/null > compat.h

while [ $# -gt 0 ]; do
	case $1 in
	-s) SFLAG=1;;
	*)
		echo "$0: $1: Unknown option" >&2
		usage 1;;
	esac
	shift
done

check_for () {
	[ -e $LOG ] && cat <<EOT >>$LOG

================================================================================

EOT
	A="Checking for $1 ... "
	printf "$A\n\n" >>$LOG
	[ -z "$SFLAG" ] && printf "$A"
}

compile () {
	rm -f ${TMPNAM}.o $TMPNAM $OUT $ERR
	$MAKE -f $OUTMK $TMPNAM > $OUT 2> $ERR
	RESULT=$?
	cat $OUT $ERR >> $LOG
	if [ $RESULT -eq 0 ]; then true; else false; fi
}

test_result () {
	RESULT=$?
	RESULT_TEXT=${1:-0} # 1: omit "no", 2: say nothing
	if [ $RESULT -eq 0 ]; then
		echo success >>$LOG
		[ -z "$SFLAG" -a $RESULT_TEXT -lt 2 ] && \
		    echo "yes$PASS_TEXT"
		PASS_TEXT=
		[ -e $TMPC ] && rm -f $TMPC
		true
	else
		[ -z "$SFLAG" -a $RESULT_TEXT -lt 1 ] && echo no
		if [ -e $TMPC ]; then
			echo "Failed program:" >>$LOG
			pr -n -t $TMPC >>$LOG
			rm -f $TMPC
		fi
		false
	fi
}

gen_mk () {
	[ $# -eq 0 ] && rm -f $OUTMK
	[ -n "$LEX" ] && echo "LEX=$LEX" >> $OUTMK
	[ -n "$FLOAT_STORE" ] && echo "FLOAT_STORE=$FLOAT_STORE" >> $OUTMK
	[ -n "$DEFS" ] && echo "DEFINES=$DEFS" >> $OUTMK
	[ -n "$INCDIR_CURSES" ] && echo "INCDIR_CURSES=$INCDIR_CURSES" >> $OUTMK
	[ -n "$RPATH_CURSES" ] && echo "RPATH_CURSES=$RPATH_CURSES" >> $OUTMK
	[ -n "$LIBDIR_CURSES" ] && echo "LIBDIR_CURSES=$LIBDIR_CURSES" \
	    >> $OUTMK
	[ -n "$LIB_CURSES" ] && echo "LIB_CURSES=$LIB_CURSES" >> $OUTMK
	[ -n "$LIB_AVLBST" ] && echo "LIB_AVLBST=$LIB_AVLBST" >> $OUTMK
	[ -n "$LIB_LEX" ] && echo "LIB_LEX=$LIB_LEX" >> $OUTMK
	[ -n "$__CDBG"    ] && echo "__CDBG=$__CDBG" >> $OUTMK
	[ -n "$__CXXDBG"  ] && echo "__CXXDBG=$__CXXDBG" >> $OUTMK
	[ -n "$__CLDBG"   ] && echo "__CLDBG=$__CLDBG" >> $OUTMK
	[ -n "$__CLXXDBG" ] && echo "__CLXXDBG=$__CLXXDBG" >> $OUTMK
	cat $INMK >> $OUTMK || exit 1
}
check_make () {
	check_for "make(1)"

	cat <<EOT >$TMPMK
all:
	true
EOT
	make -f $TMPMK >> $LOG 2>&1
	test_result && {
		MAKE=make
		return
	}

	echo "Failed makefile:" >>$LOG
	pr -n -t $TMPMK >>$LOG

	check_for "bmake(1)"

	cat <<EOT >$TMPMK
all:
	true
EOT
	bmake -f $TMPMK >> $LOG 2>&1
	test_result && MAKE=bmake
}
check_Sanitizer () {
	check_for "CC Sanitizer"

	__CDBG=
	__CLDBG=
	__CCXXDBG=
	__CXXDBG=
	__CLXXDBG=
	__CDBG="$__CDBG -Wmissing-prototypes"
	__CDBG="$__CDBG -Wstrict-prototypes"
	__CCXXDBG="$__CCXXDBG -g -O0 -fno-omit-frame-pointer -fno-optimize-sibling-calls"
	__CCXXDBG="$__CCXXDBG -Wall"
	__CCXXDBG="$__CCXXDBG -Wextra"
	__CCXXDBG="$__CCXXDBG -Wsign-compare"
	__CCXXDBG="$__CCXXDBG -Wcast-align"
	__CCXXDBG="$__CCXXDBG -Wcast-qual"
	__CCXXDBG="$__CCXXDBG -Wunused-parameter"
	__CCXXDBG="$__CCXXDBG -Wunused-function"
	__CCXXDBG="$__CCXXDBG -Wshadow"
	__CCXXDBG="$__CCXXDBG -Wwrite-strings"
	__CLXXDBG="$__CLXXDBG -fno-common -fsanitize=address"

	[ -n "$CC" ] || CC=cc
	VER=`$CC --version`

	if echo $VER | grep -iq gcc || echo $VER | \
	    grep -iq 'Free Software Foundation'; then
		[ -z "$SFLAG" ] && printf "(gcc) "
		#__CLDBG="$__CLDBG -fprofile-arcs -ftest-coverage"
		__CLDBG="$__CLDBG -fsanitize=undefined"
		__CLDBG="$__CLDBG -fsanitize=float-divide-by-zero"
		__CLDBG="$__CLDBG -fsanitize=float-cast-overflow"
	elif echo $VER | grep -q clang; then
		[ -z "$SFLAG" ] && printf "(clang) "
		__CCXXDBG="$__CCXXDBG -Wincompatible-pointer-types-discards-qualifiers"
		__CCXXDBG="$__CCXXDBG -Wmissing-variable-declarations"
		__CXXDBG="$__CXXDBG -Wunused-private-field"
		__CLXXDBG="$__CLXXDBG -fsanitize=undefined"
		__CLXXDBG="$__CLXXDBG -fsanitize=unsigned-integer-overflow"
	else
		[ -z "$SFLAG" ] && echo "Unknown compiler"
		return
	fi

	# Most C++ options can be used for C too
	__CDBG="$__CDBG $__CCXXDBG"
	__CLDBG="$__CLDBG $__CLXXDBG"
	__CXXDBG="$__CXXDBG $__CCXXDBG"
	cat <<EOT >$TMPC
int
main() {
	return 0;
}
EOT
	gen_mk
	cat <<EOT >>$OUTMK
$TMPNAM: ${TMPNAM}.o
	\$(CC) \$(__CDBG) \$(__CLDBG) -o \$@ ${TMPNAM}.o
EOT
	compile
	test_result || {
		__CLDBG=
		__CLXXDBG=
	}
}
check_float_store () {
	FLOAT_STORE=-ffloat-store
	check_for "$FLOAT_STORE"
	cat <<EOT >$TMPC
int
main() {
	return 0;
}
EOT
	gen_mk
	cat <<EOT >>$OUTMK
${TMPNAM}.o:
	\$(CC) $FLOAT_STORE -c $TMPC
EOT
	rm -f ${TMPNAM}.o
	$MAKE -f $OUTMK ${TMPNAM}.o >> $LOG 2> $TMPNAM
	cat $TMPNAM >> $LOG
	if [ -s $TMPNAM ]; then
		if grep -e $FLOAT_STORE $TMPNAM | grep -q 'not supported'
		then
			false
		elif grep -e $FLOAT_STORE $TMPNAM | grep -q 'argument unused'
		then
			false
		else
			true
		fi
	else
		true
	fi
	test_result || FLOAT_STORE=
}
check_strlcpy () {
	check_for "strlcpy(3)"

	cat <<EOT >$TMPC
#include <string.h>
int
main(int argc, char **argv) {
	char a[10];
	(void)argc;
	strlcpy(a, *argv, sizeof a);
	return 0;
}
EOT
	compile
	if test_result; then
		DEFS="$DEFS -DHAVE_STRLCPY"
	else
		H=compat.h
		grep -q '<sys/types\.h>' $H 2>/dev/null || cat <<EOT >>$H
#include <sys/types.h>
EOT
		cat <<EOT >>$H
size_t strlcpy(char *, const char *, size_t);
EOT
	fi
}
check_strlcat () {
	check_for "strlcat(3)"

	cat <<EOT >$TMPC
#include <string.h>
int
main(int argc, char **argv) {
	char a[10];
	(void)argc;
	*a = 0;
	strlcat(a, *argv, sizeof a);
	return 0;
}
EOT
	compile
	if test_result; then
		DEFS="$DEFS -DHAVE_STRLCAT"
	else
		H=compat.h
		grep -q '<sys/types\.h>' $H 2>/dev/null || cat <<EOT >>$H
#include <sys/types.h>
EOT
		cat <<EOT >>$H
size_t strlcat(char *, const char *, size_t);
EOT
	fi
}
check_fixterm () {
	check_for 'fixterm(3)'

	cat <<EOT >$TMPC
#include "compat.h"
int
main() {
	fixterm();
	return 0;
}
EOT
	gen_mk
	cat <<EOT >>$OUTMK
$TMPNAM: ${TMPNAM}.o
	\$(CC) \$(_CFLAGS) \$(_LDFLAGS) \$(RPATH_CURSES) -o \$@ ${TMPNAM}.o \$(LDADD)
EOT
	compile
	test_result && DEFS="$DEFS -DHAVE_FIXTERM"
}

check_resetterm () {
	check_for 'resetterm(3)'

	cat <<EOT >$TMPC
#include "compat.h"
int
main() {
	resetterm();
	return 0;
}
EOT
	gen_mk
	cat <<EOT >>$OUTMK
$TMPNAM: ${TMPNAM}.o
	\$(CC) \$(_CFLAGS) \$(_LDFLAGS) \$(RPATH_CURSES) -o \$@ ${TMPNAM}.o \$(LDADD)
EOT
	compile
	test_result && DEFS="$DEFS -DHAVE_RESETTERM"
}

compile_curses () {
	cat <<EOT >$TMPC
#include "compat.h"
int
main() {
	return 0;
}
EOT
	gen_mk
	cat <<EOT >>$OUTMK
$TMPNAM: ${TMPNAM}.o
	\$(CC) \$(_CFLAGS) \$(_LDFLAGS) \$(RPATH_CURSES) -o \$@ ${TMPNAM}.o \$(LDADD)
EOT
	compile
	test_result 1
}

test_curses () {
	compile_curses && return
	[ $# -gt 0 ] && for i in $*; do
		compile_curses $i && return
	done
}

check_ncursesw () {
	LIB_CURSES="-lncursesw"
	check_for $LIB_CURSES
	ODEFS=$DEFS

	# At first test for specific ncursesw.h to not used -lncursesw with
	# traditional curses.h if both curses systems are installed.

	DEFS="$DEFS -DHAVE_NCURSESW_CURSES_H"
	INCDIR_CURSES="-I/usr/include/ncurses6"
	RPATH_CURSES="-Wl,-rpath,/usr/lib64/ncurses6"
	LIBDIR_CURSES="-L/usr/lib64/ncurses6"
	PASS_TEXT=" (#include <ncursesw/curses.h>, -I/usr/include/ncurses6, \
-L/usr/lib64/ncurses6)"
	test_curses && return # Linux NC6

	INCDIR_CURSES=
	RPATH_CURSES=
	LIBDIR_CURSES=
	PASS_TEXT=" (#include <ncursesw/curses.h>)"
	test_curses && return # Linux

	INCDIR_CURSES="-I/usr/pkg/include"
	RPATH_CURSES="-Wl,-rpath,/usr/pkg/lib"
	LIBDIR_CURSES="-L/usr/pkg/lib"
	PASS_TEXT=" (#include <ncursesw/curses.h>, -I/usr/pkg/include, \
-L/usr/pkg/lib)"
	test_curses && return # NetBSD

	DEFS=$ODEFS
	INCDIR_CURSES=
	RPATH_CURSES=
	LIBDIR_CURSES=
	PASS_TEXT=
	test_curses && return # OpenBSD, FreeBSD

	[ -z "$SFLAG" ] && echo no
	false
}

check_ncurses () {
	LIB_CURSES="-lncurses"
	check_for $LIB_CURSES
	ODEFS=$DEFS

	DEFS="$DEFS -DHAVE_NCURSES_CURSES_H"
	PASS_TEXT=" (#include <ncurses/curses.h>)"
	test_curses && return # OpenIndiana

	DEFS="$DEFS -DHAVE_NCURSES_CURSES_H"
	INCDIR_CURSES="-I/usr/pkg/include"
	RPATH_CURSES="-Wl,-rpath,/usr/pkg/lib"
	LIBDIR_CURSES="-L/usr/pkg/lib"
	PASS_TEXT=" (#include <ncurses/curses.h>, -I/usr/pkg/include, \
-L/usr/pkg/lib)"
	test_curses && return # NetBSD

	DEFS=$ODEFS
	INCDIR_CURSES=
	RPATH_CURSES=
	LIBDIR_CURSES=
	PASS_TEXT=
	test_curses && return

	[ -z "$SFLAG" ] && echo no
	false
}

check_curses () {
	LIB_CURSES="-lcurses"
	check_for $LIB_CURSES
	test_curses || {
		[ -z "$SFLAG" ] && echo no
		false
		return
	}

	check_for attr_t
	cat <<EOT >$TMPC
#include "compat.h"
int
main(int argc, char **argv) {
	(void)argv;
	attr_t c = argc + '0';
	return (int)c;
}
EOT
	compile
	test_result && DEFS="$DEFS -DHAVE_ATTR_T"

	check_for 'attr_get(3)'
	cat <<EOT >$TMPC
#include "compat.h"
int
main() {
	return attr_get(NULL, NULL, NULL);
}
EOT
	gen_mk
	cat <<EOT >>$OUTMK
$TMPNAM: ${TMPNAM}.o
	\$(CC) \$(_CFLAGS) -o \$@ ${TMPNAM}.o \$(LDADD)
EOT
	compile
	test_result && DEFS="$DEFS -DHAVE_ATTR_GET"

	check_fixterm
	check_resetterm
	true
}

curses_mk_compat_h () {
	cat <<EOT >>compat.h
#ifndef _XOPEN_SOURCE_EXTENDED
# define _XOPEN_SOURCE_EXTENDED
#endif

#if defined(HAVE_NCURSESW_CURSES_H)
# include <ncursesw/curses.h>
#elif defined(HAVE_NCURSES_CURSES_H)
# include <ncurses/curses.h>
#else
# include <curses.h>

# ifndef false
#  define false 0
# endif

# ifndef true
#  define true 1
# endif
#endif
EOT
}

check_lib_curses () {
	curses_mk_compat_h
	DEFS0=$DEFS
	DEFS="$DEFS  -DHAVE_ATTR_T -DHAVE_ATTR_GET -DHAVE_FIXTERM \
-DHAVE_RESETTERM"
	check_ncursesw && return
	check_ncurses && return
	DEFS=$DEFS0
	check_curses && return
}

check_netbsd_curses () {
	curses_mk_compat_h
	[ `uname` = "NetBSD" ] && check_curses && return
	DEFS0=$DEFS
	DEFS="$DEFS  -DHAVE_ATTR_T -DHAVE_ATTR_GET -DHAVE_FIXTERM \
-DHAVE_RESETTERM"
	check_ncursesw && return
	check_ncurses && return
	DEFS=$DEFS0
	check_curses && return
}
check_curses_wch () {
	check_for "wins_wch(3)"

	cat <<EOT >$TMPC
#include "compat.h"
int
main() {
	cchar_t c;
	wins_wch(stdscr, &c);
	return 0;
}
EOT
	gen_mk
	cat <<EOT >>$OUTMK
$TMPNAM: ${TMPNAM}.o
	\$(CC) \$(_CFLAGS) \$(_LDFLAGS) \$(RPATH_CURSES) -o \$@ ${TMPNAM}.o \$(LDADD)
EOT
	compile
	test_result || return;

	DEFS="$DEFS -DHAVE_CURSES_WCH"
}
check_curses_keyname () {
	check_for "keyname(3)"

	cat <<EOT >$TMPC
#include "compat.h"
int
main(int argc, char **argv) {
	(void)argv;
	puts(keyname(argc));
	return 0;
}
EOT
	gen_mk
	cat <<EOT >>$OUTMK
$TMPNAM: ${TMPNAM}.o
	\$(CC) \$(_CFLAGS) \$(_LDFLAGS) \$(RPATH_CURSES) -o \$@ ${TMPNAM}.o \$(LDADD)
EOT
	compile
	test_result || return;

	DEFS="$DEFS -DHAVE_CURSES_KEYNAME"
}
check_isfinite () {
	check_for "isfinite(3)"

	cat <<EOT >$TMPC
#include <math.h>
int
main(int argc, char **argv) {
	(void)argv;
	return isfinite((double)argc);
}
EOT
	compile
	test_result && {
		DEFS="$DEFS -DHAVE_ISFINITE"
		return
	}

	check_for "finite(3)"

	cat <<EOT >$TMPC
#include <math.h>
int
main(int argc, char **argv) {
	(void)argv;
	return finite((double)argc);
}
EOT
	compile
	test_result 2 && {
		[ -s $ERR ] ||
		    grep -iq implicite $ERR ||
		    grep -iq warning   $ERR || {
			[ -z "$SFLAG" ] && echo yes
			return
		}
	}

	cat <<EOT >$TMPC
#include <math.h>
#include <ieeefp.h>
int
main(int argc, char **argv) {
	(void)argv;
	return finite(argc);
}
EOT
	PASS_TEXT=" (#include <ieeefp.h>)"
	compile
	test_result && DEFS="$DEFS -DUSE_IEEEFP_H"
	PASS_TEXT=
}
check_sc_attr_get () {
	check_for "sc(1) attr_get(3)"
	cat <<EOT >$TMPC
#include <limits.h>
#include "compat.h"
#include "sc.h"
int
main() {
	attr_get(NULL, NULL, NULL);
	return 0;
}
EOT
	gen_mk
	cat <<EOT >>$OUTMK
$TMPNAM: ${TMPNAM}.o
	\$(CC) \$(_CFLAGS) -o \$@ ${TMPNAM}.o \$(LDADD)
EOT
	compile
	test_result || DEFS="$DEFS -DNO_ATTR_GET"
}
check_stdbool_h () {
	check_for "<stdbool.h>"

	cat <<EOT >$TMPC
#include <stdbool.h>
int
main() {
	return 0;
}
EOT
	compile
	test_result && DEFS="$DEFS -DHAVE_STDBOOL_H"
}
	[ ! -s compat.h ] && rm compat.h
OUTMK=Makefile
INMK=${OUTMK}.in
CFG=config
TMPNAM=.$CFG
TMPMK=${TMPNAM}.mk
TMPC=${TMPNAM}.c
OUT=${TMPNAM}.out
ERR=${TMPNAM}.err
LOG=${CFG}.log
rm -f $LOG
gen_mk

check_make
#check_Sanitizer
check_float_store
check_strlcpy
check_strlcat
check_lib_curses
check_curses_wch
check_curses_keyname
check_isfinite
check_sc_attr_get
check_stdbool_h

gen_mk
rm -f $TMPNAM*

echo "#define _COMPAT_H" >>compat.h
