#!/usr/bin/env bash

workspace="$(pwd -P)"
buildfile="${workspace}/DEBBUILD"

srcdir="${workspace}/src"
pkgdir="${workspace}/pkg"

FAKE_PACKAGE=0
OVERRIDE_SOURCE=0
SHOW_TARGET_FILE=0
QUIET=0

set -o functrace
set -o nounset
set -o errtrace
set -o errexit

function err_occur {
    err "Failed at $1: ${BASH_COMMAND}"
    err "Trace line number: %s" "$*"
}
trap 'err_occur "${LINENO}" "${BASH_LINENO[@]}"' ERR

# prefer terminal safe colored and bold text when tput is supported
if tput setaf 0 &>/dev/null; then
    ALL_OFF="$(tput sgr0)"
    BOLD="$(tput bold)"
    BLUE="${BOLD}$(tput setaf 4)"
    GREEN="${BOLD}$(tput setaf 2)"
    RED="${BOLD}$(tput setaf 1)"
    YELLOW="${BOLD}$(tput setaf 3)"
else
    ALL_OFF="\e[0m"
    BOLD="\e[1m"
    BLUE="${BOLD}\e[34m"
    GREEN="${BOLD}\e[32m"
    RED="${BOLD}\e[31m"
    YELLOW="${BOLD}\e[33m"
fi
readonly ALL_OFF BOLD BLUE GREEN RED YELLOW

function msg {
    (( QUIET )) && return 0
    local mesg=$1; shift
    printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@"
}
function msg2 {
    (( QUIET )) && return 0
    local mesg=$1; shift
    printf "${BLUE}  ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@"
}
function err {
    (( QUIET )) && return 0
    local mesg=$1; shift
    printf "${RED}  ->${ALL_OFF}${BOLD}${RED} ${mesg}${ALL_OFF}\n" "$@" >&2
}

function get_full_version {
    local result="${pkgver//-/.}"
    if [[ -n "${pkgrel:-}" ]] ; then
        result="${result}-${pkgrel}"
    fi
    if [[ -n "${pkgcommit:-}" ]] ; then
        result="${result}+${pkgcommit}"
    fi
    echo "${result}"
}

function get_deb_name {
    echo "${pkgname}_$(get_full_version).deb"
}

function debian_control {
    local install_size="$(du --block-size=1K --summarize ${pkgdir} | cut -d $'\t' -f 1)"
    local fv="$(get_full_version)"

    cat << EOF | envsubst
Package: ${pkgname}
Version: ${fv}
Architecture: all
Maintainer: ${maintainer}
Installed-Size: ${install_size}
Description: ${pkgdesc}
EOF
}

function generate_deb {
    msg "generating deb package..."

    local tmpdir="$(mktemp --directory)"
    local data_tgz="${tmpdir}/data.tar.gz"
    local control_tgz="${tmpdir}/control.tar.gz"
    local debian_binary="${tmpdir}/debian-binary"

    find "${pkgdir}" -mindepth 1 -not -type d -not -path "${pkgdir}/DEBIAN*" -printf "%P\n" |
        tar -czf "${data_tgz}" -C "${pkgdir}" -T -

    find "${pkgdir}/DEBIAN" -mindepth 1 -not -type d -printf "%P\n" |
        tar -czf "${control_tgz}" -C "${pkgdir}/DEBIAN" -T -

    echo 2.0 > "${debian_binary}"

    ar r "$(get_deb_name)" "${debian_binary}" "${control_tgz}" "${data_tgz}"

    rm -rf "${tmpdir}"

    msg2 "generated deb: %s" "$(get_deb_name)"
}

function url_type {
    if [[ "${url}" == git+* ]] ; then
        echo "git"
    elif [[ "${url}" == http://* ]] ; then
        echo "http"
    elif [[ "${url}" == https://* ]] ; then
        echo "https"
    elif [[ "${url}" == ftp://* ]] ; then
        echo "ftp"
    elif [[ "${url}" == ftps://* ]] ; then
        echo "ftps"
    elif [[ "${url}" == file//* ]]; then
        echo "file"
    elif [[ "${url}" != *://* ]] ; then
        echo "file"
    else
        err "Unkown url schema: %s" "${url}"
        err "Aborting..."
        exit 1
    fi
}

function create_soft_link {
    local src="$1"
    local tgt="$2"

    if [[ ! -e "${src}" ]] ; then
        err "soft_link src %s not exist" "${src}"
    fi

    if [[ -e "${tgt}" ]] ; then
        err "soft_link tgt %s already exist" "${tgt}"
    fi

    ln --symbolic "${src}" "${tgt}"
}

function url_fragment {
    local url="$1"
    local fragment="${url#*#}"
    if [[ "${url}" == "${fragment}" ]] ; then
        return
    fi
    echo "${fragment}"
}

# $1 is relative path to file
# $2 is url/(relative path to materials)
function retrive_source_single {
    local file_name="$1"
    local url="$2"

    case "$(url_type "${url}")" in
        "git")
            if [[ -d "${file_name}" ]]; then
                msg2 "updating ${file_name} from ${url} with git..."
                git --git-dir="${workspace}/${file_name}" fetch --all
            else
                msg2 "cloning ${file_name} from ${url} with git..."
                local git_source="${url##git+}"
                git_source="${git_source%%#*}"
                git clone --mirror "${git_source}" "${workspace}/${file_name}"
            fi
            ;;
        "http"|"https"|"ftp"|"ftps")
            if (( ! OVERRIDE_SOURCE )) && [[ -f "${workspace}/${file_name}" ]] ; then
                msg2 "${file_name} already exists, skip download"
            else
                msg2 "retriving ${file_name} from ${url} with curl..."
                curl --location "${url}" --output "${workspace}/${file_name}"
            fi
            ;;
        "file")
            if [[ -e "${workspace}/${url}" ]] ; then
                msg2 "found local file: ${url}"
            else
                err "local file not found: ${url}"
                err "Aborting..."
                exit 1
            fi
            ;;
        *)
            err "retriving url as type ${url_type} not supported"
            err "Aborting..."
            exit 1
    esac
}

# $1 is relative path to file
# $2 is url/(relative path to materials)
function extract_source_single {
    local file_name="$1"
    local url="$2"
    local url_type="$(url_type "${url}")"

    case "${url_type}" in
        "git")
            [[ -d "${srcdir}/${file_name}" ]] || mkdir -p "${srcdir}/${file_name}"

            local ref=HEAD
            local frag=$(url_fragment "${url}")
            if [[ -n "${frag}" ]] ; then
                case "${frag%%=*}" in
                    "branch")
                        ref="refs/heads/${frag##*=}"
                        ;;
                    "tag")
                        ref="refs/tags/${frag##*=}"
                        ;;
                    "commit")
                        ref="${frag##*=}"
                        ;;
                    *)
                        err "unrecognized reference in git url: ${frag}"
                        exit 1
                        ;;
                esac
            fi

            msg2 "extracting git ${workspace}/${file_name} with reference ${ref}"
            git clone --shared "${workspace}/${file_name}" "${srcdir}/${file_name}"
            git -C "${srcdir}/${file_name}" switch --force-create makedeb --no-track "${ref}"
            ;;
        "file") # for material files, just soft-link under src
            create_soft_link $(realpath "${workspace}/${url}") "${srcdir}/${file_name}"
            decompress_source_file "${srcdir}/${file_name}"
            ;;
        *)      # for downloaded files, just soft-link downloaded file under src
            create_soft_link $(realpath "${workspace}/${file_name}") "${srcdir}/${file_name}"
            decompress_source_file "${srcdir}/${file_name}"
            ;;
    esac
}

# $1 is real path to file
function decompress_source_file {
    local file="$1"
    case "${file}" in
        *.tar|*.tar.gz|*.tgz|*.tar.Z|*.tar.bz2|*.tbz2|*.tar.lz|*.tlz|*.tar.xz|*.txz|*.tar.zst)
            cmd="tar" ;;
        *.gz|*.z|*.Z)
            cmd="gzip" ;;
        *.bz2|*.bz)
            cmd="bzip2" ;;
        *.xz)
            cmd="xz" ;;
        *.zst|*.zstd)
            cmd="zstd" ;;
        *.zip)
            cmd="unzip" ;;
        *)
            msg2 "no need to decompress, skip %s" "${file}"
            return
    esac

    msg2 "decompressing %s with %s" "${file}" "${cmd}"
    local res=0
    case "$cmd" in
        "tar")
            $cmd -xf "$file" --directory="${srcdir}" || res=$?
            ;;
        "unzip")
            $cmd "$file" -d "${srcdir}" || res=$?
            ;;
        *)
            rm -f -- "${file%.*}"
            $cmd -dcf -- "$file" > "${file%.*}" || res=$?
            ;;
    esac
}

error_function() {
	# first exit all subshells, then print the error
	if (( ! BASH_SUBSHELL )); then
		error "A failure occurred in %s()." "$1"
		plainerr "$(gettext "Aborting...")"
	fi
	exit $E_USER_FUNCTION_FAILED
}

# use less strict shell for custom functions
function run_function_safe {
	local restoretrap

    set +o errtrace
    set +o errexit

	restoretrap=$(trap -p ERR)
	trap "error_function '$1'" ERR

	"$1"

    set -o errtrace
    set -o errexit

	trap - ERR
	eval "$restoretrap"
}

function is_function {
    declare -F "$1" > /dev/null
}

function check_source_validation {
    # check if all source is valid
    for s in "${source[@]}"; do
        if ! grep "::" <<< "$s" > /dev/null; then
            err "source must contain \"::\" to specify file name"
            exit 1
        fi
    done
}

function clean_dir {
    msg "cleaning \$srcdir and \$pkgdir..."
    rm -rf "${srcdir}" "${pkgdir}"
    mkdir -p "${srcdir}" "${pkgdir}"
}

function retrieve_source {
    msg "retrieving source..."
    for s in "${source[@]}"; do
        file_name="${s%%::*}"
        url="${s##*::}"

        retrive_source_single "${file_name}" "${url}"
    done
}

function extract_source {
    msg "extracting source..."
    for s in "${source[@]}"; do
        file_name="${s%%::*}"
        url="${s##*::}"

        extract_source_single "${file_name}" "${url}"
    done
}

function update_pkgver {
    if ! is_function pkgver; then
        return
    fi

    msg "updating pkgver..."
    newpkgver="$(run_function_safe pkgver)"
    if [[ "${newpkgver}" != "${pkgver:-}" ]] ; then
        mapfile -t bfcontent < "${buildfile}"

        shopt -s extglob
        bfcontent=("${bfcontent[@]/#pkgver=*?(")([^ ])?(")/pkgver=$newpkgver}")
        bfcontent=("${bfcontent[@]/#pkgrel=*([^ ])/pkgrel=1}")
        shopt -u extglob

        if ! printf '%s\n' "${bfcontent[@]}" > "${buildfile}"; then
            err "Failed to update %s from %s to %s" "pkgver" "$pkgver" "$newpkgver"
            exit 1
        fi

        source "${buildfile}"

        msg2 "updated version: ${newpkgver}"
    fi

    pkgver="${newpkgver}"
}

function generate_control {

    msg "generating control info..."
    mkdir -p "${pkgdir}/DEBIAN"
    echo 2 > "${pkgdir}/DEBIAN/compat"
    debian_control > "${pkgdir}/DEBIAN/control"

    function debian_hooks_warpper { is_function "$1" && "$1" > "$2" && chmod +x "$2" || true; }
    debian_hooks_warpper debian_preinst  ${pkgdir}/DEBIAN/preinst
    debian_hooks_warpper debian_postinst ${pkgdir}/DEBIAN/postinst
    debian_hooks_warpper debian_prerm    ${pkgdir}/DEBIAN/prerm
    debian_hooks_warpper debian_postrm   ${pkgdir}/DEBIAN/postrm
}

function run_function {
    if is_function "$1" ; then
        msg "run function: $1"
        run_function_safe "$1"
    fi
}


##
## Here start the build logic
##

while (( "$#" >= 1 )); do
    case "$1" in
        -F)  FAKE_PACKAGE=1 ;;
        -OS) OVERRIDE_SOURCE=1 ;;
        -STF) SHOW_TARGET_FILE=1 ; QUIET=1 ;;
        -Q) QUIET=1 ;;
        *)   err "Unkown option $1"; break ;;
    esac
    shift
done

source "${buildfile}"


if (( ! FAKE_PACKAGE )) ; then
    if (( SHOW_TARGET_FILE )) && ! is_function pkgver ; then
        echo "$(get_deb_name)"
        exit 0
    fi

    check_source_validation

    clean_dir

    retrieve_source

    extract_source

    update_pkgver

    if (( SHOW_TARGET_FILE )) && is_function pkgver ; then
        echo "$(get_deb_name)"
        exit 0
    fi

    run_function build

    # recursive call self to run rest task in fakeroot
    msg "entering fakeroot environment..."
    fakeroot -- bash -$- "${BASH_SOURCE[0]}" -F "${ARGLIST[@]}" || exit $?
    msg "leaving fakeroot environment..."

    msg "builds has done"
else
    run_function package

    generate_control

    generate_deb
fi
