#!/usr/bin/env bash
#
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

: ${DATA_DIRECTORY:="/usr/local/share/freebsdupdate"}
: ${STAGE_FILE:="${DATA_DIRECTORY}/stage"}
: ${RELEASE_DIRECTORY:="${DATA_DIRECTORY}/release"}
: ${LOG_DIRECTORY:="${DATA_DIRECTORY}/logs"}
: ${PORTS_TREE:="/usr/ports"}
: ${PORTS_REPO:="https://git.FreeBSD.org/ports.git"}

############## Setup #########################

function die {
    local status_code="$1"
    shift
    (>&2 echo "${@}")
    exit "$status_code"
}

function log {
    (>&2 echo "${@}")
}

############## Program #########################

function main {
    assert_directories

    local stage=""
    if [ -e "$STAGE_FILE" ]; then
        local stage=$(cat "$STAGE_FILE")
    fi
    if [ "$stage" = "selfbuild" ]; then
        log_cmd stage_selfbuild
    elif [ "$stage" = "selfinstallworld" ]; then
        log_cmd stage_selfinstallworld
    elif [ "$stage" = "selfconflictcheck" ]; then
        log_cmd stage_selfconflictcheck
    elif [ "$stage" = "releasebuild" ]; then
        log_cmd stage_releasebuild
    elif [ "$stage" = "done" ]; then
        log_cmd stage_done
    else
        die 1 "Unhandled stage: \"$stage\"."
    fi
}

function log_cmd {
    "${@}" |& tee "$LOG_DIRECTORY/$(date +%Y%m%d-%s).log"
}

function self_conflict_check {
    if etcupdate status | grep -qE '^  C '; then
        die 1 'Conflicts remain in etcupdate. Run `etcupdate resolve` to fix them first.'
    fi
}

function assert_directories {
    for d in "$DATA_DIRECTORY" "$RELEASE_DIRECTORY" "$LOG_DIRECTORY"; do
        if [ ! -e "$d" ]; then
            mkdir -p "$d"
        fi
    done
}

function update_ports_tree {
    if [ ! -e "$PORTS_TREE" ]; then
        mkdir -p $PORTS_TREE
        git -C $PORTS_TREE init --initial-branch=main
        git -C $PORTS_TREE remote add origin $PORTS_REPO
    fi
    git -C $PORTS_TREE fetch origin main # 'refs/heads/main'
    git -C $PORTS_TREE checkout FETCH_HEAD
}

function set_stage {
    echo "${@}" > "$STAGE_FILE"
}

function stage_selfbuild {
    self_conflict_check
    assert_directories
    update_ports_tree

    SRCCONF=/dev/null __MAKE_CONF=/dev/null make -C /usr/src clean
    SRCCONF=/dev/null __MAKE_CONF=/dev/null make -C /usr/src buildworld buildkernel
    SRCCONF=/dev/null __MAKE_CONF=/dev/null make -C /usr/src installkernel

    set_stage "selfinstallworld"
    /sbin/shutdown -r now
}

function stage_selfinstallworld {
    etcupdate -p
    SRCCONF=/dev/null __MAKE_CONF=/dev/null make -C /usr/src installworld
    etcupdate -B

    set_stage "selfconflictcheck"
    stage_selfconflictcheck
}

function stage_selfconflictcheck {
    self_conflict_check
    set_stage "releasebuild"
    /sbin/shutdown -r now
}

function stage_releasebuild {
    local today=$(date +%Y%m%d)
    local target_directory="${RELEASE_DIRECTORY}/${today}"
    if [ -e "$target_directory" ]; then
        die 1 "The release directory $target_directory already exists. Exiting."
    fi
    SRCCONF=/dev/null __MAKE_CONF=/dev/null make -C /usr/src clean
    make -C /usr/src buildworld buildkernel
    make -C /usr/src/release obj
    make -C /usr/src/release release
    mkdir -p "$target_directory"
    make -C /usr/src/release install DESTDIR="$target_directory"
    set_stage "done"
}

function stage_done {
    log "Everything is done."
}

main "${@}"