#! /bin/bash
#                  _
#   ___ ___  _ __ | | ___   _  __ _  ___ _ __
#  / __/ _ \| '_ \| |/ / | | |/ _` |/ _ \ '_ \
# | (_| (_) | | | |   <| |_| | (_| |  __/ | | |
#  \___\___/|_| |_|_|\_\\__, |\__, |\___|_| |_|
#                       |___/ |___/
#
# conkygen.sh
# Create a Conky configuration file dynamically
#
# Copyright (c) 2020 Vintage Salt <rehashedsalt@cock.li>
# Distributed under the terms of the MIT License
#
set -e

# Read-only set-once variables
declare -r _name="$(basename -- "$0")"
# Options
declare _optout="${XDG_CONFIG_HOME:-$HOME/.config}/conky/conky.conf"
declare -i _optbar=1
declare -i _optgraph=1
declare -i _opthelp
declare -i _optverbose
declare -i _opttopitems=4
# Working variables
declare -a _args
declare _return

# Helper functions
log() {
	# Print a line to the terminal if _optverbose is greater than $2
	# $2 defaults to 0
	# loglevel 0: Daily-use messages
	# loglevel 1: Detailed but not quite debugging
	# loglevel 2: Definitely debugging
	[ -z "$1" ] && return 1
	if (( _optverbose >= ${2:-0} )); then
		printf "%s\\n" "$1"
	fi
}
warn() {
	# Print a yellow line to the terminal, respecting _optverbose
	[ -z "$1" ] && return 1
	if (( _optverbose >= ${2:-0} )); then
		if [ -t 1 ]; then
			printf "\\e[33m%s\\e[0m\\n" "$1"
		else
			printf "WARN: %s\\n" "$1"
		fi
	fi
}
error() {
	# Print a red line to the terminal, exit if $2 is specified
	[ -z "$1" ] && return 1
	if [ -t 2 ]; then
		printf "\\e[31m%s\\e[0m\\n" "$1" 1>&2
	else
		printf "ERROR: %s\\n" "$1" 1>&2
	fi
	[ -z "$2" ] && return
	exit "${2:-1}"
}
has() {
	# Parse out all arguments and try to find them in path
	# If an argument cannot be found, set _return and fail
	for prog in "$@"; do
		if ! command -v "$prog" > /dev/null 2>&1; then
			_return="$prog"
			return 1
		fi
	done
	return 0
}

# Core program functions
printhelp() {
	cat << EOF
Usage: $_name [OPTION]...
Generate a conky.conf configuration file in the current directory

  -o			Specify output file. Defaults to
			~/.config/conky/conky.conf
  -b			Hide bars
  -g			Hide graphs
  -h			Print this help text
  -t			Number of items to print in top lists. Defaults to 4
  -v			Print more status messages. Stacks

Copyright (c) 2020 rehashedsalt@cock.li
Licensed under the MIT license
EOF
}
step_header() {
	# Clobber output file with header
	cat << EOF > "$_optout"
-- vim: ts=4 sw=4 noet ai cindent syntax=lua
-- WARNING:
-- This file was generated with conkygen. See gen.sh if you need to edit this!
-- Any changes you make here can and probably will be clobbered!
EOF
}
step_config() {
	# Append config to the output file
	cat << EOF >> "$_optout"
conky.config = {
	own_window = true,
	own_window_title = 'Conky',
	own_window_class = 'Conky',
	own_window_type = 'desktop',
	own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
	own_window_argb_visual = true,
	own_window_transparent = true,
	own_window_colour = '000000',

	double_buffer = true,
	cpu_avg_samples = 2,
	net_avg_samples = 2,
	no_buffers = true,
	update_interval = 1,

	use_xft = true,
	font = 'IBM Plex Sans:style=Light:size=9',
	default_color = '#ebdbb2',
	color1 = '282828', -- Background
	color2 = 'ebdbb2', -- Foreground
	color3 = '32302f', -- Background-alt
	color4 = 'b8bb26', -- Green (Battery)
	color6 = '8ec07c', -- Cyan (Network)
	color7 = 'fabd2f', -- Yellow (Disk)
	color8 = '83a598', -- Blue (CPU)
	color9 = 'de869b', -- Magenta (Memory)

	alignment = 'top_left',
	xinerama_head = 0,
	minimum_height = 0,
	minimum_width = 304,
	border_width = 0,
	border_outer_margin = 0,
	border_inner_margin = 16,
	gap_x = 22,
	gap_y = 66,

	format_human_readable = true,
	temperature_unit = "celsius",
	if_up_strictness = "link",

	default_bar_height = 11,
	default_bar_width = 130,
	default_graph_height = 36,
	draw_borders = true,
	draw_outline = false,
	draw_graph_borders = true,
	draw_shades = false
}
EOF
}
step_text() {
	local -r nproc="$(nproc)"
	# Generate our actual functional Conky stuff
	# Header
	cat <<- 'EOF' >> "$_optout"
	conky.text = [[
	${font IBM Plex Sans:style=Light:size=64}$alignr${time %l:%M %p}$font
	${font IBM Plex Sans:style=Light:size=18}$alignr${time %A, %B %d, %Y}$font
	${font IBM Plex Sans:style=Light:size=12}${acpitemp}°C$alignr${no_update $nodename_short}$font
	EOF

	# CPU info
	cat <<- 'EOF' >> "$_optout"
	${color3}$hr$color
	${font IBM Plex Sans:style=Medium:size=9}CPU$font: $freq_g GHz$alignr$cpu%
	EOF
	if (( _optgraph > 0 )); then
		local gpl=1
		if (( nproc <= 2 )); then
			gpl=1
		elif (( nproc <= 4 )); then
			gpl=2
		elif (( nproc <= 8 )); then
			gpl=4
		else
			gpl=8
		fi
		local width=$(( ( 400 - ( gpl - 1 ) * 3 ) / gpl ))
		log "Generating $gpl graphs per row of CPU core stats" 1
		log "Graphs will be $width wide" 2
		local cpuinfo
		for i in $(seq 1 "$gpl" "$nproc"); do
			# Generate lines
			local line
			for j in $(seq 0 $(( gpl - 1 ))); do
				# Generate graphs
				local cpu=$(( i + j ))
				log "Generating graph for CPU $cpu" 2
				if [ -z "$line" ]; then
					line="\${cpugraph cpu$cpu 36,$width}"
				else
					line="$line \${cpugraph cpu$cpu 36,$width}"
				fi
			done
			# Trim whitespace
			line="\${color8}${line%" "}\$color"
			log "Appending line: $line" 2
			cpuinfo="$cpuinfo$line\n"
			unset line
		done
		while [ "$cpuinfo" != "${cpuinfo%\\n}" ]; do
			cpuinfo="${cpuinfo%\\n}"
		done
		echo -e "$cpuinfo" >> "$_optout"
	fi
	for i in $(seq $_opttopitems); do
		echo "\${color8}\${top name $i}\$color\$alignr\${top cpu $i}%" >> "$_optout"
	done

	# Memory info
	cat <<- 'EOF' >> "$_optout"
	${color3}$hr$color
	${font IBM Plex Sans:style=Medium:size=9}Memory$font: $mem / $memmax used$alignr$memperc%
	EOF
	if (( _optgraph > 0 )); then
		echo -e '${color9}${memgraph}$color' >> "$_optout"
	fi
	if (( _optbar > 0 )); then
		echo -e 'Memory$alignr$memeasyfree ${color9}${membar}$color' >> "$_optout"
		if [ -n "$(swapon)" ]; then
			echo "Swap\$alignr\$swapfree \${color9}\${swapbar}\$color" >> "$_optout"
		fi
	fi
	for i in $(seq $_opttopitems); do
		echo "\${color9}\${top_mem name $i}\$color\$alignr\${top_mem mem $i}%" >> "$_optout"
	done

	# Disk info
	cat <<- 'EOF' >> "$_optout"
	${color3}$hr$color
	${font IBM Plex Sans:style=Medium:size=9}Disk$font (R/W):$alignr${diskio /dev/sda}
	EOF
	if (( _optgraph > 0 )); then
		echo '${color7}${diskiograph_read /dev/sda 36,200} ${diskiograph_write /dev/sda 36,200}$color' >> "$_optout"
	fi
	# Mountpoint detection
	if (( _optbar > 0 )); then
		for mountpoint in / /home /var /usr /opt /boot /boot/efi; do
			if mountpoint $mountpoint > /dev/null 2>&1; then
				echo "$mountpoint\$alignr\${fs_free $mountpoint} \${color7}\${fs_bar $mountpoint}\$color" >> "$_optout"
			fi
		done
	fi
	for i in $(seq $_opttopitems); do
		echo "\${color7}\${top_io name $i}\$color\$alignr\${top_io io_perc $i}%" >> "$_optout"
	done

	# Networking
	cat <<-'EOF' >>"$_optout"
	${color3}$hr$color
	${font IBM Plex Sans:style=Medium:size=9}Network$font:${alignr}Default $gw_iface${if_up wlp1s0}
	EOF
	for interface in $(iw dev | awk '$1=="Interface"{print $2}'); do
		echo "$interface (U/D): \$alignr\${addr $interface} / \${wireless_essid $interface}" >> "$_optout"
		if (( _optgraph > 0 )); then
			echo "\${color6}\${upspeedgraph $interface 36,200} \${downspeedgraph $interface 36,200}\$color" >> "$_optout"
		fi
	done
	echo "]]" >> "$_optout"
}
genconfig() {
	log "Generating config: $_optout"
	step_header
	step_config
	step_text
}

# Main
main() {
	# Parse out arguments
	while [ -n "$1" ]; do
		# Parse out flags
		while getopts ":bgho:t:v" opt; do
			case $opt in
				b)
					_optbar=0
					;;
				g)
					_optgraph=0
					;;
				o)
					_optout="$OPTARG"
					;;
				h)
					_opthelp=1
					;;
				t)
					_opttopitems="$OPTARG"
					;;
				v)
					_optverbose+=1
					;;
				:)
					error "Option requires argument: -$OPTARG" 2
					;;
				*)
					error "Invalid option: -$OPTARG" 2
					;;
			esac
		done
		# Store arguments
		shift $((OPTIND - 1))
		if [ -n "$1" ]; then
			_args+=("$1")
			shift
		fi
		unset OPTIND
	done
	# Early hook for help
	[ -n "$_opthelp" ] && printhelp && exit 0
	# Validate critical options
	if ! (( _opttopitems >= 0 )); then
		error "Option -t requires non-negative integer as argument" 2
	fi
	# Validate core program dependencies
	log "Validating dependencies" 2
	if ! has awk basename iw seq; then
		error "Failed to find program: $_return" 1
	fi

	# Do the do
	genconfig
	exit 0
}

main "$@"