#!/usr/bin/env bash

VERSION="1.3.0"
config_path="${XDG_CONFIG_HOME:-${HOME}/.config}/gsu"

notify() {
	echo -e "$1"

	if [[ ! -z "$notify_arg" ]] && type -p notify-send >/dev/null; then
		notify-send "gsu" "$1"
	fi
}

print_help() {
	echo "\
Usage: gsu [OPTION]... SOURCE
A general screenshot and upload utility for images, video, and gifs.

SOURCE defaults to \$XDG_CONFIG_HOME/gsu/imgs if nothing is provided.
The most common location for \$XDG_CONFIG_HOME is ~/.config.

GENERAL OPTIONS
  -h, --help                 Show the help menu
  -v, --version              Show the current version
  -l, --list-displays        List your current displays and their resolutions.
  --terminal                 Open interactive commands in a new terminal.
                             SET THIS IF RUNNING FROM ANYTHING OTHER THAN A TERM,
                             i.e. xbindkeys

  --notify                   Send a libnotify notification of the output.

UTILITY OPTIONS
  -u, --upload               Upload after running the utility
  -n, --nocursor             Hide the cursor
  -d, --display NUM|TYPE     Set the selection to a specific display.
                             Can read from an argument or from stdin.
                             Values: number of display, 'active', 'all' (default)

  -r, --dmenu                Select the below action from dmenu or rofi.
  -s, --screenshot           Take a screenshot
  -m, --video                Record a video of the screen
  -g, --gif                  Record a video and convert it to the gif format

VIDEO OPTIONS
  -c, --countdown NUM        Countdown before recording"

	exit 0
}

print_version() {
	echo "gsu $VERSION"
	exit 0
}

print_displays() {
	local monitors=$(xrandr | grep -o "[0-9]*x[0-9]*[+-][0-9]*[+-][0-9]*")

	if [[ -z "$monitors" ]]; then
		echo "gsu: unable to find any displays"
		exit 1
	fi

	readarray -t monitors <<< "$monitors"
	local displays=""

	for ((i = 0; i < ${#monitors[@]}; i++)); do
		((num = i + 1))
		displays="${displays}\n${num}: ${monitors[$i]}"
	done

	displays=$(sed 's/\\n//' <<< $displays)
	echo -e "$displays"

	exit 0
}

print_not_int() {
	regex=$(echo "$1" | sed -e "s/^-\{1,2\}//")

	echo "gsu: value for '$regex' is not an integer"
	exit 1
}

print_invalid_option() {
	regex=$(echo "$1" | sed -e "s/^-\{1,2\}//")

	echo "\
gsu: invalid option -- '$regex'
Try 'gsu --help' for more information."

	exit 1
}

print_invalid_operand() {
	echo "\
gsu: invalid operand -- '$1'
Valid characters: alphanumeric, dot, slash, underscore, dash."

	exit 1
}

print_missing_deps() {
	local args=""

	for arg in "$@"; do
		args="$args '$arg'"
	done

	args=$(sed 's/ //' <<< $args)
	echo "gsu: missing required dependency(s) -- $args"

	exit 1
}

print_manually_quit() {
	echo "gsu: manually quit from user action"
	exit 1
}

get_args() {
	while [[ "$1" ]]; do
		case "$1" in
			"-h" | "--help") print_help ;;
			"-v" | "--version") print_version ;;
			"-u" | "--upload") upload="$1" ;;
			"-l" | "--list-displays") print_displays ;;

			"--terminal")
				if [[ -z "$GSU_TERM" ]]; then
					notify "\
gsu: \$GSU_TERM is not set
Set this variable in '$config_path/config.conf'."

					exit 1
				fi

				terminal_arg="$1"
			;;

			"--notify") notify_arg="$1" ;;

			"-d" | "--display")
				if [[ -t 0 ]]; then
					display="$2"
				else
					display=$(cat)
					display="${display:0:1}"

					if [[ -z "$display" ]]; then
						notify "gsu: display is empty, assuming quit from dmenu or rofi"
						exit 1
					fi
				fi
			;;

			"-r" | "--dmenu")
				if type -p dmenu >/dev/null; then
					dmenu_type=(dmenu)
				fi

				if type -p rofi >/dev/null; then
					dmenu_type=(rofi -dmenu -p "gsu > ")
				fi

				if [[ -z "$dmenu_type" ]]; then
					print_missing_deps "dmenu or rofi"
				else
					rofi_arg="$1"
				fi
			;;

			"-s" | "--screenshot") set_type "screenshot" ;;
			"-m" | "--video") set_type "video" ;;
			"-g" | "--gif") set_type "gif" ;;
			"-n" | "--nocursor") nocursor="-u"; draw_mouse="-draw_mouse 0" ;;

			"-c" | "--countdown")
				if ! [[ $2 =~ ^[0-9]+$ ]]; then
					print_not_int "$1"
				fi

				countdown_enable="$2"
			;;

			-*) print_invalid_option "$1" ;;

			*)
				if [[ "$1" =~ ^[a-zA-Z0-9_.\/-]+$ ]]; then
					operand="$1"
				else
					print_invalid_operand "$1"
				fi
			;;
		esac

		shift
	done
}

get_config() {
	mkdir -p "$config_path"

	if [[ ! -f "$config_path/config.conf" ]]; then
		if [[ -f "SYSCONFDIR/config.conf" ]]; then
			cp "SYSCONFDIR/config.conf" "$config_path"
		elif [[ -f "config/config.conf" ]]; then
			cp "config/config.conf" "$config_path"
		else
			echo "gsu: unable to find the config file"
			exit 1
		fi
	fi

	source "$config_path/config.conf"
}

get_command() {
	local MONW="0"
	local MONH="0"
	local MONX="0"
	local MONY="0"

	if [[ ! -z "$display" ]]; then
		if ! type -p xrandr >/dev/null; then
			print_missing_deps "xrandr"
		fi

		if ! type -p xdotool >/dev/null; then
			print_missing_deps "xdotool"
		fi

		local MONITORS=$(xrandr | grep -o "[0-9]*x[0-9]*[+-][0-9]*[+-][0-9]*")
		local XMOUSE=$(xdotool getmouselocation | awk -F "[: ]" '{print $2}')
		local YMOUSE=$(xdotool getmouselocation | awk -F "[: ]" '{print $4}')

		readarray -t MONITORS <<< "$MONITORS"

		for ((i = 0; i < ${#MONITORS[@]}; i++)); do
			local mon="${MONITORS[$i]}"

			local TEMPMONW=$(echo -e $mon | awk -F "[x+]" '{print $1}')
			local TEMPMONH=$(echo -e $mon | awk -F "[x+]" '{print $2}')
			local TEMPMONX=$(echo -e $mon | awk -F "[x+]" '{print $3}')
			local TEMPMONY=$(echo -e $mon | awk -F "[x+]" '{print $4}')

			case "$display" in
				"active")
					if (($XMOUSE >= $TEMPMONX)); then
						if (($XMOUSE <= $TEMPMONX + $TEMPMONW)); then
							if (($YMOUSE >= $TEMPMONY)); then
								if (($YMOUSE <= $TEMPMONY + $TEMPMONH)); then
									MONW=$TEMPMONW
									MONH=$TEMPMONH
									MONX=$TEMPMONX
									MONY=$TEMPMONY
								fi
							fi
						fi
					fi
				;;

				*)
					if [[ "$display" =~ ^[0-9]+$ ]]; then
						if (($display == $i + 1)); then
							MONW=$TEMPMONW
							MONH=$TEMPMONH
							MONX=$TEMPMONX
							MONY=$TEMPMONY
						fi
					else
						if (($TEMPMONW > $MONW)); then
							((MONW=$MONW + $TEMPMONW))
						fi

						if (($TEMPMONH > $MONH)); then
							((MONH=$MONH + $TEMPMONH))
						fi

						if (($TEMPMONX + $TEMPMONW >= $MONW)); then
							((MONW=$TEMPMONW + $TEMPMONX))
						fi

						if (($TEMPMONY + $TEMPMONH >= $MONH)); then
							((MONH=$TEMPMONH + $TEMPMONY))
						fi
					fi
				;;
			esac
		done

		if [[ "$MONW" == "0" ]] && [[ "$MONH" == "0" ]] && [[ "$MONX" == "0" ]] && [[ "$MONY" == "0" ]]; then
			notify "gsu: unable to find your display"
			exit 1
		fi
	fi

	case "$type" in
		"screenshot")
			if [[ -z "$display" ]]; then
				cmd="$(maim $nocursor -s "$operand" 2>&1>/dev/null)"
			else
				cmd="$(maim $nocursor -g "${MONW}x${MONH}+${MONX}+${MONY}" "$operand" 2>&1>/dev/null)"
			fi
		;;

		"video" | "gif")
			if [[ -z "$display" ]]; then
				read -r X Y W H G ID < <(slop -f "%x %y %w %h %g %i")

				if [[ -z "$X" ]] || [[ -z "$Y" ]] || [[ -z "$W" ]] || [[ -z "$H" ]]; then
					print_manually_quit
				fi
			fi

			if [[ ! -z "$countdown_enable" ]]; then
				countdown "$countdown_enable" "Handing control over to ffmpeg in COUNTNUM seconds."
			fi

			if [[ -z "$AUDIO" ]]; then
				if ! type -p pulseaudio >/dev/null; then
					notify "\
gsu: no audio device set, default device 'pulse' cannot be found
Either install pulseaudio, or add your audio device to `$config_path/config.conf`."

					exit 1
				fi

				AUDIO="pulse"
			fi

			echo "Press [q] or [Ctrl+C] to stop recording."

			if [[ -z "$display" ]]; then
				cmd=$(ffmpeg -y -f alsa -i $AUDIO -r 30 -f x11grab -show_region 1 $draw_mouse -s "$W"x"$H" -i :0.0+$X,$Y -c:v libx264 -crf 18 -preset:v ultrafast -c:a aac -b:a 192k -loglevel error "$output" 2>&1>/dev/null)
			else
				cmd=$(ffmpeg -y -f alsa -i $AUDIO -r 30 -f x11grab -show_region 1 $draw_mouse -s "$MONW"x"$MONH" -i :0.0+$MONX,$MONY -c:v libx264 -crf 18 -preset:v ultrafast -c:a aac -b:a 192k -loglevel error "$output" 2>&1>/dev/null)
			fi
		;;
	esac
}

set_type() {
	if [[ -z "$type" ]]; then
		type="$1"
	fi
}

countdown() {
	for ((i = $1; i > 0; i--)); do
		echo -ne "\r${2//COUNTNUM/$i}"
		sleep 1
	done

	echo
}

log() {
	if [[ ! -d "$config_path/logs" ]]; then
		mkdir -p "$config_path/logs"
	fi

	echo "$1" >"$config_path/logs/$(date +'%Y.%m.%d-%H.%M.%S.%N').log"
}

check_valid_exts() {
	local filename="${operand##*/}"
	local extension="${filename##*.}"

	IFS="," read -ra exts <<< "$1"

	for ext in "${exts[@]}"; do
		if [[ "$extension" == "$ext" ]]; then
			return
		fi
	done

	notify "gsu: invalid $type file extension -- '$extension'"
	exit 1
}

take_screenshot() {
	if ! type -p maim >/dev/null; then
		print_missing_deps "maim"
	fi

	get_command

	if [[ ! -z "$cmd" ]]; then
		if [[ "$cmd" == *"keystroke or right-click"* ]]; then
			print_manually_quit
		fi

		log "$cmd"

		notify "\
gsu: maim returned an error
Check the most recent log in '$config_path/logs'."

		exit 1
	fi
}

take_video() {
	local output="$operand"

	if ! type -p slop >/dev/null; then
		print_missing_deps "slop"
	fi

	if ! type -p ffmpeg >/dev/null; then
		print_missing_deps "ffmpeg"
	fi

	if [[ "$type" == "gif" ]]; then
		output="$operand.mp4"
	fi

	get_command

	if [[ -z "$cmd" ]]; then
		echo "Stopped recording."
	else
		log "$cmd"

		notify "\
gsu: ffmpeg returned an error
Check the most recent log in '$config_path/logs'."

		exit 1
	fi
}

make_gif() {
	if ! type -p ffmpeg >/dev/null; then
		print_missing_deps "ffmpeg"
	fi

	echo "Generating your GIF. This could take awhile depending on how large the selection was."
	local cmd=$(ffmpeg -y -i "$operand.mp4" -vf "fps=30,palettegen" -loglevel error "/tmp/palette.png" 2>&1>/dev/null)

	if [[ -z "$cmd" ]]; then
		cmd=$(ffmpeg -y -i "$operand.mp4" -i "/tmp/palette.png" -filter_complex "fps=30,scale=flags=lanczos[x];[x][1:v]paletteuse" -loglevel error "$operand")

		if [[ -z "$cmd" ]]; then
			return
		fi
	fi

	log "$cmd"

	notify "\
gsu: ffmpeg returned an error
Check the most recent log in '$config_path/logs'."

	exit 1
}

upload() {
	pushd $(dirname $operand) &>/dev/null
	local absolute="$PWD/$(basename $operand)"
	popd &>/dev/null

	if ! type -p jq >/dev/null; then
		print_missing_deps "jq"
	fi

	local cmd

	case "$type" in
		"screenshot")
			if [[ -z "$S_UPLOAD" ]]; then
				cmd=$(curl --progress-bar -F "randomname=true" -F "file=@$absolute" "https://uguu.se/api.php?d=upload-tool")
			else
				cmd=$(eval "${S_UPLOAD//GSUFILEOUT/$absolute}")
			fi
		;;

		"video")
			if [[ -z "$V_UPLOAD" ]]; then
				if [[ -z "$STREAMABLE_USER" ]] || [[ -z "$STREAMABLE_PASS" ]]; then
					notify "\
gsu: invalid streamable credentials
Fill in your credentials in '$config_path/config.conf' and try again."

					exit 1
				else
					cmd=$(curl --progress-bar -u "$STREAMABLE_USER:$STREAMABLE_PASS" -F "file=@$absolute" "https://api.streamable.com/upload" | jq -r ".shortcode")

					if [[ "${#cmd}" == 5 ]]; then
						cmd="https://streamable.com/$cmd"
					fi
				fi
			else
				cmd=$(eval "${V_UPLOAD//GSUFILEOUT/$absolute}")
			fi
		;;

		"gif")
			if [[ -z "$G_UPLOAD" ]]; then
				local cmd=$(curl -s -X POST -H "Content-Type: application/json" "https://api.gfycat.com/v1/gfycats")

				if echo "$cmd" | jq -e ".gfyname" &>/dev/null; then
					local output=$(echo "$cmd" | jq -r ".gfyname")
					cp "$absolute" "$output"

					cmd=$(curl --progress-bar -w "%{http_code}" --upload-file "$output" "https://filedrop.gfycat.com")
					rm "$output"

					if [[ "$cmd" == "200" ]]; then
					 	echo "Gfycat is processing..."
						local count=0

						while [[ "$(curl -s https://api.gfycat.com/v1/gfycats/fetch/status/$output | jq -e -r '.task')" != "complete" ]]; do
							((count++))

							if [[ $count == 300 ]]; then
								notify "gsu: gfycat timed out while processing"
								exit 1
							fi

							sleep 1
						done

						cmd="https://gfycat.com/$output"
					else
						notify "gsu: gfycat returned an invalid http code -- '$cmd'"
						exit 1
					fi
				fi
			else
				cmd=$(eval "${G_UPLOAD//GSUFILEOUT/$absolute}")
			fi
		;;
	esac

	if type -p xclip >/dev/null && [[ "$cmd" == "http"* ]]; then
		echo -n "$cmd" | xclip
		echo -n "$cmd" | xclip -sel clip
	fi

	notify "$cmd"
}

remove_args() {
	local args=()

	while [[ "$1" ]]; do
		if [[ "$1" == "--terminal" ]]; then
			shift
		else
			args+=("$1")
			shift
		fi
	done

	echo "${args[@]}"
}

main() {
	get_config
	get_args "$@"

	if [[ -z "$terminal_arg" ]]; then
		if [[ ! -z "$rofi_arg" ]]; then
			case $(echo -e "Take a screenshot\nRecord a video\nRecord a video and covert it to the gif format" | "${dmenu_type[@]}") in
				"Take a screenshot") set_type "screenshot" ;;
				"Record a video") set_type "video" ;;
				"Record a video and covert it to the gif format") set_type "gif" ;;
				*) print_manually_quit ;;
			esac
		fi

		set_type "screenshot"

		if [[ -z "$operand" ]]; then
			local ext=""

			case "$type" in
				"screenshot") ext=".png" ;;
				"video") ext=".mp4" ;;
				"gif") ext=".gif" ;;
			esac

			mkdir -p "$config_path/imgs"
			operand="$config_path/imgs/$(date +"%F.%H-%M-%S.%N")$ext"
		fi

		case "$type" in
			"screenshot")
				if [[ -z "$S_UPLOAD" ]]; then
					check_valid_exts "png,jpg"
				fi

				take_screenshot
			;;

			"video")
				if [[ -z "$V_UPLOAD" ]]; then
					check_valid_exts "mp4,mkv,mov"
				fi

				take_video
			;;

			"gif")
				if [[ -z "$G_UPLOAD" ]]; then
					check_valid_exts "gif"
				fi

				take_video
				make_gif
			;;
		esac

		if [[ ! -z "$upload" ]]; then
			upload
		else
			notify "gsu: saved to '$operand'"
		fi
	else
		pushd $(dirname ${BASH_SOURCE[0]}) &>/dev/null
		local absolute="$PWD/$(basename ${BASH_SOURCE[0]})"
		popd &>/dev/null

		local args=$(remove_args "$@")

		for ((i = 0; i < ${#GSU_TERM[@]}; i++)); do
			if [[ "${GSU_TERM[$i]}" == *"CMD"* ]]; then
				GSU_TERM[$i]="${GSU_TERM[$i]//CMD/$absolute $args}"
			fi
		done

		eval "${GSU_TERM[@]}"
	fi

	exit 0
}

main "$@"
