#!/bin/bash

set -xeuo 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 "-c: don't clone a fresh copy of the repo"
    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
}

CLONE=true
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
	c)
	    CLONE=false
	    ;;
	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 [ "${CLONE}" = false ]; then
    if [ "$(basename "$PWD")" != "lbrycrd" ]; then
	echo "Not currently in the lbrycrd directory. Cowardly refusing to go forward"
	exit 1
    fi
    SOURCE_DIR=$PWD
fi

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
    (set -o | grep xtrace | grep -q on)
    TRACE_STATUS=$?
    # disable xtrace or else this will get verbose, which is what
    # I'm trying to avoid by going through all of this nonsense anyway
    set +o xtrace
    TIME=0
    SLEEP=5
    # 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
    # restore the xtrace setting
    if [ "${TRACE_STATUS}" -eq 0 ]; then
	set -o xtrace
    fi
    wait "$PID"
    return $?
}

# run a command ($1) in the background
# logging its stdout and stderr to $2
# and wait until it completed
function background() {
    $1 >> "$2" 2>&1 &
    BACKGROUND_PID=$!
    wait_and_echo $BACKGROUND_PID "$3"
}

function cleanup() {
    rv=$?
    # cat the log file if it exists
    if [ -f "$2" ] && [ "${OUTPUT_LOG}" = true ]; then
	echo
	echo "Output of log file $2"
	echo
	tail -n 1000 "$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 -s 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 [ -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.4
    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

    if [ ! -d "${LBRYCRD_DEPENDENCIES}" ]; then
       git clone https://github.com/lbrynaut/lbrycrd-dependencies.git "${LBRYCRD_DEPENDENCIES}"
    fi
    # TODO: if the repo exists, make sure its clean: revert to head.
    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
	patch db-4.8.30.NC/dbinc/atomic.h < 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.1p
    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}"
    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_67_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="$(pkg-config icu-i18n --libs) -dl"
    export BOOST_LDFLAGS="${BOOST_PREFIX}/lib ${ICU_PREFIX}/lib ${BOOST_ICU_LIBS}"

    echo "BOOST_ICU_LIBS: $BOOST_ICU_LIBS"
    echo "BOOST_LDFLAGS: $BOOST_ICU_LIBS"

    echo "Building Boost.  tail -f ${BOOST_LOG} to see the details and monitor progress"
    ./bootstrap.sh --prefix="${BOOST_PREFIX}" "--with-icu=${ICU_PREFIX}" > "${BOOST_LOG}" 2>&1
    b2cmd="./b2 link=static cxxflags=-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"
    wget http://download.icu-project.org/files/icu4c/55.1/icu4c-55_1-src.tgz
    tar -xf icu4c-55_1-src.tgz
    rm -f icu4c-55_1-src.tgz
    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 \
                    --enable-static --enable-shared --disable-extras --disable-icuio \
                    --disable-layout --disable-layoutex --disable-tests --disable-samples
    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"
    if [ ! -d libevent ]; then
	git clone --branch release-2.0.22-stable https://github.com/libevent/libevent.git
    fi
    cd libevent
    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() {
    PREFIX=$1
    LOG=$2
    BUILD=$3
    if [ ! -d "${PREFIX}" ]; then
	trap 'cleanup "${PREFIX}" "${LOG}"' INT TERM EXIT
	cd "${LBRYCRD_DEPENDENCIES}"
        rm -rf ${PREFIX}
	mkdir -p "${PREFIX}"
	"${BUILD}" "${LOG}"
	trap - INT TERM EXIT
    fi
}

function build_lbrycrd() {
    if [ "$CLONE" == true ]; then
	cd "${LBRYCRD_DEPENDENCIES}"
	git clone https://github.com/lbryio/lbrycrd
	cd lbrycrd
    else
	cd "${SOURCE_DIR}"
    fi
    ./autogen.sh > "${LBRYCRD_LOG}" 2>&1
    LDFLAGS="-L${OPENSSL_PREFIX}/lib/ -L${BDB_PREFIX}/lib/ -L${LIBEVENT_PREFIX}/lib/ -L${ICU_PREFIX}/lib/ -static-libstdc++"
    if [ "${OS_NAME}" = "osx" ]; then
	OPTIONS="--enable-cxx --enable-static --disable-shared --with-pic"
        CPPFLAGS="-I${OPENSSL_PREFIX}/include -I${BDB_PREFIX}/include -I${LIBEVENT_PREFIX}/include/ -I${ICU_PREFIX}/include"
    else
	OPTIONS=""
        CPPFLAGS="-I${OPENSSL_PREFIX}/include -I${BDB_PREFIX}/include -I${LIBEVENT_PREFIX}/include/ -I${ICU_PREFIX}/include -Wno-unused-local-typedefs"
    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"
    if [ "${OS_NAME}" = "linux" ]; then
        LD_LIBRARY_PATH="${ICU_PREFIX}/lib" src/test/test_lbrycrd
    else
        DYLD_LIBRARY_PATH="${ICU_PREFIX}/lib" src/test/test_lbrycrd
    fi
    strip src/lbrycrdd
    strip src/lbrycrd-cli
    strip src/lbrycrd-tx
    strip src/test/test_lbrycrd
}

function clang_format_diff(){
    # run a code formatting check on any commits not in master
    # requires clang-format
    if ! git config remote.origin2.url > /dev/null; then
        git remote add origin2 https://github.com/lbryio/lbrycrd.git
    fi
    git fetch origin2
    git diff -U0 origin2/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/"
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
fi