#!/bin/bash

set -e

self=$0

defconfig_command="$self defconfig"
clean_command="$self clean"

cmake_build_dir="build"
cmake_cache_file="$cmake_build_dir/CMakeCache.txt"
cmake_userland_build_dir="userland/_build"
cmake_kernel_build_dir="kernel/_build"
cmake_libchcore_build_dirs="libchcore/_build libchcore/_install"

cmake_script_dir="scripts/build/cmake"
cmake_init_cache_default="$cmake_script_dir/LoadConfigDefault.cmake"
cmake_init_cache_ask="$cmake_script_dir/LoadConfigAsk.cmake"
cmake_init_cache_abort="$cmake_script_dir/LoadConfigAbort.cmake"
cmake_init_cache_dump="$cmake_script_dir/DumpConfig.cmake"

config_file=".config"
defconfig_file="scripts/build/default.config"

RED='\033[0;31m'
BLUE='\033[0;34m'
GREEN='\033[0;32m'
ORANGE='\033[0;33m'
BOLD='\033[1m'
NONE='\033[0m'

_echo_info() {
    echo -e "${BOLD}$@${NONE}"
}

_echo_succ() {
    echo -e "${GREEN}${BOLD}$@${NONE}"
}

_echo_warn() {
    echo -e "${ORANGE}${BOLD}$@${NONE}"
}

_echo_err() {
    echo -e "${RED}${BOLD}$@${NONE}"
}

defconfig() {
    if [ -d $cmake_build_dir ]; then
        _echo_err "There exists a build directory, please run \`$clean_command\` first"
        exit 1
    fi

    if [ -z "$1" ]; then
        plat="raspi3"
    else
        plat="$1"
    fi

    _echo_info "Generating default config file..."
    cp $defconfig_file $config_file
    _config_default
    _sync_config_with_cache
    _echo_succ "Default config written to \`$config_file\` file."
}

_check_config_file() {
    if [ ! -f $config_file ]; then
        _echo_err "There is no \`.config\` file, please run \`$defconfig_command\` first"
        exit 1
    fi
}

_config_default() {
    _echo_info "Configuring CMake..."
    cmake -B $cmake_build_dir -C $cmake_init_cache_default
}

_config_ask() {
    _echo_info "Configuring CMake..."
    cmake -B $cmake_build_dir -C $cmake_init_cache_ask
}

_sync_config_with_cache() {
    cmake -N -B $cmake_build_dir -C $cmake_init_cache_dump >/dev/null
}

config() {
    _check_config_file
    _config_ask
    _sync_config_with_cache
    _echo_succ "Config syned to \`$config_file\` file."
}

menuconfig() {
    _check_config_file
    _config_default

    echo
    _echo_warn "Note: In the menu config view, press C to save, Q to quit."
    read -p "Now press Enter to continue..."

    ccmake -B $cmake_build_dir
    _sync_config_with_cache
    _echo_succ "Config saved to \`$config_file\` file."
}

build() {
    _check_config_file
    _config_ask
    _sync_config_with_cache
    _echo_succ "Config syned to \`$config_file\` file."

    _echo_info "Building..."
    cmake --build $cmake_build_dir --target all --parallel $(nproc)
    _echo_succ "Succeeded to build all targets"
}

clean() {
    _echo_info "Cleaning..."

    if [ -f $cmake_cache_file ]; then
        _config_default # config with default values first, to avoid failure of cleaning
        cmake --build $cmake_build_dir --target clean-all
        rm -rf $cmake_build_dir
        _echo_succ "Succeeded to clean all targets"
    elif [ -d $cmake_build_dir ]; then
        rm -rf $cmake_build_dir $cmake_userland_build_dir $cmake_kernel_build_dir $cmake_libchcore_build_dirs
        _echo_warn "Succeeded to clean all targets, but some object files may be left"
    else
        _echo_info "Nothing to clean"
    fi
}

rebuild() {
    clean
    build
}

distclean() {
    clean
    _echo_info "Removing config file..."
    rm -rf $config_file

    _echo_succ "Succeeded to distclean"
}

_print_help() {
    echo -e "\
${BOLD}USAGE:${NONE}

    ${BOLD}$self [options] [command]${NONE}

${BOLD}OPTIONS:${NONE}

    ${BOLD}--local, -l${NONE}      run command in local environment (rather than docker)

${BOLD}COMMANDS:${NONE}

  Local:

    ${BOLD}help, --help, -h${NONE} print this help text

  Local or Docker:

    ${BOLD}defconfig${NONE}        generate default config
    ${BOLD}config${NONE}           run configuration step (interactively ask for config value if not set)
    ${BOLD}menuconfig${NONE}       use TUI menu to edit config
    ${BOLD}build${NONE}            build the project
    ${BOLD}clean${NONE}            clean the project
    ${BOLD}rebuild${NONE}          clean and build the project
    ${BOLD}distclean${NONE}        clean the project and remove config file
"
}

_docker_run() {
    if [ -f /.dockerenv ]; then
        # we are already in docker container
        $@
    else
        test -t 1 && use_tty="-t"
        docker run -i $use_tty --rm \
            -u $(id -u ${USER}):$(id -g ${USER}) \
            -v $(pwd):/chos -w /chos \
            ipads/chcore_builder:v1.3 \
            $self $@
    fi
}

_main() {
    run_in_docker=true
    while [ $# -gt 0 ]; do
        case $1 in
        help | --help | -h)
            _print_help
            exit
            ;;
        --local | -l)
            run_in_docker=false
            ;;
        -*)
            _echo_err "$self: invalid option \`$1\`\n"
            break
            ;;
        *)
            if [[ "$1" == "_"* || $(type -t "$1") != function ]]; then
                _echo_err "$self: invalid command \`$1\`\n"
                break
            fi

            if [[ $run_in_docker == true ]]; then
                _docker_run $@
            else
                $@
            fi
            exit
            ;;
        esac
        shift
    done

    # no command is run
    _print_help
    exit 1
}

_main $@
