#!/usr/bin/env perl

# Doxygen is useful for html documentation, but sucks
# in making manual pages. Still tool also parses the .h
# files with the doxygen documentation and creates
# the man page we want
#
# 2 way process
# 1. All the .h files are processed to create in file in which:
# filename | API | description | return values
# are documented
# 2. Another file is parsed which states which function should
# be grouped together in which manpage. Symlinks are also created.
#
# With this all in place, all documentation should be autogenerated
# from the doxydoc.

use Getopt::Std;

my $state;
my $description;
my $struct_description;
my $key;
my $return;
my $param;
my $api;
my $const;

my %description;
my %api;
my %return;
my %options;
my %manpages;
my %see_also;

my $BASE="doc/man";
my $MAN_SECTION = "3";
my $MAN_HEADER = ".ad l\n.TH ldns $MAN_SECTION \"30 May 2006\"\n";
my $MAN_MIDDLE = ".SH AUTHOR
The ldns team at NLnet Labs.

.SH REPORTING BUGS
Please report bugs to ldns-team\@nlnetlabs.nl or in 
our bugzilla at
http://www.nlnetlabs.nl/bugs/index.html

.SH COPYRIGHT
Copyright (c) 2004 - 2006 NLnet Labs.
.PP
Licensed under the BSD License. There is NO warranty; not even for
MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.
";
my $MAN_FOOTER = ".SH REMARKS
This manpage was automatically generated from the ldns source code.
";

getopts("em:",\%options);
# if -m manpage file is given process that file
# parse the file which tells us what manpages go together
my $functions, $see_also, $shorts;
my $i = 0;
my $report_errors = defined $options{'e'};
my $errors = 0;
my %unique;

if (defined $options{'m'}) {
	# process
	open(MAN, "<$options{'m'}") or die "Cannot open $options{'m'}";
		# it's line based:
		# func1, func2, .. | see_also1, see_also2, ...
		while(<MAN>) {
			chomp;
			$i += 1;
			if (/^#/) { next; }
			if (/^$/) { next; }
			my @parts = split /[\t ]*\|[\t ]*/, $_;
			$functions = shift @parts;
			@parts = split /[\t ]*-[\t ]*/, join ', ', @parts;
			$see_also = shift @parts;
			if (! $see_also) {
				@parts = split /[\t ]*-[\t ]*/, $_;
				$functions = shift @parts;
			}
			#print "{$functions}\n";
			#print "{$see_also}\n";
			my @funcs = split /[\t ]*,[\t ]*/, $functions;
			my @also = split /[\t ]*,[\t ]*/, $see_also;
			$manpages{$funcs[0]} = \@funcs;
			$see_also{$funcs[0]} = \@also;
			$shorts{$funcs[0]} = join '', @parts;
			foreach (@funcs) {
				if ($unique{$_}) {
					push @{$unique{$_}}, ($i,);
				} else {
					$unique{$_} = [$i];
				}
			}
			#print "[", $funcs[0], "]\n";
		}
	close(MAN);
	while (($func, $lines) = each %unique ) {
		if (scalar @$lines > 1) {
			print STDERR "$func in function_manpages on lines: "
			    . join(", ",@$lines) . "\n" if $report_errors;
			$errors += 1;
		}
	}
} else {
	print "Need -m file to process the .h files\n";
	exit 1;
}

# 0 - somewhere in the file
# 1 - in a doxygen par
# 2 - after doxygen, expect function

# create our pwd
mkdir "doc";
mkdir "doc/man";
mkdir "doc/man/man$MAN_SECTION";

$state = 0;
$i = 0;
my @lines = <STDIN>;
my $max = @lines;

while($i < $max) {
	$typedef = "";
	if ($lines[$i] =~ /^typedef struct/ and $lines[$i + 1] =~ /^struct/) {
		# move typedef to below struct
		$typedef = $lines[$i];
		$j = $i;
		while ($lines[$j] !~ /}/) {
			$lines[$j] = $lines[$j+1];
			$j++;
		}
	$lines[$j] = $lines[$j+1];
	$lines[$j + 1] = $typedef;
	}

	$cur_line = $lines[$i];
	chomp($cur_line);
	if ($cur_line =~ /^\/\*\*[\t ]*$/) {
		# /** Seen
		#print "Comment seen! [$cur_line]\n";
		$state = 1;
		undef $description;
		undef $struct_description;
		$i++;
		next;
	}
	if ($cur_line =~ /\*\// and $state == 1) {
		#print "END Comment seen!\n";
		if ($description =~ /^\\\\file/mg) {
			# Doxygen text for the file, do not expect
			# a function coming.
			#
			$state = 0;
		} else {
			$state = 2;
		}
		$i++;
		next;
	}

	if ($state == 1) {
		# inside doxygen 
		$cur_line =~ s/\\/\\\\/g;
		$cur_line =~ s/^[ \t]*\* ?//;
		$description = $description . "\n" . $cur_line;
		#$description = $description . "\n.br\n" . $cur_line;
	}
	if ($state == 2 and $cur_line =~ /const/) {
		# the const word exists in the function call
		#$const = "const";
		#s/[\t ]*const[\t ]*//;
	} else {
		#undef $const;
	}
	
	if ($cur_line =~ /^INLINE/) {
		$cur_line =~ s/^INLINE\s*//;
		while ($cur_line !~ /{/) {
			$i++;
			$cur_line .= " ".$lines[$i];
			$cur_line =~ s/\n//;
		}
		$cur_line =~ s/{/;/;
	}
	
	if ($cur_line =~ /^[^#*\/ ]([\w\*]+)[\t ]+(.*?)[({](.*)\s*/ and $state == 2) {
		while ($cur_line !~ /\)\s*;/) {
			$i++;
			$cur_line .= $lines[$i];
			chomp($cur_line);
			$cur_line =~ s/\n/ /g;
			$cur_line =~ s/\s\s*/ /g;
		}
		$cur_line =~ /([\w\* ]+)[\t ]+(.*?)\((.*)\)\s*;/;
		# this should also end the current comment parsing
		$return = $1;
		$key = $2;
		$api = $3;
		# sometimes the * is stuck to the function
		# name instead to the return type
		if ($key =~ /^\*/) {
			#print"Name starts with *\n";
			$key =~ s/^\*//;
			if (defined($const)) {
				$return =  $const . " " . $return . '*';
			} else {
				$return =  $return . '*';
			}
		}
		$description =~ s/\\param\[in\][ \t]*([\*\w]+)[ \t]+/.br\n\\fB$1\\fR: /g;
		$description =~ s/\\param\[out\][ \t]*([\*\w]+)[ \t]+/.br\n\\fB$1\\fR: /g;
		$description =~ s/\\return[ \t]*/.br\nReturns /g;

		# Delete leading spaces to prevent manpages to be ascii format-
		# ted and enable justification of text.
		#
		$description =~ s/^[ \t]*//mg;

		# Prevent hyphening of all caps and underscore words
		$description =~ s/\b([A-Z_]+)\b/\\%$1/g;

		$description{$key} = $description;
		$api{$key} = $api;
		$return{$key} = $return;
		undef $description;
		undef $struct_description;
		$state = 0;
	} elsif ($state == 2 and (
			$cur_line =~ /^typedef\sstruct\s(\w+)\s(\w+);/ or
			$cur_line =~ /^typedef\senum\s(\w+)\s(\w+);/)) {
		$struct_description .= "\n.br\n" . $cur_line;
		$key = $2;
		$struct_description =~ s/\/\*\*\s*(.*?)\s*\*\//\\fB$1:\\fR/g;
		$description{$key} = $struct_description;
		$api{$key} = "struct";
		$return{$key} = $1;
		undef $description;
		undef $struct_description;
		$state = 0;
	} else {
		$struct_description .= "\n.br\n" . $cur_line;
	}
	$i++;
}

# create the manpages
foreach (keys %manpages) {
	$name = $manpages{$_};
	$also = $see_also{$_};
	my $shrt = $shorts{$_};

	$filename = @$name[0];
	$filename = "$BASE/man$MAN_SECTION/$filename.$MAN_SECTION";

	my $symlink_file = @$name[0] . "." . $MAN_SECTION;

#	print STDOUT $filename,"\n";
	open (MAN, ">$filename") or die "Can not open $filename";

	print MAN  $MAN_HEADER;
	print MAN  ".SH NAME\n";
	print MAN  join ", ", @$name;
	if ($shrt) {
		print MAN " \\- $shrt";
	}
	print MAN  "\n\n";
	print MAN  ".SH SYNOPSIS\n";

	print MAN  "#include <stdint.h>\n.br\n";
	print MAN  "#include <stdbool.h>\n.br\n";

	print MAN  ".PP\n";
	print MAN  "#include <ldns/ldns.h>\n";
	print MAN  ".PP\n";

	foreach (@$name) {
		$b = $return{$_};
		$b =~ s/\s+$//;
		if ($api{$_} ne "struct") {
			print MAN  $b, " ", $_;
			print MAN  "(", $api{$_},");\n";
			print MAN  ".PP\n";
		}
	}

	print MAN  "\n.SH DESCRIPTION\n";
	foreach (@$name) {
		print MAN  ".HP\n";
		print MAN "\\fI", $_, "\\fR";
		if ($api{$_} ne "struct") {
			print MAN "()"; 
		}
#		print MAN ".br\n";
		print MAN  $description{$_};
		print MAN  "\n.PP\n";
	}

	print MAN $MAN_MIDDLE;

	if (@$also) {
		print MAN "\n.SH SEE ALSO\n\\fI";
		print MAN join "\\fR, \\fI", @$also;
		print MAN "\\fR.\nAnd ";
		print MAN "\\fBperldoc Net::DNS\\fR, \\fBRFC1034\\fR,
\\fBRFC1035\\fR, \\fBRFC4033\\fR, \\fBRFC4034\\fR  and \\fBRFC4035\\fR.\n";
	} else {
		print MAN ".SH SEE ALSO
\\fBperldoc Net::DNS\\fR, \\fBRFC1034\\fR,
\\fBRFC1035\\fR, \\fBRFC4033\\fR, \\fBRFC4034\\fR and \\fBRFC4035\\fR.\n";
	}
	
	print MAN $MAN_FOOTER;

	# create symlinks
	chdir("$BASE/man$MAN_SECTION");
	foreach (@$name) {
		print STDOUT $_,"\n";
		my $new_file = $_ . "." . $MAN_SECTION;
		if ($new_file eq $symlink_file) {
			next;
		}
		#print STDOUT "\t", $new_file, " -> ", $symlink_file, "\n";
		symlink $symlink_file, $new_file;
	}
	chdir("../../.."); # and back, tricky and fragile...
	close(MAN);
}
foreach (keys %api) {
	next if (/ / || /^$/);
	if (not $unique{$_}) {
		print STDERR "no man page for $_\n" if $report_errors;
		#$errors += 1;
	}
}

exit ($report_errors and $errors != 0);
