#!/usr/bin/zsh

# zec: Z-Shell Empire Client
#   Copyright (C) 2001, 2002, 2003, 2004  Clint Adams

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

ZEC_VERSION="0.6.0"

cat <<EOF;
  zec version ${ZEC_VERSION}, Copyright (C) 2001, 2002, 2003, 2004  Clint Adams
  zec comes with ABSOLUTELY NO WARRANTY.
  This is free software, and you are welcome to redistribute it
  under certain conditions.

EOF

zmodload -i zsh/parameter || exit 244
zmodload -i zsh/net/tcp || exit 245
zmodload -i zsh/zselect || exit 246

autoload -U tcp_open
TCP_SILENT=yes
TCP_PROMPT=

setopt extendedglob


# send to empire server
zec_send() {
	tcp_send -s empire_server -- "$@"
}

# read from empire server
zec_readline() {
	if tcp_read -t 0.5 -s empire_server 
	then
		RET="$TCP_LINE"
	else
		return 1
	fi
}

# connect to empire server
# usage: zelogin country password hostname_or_ip tcpportnum

zelogin() {

	tcp_open $3 $4 empire_server || return 1
	zec_readline
	[[ "$RET" == "2 Empire server ready" ]] || return 2
	zec_send "user $LOGNAME"
	zec_readline
	[[ "$RET" == "0 hello $LOGNAME" ]] || return 3
	zec_send "client zec $ZEC_VERSION"
	zec_readline
	[[ "$RET" == "0 talking to zec $ZEC_VERSION" ]] || return 4
	zec_send "coun $1"
	zec_readline
	[[ "$RET" == "0 country name $1" ]] || return 5
	zec_send "pass $2"
	zec_readline
	[[ "$RET" == "0 password ok" ]] || return 6
	zec_send "play"
	zec_readline
	[[ "$RET" == "2 2" ]] || return 7

	return 0
}

zelogout() {
	zec_send "quit"
	zec_readline
	[[ "$RET" == "3 so long" ]] || return 8
}

zec_alias_parse() {
	local cmd="$1"

	[[ -n "${zec_aliases[$cmd]}" ]] || return 9

	cookedline="${line/$cmd/$zec_aliases[$cmd]}"
}

zec_tool_history() {
	local i

	for i in "${(onk)history[@]}"
	do
		print "$i: ${history[$i]}"
	done
}

zec_tool_tmp() {

	local tmpfilenam="${TMPDIR:-/tmp}/zectmp$$"
	local sects types

	if [[ -z "$1" ]]
	then
		sects="*"
		types="jk"
	else
		if [[ "$1" == [[:alpha:]]## ]]
		then
			sects="*"
			types="$1"
		else
			sects="$1"
			shift
			if [[ -z "$1" ]]
			then
				types="jk"
			else
				if [[ "$1" == \?* ]]
				then
					sects += "$1"
					shift
					if [[ -z "$1" ]]
					then
						types="jk"
					else
						types="$1"
					fi
				else
					types="$1"
				fi
			fi
		fi
	fi

	zec_send "prod $sects >$tmpfilenam"
	zec_parseresponse

	if [[ -n "${types//[jkdi]/}" ]];
	then
		print "Invalid sector types in ${types}."
		return 1
	fi

	for i in ${(M)${(f)"$(<$tmpfilenam)"}:#[[:space:]]#-#[0-9]##,-#[0-9]##[[:space:]]##[${types}]*}
	do
		case "$i" in
			(* [jk] *)
			zec_send "th i ${${(z)i}[1]} ${${(z)i}[10]/i(#e)/}"
			zec_parseresponse
			;;

			(* d *)
			zec_send "th o ${${(z)i}[1]} ${${(z)i}[12]/i(#e)/}"
			zec_parseresponse
			zec_send "th l ${${(z)i}[1]} ${${(z)i}[13]/i(#e)/}"
			zec_parseresponse
			zec_send "th h ${${(z)i}[1]} ${${(z)i}[14]/i(#e)/}"
			zec_parseresponse
			;;

			(* i *)

			zec_send "th l ${${(z)i}[1]} ${${(z)i}[11]/i(#e)/}"
			zec_parseresponse
			zec_send "th h ${${(z)i}[1]} ${${(z)i}[12]/i(#e)/}"
			zec_parseresponse
			;;
		esac

	done


	rm ${tmpfilenam}
}

zec_tool_alias() {
	local i

	for i in "${(onk)zec_aliases[@]}"
	do
		print "$i: ${zec_aliases[$i]}"
	done
}

zec_tools_parse() {
	local cmd="$1"

	[[ -n "${zec_tools[(r)$cmd]}" ]] || return 10

	shift
	zec_tool_$cmd "$@"
}

zec_subcommand() {
	local vpmpt line

	vpmpt="-p $subpmpt $1> "

	vared -h "$vpmpt" line
	[[ -z "$line" ]] || print -s "$line"
	zec_send "$line"
}

unset outgo

redir_output() {
	outgo=${1}
	[[ "$outgo" != \>* ]] && exit 211
	eval exec 8$outgo
	outgo="-u8"
}

pipe_output() {
	[[ "$@" != \|* ]] && exit 212
	outgo=("${(z)@/| #/}")
	eval exec 8\>\>\(${outgo}\)
	outgo="-u8"
}

restore_output() {
	[[ "$outgo" == "-u8" ]] && exec 8>&-
	unset outgo
}

print_output() {
	print $outgo -- "${1}"
}

zec_parseresponse() {

	while zec_readline
	do

	case "$RET" in
	'1'(#b)(*)) print_output "${(q)${match[1]/ /}}"
		;;
	3*) return 1
		;;
	'4'(#b)(*)) zec_subcommand "$match[1]"
		;;
	6*) restore_output
		;;
	'8 '(#b)(*)) redir_output "$match[1]"
		;;
	'9 '(#b)(*)) pipe_output "$match[1]"
		;;
	'd'(#b)(*)) print_output "${match[1]/ /}"  # flash
		;;
	'e'(#b)(*)) print_output "INFORM (slight confusion): ${match[1]/ /}"  # inform
		;;
	*) print "OH, NO!  I AM CONFUSED BY THIS LINE!"
		print "$RET"
		zelogout
		;;
	esac

	done
}

zec_command() {
	local line

	[[ "$RET" == '6 '(#b)([0-9]##)' '([0-9]##) ]] || return 9
	vpmpt="-p $pmpt $match[1]:$match[2]%# "

	vared -he "$vpmpt" line || zelogout
	[[ -z "$line" ]] || print -s "$line"
	if zec_alias_parse "${(z)line}"
	then
		if zec_tools_parse "${(z)cookedline}"
		then
			zec_send ""
		else
			zec_send "$cookedline"
		fi
	elif zec_tools_parse "${(z)line}"
	then
		zec_send ""
	else
		zec_send "$line"
	fi
}

parse_config() {
	local CONFIGLINE
	local i=1

	while read CONFIGLINE
	do
		case $CONFIGLINE in
		(addgame*)
			games[i]="${CONFIGLINE#addgame }"
			(( i++ ))
			;;
		'alias '(#b)([^ ]##)' '(*))
			zec_aliases[${match[1]}]="$match[2]"
			;;
		(\#*) # ignore comments
			;;
		(*)
			print "Unknown config line: $CONFIGLINE"
			;;
		esac

	done <$1
}

zec_tool_foreach()
{

	if [[ $# -lt 2 ]]; then
		print "usage: foreach <sects> <command>"
		return 1
	fi

	local tmpfilenam="${TMPDIR:-/tmp}/zecforeach$$"
	local sects sect

	sects="$1"
	shift
	if [[ "$1" == \?* ]];
	then
		sects+=" $1"
		shift
	fi

	zec_send "dump $sects >$tmpfilenam"
	zec_parseresponse
	for i in ${(M)${(f)"$(<$tmpfilenam)"}:##[0-9-]##[[:space:]]##[0-9-]##[[:space:]]*}
	do
		sect=${${i/ /,}%% *}
		zec_send "${(e)*}"
		zec_parseresponse
	done

	rm ${tmpfilenam}
}

# setfood <sects> <num of updates>
zec_tool_setfood()
{
	local tmpfilenam="${TMPDIR:-/tmp}/zecsetfood$$"
	local optionstate=NONE sects
	local -A gameoptions
	integer etus=0 thresh curthresh num=1
	float babyeatrate=0.0 eatrate=0.0

	if [[ $# -lt 1 ]]; then
		sects="*"
	else
		sects="$1"
		shift
		if [[ "$1" == \?* ]];
		then
			sects+=" $1"
			shift
		fi
		if [[ $# -eq 1 ]]; then
			num=$1
		else
			print "setfood: Too many arguments"
			return 1
		fi
	fi

	zec_send "version >$tmpfilenam"
	zec_parseresponse
	for i in ${(f)"$(<$tmpfilenam)"}
	do
		case "$i" in
			(An update consists of (#b)([[:digit:]]##) empire time units.)
			etus="$match[1]"
			;;
			((#b)([[:digit:]]##) babies eat ([[:digit:].]##) units of food becoming adults.)
			babyeatrate=$(( match[2] / match[1] ))
			;;
			(In one time unit, (#b)([[:digit:]]##) people eat ([[:digit:].]##) units of food.)
			eatrate=$(( match[2] / match[1] ))
			;;
			((#b)([[:digit:]]##) civilians will give birth to ([[:digit:].]##) babies per etu.)
			civbirthrate=$(( match[2] / match[1] ))
			;;
			((#b)([[:digit:]]##) uncompensated workers will give birth to ([[:digit:].]##) babies.)
			uwbirthrate=$(( match[2] / match[1] ))
			;;
			(Options enabled in this game:)
			optionstate=enabled
			;;
			(Options disabled in this game:)
			optionstate=disabled
			;;
			(*BIG_CITY*)
			[[ optionstate == "enabled" ]] && gameoptions[BIG_CITY]=enabled
			;;
			(*NOFOOD*)
			[[ optionstate == "enabled" ]] && gameoptions[NOFOOD]=enabled
			;;
		esac
	done

	rm ${tmpfilenam}

	if [[ gameoptions[NOFOOD] == "enabled" ]];
	then
		print "setfood: food is not required in this game"
		return 1
	fi

	zec_send "dump * >$tmpfilenam"
	zec_parseresponse

	colheads=( ${(z)${(M)${(f)"$(<$tmpfilenam)"}:#*coast*}} )
	for i in ${(M)${(f)"$(<$tmpfilenam)"}:#[[:digit:]-]## [[:digit:]-]## *}
	do
	z=(${(z)i})
	civ=$z[$colheads[(i)civ]]
	mil=$z[$colheads[(i)mil]]
	uw=$z[$colheads[(i)uw]]
	curthresh=$z[$colheads[(i)f_dist]]
	(( thresh = int(ceil(num * etus * eatrate * (civ + uw + mil ) + 2 * babyeatrate * (civ * civbirthrate + uw * uwbirthrate))) ))
	if [[ $thresh -gt $curthresh ]]; then
		print "th f $z[1],$z[2] $thresh"
		zec_send "th f $z[1],$z[2] $thresh"
		zec_parseresponse
	fi
	done
}

twitter() {
	local g

	[[ -r ~/.zecrc ]] && parse_config ~/.zecrc

	case $#games in
	0)
		;;
	1) set -- ${(z)games[1]}
	shift
		;;
	*) for i in $games; do g=(${(z)i}); printf "%-20s %20s:%s\n" ${g[1]} ${g[4]} ${g[5]}; done
		print "FIXME: should give a menu here"
		set -- ${(z)games[1]}
	shift
		;;
	esac

	if [[ -z "$1" ]] || [[ -z "$2" ]] || [[ -z "$3" ]] || [[ -z "$4" ]];
	then
		print "usage: $SCRIPTNAME country password host port"
		print "or"
		return 2
		fi

		if zelogin $1 $2 $3 $4
		then
			zec_parseresponse
			while zec_command
			do
				zec_parseresponse
			done
		else
			print "Boo, you suck.  $?"
			return 2
		fi
}

[[ -n "$ZEC_DEBUG" ]] && unset TCP_SILENT
typeset -A zec_aliases
zec_aliases=(exit quit jack "tmp jk")
typeset -a zec_tools
zec_tools=(history tmp alias foreach setfood)

SCRIPTNAME="$0"

twitter "$@"
