#!/bin/bash
#
############################################################################
# lastbash - Console player for Last.fm
# Copyright (C) 2006  Costin Stroie <cstroie@users.sourceforge.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
############################################################################
#
# $Id: lastbash,v 1.101 2006/12/12 12:13:57 cstroie Exp $
#

# User settings
DEBUG="n"
HISTORY_SCROLL_DOWN="y"
HISTORY_FIELD_UNITS=(2 2 3)
REFRESH_INTERVAL="30"
PLAYER="auto" # $PLAYERS, auto
USE_PLAYER="y"
AUTO_PLAY="y"
HTTP_CLIENT="auto" # $HTTP_CLIENTS, builtin, auto
SAVE_NOWPLAYING="y"

# Program identification
PROG_NAME="lastbash"
PROG_TITLE="LastBASH"
PROG_DESC="Console player for Last.fm"
PROG_VER="0.3.1"
AUTHOR="Costin Stroie"
AUTHOR_EMAIL="cstroie@users.sourceforge.net"

# Last.fm web services settings
LASTFM_HOST="ws.audioscrobbler.com"
LASTFM_PORT="80"

# Some client settings
CLIENT_PLATFORM="linux"
CLIENT_VERSION="1.0"

# Files and directories
HOME_DIR="${HOME}/.lastbash"
DIST_CONF_FILE="/etc/lastbash.config"
CONF_FILE="${HOME_DIR}/config"
AUTH_FILE="${HOME_DIR}/auth"
PLAYER_PIPE="${HOME_DIR}/player_fifo"
PLAYLIST_FILE="${HOME_DIR}/playlist.m3u"
HTML_FILE="${HOME_DIR}/lastbash.html"
DEBUG_FILE="${HOME_DIR}/lastbash.debug"
PID_FILE="${HOME_DIR}/pid"
COMMAND_FILE="${HOME_DIR}/command"
COMMAND_FILE_LOCK="${HOME_DIR}/command.lock"
NOWPLAYING_FILE="${HOME_DIR}/now_playing"

# Internal stuff
PLAYERS="mplayer"
HTTP_CLIENTS="wget curl"
REQ_PROGRAMS="rm date tput md5sum dd chmod"
declare -a HTTP_HEADERS HTTP_RESPONSE
declare -a HISTORY_ARTIST HISTORY_ALBUM HISTORY_TRACK HISTORY_DURATION HISTORY_MIN_SEC HISTORY_ACTION
CR=$'\x0d'
LF=$'\x0a'
CTRL_D=$'\x04'
CTRL_L=$'\x0c'
CTRL_S=$'\x13'
NUMBER_REGEXP="+([0-9])"

# Set some shell options
shopt -s nocasematch

# Labels
LBL_ARTIST="Artist"
LBL_ALBUM="Album"
LBL_TRACK="Title"
LBL_LENGTH="Length"
LBL_ACTION="Action"

# Home directory and configuration files 
#-----------------------------------------------------------------------------
# Create the home directory if missing
[ ! -d "${HOME_DIR}" ] && mkdir -p "${HOME_DIR}"
# Load the settings if they exist
[ -f "${DIST_CONF_FILE}" ] && source "${DIST_CONF_FILE}"
[ -f "${CONF_FILE}" ] && source "${CONF_FILE}"
# Load the authentication settings if they exist
[ -f "${AUTH_FILE}" ] && source "${AUTH_FILE}"

# Function: Write one line to the debug file {{{1
#-----------------------------------------------------------------------------
function debug()
{
	# $1 - text to write
	[ "${DEBUG}" == "y" ] && echo "$1" >> "${DEBUG_FILE}"
}

# Function: Write the debug header {{{1
#-----------------------------------------------------------------------------
function debug_header()
{
	debug "--------------------------------------------------------"
	debug " Debug session started on `date`"
	debug "--------------------------------------------------------"
}

# Function: Write the debug footer {{{1
#-----------------------------------------------------------------------------
function debug_footer()
{
	debug "--------------------------------------------------------"
	debug " Debug session ended on `date`"
	debug "--------------------------------------------------------"
}

# Function: Set the xterm title {{{1
#-----------------------------------------------------------------------------
function set_xterm()
{
	[[ "${TERM}" =~ "^xterm" ]] && echo -ne "\e]0;$1\a"
}

# Function: Save the now_playing file {{{1
#-----------------------------------------------------------------------------
function save_nowplaying()
{
	[ "${SAVE_NOWPLAYING}" == "y" ] && echo -n "$1" > "${NOWPLAYING_FILE}"
}

# Function: Print usage summary {{{1
#-----------------------------------------------------------------------------
function url_encode()
{
	# $1 - string to encode

	# TODO This is a simple encoding: spaces are replaced with %20
	echo "${1// /%20}"
}

# Function: Sleep function (interrupted by keypress) {{{1
#-----------------------------------------------------------------------------
function sleep()
{
	# $1 - seconds to sleep

	read -n 1 -s -t "$1" SLEEP_KEY
}

# Function: Print program name and version {{{1
#-----------------------------------------------------------------------------
function prog_ver()
{
	echo "${PROG_NAME} v${PROG_VER}"
}

# Function: Initialization {{{1
#-----------------------------------------------------------------------------
function init()
{
	local P MP

	# Check the bash version
	if [ "${BASH_VERSINFO[0]}" -lt "3" ]
	then
		echo "Bash version 3 is needed."
		exit 1
	fi

	# Check some required programs
	debug "${FUNCNAME}: Checking for missing programs..."
	for P in ${REQ_PROGRAMS}
	do
		type -t "${P}" >/dev/null 2>&1 || MP="${MP}${P} "
	done

	if [ "${MP}" ]
	then
		debug "${FUNCNAME}: Missing programs: ${MP}"
		echo "The following required programs are missing: ${MP}"
		exit 1
	else
		debug "${FUNCNAME}: No missing programs"
	fi

	# Check a minimum terminal size
	T_LINES=$(tput lines)
	T_COLUMNS=$(tput cols)
	if [ "${T_LINES}" -lt 15 ] || [ "${T_COLUMNS}" -lt 40 ]
	then
		echo "Minimum terminal size is 40x15, your is ${T_COLUMNS}x${T_LINES}"
		debug "${FUNCNAME}: Terminal size insufficient: ${T_COLUMNS}x${T_LINES}"
		exit 1
	fi

	# Check the history fields proportions are right
	if [ "${HISTORY_FIELD_UNITS[0]}" == "0" ] || [ "${HISTORY_FIELD_UNITS[2]}" == "0" ]
	then
		# Restore the defaults
		HISTORY_FIELD_UNITS=(2 2 3)
	fi

	# Check and fix the refresh interval
	if [[ "${REFRESH_INTERVAL}" == ${NUMBER_REGEXP} ]]
	then
		if [ "${REFRESH_INTERVAL}" -lt "30" ]
		then
			debug "${FUNCNAME}: Refresh interval too short: ${REFRESH_INTERVAL}"
			REFRESH_INTERVAL="30"
		fi
	else
		debug "${FUNCNAME}: Refresh interval not numerical: ${REFRESH_INTERVAL}"
		REFRESH_INTERVAL="30"
	fi

	# Check for player
	if [ "${PLAYER}" == "auto" ]
	then
		PLAYER=""
		debug "${FUNCNAME}: Searching for available and supported players..."
		# Search for the first supported player
		for P in ${PLAYERS}
		do
			debug "${FUNCNAME}: Trying ${P}..."
			if type -t "${P}" >/dev/null 2>&1
			then
				debug "${FUNCNAME}: Player found: ${P}"
				PLAYER="${P}"
				break
			fi
		done
		# No player is present
		[ ! "${PLAYER}" ] && debug "${FUNCNAME}: no supported player found"
	else
		if type -t "${PLAYER}" >/dev/null 2>&1
		then
			debug "${FUNCNAME}: Player found: ${PLAYER}"
		else
			# The player is not present, switching to none
			debug "${FUNCNAME}: Player ${PLAYER} not found"
			PLAYER=""
		fi
	fi

	# Check for http client
	if [ "${HTTP_CLIENT}" == "builtin" ]
	then
		debug "${FUNCNAME}: Using the builtin HTTP client"
	elif [ "${HTTP_CLIENT}" == "auto" ]
	then
		HTTP_CLIENT=""
		debug "${FUNCNAME}: Searching for available and supported HTTP clients..."
		# Search for the first supported http client
		for P in ${HTTP_CLIENTS}
		do
			debug "${FUNCNAME}: Trying ${P}..."
			if type -t "${P}" >/dev/null 2>&1
			then
				debug "${FUNCNAME}: HTTP client found: ${P}"
				HTTP_CLIENT="${P}"
				break
			fi
		done
		# No HTTP client is present
		if [ ! "${HTTP_CLIENT}" ]
		then
			debug "${FUNCNAME}: No supported HTTP client found, using the builtin one"
			HTTP_CLIENT="builtin"
		fi
	else
		if type -t "${HTTP_CLIENT}" >/dev/null 2>&1
		then
			debug "${FUNCNAME}: HTTP client found: ${HTTP_CLIENT}"
		else
			# The HTTP client is not present, switching to builtin
			debug "${FUNCNAME}: HTTP client ${HTTP_CLIENT} not found"
			HTTP_CLIENT="builtin"
		fi
	fi
}

# Function: Quit {{{1
#-----------------------------------------------------------------------------
function quit()
{
	# $1 - exit code

	if [ "${FIRST_INSTANCE}" ]
	then
		# One last message
		tui_sbar "Quitting. Good bye."

		# Stop the player, if running
		player_quit

		# Restore the terminal
		term_exit

		# Eventually, save the settings
		#save_settings

		# Write the debug footer
		debug_footer
	fi
	exit $1
}

# Function: Save the settings {{{1
#-----------------------------------------------------------------------------
function save_settings()
{
	echo "# ${PROG_TITLE} settings" > "${CONF_FILE}"
	for DATA in LASTFM_SESSION LASTFM_BASEURL LASTFM_BASEPATH
	do
		echo "${DATA}=${!DATA}" >> "${CONF_FILE}"
	done
}

# Function: Save an m3u playlist {{{1
#-----------------------------------------------------------------------------
function save_m3u()
{
	# $1 - playlist file name
	# $2 - extinf duration
	# $3 - extinf name
	# $4 - file location / url

	# Keep the filename
	local FILE="$1"
	shift 1

	# Output the header
	echo "#EXTM3U" > "${FILE}"

	# Output the sets of 3 data: duration, name, location
	while [ "$1" ]
	do
		echo "#EXTINF:$1,$2" >> "${FILE}"
		echo "$3" >> "${FILE}"
		shift 3
	done
}

# Function: HTTP GET method {{{1
#-----------------------------------------------------------------------------
function http_get()
{
	# $1 - host
	# $2 - port
	# $3 - path

	local HTTP_CLIENT_FUNCTION RET
	local GET_PATH
	local LINE RSP="n" N=0
	local TMP_FILE="${HOME_DIR}/http.tmp"

	# Select the HTTP client function
	case "${HTTP_CLIENT}" in
		"wget")  HTTP_CLIENT_FUNCTION="http_get_wget" ;;
		"curl")  HTTP_CLIENT_FUNCTION="http_get_curl" ;;
		*)       HTTP_CLIENT_FUNCTION="http_get_builtin" ;;
	esac

	# Make sure the GET path starts with "/"
	GET_PATH="$3"
	[ "${GET_PATH:0:1}" != "/" ] && GET_PATH="/${GET_PATH}"

	# Log the request
	debug "${FUNCNAME}: Q> http://${1}:${2}${GET_PATH}"

	# Do the HTTP request
	if "${HTTP_CLIENT_FUNCTION}" "${TMP_FILE}" "$1" "$2" "${GET_PATH}"
	then
		# Append one (missing) EOL (and make sure the file exits)
		echo >> "${TMP_FILE}"

		# Clear the HTTP_* arrays
		HTTP_HEADERS=()
		HTTP_RESPONSE=()

		# Parse the result
		while read LINE
		do
			LINE="${LINE//$CR/}"
			if [ "${LINE}" == "" ]
			then
				N=0
				RSP="y"
			else
				if [ "${RSP}" == "n" ]
				then
					HTTP_HEADERS[$N]="${LINE}"
					debug "${FUNCNAME}: H> ${LINE}"
				else
					HTTP_RESPONSE[$N]="${LINE}"
					debug "${FUNCNAME}: R> ${LINE}"
				fi
			fi

			# Increment the index
			(( N++ ))
		done < "${TMP_FILE}"

		# Check the status
		if [[ "${HTTP_HEADERS[0]}" =~ "200" ]]
		then
			RET="0"
		else
			debug "${FUNCNAME}: HTTP GET status is not OK"
			RET="1"
		fi
	else
		debug "${FUNCNAME}: HTTP GET error"
		RET="2"
	fi

	# Remove the temporary file
	rm -f "${TMP_FILE}"

	# Return the status code
	return "${RET}"
}

# Function: HTTP GET method: wget implementation {{{1
#-----------------------------------------------------------------------------
function http_get_wget()
{
	# $1 - output file
	# $2 - host
	# $3 - port
	# $4 - path

	local RET

	# Send the request
	if wget --quiet \
		--tries="3" \
		--timeout="5" \
		--save-headers \
		--header="Connection: Close" \
		--user-agent="${PROG_TITLE}/${PROG_VER}" \
		--output-file="/dev/null" \
		--output-document="${1}" \
		"http://${2}:${3}${4}"
	then
		RET="0"
	else
		RET="1"
	fi

	# Return the status code
	return "${RET}"
}

# Function: HTTP GET method: curl implementation {{{1
#-----------------------------------------------------------------------------
function http_get_curl()
{
	# $1 - output file
	# $2 - host
	# $3 - port
	# $4 - path

	local RET

	# Send the request
	if curl --silent \
		--connect-timeout "5" \
		--max-time "8" \
		--retry "3" \
		--http1.0 \
		--user-agent "${PROG_TITLE}/${PROG_VER}" \
		--header "Connection: Close" \
		--dump-header - \
		--output - \
		--stderr "/dev/null" \
		"http://${2}:${3}${4}" > "${1}"
	then
		RET="0"
	else
		RET="1"
	fi

	# Return the status code
	return "${RET}"
}

# Function: HTTP GET method: builtin implementation {{{1
#-----------------------------------------------------------------------------
function http_get_builtin()
{
	# $1 - output file
	# $2 - host
	# $3 - port
	# $4 - path

	local REQUEST RET="0"

	# Construct the request
	REQUEST="GET $4 HTTP/1.0\r\nHost: $2:$3\r\nUser-Agent: ${PROG_TITLE}/${PROG_VER}\r\nConnection: Close\r\n\r\n"

	# Map the FD no.5 to tcp connection
	if exec 5<>/dev/tcp/$2/$3
	then
		# Send the request
		if echo -ne "${REQUEST}" >&5 2>/dev/null
		then
			# Save the response to the temporary file
			if dd <&5 >"${1}" 2>/dev/null
			then
				# Close the connection
				if exec 5>&-
				then
					RET="0"
				else
					RET="4"
				fi
			else
				RET="3"
			fi
		else
			RET="2"
		fi
	else
		RET="1"
	fi

	# Return the status code
	return "${RET}"
}

# Function: Connect to Last.fm and save a playlist {{{1
#-----------------------------------------------------------------------------
function lastfm_connect()
{
	# $1 - user
	# $2 - pass

	local N KEY VALUE RET

	# Do the http get
	if http_get "${LASTFM_HOST}" "${LASTFM_PORT}" "/radio/handshake.php?version=${CLIENT_VERSION}&platform=${CLIENT_PLATFORM}&username=$1&passwordmd5=$2"
	then
		# Do this only if ok
		unset LASTFM_SESSION LASTFM_STREAM LASTFM_BASEURL LASTFM_BASEPATH
		unset LASTFM_SUBSCRIBER LASTFM_BANNED LASTFM_INFO_MESSAGE

		# Parse the result
		for ((N=0; N<=${#HTTP_RESPONSE[@]}; N++))
		do
			KEY=${HTTP_RESPONSE[$N]%%=*}
			VALUE=${HTTP_RESPONSE[$N]#*=}
			case "${KEY}" in
				"session")       LASTFM_SESSION="${VALUE}" ;;
				"stream_url")    LASTFM_STREAM="${VALUE}" ;;
				"base_url")      LASTFM_BASEURL="${VALUE}" ;;
				"base_path")     LASTFM_BASEPATH="${VALUE}" ;;
				"subscriber")    LASTFM_SUBSCRIBER="${VALUE}" ;;
				"banned")        LASTFM_BANNED="${VALUE}" ;;
				"info_message")  LASTFM_INFO_MESSAGE="${VALUE}" ;;
			esac
		done

		# Check the returned data
		if [[ "${LASTFM_SESSION}" =~ "failed" ]]
		then
			RET="2"
			debug "${FUNCNAME}: Session failed"
		elif [ "${LASTFM_BANNED}" == "1" ]
		then
			RET="3"
			debug "${FUNCNAME}: Player banned"
		else
			RET="0"
			debug "${FUNCNAME}: Handshake successful"
			# Save the playlist
			save_m3u "${PLAYLIST_FILE}" "-1" "Last.fm stream" "${LASTFM_STREAM}"
		fi
	else
		RET="1"
		debug "${FUNCNAME}: Handshake failed"
	fi

	# Return the status code
	return "${RET}"
}

# Function: Retrieve Last.fm stream metadata {{{1
#-----------------------------------------------------------------------------
function lastfm_metadata()
{
	# $1 - session

	local N KEY VALUE
	local RET WS_ERROR

	# Do the http get
	if http_get "${LASTFM_BASEURL}" "${LASTFM_PORT}" "${LASTFM_BASEPATH}/np.php?session=$1"
	then
		# Do this only if ok
		unset META_ARTIST META_ARTIST_URL META_TRACK META_TRACK_URL META_ALBUM META_ALBUM_URL META_COVER
		unset META_DURATION META_STATION META_STREAMING META_DISCOVERY META_RADIOMODE META_RTP

		# Parse the result
		for ((N=0; N<=${#HTTP_RESPONSE[@]}; N++))
		do
			KEY=${HTTP_RESPONSE[$N]%%=*}
			VALUE=${HTTP_RESPONSE[$N]#*=}
			case "${KEY}" in
				"artist")             META_ARTIST="${VALUE}" ;;
				"artist_url")         META_ARTIST_URL="${VALUE}" ;;
				"track")              META_TRACK="${VALUE}" ;;
				"track_url")          META_TRACK_URL="${VALUE}" ;;
				"album")              META_ALBUM="${VALUE}" ;;
				"album_url")          META_ALBUM_URL="${VALUE}" ;;
				"albumcover_medium")  META_COVER="${VALUE}" ;;
				"trackduration")      META_DURATION="${VALUE}" ;;
				"station")            META_STATION="${VALUE}" ;;
				"streaming")          META_STREAMING="${VALUE}" ;;
				"discovery")          META_DISCOVERY="${VALUE}" ;;
				"radiomode")          META_RADIOMODE="${VALUE}" ;;
				"recordtoprofile")    META_RTP="${VALUE}" ;;
				"error")              WS_ERROR="${VALUE}" ;;
			esac
		done
		RET="0"
	else
		RET="1"
		debug "${FUNCNAME}: Metadata retrieving failed"
	fi

	# Return the status code
	return "${RET}"
}

# Function: Send a Last.fm command {{{1
#-----------------------------------------------------------------------------
function lastfm_command()
{
	# $1 - session
	# $2 - command

	local RET WS_RESPONSE WS_ERROR

	# Do the http get
	if http_get "${LASTFM_BASEURL}" "${LASTFM_PORT}" "${LASTFM_BASEPATH}/control.php?session=$1&command=$2"
	then
		# Parse the result
		for ((N=0; N<=${#HTTP_RESPONSE[@]}; N++))
		do
			KEY=${HTTP_RESPONSE[$N]%%=*}
			VALUE=${HTTP_RESPONSE[$N]#*=}
			case "${KEY}" in
				"response")  WS_RESPONSE="${VALUE}" ;;
				"error")     WS_ERROR="${VALUE}" ;;
			esac
		done

		# Check the returned data
		if [ "${WS_RESPONSE}" != "OK" ]
		then
			RET="2"
			debug "${FUNCNAME}: Command \"$2\" failed, unknown error"
		else
			RET="0"
			debug "${FUNCNAME}: Command \"$2\" sent successfully"
		fi
	else
		RET="1"
		debug "${FUNCNAME}: Command \"$2\" failed"
	fi

	# Return the status code
	return "${RET}"
}

# Function: Set a new Last.fm station {{{1
#-----------------------------------------------------------------------------
function lastfm_station()
{
	# $1 - session
	# $2 - url

	local URL=$(url_encode "$2")
	local WS_RESPONSE WS_ERROR
	local LASTFM_URL LASTFM_STATIONNAME
	local RET

	# Do the http get
	if http_get "${LASTFM_BASEURL}" "${LASTFM_PORT}" "${LASTFM_BASEPATH}/adjust.php?session=$1&url=${URL}"
	then
		# Parse the result
		for ((N=0; N<=${#HTTP_RESPONSE[@]}; N++))
		do
			KEY=${HTTP_RESPONSE[$N]%%=*}
			VALUE=${HTTP_RESPONSE[$N]#*=}
			case "${KEY}" in
				"response")     WS_RESPONSE="${VALUE}" ;;
				"error")        WS_ERROR="${VALUE}" ;;
				"url")          LASTFM_URL="${VALUE}" ;;
				"stationname")  LASTFM_STATIONNAME="${VALUE}" ;;
			esac
		done

		# Check the returned data
		if [ "${WS_RESPONSE}" != "OK" ]
		then
			if [[ "${WS_ERROR}" == ${NUMBER_REGEXP} ]]
			then
				debug "${FUNCNAME}: Error ${WS_ERROR}. Changing station failed"
				RET="${WS_ERROR}"
			else
				debug "${FUNCNAME}: Changing station failed for unknown error"
				RET="255"
			fi
		else
			RET="0"
			debug "${FUNCNAME}: Station changed"
		fi
	else
		RET="255"
		debug "${FUNCNAME}: Changing station failed"
	fi

	# Return the status code
	return "${RET}"
}

# Function: Ask username and password {{{1
#-----------------------------------------------------------------------------
function login()
{
	local INPUT DATA

	# Ask for the user name
	if [ ! "${LASTFM_USER}" ]
	then
		read -e -p "Username: " INPUT
		[ "${INPUT}" ] && LASTFM_USER="${INPUT}" || exit 1
	fi

	# Ask for the password
	if [ "${LASTFM_USER}" -a ! "${LASTFM_PASS}" ]
	then
		read -e -s -p "Password: " INPUT
		echo
		[ "${INPUT}" ] && LASTFM_PASS=`echo -n "${INPUT}" | md5sum | cut -f 1 -d " "` || exit 1
	fi

	# Save them, if ok
	if [ "${LASTFM_USER}" -a "${LASTFM_PASS}" ]
	then
		# Save them to the auth file
		echo "# ${PROG_TITLE} auth data" > "${AUTH_FILE}"
		for DATA in LASTFM_USER LASTFM_PASS
		do
			echo "${DATA}=${!DATA}" >> "${AUTH_FILE}"
		done
		chmod 600 "${AUTH_FILE}"
	fi
}


# Function: Initialize the terminal {{{1
#-----------------------------------------------------------------------------
function term_init()
{
	# Enter special mode
	tput smcup
	# Set a welcome xterm title
	set_xterm "Welcome to ${PROG_TITLE}!"

	# Define colors
	T_COLOR_BLACK="$(tput setaf 0)"
	T_COLOR_RED="$(tput setaf 1)"
	T_COLOR_GREEN="$(tput setaf 2)"
	T_COLOR_YELLOW="$(tput setaf 3)"
	T_COLOR_BLUE="$(tput setaf 4)"
	T_COLOR_MAGENTA="$(tput setaf 5)"
	T_COLOR_CYAN="$(tput setaf 6)"
	T_COLOR_WHITE="$(tput setaf 7)"
	T_BGCOLOR_BLACK="$(tput setab 0)"
	T_BGCOLOR_RED="$(tput setab 1)"
	T_BGCOLOR_GREEN="$(tput setab 2)"
	T_BGCOLOR_YELLOW="$(tput setab 3)"
	T_BGCOLOR_BLUE="$(tput setab 4)"
	T_BGCOLOR_MAGENTA="$(tput setab 5)"
	T_BGCOLOR_CYAN="$(tput setab 6)"
	T_BGCOLOR_WHITE="$(tput setab 7)"
	T_COLOR_NORMAL="$(tput op)"

	# Define attributes
	T_ATTR_BOLD="$(tput bold)"
	T_ATTR_REV="$(tput rev)"
	T_ATTR_BLINK="$(tput blink)"
	T_ATTR_INVIS="$(tput invis)"
	T_ATTR_ULINE="$(tput smul)"
	T_ATTR_NORMAL="\e[0m"

	# Some more escape sequences
	T_EL="$(tput el)"

	# Set the initial terminal size related settings
	term_resize
	# Make cursor invisible
	tput civis
}

# Function: Exit the terminal {{{1
#-----------------------------------------------------------------------------
function term_exit()
{
	# Set a goodbye xterm title
	set_xterm "Thank you for flying ${PROG_TITLE}!"
	# Make sure to reset the scrolling area
	tput csr 0 ${T_LINES}
	# Move the cursor on upper-left corner and clear the entire screen
	tput clear
	# Make cursor normal visible
	tput cnorm
	# Exit special mode
	tput rmcup
}

# Function: Compute some lengths when the terminal has been resized {{{1
#-----------------------------------------------------------------------------
function term_resize()
{
	# Find the screen size
	T_LINES=$(tput lines)
	T_COLUMNS=$(tput cols)
	# Find the maximum line and column
	T_LASTLINE=$(( T_LINES - 1 ))
	T_LASTCOLUMN=$(( T_COLUMNS - 1 ))

	# Scroll area related settings
	TSCROLL_FIRST=10
	TSCROLL_LAST=$(( T_LASTLINE - 2 ))
	TSCROLL_CURRENT=${TSCROLL_FIRST}

	# Set the history line field sizes
	#   The fixed-size fields are the following
	#    - the love/ban indicator: 1 char
	#    - the track duration: 6 chars
	#   Plus: 1 char at start and end of line
	#   Plus: 2 chars between fields (this is computed)
	# Find the total relative units
	local UNITS_TOTAL=$(( ${HISTORY_FIELD_UNITS[0]} + ${HISTORY_FIELD_UNITS[1]} + ${HISTORY_FIELD_UNITS[2]} ))
	# Find the variable line length
	local VAR_LINE=$(( ${T_COLUMNS} - 13 ))
	[ "${HISTORY_FIELD_UNITS[0]}" != "0" ] && VAR_LINE=$(( ${VAR_LINE} - 2 ))
	[ "${HISTORY_FIELD_UNITS[1]}" != "0" ] && VAR_LINE=$(( ${VAR_LINE} - 2 ))
	# Find the unit length
	local UNIT=$(( ${VAR_LINE} / ${UNITS_TOTAL} ))
	# Compute the sizes
	HISTORY_FIELD_SIZE[0]=$(( ${UNIT} * ${HISTORY_FIELD_UNITS[0]} ))
	HISTORY_FIELD_SIZE[1]=$(( ${UNIT} * ${HISTORY_FIELD_UNITS[1]} ))
	HISTORY_FIELD_SIZE[2]=$(( ${VAR_LINE} - ${HISTORY_FIELD_SIZE[0]} - ${HISTORY_FIELD_SIZE[1]} ))
	HISTORY_FIELD_SIZE[3]="6"

	# Compute the current max line length
	TCURRENT_MAX=$(( ${T_COLUMNS} - 13 ))
}

# Function: Display the Text User Interface {{{1
#-----------------------------------------------------------------------------
function tui_start()
{
	# Move the cursor on upper-left corner and clear the entire screen
	tput clear

	# Display the main parts
	tui_header
	tui_history_header
	tui_sbar
}

# Function: Display the header {{{1
#-----------------------------------------------------------------------------
function tui_header()
{
	local H_LINE S
	# Compute the field length
	S=$(( T_COLUMNS - 2 ))
	# Format the header line
	printf -v H_LINE " %${S}s " "${PROG_TITLE} v${PROG_VER}"
	# Move the cursor on upper-left corner and print
	tput home
	echo -ne "${T_COLOR_WHITE}${T_BGCOLOR_BLUE}${H_LINE}${T_COLOR_NORMAL}"
}

# Function: Display the history header {{{1
#-----------------------------------------------------------------------------
function tui_history_header()
{
	local FIELD LINE S

	# Start with the love/ban indicator
	LINE=" ${LBL_ARTIST:0:1}"

	# Add the artist, if the field units are not null
	if [ "${HISTORY_FIELD_UNITS[0]}" != "0" ]
	then
		S="${HISTORY_FIELD_SIZE[0]}"
		printf -v FIELD "  %-${S}s" "${LBL_ARTIST:0:$S}"
		LINE="${LINE}${FIELD}"
	fi

	# Add the album, if the field units are not null
	if [ "${HISTORY_FIELD_UNITS[1]}" != "0" ]
	then
		S="${HISTORY_FIELD_SIZE[1]}"
		printf -v FIELD "  %-${S}s" "${LBL_ALBUM:0:$S}"
		LINE="${LINE}${FIELD}"
	fi

	# Add the track title
	S="${HISTORY_FIELD_SIZE[2]}"
	printf -v FIELD "  %-${S}s" "${LBL_TRACK:0:$S}"
	LINE="${LINE}${FIELD}"

	# Add the track duration
	S="${HISTORY_FIELD_SIZE[3]}"
	printf -v FIELD "  %${S}s " "${LBL_LENGTH:0:$S}"
	LINE="${LINE}${FIELD}"

	# Move the cursor and print
	tput cup 8 0
	echo -ne "${T_COLOR_WHITE}${T_BGCOLOR_BLUE}${LINE}${T_COLOR_NORMAL}"
}

# Function: Display the status bar and control the timing {{{1
#-----------------------------------------------------------------------------
function tui_sbar()
{
	# $1 - the text to print
	# $2 - interval (seconds) before restoring default status bar

	if [ "$1" ]
	then
		# Display the status
		tui_statusbar "$1"
		if [ "$2" ]
		then
			# If timed, sleep and display the default status
			sleep "$2"
			tui_statusbar
		fi
	else
		# Display default status
		tui_statusbar
	fi
}

# Function: Display the status bar {{{1
#-----------------------------------------------------------------------------
function tui_statusbar()
{
	# $1 - the text to print

	local LINE L1 L2 L3 L4 S MSG
	# Set the leds
	#  - R: the player is running
	#  - P: record to profile is on
	#  - D: Discovery mode is on
	#  - B: Debug mode is on
	L1="-"
	[ "${META_RTP}" == "1" ] && L2="P" || L2="-"
	[ "${META_DISCOVERY}" == "1" ] && L3="D" || L3="-"
	[ "${DEBUG}" == "y" ] && L4="B" || L4="-"

	# Compute the main field size
	S=$(( T_COLUMNS - 27 ))

	# Set the message to display
	if [ "$1" ]
	then
		MSG="${1}"
	elif [ "${META_STREAMING}" == "true" ]
	then
		MSG="${META_STATION}"
	else
		MSG="Not streaming, please launch the player"
	fi

	# Format the status line
	printf -v LINE " %-${S}s  %-15s  [%c%c%c%c] " "${MSG:0:$S}" "${LASTFM_USER}" "${L1}" "${L2}" "${L3}" "${L4}"
	# Move the cursor on lower-left corner and print
	tput cup ${T_LINES} 0
	echo -ne "${T_COLOR_WHITE}${T_BGCOLOR_BLUE}${LINE}${T_COLOR_NORMAL}"
}

# Function: Display the keys {{{1
#-----------------------------------------------------------------------------
function tui_keys()
{
	local LINE
	printf -v LINE " \e[1;44m%1s\e[0;44m %s " "R" "Refresh" "L" "Love" "B" "Ban" "K" "Skip" "P" "RTP" "D" "Discovery" "Q" "Quit"

	# Move the cursor on lower-left corner and print
	tput cup ${T_LINES} 0
	echo -ne "${T_COLOR_WHITE}${T_BGCOLOR_BLUE} Keys: ${LINE}${T_EL}${T_COLOR_NORMAL}"
}

# Function: Display the current metadata {{{1
#-----------------------------------------------------------------------------
function tui_current()
{
	local LINE

	printf -v LINE "${T_ATTR_BOLD}%7s${T_ATTR_NORMAL} : %s" "${LBL_ARTIST}" "${META_ARTIST:0:$TCURRENT_MAX}"
	tput cup 2 2
	echo -ne "${LINE}${T_EL}"

	printf -v LINE "${T_ATTR_BOLD}%7s${T_ATTR_NORMAL} : %s" "${LBL_TRACK}" "${META_TRACK:0:$TCURRENT_MAX}"
	tput cup 3 2
	echo -ne "${LINE}${T_EL}"

	printf -v LINE "${T_ATTR_BOLD}%7s${T_ATTR_NORMAL} : %s" "${LBL_ALBUM}" "${META_ALBUM:0:$TCURRENT_MAX}"
	tput cup 4 2
	echo -ne "${LINE}${T_EL}"

	printf -v LINE "${T_ATTR_BOLD}%7s${T_ATTR_NORMAL} : %s" "${LBL_LENGTH}" "${META_MIN_SEC}"
	tput cup 5 2
	echo -ne "${LINE}${T_EL}"

	printf -v LINE "${T_ATTR_BOLD}%7s${T_ATTR_NORMAL} : %s" "${LBL_ACTION}" "${META_ACTION}"
	tput cup 6 2
	echo -ne "${LINE}${T_EL}"

	# Set the xterm title and save the now_playing file
	if [ "${META_STREAMING}" == "true" ]
	then
		set_xterm "${META_ARTIST} - ${META_TRACK}"
		save_nowplaying "${META_ARTIST} - ${META_TRACK}"
	else
		set_xterm "${PROG_TITLE}: Not streaming"
		save_nowplaying
	fi
}

# Function: Display the history area {{{1
#-----------------------------------------------------------------------------
function tui_history()
{
	# $1 - index of HISTORY array to display

	# Return if the index is invalid
	if [ "$1" -ge "${#HISTORY_ARTIST[*]}" ] || [ "$1" -lt 0 ]
	then
		return
	fi

	# Define the scroll area
	tput csr ${TSCROLL_FIRST} ${TSCROLL_LAST}

	if [ "${HISTORY_SCROLL_DOWN}" == "y" ]
	then
		# Move the cursor on the first line of the scrolling area
		tput cup ${TSCROLL_FIRST} 0
		# Scroll down one line
		tput il1
	else
		if [ ${TSCROLL_CURRENT} -gt ${TSCROLL_LAST} ]
		then
			# Move the cursor on the last line of the scrolling area
			tput cup ${TSCROLL_LAST} 0
			# Scroll up one line
			tput ind
		else
			# Move the cursor on the current line
			tput cup ${TSCROLL_CURRENT} 0
		fi
	fi

	# Display the history line
	tui_history_line "$1"

	# Increment the current line number
	(( TSCROLL_CURRENT++ ))

	# Restore the whole screen as scrolling area
	tput csr 0 ${T_LINES}
}

# Function: Re-display the whole history area {{{1
#-----------------------------------------------------------------------------
function tui_history_redisplay()
{
	local SCROLL_LINES="$(( TSCROLL_LAST - TSCROLL_FIRST + 1 ))"
	[ "${SCROLL_LINES}" -lt 0 ] && SCROLL_LINES="0"
	local HLAST="${#HISTORY_ARTIST[*]}"
	local HFIRST="$(( HLAST - SCROLL_LINES ))"
	[ "${HFIRST}" -lt 0 ] && HFIRST="0"
	local INDEX HINDEX SLINE

	# Define the scroll area
	tput csr ${TSCROLL_FIRST} ${TSCROLL_LAST}

	for (( INDEX=0; INDEX < SCROLL_LINES; INDEX++ ))
	do
		# Compute the index to display
		if [ "${HISTORY_SCROLL_DOWN}" == "y" ]
		then
			HINDEX="$(( HLAST - INDEX - 1 ))"
		else
			HINDEX="$(( HFIRST + INDEX ))"
		fi

		# Compute the scroll line number
		SLINE="$(( TSCROLL_FIRST + INDEX ))"

		# Check the index, move the cursor and print the line
		if [ "${HINDEX}" -lt "${HLAST}" ] && [ "${HINDEX}" -ge 0 ]
		then
			# Move the cursor on the determined line
			tput cup ${SLINE} 0
			# Display the history line
			tui_history_line "${HINDEX}"
		fi
	done

	# Compute the scroll line number
	TSCROLL_CURRENT="$(( TSCROLL_FIRST + HLAST ))"

	# Restore the whole screen as scrolling area
	tput csr 0 ${T_LINES}
}

# Function: Display the line in history area, using column positioning {{{1
#-----------------------------------------------------------------------------
function tui_history_line_col()
{
	# $1 - index in HISTORY array

	local FIELD LINE S C

	# Clear the line
	LINE="${T_EL}"

	# Move to column
	C="1"
	LINE="${LINE}$(tput hpa $C)"

	# Start with the love/ban indicator
	if [ "${HISTORY_ACTION[$1]}" ]
	then
		LINE="${LINE}${HISTORY_ACTION[$1]:0:1}"
	else
		LINE="${LINE} "
	fi

	# Add the artist, if the field units are not null
	if [ "${HISTORY_FIELD_UNITS[0]}" != "0" ]
	then
		C=$(( C + 3 ))
		S="${HISTORY_FIELD_SIZE[0]}"
		printf -v FIELD "%-${S}s" "${HISTORY_ARTIST[$1]:0:$S}"
		LINE="${LINE}$(tput hpa $C)${FIELD}"
	fi

	# Add the album, if the field units are not null
	if [ "${HISTORY_FIELD_UNITS[1]}" != "0" ]
	then
		C=$(( C + S + 2 ))
		S="${HISTORY_FIELD_SIZE[1]}"
		printf -v FIELD "%-${S}s" "${HISTORY_ALBUM[$1]:0:$S}"
		LINE="${LINE}$(tput hpa $C)${FIELD}"
	fi

	# Add the track title
	C=$(( C + S + 2 ))
	S="${HISTORY_FIELD_SIZE[2]}"
	printf -v FIELD "%-${S}s" "${HISTORY_TRACK[$1]:0:$S}"
	LINE="${LINE}$(tput hpa $C)${FIELD}"

	# Add the track duration
	C=$(( C + S + 2 ))
	printf -v FIELD "%6s " "${HISTORY_MIN_SEC[$1]}"
	LINE="${LINE}$(tput hpa $C)${FIELD}"

	# Output the line
	echo -n "${LINE}"
}

# Function: Display the line in history area {{{1
#-----------------------------------------------------------------------------
function tui_history_line()
{
	# $1 - index in HISTORY array

	local FIELD LINE S

	# Start with the love/ban indicator
	if [ "${HISTORY_ACTION[$1]}" ]
	then
		LINE=" ${HISTORY_ACTION[$1]:0:1}"
	else
		LINE="  "
	fi

	# Add the artist, if the field units are not null
	if [ "${HISTORY_FIELD_UNITS[0]}" != "0" ]
	then
		S="${HISTORY_FIELD_SIZE[0]}"
		printf -v FIELD "  %-${S}s" "${HISTORY_ARTIST[$1]:0:$S}"
		LINE="${LINE}${FIELD}"
	fi

	# Add the album, if the field units are not null
	if [ "${HISTORY_FIELD_UNITS[1]}" != "0" ]
	then
		S="${HISTORY_FIELD_SIZE[1]}"
		printf -v FIELD "  %-${S}s" "${HISTORY_ALBUM[$1]:0:$S}"
		LINE="${LINE}${FIELD}"
	fi

	# Add the track title
	S="${HISTORY_FIELD_SIZE[2]}"
	printf -v FIELD "  %-${S}s" "${HISTORY_TRACK[$1]:0:$S}"
	LINE="${LINE}${FIELD}"

	# Add the track duration
	printf -v FIELD "  %6s " "${HISTORY_MIN_SEC[$1]}"
	LINE="${LINE}${FIELD}"

	# Output the line
	echo -n "${LINE}"
}

# Function: Fetch and display the metadata {{{1
#-----------------------------------------------------------------------------
function tui_metadata()
{
	local RET HINDEX

	# Save the current metadata
	local PREV_ARTIST="${META_ARTIST}"
	local PREV_ALBUM="${META_ALBUM}"
	local PREV_TRACK="${META_TRACK}"
	local PREV_DURATION="${META_DURATION}"
	local PREV_MIN_SEC="${META_MIN_SEC}"
	local PREV_ACTION="${META_ACTION}"

	# Set the status line
	tui_sbar "Retrieving metadata..."

	# Fetch the metadata
	if lastfm_metadata "${LASTFM_SESSION}"
	then
		# And only if any metadata has changed
		if [ "${META_ARTIST}" != "${PREV_ARTIST}" ] || \
			[ "${META_ALBUM}" != "${PREV_ALBUM}" ] || \
			[ "${META_TRACK}" != "${PREV_TRACK}" ] || \
			[ "${META_DURATION}" != "${PREV_DURATION}" ]
		then
			# Clear the action
			unset META_ACTION META_ACTION_LOVE META_ACTION_SKIP META_ACTION_BAN

			# Compute the minutes and seconds only if streaming
			if [ "${META_STREAMING}" == "true" ]
			then
				printf -v META_MIN_SEC "%d:%02d" "$(( META_DURATION / 60 ))" "$(( META_DURATION % 60 ))"
			else
				unset META_MIN_SEC
			fi

			# Refresh the display
			tui_current

			# Add to history if there is anything to add
			if [ "${PREV_ARTIST}" ] || [ "${PREV_ALBUM}" ] || [ "${PREV_TRACK}" ]
			then
				# Save in history
				HINDEX="${#HISTORY_ARTIST[*]}"
				HISTORY_ARTIST[$HINDEX]="${PREV_ARTIST}"
				HISTORY_ALBUM[$HINDEX]="${PREV_ALBUM}"
				HISTORY_TRACK[$HINDEX]="${PREV_TRACK}"
				HISTORY_DURATION[$HINDEX]="${PREV_DURATION}"
				HISTORY_MIN_SEC[$HINDEX]="${PREV_MIN_SEC}"
				HISTORY_ACTION[$HINDEX]="${PREV_ACTION}"

				tui_history "${HINDEX}"
			fi
		fi
		RET="0"
	else
		RET="1"
		tui_sbar "Metadata retrieving failed"
	fi

	# Restore the status bar
	sleep 1
	tui_sbar

	# Return the status code
	return "${RET}"
}

# Function: TUI for connecting {{{1
#-----------------------------------------------------------------------------
function tui_lastfm_connect()
{
	local RET

	# Set the status line
	tui_sbar "Connecting to Last.fm..."

	# Try connecting
	lastfm_connect "${LASTFM_USER}" "${LASTFM_PASS}"
	RET="$?"

	# Check the error code
	if [ "${RET}" == "0" ]
	then
		# TODO Display the LASTFM_INFO_MESSAGE
		# Change the station, if required
		if [ "${LASTFM_STATION}" ]
		then
			tui_lastfm_station "${LASTFM_STATION}"
		else
			# Update the status line
			tui_sbar "Connected"
			sleep 1
			# Get the initial metadata
			tui_metadata
		fi
	elif [ "${RET}" == "2" ]
	then
		tui_sbar "Last.fm session failed, check credentials"
	elif [ "${RET}" == "3" ]
	then
		tui_sbar "Player banned, contact the author"
	else
		RET="1"
		tui_sbar "Last.fm handshake failed"
	fi

	# Return the status code
	return "${RET}"
}

# Function: TUI for changing station {{{1
#-----------------------------------------------------------------------------
function tui_lastfm_station()
{
	# $1 - station

	local RET ERRMSG

	# Set the status line
	tui_sbar "Setting new Last.fm station..."

	# Try changing the station
	lastfm_station "${LASTFM_SESSION}" "$1"
	RET="$?"

	# Check the error code
	if [ "${RET}" == "0" ]
	then
		# Update the status line
		tui_sbar "Station changed"
	else
		case "${RET}" in
			"1") ERRMSG="Not enough content to play this station" ;;
			"2") ERRMSG="The group has not enough members for radio" ;;
			"3") ERRMSG="The artist has not enough fans for radio" ;;
			"4") ERRMSG="The item is not available for streaming" ;;
			"5") ERRMSG="Feature available only to subscribers" ;;
			"6") ERRMSG="Not enough neighbours for this radio" ;;
			"7") ERRMSG="Streaming system is offline for maintenance" ;;
			*)   ERRMSG="Changing station failed for unknown error" ;;
		esac

		# Display the detailed error messages
		tui_sbar "${ERRMSG}"
	fi

	# Sleep one moment
	sleep 1
	#[ "${RET}" == "0" ] && tui_metadata

	# Return the status code
	return "${RET}"
}

# Function: TUI for Last.fm command {{{1
#-----------------------------------------------------------------------------
function tui_lastfm_command()
{
	# $1 - command

	local RET

	# Set the status line
	tui_sbar "Sending command: $1"

	# Try sending the command
	if lastfm_command "${LASTFM_SESSION}" "$1"
	then
		RET="0"
		# Update the status line
		tui_sbar "Command sent successfully"
	else
		RET="1"
		# Update the status line
		tui_sbar "Error sending command"
	fi

	# Return the status code
	return "${RET}"
}

# Function: TUI for Last.fm love command {{{1
#-----------------------------------------------------------------------------
function tui_lastfm_command_love()
{
	local RET

	# One love per track
	[ "${META_ACTION_LOVE}" ] && return 0

	# Try sending the command
	if tui_lastfm_command "love"
	then
		RET="0"
		META_ACTION_LOVE="y"
		META_ACTION="Loved"

		# Refresh the display
		tui_current
	else
		RET="1"
	fi

	# Sleep one moment and restore the status bar
	sleep 1
	tui_sbar

	# Return the status code
	return "${RET}"
}

# Function: TUI for Last.fm skip command {{{1
#-----------------------------------------------------------------------------
function tui_lastfm_command_skip()
{
	local RET

	# One skip per track
	[ "${META_ACTION_SKIP}" ] && return 0

	# Try sending the command
	if tui_lastfm_command "skip"
	then
		RET="0"
		META_ACTION_SKIP="y"
	else
		RET="1"
	fi

	# Sleep one moment and refresh the metadata
	sleep 1
	tui_metadata

	# Return the status code
	return "${RET}"
}

# Function: TUI for Last.fm ban command {{{1
#-----------------------------------------------------------------------------
function tui_lastfm_command_ban()
{
	local RET

	# One ban per track
	[ "${META_ACTION_BAN}" ] && return

	# Try sending the command
	if tui_lastfm_command "ban"
	then
		RET="0"
		META_ACTION_BAN="y"
		META_ACTION="Banned"

		# Refresh the display
		tui_current
	else
		RET="1"
	fi

	# Sleep one moment and refresh the metadata
	sleep 1
	tui_metadata

	# Return the status code
	return "${RET}"
}

# Function: TUI for toggling Record to Profile {{{1
#-----------------------------------------------------------------------------
function tui_lastfm_rtp()
{
	local RET="0"

	if [ "${META_RTP}" == "1" ]
	then
		tui_sbar "Disabling RTP..."
		lastfm_command "${LASTFM_SESSION}" "nortp" || RET="1"
	else
		tui_sbar "Enabling RTP..."
		lastfm_command "${LASTFM_SESSION}" "rtp" || RET="2"
	fi

	# Say something if action failed
	if [ "${RET}" != "0" ]
	then
		tui_sbar "Action failed"
	fi

	# Sleep one moment and refresh the metadata
	sleep 1
	tui_metadata

	# Return the status code
	return "${RET}"
}

# Function: TUI for toggling Discovery mode {{{1
#-----------------------------------------------------------------------------
function tui_lastfm_discovery()
{
	local RET="0"

	if [ "${META_DISCOVERY}" == "1" ]
	then
		tui_sbar "Disable discovery mode..."
		lastfm_station "${LASTFM_SESSION}" "lastfm://settings/discovery/off" || RET="1"
	else
		tui_sbar "Enable discovery mode..."
		lastfm_station "${LASTFM_SESSION}" "lastfm://settings/discovery/on" || RET="2"
	fi

	# Say something if action failed
	if [ "${RET}" != "0" ]
	then
		tui_sbar "Action failed"
	fi

	# Sleep one moment and refresh the metadata
	sleep 1
	tui_metadata

	# Return the status code
	return "${RET}"
}

# Function: TUI for toggling history scrolling direction {{{1
#-----------------------------------------------------------------------------
function tui_scroll_toggle()
{
	if [ "${HISTORY_SCROLL_DOWN}" == "y" ]
	then
		tui_sbar "History scroll upward"
		HISTORY_SCROLL_DOWN="n"
	else
		tui_sbar "History scroll downward"
		HISTORY_SCROLL_DOWN="y"
	fi

	# Redisplay the scrolling area
	tui_history_redisplay

	# Sleep one moment and restore the status bar
	sleep 1
	tui_sbar
}

# Function: TUI for toggling debug mode {{{1
#-----------------------------------------------------------------------------
function tui_debug_toggle()
{
	if [ "${DEBUG}" == "y" ]
	then
		tui_sbar "Debug mode disabled"
		debug_footer
		DEBUG="n"
	else
		tui_sbar "Debug mode enabled"
		debug_header
		DEBUG="y"
	fi

	# Sleep one moment and restore the status bar
	sleep 1
	tui_sbar
}

# Function: Send commands to the player {{{1
#-----------------------------------------------------------------------------
function player_command()
{
	# $1 - command

	[ "${USE_PLAYER}" == "y" ] || return

	if [ -p "${PLAYER_PIPE}" ]
	then
		echo "$1" > "${PLAYER_PIPE}"
		debug "${FUNCNAME} (${PLAYER}): $1"
	fi
}

# Function: Start the player {{{1
#-----------------------------------------------------------------------------
function player_start()
{
	# Return if no player is to be used
	[ "${USE_PLAYER}" != "y" ] && return

	if ! type -t mkfifo >/dev/null 2>&1
	then
		tui_sbar "No way to control the player, mkfifo is absent" "2"
		debug "${FUNCNAME}: mkfifo is absent"
		USE_PLAYER="n"
		return 1
	fi

	# Set the status line
	tui_sbar "Starting the backend player..."

	# Create the named pipe
	if [ -e "${PLAYER_PIPE}" ]
	then
		rm -f "${PLAYER_PIPE}"
	fi
	mkfifo -m 600 "${PLAYER_PIPE}"
	if [ -p "${PLAYER_PIPE}" ]
	then
		debug "${FUNCNAME}: Pipe created"
	else
		debug "${FUNCNAME}: Pipe not created"
		tui_sbar "Unable to create the player controlling pipe" "1"
		return 1
	fi

	# Start the player in background
	if [ "${PLAYER}" == "mplayer" ]
	then
		mplayer -cache 512 -idle -slave -quiet -input file="${PLAYER_PIPE}" >/dev/null 2>&1 &
		debug "${FUNCNAME}: Player ${PLAYER} started"
	elif [ "${PLAYER}" == "mpg123" ]
	then
		mpg123 --remote --remote-err --quiet <"${PLAYER_PIPE}" >/dev/null 2>&1 &
		debug "${FUNCNAME}: Player ${PLAYER} started"
	else
		debug "${FUNCNAME}: No player started"
		tui_sbar "No supported player found"
	fi

	# Wait a second
	sleep 1

	# Update the status line
	tui_sbar "Player started (${PLAYER})"

	[ "${AUTO_PLAY}" == "y" ] && player_load
}

# Function: Load the stream in player {{{1
#-----------------------------------------------------------------------------
function player_load()
{
	# Return if no player is to be used
	[ "${USE_PLAYER}" != "y" ] && return

	# Set the status line
	tui_sbar "Loading the stream..."

	# Load the stream and start playing
	if [ "${PLAYER}" == "mplayer" ]
	then
		player_command "loadfile ${LASTFM_STREAM}"
	elif [ "${PLAYER}" == "mpg123" ]
	then
		player_command "load ${LASTFM_STREAM}"
	else
		debug "${FUNCNAME}: No player running"
		tui_sbar "No supported player is running"
	fi

	# Wait a second
	sleep 1

	# Update the status line
	tui_sbar "Stream loaded and playing" "1"
}

# Function: Stop playing {{{1
#-----------------------------------------------------------------------------
function player_stop()
{
	[ "${USE_PLAYER}" != "y" ] && return

	# Set the status line
	tui_sbar "Stop playing the stream..."

	player_command "stop"

	# Restore the status bar
	sleep 1
	tui_sbar
}

# Function: Stop the player {{{1
#-----------------------------------------------------------------------------
function player_quit()
{
	[ "${USE_PLAYER}" != "y" ] && return

	# Set the status line
	tui_sbar "Closing the player..."

	if [ "${PLAYER}" == "mplayer" ]
	then
		player_command "quit"
	elif [ "${PLAYER}" == "mpg123" ]
	then
		killall mpg123
	else
		tui_sbar "No supported player is running"
		debug "${FUNCNAME}: No player running"
	fi

	# Wait a second
	sleep 1

	# Remove the FIFO
	rm -f "${PLAYER_PIPE}"

	# Set the status line
	tui_sbar "Player closed"
}

# Function: Check if another instance is running {{{1
#-----------------------------------------------------------------------------
function first_instance()
{
	local RET PID

	if [ -f "${PID_FILE}" ]
	then
		PID="$(< $PID_FILE)"
		# Check if we can send a signal to that process
		if kill -0 "${PID}" >/dev/null 2>&1
		then
			RET="1"
		else
			RET="0"
			rm -f "${PID_FILE}"
		fi
	else
		RET="0"
	fi

	return "${RET}"
}

# Function: Send commands to the running instance {{{1
#-----------------------------------------------------------------------------
function remote_call()
{
	# $@ - the commands

	local PID="$$"

	# Check and create the lock
	if ( set -o noclobber; echo "${PID}" > "${COMMAND_FILE_LOCK}" ) 2>/dev/null
	then
		# Create the lock
		echo "$$" > "${COMMAND_FILE_LOCK}"
		# Send the commands
		echo "$@" >> "${COMMAND_FILE}"
		# Remove the lock
		rm -f "${COMMAND_FILE_LOCK}"
		# Signal the first instance
		kill -USR1 "$(< $PID_FILE)"
	else
		echo "Lockfile present, commands not sent"
	fi
}

# Function: Execute arbitrary commands {{{1
#-----------------------------------------------------------------------------
function execute_command()
{
	# $1 - command
	# $2 - arguments (optional)

	case "$1" in
		"REFRESH")   tui_metadata ;;
		"STATION")   tui_lastfm_station "$2" ;;
		"SKIP")      tui_lastfm_command_skip ;;
		"BAN")       tui_lastfm_command_ban ;;
		"LOVE")      tui_lastfm_command_love ;;
		"RTP")       tui_lastfm_rtp ;;
		"DISCOVERY") tui_lastfm_discovery ;;
		"WINCH")     sigwinch ;;
		"DEBUG")     tui_debug_toggle ;;
		# Quit
		# FIXME Terminal left in wrong status
		"QUIT")     quit 0 ;;
		# Any other command
		*)          tui_sbar "Command not understood: $1" "1" ;;
	esac
}

# Function: Receive the USR1 signal {{{1
#-----------------------------------------------------------------------------
function sigusr1()
{
	local CMD ARGS PID="$$"

	# Check and create the lock
	if ( set -o noclobber; echo "${PID}" > "${COMMAND_FILE_LOCK}" ) 2>/dev/null
	then
		# Check the command file exists
		if [ -f "${COMMAND_FILE}" ]
		then
			# Read the commands, one per line, and execute them
			while read CMD ARGS
			do
				[ "${CMD}" ] && execute_command "${CMD}" "${ARGS}"
			done < "${COMMAND_FILE}"

			# Remove the command file
			rm -f "${COMMAND_FILE}"
		else
			# There is no command file
			tui_sbar "No commands to execute"
		fi

		# Remove the lock
		rm -f "${COMMAND_FILE_LOCK}"
	else
		tui_sbar "Lockfile present, remote call ignored"
	fi

	# Restore the status bar
	sleep 1
	tui_sbar
}

# Function: Receive the WINCH signal {{{1
#-----------------------------------------------------------------------------
function sigwinch()
{
	# Recompute the terminal size related parameters
	term_resize

	# Redisplay the TUI
	tui_start
	tui_current
	tui_history_redisplay
}

# Function: Receive the INT and TERM signals {{{1
#-----------------------------------------------------------------------------
function sigint()
{
	quit 0
}

# Function: Receive the EXIT signal {{{1
#-----------------------------------------------------------------------------
function sigexit()
{
	if [ "${FIRST_INSTANCE}" ]
	then
		# Remove stale files
		rm -f "${PID_FILE}" "${PLAYER_PIPE}" "${COMMAND_FILE}" "${COMMAND_FILE_LOCK}"
	fi
}


# Function: Receive the TSTP signal {{{1
#-----------------------------------------------------------------------------
function sigtstp()
{
	tui_sbar "Cannot suspend" "1"
}

# Function: The read key loop {{{1
#-----------------------------------------------------------------------------
function read_key_loop()
{
	local KEY DONE
	while [ ! "${DONE}" ]
	do
		read -n 1 -s -t "${REFRESH_INTERVAL}" KEY
		case "${KEY}" in
			"r") tui_metadata ;;
			"l") tui_lastfm_command_love ;;
			"b") tui_lastfm_command_ban ;;
			"k") tui_lastfm_command_skip ;;
			"p") tui_lastfm_rtp ;;
			"d") tui_lastfm_discovery ;;
			"${CTRL_S}") tui_scroll_toggle ;;
			# Redisplay the TUI
			"${CTRL_L}") sigwinch ;;
			# Toggle debug mode
			"${CTRL_D}") tui_debug_toggle ;;
			# Player commands
			"x") player_load ;;
			"X") player_start ;;
			"v") player_stop ;;
			"V") player_quit ;;
			"m") player_mute ;;
			# Quit
			"q") DONE="y" ;;
			# Any key or timeout
			*)
				if [ "${KEY}" ]
				then
					# FIXME this gets executed after a remote call, too
					tui_keys
					sleep 3
					tui_sbar
				else
					tui_metadata
				fi
				;;
		esac
		# Reset the key
		unset KEY
	done
}

# Function: Print usage summary {{{1
#-----------------------------------------------------------------------------
function usage()
{
	prog_ver
	echo
	echo "Usage: ${0##*/} [options] [station]"
	echo
	echo "Options:"
	echo "  -a,-A           Enable/Disable the autoplay feature"
	echo "  -d              Enable the debug mode"
	echo "  -g HTTP_client  Set the HTTP client to use for the webservice"
	echo "  -p player       Set the backend player"
	echo "  -r refresh      Set the metadata refresh interval"
	echo "  -u username     Set new username to login with and clear the password"
	echo "  -c command      Set the command to send to the running instance"
	echo "  -v              Show program name and version"
	echo "  -h              Show this quick help"
	echo
	echo "HTTP clients: ${HTTP_CLIENTS} builtin auto"
	echo "Players: ${PLAYERS} auto"
	echo
	echo "Station: any valid Last.fm station, such as the following:"
	echo "  lastfm://globaltags/rock"
	echo "  lastfm://user/cstroie/neighbours"
	echo "  lastfm://group/LastBASH"
}
# }}}1

# The main part
#-----------------------------------------------------------------------------

# Check commandline options
while getopts ":c:g:p:r:u:aAdvh" OPTION
do
	case "${OPTION}" in
		"a")	AUTO_PLAY="y" ;;
		"A")	AUTO_PLAY="n" ;;
		"c")	LASTFM_COMMAND="${OPTARG}" ;;
		"d")	DEBUG="y" ;;
		"g")	HTTP_CLIENT="${OPTARG}" ;;
		"p")	PLAYER="${OPTARG}" ;;
		"r")	REFRESH_INTERVAL="${OPTARG}" ;;
		"u")	LASTFM_USER="${OPTARG}"; LASTFM_PASS="" ;;
		"v")	prog_ver && exit 0 ;;
		"h")	usage && exit 0 ;;
		"?")	echo "Unexpected argument: \"${OPTARG}\""; exit 1 ;;
		":")	echo "Required argument not found for \"${OPTARG}\""; exit 1 ;;
	esac
done

# Some quick helps
if [ "${HTTP_CLIENT}" == "help" ]
then
	echo "${HTTP_CLIENTS} builtin auto"
	exit 0
elif [ "${PLAYER}" == "help" ]
then
	echo "${PLAYERS} auto"
	exit 0
fi

# Write the debug header
debug_header

# Set a parameter, if this is the first instance
first_instance && FIRST_INSTANCE="y"

# This should be the Last.fm station
if [[ "${!OPTIND}" =~ "^lastfm://" ]]
then
	# This is the new station to tune into
	if [ "${FIRST_INSTANCE}" ]
	then
		# Store the station to load after the init
		LASTFM_STATION="${!OPTIND}"
	else
		# Another instance is already running
		remote_call "STATION ${!OPTIND}"
		exit 0
	fi
elif [ "${!OPTIND}" ]
then
	# The string is unexpected
	usage
	exit 1
fi

# Check if this is not the first instance
if [ ! "${FIRST_INSTANCE}" ]
then
	# Check if there is a command to execute
	if [ "${LASTFM_COMMAND}" ]
	then
		remote_call "${LASTFM_COMMAND}"
		exit 0
	else
		# Nothing to do
		echo "There is another program instance running (PID: $(<${PID_FILE}))"
		exit 1
	fi
fi

# Let's initialize
init

# Ask user name and password, if needed
login

# Now, let's save the PID
echo -n $$ > "${PID_FILE}"
# And trap the USR1 signal
trap sigusr1 USR1

# Trap the WINCH signal, too
trap sigwinch WINCH

# Initialize the terminal
term_init

# Start the Text User Interface
tui_start
tui_current

# Trap INT, TERM, HUP and EXIT signals
trap sigint INT TERM HUP
trap sigexit EXIT

# Trap TSTP (suspend)
trap sigtstp TSTP

# Next, connect to the Last.fm station
if tui_lastfm_connect
then
	# Start the player
	player_start
	# Enter the readkey loop
	read_key_loop
else
	# Wait for the user read the status and exit
	read -n 1 -s -t ${REFRESH_INTERVAL} JUNK
fi

# Quit
quit 0

# vim: set ft=sh nowrap nu foldmethod=marker:
