#!/bin/bash

set -euo pipefail

# SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"

ARCHS=(
    "x86_64"
    "aarch64"
)

HOSTARCH=$(uname -m)

SOURCE_DIR=""
BUILD_DIR=""
OUTPUT_DIR=""
EXECUTABLE_NAME=""

# set flags from command line
RELEASE=0
MULTIBUILD=0


function help() {
    echo "Usage: $0 [options]"
    echo "Options:"
    echo "  version   Show version information and exit"
    echo " "
    echo "  <directory>  Build the project in <directory>"
    echo "     -r, --release   Build release version"
    echo "     -m, --multi     Build for multiple architectures"
}

function version() {
    echo "magicbuild 0.1"
}

# ----------------------------------------------------------------------------------------------------------
# CHECK PACKAGES
# ----------------------------------------------------------------------------------------------------------

function check_packages() {

    # Define packages based on distribution
    case $OS in
        "Ubuntu"|"Debian GNU/Linux")
            # Common packages for both Ubuntu and Debian
            PACKAGES="cmake make g++ build-essential upx musl-tools wget tar ccache ninja-build mold"
            ;;
        *)
            print_error "Unsupported distribution: $OS"
            exit 1
            ;;
    esac

    # Function to check if a package is installed
    is_package_installed() {
        dpkg -l "$1" 2>/dev/null | grep -q "^ii"
    }

    # Install missing packages
    print_status "Checking required packages..."
    for pkg in $PACKAGES; do
        if ! is_package_installed "$pkg"; then
            echo "Missing package: $pkg"
            exit 1
        fi
        echo "Package $pkg is installed"
    done


}

# ----------------------------------------------------------------------------------------------------------
# MUSL CROSS COMPILERS
# ----------------------------------------------------------------------------------------------------------

function installmusl() {
    echo "Installing musl toolchain"

    # Set install directory
    if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
        USER_HOME=$(eval echo "~${SUDO_USER}")
    else
        USER_HOME="$HOME"
    fi
    INSTALL_DIR="$USER_HOME/.musl-cross"
    mkdir -p "$INSTALL_DIR"

    TMPDIR=$(mktemp -d)
    trap 'rm -rf "$TMPDIR"' EXIT

    function install_musl_cross() {
        local TOOLCHAIN="$1"
        local MUSL_CC_URL="https://musl.cc"
        if [ ! -d "$INSTALL_DIR/$TOOLCHAIN" ]; then
            echo "Downloading $TOOLCHAIN musl cross toolchain..."
            wget -nc -O "$TMPDIR/$TOOLCHAIN.tgz" "$MUSL_CC_URL/$TOOLCHAIN.tgz"
            tar -C "$INSTALL_DIR" -xvf "$TMPDIR/$TOOLCHAIN.tgz"
        fi
    }

    function check_path() {
        if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
            local BASHRC="$USER_HOME/.bashrc"
            local TOOLCHAIN="$1"
            local MUSL_PATH="$INSTALL_DIR/$TOOLCHAIN/bin"
            if ! echo "$PATH" | grep -q "$MUSL_PATH"; then
                echo "Adding $MUSL_PATH to PATH in $BASHRC"
                PATH_LINE="export PATH=\"$MUSL_PATH:\$PATH\""
                if ! grep -Fxq "$PATH_LINE" "$BASHRC"; then
                    echo "" >> "$BASHRC"
                    echo "# Add musl cross compilers to PATH for dropshell" >> "$BASHRC"
                    echo "$PATH_LINE" >> "$BASHRC"
                    echo "Added musl cross compilers to $BASHRC"
                    echo "You should run 'source ~/.bashrc' to update your PATH"
                else
                    echo "You should run 'source ~/.bashrc' to update your PATH"
                fi
            fi
        fi
    }

    TOOLCHAIN_LIST=(
        "aarch64-linux-musl-cross"
        "x86_64-linux-musl-cross"
        "x86_64-linux-musl-native"
    )

    for TOOLCHAIN in "${TOOLCHAIN_LIST[@]}"; do
        install_musl_cross "$TOOLCHAIN"
        check_path "$TOOLCHAIN"
    done

    # Clean up
    rm -rf "$TMPDIR"
}

# ----------------------------------------------------------------------------------------------------------
# BUILD
# ----------------------------------------------------------------------------------------------------------

CMAKE_FILE="CMakeLists.txt"

function get_executable_name() {
    EXECUTABLE_NAME=""
    local var_value=""

    echo "Getting executable name from ${SOURCE_DIR}/${CMAKE_FILE}"

    while IFS= read -r line; do
        # Look for set(PROJECT_EXE_NAME ipdemo)
        if [[ "$line" =~ set\(PROJECT_EXE_NAME[[:space:]]+([a-zA-Z0-9_-]+)\) ]]; then
            var_value=$(echo "$line" | sed -E 's/.*set\(PROJECT_EXE_NAME[[:space:]]+([a-zA-Z0-9_-]+)\).*/\1/')
        fi
        # Look for add_executable(${PROJECT_EXE_NAME}
        if [[ "$line" =~ add_executable\([[:space:]]*\$\{PROJECT_EXE_NAME\}[[:space:]] ]]; then
            echo "Found executable name: $var_value"
            EXECUTABLE_NAME="$var_value"
        fi
    done < "${SOURCE_DIR}/${CMAKE_FILE}"

    if [[ -z "$EXECUTABLE_NAME" ]]; then
        echo "Executable name not found."
        exit 1
    else
        echo "Executable name: $EXECUTABLE_NAME"
    fi
}

function build_arch() {
    local ARCH="$1"
    local PREVDIR="$PWD"
    local JOBS;
    JOBS=$(nproc) # Set JOBS to the number of available CPU cores
    cd "${SOURCE_DIR}" || exit 1

    local ARCH_BUILD_DIR=${BUILD_DIR}/${ARCH}
    mkdir -p "${ARCH_BUILD_DIR}"

    echo "Building for ${ARCH} in ${ARCH_BUILD_DIR}, from ${SOURCE_DIR}"

    if [ ! -f "${HOME}/.musl-cross/${ARCH}-linux-musl-cross/bin/${ARCH}-linux-musl-g++" ]; then
        installmusl
    fi

    CMAKE_BUILD_TYPE="Release"
    if [ "$RELEASE" -eq 0 ]; then
        CMAKE_BUILD_TYPE="Debug"
    fi

    export CC="${HOME}/.musl-cross/${ARCH}-linux-musl-cross/bin/${ARCH}-linux-musl-gcc"
    export CXX="${HOME}/.musl-cross/${ARCH}-linux-musl-cross/bin/${ARCH}-linux-musl-g++"
    export SYSROOT="${HOME}/.musl-cross/${ARCH}-linux-musl-cross/${ARCH}-linux-musl/sysroot"
    export OPENSSL_ROOT_DIR="${SYSROOT}/usr"
    export CFLAGS="--sysroot=$SYSROOT"
    export CXXFLAGS="--sysroot=$SYSROOT"
    export LDFLAGS="--sysroot=$SYSROOT"
    export MAKEFLAGS="-j${JOBS}"

    cmake -B "${ARCH_BUILD_DIR}" -G Ninja \
        -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" \
        -DCMAKE_C_COMPILER_LAUNCHER=ccache \
        -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
        -DCMAKE_LINKER=mold \
        -DCMAKE_C_COMPILER_TARGET="${ARCH}-linux-musl" \
        -DCMAKE_CXX_COMPILER_TARGET="${ARCH}-linux-musl" \
        -DCMAKE_C_FLAGS="${CFLAGS}" \
        -DCMAKE_CXX_FLAGS="${CXXFLAGS}" \
        -DCMAKE_FIND_ROOT_PATH="${SYSROOT}" \
        -DCMAKE_SYSROOT="${SYSROOT}"
    cd "${ARCH_BUILD_DIR}" || exit 1
    ninja -k0 -j"${JOBS}"

    if [ "$RELEASE" -eq 1 ]; then
        upx "${ARCH_BUILD_DIR}/${EXECUTABLE_NAME}"
    fi

    if [ ! -d "${OUTPUT_DIR}" ]; then
        mkdir -p "${OUTPUT_DIR}"
    fi
    cp "${ARCH_BUILD_DIR}/${EXECUTABLE_NAME}" "${OUTPUT_DIR}/${EXECUTABLE_NAME}.${ARCH}"
    
    cd "${PREVDIR}" || exit 1
}

function build() {
    if [ -z "$1" ]; then
        help
        echo " "
        echo "Error: DIR is required"
        exit 1
    fi

    echo "Building project in directory $1"
    if [ ! -d "$1" ]; then
        echo "Directory $1 does not exist"
        exit 1
    fi

    # remove trailing slash from SOURCE_DIR
    SOURCE_DIR="$1"
    SOURCE_DIR=$(echo "$SOURCE_DIR" | sed 's:/*$::')

    # make SOURCE_DIR absolute
    SOURCE_DIR=$(realpath "$SOURCE_DIR")

    BUILD_DIR="${SOURCE_DIR}/build"
    OUTPUT_DIR="${SOURCE_DIR}/output"

    if [ ! -f "${SOURCE_DIR}/${CMAKE_FILE}" ]; then
        echo "${CMAKE_FILE} not found in ${SOURCE_DIR}"
        exit 1
    fi

    get_executable_name

    # if have the -m option, then build for multiple architectures
    if [ $MULTIBUILD -eq 1 ]; then
        for arch in "${ARCHS[@]}"; do
            build_arch "$arch"
        done
    else
        build_arch "$HOSTARCH"
    fi
}


# ----------------------------------------------------------------------------------------------------------
# MAIN
# ----------------------------------------------------------------------------------------------------------

function main() {

    while getopts "rm" opt; do
        case $opt in
            r)
                RELEASE=1
                ;;
            m)
                MULTIBUILD=1
                ;;
            *)
                help
                exit 1
                ;;
        esac
    done

    # Shift the processed options out of the argument list
    shift $((OPTIND-1))

    # get command argument, now that the flags are set
    CMD="$1"

    if [ -z "$CMD" ]; then
        help
        exit 1
    fi


    case "$CMD" in
        version)
            version
            ;;
        *)
            build "$1"
            ;;
    esac
}

main "$@"