#!/bin/sh

# Written by Aetnaeus.
# Source: https://github.com/lemnos/theme.sh.
# Licensed under the WTFPL provided this notice is preserved.

# Find a broken theme? Want to add a missing one? PRs are welcome.

VERSION=v1.1.5

# Use truecolor sequences to simulate the end result.

preview() {
	awk -F": " -v target="$1" '
		BEGIN {
			"tput cols" | getline nc
			"tput lines" | getline nr
			nc = int(nc)
			nr = int(nr)
		}

		/^# Themes/ { start++;next }
		!start { next }

		function hextorgb(s) {
			hexchars = "0123456789abcdef"
			s = tolower(s)

			r = (index(hexchars, substr(s, 2, 1))-1)*16+(index(hexchars, substr(s, 3, 1))-1)
			g = (index(hexchars, substr(s, 4, 1))-1)*16+(index(hexchars, substr(s, 5, 1))-1)
			b = (index(hexchars, substr(s, 6, 1))-1)*16+(index(hexchars, substr(s, 7, 1))-1)
		}

		function fgesc(col) {
			hextorgb(col)
			return sprintf("\x1b[38;2;%d;%d;%dm", r, g, b)
		}

		function bgesc(col) {
			hextorgb(col)
			return sprintf("\x1b[48;2;%d;%d;%dm", r, g, b)
		}

		$0 == target {s++}

		s && /^foreground:/ { fg = $2 }
		s && /^background:/ { bg = $2 }
		s && /^[0-9]+:/ { a[$1] = $2 }

		/^ *$/ {s=0}

		function puts(s,   len,   i,   normesc,   filling) {
			normesc = sprintf("\x1b[0m%s%s", fgesc(fg), bgesc(bg))

			len=s
			gsub(/\033\[[^m]*m/, "", len)
			len=length(len)

			filling=""
			for(i=0;i<(nc-len);i++) filling=filling" "

			printf "%s%s%s%s\n", normesc, s, normesc, filling, ""
			nr--
		}

		END {
			puts("")
			for (i = 0;i<16;i++)
				puts(sprintf("  %s Color %d\x1b[0m", fgesc(a[i]), i))

			# Note: Some terminals use different colors for bolded text and may produce slightly different ls output.

			puts("")
			puts(" # ls --color -F")
			puts(sprintf("    file"))
			puts(sprintf("    \x1b[1m%sdir/", fgesc(a[4])))
			puts(sprintf("    \x1b[1m%sexecutable", fgesc(a[10])))
			puts(sprintf("    \x1b[1m%ssymlink\x1b[0m%s%s", fgesc(a[6]), fgesc(fg), bgesc(bg)))


			while(nr > 0) puts("")

			printf "\x1b[0m"
		}
	' < "$0"
}

# Alphabetize and dedupe theme list.

normalize_themes() {
	awk '
		# We could eliminate the sorting logic by using gnu extensions but that would reduce portability.

		function cmp(a,b,ordTbl,    i,c1,c2,n) {
			n = length(a) > length(b) ? length(b) : length(a)
			for(i = 1;i <= n;i++) {
				c1 = substr(a, i, 1)
				c2 = substr(b, i, 1)

				if(c1 != c2)
					return ordTbl[c1] < ordTbl[c2]
			}

			return length(a) < length(b)
		}

		function sort(a,n,    i,j,tmp,ordTbl) {
			 for(i = 0;i < 256;i++) ordTbl[sprintf("%c", i)] = i

			for(i = 0;i < n;i++) {
				tmp = a[i]
				j = i-1
				while(j >= 0 && cmp(tmp, a[j], ordTbl)) {
					a[j+1] = a[j]
					j--
				}

				a[j+1] = tmp
			}
		}

		function sortKeys(a,keys,    n,k) {
			for(k in a)
				keys[n++] = k
			sort(keys, n)
			return n
		}

		/^ *$/ { inTheme = 0;next }
		!inTheme { name = $0;inTheme=1;themes[name] = "";next }
		inTheme { themes[name] = themes[name]$0"\n" }

		END {
			n = sortKeys(themes, names)

			print ""
			for(i = 0;i < n;i++) {
				print names[i]
				print tolower(themes[names[i]])
			}
		}
	' "$@"
}

# Generate themes from one or more supplied kitty config files.

generate_themes() {
	awk -v argc=$# '
		function chkProp(prop) {
			if(!props[prop]) {
				printf "ABORTING: %s is missing required property '\''%s'\''\n", currentFile, prop > "/dev/stderr"
				aborted++
				exit -1
			}
		}

		function printTheme(    name,i,prop) {
			name = currentFile
			gsub(/.*\//, "", name)
			gsub(/\.conf$/, "", name)

			print name

			for (i = 0;i < 16;i++) {
				prop = sprintf("color%d", i)

				chkProp(prop)
				printf "%d: %s\n", i, props[prop]
			}

			chkProp("foreground")
			chkProp("background")
			chkProp("cursor")

			print "foreground: "props["foreground"]
			print "background: "props["background"]
			print "cursor: "props["cursor"]
			print ""
		}

		FILENAME != currentFile {
			if(currentFile)
				printTheme()

			currentFile = FILENAME
			delete props
		}

		{ props[$1] = $2 }

		END { if(!aborted) printTheme() }
	' "$@"
}

# Add themes to the script from one or more supplied kitty config files.

add() {
	tmp1="$(mktemp)"
	tmp2="$(mktemp)"

	awk 'i { print } /^# Themes/ { i++ }' "$0" > "$tmp1"
	echo "" >> "$tmp1"
	generate_themes "$@" >> "$tmp1" || exit $?

	awk '{print} /^# Themes/ { exit }'  "$0" > "$tmp2"
	normalize_themes "$tmp1" >> "$tmp2"


	rm "$tmp1"
	cat "$tmp2" > "$0" || exit $?

	printf 'Successfully annexed %d themes. More! Feed me more!\n' $#
}

preview2() {
	INHIBIT_THEME_HIST=1 "$0" "$1"

	printf '\033[30mColor 0\n'
	printf '\033[31mColor 1\n'
	printf '\033[32mColor 2\n'
	printf '\033[33mColor 3\n'
	printf '\033[34mColor 4\n'
	printf '\033[35mColor 5\n'
	printf '\033[36mColor 6\n'
	printf '\033[37mColor 7\n'

	printf '\033[90mColor 8\n'
	printf '\033[91mColor 9\n'
	printf '\033[92mColor 10\n'
	printf '\033[93mColor 11\n'
	printf '\033[94mColor 12\n'
	printf '\033[95mColor 13\n'
	printf '\033[96mColor 14\n'
	printf '\033[97mColor 15\n'

	printf '\n\033[0m'
	printf '# ls --color -F\n'
	printf '    file\n'
	printf '    \033[01;34mdir/\033[0m\n'
	printf '    \033[01;32mexecutable\033[0m*\n'
	printf '    \033[01;36msymlink\033[0m\n'

	printf '\033[0m'
}

# Consumes a theme.sh definition from STDIN and applies it.

apply_theme() {
awk '
	function tmuxesc(s) { return sprintf("\033Ptmux;\033%s\033\\", s) }
	function normalize_term() {
		# Term detection voodoo

		if(ENVIRON["TERM_PROGRAM"] == "iTerm.app")
			term="iterm"
		else if(ENVIRON["TMUX"]) {
			"tmux display-message -p \"#{client_termname}\"" | getline term
			"tmux display-message -p \"#{client_termtype}\"" | getline termname

			if(substr(termname, 1, 5) == "iTerm")
				term="iterm"
			is_tmux++
		} else
			term=ENVIRON["TERM"]
	}

	BEGIN {
		normalize_term()

		if(term == "iterm") {
			bgesc="\033]Ph%s\033\\"
			fgesc="\033]Pg%s\033\\"
			colesc="\033]P%x%s\033\\"
			curesc="\033]Pl%s\033\\"
		} else {
			#Terms that play nice :)

			fgesc="\033]10;#%s\007"
			bgesc="\033]11;#%s\007"
			curesc="\033]12;#%s\007"
			colesc="\033]4;%d;#%s\007"
		}

		if(is_tmux) {
			fgesc=tmuxesc(fgesc)
			bgesc=tmuxesc(bgesc)
			curesc=tmuxesc(curesc)
			colesc=tmuxesc(colesc)
		}
	}

	/^foreground:/ { printf fgesc, substr($2, 2) > "/dev/tty" }
	/^background:/ { printf bgesc, substr($2, 2) > "/dev/tty" }
	/^cursor:/ { printf curesc, substr($2, 2) > "/dev/tty" }
	/^[0-9]+:/ { printf colesc, $1, substr($2, 2) > "/dev/tty" }
'
}

# Sets the current theme given a name and does the requisite bookkeeping.

set_current_theme() {
	awk -F": " -v target="$1" -v script="$0" '
		/^# Themes/ { start++;next; }
		!start { next }

		$0 == target { found++;next; }

		found { theme = theme $0 "\n" }
		found && /^ *$/ { exit }

		END {
			if(found) {
				printf "%s", theme | script

				config_dir = (ENVIRON["XDG_CONFIG_HOME"] ?  ENVIRON["XDG_CONFIG_HOME"] : ENVIRON["HOME"])

				histfile = config_dir"/.theme_history"
				inhibit_hist=ENVIRON["INHIBIT_THEME_HIST"]

				if(!inhibit_hist) {
					while((getline < histfile) > 0)
						if($0 != target)
							out = out $0 "\n"
					close(histfile)

					out = out target
					print out > histfile
				}
			} else {
					printf "Theme not found: %s\n", target > "/dev/stderr"
					exit(-1)
			}
		}
	' < "$0"
}

# Dump the current theme in a format consumable by theme.sh
# by attempting to read it from the terminal.
#
# NOTE: Many terms don't support this properly (e.g alacritty)

# Refs

# https://github.com/microsoft/terminal/issues/3718
# https://github.com/alacritty/alacritty/blob/master/alacritty_terminal/src/ansi.rs#L972

print_current_theme() {
	awk '
	function print_response(s) {
		names["10;"] = "foreground"
		names["11;"] = "background"
		names["12;"] = "cursor"
		for (i = 0; i < 16; i++)
			names[sprintf("4;%d;", i)] = i

		split(s, a, "]")
		for (i in a) {
			if (match(a[i], /rgb:/)) {
				key = substr(a[i], 1, RSTART-1)

				r=substr(a[i], RSTART+4, 2)
				g=substr(a[i], RSTART+9, 2)
				b=substr(a[i], RSTART+14, 2)

				printf "%s: %s\n", names[key], "#"r g b
			}
		}
	}

	# We cant just use RS/getline for this since
	# mawk does input buffering :(.

	function read_response() {
		buf = ""

		# Accrue data until we encounter the terminating CSI response
		while ((end=index(buf,"[")) == 0) {
			# poor POSIX mans read :/
			cmd="dd if=/dev/tty bs=1024 count=1 2> /dev/null"

			while (cmd|getline data)
				buf = buf data

			close(cmd)
		}

		buf = substr(buf, 1, end-1)
		return buf
	}

	BEGIN {
		system("stty cbreak -echo")

		tty = "/dev/tty"

		# Yo dawg, I heard you like multiplexers...
		if (ENVIRON["TMUX"]) {
			# If we are running inside tmux we sent the request sequences
			# to the currently attached terminal. Note that we still
			# read the result from the virtual terminal.

			# Flow:
			# theme.sh (request) -> tty (response) -> pts (response) -> theme.sh
			# where pts is the tmux pseudoterminal.

			"tmux display-message -p \"#{client_tty}\""|getline tty
		}

		# Terminals may ignore these.

		for(i=0;i<16;i++)
			printf "\033]4;%d;?\007", i > tty

		printf "\033]10;?\007" > tty
		printf "\033]11;?\007" > tty
		printf "\033]12;?\007" > tty

		# Use a CSI DA1 sequence (supported by all terms)
		# as a sentinel value to indicate end-of-response.
		# (assumes request-response order is fifo)

		printf "\033[c" > tty

		print_response(read_response())

		system("stty -cbreak echo")
	}
	'
}

isColorTerm() {
	if [ -z "$TMUX" ]; then
		[ -n "$COLORTERM" ]
	else
		tmux display-message -p '#{client_termfeatures}'|grep -q RGB
	fi
}

list() {
	case "$filterFlag" in
		--light) filter=2 ;;
		--dark) filter=1 ;;
		*) filter=0 ;;
	esac

	awk -v filter="$filter" -F": " '
		BEGIN {
			config_dir = ENVIRON["XDG_CONFIG_HOME"] ?  ENVIRON["XDG_CONFIG_HOME"] : ENVIRON["HOME"]

			histfile = config_dir"/.theme_history"
			while((getline < histfile) > 0) {
				mru[nmru++] = $0
				mruIndex[$0] = 1
			}
		}

		function luma(s,    r,g,b,hexchars) {
			hexchars = "0123456789abcdef"
			s = tolower(s)

			r = (index(hexchars, substr(s, 2, 1))-1)*16+(index(hexchars, substr(s, 3, 1))-1)
			g = (index(hexchars, substr(s, 4, 1))-1)*16+(index(hexchars, substr(s, 5, 1))-1)
			b = (index(hexchars, substr(s, 6, 1))-1)*16+(index(hexchars, substr(s, 7, 1))-1)

			return 0.2126 * r + 0.7152 * g + 0.0722 * b
		}

		/^# Theme/ { st++;next }
		!st { next }

		/^ *$/ { inner = 0;next }
		!inner { name = $0;inner++;next }

		/^background/ {
			if((filter == 1 && luma($2) > 130) ||
			   (filter == 2 && luma($2) <= 130))
				next

			candidates[name] = 1
		}

		END {
			for(c in candidates) {
				if(!mruIndex[c])
					print(c)
			}

			for(i = 0;i < nmru;i++)
				if(candidates[mru[i]])
					print(mru[i])
		}
	' < "$0"
}


if [ -z "$1" ]; then
	if [ -t 0 ]; then
		echo "usage: $(basename "$0") [-v] [-h] <option>|<theme>"
		exit 255
	else
		apply_theme
		exit 0
	fi
fi

case "$1" in
	--dark|--light)
		filterFlag=$1
		shift
		;;
esac

case "$1" in
-h|--help)
		cat << "!"
usage: theme.sh [--light] | [--dark] <option> | <theme>

  If <theme> is provided it will immediately be set. Otherwise --dark or
  --light optionally act as filters on the supplied option. Theme history is
  stored in ~/.theme_history or ($XDG_CONFIG_HOME/.theme_history if set) by
  default and will be used for ordering the otherwise alphabetical theme list
  in the relevant options (-l/-i/-i2).

  E.G:
    'theme.sh --dark -i'

  will start an interactive selection of dark themes with the user's
  most recently selected themes at the bottom of the list.

  Theme definitions consistent with the internal format can also be piped
  directly into the script.

  E.G:

  # theme.sh < input

  Where input has the form:

      0: #4d4d4d
      1: #4d4d4d
      ...
      foreground: #dcdccc
      background: #3f3f3f
      cursor: #dcdccc

OPTIONS
  -l,--list                 Print all available themes.
  -i,--interactive          Start the interactive selection mode (requires fzf).
  -i2,--interactive2        Interactive mode #2. This shows the theme immediately
                            instead of showing it in the preview window. Useful
			    if your terminal does have TRUECOLOR support.
  -r,--random               Set a random theme and print its name to stdout.
  -a,--add <kitty config>   Annexes the given kitty config file.
  -p,--print-theme          Attempt to read the current theme from the terminal
                            and print it to stdout in a format consumable by theme.sh.
                            NOTE: not all terminals support this option,
		            do not rely on it in scripts.
  -v,--version              Print the version and exit.

SCRIPTING
  If used from within a script, you will probably want to set
  INHIBIT_THEME_HIST=1 to avoid mangling the user's theme history.
!
	;;
-p|--print-theme)
	print_current_theme
	;;
-i2|--interactive2)
	command -v fzf > /dev/null 2>&1 || { echo "ERROR: -i requires fzf" >&2; exit 1; }
	"$0" $filterFlag -l|fzf\
		--tac\
		--bind "enter:execute-silent($0 {})+accept"\
		--bind "ctrl-c:execute($0 -l|tail -n1|xargs $0)+abort"\
		--bind "esc:execute($0 {};echo {})+abort"\
		--exact\
		--no-sort\
		--preview "$0 --preview2 {}"
	;;
-r|--random)
	# Sort -R is not portable :/

	theme=$($0 $filterFlag -l|awk '{a[n++]=$0};END{srand();print(a[int(rand()*n)])}')
	$0 "$theme"
	echo "Theme: $theme"
	;;
-i|--interactive)
	command -v fzf > /dev/null 2>&1 || { echo "ERROR: -i requires fzf" >&2; exit 1; }
	if ! isColorTerm; then
		printf "WARNING: This does not appear to be a truecolor terminal, falling back to -i2
         (use -i2 explicitly to get rid of this message or set COLORTERM)\n\n" >&2
		"$0" $filterFlag -i2
	else
		"$0" $filterFlag -l|fzf\
			--tac\
			--exact\
			--bind "ctrl-c:abort"\
			--bind "esc:execute(echo {})+abort"\
			--bind "enter:execute-silent($0 {})+accept"\
			--no-sort\
			--preview "$0 --preview {}"
	fi
	;;
-l|--list)
	list
	;;
-a|--add)
	shift
	add "$@"
	;;
--preview2)
	preview2 "$2"
	;;
--preview)
	preview "$2"
	;;
-v|--version)
	echo "$VERSION (original source https://github.com/lemnos/theme.sh)"
	;;
*)
	set_current_theme "$1"
	;;
esac

exit $?

# Themes start here (avoid editing by hand)

ultramar-dark
0: #33333a
1: #b73030
2: #6d974b
3: #b2872f
4: #3f6e90
5: #9c6992
6: #5b8277
7: #b0afa8
8: #676775
9: #c45c5c
10: #92b078
11: #e2b55a
12: #81acc1
13: #b48ead
14: #7fac96
15: #faf6e5
foreground: #fcf8e2
background: #151517
cursor: #ffffff

ultramar-light
0: #151517
1: #b73030
2: #6d974b
3: #b2872f
4: #3f6e90
5: #9c6992
6: #5b8277
7: #91908d
8: #33333a
9: #c45c5c
10: #92b078
11: #e2b55a
12: #75a2b8
13: #b48ead
14: #7fac96
15: #b0afa8
foreground: #1b1e25
background: #faf6e5
cursor: #000000