#!/bin/bash
# [b]rowse - overview of given files or current directory
# depends: tput stat bat checkaccess(in my dotfiles)
# optdepends: timg, neovim (compressed files), pdftoppm (PDF), mtn (video), audiowaveform, imagemagick (images)
# args: files to inspect, any arg starting with dash is passed on to bat
#
# Supports:
# - listing directories
# - listing contents of any compressed file with neovim
# - visual files are displayed with timg
#   video thumbnails via mtn, pdf pages from pdftoppm
# - text files are displayed through bat
# Automatically requests elevation through sudo when needed

set -o pipefail

inspect=false

opts='itvh'
while getopts "$opts" OPTION; do
  case "$OPTION" in
  	# inspect: Show file info without preview
  	(i) inspect=true;;
    (t) tree=true;;
	(v) set -eo xtrace;;
    (h|?) echo "Usage: $(basename $0) [-$opts] <paths...>" && exit 2;;
  esac
done
shift "$(($OPTIND -1))"

checkperm() {
	checkaccess -r "$@" || elevate=sudo
	mime="$(test -n "$shifted" || $elevate file --dereference --mime "$@")"
}
fileinfo() {
	tput setaf 6
	for arg
	do case "$arg" in (-*) continue;; esac
		$elevate file --exclude elf -E "$arg"
		$elevate ssh-keygen -l -f "$arg" 2>/dev/null
		# TODO do not grep bitrate but extract properly
		#probe="$($elevate ffprobe "$arg" 2>&1)"
		#echo $probe | grep -v -e '00:00:00.04' -e 'ansi' &&
		$elevate ffprobe -hide_banner "$arg" 2>&1 | grep "bitrate: ....\? " | sed 's/, start:[^,]\+,/,/' ||
			$elevate stat --format "%U:%G %A %s $(
					size="$($elevate unzip -l "$arg" 2>/dev/null | tail -1)" &&
						echo "(uncompressed $(echo $size | cut -d' ' -f1 | numfmt --to=iec-i --suffix=B))"
				) - birth %.10w mod %.10y" "$arg" | numfmt --field=3 --to=iec-i --padding=6 --suffix=B
	done
	tput sgr0
}

prefix=/tmp/b
mkdir -p "$prefix"
declare -a timg timga bat batplain ls
for arg; do
	case "$arg" in (-*) flags="$flags $arg"; continue;; esac
	checkperm "$arg"
	if ! $elevate test -e "$arg"
	then if test -h "$arg"
		then fileinfo "$arg"
		else echo "File not found: '$arg'" 1>&2
		fi
		continue
	fi
	# amount of columns in a grid
	grid=$(expr $(tput cols) / \( 25 - \( $# / 2 \) \& $# \< 30 \| 5 \))
	tmpfile="$prefix/$(basename "$arg")"
	mkdir -p "$prefix"
	case "$mime" in
	(*\ application/pdf\;*)
		echo Converting "$arg"
		grid=$(expr $(tput cols) \* $# / $(tput lines))
		grid=$(expr 3 \& $grid \< 3 \| $grid)
		#limit=$(expr $grid \& \( $grid \> 3 \| $# \> 1 \) \| $grid '*' 2)
		limit=$grid
		test -f "$tmpfile-1.ppm" || pdftoppm -forcenum -r 70 "$arg" "$tmpfile" -l $limit
		find "$prefix" -path "$tmpfile*.ppm" | sort | head -$limit | xargs -d'\n' timg -W --grid=$grid
		;;
	(*\ application/*document*|*.xlsx:\ *)
		# https://ask.libreoffice.org/t/convert-to-command-line-parameter/840/4
		echo Converting "$arg"
		soffice --headless --convert-to png --outdir "$prefix" "$arg" >/dev/null
		timg+=("${tmpfile%.*}.png");;
	(*/x-xcf*)
		echo Converting "$arg"
		convert -flatten "$arg" png:"$tmpfile"
		timg+=("$tmpfile");;
	(*\ video/*)
		suffix=_thumbs.jpg
		mtn -q -i -t -W -r2 -D6 -b 0,6 -c $grid -w $(expr $(tput cols) '*' 20) \
			-O "$prefix" -o "$suffix" "$arg"
		timg -W "$prefix/$(basename "${arg%.*}")$suffix"
		;;
	(*\ image/*)
		timg+=("$arg")
		which identify >/dev/null && continue;;
	(*\ inode/directory\;*)
		ls+=("$arg")
		test -L "$arg" || continue
		;;
	(*)
		case "$(file --dereference "$arg")" in
		(*\ ?udio*)
			# TODO preconvert aac - |*\ ADTS\ *
			if ! $inspect && which audiowaveform >/dev/null; then
				img="$tmpfile.png"
				case $TERM in (*-kitty) kitty=true; audioheight=2;; (*) audioheight=5;; esac
				find "$img" -not -empty 2>/dev/null | grep --quiet . ||
				audiowaveform --quiet --pixels-per-second 2 --height 36 --width 2000 --amplitude-scale auto \
					--background-color 000000 --waveform-color 99BBFF --axis-label-color 000000 \
					--input-filename "$arg" --output-format png >"$img" && {
						test "$kitty" && timg -g x$audioheight "$arg" &&
							printf "\\033[${audioheight}A%$(expr $audioheight \* 3)s"
						timg -g x$audioheight --auto-crop --upscale "$img"
					}
			fi
			timga+=("$arg")
			;;
		(*:\ *compress*|*\ archive*)
			list="$tmpfile-list.txt"
			case "$arg" in
				(*.7z) test $# = 1 && 7z l "$arg";;
				(*.tar*) tar --list --file "$arg";;
				(*)
				if test $# = 1
				then nvim "$arg"
				else case "$arg" in (*.part);; (*)
					nvim -es "+2w$list|5,w>>$list" "$arg"
					batplain+=("$list");;
					esac
				fi
			esac
			;;
		(*:\ *database*) highlight "Tables" && sqlite3 "$arg" ".tables";;
		(*:\ data) ;;
		(*) bat+=("$arg")
			timga+=("$arg")
			continue
			;;
		esac
		;;
	esac
	fileinfo "$arg"
done

# timg: images
# timga: potentially viewable as image
if test "$timg"; then
	# TODO Don't show info on all images for gifs
	$inspect ||
		if which timg >/dev/null
		then $elevate timg $(test "$timga" && echo "-V") --rotate=exif -g $(tput cols)x$(expr $(tput lines) / 2) \
			$(test $# -gt 1 &&
			echo "-t0.2 --center $(test $# -lt 20 && echo "--title") --grid=$((grid < $# ? grid : $#))x2") \
			"${timg[@]}" "${timga[@]}" 2>/dev/null || true
		else for img in "${timg[@]}"
			do catimg -H $(expr $(tput lines) / 2) $img
			done
		fi
	if which identify >/dev/null && ( $inspect || test $# -lt 10 ); then
		tput setaf 6
		for img in "${timg[@]}"
		do case $img in (*.gif) continue;; esac
			ident="$(identify -ping -precision 3 -format "%wx%h %b %m %[bit-depth]-bit %[colorspace]" "$img")"
			printf "%11s %-30s	%s\n" "${ident%% *}" "$(basename "$img")" "${ident#* }"
		done
		tput sgr0
	fi
fi

pager="${PAGER:-'less -RF'}"
# bat: unknown files
# batplain: files to print without header
if test "$bat" -o "$batplain"; then
	test "$(bat --version | cut -d. -f2)" -gt 16 && rule=,rule
	if test $# -gt ${#bat[@]} -a $# -gt ${#batplain[@]} && test -z "$flags"
	then cut="--line-range :7"
		batpager="cut -c-$(expr $(tput cols) \* 19 / 10 | cut -d. -f1)"
	fi
	batcommand="$elevate bat $cut $flags --pager"
	batstyle="--style plain$rule"
	test "$batplain" && $batcommand "${batpager:-$pager}" $batstyle "${batplain[@]}"
	test "$bat" &&
		if test "$cut" && ! $inspect
		then case $TERM in (*kitty)
				declare -a timgtxt
				for file in "${bat[@]}"
				do txt="$prefix/$(basename "$file").txt"
					cp "$file" "$txt"
					timgtxt+=("$txt")
				done
				timg -V --grid="$(expr 4 \& ${#bat[@]} \> 5 \| ${#bat[@]})" --title="%b" --frames=3 "${timgtxt[@]}"
				#fileinfo "${bat[@]}"
				;;
			(*) for file in "${bat[@]}"; do
					$batcommand "${batpager:-$pager}" $batstyle "$file"
					fileinfo "$file"
				done;;
			esac
		else
			if ! $inspect
			then json=false
				if which cj >/dev/null; then
					json=true
					for file in "${bat[@]}"; do
						case $file in (*.json|*.geojson);; (*) json=false;; esac
					done
				fi
				if $json
				then cj "${bat[@]}"
				else $batcommand "${batpager:-$pager}" $batstyle,header$(test $# -gt 1 && echo ",numbers") "${bat[@]}"
				fi
			fi
			if $inspect || test $# -lt $(expr $(tput lines) / 3);
			then fileinfo "${bat[@]}"
			fi
		fi
fi

if test "$ls" -o $# -le $(echo "$flags" | wc -w); then
	checkperm .
	# Alternative: find -exec ls -dl {} +
	{
		# TODO handle single quotes in filenames
		# TODO listing sometimes doubles as exa prints partial listings while working
		timeout .6s sh -c "
			if test '$tree'
			then $elevate tree -a --dirsfirst --du -h -C -L 3 $flags $(printf "'%s' " "${ls[@]:-.}")
			elif which exa 2>/dev/null >&2
			then $elevate exa --icons --color=always --long --group --classify --all --all --sort=changed --reverse $flags $(printf "'%s' " "${ls[@]:-.}")
			else $elevate ls -l $(test $# -gt ${#ls[@]} && echo '-d') --color=always --human-readable --si --group-directories-first --file-type --dereference-command-line-symlink-to-dir --all $flags $(printf "'%s' " "${ls[@]:-.}")
			fi
		" || $elevate ls $(test $# -gt ${#ls[@]} && echo "-d") --color=always --human-readable --si --dereference-command-line --all --sort=none $flags "${ls[@]:-.}"
	} | $pager --quit-if-one-screen
fi
