#!/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, and a "bot" fe, ie automatic

###
#
# 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-2004 Mike Hearn (mike@navi.cx)
#
###


# for protocol details see main/doc/PROTOCOL

# supported protocol versions
ttyfe_protocol_versions="6"

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

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

[ -e /etc/autopackage/config ] && source /etc/autopackage/config;
locked_global="$autopackage_deny_install"
locked_user="$autopackage_deny_user_install"
[ -e ${XDG_CONFIG_HOME:-~/.config}/autopackage/config ] && source ${XDG_CONFIG_HOME:-~/.config}/autopackage/config;

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
}

function queueStr() {
    local binding=`echo "$data" | head -n 1`
    local tmp="queue_${current_group}_bindings";

    if [[ "${!tmp}" != "" ]]; then # avoid adding a trailing space when only one binding...
    	export queue_${current_group}_bindings="${!tmp} $binding"
    else
	export queue_${current_group}_bindings="$binding";
    fi;
    export queue_${current_group}_${binding}_type="$1";
    export queue_${current_group}_${binding}_default="`echo "$data" | tail -n +2 | head -n 1`";
    export queue_${current_group}_${binding}_message="`echo "$data" | tail -n +3`"

    if [[ "$bindings" != "" ]]; then
	export bindings="$bindings
$binding:$current_group"
    else
    	export bindings="$binding:$current_group";
    fi

    unset binding
}

function processQueue() {
    local cur_group="";
    tput cvvis  # Make cursor visible

    for bl in $bindings; do
      local b;
      local g;
      b=`echo "$bl" | sed 's/:.*//'`
      g=`echo "$bl" | sed 's/.*://'`
      d=`echo "$groups" | grep "$g" | sed 's/.*://'`
      if [[ "$cur_group" != "$g" ]]; then
	cur_group="$g";
      fi

      local message="queue_${g}_${b}_message"
      local default="queue_${g}_${b}_default"
      local type="queue_${g}_${b}_type"

      if [[ "${!type}" == "string" ]]; then
	read -e -p "`indent`${!message} (${!default}): ";
	if [[ "$REPLY" != "" ]]; then
	    export bindings_${g}_${b}="$REPLY"
	else # use default
	    local tmp2="queue_${g}_${b}_default"
	    export bindings_${g}_${b}="${!tmp2}"
	fi
      fi

      if [[ "${!type}" == "path" ]]; then
	read -e -p "`indent`${!message} (${!default}): ";
	if [[ "$REPLY" != "" ]]; then
	    local tmp2="$REPLY"

	    # this expands ~ into the home dir....
	    if echo "$tmp2" | grep '~' >/dev/null; then
		tmp2=`echo "$tmp2" | sed "s|~|$HOME|"`;
	    fi

	    export bindings_${g}_${b}="$tmp2"
	else # use default
	    local tmp2="queue_${g}_${b}_default"
	    local tmp2="${!tmp2}"
	    # this expands ~ into the home dir....
	    if echo "$tmp2" | grep '~' >/dev/null; then
		tmp2=`echo "$tmp2" | sed "s|~|$HOME|"`;
	    fi
	    export bindings_${g}_${b}="$tmp2"
	fi
      fi
    done

    tput civis  # Hide cursor again

    return 1;
}

# 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
	echo -n "[$seriesid/$seriesmax]"
    fi
}


function echoProgressBar() {
    indent;
    printf "%2.f%%" $1
    [[ "$1" != "100" ]] && echo -n " ";
    echo -n "[";
    for (( i=0; i < $progressbar_width; i++ )); do
        p1=$[ $i * 2 ];
        p2=$[ ($i * 2) - 1 ];
        if (( $p1 < $1 )); then
            echo -n "=";
        elif (( $p2 > $1 )); 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_TTYFE_INSTALL_SUCCESS_SINGULAR"
		      else
			  out "$intl_APKG_TTYFE_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_TTYFE_MENU_ENTRY_SINGULAR"
		      else
			  out "$intl_APKG_TTYFE_MENU_ENTRY_PLURAL"
		      fi
		      normal
		      out "$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 (( entrycount > 1 )); then out -n "* "; fi
			  if [[ "$categories" == "" ]]; then
			      out "* $dispname"
			  else
			      out "* $dispname ($categories)"
			  fi
		      done
		      out ""
		  fi
                  
		  local sizecount=$( echo "$data" | sed -n "$[6+$packagecount+$entrycount]p" )
		  if (( sizecount > 0 )); then
		      bold
		      out "$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
			  (( total > 1228 )) && total_whole=$(( $total / 1024 )) && total_decimal=$(( $total % 1000 )) && total_decimal=`echo "$total_decimal" | cut -c -1`&& total_unit=`out "$intl_UNIT_KILOBYTE"`
			  (( total > 1258291 )) && total_whole=$(( $total / 1048576 )) && total_decimal=$(( $total % 1048576 )) && total_decimal=`echo "$total_decimal" | cut -c -2`&& total_unit=`out "$intl_UNIT_MEGABYTE"`
			  (( total > 1288490188 )) && total_whole=$(( $total / 1073741824 )) && total_decimal=$(( $total % 1073741824 )) && total_decimal=`echo "$total_decimal" | cut -c -3`&& total_unit=`out "$intl_UNIT_GIGABYTE"`
			  if [[ "$total_unit" == "" ]]; then
			      total_unit=`out "$intl_UNIT_BYTE"`
			      out "$intl_APKG_TTYFE_TOTALSIZE" "$total $total_unit"
			  else
			      out "$intl_APKG_TTYFE_TOTALSIZE" "$total_whole.$total_decimal $total_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 [[ "$recommendcount" == "1" ]]; then
			  out "$intl_APKG_TTYFE_FAILED_RECOMMENDS_SINGULAR"
		      else
			  out "$intl_APKG_TTYFE_FAILED_RECOMMENDS_PLURAL"
		      fi
		      normal
		      out "$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_TTYFE_FAILED_INSTALL_PACKAGES_SINGULAR "
		      else
			  out "$intl_APKG_TTYFE_FAILED_INSTALL_PACKAGES_PLURAL"
		      fi

		      out "$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_TTYFE_CAN_REMOVE_WITH" "$shortname"
                      echo
                  fi

		  ;;
	verify) if [[ "$result" == "success" ]]; then
		    out "$intl_APKG_TTYFE_VERIFY_OK"
		else
		    out "$intl_APKG_TTYFE_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_TTYFE_SEARCHING | sed 's/./ /g'`"
	tput rc
    fi
}

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

#tput civis; # make cursor invisible
trap 'tput cvvis' 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

while true; do # start a quick infinite loop ;)
    input=`cat "$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" ]] && [[ "$command" != "SWITCH GROUP" ]]; 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_TTYFE_TESTING" "$data";
		    ;;
	"FAILTEST") clearSearchingIfNeeded;
	            bold; red;
		    out "$intl_FAILED";
		    normal;
		    ;;
	"PASSTEST") clearSearchingIfNeeded;
	            green; out "$intl_PASSED"; normal;
		    ;;
	"RECOMMEND") clearSearchingIfNeeded;
	             magenta;
		     out "$intl_APKG_TTYFE_RECOMMENDED";
		     normal;
		     ;;

	"SEARCHING") normal; tput sc; blue; outn "$intl_APKG_TTYFE_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_TTYFE_PREPARING: $dispname"'
			elif [[ "$context" == "install" ]]; then
			    queueOnOutput 'drawHeaderBlue "`indent`$intl_APKG_TTYFE_INSTALLING: $dispname `printSeries`"'
			elif [[ "$context" == "verify" ]]; then
			    queueOnOutput 'drawHeaderBlue "`indent`$intl_APKG_TTYFE_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
		;;
	"INTERACT STRING") queueStr string;
			 ;;
	"INTERACT PATH")  queueStr path;
			 ;;

	"SWITCH GROUP") new_group=`echo "$data" | head -n 2 | sed '2d'`;
			if ! echo "$groups" | grep "$new_group" >/dev/null; then
			    new_group_description=`echo "$data" | sed '1d'`;
			    groups="$groups
$new_group:$new_group_description"
			fi
		        current_group="$new_group"
		        ;;

        "WAIT") processQueue;;

	"GET")  #echo "getting $data in group $current_group";
		tmp="bindings_${current_group}_${data}"
		echo ${!tmp} > "$pipe";
		unset tmp; continue;
	       ;;

	"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_TTYFE_DOWNLOAD_BANNER" "` echo \"$data\" | sed -n '1p'`"
			  download_message="$intl_APKG_TTYFE_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_TTYFE_CONNECTING"
			  elif [[ "$m" == "NEGOTIATING" ]]; then
			      download_message="$intl_APKG_TTYFE_NEGOTIATING"
			  fi
			  displayDownloadProgress
			  ;;

	"DOWNLOAD-FINISH") # we should probably do error checking and other boring stuff here :D (just kidding)
	                   tput cvvis
	                   download_progress="100"
			   displayDownloadProgress
	                   echo
	                   ;;

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