#!/bin/sh
# Usage: irc [<server>] [<port>] [<nick>] [<username>] [<realname>]

[ -z "$IRC" ] && {
	fd="/tmp/.irc2"; rm -f "$fd"; mkfifo "$fd"
	nc "${1:-irc.libera.chat}" "${2:-6667}" <"$fd" | \
		IRC=1 OUTFD=3 exec "$0" "$@" 3>&1 >"$fd"
}

stty_save=$(stty -g </dev/tty)
stty -echo -icanon min 1 time 0 onlcr </dev/tty
trap 'stty "$stty_save" </dev/tty; kill 0' EXIT HUP INT
if [ -n "$OUTFD" ]; then
	out="/dev/fd/$OUTFD"
else
	out="/dev/tty"
fi
printf 'NICK %s\r\n' "${nick:=${3:-$(id -un)}}"
printf 'USER %s localhost * :%s\r\n' "${4:-$nick}" "${5:-$nick}"

curr=""
[ -n "${IRC_CMD_PREFIXES:-}" ] || IRC_CMD_PREFIXES="/ ; : &"

is_cmd_prefix() {
	cmd=$1
	[ -z "$cmd" ] && return 1
	first=${cmd%"${cmd#?}"}
	case $IRC_CMD_PREFIXES in
		*' '*) 
			for p in $IRC_CMD_PREFIXES; do
				[ "$first" = "$p" ] && return 0
			done
			return 1
		;;
	esac
	case $IRC_CMD_PREFIXES in
		*"$first"*) return 0 ;;
	esac
	return 1
}

handle_cmd() {
	cmd=$1
	[ -z "$cmd" ] && return
	if is_cmd_prefix "$cmd"; then
		[ "${cmd#?}" = "" ] && return
		cmd="/${cmd#?}"
	fi
	case $cmd in
		/join*)
			curr=${cmd#* }
			printf '%s\r\n' "${cmd#/}"
		;;
		/part*)
			channel=${cmd#* }
			printf 'PART %s\r\n' "$channel"
		;;
		/quit*)
			reason=${cmd#* }
			[ -z "$reason" ] && reason="Leaving"
			printf 'QUIT :%s\r\n' "$reason"
			exit 0
		;;
		/nick*)
			newnick=${cmd#* }
			printf 'NICK %s\r\n' "$newnick"
		;;
		/whois*)
			target=${cmd#* }
			printf 'WHOIS %s\r\n' "$target"
		;;
		/list*)
			channel=${cmd#* }
			if [ -z "$channel" ]; then
				printf 'LIST\r\n'
			else
				printf 'LIST %s\r\n' "$channel"
			fi
		;;
		/topic*)
			channel=${cmd#* }
			printf 'TOPIC %s\r\n' "$channel"
		;;
		/msg*)
			target_msg=${cmd#* }
			target=${target_msg%% *}
			curr=$target
			if [ "$target_msg" != "$target" ]; then
				msg=${target_msg#* }
				printf 'PRIVMSG %s :%s\r\n' "$target" "$msg"
				printf ':%s!%s@localhost PRIVMSG %s :%s\n' "$nick" "${4:-$nick}" "$target" "$msg" >"$out"
			fi
		;;
		/invite*)
			user_channel=${cmd#* }
			user=${user_channel%% *}
			channel=${user_channel#* }
			printf 'INVITE %s %s\r\n' "$user" "$channel"
		;;
		/kick*)
			channel_user=${cmd#* }
			channel=${channel_user%% *}
			user=${channel_user#* }
			printf 'KICK %s %s\r\n' "$channel" "$user"
		;;
		/mode*)
			channel_mode=${cmd#* }
			channel=${channel_mode%% *}
			mode=${channel_mode#* }
			printf 'MODE %s %s\r\n' "$channel" "$mode"
		;;
		/away*)
			message=${cmd#* }
			if [ -z "$message" ]; then
				printf 'AWAY\r\n'
			else
				printf 'AWAY :%s\r\n' "$message"
			fi
		;;
		/me*)
			action=${cmd#* }
			printf 'PRIVMSG %s :\001ACTION %s\001\r\n' "$curr" "$action"
			printf ':%s!%s@localhost PRIVMSG %s :\001ACTION %s\001\n' "$nick" "${4:-$nick}" "$curr" "$action" >"$out"
		;;
		/*)
			printf '%s\r\n' "${cmd#/}"
		;;
		*)
			printf 'PRIVMSG %s :%s\r\n' "$curr" "$cmd"
			printf ':%s!%s@localhost PRIVMSG %s :%s\n' "$nick" "${4:-$nick}" "$curr" "$cmd" >"$out"
		;;
	esac
}

buf=""
cur=0

buf_len() {
	printf '%s' "$buf" | awk '{ print length($0) }'
}

norm_int() {
	case ${1:-0} in
		''|*[!0-9]*)
			printf '0'
		;;
		*)
			printf '%s' "$1"
		;;
	esac
}

calc_add() {
	awk -v a="$1" -v b="$2" 'BEGIN{
		if (a !~ /^[0-9]+$/) a = 0
		if (b !~ /^[0-9]+$/) b = 0
		print a + b
	}'
}

calc_sub_nonneg() {
	awk -v a="$1" -v b="$2" 'BEGIN{
		if (a !~ /^[0-9]+$/) a = 0
		if (b !~ /^[0-9]+$/) b = 0
		d = a - b
		if (d < 0) d = 0
		print d
	}'
}

buf_left() {
	n=$(norm_int "${1:-0}")
	[ "$n" -le 0 ] && { printf ''; return; }
	printf '%s' "$buf" | awk -v n="$n" '{ print substr($0, 1, n) }'
}

buf_right() {
	n=$(norm_int "${1:-0}")
	[ "$n" -le 0 ] && { printf '%s' "$buf"; return; }
	printf '%s' "$buf" | awk -v n="$n" '{ print substr($0, n + 1) }'
}

buf_insert() {
	ch=$1
	cur=$(norm_int "${cur:-0}")
	left=$(buf_left "$cur")
	right=$(buf_right "$cur")
	buf=$left$ch$right
	cur=$(calc_add "$cur" 1)
}

buf_backspace() {
	cur=$(norm_int "${cur:-0}")
	[ "$cur" -le 0 ] && return
	left=$(buf_left "$(calc_sub_nonneg "$cur" 1)")
	right=$(buf_right "$cur")
	buf=$left$right
	cur=$(calc_sub_nonneg "$cur" 1)
}

buf_delete() {
	cur=$(norm_int "${cur:-0}")
	len=$(buf_len)
	len=$(norm_int "$len")
	[ "$cur" -ge "$len" ] && return
	left=$(buf_left "$cur")
	right=$(buf_right "$(calc_add "$cur" 1)")
	buf=$left$right
}

buf_move_left() {
	cur=$(norm_int "${cur:-0}")
	[ "$cur" -gt 0 ] && cur=$(calc_sub_nonneg "$cur" 1)
}

buf_move_right() {
	cur=$(norm_int "${cur:-0}")
	len=$(buf_len)
	len=$(norm_int "$len")
	[ "$cur" -lt "$len" ] && cur=$(calc_add "$cur" 1)
}

buf_move_home() {
	cur=0
}

buf_move_end() {
	cur=$(norm_int "$(buf_len)")
}

render_line() {
	cur=$(norm_int "${cur:-0}")
	len=$(norm_int "$(buf_len)")
	[ "$cur" -lt 0 ] && cur=0
	[ "$cur" -gt "$len" ] && cur=$len
	printf '\r\033[2K' >/dev/tty
	printf '%s' "$buf" >/dev/tty
	if [ "$cur" -lt "$len" ]; then
		back=$(calc_sub_nonneg "$len" "$cur")
		printf '\033[%sD' "$back" >/dev/tty
	fi
}

read_char() {
	dd bs=1 count=1 2>/dev/null </dev/tty
}
while :; do
	ch=$(dd bs=1 count=1 2>/dev/null </dev/tty)
	case $ch in
		"$(printf '\r')"|"$(printf '\n')")
			printf '\r\033[2K' >/dev/tty
			handle_cmd "$buf"
			buf=""
			cur=0
		;;
		"$(printf '\177')"|"$(printf '\010')")
			if [ "$cur" -gt 0 ]; then
				buf_backspace
				render_line
			fi
		;;
		"$(printf '\003')")
			printf '^C\r\n' >/dev/tty
			exit 130
		;;
		"$(printf '\001')") # Ctrl-A
			buf_move_home
			render_line
		;;
		"$(printf '\005')") # Ctrl-E
			buf_move_end
			render_line
		;;
		"$(printf '\033')") # escape sequence
			ch2=$(read_char)
			if [ "$ch2" = "[" ]; then
				ch3=$(read_char)
				case $ch3 in
					"D") # left
						buf_move_left
						render_line
					;;
					"C") # right
						buf_move_right
						render_line
					;;
					"H") # home
						buf_move_home
						render_line
					;;
					"F") # end
						buf_move_end
						render_line
					;;
					"3") # delete
						ch4=$(read_char)
						if [ "$ch4" = "~" ]; then
							buf_delete
							render_line
						fi
					;;
				esac
			fi
		;;
		*)
			buf_insert "$ch"
			render_line
		;;
	esac
done 2>/dev/null &

while IFS= read -r line; do
	case $line in
		'PING '*)
			printf 'PONG %s\r\n' "${line#* }"
		;;
		'*'/*)
			printf '%s\n' "$line" >"$out"
		;;
		*)
			printf '%s\n' "$line" >"$out"
		;;
	esac
done
