#!/bin/bash

# This is the terminal front end program. It communicates with the
# autopackage tools through a named pipe and is responsible for
# displaying output. The reason for this system is so you can have
# multiple front ends, for instance not just a tty fe, but also a
# KDE/GNOME frontend. It also introduces concurrency, meaning the
# frontend can be fully responsive even if the backend is busy.

###
#
# This code is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Copyright 2002-2005 Mike Hearn (mike@plan99.net)
#
###


# for protocol details see main/doc/PROTOCOL

# supported protocol versions
ttyfe_protocol_versions="7"

# keeps track of when it's time to quit
session_depth=0;

# get the named pipe as $1
pipe="$1"

# setup XDG configuration variables scoped for autopackage
#
#   AUTOPACKAGE_CONFIG_HOME     User configuration directory
#   AUTOPACKAGE_CONFIG_DIRS     System configuration directories
#   AUTOPACKAGE_CONFIG_DIR      Determined system configuration directory for autopackage

if [ -z "$AUTOPACKAGE_CONFIG_HOME" ]; then
    export AUTOPACKAGE_CONFIG_HOME
    if [ -z "$XDG_CONFIG_HOME" ]; then
        AUTOPACKAGE_CONFIG_HOME="$HOME/.config"
    else
        AUTOPACKAGE_CONFIG_HOME="$XDG_CONFIG_HOME"
    fi
fi

if [ -z "$AUTOPACKAGE_CONFIG_DIRS" ]; then
    export AUTOPACKAGE_CONFIG_DIRS
    if [ -z "$XDG_CONFIG_DIRS" ]; then
        AUTOPACKAGE_CONFIG_DIRS="/etc/xdg"
    else
        AUTOPACKAGE_CONFIG_DIRS="$XDG_CONFIG_DIRS"
    fi
fi

if [ -z "$AUTOPACKAGE_CONFIG_DIR" ]; then
    export AUTOPACKAGE_CONFIG_DIR
    _AUTOPACKAGE_CONFIG_DIRS=$( echo "$AUTOPACKAGE_CONFIG_DIRS" | tr ':' ' ' )
    for _CONFIGURATION_DIR in $_AUTOPACKAGE_CONFIG_DIRS; do
        if [ -e "$_CONFIGURATION_DIR/autopackage/config" ]; then
            AUTOPACKAGE_CONFIG_DIR="$_CONFIGURATION_DIR"
            break
        fi
    done
    unset _AUTOPACKAGE_CONFIG_DIRS
fi

# set defaults that might not be loaded from system configurations
export autopackage_deny_user=false

# load system configurations
[ -e /etc/autopackage/config ] && source /etc/autopackage/config;
[ -e "$AUTOPACKAGE_CONFIG_DIR/autopackage/config" ] && source "$AUTOPACKAGE_CONFIG_DIR/autopackage/config";

# load user configuration if allowed from system configuration
if ! $autopackage_deny_user; then
    [ -e "$AUTOPACKAGE_CONFIG_HOME/autopackage/config" ] && source "$AUTOPACKAGE_CONFIG_HOME/autopackage/config";
fi

locked_global="$autopackage_deny_install"
locked_user="$autopackage_deny_user_install"

source "$autopackage_prefix/share/autopackage/apkg-funclib" # needed for formatting commands

############################################################################################

# prints the indents to indicate dependency depth
function indent() {
    local i
    local j
    if [[ "$1" == "" ]]; then j="$session_depth"; else j="$1"; fi
    (( j-- ))
    if [[ "$j" == "0" ]]; then return 0; fi;
    for i in `seq 1 $j`; do echo -n "#"; done;
    echo -n " ";
}

# Execute a command, and kill it if it doesn't exit in time
function _timeout()
{
    set -m
    ( sleep $1 ) &
    local pid1=`jobs -l | tail -n 1 | awk '{print $2}'`

    ( set -m; trap "kill %1 2>/dev/null" EXIT; eval "$2"; kill "$pid1" > /dev/null ) &
    local pid2=`jobs -l | tail -n 1 | awk '/ Running / {print $2}'`

    set +m
    wait $pid1
    kill $pid2 2>/dev/null
}

function timeout()
{
    local cmd="$2"
    if echo "$cmd" | grep '&$' >/dev/null; then
        cmd=`echo "$cmd" | sed 's/&$//'`
        ( trap "" SIGHUP; _timeout "$1" "$cmd" ) 2>/dev/null &
    else
        _timeout "$1" "$cmd" 2>/dev/null
    fi
}

function checkProtocolVersions() {
    if ! echo "$ttyfe_protocol_versions" | grep "`echo \"$1\" | head -n 1 | sed 's/HELLO //'`" >/dev/null; then
	echo; red; outn "$intl_FAIL "; normal; out "$intl_PROTOCOL_MISMATCH" "$1"; normal;
	timeout 3 "echo \"UHOH VERSION_MISMATCH\" > \"$pipe\" &";
	exit 1;
    fi
}

# FIXME: this doesn't work for some reason. The backend never gets the echo :(
function abort() {
    echo "ABORT" > "$pipe"
    echo;
    red; out "$intl_ABORTED"; normal;
    exit 1;
}

function printSeries() {
    if [[ "$seriesmax" != "0" ]]; then
	printf "($intl_APKG_FE_PACKAGE_PROGRESS)" $seriesid $seriesmax 
#	echo -n "[$seriesid/$seriesmax]"
    fi
}


function echoProgressBar() {
    local progressbar_current="$1"
    indent;
    printf "%2.f%%" "$progressbar_current"
    if (( progressbar_current < 100 )); then
        echo -n " "
    fi
    echo -n "[";
    for (( i=0; i < $progressbar_width; i++ )); do
        p1=$[ $i * 2 ];
        p2=$[ ($i * 2) - 1 ];
        if (( p1 < progressbar_current )); then
            echo -n "=";
        elif (( p2 > progressbar_current )); then
            echo -n " ";
        else
            echo -n ">";
        fi
    done
    echo -n "] $progressbar_label"
    if [[ "$2" != "" ]]; then
        echo -n "  ETA $2"
    fi
}

 # some systems (eg, Fedora Core 2) are buggy and need to be forced here with TERM
left_column=`TERM=ansi tput hpa 0`

function drawProgressBar() {
    a=$[ $progressbar_cur*100 / $progressbar_max ]
    echoProgressBar "$a"
    echo -n "$left_column"
}

function displayDownloadProgress() {
    progressbar_label="${download_rate}Kb/s"
    if [[ "$download_message" != "" ]]; then
        for i in $( seq 1 80 ); do echo -n " "; done; echo -n "$left_column"
        indent; echo -n " $download_progress% "
        echo -n "["
        for i in $( seq 1 $progressbar_width ); do echo -n " "; done
        echo -n "] $download_message"
    else
	if [[ "$download_progress" == "?" ]]; then
	    echoProgressBar "0" "$download_time      "
	else
            echoProgressBar "$download_progress" "$download_time       " # this is a nasty hack. of course, keeping track of the longest message is kind of slow and icky, but we should do it eventually, otherwise it won't internationalize
	fi
    fi
    echo -n "$left_column"
}

function displaySummary() {
    echo
    case $optype in
	install)  local packagecount=$( echo "$data" | sed -n '4p' )
	          if (( packagecount > 0 )); then
		      bold
		      if [[ "$packagecount" == "1" ]]; then
			  out "$intl_APKG_FE_INSTALL_SUCCESS_SINGULAR"
		      else
			  out "$intl_APKG_FE_INSTALL_SUCCESS_PLURAL" "$packagecount"
		      fi
		      normal
		      echo "$data" | sed -n "1,4d;p" | for i in `seq 1 $packagecount`; do
			  read; local name="$REPLY"
			  echo "* $name"
		      done
		      out ""
		  fi
		  local entrycount=$( echo "$data" | sed -n "$[5+$packagecount]p" )
		  if (( entrycount > 0 )); then
		      bold
		      if [[ "$entrycount" == "1" ]]; then
			  out "$intl_APKG_FE_MENU_ENTRY_SINGULAR"
		      else
			  out "$intl_APKG_FE_MENU_ENTRY_PLURAL"
		      fi
		      normal
		      echo "$data" | sed -n "1,$[5+$packagecount]d;p" | for i in `seq 1 $entrycount`; do
			  read; local entry="$REPLY"
			  local categories=$( cat "$entry" | grep "^Categories=" | sed 's/^Categories=//; s/Application;//; s/Core;//; s/KDE;//; s/GNOME//; s/Qt;//; s/GTK;//; s/X-Red-Hat-Base;//; s/[;]$//; s/;/, /g' )
			  local dispname=$( cat "$entry" | grep "^Name=" | sed 's/^Name=//' )
			  if [[ "$categories" == "" ]]; then
			      echo "* $dispname"
			  else
			      echo "* $dispname ($categories)"
			  fi
		      done
		      echo ""
		  fi

		  local sizecount=$( echo "$data" | sed -n "$[6+$packagecount+$entrycount]p" )
		  if (( sizecount > 0 )); then
		      bold
		      echo "$data" | sed -n "1,$[6+$packagecount+$entrycount]d;p" | for i in `seq 1 1`; do
			  read; local total="$REPLY" total_unit
			  # we move to the next unit when 1.2 of previous unit is present
			  if (( total > 1200000000 )); then
			    totaldec_whole=$((   $total / 1000000000 ))
			    totaldec_frac=$(( $total / 1000000 % 1000 + 1000))
			    totaldec_unit=`out "$intl_UNIT_GIGABYTE"`
			    totalbin_whole=$((   $total / 1073741824 ))
			    totalbin_frac=$(( $total * 125 / 134217728 % 1000 + 1000 ))
			    totalbin_whole=$((   $totalbin_frac / 1000 ))
			    totalbin_frac=$(( $totalbin_frac % 1000 + 1000 ))
			    totalbin_unit=`out "$intl_UNIT_GIBIBYTE"`
			  elif (( total > 1200000 )); then
			    totaldec_whole=$((   $total / 1000000 ))
			    totaldec_frac=$(( $total / 10000 % 100 + 100))
			    totaldec_unit=`out "$intl_UNIT_MEGABYTE"`
			    totalbin_frac=$(( $total * 25 / 262144 ))
			    totalbin_whole=$((   $totalbin_frac / 100 ))
			    totalbin_frac=$(( $totalbin_frac % 100 + 100 ))
			    totalbin_unit=`out "$intl_UNIT_MEBIBYTE"`
			  elif (( total > 1200 )); then
			    totaldec_whole=$((   $total / 1000 ))
			    totaldec_frac=$(( $total / 100 % 10 + 10))
			    totaldec_unit=`out "$intl_UNIT_KILOBYTE"`
			    totalbin_frac=$(( $total * 5 / 512 ))
			    totalbin_whole=$((   $totalbin_frac / 10 ))
			    totalbin_frac=$(( $totalbin_frac % 10 + 10 ))
			    totalbin_unit=`out "$intl_UNIT_KIBIBYTE"`
			  fi
			  if [ -z "$totalbin_unit" ]; then
			      out "$intl_APKG_FE_TOTALSIZE" "$total $intl_UNIT_BYTE"
			  else
			      out "$intl_APKG_FE_TOTALSIZE" "$totalbin_whole.${totalbin_frac#1} $totalbin_unit ($totaldec_whole.${totaldec_frac#1} $totaldec_unit)"
			  fi
		      done
		      normal
		      out ""
		  fi

		  local recommendcount=`echo "$data" | sed -n "1,$[6+$packagecount+$entrycount+$sizecount]d;p" | head -n 1`;
		  if (( recommendcount > 0 )); then
		      bold
		      if [ $HAVE_NGETTEXT -eq 1 ]; then
			  format=$( _i18n_recommends_string $recommendcount)
			  #$(ngettext "$intl_APKG_FE_FAILED_RECOMMENDS_SINGULAR" "$intl_APKG_FE_FAILED_RECOMMENDS_PLURAL" $recommendcount)
			  out "$format" $recommendcount
		      else
			  if [[ "$recommendcount" == "1" ]]; then
			      out "$intl_APKG_FE_FAILED_RECOMMENDS_SINGULAR" $recommendcount
			  else
			      out "$intl_APKG_FE_FAILED_RECOMMENDS_PLURAL" $recommendcount
			  fi
		      fi
		      normal
		      echo "$data" | sed -n "1,$[7+$packagecount+$entrycount+$sizecount]d;p" | for i in `seq 1 $recommendcount`; do
			  read; local recommend="$REPLY"
			  if (( recommendcount > 0 )); then echo -n "* "; fi
			  out "$recommend"
		      done
		  fi

		  # any failed packages?
		  if [[ "$result" == "failure" ]]; then
		      local failedcount=$( echo "$data" | sed -n "$[7+$packagecount+1+$entrycount]p" );

		      if [[ "$failedcount" == "1" ]]; then
			  outn "$intl_APKG_FE_FAILED_INSTALL_PACKAGES_SINGULAR "
		      else
			  outn "$intl_APKG_FE_FAILED_INSTALL_PACKAGES_PLURAL"
		      fi
		      echo "$data" | sed -n "1,$[5+$packagecount+1+$entrycount]d;p" | for i in `seq 1 $failedcount`; do
			  read;
			  if (( failedcount > 1 )); then
			      out "* $REPLY"
			  else
			      out "$REPLY"
			  fi
		      done
		  else
                      echo
		      out "$intl_APKG_FE_REMOVE_COMMAND" "`bold`package remove $shortname`normal`"
                      echo
                  fi

		  ;;
	verify) if [[ "$result" == "success" ]]; then
		    out "$intl_APKG_FE_VERIFY_OK"
		else
		    out "$intl_APKG_FE_VERIFY_BAD"
		fi

				;;
    esac
}

# delay the command until something is about to be printed - only 1 item in the queue allowed currently
function queueOnOutput() {
    holding_cmd="$1"
}

function clearSearchingIfNeeded() {
    if $clear_searching; then
	tput sc
	echo -n "`echo $intl_APKG_FE_SEARCHING | sed 's/./ /g'`"
	tput rc
    fi
}

function displayUninstalling() {
    local n="$displayname"
    if [[ "$n" == "" ]]; then
        # rpm doesn't have any concept of display names
        n="$shortname"
    fi

    out "$intl_APKG_FE_REMOVING" "$n"
    if [[ "$package_manager" != "autopackage" ]]; then
        local m=`echo "$package_manager" | tr [:lower:] [:upper:]`
        outn "$intl_APKG_FE_REMOVING_OTHER" "$m" "$shortname"
        outn " "
    else
        outn "$intl_APKG_FE_REMOVING_APKG" "$n"
        outn " "
    fi
}

function displayUninstallingDone() {
    green
    out "$intl_DONE"
    normal
}

function displayUninstallingFail() {
    red
    out "$intl_FAILED"
    normal
    out "`red`${intl_FAIL}`normal` $1"
}

############################################################################################

#tput civis; # make cursor invisible
trap 'tput cnorm' EXIT

cur_name=""
cur_dispname=""
cur_context=""
current_group="default"
# a list of group:description strings
groups="$current_group:Options"
bindings=""
seriesid="0"
seriesmax="0"

progressbar_max=0
progressbar_cur=0
progressbar_width=50
progressbar_label=""
progress_started=false
clear_searching=false # do we need to wipe out the "searching" part of a test

download_progress=0
download_rate=0

echo

while true; do # start a quick infinite loop ;)
    input=`<"$pipe"`
    command=`echo "$input" | head -n 1`
    data=`echo "$input" | tail -n +2`
    if [[ "$command" != "PROGRESSBAR" ]]; then trace "ttyfe:`indent`got command $command"; fi

    if echo "$command" | grep '^HELLO' >/dev/null; then
	# a new package has joined us. welcome!
	trace "ttyfe:`indent`package joined session"
	checkProtocolVersions "$command"
	cur_name="$data"
	(( session_depth++ ))
	trace "ttyfe:`indent`session depth is now $session_depth"
	echo "LETS GO" > "$pipe"
	continue;
    fi

    if [[ "$command" != "TERMINATE" ]]; then
	aborted=false; # clear the fail flag

	# this is a really ugly hack, we should probably alter the protocol to make this simpler
	if [[ "$holding_cmd" != "" ]] && [[ "$command" != "IDENTIFY" ]] && [[ "$command" != "SERIES" ]]; then
	    eval $holding_cmd;
	    holding_cmd="";
	fi
    fi;

    case "$command" in
	"STATUS")   $progress_started && echo;
	            normal; # make normal text
		    indent; out "$data";
		    ;;
        "TEST")     indent; normal; outn "$intl_APKG_FE_TESTING" "$data"; outn " ";
		    ;;
	"FAILTEST") clearSearchingIfNeeded;
            bold; red;
		    out "$intl_FAILED";
		    normal;
		    ;;
	"PASSTEST") clearSearchingIfNeeded;
	        green; out "$intl_PASSED"; normal;
		    ;;
	"RECOMMEND")
			clearSearchingIfNeeded;
			magenta;
			out "$intl_APKG_FE_RECOMMENDED";
			normal;
			;;

	"SEARCHING")
			normal
			tput sc; blue; outn "$intl_APKG_FE_SEARCHING"; normal; tput rc;
			clear_searching=true
			;;

	"IDENTIFY") dispname=$( echo "$data" | sed -n '1p' );
	            export seriesid=$( echo "$data" | sed -n '2p' );
	            context=$( echo "$data"  | sed -n '3p' );
		    if [[ "$dispname" != "$cur_dispname" ]] || [[ "$context" != "$cur_context" ]]; then # don't redraw the header several times
			cur_dispname="$dispname"
			cur_context="$context"
			if [[ "$context" == "prep" ]]; then
			    queueOnOutput 'drawHeaderCyan "`indent`$intl_APKG_FE_PREPARING: $dispname"'
			elif [[ "$context" == "install" ]]; then
			    queueOnOutput 'drawHeaderBlue "`indent`$intl_APKG_FE_INSTALLING: $dispname `printSeries`"'
			elif [[ "$context" == "verify" ]]; then
			    queueOnOutput 'drawHeaderBlue "`indent`$intl_APKG_FE_VERIFYING: $dispname `printSeries`"'
			elif [[ "$context" == "none" ]]; then
			    queueOnOutput 'drawHeaderBlue "`indent`$dispname `printSeries`"'
			fi
		    fi
	            ;;

	"SERIES") seriesmax="$data";
	          trace new seriesmax=$seriesmax
	          ;;

	"PROGRESSBAR") progressbar_max=$( echo "$data" | sed -n '1p' );
	               progressbar_cur=$( echo "$data" | sed -n '2p' );
		       progressbar_label=$( echo "$data" | sed -n '3p' );
		       if [[ "$progressbar_cur" == "$progressbar_max" ]]; then
			   drawProgressBar
			   echo # newline so we don't overwrite the progress bar
			   tput cnorm 2>/dev/null
			   progress_started=false
		       else
		           if ! $progress_started; then
				tput civis 2>/dev/null
		           fi
			   drawProgressBar
			   progress_started=true
		       fi
		       ;;

        "TERMINATE") trace "ttyfe:`indent`package left session";
     		     (( session_depth-- ));
		     trace "ttyfe:`indent`session depth is now $session_depth"
		     if [[ "$session_depth" == "0" ]]; then
			echo "OK" > "$pipe"	# send confirmation
			tput cnorm 2>/dev/null	# restore cursor
			exit 0
		     fi
		;;
	"FAIL") if (( `echo "$data" | wc -l | sed 's/^ *//'` > 1 )); then
	          indent; red; outn "$intl_FAIL "; normal; echo; echo "$data" | while read; do indent; out "$REPLY";  done;
		else
		  indent; red; outn "$intl_FAIL "; normal; out "$data";
		fi
		aborted=true
		;;

	"DISPLAY-SUMMARY") optype=$( echo "$data" | sed -n '1p' )
	                   result=$( echo "$data" | sed -n '2p' )
                           shortname=$( echo "$data" | sed -n '3p' )
	                   displaySummary
			   ;;

	"DOWNLOAD-START") download_progress=0
			  download_rate=0
			  ##indent; out "$intl_APKG_FE_DOWNLOAD_BANNER" "` echo \"$data\" | sed -n '1p'`"
			  download_message="$intl_APKG_FE_DOWNLOAD_INITIALIZING"
			  tput civis
	                  displayDownloadProgress
	                  ;;

	"DOWNLOAD-PROGRESS")
			  download_progress=$( echo "$data" | sed -n '2p' )
	                  download_rate=$( echo "$data" | sed -n '1p' )
			  download_message=""
			  download_time=$( echo "$data" | sed -n '3p' )
			  if [[ "$download_progress" == "100" ]]; then
			      tput cnorm
			  else
			      displayDownloadProgress
			  fi
			  ;;

	"DOWNLOAD-PREPARE")
	                  tput civis
	                  m=$( echo "$data"  | sed -n '1p' )
			  if [[ "$m" == "CONNECTING" ]]; then
			      download_message="$intl_APKG_FE_CONNECTING"
			  elif [[ "$m" == "NEGOTIATING" ]]; then
			      download_message="$intl_APKG_FE_NEGOTIATING"
			  fi
			  displayDownloadProgress
			  ;;

	"DOWNLOAD-FINISH") tput cnorm
	                   download_progress="100"
			   displayDownloadProgress
	                   echo
	                   ;;

	"UNINSTALLING") displayname=$( echo "$data" | sed -n '1p' )
	                shortname=$( echo "$data" | sed -n '2p' )
			package_manager=$( echo "$data" | sed -n '3p' )
			displayUninstalling
			;;

	"UNINSTALL-DONE") displayUninstallingDone
	                ;;

	"UNINSTALL-FAIL") displayUninstallingFail "$data"
	                ;;

	*) err "Unknown command received [$command] - system destabilised, expect the unexpected";;
    esac
    echo "OK" > "$pipe"
done
