#!/usr/bin/env bash

# patch-tool: A tool for maintaining the head.hackage patchset

set -e

patches_dir=$(realpath $(dirname $0)/../patches)

split_pkg_version() {
    package=$(echo $1 | sed 's/\(.\+\)-\([0-9]\+\(\.[0-9]\+\)*\)/\1/')
    version=$(echo $1 | sed 's/\(.\+\)-\([0-9]\+\(\.[0-9]\+\)*\)/\2/')
}

# Return the version number of the most recent release of the given package
latest_version() {
    pkg=$1
    curl -s -H "Accept: application/json" -L -X GET http://hackage.haskell.org/package/$pkg/preferred | jq '.["normal-version"] | .[0]' -r
}

drop_old() {
    for ext in patch cabal; do
        for file in patches/*.$ext; do
            split_pkg_version $(basename $file .$ext)
            latest=$(latest_version $package)
            #echo "$file $package $version $latest "
            if [ "$latest" != "$version" ]; then
                echo "$file is obsolete (latest version of $package is $latest), removed"
                git rm -q $file
            fi
        done
    done
}

add_pkg_dirs() {
    local dirs=$@
    echo "optional-packages: $dirs" >> cabal.project.local
    echo "Added $dirs to cabal.project.local"
}

patch_pkg() {
    local pkg=$(basename $1 .patch)
    local pkg_dir=packages/$pkg
    local patch=$patches_dir/$pkg.patch
    local cabal=$patches_dir/$pkg.cabal

    local patched=
    if [ -s "$patch" ]; then
        echo "Applied patch $patch to $pkg"
        git -C $pkg_dir apply $patch
        patched=1
    fi

    if [ -f "$cabal" ]; then
        echo "Updated cabal file of $pkg with $cabal"
        cp "$cabal" $pkg_dir/*.cabal
        patched=1
    fi

    if [ -n "$patched" ]; then
        git -C $pkg_dir commit -q -a -m "head.hackage.org patch"
    fi
}

unpack_patch_pkg() {
    local patch=$(basename $1)
    local pkg=$(basename $patch .patch)
    unpack_pkg $pkg
    patch_pkg $patch
}

unpack_patch_all() {
    for p in $patches_dir/*.patch; do
        if [ -d packages/$(basename $p .patch) ]; then
            echo "$(basename $p) is already extracted."
        else
            unpack_patch_pkg $p
        fi
    done
}

# Unpack the given package
unpack_pkg() {
    local pkg=$1
    pushd packages
    cabal unpack --pristine $pkg
    local pkg_dir="$(ls -d $pkg* | head)"
    if [ -z "$pkg_dir" ]; then
        echo "failed to unpack $pkg"
        exit 1
    fi
    cd $pkg_dir
    git init
    git add .
    git commit -q -m "Initial commit of $pkg"
    git tag upstream
    popd
    add_pkg_dirs packages/$pkg_dir
}

update_patches() {
    for p in packages/*; do
        local patch_path=$patches_dir/$(basename $p)
        git -C $p diff --ignore-cr-at-eol upstream > $patch_path.patch
        git -C $patches_dir add $patch_path.patch
        if [ -f $patch_path.cabal ]; then
            git -C $patches_dir rm $patch_path.cabal
        fi
    done
    git -C $patches_dir status .
}

build_all() {
    unpack_patch_all
    for p in packages/*; do
        split_pkg_version $(basename $p .patch)
        echo
        echo "=============================================================================="
        echo "Building $package..."
        echo "=============================================================================="
        cabal v2-build \
          --allow-newer=base,ghc,template-haskell,ghc-prim,containers,integer-gmp,ghc-bignum \
          $package
    done
}

mkdir -p packages

usage() {
    cat <<EOF
usage: $0 MODE ...

Modes:
  unpack-patch-all      unpack and patch all packages with patches
  unpack \$pkg           unpack the given package into packages/
  unpack-patch \$pkg     unpack and apply patches to the given package
  update-patches        update patches for all unpacked packages
  drop-old              drop patches for obsolete package versions
  build-all             build all patches with cabal v2-build
EOF
}

case "X$1" in
    Xunpack)
        unpack_pkg $2
        ;;

    Xunpack-patch-all)
        unpack_patch_all
        ;;

    Xunpack-patch)
        patch=$2
        unpack_pkg $(basename $patch .patch)
        patch_pkg $patch
        ;;

    Xupdate-patches)
        update_patches
        ;;

    Xdrop-old)
        drop_old
        ;;

    Xbuild-all)
        build_all
        ;;

    *)
        echo "unknown mode $1"
        usage
esac
