#!/bin/sh

# Builds the site in the current repository

set -e

# Remember to always use "local" time formats in git commands!
export TZ="UTC0"

ENGINE_PATH="$(dirname "$0")"
SITE_PATH="$(readlink -f "$ENGINE_PATH/..")"
SRC_PATH="$SITE_PATH/src"
CSS_PATH="$SITE_PATH/css"

PAGE_TEMPLATE="$ENGINE_PATH/page.html"
NAV_TEMPLATE="$ENGINE_PATH/nav-item.html"
CSS_ITEM_TEMPLATE="$ENGINE_PATH/stylesheet.html"

REDIRECT_TEMPLATE="$ENGINE_PATH/redirect.html"
INDEX_TEMPLATE="$ENGINE_PATH/index-content.html"
INDEX_ITEM_TEMPLATE="$ENGINE_PATH/index-item.html"
HISTORY_TEMPLATE="$ENGINE_PATH/history-content.html"
HISTORY_ITEM_TEMPLATE="$ENGINE_PATH/history-item.html"

# Reserved names don't get deleted or built as pages
RESERVED_PAGENAMES="footer|README"
RESERVED_FILENAMES="src|engine|css|img|CNAME|robots\.txt"
DRAFT_PREFIX="DRAFT_"

FOOTER_FILE="$SRC_PATH/footer.html"

urlify() {
	# Convert a value to URL-safe(r) form
	echo "$1" \
		| tr -d '!"%^&*()+=[]{}:;@#|\\<>,.?\r\n'\' \
		| tr -s "[:space:]" "-" \
		| tr "[:upper:]" "[:lower:]"
}

page_name() {
	basename "$1" | cut -d. -f1 | sed -e "s/^$DRAFT_PREFIX//"
}

page_path() {
	source_file="$1"

	rel_path="$(dirname "$source_file" | sed -e "s|^$SRC_PATH/\?||")"

	if ! is_index_name "$source_file"; then
		name="$(page_name "$source_file")"
		rel_path="$rel_path/$name"
	fi

	readlink -m "$SITE_PATH/$(urlify "$rel_path")"
}


section_name() {
	echo "$1" \
		| xargs --null dirname \
		| sed -e "s|^$SRC_PATH||" \
		| xargs basename 2>/dev/null || echo ""
}


is_reserved_name() {
	basename "$1" \
		| grep -xE "$RESERVED_FILENAMES" > /dev/null \
	||
	page_name "$1" \
		| grep -xE "$RESERVED_PAGENAMES" >/dev/null
}

is_index_name() {
	[ "$(page_name "$1")" = "index" ]
}

is_draft_name() {
	basename "$1" | grep -e "^$DRAFT_PREFIX" >/dev/null
}

is_error_name() {
	basename "$1" \
		| cut -d"." -f1 \
		| grep -e "^[0-9]\{3\}$" >/dev/null
}


dir_indexable_files() {
	source_path="$1"

	# Explicitly check empty dir, as * below doesn't work as expected
	[ -z "$(ls "$source_path")" ] && return 0

	for source_file in "$source_path"/*; do
		is_reserved_name "$source_file" && continue
		is_draft_name "$source_file" && continue
		is_error_name "$source_file" && continue
		echo "$source_file"
	done
}

is_empty_src_dir() {
	source_path="$1"
	[ ! -d "$source_path" ] && return 1
	[ -n "$(dir_indexable_files "$source_path")" ] && return 1
	return 0
}


site_rel_path() {
	echo "$1" | sed "s|^$SITE_PATH||"
}


#
# Check environment
#

PANDOC="$(which pandoc || echo -n)"

ext_to_converter() {
	case "$1" in
		"html")
			echo -n 'cat'
			;;

		"md")
			if [ -z "$PANDOC" ]; then
				echo "ERROR: Pandoc must be installed to use markdown" >&2
				return 1
			fi

			echo -n "$PANDOC -f markdown-smart -t html"
			;;

		*)
			echo "WARNING: Unknown file type $extension" >&2
			return 0
			;;
	esac
}


#
# Derive site-wide parameters
#

capitalise() {
	read val
	echo -n "$val" | head -c1 | tr "[:lower:]" "[:upper:]"
	echo -n "$val" | tail -c+2
}


echo "# Building site"

SITE_NAME="$(
	{
		git describe --tags --first-parent --abbrev=0 2>/dev/null \
		|| git symbolic-ref --short HEAD 2>/dev/null \
		|| echo -n "Unnamed website"
	} \
	| sed 's/-/ /g' \
	| capitalise
)"
echo "$SITE_NAME"


echo "## Prepopulating page template"

SITE_PAGE_TEMPLATE="$(mktemp)"
echo "$SITE_PAGE_TEMPLATE"

cat "$PAGE_TEMPLATE" \
	| sed -e "s/<SITE-NAME>/$SITE_NAME/g" \
	> "$SITE_PAGE_TEMPLATE"


[ -f "$FOOTER_FILE" ] &&
	cat "$FOOTER_FILE" \
		| sed -i -e "/<FOOTER-CONTENT>/ { r /dev/stdin" -e ';d};' \
			"$SITE_PAGE_TEMPLATE"


echo "### Building main navigation"

build_pages_nav() {
	dir_indexable_files "$SRC_PATH" | while read -r page_file; do
		is_index_name "$page_file" && continue
		is_empty_src_dir "$page_file" && continue

		name="$(page_name "$page_file")"
		url="$(site_rel_path "$(page_path "$page_file")")"

		cat "$NAV_TEMPLATE" \
			| sed -e "s|<URL>|$url|g" \
				-e "s|<TITLE>|$name|g"
	done
}

build_pages_nav \
	| sed -i -e "/<PAGES-NAVIGATION>/ { r /dev/stdin" -e ';d};' \
		"$SITE_PAGE_TEMPLATE"


echo "## Removing previous files"
cd "$SITE_PATH"
for extant in *; do
	is_reserved_name "$extant" && continue

	rm -rfv "$extant"
done


#
# Build the individual pages
#

echo "## Generating pages"

page_index_file() {
	source_file="$1"

	output_path="$(page_path "$source_file")"
	output_index="$output_path/index.html"

	if is_error_name "$source_file"; then
		# Error handlers, eg. 404 need to be at a root'ier level
		output_index="$(dirname "$output_path")/$(page_name "$input_file").html"
	fi

	readlink -m "$output_index"
}

page_history_file() {
	source_file="$1"

	readlink -m "$(page_path "$source_file")/history.html"
}

relative_url() {
	source="$1"
	target="$2"

	if is_index_name "$target"; then
		target="$(dirname "$target")"
	fi
	realpath --relative-to "$(dirname "$source")" "$target"
}


page_title() {
	source_file="$1"

	section_title="$(section_name "$source_file")"

	if ! is_index_name "$source_file"; then
		# Regular file
		echo "$(page_name "$source_file") | $SITE_NAME"
	elif [ -z "$section_title" ]; then
		# Root index file
		echo "$SITE_NAME"
	else
		# Regular index file
		echo "$section_title | $SITE_NAME"
	fi
}


sub_page_vars() (
	input_file="$1"

	## NB. Using --reverse neuters --follow, so we tail instead
	date_created="$(git log --follow --find-renames=20% --author-date-order --date=local --pretty=format:"%as" -- "$input_file" | tail -n1)"
	date_updated="$(git log --follow --find-renames=20% -n1 --author-date-order --date=local --pretty=format:"%as" -- "$input_file")"

	sed \
		-e "s/<DATE-CREATED>/$date_created/g" \
		-e "s/<DATE-UPDATED>/$date_updated/g" \
		</dev/stdin
)


page_old_paths() {
	# Paths to where the page used to be
	source_file="$1"

	current_path="$(page_path "$source_file")"

	git log --follow --find-renames=20% --format="" --name-only -- "$source_file" \
		| while read -r old_file; do
			old_path="$(page_path "$(readlink -m "$old_file")")"
			[ "$current_path" = "$old_path" ] && continue
			echo "$old_path"
		done \
		| sort | uniq
}

build_redirects() {
	# Creates HTML redirection replacements for when page URLs change
	source_file="$1"

	old_paths="$(page_old_paths "$source_file")"
	[ -z "$old_paths" ] && return

	echo "#### Redirect pages"

	current_url="$(site_rel_path "$(page_path "$source_file")")"
	echo "$old_paths" \
		| while read -r old_path; do
			mkdir -p "$old_path"
			output_file="$old_path/index.html"
			[ -f "$output_file" ] && continue

			cat "$REDIRECT_TEMPLATE" \
				| sed -e "s|<PAGE-URL>|$current_url|g" \
				> "$output_file"
		done
}


dir_all_hashes() {
	# Returns all commit hashes affecting a directory
	# in no particular order
	source_path="$1"

	find "$source_path" -type f \
		| while read -r source_file; do
			git log --follow --find-renames=20% --format="%H" -- "$source_file"
		done \
		| sort | uniq
}

build_history_items() (
	source_path="$1"

	# Regular git log --follow doesn't follow renames for directories :(
	interesting_hashes="$(dir_all_hashes "$source_path")"
	git log --date=format-local:"%Y-%m-%d %H:%M:%SZ" --format="%H%x09%h%x09%ad%x09%s" \
		| grep -e "$interesting_hashes" \
		| while read -r line; do
			longhash="$(echo "$line" | cut -f1)"
			shorthash="$(echo "$line" | cut -f2)"
			date="$(echo "$line" | cut -f3)"
			subject="$(echo "$line" | cut -f4-)"
			cat "$HISTORY_ITEM_TEMPLATE" \
				| sed \
					-e "s|<LONGHASH>|$longhash|g" \
					-e "s|<SHORTHASH>|$shorthash|g" \
					-e "s|<DATE>|$date|g" \
					-e "s|<SUBJECT>|$subject|g"
		done
)

build_history() (
	source_path="$1"

	is_reserved_name "$source_path" && return 0

	echo "#### History page"

	output_file="$(page_history_file "$source_path")"
	output_path="$(dirname "$output_file")"
	rel_output_path="$(site_rel_path "$output_path")"
	if [ -z "$rel_output_path" ]; then
		rel_output_path="/"
	fi

	mkdir -p "$output_path"

	title="History | $(page_title "$source_path")"
	page_url="$(relative_url \
		"$output_file" \
		"$(page_index_file "$source_path")" \
		)"

	build_history_items "$source_path" \
		| sed -e "/<HISTORY-ITEMS>/ { r /dev/stdin" -e ';d};' \
			-e "s|<HISTORY-TITLE>|$rel_output_path|g" \
			"$HISTORY_TEMPLATE" \
		| sed -e "/<PAGE-CONTENT>/ { r /dev/stdin" -e ';d};' \
			"$SITE_PAGE_TEMPLATE" \
		| sed -e "s/<PAGE-TITLE>/$title/g" \
		| sed -e "s|<PAGE-URL>|$page_url|g" \
		| sed -e "s|<HISTORY-URL>||g" \
		| sub_css \
		| sub_page_vars "$source_path" \
		> "$output_file"
)


sub_css() (
	indent=""
	while IFS= read -r line; do
		if echo "$line" | grep '<STYLESHEETS>' >/dev/null; then
			indent="$(echo "$line" | sed -e 's/<STYLESHEETS>.*$//')"
			break
		fi
		echo "$line"
	done

	for source_file in "$CSS_PATH"/*; do
		stylesheet="$(site_rel_path "$source_file")"
		echo -n "$indent"
		cat "$CSS_ITEM_TEMPLATE" \
			| sed \
				-e "s|<URL>|$stylesheet|g"
	done

	while IFS= read -r line; do
		echo "$line"
	done
)


build_file() (
	input_file="$1"

	is_reserved_name "$input_file" && return 0

	output_path="$(page_path "$input_file")"
	output_index="$(page_index_file "$input_file")"

	rel_output_path="$(site_rel_path "$output_path")"
	if is_index_name "$input_file"; then
		rel_output_path="$rel_output_path/index"
	fi
	echo "### $rel_output_path"

	mkdir -p "$output_path"

	history_url="$(relative_url \
		"$output_index" \
		"$(page_history_file "$input_file")" \
		)"

	extension="$(basename "$input_file" | cut -d. -f2)"
	converter="$(ext_to_converter "$extension")"

	if [ -n "$converter" ]; then
		pages_navigation="$(build_pages_nav)"

		cat "$input_file" \
			| $converter \
			| sed -e "/<PAGE-CONTENT>/ { r /dev/stdin" -e ';d};' \
				"$SITE_PAGE_TEMPLATE" \
			| sed -e "s/<PAGE-TITLE>/$(page_title "$input_file")/g" \
			| sed -e "s|<HISTORY-URL>|$history_url|g" \
			| sub_css \
			| sub_page_vars "$input_file" \
			> "$output_index"

		build_history "$input_file"
		build_redirects "$input_file"
	fi
)


build_dir_index_items() (
	source_path="$1"

	dir_indexable_files "$source_path" \
		| sort --ignore-case \
		| while read -r source_file; do
			title="$(page_name "$source_file")"
			output_path="$(page_path "$source_file")"
			url="$(site_rel_path "$output_path")"

			cat "$INDEX_ITEM_TEMPLATE" \
				| sed -e "s|<URL>|$url|g" \
					-e "s|<TITLE>|$title|g"
		done
)

build_dir_index() (
	source_path="$1"

	dir_name="$(page_name "$source_path")"
	output_path="$(page_path "$source_path")"

	rel_output_path="$(site_rel_path "$output_path")"
	echo "### $rel_output_path/index"

	history_url="$(relative_url \
		"$(page_index_file "$source_path")" \
		"$(page_history_file "$source_path")" \
		)"

	build_dir_index_items "$source_path" \
		| sed -e "/<INDEX-ITEMS>/ { r /dev/stdin" -e ';d};' \
			-e "s/<INDEX-TITLE>/$dir_name/g" \
			"$INDEX_TEMPLATE" \
		| sed -e "/<PAGE-CONTENT>/ { r /dev/stdin" -e ';d};' \
			"$SITE_PAGE_TEMPLATE" \
		| sed -e "s/<PAGE-TITLE>/$(page_title "$source_path")/g" \
		| sed -e "s|<HISTORY-URL>|$history_url|g" \
		| sub_css \
		| sub_page_vars "$source_path" \
		> "$output_path/index.html"

	build_history "$source_path"
)

build_dir() (
	source_path="$1"
	indexed=""

	for source_file in "$source_path"/*; do
		if [ -d "$source_file" ]; then

			[ -z "$(ls "$source_file")" ] && continue
			build_dir "$source_file"
		else
			if is_index_name "$source_file"; then
				indexed="yup"
			fi
			build_file "$source_file"
		fi
	done

	if [ -z "$indexed" ] \
			&& [ -n "$(dir_indexable_files "$source_path")" ]; then
		build_dir_index "$source_path"
	fi
)

build_dir "$SRC_PATH"


echo "## Removing temporary files"
rm "$SITE_PAGE_TEMPLATE"
