#!/bin/bash

set -euox pipefail

function HELP {
    echo "Build lbrycrd"
    echo "-----"
    echo "When run without any arguments, this script expects the current directory"
    echo "to be the lbrycrd repo and it builds what is in that directory"
    echo
    echo "This is a long build process so it can be split into two parts"
    echo "Specify the -d flag to build only the dependencies"
    echo "and the -l flag to build only lbrycrd. This will fail"
    echo "if the dependencies weren't built earlier"
    echo
    echo "Optional arguments:"
    echo
    echo "-f: check formatting of committed code relative to master"
    echo "-r: remove intermediate files."
    echo "-l: build only lbrycrd"
    echo "-d: build only the dependencies"
    echo "-o: timeout build after 40 minutes"
    echo "-t: turn trace on"
    echo "-h: show help"
    exit 1
}

CLEAN=false
CHECK_CODE_FORMAT=false
BUILD_DEPENDENCIES=true
BUILD_LBRYCRD=true
TIMEOUT=false
THREE_MB=3145728
# this flag gets set to False if
# the script exits due to a timeout
OUTPUT_LOG=true

while getopts :crfldoth:w:d: FLAG; do
    case $FLAG in
	r)
	    CLEAN=true
	    ;;
	f)
	    CHECK_CODE_FORMAT=true
	    ;;
	l)
	    BUILD_DEPENDENCIES=false
	    ;;
	d)
	    BUILD_LBRYCRD=false
	    ;;
	o)
	    TIMEOUT=true
	    ;;
	t)
	    set -o xtrace
	    ;;
	h)
	    HELP
	    ;;
	\?) #unrecognized option - show help
	    echo "Option -$OPTARG not allowed."
	    HELP
	    ;;
	:)
	    echo "Option -$OPTARG requires an argument."
	    HELP
	    ;;
    esac
done

shift $((OPTIND-1))

SUDO=''
if (( EUID != 0 )); then
    SUDO='sudo'
fi

if [ "$(basename "$PWD")" != "lbrycrd" ]; then
    echo "Not currently in the lbrycrd directory. Cowardly refusing to go forward"
    exit 1
fi
SOURCE_DIR=$PWD

if [ -z "${TRAVIS_OS_NAME+x}" ]; then
    if [ "$(uname -s)" = "Darwin" ]; then
	OS_NAME="osx"
    else
	OS_NAME="linux"
    fi
else
    OS_NAME="${TRAVIS_OS_NAME}"
fi

if [ -z "${TRAVIS_BUILD_DIR+x}" ]; then
    START_TIME_FILE="$PWD/start_time"
else
    # if we are on travis (the primary use case for setting a timeout)
    # this file is created when the build starts
    START_TIME_FILE="$TRAVIS_BUILD_DIR/start_time"
fi
rm -f ${START_TIME_FILE}
date +%s > ${START_TIME_FILE}

NEXT_TIME=60
function exit_at_60() {
    if [ -f "${START_TIME_FILE}" ]; then
	NOW=$(date +%s)
	START=$(cat "${START_TIME_FILE}")
	TIMEOUT_SECS=3600 # 60 * 60
	TIME=$((NOW - START))
	if (( TIME > NEXT_TIME )); then
	    echo "Build has taken $((TIME / 60)) minutes: $1"
	    NEXT_TIME=$((TIME + 60))
	fi
	if [ "$TIMEOUT" = true ] && (( TIME > TIMEOUT_SECS )); then
	    echo 'Exiting at 60 minutes to allow the cache to populate'
	    OUTPUT_LOG=false
	    exit 1
	fi
    fi
}

# two arguments
#  - pid (probably from $!)
#  - echo message
function wait_and_echo() {
    PID=$1
    TIME=0
    SLEEP=3
    # loop until the process is no longer running
    # check every $SLEEP seconds, echoing a message every minute
    while (ps -p "${PID}" > /dev/null); do
	exit_at_60 "$2"
	sleep "${SLEEP}"
    done
}

# run a command ($1) in the background
# logging its stdout and stderr to $2
# and wait until it completed
function background() {
    eval $1 >> "$2" 2>&1 &
    BACKGROUND_PID=$!
    (
        set +xe # do not echo each sleep call in trace mode
        wait_and_echo $BACKGROUND_PID "$3"
    )
    wait $BACKGROUND_PID
}

function cleanup() {
    rv=$?
    if [ $rv -eq 0 ]; then
        return $rv
    fi
    # cat the log file if it exists
    if [ -f "$2" ] && [ "${OUTPUT_LOG}" = true ]; then
	echo
	echo "Output of log file $2"
	echo
        cat "$2"
#	tail -n 200 "$2"
	echo
    fi
    # delete the build directory
    rm -rf "$1"
    echo "Build failed. Removing $1"
    exit $rv
}

function cat_and_exit() {
    rv=$?
    # cat the log file if it exists
    if [ -f "$1" ] && [ "${OUTPUT_LOG}" = true ]; then
	echo
	echo "Output of log file $1"
	echo
	# This used to be the last 3MB but outputing that
	# caused problems on travis.
	# Hopefully the last 1000 lines is enough
	# to debug whatever went wrong
	tail -n 1000 "$1"
	echo
    fi
    exit $rv
}

function brew_if_not_installed() {
    if ! brew ls | grep $1 --quiet; then
	brew install $1
    fi
}

function install_brew_packages() {
    brew update > /dev/null
    brew unlink python
    brew_if_not_installed autoconf
    brew_if_not_installed automake
    # something weird happened where glibtoolize was failing to find
    # sed, and reinstalling fixes it.
    brew reinstall libtool
    brew_if_not_installed pkg-config
    brew_if_not_installed protobuf
    brew_if_not_installed gmp

    if [ "${CHECK_CODE_FORMAT}" = true ]; then
        brew_if_not_installed clang-format
    fi
}

function install_apt_packages() {
    if [ -d "${OUTPUT_DIR}" ]; then
        return 0
    fi

    if [ -z "${TRAVIS+x}" ]; then
	# if not on travis, its nice to see progress
	QUIET=""
    else
	QUIET="-qq"
    fi
    # get the required OS packages
    $SUDO apt-get ${QUIET} update
    $SUDO apt-get ${QUIET} install -y --no-install-recommends \
	  build-essential python-dev libbz2-dev libtool \
	  autotools-dev autoconf git pkg-config wget \
	  ca-certificates automake bsdmainutils

    if [ "${CHECK_CODE_FORMAT}" = true ]; then
        $SUDO apt-get ${QUIET} install -y --no-install-recommends \
            clang-format-3.9
    fi

}

function build_dependencies() {
    if [ "${OS_NAME}" = "osx" ]; then
        PARALLEL="-j $(sysctl -n hw.ncpu)"
	install_brew_packages
    else
        PARALLEL="-j $(grep -c processor /proc/cpuinfo)"
	install_apt_packages
    fi

    if [ "$CLEAN" = true ]; then
	rm -rf "${LBRYCRD_DEPENDENCIES}"
	rm -rf "${OUTPUT_DIR}"
    fi

    mkdir -p "${LBRYCRD_DEPENDENCIES}"

    # Download required dependencies (if not already present)
    pushd ${LBRYCRD_DEPENDENCIES} > /dev/null
    if [ ! -f db-4.8.30.NC.zip ]; then
        wget http://download.oracle.com/berkeley-db/db-4.8.30.NC.zip
        unzip -o -q db-4.8.30.NC.zip
    fi
    if [ ! -f libevent-2.1.8-stable.tar.gz ]; then
        wget https://github.com/libevent/libevent/releases/download/release-2.1.8-stable/libevent-2.1.8-stable.tar.gz
        tar -xzf libevent-2.1.8-stable.tar.gz
    fi
    if [ ! -f openssl-1.0.2r.tar.gz ]; then
        wget https://www.openssl.org/source/openssl-1.0.2r.tar.gz
        tar -xzf openssl-1.0.2r.tar.gz
    fi
    if [ ! -f icu4c-63_1-src.tgz ]; then
        wget http://download.icu-project.org/files/icu4c/63.1/icu4c-63_1-src.tgz
        tar -xzf icu4c-63_1-src.tgz
    fi
    if [ ! -f boost_1_64_0.tar.bz2 ]; then
        wget https://dl.bintray.com/boostorg/release/1.64.0/source/boost_1_64_0.tar.bz2
        tar -xjf boost_1_64_0.tar.bz2
    fi

    mkdir -p "${LOG_DIR}"

    build_dependency "${OPENSSL_PREFIX}" "${LOG_DIR}/openssl_build.log" build_openssl
    build_dependency "${ICU_PREFIX}" "${LOG_DIR}/icu_build.log" build_icu
    build_dependency "${BDB_PREFIX}" "${LOG_DIR}/bdb_build.log" build_bdb

    set +u
    export PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:${OPENSSL_PREFIX}/lib/pkgconfig"
    set -u

    build_dependency "${BOOST_PREFIX}" "${LOG_DIR}/boost_build.log" build_boost
    build_dependency "${LIBEVENT_PREFIX}" "${LOG_DIR}/libevent_build.log" build_libevent
}

function build_bdb() {
    BDB_LOG="$1"
    if [ "${OS_NAME}" = "osx" ]; then
        # TODO: make this handle already patched files
	patch db-4.8.30.NC/dbinc/atomic.h < ../contrib/patches/atomic.patch
    fi
    cd db-4.8.30.NC/build_unix
    echo "Building bdb.  tail -f $BDB_LOG to see the details and monitor progress"
    ../dist/configure --prefix="${BDB_PREFIX}" --enable-cxx --disable-shared --with-pic > "${BDB_LOG}"
    background "make ${PARALLEL}" "${BDB_LOG}" "Waiting for bdb to finish building"
    make install >> "${BDB_LOG}" 2>&1
}

function build_openssl() {
    OPENSSL_LOG="$1"
    mkdir -p "${OPENSSL_PREFIX}/ssl"
    cd openssl-1.0.2r
    echo "Building openssl.  tail -f $OPENSSL_LOG to see the details and monitor progress"
    if [ "${OS_NAME}" = "osx" ]; then
	./Configure --prefix="${OPENSSL_PREFIX}" --openssldir="${OPENSSL_PREFIX}/ssl" \
		    -fPIC darwin64-x86_64-cc \
		    no-shared no-dso no-engines > "${OPENSSL_LOG}"
        make depend
    else
    [[ $(uname -m) = 'i686' ]] && OS_ARCH="linux-generic32" || OS_ARCH="linux-x86_64"
	./Configure --prefix="${OPENSSL_PREFIX}" --openssldir="${OPENSSL_PREFIX}/ssl" \
		    ${OS_ARCH} -fPIC -static no-shared no-dso > "${OPENSSL_LOG}"
    fi
    background "make ${PARALLEL}" "${OPENSSL_LOG}" "Waiting for openssl to finish building"
    make install >> "${OPENSSL_LOG}" 2>&1
}

function build_boost() {
    BOOST_LOG="$1"
    cd boost_1_64_0

    echo "int main() { return 0; }" > libs/regex/build/has_icu_test.cpp
    echo "int main() { return 0; }" > libs/locale/build/has_icu_test.cpp

    export BOOST_ICU_LIBS="-L${ICU_PREFIX}/lib -licui18n -licuuc -licudata -ldl"
    export BOOST_LIBRARIES="chrono,filesystem,program_options,system,locale,regex,thread,test"

    echo "Building Boost.  tail -f ${BOOST_LOG} to see the details and monitor progress"
    ./bootstrap.sh --prefix="${BOOST_PREFIX}" --with-icu="${ICU_PREFIX}" --with-libraries=${BOOST_LIBRARIES} > "${BOOST_LOG}" 2>&1
    b2cmd="./b2 --reconfigure ${PARALLEL} link=static cxxflags=\"-std=c++11 -fPIC\" install boost.locale.iconv=off boost.locale.posix=off -sICU_PATH=\"${ICU_PREFIX}\" -sICU_LINK=\"${BOOST_ICU_LIBS}\""
    background "${b2cmd}" "${BOOST_LOG}" "Waiting for boost to finish building"
}

function build_icu() {
    ICU_LOG="$1"
    mkdir -p "${ICU_PREFIX}/icu"
    pushd icu/source > /dev/null
    echo "Building icu.  tail -f $ICU_LOG to see the details and monitor progress"
    ./configure --prefix="${ICU_PREFIX}" --enable-draft --enable-tools \
                    --disable-shared --enable-static --disable-extras --disable-icuio --disable-dyload \
                    --disable-layout --disable-layoutex --disable-tests --disable-samples CFLAGS=-fPIC CPPFLAGS=-fPIC > "${ICU_LOG}"
    if [ ! -z ${TARGET+x} ]; then
        TMP_TARGET="${TARGET}"
        unset TARGET
    fi
    set +e
    background "make ${PARALLEL} VERBOSE=1" "${ICU_LOG}" "Waiting for icu to finish building"
    make install >> "${ICU_LOG}" 2>&1
    if [ ! -z ${TARGET+x} ]; then
        TARGET="${TMP_TARGET}"
    fi
    set -e
    popd > /dev/null
}

function build_libevent() {
    LIBEVENT_LOG="$1"
    cd libevent-2.1.8-stable
    echo "Building libevent.  tail -f ${LIBEVENT_LOG} to see the details and monitor progress"
    ./autogen.sh > "${LIBEVENT_LOG}" 2>&1
    ./configure --prefix="${LIBEVENT_PREFIX}" --enable-static --disable-shared --with-pic \
		LDFLAGS="-L${OPENSSL_PREFIX}/lib" \
		CPPFLAGS="-I${OPENSSL_PREFIX}/include" >> "${LIBEVENT_LOG}" 2>&1
    background "make ${PARALLEL}" "${LIBEVENT_LOG}" "Waiting for libevent to finish building"
    make install >> "${LIBEVENT_LOG}"
}

function build_dependency() {
    pushd .
    PREFIX=$1
    LOG=$2
    BUILD=$3
    cd "${LBRYCRD_DEPENDENCIES}"
    mkdir -p "${PREFIX}"
    trap 'cleanup "${PREFIX}" "${LOG}"' INT TERM EXIT
	"${BUILD}" "${LOG}"
	trap - INT TERM EXIT
    popd
}

function build_lbrycrd() {
    cd "${SOURCE_DIR}"
    ./autogen.sh > "${LBRYCRD_LOG}" 2>&1
    LDFLAGS="-L${OPENSSL_PREFIX}/lib -L${BDB_PREFIX}/lib -L${LIBEVENT_PREFIX}/lib"
    OPTIONS="--enable-cxx --enable-static --disable-shared --with-pic"
    if [ "${OS_NAME}" = "osx" ]; then
        CPPFLAGS="-I${OPENSSL_PREFIX}/include -I${BDB_PREFIX}/include -I${LIBEVENT_PREFIX}/include -I${ICU_PREFIX}/include"
    else
        CPPFLAGS="-I${OPENSSL_PREFIX}/include -I${BDB_PREFIX}/include -I${LIBEVENT_PREFIX}/include -I${ICU_PREFIX}/include -Wno-unused-local-typedefs -Wno-deprecated -Wno-implicit-fallthrough"
    fi

    CPPFLAGS="${CPPFLAGS}" LDFLAGS="${LDFLAGS}"  \
            ./configure --without-gui ${OPTIONS} \
	    --with-boost="${BOOST_PREFIX}"   \
            --with-icu="${ICU_PREFIX}" >> "${LBRYCRD_LOG}" 2>&1

    background "make ${PARALLEL}" "${LBRYCRD_LOG}" "Waiting for lbrycrd to finish building"
}

function clang_format_diff(){
    # run a code formatting check on any commits not in master
    # requires clang-format
    git diff -U0 origin/master -- '*.h' '*.cpp' | ./contrib/devtools/clang-format-diff.py -p1
}

# these variables are needed in both functions
LBRYCRD_DEPENDENCIES="$(pwd)/lbrycrd-dependencies"
OUTPUT_DIR="$(pwd)/build"
LOG_DIR="$(pwd)/logs"
ICU_PREFIX="${OUTPUT_DIR}/icu"
BDB_PREFIX="${OUTPUT_DIR}/bdb"
OPENSSL_PREFIX="${OUTPUT_DIR}/openssl"
BOOST_PREFIX="${OUTPUT_DIR}/boost"
LIBEVENT_PREFIX="${OUTPUT_DIR}/libevent"

if [ "${BUILD_DEPENDENCIES}" = true ]; then
    build_dependencies
fi

if [ "${CHECK_CODE_FORMAT}" = true ]; then
    LINES_W_FORMAT_REQUIRED=$(clang_format_diff | wc -l)
    if [ ${LINES_W_FORMAT_REQUIRED} -ne 0  ]; then
        echo "Failed to pass clang format diff: See below for the diff"
        clang_format_diff
        exit 1
    fi
fi

set +u
export PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:${OPENSSL_PREFIX}/lib/pkgconfig:${LIBEVENT_PREFIX}/lib/pkgconfig:${ICU_PREFIX}/lib/pkgconfig"
set -u

if [ "${BUILD_LBRYCRD}" = true ]; then
    LBRYCRD_LOG="${LOG_DIR}/lbrycrd_build.log"
    echo "Building lbrycrd.  tail -f ${LBRYCRD_LOG} to see the details and monitor progress"
    trap 'cat_and_exit "${LBRYCRD_LOG}"' INT TERM EXIT
    build_lbrycrd
    trap - INT TERM EXIT

    ./src/test/test_lbrycrd
    set +u
    if [[ ! $CXXFLAGS =~ -g ]]; then
        strip src/lbrycrdd
        strip src/lbrycrd-cli
        strip src/lbrycrd-tx
    fi
fi