#!/bin/sh

quiet=no
verbose=no
dryrun=no
list=no

version=""          # forgiving user input version

hpVersion=""        # like "2014.2.0.0"
ghcVersion=""       # like "7.8.3"
arch=""             # "i386" or "x86_64"

hpDirname=""        # "2014.2.0.0-x86_64" or older form "ghc-7.6.3"
ghcDirname=""       # "7.8.3-x86_64"

hpInstallDir="/Library/Haskell"
ghcInstallDir="/Library/Frameworks/GHC.framework/Versions"

msg=""

findItems() {
    ( cd "$1" && ls -d -1 ${2:-*} ) 2>/dev/null
}

###
### Version and Arch Mechanics
###

extractVersions() {
    # takes a very liberal set of formats, and figures out the version info:
    #   ver         ghc version
    #   ver-arch    ghc version and arch, also dirname in GHC installs
    #                   and newer HP installs
    #   ghc-ver     dirnames used in older HP installs
    # in each case, ver can be x.y or x.y.z

    hpVersion=""
    ghcVersion=""
    arch=""
    msg=""

    input="${1#ghc-}"   # remove ghc- prefix if present
    case "$input" in
        *-*)    arch="${input#*-}"
                input="${input%-*}"
                ;;
    esac

    case "$input" in
        7.6.3)      ghcVersion="$input" ; hpVersion="2013.2.0.0" ;;
        7.4.2)      ghcVersion="$input" ; hpVersion="2012.4.0.0" ;;
        7.4.1)      ghcVersion="$input" ; hpVersion="2012.2.0.0" ;;
        7.0.4)      ghcVersion="$input" ; hpVersion="2011.4.0.0" ;;
        7.0.3)      ghcVersion="$input" ; hpVersion="2011.2.0.0" ;;
        [67].*)     ghcVersion="$input" ;;

        *)          msg="Can't parse $input as a GHC version"
                    return 1
                    ;;
    esac

    # see if there is an installed version w/bigger number
    for d in $( findItems "$ghcInstallDir" "$ghcVersion"'*' ) ; do
        ghcVersion="${d%-*}"   # last one will be highest!
        ghcArch="${d#*-}"
    done

    if [ -z "$arch" ] ; then
        arch="${ghcArch}"
    fi

    if [ -z "$arch" ] ; then
        msg="Can't determine architecture, please specify one (i386 or x86_64)"
        return 1
    fi

    ghcDirname="$ghcVersion-$arch"
    ghcRoot="$ghcInstallDir/$ghcDirname"

    if [ \! -d "$ghcRoot" ] ; then
        msg="GHC $ghcVersion $arch not found in $ghcInstallDir"
        return 1
    fi

    # see if there is an installed platform to match
    if [ -d "$hpInstallDir/ghc-$ghcVersion" ] ; then
        # old style platform install
        hpDirname="ghc-$ghcVersion"
        hpRoot="$hpInstallDir/$hpDirname"
        for r in $( findItems "$hpRoot/lib/registrations" 'haskell-platform-*.conf' ) ; do
            q="${r#haskell-platform-}"
            hpVersion="${q%.conf}"
        done
    elif [ -d "$hpInstallDir/ghc-$ghcVersion-$arch" ] ; then
        # new style platform install
        hpDirname="ghc-$ghcVersion-$arch"
        hpRoot="$hpInstallDir/$hpDirname"
        for v in $( findItems "$hpRoot" 'version-*' ) ; do
            hpVersion="${v#version-}"
        done
    else
        hpVersion="--none--"
        hpDirname=""
        hpRoot=""
    fi

    return 0
}


###
### Decode arguments
###

usage() {
    echo "$0 -l"
    echo "$0 [-q | -v] [-n] [ version [ arch ] ]"
    echo "    -l | --list       just list installed versions"
    echo "    -q | --quiet      supress informational output"
    echo "    -v | --verbose    print actions as they are taken"
    echo "    -n | --dryrun     don't actually do anything (implies -v)"
    echo ""
    echo "    version     ex. 7.8.3, defaults to latest"
    echo "    arch        i386 or x86_64, defaults to what's installed"
}

originalArgs="$*"

while [ $# -gt 0 ]
do
    case "$1" in
        -l|--list)      list=yes ;;
        -q|--quiet)     quiet=yes ; verbose=no ;;
        -v|--verbose)   quiet=no ; verbose=yes ;;
        -n|--dryrun)    dryrun=yes ;;
        -f|--force)     force=yes ;;
        -\?|--help)     usage ; exit 0 ;;
        -*)             echo "Unknown flag $1" ; usage ; exit 1 ;;
        *)  if [ -z "$version" ] ; then
                version="$1"
            elif [ -z "$arch" ] ; then
                arch="$1"
            else
                echo "Too many non-option arguments" ; usage ; exit 1
            fi
            ;;
    esac
    shift
done

if [ "$list" = "yes" ] ; then
    for version in $( findItems "$ghcInstallDir" ) ; do
        if extractVersions $version ; then
            if [ -n "$hpRoot" ] ; then
                echo "$ghcVersion-$arch - with platform $hpVersion"
                if [ "$verbose" = "yes" ] ; then
                    echo "    ghc      at $ghcRoot"
                    echo "    platform at $hpRoot"
                fi
            else
                echo "$ghcVersion $arch - no platform"
                if [ "$verbose" = "yes" ] ; then
                    echo "    ghc      at $ghcRoot"
                fi
            fi
        fi
    done
    exit 0
fi


###
### Validate haskell platform version
###

if [ -z "$version" ] ; then
    version=$( ( for d in $( findItems "$ghcInstallDir" ) ; \
                 do extractVersions "$d" && echo "$ghcVersion" ; \
                 done ) \
               | sort -t '.' -n | tail -1 )
fi

if extractVersions $version ; then
    ok="yes"
else
    if [ -z "$version" ] ; then
        echo "No GHC or Platform versions installed in /Library"
    else
        echo "$msg"
    fi
    exit 1
fi

if [ \! -d "$hpRoot" -a "$quiet" = "no" ] ; then
    echo "Warning: No corresponding Haskell Platform found"
fi



###
### Check for Xcode command line tools
###

if [ \! \( -f /usr/bin/gcc -a -x /usr/bin/gcc \) ] ; then
    echo "There is no compiler installed at /usr/bin/gcc"
    echo "Install Xcode's command line tools, or another compiler tool chain."
    exit 1
fi


###
### Root check
###


if [ "$dryrun" = "no" -a `id -u` -ne 0 ]
then
    echo "You must be root to activate a particular Haskell Platform."
    echo "Please rerun this command sudo:"
    echo "    sudo $0 $originalArgs"
    exit 1
fi


###
### Get ready to actually do stuff
###

if [ "$dryrun" = "yes" -a "$verbose" = "yes" ] ; then
    echo "Dry run mode enabled, would have run the following commands:"
fi

run() {
    if [ "$verbose" = "yes" ] ; then
        echo "    $*"
    fi
    if [ "$dryrun" = "no" ] ; then
        "$@"
    fi
}

symLink() {
    run rm -rf "$2"
    run ln -sf "$1" "$2"
}

symLinkInto() {
    run ln -sf "$@"
}

###
### Fix up bits in platform tree
###

if [ -d "$hpRoot" ] ; then
    if [ -x $hpRoot/bin/cabal.wrap ]
    then
        run mv $hpRoot/bin/cabal $hpRoot/bin/cabal.real
        symLink cabal.wrap $hpRoot/bin/cabal
    fi

    symLink $ghcRoot/usr/share/doc/ghc/html $hpRoot/doc/ghc-doc
    symLink $ghcRoot/usr/share/doc/ghc/html/libraries/ghc-$ghcVersion $hpRoot/doc/ghc-api
fi

###
### Repoint current links
###

symLink $ghcDirname /Library/Frameworks/GHC.framework/Versions/Current

if [ -d "$hpRoot" ] ; then
    symLink $hpDirname /Library/Haskell/current
    symLink current/bin /Library/Haskell/bin
    symLink current/doc /Library/Haskell/doc
fi

###
### Set up /usr
###

symLinkInto $ghcRoot/usr/bin/* /usr/bin
symLinkInto $ghcRoot/usr/share/man/man1/* /usr/share/man/man1
symLinkInto $ghcRoot/usr/share/doc/ghc /usr/share/doc

if [ -d "$hpRoot" ] ; then
    symLinkInto $hpRoot/bin/* /usr/bin
fi


###
### Patch settings file

extractSetting() {
    sed -n -e "/$1/"'s/.*, *"\(.*\)").*/\1/p' $settingsFile
}
updateSetting() {
    run sed -e "/$1/"'s/, *".*"/, "'"$2"'"/' -i '.bak' $settingsFile
}
determineCompiler() {
    case "$( "$1" --version 2>/dev/null )" in
        *clang*)    echo clang ;;
        *gcc*)      echo gcc ;;
        *cpphs*)    echo cpphs ;;
        *)          echo unknown ;;
    esac
}

settingsFile="$ghcRoot/usr/lib/ghc-$ghcVersion/settings"
if [ -f "$settingsFile" ] ; then
    if grep -q '"Haskell CPP flags"' "$settingsFile" ; then
        cmd=$( extractSetting "Haskell CPP command" )
        case "$( determineCompiler $cmd )" in
            clang)  cppArgs="-E -undef -traditional -Wno-invalid-pp-token -Wno-unicode -Wno-trigraphs" ;;
            gcc)    cppArgs="-E -undef -traditional" ;;
            hscpp)  cppArgs="--cpp -traditional" ;;
            *)  if [ "$quiet" = "no" ] ; then
                    echo "Unrecgonized Haskell CPP command $cmd"
                    echo "Leaving Haskell CPP flags unchanged in settings"
                fi
                ;;
        esac
        if [ -n "$cppArgs" ] ; then
            updateSetting "Haskell CPP flags" "$cppArgs"
        fi
    else
        cmd=$( extractSetting "C compiler command" )
        if [ "$( determineCompiler $cmd )" = "clang" ] ; then
            wrapperCmd=/usr/bin/ghc-clang-wrapper
            if [ -f "$wrapperCmd" -a -e "$wrapperCmd" ] ; then
                updateSetting "C compiler command" "$wrapperCmd"
            else
                echo "The compiler is clang, but $wrapperCmd isn't installed"
                echo "Older GHCs may not work unless this is installed"
                echo "Install $wrapperCmd and re-run this script"
            fi
        fi
    fi
else
    if [ "$( determineCompiler /usr/bin/gcc )" = "clang" ] ; then
        echo "The compiler is clang, but ghc $ghcVersion is old"
        echo "and has no settings file to change the compiler."
        echo "There is no simple work around."
    fi
fi


###
### Register platform packages
###

if [ -d "$hpRoot" ] ; then
    for conf in $hpRoot/lib/registrations/*
    do
        run /usr/bin/ghc-pkg register --verbose=0 --force $conf 2>/dev/null
    done
fi


###
### Report
###

if [ "$quiet" = "no" ] ; then
    if [ "$dryrun" = "no" ] ; then
        verb="now set to"
    else
        verb="would be set to"
    fi
    echo
    echo "Haskell $verb:"
    echo "    GHC      $ghcVersion"
    echo "    Arch.    $arch"
    echo "    Platform $hpVersion"

    if [ "$dryrun" = "no" ] ; then
        echo ""
        echo "View documentation with this command:"
        echo "    open /Library/Haskell/doc/start.html"
    fi
fi