#!/bin/sh
# today - keep a daily journal
# SPDX-FileCopyrightText: 2022-2023 Sotiris Papatheodorou
# SPDX-FileCopyrightText: 2024 Ángel Castañeda
# SPDX-License-Identifier: GPL-3.0-or-later
set -eu

usage() (
	name="${0##*/}"
	printf '%s v6.2.1\n' "$name"
	printf 'Usage: %s [[[YYYY-]MM-]DD]\n' "$name"
	printf '       %s grep ARGUMENT ...\n' "$name"
	printf '       %s log [-v]\n' "$name"
	printf '       %s -h\n' "$name"
)

fatal() {
	printf '%s\n' "$1" >&2
	usage >&2
	exit 2
}

# Usage: expand_date DATE
# Expand a date in the format DD or MM-DD to YYYY-MM-DD using the current month
# and year.
expand_date() {
	case "$1" in
		??)
			printf '%s\n' "$(date '+%Y-%m-')$1"
			;;
		??-??)
			printf '%s\n' "$(date '+%Y-')$1"
			;;
		*)
			printf '%s\n' "$1"
			;;
	esac
}

# Usage: journal_file DATE
# Print the filename of the journal file for DATE.
journal_file() {
	printf '%s/%s%s\n' "$TODAY_DIR" "$(expand_date "$1")" "$TODAY_SUFFIX" |
		valid_journal_files
}

# Usage: journal_files
# Print the filenames of all journal files, one per line.
journal_files() {
	find "$TODAY_DIR" ! -path "$TODAY_DIR" -prune -type f |
		valid_journal_files
}

# Usage: valid_journal_files
# Read filenames from standard input and print only those that correspond to
# valid today journals on standard output. Checks that the files are in
# $TODAY_DIR, end with $TODAY_SUFFIX and that their basename is a valid date.
valid_journal_files() {
	awk -v prefix="$TODAY_DIR/" -v suffix="$TODAY_SUFFIX" '
	function year(s) { sub("-..-..$", "", s); return s + 0 }
	function month(s) { sub("^....-", "", s); sub("-..$", "", s); return s + 0 }
	function day(s) { sub("^....-..-", "", s); return s + 0 }
	function is_leap(y) {
		if (y % 4 != 0) return 0
		else if (y % 100 != 0) return 1
		else if (y % 400 != 0) return 0
		else return 1
	}
	function valid_prefix(s) {
		return substr(s, 1, length(prefix)) == prefix
	}
	function valid_suffix(s) {
		return substr(s, length(s) - length(suffix) + 1) == suffix
	}
	BEGIN {
		days[1] = 31; days[2] = 28; days[3] = 31; days[4] = 30;
		days[5] = 31; days[6] = 30; days[7] = 31; days[8] = 31;
		days[9] = 30; days[10] = 31; days[11] = 30; days[12] = 31;
		gsub("/+", "/", prefix)
	}
	NF != 1 { next }
	{
		f = $0
		if (!valid_prefix(f)) next
		f = substr(f, length(prefix) + 1)
		if (!valid_suffix(f)) next
		f = substr(f, 1, length(f) - length(suffix))
		if (f !~ /^[0-9][0-9][0-9][0-9]-(0[1-9]|1[0-2])-([0-3][0-9])$/) next
		d = day(f)
		if (is_leap(year(f))) days[2] = 29; else days[2] = 28
		if (d < 1 || d > days[month(f)]) next
		print
	}'
}

edit_file() (
	if [ "$#" -gt 1 ]
	then
		fatal 'Too many arguments'
	fi
	TODAY_FILE=$(journal_file "$1")
	if [ -z "$TODAY_FILE" ]
	then
		fatal "Invalid date: $1"
	fi
	mkdir -p "${TODAY_FILE%/*}"
	set +e
	eval "$TODAY_CMD"
	set -e
	# Remove empty files.
	if [ ! -s "$TODAY_FILE" ]
	then
		rm -f "$TODAY_FILE"
	fi
)

search_files() (
	if [ "$#" -eq 0 ]
	then
		fatal 'Too few arguments'
	fi
	# Disable globbing and split fields only on newlines to allow passing
	# journal filenames with spaces to grep.
	set -f
	# shellcheck disable=SC2046
	env IFS= grep "$@" $(journal_files)
)

view_files() (
	case "$#" in
		0)
			sort_args='-r'
			;;
		1)
			if [ "$1" = '-v' ]
			then
				sort_args=
			else
				fatal "Invalid option to log: $1"
			fi
			;;
		*)
			fatal 'Too many options to log'
			;;
	esac
	# shellcheck disable=SC2086
	journal_files | sort $sort_args | while IFS= read -r f
	do
		printf '%s\n\n' "$(basename "$f" "$TODAY_SUFFIX")"
		${TODAY_OPEN:-cat} "$f"
		printf '\n\n\n'
	done | pager_if_terminal
)

pager_if_terminal() {
	if [ -t 1 ]
	then
		${PAGER:-more}
	else
		cat
	fi
}



# shellcheck disable=SC2016
TODAY_CMD=${TODAY_CMD:-'${EDITOR:-vi} "$TODAY_FILE"'}
TODAY_DIR=${TODAY_DIR-${XDG_DATA_HOME:-${HOME}/.local/share}/today}
TODAY_SUFFIX=${TODAY_SUFFIX-.md}

if [ "$#" -eq 0 ]
then
	set -- "$(date '+%Y-%m-%d')"
fi

case "$1" in
	g|gr|gre|grep)
		shift
		search_files "$@"
		;;
	l|lo|log)
		shift
		view_files "$@"
		;;
	-h)
		usage
		;;
	*)
		edit_file "$@"
		;;
esac
