#!/bin/sh

### Code:

usage() {
	cat <<\EOF
ht: Build a website
Usage:	ht -h
	ht [OPTIONS...] FILE...

FLAGS:
 -h	Show this help and exit.
 -B	Treat all targets as "new" (like in `make').

OPTIONS:
 -u BASEURL	The base URL of the built site.  Default: https://example.com
 -o OUTD	Output built files to OUTD.  Default: ./out/
 -w WORKD	Use WORKD as the working directory.  Default: /tmp/ht/
 -p PROC	Use PROC to process each input file.  Default: ./ht.awk
 -P TEMPLATE	Use TEMPLATE as the page template.  Default: ./page.tmpl.htm
 -i ITEMFMT	Format individual items with ITEMFMT in the site index.
 -I TEMPLATE	Use TEMPLATE as the index template.  Default: ./index.tmpl.htm
 -f ITEMFMT	Format individual items with ITEMFMT in the site feed.
 -F TEMPLATE	Use TEMPLATE as the feed template.  Default: ./feed.tmpl.xml
 -s DIRECTORY	The DIRECTORY holding static files (images, css, etc.).
			Default: ./static/
 -S DIRECTORY	The DIRECTORY to copy static files to.  Default: ./out/static.

PARAMETERS:
 FILE...	The list of files to include in the website.
EOF
	exit "${1:-0}"
}

main() {
	configure "$@"
	shift $((OPTIND - 1))
	prepare
	static_copy

	for input; do
		page_write "$PAGE_TEMPLATE" "$input"
	done

	if [ -n "$INDEXEACH" ]; then
		index_write "$INDEX_TEMPLATE" "$INDEXEACH" "$OUTD/$INDEXNAME"
	fi
	if [ -n "$FEEDEACH" ]; then
		index_write "$FEED_TEMPLATE" "$FEEDEACH" "$OUTD/$FEEDNAME"
	fi
	printf 'Done.  ' >&2
	if test -f "$WORKD/ok"; then
		eprint "No errors reported."
	else
		eprint "There were errors."
	fi
}

configure() {
	OUTD="${HT_OUT_DIR:-./out}"
	WORKD="${HT_WORKD:-/tmp/ht}"
	PROC="${HT_PROC:-./ht.awk}" # XXX: Needs better resolution
	BASEURL="${HT_BASEURL:-https://example.com}"
	PAGE_TEMPLATE="${HT_PAGE_TEMPLATE:-./page.tmpl.htm}"
	INDEX_TEMPLATE="${HT_INDEX_TEMPLATE:-./index.tmpl.htm}"
	FEED_TEMPLATE="${HT_FEED_TEMPLATE:-./feed.tmpl.xml}"
	ALLNEW=false
	STATICD="${HT_STATIC_DIR:-./static}"
	STATICOUT="${HT_STATIC_OUTPUT_DIR:-${OUTD}/static}"

	INDEXNAME="${HT_INDEX_NAME:-index.html}"
	if [ -n "$HT_INDEX_ITEM_FORMAT" ]; then
		INDEXEACH="$HT_INDEX_ITEM_FORMAT"
	else
		# shellcheck disable=2016
		INDEXEACH='<li><a href=\"$BASEURL/${file}/\">${title}</a></li>'
	fi

	FEEDNAME="${HT_FEED_NAME:-feed.xml}"
	if [ -n "$HT_FEED_ITEM_FORMAT" ]; then
		FEEDEACH="$HT_FEED_ITEM_FORMAT"
	else
		# shellcheck disable=2016
		FEEDEACH='<entry>
		<id>$BASEURL/$file</id>
		<link rel=\"alternate\" href=\"$BASEURL/$file/\" />
		<title>${title}</title>
		<updated>$(date -u -d "@${time}" -R)</updated>
		<author>$AUTHOR</author>
		<content type=\"text/html\">
		<![CDATA[$(cat "$WORKD/${file}.body")]]>
		</content>
		</entry>'
	fi

	# shellcheck disable=2034 # BASEURL is used in templates
	while getopts hBo:w:p:i:I:f:F:u:s:S: opt; do
		case "$opt" in
		h) usage ;;
		o) OUTD="$OPTARG" ;;
		w) WORKD="$OPTARG" ;;
		p) PROC="$OPTARG" ;;
		P) PAGE_TEMPLATE="$OPTARG" ;;
		i) INDEXEACH="$OPTARG" ;;
		I) INDEX_TEMPLATE="$OPTARG" ;;
		f) FEEDEACH="$OPTARG" ;;
		F) FEED_TEMPLATE="$OPTARG" ;;
		u) BASEURL="$OPTARG" ;;
		B) ALLNEW=true ;;
		s) STATICD="$OPTARG" ;;
		S) STATICOUT="$OPTARG" ;;
		*) usage 1 ;;
		esac
	done

	INDEX="$WORKD/index.txt"
}

prepare() {
	test -n "$WORKD" && rm -rf "$WORKD"
	mkdir -p "$OUTD" "$WORKD"
	test -x "$PROC" || {
		eprint "Can't find processor: $PROC"
		exit 2
	}
	touch "$WORKD/ok"
}

### Utilities

print() {
	printf '%s\n' "$*"
}

eprint() {
	print "$@" >&2
}

olderp() { # olderp REF OTHER
	# Is REF older than OTHER ?  Necessary b/c test -ot is not POSIX.
	a="$1"
	b="$2"

	if a_age="$(stat -c%Y "$a" 2>/dev/null)"; then
		a_exist=true
	else
		a_exist=false
	fi
	if b_age="$(stat -c%Y "$b" 2>/dev/null)"; then
		b_exist=true
	else
		b_exist=false
	fi

	if $a_exist && ! $b_exist; then
		# B doesn't exist -- so B is newer
		# eprint "$a is older than $b (doesn't exist)"
		return 0
	elif ! $a_exist && $b_exist; then
		# A doesn't exist -- so A is newer
		# eprint "$a (doesn't exist) is newer than $b"
		return 1
	else
		# A's age > B's age ?
		# eprint "$a ($a_age) <= $b ($b_age)?"
		test "$a_age" -le "$b_age"
	fi
}

uptodate() { # uptodate FILE DEPENDENCIES... # && return
	# Check if FILE is up-to-date with DEPENDENCIES.
	$ALLNEW && return 1
	uptodate=1
	file="$1"
	shift
	for dep in "$@"; do
		olderp "$dep" "$file" && uptodate=0
	done
	return $uptodate
}

### File conversion

template_expand() { # template_expand TEMPLATES...
	end="tmpl_$(date +%s)_${count:=0}"
	eval "$(
		print "cat <<$end"
		cat "$@"
		print
		print "$end"
	)"
	count=$((count + 1))
}

meta_save() { # meta_save [-c COMMENTCH] [-m METACH] META_FILE < INPUT
	COMMENTCH=';'
	METACH='@'
	while getopts c:m: opt; do
		case "$opt" in
		c) COMMENTCH="$OPTARG" ;;
		m) METACH="$OPTARG" ;;
		*) ;;
		esac
	done
	shift $((OPTIND - 1))
	sed -n "s/^${COMMENTCH}${COMMENTCH}${METACH}//p" 2>/dev/null | tee "$1"
}

meta_clear() { # meta_clear META_FILE
	while read -r line; do
		case "$line" in
		*'()'*) unset -f "${line%()*}" ;;
		*=*) unset -v "${line%=*}" ;;
		*) ;;
		esac
	done <"$1" 2>/dev/null
}

page_write() { # html_write TEMPLATE INPUT
	template="$1"
	file="$2"

	fn="${file##*/}"
	fn="${fn%%.*}"
	out="${OUTD}/${fn}/index.html"
	mkdir -p "${out%/*}"
	meta="$WORKD/${fn}.meta"

	eval "$(meta_save "$meta" <"$file")"
	stat -c "%Y	${fn}	$meta" "$file" >>"$INDEX"

	uptodate "$out" "$template" "$file" && return

	eprint "Page:  $file -> $out"
	if
		! "$PROC" "$file" |
			tee "$WORKD/${fn}.body" |
			template_expand "$template" >"$out"
	then
		eprint "$file -> $out ... ERROR!"
		rm "$WORKD/ok"
		return
	fi
	meta_clear "$meta"
}

index_write() { # index_write TEMPLATE EACH OUTFILE
	template="$1"
	each="$2"
	out="$3"

	test -f "$INDEX" || return 1
	uptodate "$out" "$template" "$INDEX" && return

	eprint "Index: $out"
	# shellcheck disable=2034 # file and time can be used in `each'
	sort -k1,1 -nr "$INDEX" |
		while IFS='	' read -r time file meta; do
			# shellcheck disable=1090
			. "$meta"
			if ! item="$(eval print "\"$each\"")"; then
				eprint "ERROR: couldn't add '$file' to $out"
				rm "$WORKD/ok"
			fi
			print "$item"
			meta_clear "$meta"
		done |
		template_expand "$template" >"$out"
}

### Static files

static_copy() { # static_copy
	test -d "$STATICD" || return 1
	if command -v rsync 2>/dev/null; then
		eprint rsync -avz "$STATICD/" "$STATICOUT/"
		rsync -avz "$STATICD/" "$STATICOUT/"
	else
		eprint cp -r "$STATICD"/* "$STATICOUT/"
		cp -r "$STATICD"/* "$STATICOUT/"
	fi
}

### Do the thing

test "$DEBUG" && set -x
test "$SOURCE" || main "$@"