#!/bin/bash
#
############################################################################
# lastbash - Console player for Last.fm
# Copyright (C) 2006-2008  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; version 2 of the License.
#
# 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$
#

# 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"
SAVE_HTML="y"
SAVE_HISTORY_CSV="y"

# Program identification
PROG_NAME="lastbash"
PROG_TITLE="LastBASH"
PROG_DESC=$"Console player for Last.fm"
PROG_VER="0.3.3"
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"
HISTORY_CSV_FILE="${HOME_DIR}/history.csv"

# Internal stuff
PLAYERS="mplayer"
HTTP_CLIENTS="wget curl nc netcat"
REQ_PROGRAMS="rm date tput stty md5sum dd chmod"
HTML_TEMPLATE="${HOME_DIR}/lastbash_template.html"
PLAYER_PID=""
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_H=$'\x08'
CTRL_L=$'\x0c'
CTRL_S=$'\x13'
CTRL_V=$'\x16'
CTRL_X=$'\x18'
ARROW_UP="$(tput kcuu1)"
ARROW_DOWN="$(tput kcud1)"
ARROW_LEFT="$(tput kcub1)"
ARROW_RIGHT="$(tput kcuf1)"
NUMBER_REGEXP="+([0-9])"

# Set some shell options
shopt -s nocasematch

# Localization stuff
TEXTDOMAIN="lastbash"
TEXTDOMAINDIR="messages"

# 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()
{
	local MSG
	printf -v MSG " %s `date`" $"Debug session started on"
	debug "--------------------------------------------------------"
	debug "${MSG}"
	debug "--------------------------------------------------------"
}

# Function: Write the debug footer {{{1
#-----------------------------------------------------------------------------
function debug_footer()
{
	local MSG
	printf -v MSG " %s `date`" $"Debug session ended on"
	debug "--------------------------------------------------------"
	debug "${MSG}"
	debug "--------------------------------------------------------"
}

# Function: Set the terminal title {{{1
#-----------------------------------------------------------------------------
function set_term_title()
{
	# $1 - new terminal title

	if [ "${TERM:0:5}" == "xterm" ] || [ "${TERM:0:4}" == "rxvt" ]
	then
		# Set the title for xterm and rxvt
		echo -ne "\e]0;$1\a"
	elif [ "${TERM:0:6}" == "screen" ]
	then
		# Set the window title
		echo -ne "\ek$1\e\\"
		# Set the window hardstatus
		echo -ne "\e]0;$1\a"
	fi
}

# Function: Save the now_playing file {{{1
#-----------------------------------------------------------------------------
function save_nowplaying()
{
	# $1 - string to save in the now_playing file

	[ "${SAVE_NOWPLAYING}" == "y" ] && echo -n "$1" > "${NOWPLAYING_FILE}"
}

# Function: Save the HTML file {{{1
#-----------------------------------------------------------------------------
function save_html()
{
	# Return if we should not save the html file or the template is missing
	if [ "${SAVE_HTML}" != "y" ] || [ ! -f "${HTML_TEMPLATE}" ]
	then
		return
	fi

	local RET

	# Replace the placeholders in template
	# Placeholders:
	#   @TITLE@ @COVER_URL@ @ARTIST_URL@ @ARTIST_NAME@ @TRACK_URL@ @LENGTH@
	#   @TRACK_NAME@ @ALBUM_URL@ @ALBUM_NAME@ @STATION_URL@ @STATION_NAME@

	local COVER_URL ARTIST_URL ARTIST_NAME TRACK_URL LENGTH TRACK_NAME ALBUM_URL ALBUM_NAME STATION_URL STATION_NAME

	# Some safety checks
	[ "${META_COVER}" ] && COVER_URL="${META_COVER}" || COVER_URL="http://static.last.fm/depth/catalogue/noimage/cover_large.gif"

	ARTIST_NAME="${META_ARTIST//&/\&}"
	TRACK_NAME="${META_TRACK//&/\&}"
	ALBUM_NAME="${META_ALBUM//&/\&}"
	STATION_NAME="${META_STATION//&/\&}"

	[ "${META_ARTIST_URL}" ] && ARTIST_URL="${META_ARTIST_URL}" || ARTIST_URL="#"
	[ "${META_TRACK_URL}" ] && TRACK_URL="${META_TRACK_URL}" || ARTIST_URL="#"
	[ "${META_ALBUM_URL}" ] && ALBUM_URL="${META_ALBUM_URL}" || ARTIST_URL="#"
	[ "${META_STATION_URL}" ] && STATION_URL="${META_STATION_URL}" || ARTIST_URL="#"

	sed -e "/<?lastbash [^? \t]\+?>/s|<?lastbash TITLE?>|${ARTIST_NAME} - ${TRACK_NAME}|g;
	s|<?lastbash COVER_URL?>|${COVER_URL}|g;
	s|<?lastbash ARTIST_URL?>|${ARTIST_URL}|g;
	s|<?lastbash ARTIST_NAME?>|${ARTIST_NAME}|g;
	s|<?lastbash TRACK_URL?>|${TRACK_URL}|g;
	s|<?lastbash TRACK_NAME?>|${TRACK_NAME}|g;
	s|<?lastbash ALBUM_URL?>|${ALBUM_URL}|g;
	s|<?lastbash ALBUM_NAME?>|${ALBUM_NAME}|g;
	s|<?lastbash STATION_URL?>|${STATION_URL}|g;
	s|<?lastbash STATION_NAME?>|${STATION_NAME}|g;
	s|<?lastbash LENGTH?>|${META_MIN_SEC}|g" < "${HTML_TEMPLATE}" > "${HTML_FILE}" &2>/dev/null

	# Get the status code
	RET=$?
	return "${RET}"
}

# Function: Save the history in a CSV formatted file {{{1
#-----------------------------------------------------------------------------
function save_history_csv()
{
	# $1 - index of HISTORY array to display

	# Return if we should not save the history
	[ "${SAVE_HISTORY_CSV}" == "y" ] || return

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

	# The first row contains the columns titles
	[ -f "${HISTORY_CSV_FILE}" ] || echo "\"${LBL_ARTIST}\",\"${LBL_ALBUM}\",\"${LBL_TRACK}\",\"${LBL_LENGTH}\",\"${LBL_ACTION}\"" > "${HISTORY_CSV_FILE}"

	local ARTIST ALBUM TRACK DURATION ACTION

	# Safety stuff
	ARTIST="${HISTORY_ARTIST[$1]//\"/\"\"}"
	ALBUM="${HISTORY_ALBUM[$1]//\"/\"\"}"
	TRACK="${HISTORY_TRACK[$1]//\"/\"\"}"
	DURATION="${HISTORY_DURATION[$1]//\"/\"\"}"
	ACTION="${HISTORY_ACTION[$1]//\"/\"\"}"

	# Save the history items
	echo "\"${ARTIST}\",\"${ALBUM}\",\"${TRACK}\",\"${DURATION}\",\"${ACTION}\"" >> "${HISTORY_CSV_FILE}"
}

# Function: Run an external command on track change {{{1
#-----------------------------------------------------------------------------
function run_on_track_change()
{
	# TODO Add generic support for notification
	type -t notify-send >/dev/null 2>&1 && notify-send -t 2000 -i gtk-dialog-info "${PROG_TITLE}" "${META_ARTIST} - ${META_TRACK}" 2>/dev/null 1>/dev/null
}

# 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_int()
{
	# $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 MSG

	# Check the bash version
	if [ "${BASH_VERSINFO[0]}" -lt "3" ]
	then
		echo $"Bash version 3 is needed."
		printf -v MSG "%s: %s: %s" "${FUNCNAME}" $"Unsupported Bash version" "${BASH_VERSION}"
		debug "${MSG}"
		exit 1
	fi

	# Note the bash version and the machine type
	debug "${FUNCNAME}: Using Bash $BASH_VERSION on $MACHTYPE"

	# 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

	# TODO Check some needed terminal capabilities
	if tput longname >/dev/null 2>&1
	then
		local TERM_LONGNAME="$(tput longname 2>/dev/null)"
		debug "${FUNCNAME}: Using $(tput longname 2>/dev/null)"
	else
		debug "${FUNCNAME}: Unknown terminal: ${TERM}"
		echo "Unknown terminal: ${TERM}"
		exit 1
	fi

	# Ask for 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

	# Compute some string sizes and padding
	local L LL
	LBL_MAXLENGTH="0"
	for L in LBL_ARTIST LBL_ALBUM LBL_TRACK LBL_LENGTH LBL_ACTION
	do
		LL=${!L}
		if [ "${#LL}" -gt "${LBL_MAXLENGTH}" ]
		then
			LBL_MAXLENGTH="${#LL}"
			TCURRENT_PADDING="${LL}"
		fi
	done
	TCURRENT_PADDING=${TCURRENT_PADDING//?/ }

	# INIT done
	debug "${FUNCNAME}: Init done"
}

# 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" ;;
		"nc""netcat")    HTTP_CLIENT_FUNCTION="http_get_nc" ;;
		*)       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 exists)
		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}"

		# Get the protocol, status and error message
		local PROTO STATUS MSG
		PROTO="${HTTP_HEADERS[0]%% *}"
		MSG="${HTTP_HEADERS[0]#* }"
		STATUS="${MSG%% *}"
		MSG="${MSG#* }"
		if [ "${STATUS}" == "200" ]
		then
			RET="0"
		elif [ "${STATUS}" == "403" ]
		then
			debug "${FUNCNAME}: HTTP GET status is ${STATUS} ${MSG}"
			RET="4"
		elif [ "${STATUS}" == "503" ]
		then
			debug "${FUNCNAME}: HTTP GET status is ${STATUS} ${MSG}"
			RET="5"
		else
			debug "${FUNCNAME}: HTTP GET status is not OK: ${STATUS} ${MSG}"
			RET="1"
		fi
	else
		debug "${FUNCNAME}: Unidentified 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="30" \
		--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: nc implementation {{{1
#-----------------------------------------------------------------------------
function http_get_nc()
{
	# $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"

	# Send the request
	if echo -ne "${REQUEST}" | "${HTTP_CLIENT}"  -w "5" "$2" "$3" > "$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&language=en"
	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}" ]
		then
			RET="4"
			debug "${FUNCNAME}: No session retrieved"
		elif [[ "${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_STATION_URL 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}" ;;
				"station_url")        META_STATION_URL="${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}&lang=en"
	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()
{
	# Initialize the terminal according to the type of terminal
	# in the environmental variable TERM
	tput init
	# Enter special mode
	tput smcup
	# Set a welcome xterm title
	set_term_title "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="$(tput sgr0)"

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

	# Set the initial terminal size related settings
	term_resize
	# Disable terminal echo mode
	stty -echo
	# Make cursor invisible
	tput civis
}

# Function: Exit the terminal {{{1
#-----------------------------------------------------------------------------
function term_exit()
{
	# Set a goodbye xterm title
	set_term_title "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
	# Enable terminal echo mode
	stty echo
	# 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 ))

	# One line of T_COLUMNS space characters
	printf -v T_EMPTYLINE "%${T_COLUMNS}s"

	# 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"
	# Create the padds
	HISTORY_FIELD_PADD[0]=${T_EMPTYLINE:0:${HISTORY_FIELD_SIZE[0]}}
	HISTORY_FIELD_PADD[1]=${T_EMPTYLINE:0:${HISTORY_FIELD_SIZE[1]}}
	HISTORY_FIELD_PADD[2]=${T_EMPTYLINE:0:${HISTORY_FIELD_SIZE[2]}}
	HISTORY_FIELD_PADD[3]=${T_EMPTYLINE:0:${HISTORY_FIELD_SIZE[3]}}

	# Compute statusbar related sizes and paddings
	SBAR_UNAME_SIZE="15"
	SBAR_UNAME_PADD=${T_EMPTYLINE:0:${SBAR_UNAME_SIZE}}
	SBAR_MAIN_SIZE=$(( T_COLUMNS - SBAR_UNAME_SIZE - 12 ))
	SBAR_MAIN_PADD=${T_EMPTYLINE:0:${SBAR_MAIN_SIZE}}

	# Compute the current max line length
	TCURRENT_MAX=$(( T_COLUMNS - LBL_MAXLENGTH - 6 ))
}

# 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
		LINE="${LINE}  ${LBL_ARTIST}${HISTORY_FIELD_PADD[0]:${#LBL_ARTIST}}"
	fi

	# Add the album, if the field units are not null
	if [ "${HISTORY_FIELD_UNITS[1]}" != "0" ]
	then
		LINE="${LINE}  ${LBL_ALBUM}${HISTORY_FIELD_PADD[1]:${#LBL_ALBUM}}"
	fi

	# Add the track title
	LINE="${LINE}  ${LBL_TRACK}${HISTORY_FIELD_PADD[2]:${#LBL_TRACK}}"

	# Add the track duration
	LINE="${LINE}  ${HISTORY_FIELD_PADD[3]:${#LBL_LENGTH}}${LBL_LENGTH:0:6} "

	# 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 LEDS L1 L2 L3 L4 S MSG UNAME
	# Set the leds
	#  - R: the player is running
	#  - P: record to profile is on
	#  - D: Discovery mode is on
	#  - B: Debug mode is on
	player_running && L1="R" || L1="-"
	[ "${META_RTP}" == "1" ] && L2="P" || L2="-"
	[ "${META_DISCOVERY}" == "1" ] && L3="D" || L3="-"
	[ "${DEBUG}" == "y" ] && L4="B" || L4="-"
	LEDS="[${L1}${L2}${L3}${L4}]"

	# 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
	MSG="${MSG:0:${SBAR_MAIN_SIZE}}"

	# Trim the username, if too long
	UNAME="${LASTFM_USER:0:15}"

	# Format the status line
	LINE=" ${MSG}${SBAR_MAIN_PADD:${#MSG}}  ${UNAME}${SBAR_UNAME_PADD:${#UNAME}}  ${LEDS} "
	# 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 " ${T_ATTR_BOLD}${T_BGCOLOR_BLUE}%1s${T_ATTR_NORMAL}${T_BGCOLOR_BLUE} %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()
{
	tput cup 2 2
	echo -ne "${T_ATTR_BOLD}${TCURRENT_PADDING:${#LBL_ARTIST}}${LBL_ARTIST}${T_ATTR_NORMAL} : ${META_ARTIST:0:$TCURRENT_MAX}${T_EL}"

	tput cup 3 2
	echo -ne "${T_ATTR_BOLD}${TCURRENT_PADDING:${#LBL_TRACK}}${LBL_TRACK}${T_ATTR_NORMAL} : ${META_TRACK:0:$TCURRENT_MAX}${T_EL}"

	tput cup 4 2
	echo -ne "${T_ATTR_BOLD}${TCURRENT_PADDING:${#LBL_ALBUM}}${LBL_ALBUM}${T_ATTR_NORMAL} : ${META_ALBUM:0:$TCURRENT_MAX}${T_EL}"

	tput cup 5 2
	echo -ne "${T_ATTR_BOLD}${TCURRENT_PADDING:${#LBL_LENGTH}}${LBL_LENGTH}${T_ATTR_NORMAL} : ${META_MIN_SEC:0:$TCURRENT_MAX}${T_EL}"

	tput cup 6 2
	echo -ne "${T_ATTR_BOLD}${TCURRENT_PADDING:${#LBL_ACTION}}${LBL_ACTION}${T_ATTR_NORMAL} : ${META_ACTION:0:$TCURRENT_MAX}${T_EL}"

	# Set the xterm title and save the now_playing file
	if [ "${META_STREAMING}" == "true" ]
	then
		set_term_title "${META_ARTIST} - ${META_TRACK}"
		save_nowplaying "${META_ARTIST} - ${META_TRACK}"
		run_on_track_change "1"
		save_html "1" || tui_sbar $"Error while creating the HTML file."
	else
		set_term_title $"Not streaming"
		save_nowplaying
		save_html
	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
		L="${HISTORY_FIELD_SIZE[0]}"
		S="${HISTORY_ARTIST[$1]:0:$L}"
		LINE="${LINE}  ${S}${HISTORY_FIELD_PADD[0]:${#S}}"
	fi

	# Add the album, if the field units are not null
	if [ "${HISTORY_FIELD_UNITS[1]}" != "0" ]
	then
		L="${HISTORY_FIELD_SIZE[1]}"
		S="${HISTORY_ALBUM[$1]:0:$L}"
		LINE="${LINE}  ${S}${HISTORY_FIELD_PADD[1]:${#S}}"
	fi

	# Add the track title
	L="${HISTORY_FIELD_SIZE[2]}"
	S="${HISTORY_TRACK[$1]:0:$L}"
	LINE="${LINE}  ${S}${HISTORY_FIELD_PADD[2]:${#S}}"

	# Add the track duration
	L="${HISTORY_FIELD_SIZE[3]}"
	S="${HISTORY_MIN_SEC[$1]:0:$L}"
	LINE="${LINE}  ${HISTORY_FIELD_PADD[3]:${#S}}${S} "

	# 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}"
				save_history_csv "${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 M

	# Set the status line
	printf -v M $"Sending command: %s" "$1"
	tui_sbar "${M}"

	# 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: TUI for changing the station: input dialog {{{1
#-----------------------------------------------------------------------------
function tui_station_change_input()
{
	# $1 - PARAMETER to store the input in
	# $2 - prompt

	local RET READ_PARAMS

	# Move the cursor on lower-left corner and print
	tput cup ${T_LINES} 0
	echo -ne "${T_COLOR_WHITE}${T_BGCOLOR_BLUE}${T_EL}"
	# Make cursor normal visible
	tput cnorm
	# Enable terminal echo mode
	stty echo

	# Add the prompt, if specified
	[ "$2" ] && READ_PARAMS="-p $2: "
	# Read the user input
	if read -t 10 -e "${READ_PARAMS}" ${1}
	then
		[ "${!1}" ] && RET="0" || RET="1"
	else
		RET="2"
	fi

	# Disable terminal echo mode
	stty -echo
	# Make cursor invisible
	tput civis

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

# Function: TUI for changing the station: full url {{{1
#-----------------------------------------------------------------------------
function tui_station_change_url()
{
	local RET INPUT

	if tui_station_change_input INPUT $"Last.fm URL"
	then
		if tui_lastfm_station "${INPUT}"
		then
			RET="0"
			# Restore the metadata
			tui_metadata
		else
			RET="1"
			# Restore the status bar
			tui_sbar
		fi
	else
		RET="2"
		# Restore the status bar
		tui_sbar
	fi

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

# Function: TUI for changing the station: global tags {{{1
#-----------------------------------------------------------------------------
function tui_station_change_globaltags()
{
	local RET INPUT

	if tui_station_change_input INPUT $"Tags"
	then
		if tui_lastfm_station "lastfm://globaltags/${INPUT}"
		then
			RET="0"
			# Restore the metadata
			tui_metadata
		else
			RET="1"
			# Restore the status bar
			tui_sbar
		fi
	else
		RET="2"
		# Restore the status bar
		tui_sbar
	fi

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

# Function: TUI for changing the station: group station {{{1
#-----------------------------------------------------------------------------
function tui_station_change_group()
{
	local RET INPUT

	if tui_station_change_input INPUT $"Group"
	then
		if tui_lastfm_station "lastfm://group/${INPUT}"
		then
			RET="0"
			# Restore the metadata
			tui_metadata
		else
			RET="1"
			# Restore the status bar
			tui_sbar
		fi
	else
		RET="2"
		# Restore the status bar
		tui_sbar
	fi

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

# Function: TUI for changing the station: neighbours' station {{{1
#-----------------------------------------------------------------------------
function tui_station_change_neighbours()
{
	local RET INPUT

	if tui_station_change_input INPUT $"User's neighbours"
	then
		if tui_lastfm_station "lastfm://user/${INPUT}/neighbours"
		then
			RET="0"
			# Restore the metadata
			tui_metadata
		else
			RET="1"
			# Restore the status bar
			tui_sbar
		fi
	else
		RET="2"
		# Restore the status bar
		tui_sbar
	fi

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

# Function: TUI for changing the station: recommended {{{1
#-----------------------------------------------------------------------------
function tui_station_change_recommended()
{
	local RET INPUT

	if tui_station_change_input INPUT $"Recommended to user"
	then
		if tui_lastfm_station "lastfm://user/${INPUT}/recommended/100"
		then
			RET="0"
			# Restore the metadata
			tui_metadata
		else
			RET="1"
			# Restore the status bar
			tui_sbar
		fi
	else
		RET="2"
		# Restore the status bar
		tui_sbar
	fi

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

# Function: TUI for changing the station: similar artists {{{1
#-----------------------------------------------------------------------------
function tui_station_change_similarartists()
{
	local RET INPUT

	if tui_station_change_input INPUT $"Similar artists to"
	then
		if tui_lastfm_station "lastfm://artist/${INPUT}/similarartists"
		then
			RET="0"
			# Restore the metadata
			tui_metadata
		else
			RET="1"
			# Restore the status bar
			tui_sbar
		fi
	else
		RET="2"
		# Restore the status bar
		tui_sbar
	fi

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

# Function: TUI for changing the station: fan {{{1
#-----------------------------------------------------------------------------
function tui_station_change_fan()
{
	local RET INPUT

	if tui_station_change_input INPUT $"Artist fan"
	then
		if tui_lastfm_station "lastfm://artist/${INPUT}/fans"
		then
			RET="0"
			# Restore the metadata
			tui_metadata
		else
			RET="1"
			# Restore the status bar
			tui_sbar
		fi
	else
		RET="2"
		# Restore the status bar
		tui_sbar
	fi

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

# Function: Check if the player is running {{{1
#-----------------------------------------------------------------------------
function player_running()
{
	local RET

	# If the PID is not null...
	if [ "${PLAYER_PID}" ]
	then
		# And can be signalled...
		if kill -0 "${PLAYER_PID}" >/dev/null 2>&1
		then
			RET="0"
		else
			RET="1"
			PLAYER_PID=""
		fi
	else
		RET="1"
		PLAYER_PID=""
	fi

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

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

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

	local RET

	if [ -p "${PLAYER_PIPE}" ]
	then
		if echo "$1" > "${PLAYER_PIPE}"
		then
			debug "${FUNCNAME} (${PLAYER}): Command \"$1\" sent"
			RET="0"
		else
			debug "${FUNCNAME} (${PLAYER}): Command \"$1\" not sent"
			RET="1"
		fi
	else
		RET="1"
	fi

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

# Function: Start the player {{{1
#-----------------------------------------------------------------------------
function player_start()
{
	local M

	# Return if no player is to be used
	[ "${USE_PLAYER}" != "y" ] && return

	debug "${FUNCNAME}: no supported player found"

	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"
		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 2
	fi

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

	# Wait a second
	sleep 1

	# Check if the player is still running
	if player_running
	then
		debug "${FUNCNAME}: Player ${PLAYER} started"
		printf -v M "Player started (%s)" "${PLAYER}"
		tui_sbar "${M}"
		RET="0"
		# Load the stream, if configured to
		[ "${AUTO_PLAY}" == "y" ] && player_load
	else
		debug "${FUNCNAME}: Could not start player ${PLAYER}"
		printf -v M "Player %s could not be started" "${PLAYER}"
		tui_sbar "${M}"
		RET="3"
	fi

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

# Function: Load the stream in player {{{1
#-----------------------------------------------------------------------------
function player_load()
{
	# Return if no player is to be used
	player_running || return

	local RET

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

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

	# Wait a second (or even longer, to let the stream be loaded)
	sleep 1

	# Update the status line
	if [ "${RET}" == "0" ]
	then
		tui_sbar $"Stream loaded and playing" "5"
		tui_metadata
	else
		tui_sbar $"Stream not loaded"
	fi

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

# Function: Pause playing {{{1
#-----------------------------------------------------------------------------
function player_pause()
{
	player_running || return

	local RET

	# Set the status line
	tui_sbar $"Pause/Unpause playing the stream..."

	# Stop playing
	if [ "${PLAYER}" == "mplayer" ]
	then
		player_command "pause"
		RET=$?
	elif [ "${PLAYER}" == "mpg123" ]
	then
		player_command "pause"
		RET=$?
	else
		RET="1"
		debug "${FUNCNAME}: No supported player running"
		tui_sbar $"No supported player is running"
	fi

	# Restore the status bar
	sleep 1
	tui_sbar

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

# Function: Mute the player {{{1
#-----------------------------------------------------------------------------
function player_mute()
{
	player_running || return

	local RET

	# Set the status line
	tui_sbar $"Muting/Unmuting..."

	# Mute
	if [ "${PLAYER}" == "mplayer" ]
	then
		player_command "mute"
		RET=$?
	elif [ "${PLAYER}" == "mpg123" ]
	then
		RET="1"
		tui_sbar "Command not supported"
	else
		RET="1"
		debug "${FUNCNAME}: No supported player running"
		tui_sbar $"No supported player is running"
	fi

	# Restore the status bar
	sleep 1
	tui_sbar

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

# Function: Player volume up {{{1
#-----------------------------------------------------------------------------
function player_volup()
{
	player_running || return

	local RET

	# Set the status line
	tui_sbar $"Volume up"

	# Volume up
	if [ "${PLAYER}" == "mplayer" ]
	then
		player_command "volume +10"
		RET=$?
	elif [ "${PLAYER}" == "mpg123" ]
	then
		RET="1"
		tui_sbar "Command not supported"
	else
		RET="1"
		debug "${FUNCNAME}: No supported player running"
		tui_sbar $"No supported player is running"
	fi

	# Restore the status bar
	sleep 1
	tui_sbar

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

# Function: Player volume down {{{1
#-----------------------------------------------------------------------------
function player_voldown()
{
	player_running || return

	local RET

	# Set the status line
	tui_sbar $"Volume down"

	# Volume down
	if [ "${PLAYER}" == "mplayer" ]
	then
		player_command "volume -10"
		RET=$?
	elif [ "${PLAYER}" == "mpg123" ]
	then
		RET="1"
		tui_sbar "Command not supported"
	else
		RET="1"
		debug "${FUNCNAME}: No supported player running"
		tui_sbar $"No supported player is running"
	fi

	# Restore the status bar
	sleep 1
	tui_sbar

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

# Function: Stop playing {{{1
#-----------------------------------------------------------------------------
function player_stop()
{
	player_running || return

	local RET

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

	# Stop playing
	if [ "${PLAYER}" == "mplayer" ]
	then
		RET="1"
		tui_sbar $"Command not supported"
	elif [ "${PLAYER}" == "mpg123" ]
	then
		RET="1"
		tui_sbar $"Command not supported"
	else
		RET="1"
		debug "${FUNCNAME}: No supported player running"
		tui_sbar $"No supported player is running"
	fi

	# Restore the status bar
	sleep 1
	tui_sbar

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

# Function: Stop the player {{{1
#-----------------------------------------------------------------------------
function player_quit()
{
	player_running || return

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

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

	# Wait a second
	sleep 1

	# Check the result
	if [ "${RET}" == "0" ]
	then
		tui_sbar $"Player closed"
		# Remove the FIFO
		rm -f "${PLAYER_PIPE}"
	else
		tui_sbar $"Could not close the player"
	fi

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

# 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 the status
	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)

	# Log the event
	debug "${FUNCNAME}: Execute: $1 $2"

	# Select the command and execute it
	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

	# Wait for all background processes to finish
	wait
}


# 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_H}") tui_scroll_toggle ;;
			# Redisplay the TUI
			"${CTRL_L}") sigwinch ;;
			# Toggle debug mode
			"${CTRL_D}") tui_debug_toggle ;;
			# Change station
			"u") tui_station_change_url ;;
			"t") tui_station_change_globaltags ;;
			"g") tui_station_change_group ;;
			"n") tui_station_change_neighbours ;;
			"c") tui_station_change_recommended ;;
			"a") tui_station_change_similarartists ;;
			"f") tui_station_change_fan ;;
			# Player commands
			"x") player_load ;;
			"${CTRL_X}") player_start ;;
			"c") player_pause ;;
			"v") player_stop ;;
			"${CTRL_V}") player_quit ;;
			"m") player_mute ;;
			"+") player_volup ;;
			"-") player_voldown ;;
			# 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:
