#!/bin/sh -e
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

# next steps:
# try running this, figure out what to do with the vars in the generated files
# in the bottom

# cd into this script’s directory
rootdir="$(cd "${0%/*}" 2>/dev/null; echo "$PWD")"
basename=`basename $0`

PACKAGE_AUTHOR_NAME="The Apache Software Foundation"

REBAR3_BRANCH="3.25.1"
ERLFMT_VERSION="v1.7.0"

# TEST=0
WITH_PROPER="true"
WITH_FAUXTON="true"
WITH_DOCS="true"
WITH_NOUVEAU="false"
WITH_CLOUSEAU="false"
ERLANG_MD5="false"
SKIP_DEPS="false"
WITH_SPIDERMONKEY="true"

run_erlang() {
    erl -noshell -eval "$1" -eval "halt()."
}

COUCHDB_USER="$(whoami 2>/dev/null || echo couchdb)"
JS_ENGINE=${JS_ENGINE:-"spidermonkey"}
SM_VSN=${SM_VSN:-"91"}
CLOUSEAU_MTH=${CLOUSEAU_MTH:-"dist"}
CLOUSEAU_URI=${CLOUSEAU_URI:-"https://github.com/cloudant-labs/clouseau/releases/download/%s/clouseau-%s-dist.zip"}
CLOUSEAU_VSN=${CLOUSEAU_VSN:-"2.25.0"}
CLOUSEAU_DIR="$(pwd)"/clouseau
ARCH="$(uname -m)"
MULTIARCH_NAME="$(command -v dpkg-architecture > /dev/null && dpkg-architecture -q DEB_HOST_MULTIARCH || true)"
ERLANG_VER="$(run_erlang 'io:put_chars(erlang:system_info(otp_release)).')"
ERLANG_OS="$(run_erlang 'case os:type() of {OS, _} -> io:format("~s~n", [OS]) end.')"

display_help () {
    cat << EOF
Usage: $basename [OPTION]

The $basename command is responsible for generating the build
system for Apache CouchDB.

Options:

  -h | --help                 display a short help message and exit
  -u | --user USER            set the username to run as (defaults to $COUCHDB_USER)
  --disable-fauxton           do not build Fauxton
  --disable-docs              do not build any documentation or manpages
  --with-nouveau              build the new experimental search module
  --with-clouseau             build the Clouseau search module
  --erlang-md5                use erlang for md5 hash operations
  --dev                       alias for --disable-docs --disable-fauxton
  --spidermonkey-version VSN  specify the version of SpiderMonkey to use (defaults to $SM_VSN)
  --clouseau-method MTH       specify the method for Clouseau to deploy: git or dist (defaults to $CLOUSEAU_MTH)
  --clouseau-uri URI          specify the location for retrieving Clouseau (defaults $(printf "$CLOUSEAU_URI" "$CLOUSEAU_VSN" "$CLOUSEAU_VSN"))
  --clouseau-version VSN      specify the version (tag/branch for git) of Clouseau to use (defaults to $CLOUSEAU_VSN)
  --skip-deps                 do not update erlang dependencies
  --rebar=PATH                use rebar by specified path (version >=2.6.0 && <3.0 required)
  --rebar3=PATH               use rebar3 by specified path
  --erlfmt=PATH               use erlfmt by specified path
  --js-engine=ENGINE          use js engine: spidermonkey or quickjs, defaults to spidermonkey
  --disable-spidermonkey      disable spidermonkey, don't try to build it
EOF
}

parse_opts() {
    while :; do
        case $1 in
            -h|--help)
                display_help
                exit
                ;;

            --without-proper)
                WITH_PROPER="false"
                shift
                continue
                ;;

            --disable-fauxton)
                WITH_FAUXTON="false"
                shift
                continue
                ;;

            --disable-docs)
                WITH_DOCS="false"
                shift
                continue
                ;;

            --with-nouveau)
                WITH_NOUVEAU="true"
                shift
                continue
                ;;

            --with-clouseau)
                WITH_CLOUSEAU="true"
                shift
                continue
                ;;

            --erlang-md5)
                ERLANG_MD5="true"
                shift
                continue
                ;;

            --dev)
                WITH_DOCS="false"
                WITH_FAUXTON="false"
                WITH_SPIDERMONKEY="false"
                shift
                continue
                ;;

            --dev-with-nouveau)
                WITH_DOCS="false"
                WITH_FAUXTON="false"
                WITH_NOUVEAU="true"
                WITH_SPIDERMONKEY="false"
                shift
                continue
                ;;

            --skip-deps)
                SKIP_DEPS="true"
                shift
                continue
                ;;

            --rebar)
                if [ -x "$2" ]; then
                    version=`$2 --version 2> /dev/null | grep -o "2\.[6-9]\.[0-9]"`
                    if [ $? -ne 0 ]; then
                        printf 'Rebar >=2.6.0 and <3.0.0 required' >&2
                        exit 1
                    fi
                    eval REBAR=$2
                    shift 2
                    continue
                else
                    printf 'ERROR: "--rebar" requires valid path to executable.\n' >&2
                    exit 1
                fi
                ;;

            --rebar3)
                if [ -x "$2" ]; then
                    eval REBAR3=$2
                    shift 2
                    continue
                else
                    printf 'ERROR: "--rebar3" requires valid path to executable.\n' >&2
                    exit 1
                fi
                ;;

            --erlfmt)
                if [ -x "$2" ]; then
                    eval ERLFMT=$2
                    shift 2
                    continue
                else
                    printf 'ERROR: "--erlfmt" requires valid path to executable.\n' >&2
                    exit 1
                fi
                ;;

            --user|-u)
                if [ -n "$2" ]; then
                    eval COUCHDB_USER=$2
                    shift 2
                    continue
                else
                    printf 'ERROR: "--user" requires a non-empty argument.\n' >&2
                    exit 1
                fi
                ;;
            --user=?*)
                eval COUCHDB_USER=${1#*=}
                ;;
            --user=)
                printf 'ERROR: "--user" requires a non-empty argument.\n' >&2
                exit 1
                ;;

            --spidermonkey-version)
                if [ -n "$2" ]; then
                    eval SM_VSN=$2
                    shift 2
                    continue
                else
                    printf 'ERROR: "--spidermonkey-version" requires a non-empty argument.\n' >&2
                    exit 1
                fi
                ;;
            --spidermonkey-version=?*)
                eval SM_VSN=${1#*=}
                ;;
            --spidermonkey-version=)
                printf 'ERROR: "--spidermonkey-version" requires a non-empty argument.\n' >&2
                exit 1
                ;;
            --js-engine)
                if [ -n "$2" ]; then
                    eval JS_ENGINE=$2
                    shift 2
                    continue
                else
                    printf 'ERROR: "--js-engine" requires a non-empty argument.\n' >&2
                    exit 1
                fi
                ;;
            --js-engine=?*)
                eval JS_ENGINE=${1#*=}
                ;;
            --js-engine=)
                printf 'ERROR: "--js-engine" requires a non-empty argument.\n' >&2
                exit 1
                ;;
            --disable-spidermonkey)
                WITH_SPIDERMONKEY="false"
                shift
                continue
                ;;

	    --clouseau-version)
		if [ -n "$2" ]; then
		    eval CLOUSEAU_VSN=$2
		    shift 2
		    continue
		else
		    printf 'ERROR: "--clouseau-version" requires a non-empty argument.\n' >&2
		    exit 1
		fi
		;;
	    --clouseau-version=?*)
		eval CLOUSEAU_VSN=${1#*=}
		;;
	    --clouseau-version=)
	        printf 'ERROR: "--clouseau-version" requires a non-empty argument.\n' >&2
	        exit 1
	        ;;

	    --clouseau-method)
		if [ -n "$2" ]; then
		    eval CLOUSEAU_MTH=$2
		    shift 2
		    continue
		else
		    printf 'ERROR: "--clouseau-method" requires a non-empty argument.\n' >&2
		    exit 1
		fi
		;;
	    --clouseau-method=?*)
		eval CLOUSEAU_MTH=${1#*=}
		;;
	    --clouseau-method=)
	        printf 'ERROR: "--clouseau-method" requires a non-empty argument.\n' >&2
	        exit 1
	        ;;

	    --clouseau-uri)
		if [ -n "$2" ]; then
		    eval CLOUSEAU_URI=$2
		    shift 2
		    continue
		else
		    printf 'ERROR: "--clouseau-uri" requires a non-empty argument.\n' >&2
		    exit 1
		fi
		;;
	    --clouseau-uri=?*)
		eval CLOUSEAU_URI=${1#*=}
		;;
	    --clouseau-uri=)
	        printf 'ERROR: "--clouseau-uri" requires a non-empty argument.\n' >&2
	        exit 1
	        ;;

            --) # End of options
                shift
                break
                ;;
            -?*)
                echo "WARNING: Unknown option '$1', ignoring" >&2
                shift
                ;;
            *) # Done
                break
        esac
        shift
    done
}

parse_opts $@

if [ "${WITH_SPIDERMONKEY}" = "true" ] && [ "${ARCH}" = "aarch64" ] && [ "${SM_VSN}" = "60" ]; then
  echo "ERROR: SpiderMonkey 60 is known broken on ARM 64 (aarch64). Use another version instead."
  exit 1
fi

if [ "${WITH_SPIDERMONKEY}" = "true" ] && [ "${ERLANG_OS}" = "unix" ]; then
    case "${SM_VSN}" in
        1.8.5)
            SM_HEADERS="js"
            ;;
        *)  SM_HEADERS="mozjs-${SM_VSN}"
            ;;
        esac

    # This list is taken from src/couch/rebar.config.script, please keep them in sync.
    if [ ! -d "/usr/include/${SM_HEADERS}" ] && \
        [ -n "${MULTIARCH_NAME}" -a ! -d "/usr/include/${MULTIARCH_NAME}/${SM_HEADERS}" ] && \
        [ ! -d "/usr/local/include/${SM_HEADERS}" ] && \
        [ ! -d "/opt/homebrew/include/${SM_HEADERS}" ]; then
        echo "ERROR: SpiderMonkey ${SM_VSN} is not found. Please specify with --spidermonkey-version."
        exit 1
    fi
fi

# If spidermonkey was disabled but JS_ENGINE set to "spidermonkey", reset it to "quickjs"
if [ "${WITH_SPIDERMONKEY}" = "false" ] && [ "${JS_ENGINE}" = "spidermonkey" ]; then
   echo "NOTICE: Spidermonkey was disabled, but JS_ENGINE=spidermonkey. Setting JS_ENGINE=quickjs"
   JS_ENGINE="quickjs"
fi

# If we're in a release tarball and we don't have proper, then mark it as skipped
if [ ! -d .git ] && [ "$WITH_PROPER" = "true" ] && [ ! -d src/proper ]; then
    WITH_PROPER="false"
fi

# If we're in a release tarball and we don't have spidermonkey, then mark it as skipped and enable quickjs
if [ ! -d .git ] && [ "$WITH_SPIDERMONKEY" = "true" ] && [ ! -d src/couch/priv/couch_js ]; then
    echo "NOTICE: Spidermonkey was disabled in release tarball. Setting JS_ENGINE=quickjs"
    WITH_SPIDERMONKEY="false"
    JS_ENGINE="quickjs"
fi

echo "==> configuring couchdb in rel/couchdb.config"
cat > rel/couchdb.config << EOF
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
%   http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.
%
% The contents of this file are auto-generated by configure
%
{package_author_name, "$PACKAGE_AUTHOR_NAME"}.
{prefix, "."}.
{data_dir, "./data"}.
{view_index_dir, "./data"}.
{nouveau_index_dir, "./data/nouveau"}.
{nouveau_url, "http://127.0.0.1:5987"}.
{nouveau_port, 5987}.
{nouveau_admin_port, 5988}.
{state_dir, "./data"}.
{log_file, "$LOG_FILE"}.
{fauxton_root, "./share/www"}.
{user, "$COUCHDB_USER"}.
{js_engine, "$JS_ENGINE"}.
{spidermonkey_version, "$SM_VSN"}.
{with_spidermonkey, $WITH_SPIDERMONKEY}.
{node_name, "-name couchdb@127.0.0.1"}.
{cluster_port, 5984}.
{backend_port, 5986}.
{prometheus_port, 17986}.
EOF

cat > install.mk << EOF
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
#
# The contents of this file are auto-generated by configure
#
package_author_name = $PACKAGE_AUTHOR_NAME

with_fauxton = $WITH_FAUXTON
with_docs = $WITH_DOCS
with_nouveau = $WITH_NOUVEAU
with_clouseau = $WITH_CLOUSEAU

user = $COUCHDB_USER
js_engine = $JS_ENGINE
spidermonkey_version = $SM_VSN
with_spidermonkey = $WITH_SPIDERMONKEY
EOF

cat > $rootdir/config.erl << EOF
{with_proper, $WITH_PROPER}.
{erlang_md5, $ERLANG_MD5}.
{js_engine, "$JS_ENGINE"}.
{spidermonkey_version, "$SM_VSN"}.
{with_spidermonkey, $WITH_SPIDERMONKEY}.
EOF

install_local_rebar() {
    if [ ! -x "${rootdir}/bin/rebar" ]; then
        if [ ! -d "${rootdir}/src/rebar" ]; then
            git clone --depth 1 https://github.com/apache/couchdb-rebar.git ${rootdir}/src/rebar
        fi
        make -C ${rootdir}/src/rebar
        mv ${rootdir}/src/rebar/rebar ${rootdir}/bin/rebar
        make -C ${rootdir}/src/rebar clean
    fi
}

install_local_rebar3() {
    if [ ! -x "${rootdir}/bin/rebar3" ]; then
        if [ ! -d "${rootdir}/src/rebar3" ]; then
            git clone --depth 1 --branch ${REBAR3_BRANCH} https://github.com/erlang/rebar3.git ${rootdir}/src/rebar3
        fi
        cd src/rebar3
        ./bootstrap
        mv ${rootdir}/src/rebar3/rebar3 ${rootdir}/bin/rebar3
        cd ../..
    fi
}

install_local_erlfmt() {
    if [ ! -x "${rootdir}/bin/erlfmt" ]; then
        if [ ! -d "${rootdir}/src/erlfmt" ]; then
            git clone --depth 1 --branch "${ERLFMT_VERSION}" https://github.com/WhatsApp/erlfmt.git ${rootdir}/src/erlfmt
        fi
        cd "${rootdir}"/src/erlfmt
        ${REBAR3} as release escriptize
        mv ${rootdir}/src/erlfmt/_build/release/bin/erlfmt ${rootdir}/bin/erlfmt
        ${REBAR3} clean
        cd ../..
    fi
}

check_local_clouseau_dir() {
    _dir="$1"

    if [ -e "$_dir" ]; then
	printf "ERROR: \"%s\" already exists.  Please remove or move it away first.\n" "$_dir" >&2
	exit 1
    fi
}

fetch_file() {
    _file_name="$1"
    _file_url="$2"

    if ! curl -sSL --max-redirs 1 -o "$_file_name" "$_file_url"; then
	printf "ERROR: %s could not be downloaded.\n" "$_file_url" >&2
	exit 1
    fi
}

install_local_clouseau() {
    case "$CLOUSEAU_MTH" in
	dist)
	    _DIST_URL=$(printf "$CLOUSEAU_URI" "$CLOUSEAU_VSN" "$CLOUSEAU_VSN")
	    _MAVEN_BASE_URI=https://repo1.maven.org/maven2
	    _LOGBACK_DIST_URL="$_MAVEN_BASE_URI"/ch/qos/logback

	    : "${LOGBACK_VERSION:=1.2.13}"
	    _LOGBACK_CORE_JAR=logback-core-"$LOGBACK_VERSION".jar
	    _LOGBACK_CORE_URL="$_LOGBACK_DIST_URL"/logback-core/"$LOGBACK_VERSION"/"$_LOGBACK_CORE_JAR"
	    _LOGBACK_CLASSIC_JAR=logback-classic-"$LOGBACK_VERSION".jar
	    _LOGBACK_CLASSIC_URL="$_LOGBACK_DIST_URL"/logback-classic/"$LOGBACK_VERSION"/"$_LOGBACK_CLASSIC_JAR"

	    check_local_clouseau_dir "$CLOUSEAU_DIR"
	    mkdir -p "$CLOUSEAU_DIR"

	    echo "Fetching Clouseau from $_DIST_URL..."

	    fetch_file clouseau.zip "$_DIST_URL"

	    if ! unzip -q -j clouseau.zip -d "$CLOUSEAU_DIR"; then
		printf "ERROR: Clouseau distribution package (clouseau.zip) could not be extracted.\n" >&2
		exit 1
	    fi

	    rm clouseau.zip

	    echo "Fetching Logback $LOGBACK_VERSION from $_LOGBACK_DIST_URL..."

	    fetch_file "$CLOUSEAU_DIR"/"$_LOGBACK_CORE_JAR" "$_LOGBACK_CORE_URL"
	    fetch_file "$CLOUSEAU_DIR"/"$_LOGBACK_CLASSIC_JAR" "$_LOGBACK_CLASSIC_URL"
	    ;;

	git)
	    echo "Cloning Clouseau from $CLOUSEAU_URI ($CLOUSEAU_VSN)..."
	    check_local_clouseau_dir "$CLOUSEAU_DIR"
	    git clone --depth 1 --branch "$CLOUSEAU_VSN" "$CLOUSEAU_URI" "$CLOUSEAU_DIR"
	    ;;

	*) printf "ERROR: Invalid deployment method for Clouseau.  Please use either \`dist\` or \`git\` verbatim.\n" >&2
	   exit 1
    esac
}

if [ -z "${REBAR}" ]; then
    install_local_rebar
    REBAR=${rootdir}/bin/rebar
fi

if [ -z "${REBAR3}" ]; then
    install_local_rebar3
    REBAR3=${rootdir}/bin/rebar3
fi

if [ -z "${ERLFMT}" ]; then
    install_local_erlfmt
    ERLFMT=${rootdir}/bin/erlfmt
fi

if [ "$WITH_CLOUSEAU" = "true" ]; then
    install_local_clouseau
fi

# only update dependencies, when we are not in a release tarball
if [ -d .git ] && [ "$SKIP_DEPS" = "false" ]; then
    echo "==> updating dependencies"
    ${REBAR} get-deps update-deps
fi

# External repos frequently become integrated with the primary repo,
# resulting in obsolete .git directories, and possible confusion.
# It is usually a good idea to delete these .git directories.
for path in $(find src -name .git -type d); do
    git ls-files --error-unmatch $(dirname $path) > /dev/null 2>&1 && \
        echo "WARNING unexpected .git directory $path"
done

echo "You have configured Apache CouchDB, time to relax. Relax."
