#!/usr/bin/env sh

#
# Argument Parsing
#

echo "Build Configuration"
echo "-------------------"

BUILD="build"
OUT="out"
SRC="src"
INCLUDE="src/include"
TOOLS="tools"

# Default compiler flags. These must be supported by all POSIX C compilers.
# "Fancy" compilers that have additional options must be detected and set below.
CFLAGS="-O1 -D_DEFAULT_SOURCE -I${INCLUDE}"
LIBS="-lm -lpthread"

# Default args for all platforms.
SCRIPT_ARGS="--prefix=/usr/local --lib-name=Cytoplasm"

# Set SSL flags depending on the platform.
case "$(uname)" in
    OpenBSD)
        SCRIPT_ARGS="${SCRIPT_ARGS} --with-libressl"
        ;;
    *)
        SCRIPT_ARGS="${SCRIPT_ARGS} --with-openssl"
        ;;
esac

# Set compiler depending on the platform.
case "$(uname)" in
    Linux|NetBSD)
        # These systems typically use GCC.
        SCRIPT_ARGS="${SCRIPT_ARGS} --cc=gcc"
        ;;
    OpenBSD|FreeBSD)
        # These systems typically use Clang.
        SCRIPT_ARGS="${SCRIPT_ARGS} --cc=clang"
        ;;
    *)
        # Use default compiler which is required to be present on
        # all POSIX-compliant systems.
        SCRIPT_ARGS="${SCRIPT_ARGS} --cc=c99"
        ;;
esac

# Append any additional args specified by user
SCRIPT_ARGS="${SCRIPT_ARGS} $@"

echo "Processing options..."
echo "Ran with arguments: $SCRIPT_ARGS"

# Process all arguments
for arg in $SCRIPT_ARGS; do
    case "$arg" in
        --cc=*)
            CC=$(echo "$arg" | cut -d '=' -f 2-)
            case "${CC}" in
                gcc*|clang*)
                    # "Fancy" compilers that support a plethora of additional flags we
                    # want to enable if present.
                    CFLAGS="-Wall -Werror -pedantic -std=c99 ${CFLAGS}"
                    LDFLAGS="-flto -fdata-sections -ffunction-sections -s -Wl,-gc-sections"
                    ;;
            esac
            ;;
        --with-openssl)
            TLS_IMPL="TLS_OPENSSL"
            TLS_LIBS="-lcrypto -lssl"
            ;;
        --with-libressl)
            TLS_IMPL="TLS_LIBRESSL"
            TLS_LIBS="-ltls -lcrypto -lssl"
            ;;
        --disable-tls)
            TLS_IMPL=""
            TLS_LIBS=""
            ;;
        --prefix=*)
            PREFIX=$(echo "$arg" | cut -d '=' -f 2-)
            ;;
        --lib-name=*)
            LIB_NAME=$(echo "$arg" | cut -d '=' -f 2-)
            ;;
        --enable-debug)
            DEBUG="-O0 -g"
            ;;
        --disable-debug)
            DEBUG=""
            ;;
        *)
            echo "Invalid argument: $arg"
            exit 1
            ;;
    esac
done

if [ -n "$TLS_IMPL" ]; then
    CFLAGS="${CFLAGS} -DTLS_IMPL=${TLS_IMPL}"
    LIBS="${LIBS} ${TLS_LIBS}"
fi

CFLAGS="${CFLAGS} '-DLIB_NAME=\"${LIB_NAME}\"' ${DEBUG}"
LDFLAGS="${LIBS} ${LDFLAGS}"

#
# Makefile generation
#

collect() {
	from="$1"
	orig_ext="$2"
	new_ext="$3"
	prefix="$4"
	exec="$5"

	find "${from}" -name "*${orig_ext}" -type f | while IFS= read -r src; do
		src=$(echo "$src" | sed -e "s|^${from}||g")
		obj=$(echo "$src" | sed -e "s|${orig_ext}\$|${new_ext}|g")

		obj="${prefix}${obj}"
		src="${from}${src}"

		"${exec}" "${src}" "${obj}"
	done
}

print_src() {
	printf '%s ' "$1"
}

print_obj() {
	printf '%s ' "$2"
}

get_deps() {
    src="$1"

    ${CC} -I${INCLUDE} -E "$src" \
        | grep '^#'              \
        | awk '{print $3}'       \
        | cut -d '"' -f 2        \
        | sort                   \
        | uniq                   \
        | grep -v '^[/<]'        \
        | grep "^${SRC}/"        \
        | while IFS= read -r dep; do
        printf "%s " "$dep"
    done
}

compile_obj() {
	src="$1"
	obj="$2"

	echo "${obj}: $(get_deps ${src})"
	echo "${TAB}@mkdir -p $(dirname ${obj})"
	echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${src}\""
}

compile_bin() {
	src="$1"
	out="$2"

    echo "${out}: ${OUT}/lib/lib${LIB_NAME}.a ${OUT}/lib/lib${LIB_NAME}.so ${src}"
    echo "${TAB}@mkdir -p ${OUT}/bin"
    echo "${TAB}\$(CC) \$(CFLAGS) -o \"${out}\" \"${src}\" -L${OUT}/lib \$(LDFLAGS) -l${LIB_NAME}"
}

compile_doc() {
	src="$1"
	out="$2"
    pref="LD_LIBRARY_PATH=${OUT}/lib "

	echo "${out}: ${OUT}/bin/hdoc ${src}"
	echo "${TAB}@mkdir -p ${OUT}/man/man3"
	echo "${TAB}${pref}${OUT}/bin/hdoc -D \"Os=${LIB_NAME}\" -i \"${src}\" -o \"${out}\""
}

install_out() {
	src="$1"
	out="$2"
	dir=$(dirname "$out")

	echo "${TAB}mkdir -p \"$dir\""
	echo "${TAB}cp \"$src\" \"$out\""
}

install_man() {
	src="${OUT}/man/man3/${LIB_NAME}-$(basename $1 .h).3"
	out="$2"
    dir=$(dirname "$out")

	echo "${TAB}mkdir -p \"$dir\""
	echo "${TAB}cp \"$src\" \"$out\""
}

install_tool() {
    src=${OUT}/bin/$(basename "$1" .c)
    out="$2"
    dir=$(dirname "$out")

	echo "${TAB}mkdir -p \"$dir\""
	echo "${TAB}cp \"$src\" \"$out\""
}

uninstall_out() {
	src="$1"
	out="$2"

	echo "${TAB}rm \"$out\""
}

echo "Generating Makefile..."

OBJS=$(collect ${SRC}/ .c .o ${BUILD}/ print_obj)
TAB=$(printf '\t')

cat << EOF > Makefile
.POSIX:

# Generated by '$0' on $(date).
# This file should generally not be manually edited.

CC = ${CC}
PREFIX = ${PREFIX}
CFLAGS = ${CFLAGS}
LDFLAGS = ${LDFLAGS}

all: ${LIB_NAME} docs tools
docs: $(collect ${INCLUDE}/ .h .3 ${OUT}/man/man3/${LIB_NAME}- print_obj)
tools: $(collect ${TOOLS}/ .c '' ${OUT}/bin/ print_obj)

print-libs:
${TAB}@echo ${LIBS}

format:
${TAB}find . -name '*.c' | while IFS= read -r src; do \\
${TAB}  if indent "\$\$src"; then \\
${TAB}    rm \$\$(basename "\$\$src").BAK; \\
${TAB}  fi \\
${TAB}done

license:
${TAB}find . -name '*.[ch]' | while IFS= read -r src; do \\
${TAB}  srcHeader=\$\$(grep -n -m 1 '^ \*/' "\$\$src" | cut -d ':' -f 1); \\
${TAB}  head -n\$\$srcHeader \$\$src | \\
${TAB}    diff -u -p - "LICENSE.txt" | \\
${TAB}    patch "\$\$src" | grep -v "^Hmm"; \\
${TAB}done

${LIB_NAME}: ${OUT}/lib/lib${LIB_NAME}.a ${OUT}/lib/lib${LIB_NAME}.so

install: ${LIB_NAME}
${TAB}install -D ${OUT}/lib/lib${LIB_NAME}.a \$(PREFIX)/lib/lib${LIB_NAME}.a
${TAB}install -D ${OUT}/lib/lib${LIB_NAME}.so \$(PREFIX)/lib/lib${LIB_NAME}.so
$(collect ${INCLUDE}/ '' '' \$\(PREFIX\)/include/${LIB_NAME}/ install_out)
$(collect ${INCLUDE}/ .h .3 \$\(PREFIX\)/man/man3/${LIB_NAME}- install_man)
$(collect ${TOOLS}/ '.c' '' \$\(PREFIX\)/bin/ install_tool)

uninstall:
${TAB}rm -r \$(PREFIX)/lib/lib${LIB_NAME}.*
${TAB}rm -r \$(PREFIX)/include/${LIB_NAME}
${TAB}rm -r \$(PREFIX)/man/man3/${LIB_NAME}-*
$(collect ${TOOLS}/ '.c' '' \$\(PREFIX\)/bin/ uninstall_out)

clean:
${TAB}rm -r "${BUILD}" "${OUT}"

${OUT}/lib/lib${LIB_NAME}.a: ${OBJS}
${TAB}@mkdir -p ${OUT}/lib
${TAB}\$(AR) rcs ${OUT}/lib/lib${LIB_NAME}.a ${OBJS}

${OUT}/lib/lib${LIB_NAME}.so: ${OBJS}
${TAB}@mkdir -p ${OUT}/lib
${TAB}\$(CC) -shared -o ${OUT}/lib/lib${LIB_NAME}.so ${OBJS} ${LDFLAGS}

$(collect ${SRC}/ .c .o ${BUILD}/ compile_obj)
$(collect ${TOOLS}/ .c '' ${OUT}/bin/ compile_bin)
$(collect ${INCLUDE}/ .h .3 ${OUT}/man/man3/${LIB_NAME}- compile_doc)

EOF

echo "Done. Run 'make' to build ${LIB_NAME}."