#!/usr/bin/env bash

help()
{
	cat <<-EOF
	Copyright 2023 Tj <hacker@iam.tj>
	Licensed on the terms of the GNU General Public Licence version 3.

	Parse a plain Makefile that may contain comments with HTML formatting clues
	Act on the clues (inserting hyperlinks, wrapping comment blocks in spans)
	Insert into HTML file, replacing any existing block.

	## HTML file:
	Contains two comments to mark begin/end of insertion point
	<!-- MakefileBegin -->
	<!-- MakefileEnd -->

	## Makefile parsing:
	Any comment blocks (including empty lines) in the Makefile will be surrounded
	 with a SPAN set to a CSS class
	<span class="comment">
	# some comment

	# some more comment
	# multi-line comment
	</span>

	The SPAN will end before the first non-comment, non-empty, non-whitespace, line

	The default class name is "comment"
	This can be over-ridden in the calling environment with:

	export SCLASS="myclass"
	$0

	or

	SCLASS="myclass" $0

	### Hyperlinks
	The Makefile can contain hyperlink marker comments
	Marker comments are removed from the output
	Each marker is in the line immediately following the line that needs the hyperlink inserting
	The marker MUST be at the start of its line
	It begins with zero or more TAB characters followed by the comment character #
	0 or more spaces follow
	A sequence of 1 or more ^ characters indicate the text to be wrapped in a hyperlink
	The ^ characters are aligned under the text in the previous line to be hyperlinked
   (so needs a fixed-width font whilst editing to be accurate)
	1 space follows the ^ characters
	<a> element attributes follow the space (href, id, and others)
	End of Line immediately follows the attributes

	E.g:
	#!/usr/bin/env make -f
	#^^^^^^^^^^^^^^^^^^^^^ href="https://en.wikipedia.org/wiki/Shebang_(Unix)"
	# Here is an example link
	#            ^^^^^^^ href="https://example.com"
	#
	 this would result in:
	<pre>
	<a target="mDoc" href="https://en.wikipedia.org/wiki/Shebang_(Unix)">#!/usr/bin/env make -f</a>
	# Here is an <a target="mDoc" href="https://example.com">example</a> link
	</pre>

	Hyperlinks with an HREF have a named target so that following them doesn't replace the current page
	The default target name is "mDoc"
	This can be over-ridden in the calling environment with:

	export ATARGET="mytarget"
	$0

	or

	ATARGET="mytarget" $0

	Hyperlinks can also be ANCHORS - targets to link to.
	Any <a> element with an ID attribute is an anchor
	It doesnt need an HREF attribute
	E.g:
	.PHONY:
	#^^^^^^ id="phony"
	 this would result in:
	<a id="phony">.PHONY:</a>

  Any ID attributes are added to a sorted index.
	The index is inserted into the HTML between these markers:
	<!-- IndexBegin -->
	<!-- IndexEnd -->

	The index entries are hyperlinks pointing to ANCHORS in the document.
	Activating any index entry will jump to the ANCHOR.
	
	EOF
}

if [[ -z "$1" ]] || [[ -z "$2" ]]; then
	echo "usage: $0 path/to/valid/Makefile path/to/template.html"
	help
	exit 1
fi

if ! [[ -f "$1" ]]; then
	echo "error: file not found '$1'"
	exit 2
fi
if ! [[ -f "$2" ]]; then
	echo "error: file not found '$2'"
	exit 3
fi
if ! [[ -w "${2%/*}" ]]; then
	echo "error: need write permission for '${2%/*}'"
	exit 4
fi
if ! mapfile -t makefile < "$1"; then
	echo "error: unable to mapfile '$1'"
	exit 5
fi
if ! mapfile -t html < "$2"; then
	echo "error: cannot mapfile '$2'"
	exit 6
fi

ATARGET="${ATARGET:-mDoc}"
SCLASS="${SCLASS:-comment}"

[[ -z "$DEBUG" ]] &&  cp --backup=numbered "$2" "$2.bak"

# keep track of index elements
declare -A index

# first: identify and insert A elements
lines="${#makefile[@]}"
[[ -n "$DEBUG" ]] && echo "Lines: $lines"

regexp_trigger='\t*# *\^'
regexp_attribute='={1}'
regexp_href='href=[^ ]+'
regexp_id='id=[^ ]+'
for line in $(seq 0 "$lines"); do
	if [[ "${makefile[$line]}" =~ $regexp_trigger ]]; then
		# parse the formatting line
		len_line=${#makefile[$line]}
		prefix="${makefile[$line]%%^*}"

		len_prefix=${#prefix}
		if [[ len_prefix -eq 1 ]]; then
			# special case; link is from start of line
			adjust=-1
		else
			adjust=0
		fi
		len_prefix=$(( len_prefix + adjust ))

		attributes="${makefile[$line]##*^}"
		len_attributes=${#attributes}
		len_link=$(( ${#makefile[$line]} - len_prefix - len_attributes ))

		# and now the line to edited
		edit=$((line - 1))
		edit_line="${makefile[$edit]}"
		edit_prefix="${edit_line:0:$(( len_prefix ))}"
		edit_suffix="${edit_line:$(( len_prefix + len_link  ))}"
		edit_link="${edit_line:$(( ${#edit_prefix} )):$(( len_link ))}"
		new_link=''

		if [[ "$attributes" =~ $regexp_attribute ]]; then
			# attributes is expected to start with a space but include it just in case!
			new_link='<a '"$attributes"
			if [[ "$attributes" =~ $regexp_href ]]; then
				new_link+=' target="'"${ATARGET}"'"' 
			fi
			new_link+='>'"${edit_link}"'</a>'

			if [[ "$attributes" =~ $regexp_id ]]; then
				# need to split this out and add it to the index array so an index can be inserted later
				id="${attributes##*id=\"}"
				idx="${id%%\" *}"
				index["$idx"]="$edit_link"
			fi
		fi
		new_line="${edit_prefix}${new_link}${edit_suffix}"

		if [[ -n "$DEBUG" ]]; then
			echo "prefix='$prefix' len=$len_prefix"
			echo "attributes='$attributes' len=$len_attributes"
			echo "edit_prefix='$edit_prefix'"
			echo "edit_suffix='$edit_suffix'"
			echo "edit_link='$edit_link'"
			echo "new_link='$new_link'"
			echo "before: '$edit_line'"
			echo "after : '$new_line'"
		fi

		makefile[edit]="${new_line}"
		unset "makefile[line]"
	fi
done

if [[ -n "$DEBUG" ]]; then
	lines="${#makefile[@]}"
	echo "Lines: $lines"
	for line in "${!makefile[@]}"; do
		echo "$line: ${makefile[$line]}"
	done
fi

# second: wrap comments in SPAN
lines="${#makefile[@]}"
declare -a makefile_new
comment=false
regexp_comment='^#'
regexp_blank='^[ \t]*$'
for line in "${!makefile[@]}"; do
	if ! $comment && [[ "${makefile[$line]}" =~ $regexp_comment ]]; then
		comment="true"
		# to prevent an extra PRE line being rendered insert SPAN on same line
		makefile_new+=('<span class="'"${SCLASS}"'">'"${makefile[$line]}")
		continue
	fi

	if $comment && ! [[ "${makefile[$line]}" =~ $regexp_comment ]] && ! [[ "${makefile[$line]}" =~ $regexp_blank ]]; then
		comment=false
		# makefile_new+=('</span>')
		makefile_new[  ${#makefile_new[@]} - 1 ]="${makefile_new[ $(( ${#makefile_new[@]} - 1 )) ]}</span>"
	fi

	makefile_new+=("${makefile[$line]}")
done

if [[ -n "$DEBUG" ]]; then
	lines="${#makefile_new[@]}"
	echo "Lines: $lines"
	for line in "${!makefile_new[@]}"; do
		echo "$line: ${makefile_new[$line]}"
	done
fi

# move keys and values into indexed arrays
declare -a t_key
declare -a t_val
for i in "${!index[@]}"; do
	t_key+=("$i")
	t_val+=("${index["$i"]}")
done

# sort into new arrays
declare -a i_key
declare -a i_val
while read -r key value trash; do
	i_key+=("$key")
	i_val+=("$value")
done < <(
	for i in $(seq 0 $(( ${#t_key[@]} - 1 )) ); do
		echo "${t_key[$i]} ${t_val[$i]} trash"
	done | sort --ignore-case --key=2
	# sort the index by value
)
unset t_key
unset t_val
unset index

# read HTML file; copy lines into new array and replace existing formatted Makefile and Index
html_lines="${#html[@]}"
[[ -n "$DEBUG" ]] && echo "HTML Lines: $html_lines"

declare -a html_new
regexp_mk_begin='<!-- MakefileBegin -->'
regexp_mk_end='<!-- MakefileEnd -->'
mk_done="false"
mk_skip="false"
regexp_idx_begin='<!-- IndexBegin -->'
regexp_idx_end='<!-- IndexEnd -->'
idx_done="false"
idx_skip="false"
for html_line in $(seq 0 "$html_lines"); do
	if [[ "${html[$html_line]}" =~ $regexp_mk_end ]]; then
		mk_skip="false"
	fi

	if ! $mk_done && $mk_skip; then
		# insert formatted Makefile
		html_new+=("<pre>")
		mk_lines="${#makefile_new[@]}"
		for mk_line in $(seq 0 "$mk_lines"); do
			html_new+=("${makefile_new[$mk_line]}")
		done
		html_new+=("</pre>")
		mk_done="true"
	fi

	if [[ "${html[$html_line]}" =~ $regexp_idx_end ]]; then
		idx_skip="false"
	fi

	if ! $idx_done && $idx_skip; then
		# insert the sorted index
		html_new+=("		<ul>")
		for i in $(seq 0 $(( ${#i_key[@]} - 1 )) ); do
			html_new+=("			<li><a href=\"#${i_key[$i]}\">${i_val[$i]}</a></li>")
		done
		html_new+=("		</ul>")
		idx_done="true"
	fi

	if ! $mk_skip && ! $idx_skip; then
		# copy original line - no modification required
		html_new+=("${html[$html_line]}")
	fi

	if [[ "${html[$html_line]}" =~ $regexp_mk_begin ]]; then
		mk_skip="true"
	fi

	if [[ "${html[$html_line]}" =~ $regexp_idx_begin ]]; then
		idx_skip="true"
	fi
done

if [[ -n "$DEBUG" ]]; then
	echo "Index: ${#i_key[@]}"
	for i in $(seq 0 $(( ${#i_val[@]} - 1 )) ); do
		echo "${i_key[$i]} = '${i_val[$i]}'"
	done
fi

output="$2"
[[ -n "$DEBUG" ]] && output="debug.html"
{
	for line in $(seq 0 "${#html_new[@]}"); do
		echo "${html_new[$line]}"
	done
}  > "$output"
echo "Output: $output"
