#!/usr/bin/env sh
#
# td: "Telodendria Developer"
#
# A helper script that operates sort of like Make, except it is
# a lot easier to read and write than a Makefile, and it works on
# any POSIX system.
#
# I chose to use a custom build script instead of using Make because
# it is much more portable and flexible. This script doesn't only
# handle building the code, it also handles formatting it, as well
# as generating patch files.

addprefix() {
    prefix="$1"
    shift
    for thing in "$@"; do
        echo "${prefix}${thing}"
    done

    unset prefix
    unset thing
}

if [ -z "$TELODENDRIA_ENV" ]; then
    . tools/env.sh
fi

#
# Set variables
#
# This syntax may look odd, but as far as I can tell, it is POSIX,
# and it allows the values to be overridden by the environment,
# such that these serve more as sane defaults than hard requirements.
#

: "${TELODENDRIA_VERSION:=0.4.0}"
: "${CVS_TAG:=Telodendria-$(echo $TELODENDRIA_VERSION | sed 's/\./_/g')}"

: "${DEFINES:=-D_DEFAULT_SOURCE -DTELODENDRIA_VERSION=\"${TELODENDRIA_VERSION}-$(uname)\"}"
: "${INCLUDES:=src/include Cytoplasm/src/include}"

: "${CC:=cc}"
: "${AR:=ar}"
: "${CFLAGS:=-Wall -Wextra -pedantic -std=c89 -O3 -pipe}"
: "${STATIC:=-static -Wl,-static}"
: "${LD_EXTRA:=-flto -fdata-sections -ffunction-sections -s -Wl,-gc-sections}"
: "${PROG:=telodendria}"

. "$(pwd)/tools/lib/common.sh"

CFLAGS="${CFLAGS} ${DEFINES} $(addprefix -I$(pwd)/ ${INCLUDES})"

MAIN="Main"

if [ "$DEBUG" = "1" ]; then
    CFLAGS="$CFLAGS -O0 -g"
    PROG="$PROG-debug"
else
    LDFLAGS="${LDFLAGS} ${LD_EXTRA}"
fi

# Check the modificiation time of a file. This is used to do
# incremental builds; we only want to rebuild files that have
# have changed.
mod_time() {
    if [ -n "$1" ] && [ -f "$1" ]; then
        case "$(uname)" in
            Linux | CYGWIN_NT* | Haiku)
                stat -c %Y "$1"
                ;;
            *BSD | DragonFly | Minix)
                stat -f %m "$1"
                ;;
            *)
                # Platform unknown, force rebuilding the whole
                # project every time.
                echo "0"
                ;;
        esac
    else
        echo "0"
    fi
}

# Substitute shell variables in a stream with their actual value
# in this shell.
setsubst() {
    SED="/tmp/sed-$RANDOM.txt"

    (
        set | while IFS='=' read -r var val; do
            val=$(echo "$val" | cut -d "'" -f 2- | rev | cut -d "'" -f 2- | rev)
            echo "s|\\\${$var}|$val|g"
        done

        echo "s|\\\${[a-zA-Z_]*}||g"
        echo "s|'''|'|g"
    ) >"$SED"

    sed -f "$SED" $@
    rm "$SED"
}

recipe_docs() {
    mkdir -p build/man/man3

    for header in $(find src -name '*.h'); do
        basename=$(basename "$header")
        man=$(echo "build/man/man3/$basename" | sed -e 's/\.h$/\.3/')

        if [ $(mod_time "$header") -ge $(mod_time "$man") ]; then
            echo "DOC $basename"
            if ! hdoc -D Os=Telodendria -i "$header" -o "$man"; then
                rm "$man"
                exit 1
            fi
        fi
    done

    if which makewhatis 2>&1 > /dev/null; then
        makewhatis "$(pwd)/man"
    fi
}

recipe_cytoplasm() {
    cd Cytoplasm
    export TLS_IMPL
    export CFLAGS
    if ! sh make.sh; then
        exit 1
    fi
    CYTOPLASM_FLAGS="Cytoplasm/out/lib/cytoplasm.o -LCytoplasm/out/lib -lcytoplasm $(sh make.sh libs)"
    cd - > /dev/null
}

# Build the source code, and generate  the 'build/telodendria'
# binary.
recipe_build() {
    recipe_cytoplasm

    cd src
    mkdir -p ../build

    echo "CC = ${CC}"
    echo "CFLAGS = ${CFLAGS}"
    echo "LDFLAGS = ${CYTOPLASM_FLAGS} ${LDFLAGS} ${STATIC}"
    echo

    do_rebuild=0
    objs=""
    for src in $(find . -name '*.c' | cut -d '/' -f 2-); do
        obj=$(echo "build/$src" | sed -e 's/\.c$/\.o/')

        if [ $(basename "$obj" .o) != "$MAIN" ]; then
            objs="$objs $obj"
        fi

        if [ $(mod_time "$src") -ge $(mod_time "../$obj") ]; then
            echo "CC $(basename $obj)"
            obj_dir=$(dirname "../$obj")
            mkdir -p "$obj_dir"
            if ! $CC $CFLAGS -fPIC -c -o "../$obj" "$src"; then
                exit 1
            fi
            do_rebuild=1
        fi
    done

    cd ..

    if [ $do_rebuild -eq 1 ] || [ ! -f "build/$PROG" ]; then
        echo "LD $PROG"
        if ! $CC -o "build/$PROG" $objs "build/$MAIN.o" ${CYTOPLASM_FLAGS} ${LDFLAGS} ${STATIC}; then
            exit 1
        fi
    fi

    for src in $(find tools/src -name '*.c'); do
        out=$(basename "$src" .c)
        out="build/tools/$out"

        if [ $(mod_time "$src") -ge $(mod_time "$out") ] || [ $do_rebuild -eq 1 ]; then
            echo "CC $(basename $out)"
            mkdir -p "$(dirname $out)"
            if ! $CC $CFLAGS -o "$out" "$src" $objs ${CYTOPLASM_FLAGS} ${LDFLAGS} ${STATIC}; then
                exit 1
            fi
        fi
    done

    recipe_docs
}

recipe_run() {
    if [ -f "build/$PROG" ]; then
        "build/$PROG" -d data

        if [ -f "data/telodendria-leaked.txt" ]; then
            echo "WARNING: Telodendria is leaking memory."
            echo "Please fix memory leaks."
        fi
    else
        echo "build/$PROG does not exist; build it first."
    fi
}

# Remove all build files, which can be regenerated by re-running the
# build recipe.
recipe_clean() {
    cd Cytoplasm
    sh make.sh clean
    cd ..
    rm -r build
}

# Update copyright comments in sources and header files.
recipe_license() {
    find . -name '*.[ch]' | while IFS= read -r src; do
        if [ -t 1 ]; then
            printf "LICENSE %s%*c\r" $(basename "$src") "16" " "
        fi
        srcHeader=$(grep -n -m 1 '^ \*/' "$src" | cut -d ':' -f 1)
        head "-n$srcHeader" "$src" |
            diff -u -p - LICENSE.txt |
            patch "$src" | grep -v "^Hmm"
    done
    if [ -t 1 ]; then
        printf "%*c\n" "50" " "
    fi
}

# Format source code files by running indent(1) on them.
recipe_format() {
    find . -name '*.c' | while IFS= read -r src; do
        if [ -t 1 ]; then
            printf "FMT %s%*c\r" $(basename "$src") "16" " "
        fi
        if indent "$src"; then
            rm $(basename "$src").BAK
        fi
    done
    if [ -t 1 ]; then
        printf "%*c\n" "50" " "
    fi
}

# Deploy the Telodendria website by copying the required files to
# a web root defined by TELODENDRIA_PUB.
recipe_site() {
    if [ -z "$TELODENDRIA_PUB" ]; then
        echo "No public root directory specified."
        echo "Set TELODENDRIA_PUB."
        exit 1
    fi

    recipe_docs

    # Set some variables that may be replaced in the files.
    DATE=$(date)
    USER_DOCS=$(man-table user)
    DEV_DOCS=$(man-table dev)

    cd site/
    find . -type f | grep -v CVS | while IFS= read -r file; do
        dest="$TELODENDRIA_PUB/$file"
        dir=$(dirname "$dest")

        echo "$dest"

        mkdir -p "$dir"
        setsubst "$file" >"$dest"
    done
    cd - >/dev/null

    find man/ build/man/ -name '*.[1-9]' | while IFS= read -r man; do
        section=$(basename "$man" | rev | cut -d '.' -f 1 | rev)
        html=$(basename "$man")

        mkdir -p "$TELODENDRIA_PUB/man/man$section/"
        mandoc -Thtml \
            -O style=/style.css,man=/man/man%S/%N.%S.html "$man" \
            >"$TELODENDRIA_PUB/man/man$section/$html.html"
        echo "$TELODENDRIA_PUB/man/man$section/$html.html"
    done
}

# Generate a release tarball, checksum and sign it, and push it to
# the web root.
recipe_release() {
    if [ -z "$TELODENDRIA_PUB" ]; then
        echo "No public root directory specified."
        echo "Set TELODENDRIA_PUB."
        exit 1
    fi

    # Tag the release at this point in time.
    cvs tag "$CVS_TAG"

    mkdir -p "$TELODENDRIA_PUB/pub/v$TELODENDRIA_VERSION"
    cd "$TELODENDRIA_PUB/pub/v$TELODENDRIA_VERSION"

    # Generate the release tarball.
    cvs export "-r$CVS_TAG" "Telodendria"
    mv "Telodendria" "Telodendria-v$TELODENDRIA_VERSION"
    tar -czf "Telodendria-v$TELODENDRIA_VERSION.tar.gz" \
        "Telodendria-v$TELODENDRIA_VERSION"
    rm -r "Telodendria-v$TELODENDRIA_VERSION"

    # Checksum the release tarball.
    sha256 "Telodendria-v$TELODENDRIA_VERSION.tar.gz" \
        >"Telodendria-v$TELODENDRIA_VERSION.tar.gz.sha256"

    # Sign the release tarball.
    if [ ! -z "$TELODENDRIA_SIGNIFY_SECRET" ]; then
        signify -S -s "$TELODENDRIA_SIGNIFY_SECRET" \
            -m "Telodendria-v$TELODENDRIA_VERSION.tar.gz" \
            -x "Telodendria-v$TELODENDRIA_VERSION.tar.gz.sig"
    else
        echo "Warning: TELODENDRIA_SIGNIFY_SECRET not net."
        echo "The built tarball will not be signed."
    fi
}

# Generate a formatted patch file. The Telodendria project isn't
# really picky about how patches look, but this is how we like them
# best. Makes them easy to read.
recipe_patch() {
    # If the user has not set their MXID, try to deduce one from
    # their system.
    if [ -z "$MXID" ]; then
        MXID="@${USER}:$(hostname)"
    fi

    # If the user has not set their EDITOR, use a safe default.
    # (vi should be available on any POSIX system)
    if [ -z "$EDITOR" ]; then
        EDITOR=vi
    fi

    NORMALIZED_MXID=$(echo "$MXID" | sed -e 's/@//g' -e 's/:/-/g')
    PATCH_FILE="${NORMALIZED_MXID}_$(date +%s).patch"

    # Generate the actual patch file
    # Here we write some nice headers, and then do a cvs diff.
    (
        printf 'From: "%s" <%s>\n' "$DISPLAY_NAME" "$MXID"
        echo "Date: $(date)"
        echo "Subject: "
        echo
        echo "[ ] I have read the Telodendria Project developer certificate"
        echo "    of origin, and certify that I have permission to submit"
        echo "    this patch under the conditions specified in it."
        echo
        cvs -q diff -uNp $PATCHSET | grep -v '^\? '
    ) >"$PATCH_FILE"

    "$EDITOR" "$PATCH_FILE"
    echo "$PATCH_FILE"
}

recipe_diff() {
    tmp_patch="/tmp/telodendria-$(date +%s).patch"
    cvs -q diff -uNp $PATCHSET >"$tmp_patch"
    if [ -z "$PAGER" ]; then
        PAGER="less -F"
    fi

    $PAGER "$tmp_patch"
    rm "$tmp_patch"
}

# Execute the user-specified recipes.
for recipe in $@; do
    recipe_$recipe
done

# If no recipe was provided, run a build.
if [ -z "$1" ]; then
    recipe_build
fi