#!/bin/sh

# src/tools/pgindent/pgindent

# Known bugs:
#
# Blank line is added after parentheses; seen as a function definition, no space
# after *:
#	y = (int) x *y;
#
# Structure/union pointers in function prototypes and definitions have an extra
# space after the asterisk:
#
#	void x(struct xxc * a);

if [ "$#" -lt 2 ]
then	echo "Usage:  $(basename $0) typedefs file [...]" 1>&2
	exit 1
fi

TYPEDEFS="$1"
shift

[ -z "$INDENT" ] && INDENT=pg_bsd_indent
INDENT_VERSION="1.1"

trap "rm -f /tmp/$$ /tmp/$$a" 0 1 2 3 15

# check the environment

entab </dev/null >/dev/null
if [ "$?" -ne 0 ]
then	echo "Go to the src/tools/entab directory and do a 'make' and 'make install'." >&2
	echo "This will put the 'entab' command in your path." >&2
	echo "Then run $0 again."
	exit 1
fi
$INDENT -? </dev/null >/dev/null 2>&1
if [ "$?" -ne 1 ]
then	echo "You do not appear to have '$INDENT' installed on your system." >&2
	exit 1
fi
if [ "`$INDENT -V`" != "$INDENT $INDENT_VERSION" ]
then	echo "You do not appear to have $INDENT version $INDENT_VERSION installed on your system." >&2
	exit 1
fi
$INDENT -gnu </dev/null >/dev/null 2>&1
if [ "$?" -eq 0 ]
then	echo "You appear to have GNU indent rather than BSD indent." >&2
	echo "See the pgindent/README file for a description of its problems." >&2
	EXTRA_OPTS="-cdb -bli0 -npcs -cli4 -sc"
else
	EXTRA_OPTS="-cli1"
fi

for FILE
do
	cat "$FILE" |

# Convert // comments to /* */
	sed 's;^\([ 	]*\)//\(.*\)$;\1/* \2 */;g' |

# Avoid bug that converts 'x =- 1' to 'x = -1'
	sed 's;=- ;-= ;g' |

# Mark some comments for special treatment later
	sed 's;/\*  *---;/*---X_X;g' |

# 'else' followed by a single-line comment, followed by
# a brace on the next line confuses BSD indent, so we push
# the comment down to the next line, then later pull it
# back up again.  Add space before _PGMV or indent will add
# it for us.
	sed 's;\([} 	]\)else[ 	]*\(/\*\)\(.*\*/\)[ 	]*$;\1else\
    \2 _PGMV\3;g' |

# Indent multi-line after-'else' comment so BSD indent will move it properly.
# We already moved down single-line comments above.  Check for '*' to make
# sure we are not in a single-line comment that has other text on the line.
	sed 's;\([} 	]\)else[ 	]*\(/\*[^\*]*\)[ 	]*$;\1else\
    \2;g' |
	detab -t4 -qc |

# Work around bug where function that defines no local variables misindents
# switch() case lines and line after #else.  Do not do for struct/enum.
	awk '	BEGIN	{line1 = ""; line2 = ""}
		{
			line2 = $0;
	 		if (NR >= 2)
				print line1;
			if (NR >= 2 &&
			    line2 ~ /^{[ 	]*$/ &&
			    line1 !~ /^struct/ &&
			    line1 !~ /^enum/ &&
			    line1 !~ /^typedef/ &&
			    line1 !~ /^extern[ 	][ 	]*"C"/ &&
			    line1 !~ /=/ &&
			    line1 ~ /\)/)
				print "int	pgindent_func_no_var_fix;";
			line1 = line2;
		}
		END {
			if (NR >= 1)
				print line1;
		}' |

# Prevent indenting of code in 'extern "C"' blocks.
	awk '	BEGIN	{line1 = ""; line2 = ""; skips = 0}
		{
			line2 = $0;
			if (skips > 0)
				skips--;
			if (line1 ~ /^#ifdef[ 	]*__cplusplus/ &&
			    line2 ~ /^extern[ 	]*"C"[ 	]*$/)
			{
				print line1;
				print line2;
				if (getline && $0 ~ /^{[ 	]*$/)
					print "/* Open extern \"C\" */";
				else	print $0;
				line2 = "";
				skips = 2;
			}
			else if (line1 ~ /^#ifdef[ 	]*__cplusplus/ &&
			    line2 ~ /^}[ 	]*$/)
			{
				print line1;
				print "/* Close extern \"C\" */";
				line2 = "";
				skips = 2;
			}
			else
	 			if (skips == 0 && NR >= 2)
					print line1;
			line1 = line2;
		}
		END {
			if (NR >= 1 && skips <= 1)
				print line1;
		}' |

# Protect backslashes in DATA().
	sed 's;^DATA(.*$;/*&*/;' |

# Protect wrapping in CATALOG().
	sed 's;^CATALOG(.*$;/*&*/;' >/tmp/$$a

	egrep -v '^(FD_SET|date|interval|timestamp|ANY)$' "$TYPEDEFS" | sed -e '/^$/d' > /tmp/$$b

# We get the list of typedef's from /src/tools/find_typedef
	$INDENT -bad -bap -bc -bl -d0 -cdb -nce -nfc1 -di12 -i4 -l79 \
		-lp -nip -npro -bbb $EXTRA_OPTS -U/tmp/$$b \
		/tmp/$$a >/tmp/$$ 2>&1

	if [ "$?" -ne 0 -o -s /tmp/$$ ]
	then	echo
		echo "$FILE"
		cat /tmp/$$
	fi
	cat /tmp/$$a |

# Restore DATA/CATALOG lines.
	sed 's;^/\*\(DATA(.*\)\*/$;\1;' |
	sed 's;^/\*\(CATALOG(.*\)\*/$;\1;' |

# Remove tabs and retab with four spaces.
	detab -t8 -qc |
	entab -t4 -qc |
	sed 's;^/\* Open extern \"C\" \*/$;{;' |
	sed 's;^/\* Close extern \"C\" \*/$;};' |
	sed 's;/\*---X_X;/* ---;g' |

# Workaround indent bug for 'static'.
	sed 's;^static[ 	][ 	]*;static ;g' |

# Remove too much indenting after closing brace.
	sed 's;^}	[ 	]*;}	;' |

# Indent single-line after-'else' comment by only one tab.
	sed 's;\([} 	]\)else[ 	]*\(/\*.*\*/\)[ 	]*$;\1else	\2;g' |

# Pull in #endif comments.
	sed 's;^#endif[ 	][ 	]*/\*;#endif   /*;' |

# Work around misindenting of function with no variables defined.
	awk '
	{
		if ($0 ~ /^[ 	]*int[ 	]*pgindent_func_no_var_fix;/)
		{
			if (getline && $0 != "")
				print $0;
		}
		else 	print $0;
	}' |

# Add space after comments that start on tab stops.
	sed 's;\([^ 	]\)\(/\*.*\*/\)$;\1	\2;' |

# Move trailing * in function return type.
	sed 's;^\([A-Za-z_][^ 	]*\)[ 	][ 	]*\*$;\1 *;' |

# Remove un-needed braces around single statements.
# Do not use because it uglifies PG_TRY/PG_CATCH blocks and probably
# isn't needed for general use.
#	awk '
#	{
#			line3 = $0;
#			if (skips > 0)
#				skips--;
#			if (line1 ~ /		*{$/ &&
#			    line2 ~ /		*[^;{}]*;$/ &&
#			    line3 ~ /		*}$/)
#			{
#				print line2;
#				line2 = "";
#				line3 = "";
#				skips = 3;
#			}
#			else
#	 			if (skips == 0 && NR >= 3)
#					print line1;
#			line1 = line2;
#			line2 = line3;
#		}
#		END {
#			if (NR >= 2 && skips <= 1)
#				print line1;
#			if (NR >= 1 && skips <= 2)
#				print line2;
#		}' |

# Remove blank line between opening brace and block comment.
	awk '
	{
			line3 = $0;
			if (skips > 0)
				skips--;
			if (line1 ~ /	*{$/ &&
			    line2 ~ /^$/ &&
			    line3 ~ /		*\/[*]$/)
			{
				print line1;
				print line3;
				line2 = "";
				line3 = "";
				skips = 3;
			}
			else
	 			if (skips == 0 && NR >= 3)
					print line1;
			line1 = line2;
			line2 = line3;
		}
		END {
			if (NR >= 2 && skips <= 1)
				print line1;
			if (NR >= 1 && skips <= 2)
				print line2;
		}' |

# Pull up single-line comment after 'else' that was pulled down above
	awk '
		{
			if (NR != 1)
			{
				if ($0 ~ "/[*] _PGMV")
				{
					# remove tag
					sub(" _PGMV", "", $0);
					# remove leading whitespace
					sub("^[ 	]*", "", $0);
					# add comment with single tab prefix
					print prev_line"	"$0;
					# throw away current line
					getline;
				}
				else
					print prev_line;
			}
			prev_line = $0;
		}
		END {
			if (NR >= 1)
				print prev_line;
		}' |

# Remove trailing blank lines, helps with adding blank before trailing #endif.
	awk '	BEGIN	{blank_lines = 0;}
		{
			line1 = $0;
	 		if (line1 ~ /^$/)
				blank_lines++;
			else
			{
				for (; blank_lines > 0; blank_lines--)
					printf "\n";
				print line1;
			}
		}' |

# Remove blank line before #else, #elif, and #endif.
	awk '	BEGIN	{line1 = ""; line2 = ""; skips = 0}
		{
			line2 = $0;
			if (skips > 0)
				skips--;
			if (line1 ~ /^$/ &&
			    (line2 ~ /^#else/ ||
			     line2 ~ /^#elif/ ||
			     line2 ~ /^#endif/))
			{
				print line2;
				line2 = "";
				skips = 2;
			}
			else
	 			if (skips == 0 && NR >= 2)
					print line1;
			line1 = line2;
		}
		END {
			if (NR >= 1 && skips <= 1)
				print line1;
		}' |

# Add blank line before #endif if it is the last line in the file.
	awk '	BEGIN	{line1 = ""; line2 = ""}
		{
			line2 = $0;
	 		if (NR >= 2)
				print line1;
			line1 = line2;
		}
		END {
			if (NR >= 1 && line2 ~ /^#endif/)
				printf "\n";
			print line1;
		}' |

#  Move prototype names to the same line as return type.  Useful for ctags.
#  Indent should do this, but it does not.  It formats prototypes just
#  like real functions.
	awk '	BEGIN	{paren_level = 0}
	{
		if ($0 ~ /^[a-zA-Z_][a-zA-Z_0-9]*[^\(]*$/)
		{
			saved_len = 0;
			saved_lines[++saved_len] = $0;
			if ((getline saved_lines[++saved_len]) == 0)
				print saved_lines[1];
			else
			if (saved_lines[saved_len] !~ /^[a-zA-Z_][a-zA-Z_0-9]*\(/ ||
			    saved_lines[saved_len] ~  /^[a-zA-Z_][a-zA-Z_0-9]*\(.*\)$/ ||
			    saved_lines[saved_len] ~  /^[a-zA-Z_][a-zA-Z_0-9]*\(.*\);$/)
			{
				print saved_lines[1];
				print saved_lines[2];
			}
			else
			{
				while (1)
				{
					if ((getline saved_lines[++saved_len]) == 0)
						break;
					if (saved_lines[saved_len] ~ /^[^ 	]/ ||
					    saved_lines[saved_len] !~ /,$/)
						break;
				}
				for (i=1; i <= saved_len; i++)
				{
					if (i == 1 && saved_lines[saved_len] ~ /\);$/)
					{
						printf "%s", saved_lines[i];
						if (substr(saved_lines[i], length(saved_lines[i]),1) != "*")
							printf " ";
					}
					else	print saved_lines[i];
				}
			}
		}
		else	print $0;
	}' |

# Fix indenting of typedef caused by __cplusplus in libpq-fe.h.
	(
		if echo "$FILE" | grep -q 'libpq-fe.h$'
		then	sed 's/^[	]*typedef enum/typedef enum/'
		else	cat
		fi
	) |
# end
	cat >/tmp/$$ && cat /tmp/$$ >"$FILE"
done

# The 'for' loop makes these backup files useless so delete them
rm -f *a.BAK
