#!/bin/bash
# Copyright (C) 2006 Nokia Corporation
#
# This is proprietary software owned by Nokia Corporation.
# 
# Contact: Maemo Integration <integration@maemo.org>

__self=`basename $0`

# Release configuration
__version=4.0
__release=public-alpha
__url=http://tablets-dev.nokia.com/unstable/chinook-alpha

# Target configurations
__target_toolchain=cs2005q3.2-glibc
__target_prefix=SDK_ALPHA
__rootstrap_prefix=maemo-sdk-rootstrap
__rootstrap_suffix=tgz

# Target configuration for armel
__armel_toolchain=${__target_toolchain}-arm
__armel_target=${__target_prefix}_ARMEL
__armel_devkits=perl:debian-sarge:maemo3-debian:maemo3-tools:cputransp
__armel_cputransp=qemu-arm-0.8.2-sb2

# Target configuration for i386
__i386_toolchain=${__target_toolchain}-i386
__i386_target=${__target_prefix}_X86
__i386_devkits=perl:debian-sarge:maemo3-debian:maemo3-tools:doctools

# Scratchbox 
__scratchbox_group=sbox
__scratchbox_require=1.0.7
__scratchbox_devkits=`echo "$__armel_devkits $__i386_devkits" | sed 's,:,\n,g' | sort -u`


# Shows usage text
function usage() {
	cat <<EOF
Usage: $__self [OPTIONS]

Installer for Maemo $__version '$__release' release.
Scratchbox installation is a precondition of running this script.

Creates '$__armel_target' and '$__i386_target' scratchbox targets, downloads
and installs the maemo $__version '$__release' rootstraps and installs system files
to these targets.

Options:
	-v	Display version and exit.
	-h	Show this usage guide.
	-c	Use existing downloads, don't try to download again.
	-y	Yes, force remove of existing targets.
	-p URI	Specify http_proxy for scratchbox user (default no proxy).
	-s PATH	Specify Scratchbox installation path (default /scratchbox).
	-q NAME	Specify Qemu version (default $__armel_cputransp).
	-a FILE	Specify alternative sources.list file for both targets.

EOF
}

# Show version information
function version() {
	echo "Installer for Maemo $__version '$__release' release."
}


# Fail in case str is empty and print out message
# empty (str,msg)
function empty () {
	local str=$1
	local message=$2
	if [ "x$str" = "x" ] ; then
		echo "E: Program error. $message"
		echo "E: If this problem persists please report to <integration@maemo.org>"
		exit 1
	fi
}

# Downloads file in url given with parameter
# download (outdir,url)
#	outdir must be non-empty
#	url must be non-empty
function download() {
	local outdir=$1
	local url=$2
	local file=`basename $url`
	empty "$outdir" "Download output directory string must be non-empty."
	empty "$url"  "Download URI string must be non-empty."
	# TODO Add timeout to file download
	wget -nd -P $outdir $url
	if [ $? != 0 ] ; then
		echo "E: Couldn't retrieve file '$file'."
		exit 1
	fi
	# TODO Check that downloaded file is readable from path
}

# Run gunzip archive integrity check on file
# test_integrity (file)
#	file must be non-empty
function test_integrity() {
	local file=$1
	empty "$file" "File to be checked must be non-empty."
	gunzip -qt $file 2>/dev/null
	return $?
}

# Taken from earlier maemo installer
# This function takes two version number strings as an argument.
# Return value is
#   0, if the version numbers are identical
#   1, if the first version number is greater
#   2, if the second version number is greater
# TODO Check that arguments lhs and rhs are in the form DIGIT.DIGIT.DIGIT
# compare_version_numbers (lhs,rhs)
function compare_version_numbers() {
	local lhs=$1
	local rhs=$2
	local c=1
	while [ 1 ]; do
		V1=`echo $lhs | cut -d . --fields $c`
		V2=`echo $rhs | cut -d . --fields $c`
		LEN1=`echo $V1 | wc -c`
		LEN2=`echo $V2 | wc -c`

		if [[ "$LEN1" -le "1" && "$LEN2" -le "1" ]]; then
			return 0
		fi
		if [[ "$LEN2" -le "1" || "$V1" -gt "$V2" ]]; then
			return 1
		fi
		if [[ "$LEN1" -le "1" || "$V1" -lt "$V2" ]]; then
			return 2
		fi
		c=$((c+1))
	done
}

# Scratchbox installation has minimum Qemu version
# TODO Add more cputransp sanity tests
# has_cputransp (scratchbox,cputransp)
# 	cputransp must be non-empty
function has_cputransp () {
	local scratchbox=$1
	local cputransp=$2
	empty "$cputransp" "Scratchbox CPU transparency string must be non-empty."
	if [ -x $scratchbox/devkits/cputransp/bin/$cputransp ] ; then
		return 0
	fi
	return 1
}

# Scratchbox installation has toolchain installed
# TODO Add more toolchain sanity tests
# has_toolchain (scratchbox,toolchain)
# 	toolchain must be non-empty
function has_toolchain() {
	local scratchbox=$1
	local toolchain=$2
	empty "$toolchain" "Scratchbox toolchain string must be non-empty."
	if [ -d $scratchbox/compilers/$toolchain ] ; then
		return 0
	fi
	return 1
}

# Scratchbox installation has devkit installed
# TODO Add more devkit sanity tests
# TODO Required devkit minimum version check
# has_devkit (scratchbox,devkit)
# 	devkit must be non-empty
function has_devkit() {
	local scratchbox=$1
	local devkit=$2
	empty "$devkit" "Scratchbox devkit string must be non-empty."
	if [ -d $scratchbox/devkits/$devkit ] ; then
		return 0
	fi
	return 1
}

# Scratchbox installation has target installed
# has_target (scratchbox,target)
# 	target must be non-empty
function has_target() {
	local scratchbox=$1
	local target=$2
	empty "$target" "Scratchbox target string must be non-empty."
	if [ -d $scratchbox/users/$USER/targets/$target ] ; then 
		return 0
	fi
	return 1
}


# Scratchbox installation has sessions running 
# has_sessions (scratchbox)
function has_sessions() {
	local scratchbox=$1
	if ! $scratchbox/tools/bin/sb-conf list --targets 2>&1 | grep -q "^sbrsh-conf: No current target$" 1>/dev/null; then
		if [ `$scratchbox/tools/bin/sb-conf list --sessions | wc -l` -gt 1 ] ; then
			return 0
		fi
	fi
	return 1
}

# Download a file from url to dest directory
# get_file (url, dest, cached, description)
function get_file () {
	local url=$1
	local dest=$2
	local cached=$3
	local description=$4
	local file=`basename $url`
	local destfile=$dest/$file
	empty "$dest" "Download output directory string must be non-empty."
	empty "$url"  "Download URI string must be non-empty."
	# Remove files downloaded earlier (default yes)
	if [ "x$cached" != "xyes" ] && [ -r $destfile ] ;  then
		rm -f $destfile
	fi 
	# Download file found from URI if not already found
	if [ ! -r  $destfile ] ; then
		echo "Downloading $description."
		download $dest $url
	else
		echo "Using previously downloaded $description."
	fi
}

# Set up Scratchbox target
# setup_target (scratchbox,target, rootstrap, toolchain, devkits, cputransp)
function setup_target() {
	local scratchbox=$1
	local target=$2
	local rootstrap=$3
	local toolchain=$4
	local devkits=$5
	local cputransp=$6
	empty "$target" "Scratchbox target name must be non-empty."
	empty "$rootstrap" "Scratchbox target rootstrap filename must be non-empty."
	empty "$toolchain" "Scratchbox target toolchain must be non-empty."
	echo "Setting up '$target' target."
	# Settings string
	local setup_string="--compiler=$toolchain"
	if [ "x$devkits" != "x" ] ; then
		setup_string="$setup_string --devkits=$devkits"
	fi
	if [ "x$cputransp" != "x" ] ; then
		setup_string="$setup_string --cputransp=$cputransp"
	fi
	# Setup
	$scratchbox/tools/bin/sb-conf setup $target --force $setup_string
	if [ $? != 0 ] ; then 
		echo "E: Scratchbox command 'sb-conf setup' returned error $?."
		exit 1
	fi
	# Reset
	$scratchbox/tools/bin/sb-conf reset -f $target
	if [ $? != 0 ] ; then
		echo "E: Scratchbox command 'sb-conf reset' returned error $?."
		exit 1
	fi
	# Select
	$scratchbox/tools/bin/sb-conf select $target
	if [ $? != 0 ] ; then 
		echo "E: Scratchbox command 'sb-conf select' returned error $?."
		exit 1
	fi
	# Rootstrap
	$scratchbox/tools/bin/sb-conf rootstrap $target $rootstrap
	if [ $? != 0 ] ; then 
		echo "E: Scratchbox command 'sb-conf rootstrap' returned error $?."
		exit 1
	fi
	# Install
	$scratchbox/tools/bin/sb-conf install $target --etc --devkits --fakeroot
	if [ $? != 0 ] ; then 
		echo "E: Scratchbox command 'sb-conf install' returned error $?."
		exit 1
	fi
	echo "Target '$target' has been created."
	echo
}


# Prints out an underlined text banner
# phase (text)
function phase () {
	local text=$1
	local underline=`echo $text | sed 's,.,-,g'`
	echo
	echo "$text"
	echo "$underline"
	echo
}

# Main
# TODO Add long command line options
while getopts "hvycp:s:q:a:" opt ; do
	case "$opt" in
		h)
			usage 
			exit 0
			;;
		v)
			version 
			exit 0
			;;
		y)
			__force=yes 
			;;
		p)
			__proxy=$OPTARG 
			;;
		s)
			__scratchbox=$OPTARG 
			;;
		q)
			__armel_cputransp=$OPTARG 
			;;
		a)
			__sources_list=$OPTARG 
			if [ "x$__sources_list" != "x" ] && [ ! -r $__sources_list ] ; then
				echo "E: Alternative sources.list '$__sources_list' file not found."
				echo "E: Please check that '-a FILE' option arg file exists."
				exit 1
			fi
			;;
		*)
			echo "W: Unknown option '$opt'"
			;;
	esac
done

# Runtime options
if [ -z $__scratchbox ] ; then
	__scratchbox=/scratchbox
fi
__sbhome=$__scratchbox/users/$USER/home/$USER 

# Set defaults
if [ -z $__force ] ; then
	__force=no
fi

if [ -z $__cached ] ; then
	__cached=yes
fi

# If no proxy is given use the same as the host environment
if [ -z $__proxy ] ; then
	__proxy=$http_proxy
fi


cat <<END
This script will install maemo SDK $__version '$__release' release to your computer.

Install options
Force remove of existing targets ($__force)
Alternative sources.list ('$__sources_list')

Target configuration for armel ($__armel_target)
compiler=$__armel_toolchain
devkits=$__armel_devkits
cputransp=$__armel_cputransp

Target configuration for i386 ($__i386_target)
compiler=$__i386_toolchain
devkits=$__i386_devkits
END

phase "Checking for prerequisites"

# TODO Check that all parameters are non-empty

# Check for wget
__wget=`which wget`
echo "Which wget tool in path..."
if [ -z $__wget ] || [ ! -x $__wget ] ; then
	echo "E: This script requires wget to download rootstraps and installer files."
	echo "E: On most Linux distributions this is provided by 'wget' package."
	exit 1
fi

# Check for running inside scratchbox
echo "Running outside of scratchbox..."
if [ -r /targets/links/scratchbox.config ] ; then
	echo "E: This script needs to be run outside of scratchbox."
	exit 1
fi

# Check not run as user root
echo "Script not run as user root..."
if [ $UID = 0 ] ; then
	echo "E: This script should NOT be run as user root."
	exit 1
fi


# Check for scratchbox installation
echo "Scratchbox installation found..."
if [ ! -d $__scratchbox  ] ; then
        echo "E: Scratchbox not found in installation path '$__scratchbox'."
        echo "E: Please complete scratchbox installation first."
        echo "E: Specify an alternative installation path using '-s PATH' option."
        exit 1
fi

# Check for scratchbox user
echo "Scratchbox home directory..."
if [ ! -d $__scratchbox/users/$USER ] ; then
        echo "E: Scratchbox directory for user not present."
        echo "E: Add user with '$__scratchbox/sbin/sbox_adduser $USER'."
        echo "E: Specify an alternative installation path using '-s PATH' option."
        exit 1
fi

# Check for sb-conf tool
echo "Scratchbox sb-conf tool..."
if [ ! -x $__scratchbox/tools/bin/sb-conf ] ; then
        echo "E: Scratchbox sb-conf tool not found in '$__scratchbox'."
        echo "E: This is most likely due to old version of scratchbox."
        echo "E: Please complete scratchbox installation first."
        echo "E: Specify an alternative installation path using '-s PATH' option."
        exit 1
fi

# Check for scratchbox bind mount
echo "Scratchbox users bind mount..."
if [ ! -d $__scratchbox/users/$USER/scratchbox ] ; then
        echo "E: Scratchbox bind mount for user not present."
        echo "E: Start scratchbox service with 'sudo $__scratchbox/sbin/sbox_ctl start'."
        echo "E: Specify an alternative installation path using '-s PATH' option."
        exit 1
fi

# Check for scratchbox properly set up
echo "Scratchbox users /dev set up..."
if [ ! -r $__scratchbox/users/$USER/dev/null ]; then
	echo "E: Scratchbox user's /dev is not properly set up. Couldn't read /dev/null."
        echo "E: Start scratchbox service with 'sudo $__scratchbox/sbin/sbox_ctl start'."
        echo "E: Specify an alternative installation path using '-s PATH' option."
	exit 1
fi

# Check for home directory inside scratchbox
echo "Scratchbox home directory..."
if [ ! -d $__sbhome ] ; then
        echo "E: Scratchbox home directory '$__sbhome' not found."
        echo "E: Add user with '$__scratchbox/sbin/sbox_adduser $USER'."
        echo "E: Specify an alternative installation path using '-s PATH' option."
        exit 1
fi

# Check for scratchbox login
echo "Scratchbox login..."
if [ ! -r $__scratchbox/login ] ; then
        echo "E: Scratchbox login not found in '$__scratchbox'."
        echo "E: Please complete scratchbox installation first."
        echo "E: Specify an alternative installation path using '-s PATH' option."
        exit 1
fi

# Check for scratchbox login
# User in sbox group
echo "Scratchbox login executable..."
if [ ! -x $__scratchbox/login ] ; then
        echo "E: Scratchbox login found but not executable by user."
	echo "E: Please check that user is member of the group specified in scratchbox"
	echo "E: installation (default '$__scratchbox_group')."
	echo "E: Also start a new login terminal after adding group membership."
        exit 1
fi

# Execute sb-conf tool
# TODO Check that version string is in the form DIGIT.DIGIT.DIGIT
echo "Scratchbox sb-conf tool execute..."
__scratchbox_version=`$__scratchbox/tools/bin/sb-conf version 2>/dev/null | tail -1`
if [ $? != 0 ]  ; then
        echo "E: Couldn't execute scratchbox utility sb-conf."
        echo "E: Please complete scratchbox installation first."
        echo "E: Specify an alternative installation path using '-s PATH' option."
	exit 1
fi

# Check for scratchbox version
echo "Scratchbox version requirement..."
compare_version_numbers $__scratchbox_require $__scratchbox_version 
if [ $? = 1 ] ; then
        echo "E: Scratchbox version is too old (scratchbox-core $__scratchbox_version)."
        echo "E: The minimum required scratchbox-core version is $__scratchbox_require."
        echo "E: Please refer to http://scratchbox.org/"
        echo "E: Specify an alternative installation path using '-s PATH' option."
        exit 1
fi

# Check for CPU transparency method
echo "Scratchbox CPU transparency method for ARMEL is present..."
if ! has_cputransp $__scratchbox $__armel_cputransp ; then
        echo "E: CPU transparency method '$__armel_cputransp' not found."
	echo "E: Please complete scratchbox installation first."
        echo "E: Specify an alternative CPU transparency method using '-q NAME' option."
        exit 1
fi

# Check that toolchains are found
echo "Scratchbox toolchains..."
if ! has_toolchain $__scratchbox $__armel_toolchain || ! has_toolchain $__scratchbox $__i386_toolchain ; then
	echo "E: Toolchain $__armel_toolchain required for '$__armel_target' target."
	echo "E: Toolchain $__i386_toolchain required for '$__i386_target' target."
	echo "E: Please complete scratchbox installation first."
        echo "E: Specify an alternative installation path using '-s PATH' option."
	exit 1
fi

# Check that devkits are found
echo "Scratchbox required devkits..."
for __devkit in $__scratchbox_devkits ; do 
	if ! has_devkit $__scratchbox $__devkit ; then
		echo "E: Scratchbox devkit $__devkit not found."
		echo "E: Please complete scratchbox installation first."
		echo "E: Specify an alternative installation path using '-s PATH' option."
		exit 1
	fi
done

# Acceptance
echo "Acceptance to reset existing targets..."
if [ "x$__force" != "xyes" ] && ( has_target $__scratchbox $__i386_target || has_target $__scratchbox $__armel_target ) ; then
	echo "W: Running this installer will remove existing targets $__i386_target and $__armel_target."
	echo "E: You need to force me with '-y' to reset existing targets."
	exit 1
fi

# Alternative sources.list
echo "Alternative sources.list file exists..."
if [ "x$__sources_list" != "x" ] && [ ! -r $__sources_list ] ; then
	echo "E: Alternative sources.list '$__sources_list' file not found."
	echo "E: Please check that '-a FILE' option arg file exists."
	exit 1
fi

# Check for scratchbox sessions running
echo "Check for scratchbox sessions running..."
if has_sessions $__scratchbox ; then
	echo "E: You must close your other scratchbox sessions first"
	echo "E: Specify an alternative installation path using '-s PATH' option."
	exit 1
fi

# Check that http_proxy URI is formed correctly
echo "Checking http_proxy URI format..."
if [ "x$__proxy" != "x" ] && [  `expr match "$__proxy" 'http://'` -ne 7 ] ; then
	echo "E: Proxy URI is malformed. Must start with 'http://'."
	echo "E: Specify an alternative http proxy URI using '-p URI' option."
	exit 1
fi

# Set http_proxy URI in .bashrc
if [ "x$__proxy" != "x"  ]  ; then
	if [ -r $__sbhome/.bashrc ] && grep -q "^export http_proxy=" $__sbhome/.bashrc ; then
		echo "Found http_proxy setting in scratchbox user's .bashrc , not set."
	else
		echo "export http_proxy=$__proxy" >>  $__sbhome/.bashrc
		echo "Added http_proxy setting to scratchbox user's .bashrc ."
	fi
fi


echo
echo "Everything seems ok."

phase "Downloading rootstraps and installer files"

# Download i386 and armel rootstraps, check for archive integrity
for __arch in armel i386 ; do
	get_file ${__url}/${__arch}/${__rootstrap_prefix}_${__version}alpha_${__arch}.${__rootstrap_suffix} $__sbhome $__cached "$__arch rootstrap"
	echo "Testing downloaded $__arch rootstrap integrity."
	if ! test_integrity ${__sbhome}/${__rootstrap_prefix}_${__version}alpha_${__arch}.${__rootstrap_suffix} ; then
		echo "E: Downloaded $__arch rootstrap file failed integrity test."
		exit 1
	fi
done


# Setup and create SDK_ARMEL targets
setup_target $__scratchbox \
	$__armel_target \
	${__rootstrap_prefix}_${__version}alpha_armel.${__rootstrap_suffix} \
	$__armel_toolchain \
	$__armel_devkits \
	$__armel_cputransp

# Setup and create SDK_X86 target
setup_target $__scratchbox \
	$__i386_target \
	${__rootstrap_prefix}_${__version}alpha_i386.${__rootstrap_suffix} \
	$__i386_toolchain \
	$__i386_devkits

# TODO Setup and create SDK_KERNEL target

# Replace sources.list on both targets
if [ "x$__sources_list" != "x" ] ; then
	for __update_target in $__armel_target $__i386_target ; do 
		echo "Replacing sources.list on '$__update_target' target."
		cp $__sources_list $__scratchbox/users/$USER/targets/$__update_target/etc/apt/sources.list
	done
fi


# Add proxy configuration to apt on both targets
if [ "x$__proxy" != "x"  ]  ; then
	for __update_target in $__armel_target $__i386_target ; do
		echo "Adding proxy configuration for apt in $__update_target..."
		echo "Acquire::http::Proxy \"$__proxy\";" >  $__scratchbox/users/$USER/targets/$__update_target/etc/apt/apt.conf.d/99proxy
	done
fi

phase "Update list of available packages"

# Run apt-get update on both targets
for __update_target in $__armel_target $__i386_target ; do 
	echo "Running apt-get update on '$__update_target'."
	$__scratchbox/tools/bin/sb-conf select $__update_target
	$__scratchbox/login apt-get -o Acquire::http::TimeOut=10 -o Acquire::http::Retries=2 update
	if [ $? != 0 ] ; then 
		echo "E: Please correct any network problems or your targets' /etc/apt/sources.list."
		echo "E: Run 'apt-get update' manually to complete installation."
		echo "E: Scratchbox login returned error $?."
		#exit 1
		__update_failed="true"
	fi
	echo
done

phase "Installation was successful!"

cat <<END
IMPORTANT! Please read this. 

You now have the maemo $__version '$__release' installed on your computer.
You can now start your maemo SDK session with $__scratchbox/login and
then select your target with 'sb-conf select $__armel_target' for armel
target or 'sb-conf select $__i386_target' for i386 target.

If you have any problems with targets' package databases, you can try
running 'fakeroot apt-get -f install' on your scratchbox target.
This command will try to fix any problems with the package database.

END

if [ "$__update_failed" = "true" ] ; then
cat << END
Even though the SDK was successfully installed, the installation process
was unable to run 'apt-get update' inside the installed targets in order 
to update the local package database. This could be due to a error in
network configuration inside scratchbox. A common cause is the 'hosts' line
in /scratchbox/etc/nsswitch.conf. Usually the following should be enough:
hosts:          files dns

Once the network connections inside scratchbox are working, you should run
'apt-get update' on both targets.
END
fi


echo "Happy hacking!"
echo
