#!/bin/sh

clear_buf() {
	while
		tput cup "${i:-0}" 0 && tput el \
		&& [ "$(( i+=1 ))" -le "$1" ]
	do :; done
}

term_init() {
	cols=$(tput cols)
	rows=$(tput lines)
	start_headers=$(( rows / 4 ))
	start_body=$(( start_headers + 1 ))
	end_body=$(( rows - start_body - 2 ))
	term_clear_headers=$(clear_buf "$start_headers"; tput cup 0 0)
	term_clear_body=$(tput cup "$(( start_body ))" 0; tput ed)
	term_move_status=$(tput cup "$(( rows ))" $(( cols - 5 )); tput el)
	term_move_cmd=$(tput cup "$(( rows ))" 0)
	term_init_cmd=$(tput cnorm; tput el)
	term_done_cmd=$(tput civis)
}

statusline() {
	printf "%s%s" "$term_move_status" "$@"
}

cmdline() {
	printf "%s%s:" "$term_move_cmd" "$term_init_cmd"
	stty "$stty_default"
	tput cnorm
	read -r cmd
	stty -echo -icanon
	case "$cmd" in
		"|"*) ;;
		"!"*)
			tput sgr0 && tput rmcup # restore to content before mvi
			eval "${cmd#!*}"
			# wait for enter, and delete message
			tput sc; printf "[enter to continue]" && read -r d; tput rc; tput el
			tput smcup # save new content
		;;
		q) close ;;
	esac
	printf "%s" "$term_done_cmd"
	update_body=1
	draw
}

update() {
	buf_row=1
	[ "$buf_col" -le 0 ] && buf_col=1
	mshow 2>/dev/null | mcolor \
		| cut -c "$(( buf_col ))-$(( cols + buf_col - 1 ))" \
		| trunc_lines >"$buf_path"
	buf_len=$(wc -l <"$buf_path")
	: $(( buf_len = buf_len + 1 ))
	: $(( buf_end = buf_len - end_body ))
	update_body=1
}

trunc_lines() {
	t=$(tput el)
	while read -r l; do
		printf "%s%s\n" "$l" "$t"
	done
}

headers() {
	printf "%s" "$term_clear_headers"
	
	: $(( x = hdr_row - (start_headers / 2) ))
	: $(( y = hdr_row + (start_headers / 2) ))

	[ "$x" -gt 0 ] && x="+$x"
	[ "$y" -gt 0 ] && y="+$y"

	COLUMNS=$cols mscan ".$x:.$y" 2>/dev/null \
		| awk '
function fg(c, s) { return sprintf("\033[38;5;%03dm%s\033[0m", c, s) }
function so(s) { return sprintf("\033[1m%s\033[0m", s) }
/^>/ { print so(fg(119, $0)); next }
/^ *\\_/ { print fg(242, $0); next }
{ print }
'
}

body() {
	[ "$buf_row" -gt "$buf_end" ] && buf_row=$buf_end
	[ "$buf_row" -lt 1 ] && buf_row=1

	: $(( x = buf_row ))
	: $(( y = end_body + buf_row ))

	[ "${update_body}" = "${x},${y}p" ] && return

	printf "%s" "$term_clear_body"

	update_body="${x},${y}p"
	sed -n "$update_body" "$buf_path"

	[ "$buf_len" -gt "$end_body" ] \
		&& statusline "$(( 100 * y / buf_len ))%"
}

draw() {
	headers
	body
}

init() {
	tput smcup # save position
	tput civis # cursor invisible
	term_init

	stty_default=$(stty -g)
	stty -echo -icanon

	update
	draw
}

close() {
	tput sgr0 # reset char attributes
	tput cnorm # normal cursor
	tput rmcup # restore position
	stty "$stty_default" # restore stty settings
	[ -e "$buf_path" ] && rm "$buf_path"
	exit "${1-0}"
}

motion() {
	[ -z "$mv" ] || [ -z "$in_new" ] && return
	mseq -C "$in_new";
	mv=
	in_new=
}

readchar() {
	c=$(dd bs=1 count=1 2>/dev/null)
	printf '%d' "'$c"
}

in_read() {
	set -- $in_buf
	[ "$#" -eq 0 ] && in_key=$(readchar) && return
	in_key=$1; shift; in_buf=$@
}

in_back() {
	set -- "$in_key" $in_buf
	in_buf=$@
}

in_prefix() {
	n=
	while [ "$in_key" -le 57 ] && [ "$in_key" -ge 48 ]; do
		: $(( n = n * 10 + $(printf \\$(printf "%03o" "$in_key")) ))
		in_read
	done
	[ "$1" -eq "1" ] && in_cnt1=${n:-0} || in_cnt2=${n:-0}
}

in_motionln() {
	cnt=$(( (in_cnt1 < 1 ? 1 : in_cnt1) * (in_cnt2 < 1 ? 1 : in_cnt2) ))
	mv=
	case "$in_key" in
	# return + j
	0|43|106) mv=".:.+$cnt"; in_new=".+$cnt" ;;
	# - k
	45|107) mv=".-$cnt:."; in_new=".-$cnt" ;;
	# G
	71)
		[ "$in_cnt1" -eq 0 ] && [ "$in_cnt2" -eq 0 ] \
			&& in_new="\$" \
			&& mv="$in_cur:$in_new" \
			&& return
		in_new="$cnt"
		[ "$cnt" -gt "$in_cur" ] \
			&& mv="$in_cur:$in_new" \
			|| mv="$in_new:$in_cur"
		;;
	# P [
	80|91)
		in_new=$in_cur
		while [ $(( cnt-=1 )) -ge 0 ]; do
			j=$(mscan -n "$in_new=" | head -n1)
			mv="$j:$in_new $mv"
			in_new=$(( j - 1 ))
		done
		: $(( in_new+=1 ))
		# XXX: should [ ] move one more???
		#[ "$in_cur" -eq "$in_new" ] && : $(( in_new-=1 ))
		#true
		;;
	# N ]
	78|93)
		# mv=".:$(mseq ".=" | tail -n1 | mscan -n)"
		in_new=$in_cur
		while [ $(( cnt-=1 )) -ge 0 ]; do
			j=$(mscan -n "$in_new=" | tail -n1)
			mv="$mv $in_new:$j"
			in_new=$(( j + 1 ))
		done
		: $(( in_new-=1 ))
		#[ "$in_cur" -eq "$in_new" ] && : $(( in_new+=1 ))
		#true
		;;
	# {
	123) in_new=$(mscan -n ".^" | head -n1); mv="$in_new:." ;;
	# }
	125) in_new=$(mscan -n "._" | tail -n1); mv=".:$in_new" ;;
	*) false ;;
	esac
}

in_action() {
	c=$in_key

	in_read
	in_prefix 2
	in_motionln || { [ "$in_key" -eq "$c" ] && mv="."; }
	[ -z "$mv" ] && return 1

	case "$c" in
	# d
	100)
		mflag -S "$mv" >/dev/null
		mseq -f : | mseq -S
		motion
		headers
		;;
	# u
	117)
		mflag -s "$mv" >/dev/null
		mseq -f : | mseq -S
		motion
		headers
		;;
	esac
}

in_esc() {
	stty min 0 time 1
	c=$(readchar)
	rv=0
	case "$c" in
	91)
		c=$(readchar)
		# page up/down
		[ "$c" -eq 53 ] && : $(( buf_row-=end_body ))
		[ "$c" -eq 54 ] && : $(( buf_row+=end_body ))
		c=$(readchar)
		body
		;;
	*) c=$(readchar); c=$(readchar); rv=1 ;;
	esac
	stty min 1 time 0
	return $rv
}

trap 'close 130;' INT TERM

buf_path=$(mktemp /tmp/.mcurse_body.XXXXXX)

buf_col=1
buf_row=1
buf_len=
buf_end=

hdr_row=0

init

while :; do
	in_buf=
	in_key=
	in_cnt1=0
	in_cnt2=0
	in_cur=$(mscan -n .)
	in_new=

	printf "%s" "$term_move_cmd"

	in_read

	[ "$in_key" -eq 27 ] \
		&& in_esc \
		&& continue

	in_prefix 1

	in_motionln \
		&& motion \
		&& update \
		&& draw \
		&& continue

	case "$in_key" in
	# d u
	100|117) in_action ;;
	# D
	68) in_key="100"; in_back; in_action ;; # input buffer to dd
	# U
	85) in_key="117"; in_back; in_action ;; # input buffer to uu
	# e
	101) ${EDITOR=ed} $(mseq .) && update && draw ;;
	# v
	118) ${VISUAL=vi} $(mseq .) && update && draw ;;
	# L | C-l
	76|12) tput clear && term_init && update && draw ;;
	# :
	58) cmdline ;;
	# q
	113) close ;;
	# c
	99) hdr_row=0 && headers ;;
	# J
	74) : $(( buf_row+=(in_cnt1 < 1 ? 1 : in_cnt1) )) && body ;;
	# K
	75) : $(( buf_row-=(in_cnt1 < 1 ? 1 : in_cnt1) )) && body ;;
	esac
done
