#!/usr/bin/env bash

CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

source "$CURRENT_DIR/helpers.sh"

MAXIMUM_PADDING="25"   # maximum padding below the result when it can't be centered

# jump to 'next' or 'prev' match
# global var for this file
NEXT_PREV="$1"

# 'vi' or 'emacs', this variable used as a global file constant
TMUX_COPY_MODE="$(tmux_copy_mode)"

_file_number_of_lines() {
	local file="$1"
	echo "$(wc -l $file | $AWK_CMD '{print $1}')"
}

_get_result_line() {
	local file="$1"
	local number="$2"
	echo "$(head -"$number" "$file" | tail -1)"
}

_string_starts_with_digit() {
	local string="$1"
	echo "$string" |
		\grep -q '^[[:digit:]]\+:'
}

_get_line_number() {
	local string="$1"
	local copycat_file="$2"			# args 2 & 3 used to handle bug in OSX grep
	local position_number="$3"
	if _string_starts_with_digit "$string"; then
		# we have a number!
		local grep_line_number="$(echo "$string" | cut -f1 -d:)"
		# grep line number index starts from 1, tmux line number index starts from 0
		local tmux_line_number="$((grep_line_number - 1))"
	else
		# no number in the results line This is a bug in OSX grep.
		# Fetching a number from a previous line.
		local previous_line_num="$((position_number - 1))"
		local result_line="$(_get_result_line "$copycat_file" "$previous_line_num")"
		# recursively invoke this same function
		tmux_line_number="$(_get_line_number "$result_line" "$copycat_file" "$previous_line_num")"
	fi
	echo "$tmux_line_number"
}

_get_match() {
	local string="$1"
	local full_match
	if _string_starts_with_digit "$string"; then
		full_match="$(echo "$string" | cut -f2- -d:)"
	else
		# This scenario handles OS X grep bug "no number in the results line".
		# When there's no number at the beginning of the line, we're taking the
		# whole line as a match. This handles the result line like this:
		# `http://www.example.com` (the `http` would otherwise get cut off)
		full_match="$string"
	fi
	echo -n "$full_match"
}

_escape_backslash() {
	local string="$1"
	echo "$(echo "$string" | sed 's/\\/\\\\/g')"
}

_get_match_line_position() {
	local file="$1"
	local line_number="$2"
	local match="$3"
	local adjusted_line_num=$((line_number + 1))
	local result_line=$(tail -"$adjusted_line_num" "$file" | head -1)

	# OS X awk cannot have `=` as the first char in the variable (bug in awk).
	# If exists, changing the `=` character with `.` to avoid error.
	local platform="$(uname)"
	if [ "$platform" == "Darwin" ]; then
		result_line="$(echo "$result_line" | sed 's/^=/./')"
		match="$(echo "$match" | sed 's/^=/./')"
	fi

	# awk treats \r, \n, \t etc as single characters and that messes up match
	# highlighting. For that reason, we're escaping backslashes so above chars
	# are treated literally.
	result_line="$(_escape_backslash "$result_line")"
	match="$(_escape_backslash "$match")"

	local index=$($AWK_CMD -v a="$result_line" -v b="$match" 'BEGIN{print index(a,b)}')
	local zero_index=$((index - 1))
	echo "$zero_index"
}

_copycat_jump() {
	local line_number="$1"
	local match_line_position="$2"
	local match="$3"
	local scrollback_line_number="$4"
	_copycat_enter_mode
	_copycat_exit_select_mode
	_copycat_jump_to_line "$line_number" "$scrollback_line_number"
	_copycat_position_to_match_start "$match_line_position"
	_copycat_select "$match"
}

_copycat_enter_mode() {
	tmux copy-mode
}

# clears selection from a previous match
_copycat_exit_select_mode() {
	if [ "$TMUX_COPY_MODE" == "vi" ]; then
		# vi mode
		tmux send-keys Escape
	else
		# emacs mode
		tmux send-keys C-g
	fi
}

# "manually" go up in the scrollback for a number of lines
_copycat_manually_go_up() {
	local line_number="$1"
	if [ "$TMUX_COPY_MODE" == "vi" ]; then
		# vi copy mode
		tmux send-keys "$line_number" k 0
	else
		# emacs copy mode
		for (( c=1; c<="$line_number"; c++ )); do
			tmux send-keys C-p
		done
		tmux send-keys C-a
	fi
}

_copycat_create_padding_below_result() {
	local number_of_lines="$1"
	local maximum_padding="$2"
	local padding

	# Padding should not be greater than half pane height
	# (it wouldn't be centered then).
	if [ "$number_of_lines" -gt "$maximum_padding" ]; then
		padding="$maximum_padding"
	else
		padding="$number_of_lines"
	fi

	# cannot create padding, exit function
	if [ "$padding" -eq "0" ]; then
		return
	fi

	if [ "$TMUX_COPY_MODE" == "vi" ]; then
		# vi copy mode
		tmux send-keys "$padding" j "$padding" k
	else
		# emacs copy mode
		for (( c=1; c<="$padding"; c++ )); do
			tmux send-keys C-n
		done
		for (( c=1; c<="$padding"; c++ )); do
			tmux send-keys C-p
		done
	fi
}

# performs a jump to go to line
_copycat_go_to_line_with_jump() {
	local line_number="$1"
	# first jumps to the "bottom" in copy mode so that jumps are consistent
	if [ "$TMUX_COPY_MODE" == "vi" ]; then
		# vi copy mode
		tmux send-keys G 0 :
	else
		# emacs copy mode
		tmux send-keys "M->" C-a g
	fi
	tmux send-keys "$line_number" C-m
}

# maximum line number that can be reached via tmux 'jump'
_get_max_jump() {
	local scrollback_line_number="$1"
	local window_height="$2"
	local max_jump=$((scrollback_line_number - $window_height))
	# max jump can't be lower than zero
	if [ "$max_jump" -lt "0" ]; then
		max_jump="0"
	fi
	echo "$max_jump"
}

_copycat_jump_to_line() {
	local line_number="$1"
	local scrollback_line_number="$2"
	local window_height="$(tmux display-message -p '#{pane_height}')"
	local correct_line_number

	local max_jump=$(_get_max_jump "$scrollback_line_number" "$window_height")
	local correction="0"

	if [ "$line_number" -gt "$max_jump" ]; then
		# We need to 'reach' a line number that is not accessible via 'jump'.
		# Introducing 'correction'
		correct_line_number="$max_jump"
		correction=$((line_number - $correct_line_number))
	else
		# we can reach the desired line number via 'jump'. Correction not needed.
		correct_line_number="$line_number"
	fi

	_copycat_go_to_line_with_jump "$correct_line_number"

	if [ "$correction" -gt "0" ]; then
		_copycat_manually_go_up "$correction"
	fi

	# If no corrections (meaning result is not at the top of scrollback)
	# we can then 'center' the result within a pane.
	if [ "$correction" -eq "0" ]; then
		local half_window_height="$((window_height / 2))"
		# creating as much padding as possible, up to half pane height
		_copycat_create_padding_below_result "$line_number" "$half_window_height"
	fi
}

_copycat_position_to_match_start() {
	local match_line_position="$1"
	[ "$match_line_position" -eq "0" ] && return 0

	if [ "$TMUX_COPY_MODE" == "vi" ]; then
		# vi copy mode
		tmux send-keys "$match_line_position" l
	else
		# emacs copy mode
		# emacs doesn't have repeat, so we're manually looping :(
		for (( c=1; c<="$match_line_position"; c++ )); do
			tmux send-keys C-f
		done
	fi
}

_copycat_select() {
	local match="$1"
	local length="${#match}"
	if [ "$TMUX_COPY_MODE" == "vi" ]; then
		# vi copy mode
		tmux send-keys Space "$length" l h # selection correction for 1 char
	else
		# emacs copy mode
		tmux send-keys C-Space
		# emacs doesn't have repeat, so we're manually looping :(
		for (( c=1; c<="$length"; c++ )); do
			tmux send-keys C-f
		done
		# NO selection correction for emacs mode
	fi
}

# all functions above are "private", called from `do_next_jump` function

get_new_position_number() {
	local copycat_file="$1"
	local current_position="$2"
	local new_position

	# doing a forward/up jump
	if [ "$NEXT_PREV" == "next" ]; then
		local number_of_results=$(wc -l "$copycat_file" | $AWK_CMD '{ print $1 }')
		if [ "$current_position" -eq "$number_of_results" ]; then
			# position can't go beyond the last result
			new_position="$current_position"
		else
			new_position="$((current_position + 1))"
		fi

	# doing a backward/down jump
	elif [ "$NEXT_PREV" == "prev" ]; then
		if [ "$current_position" -eq "1" ]; then
			# position can't go below 1
			new_position="1"
		else
			new_position="$((current_position - 1))"
		fi
	fi
	echo "$new_position"
}

do_next_jump() {
	local position_number="$1"
	local copycat_file="$2"
	local scrollback_file="$3"

	local scrollback_line_number=$(_file_number_of_lines "$scrollback_file")
	local result_line="$(_get_result_line "$copycat_file" "$position_number")"
	local line_number=$(_get_line_number "$result_line" "$copycat_file" "$position_number")
	local match=$(_get_match "$result_line")
	local match_line_position=$(_get_match_line_position "$scrollback_file" "$line_number" "$match")
	_copycat_jump "$line_number" "$match_line_position" "$match" "$scrollback_line_number"
}

notify_about_first_last_match() {
	local current_position="$1"
	local next_position="$2"
	local message_duration="1500"

	# if position didn't change, we are either on a 'first' or 'last' match
	if [ "$current_position" -eq "$next_position" ]; then
		if [ "$NEXT_PREV" == "next" ]; then
			display_message "Last match!" "$message_duration"
		elif [ "$NEXT_PREV" == "prev" ]; then
			display_message "First match!" "$message_duration"
		fi
	fi
}

main() {
	if in_copycat_mode; then
		local copycat_file="$(get_copycat_filename)"
		local scrollback_file="$(get_scrollback_filename)"
		local current_position="$(get_copycat_position)"
		local next_position="$(get_new_position_number "$copycat_file" "$current_position")"
		do_next_jump "$next_position" "$copycat_file" "$scrollback_file"
		notify_about_first_last_match "$current_position" "$next_position"
		set_copycat_position "$next_position"
	fi
}
main
