package kdocIDLhtml;

use File::Path;
use File::Basename;

use Carp;
use Ast;
use kdocAstUtil;
use kdocHTMLutil;

=head1 kdocIDLhtml

Generate HTML docs for an IDL syntax tree.

=cut

BEGIN
{
	@clist = ();

	# Host information

	if ( $^O eq "MsWin32" ) {
		# Windows
 		$host=$ENV{COMPUTERNAME};
 		$who = $ENV{USERNAME};	
	}
	else {
		# Unix
		$host = `uname -n`;	chop $host;
		$who = `whoami`;	chop $who;
	}

	$now = localtime;
	$gentext = "$who\@$host on $now, using kdoc $main::Version.";

	# bottom of every page
	$docBotty =<<EOF;
<HR>
	<table>
	<tr><td><small>Generated by: $gentext</small></td></tr>
	</table>
</BODY>
</HTML>
EOF

	# used to convert node types to headings

	%typedesc = (	
			'enum' 	=> 'Enumerations',
			'exception' => 'Exceptions',
			'interface' => 'Interfaces',
			'method' => 'Methods',
			'struct' => 'Structures',
			'typedef' => 'Types',
			'var' => 'Constants'
	);
}

sub writeDoc
{
	( $lib, $rootnode, $outputdir, $opt ) = @_;

	$debug = $main::debug;
	@docQueue = ();

	print "Generating HTML documentation. \n" unless $main::quiet;

	mkpath( $outputdir ) unless -f $outputdir;

	makeReferences( $rootnode );
	makeModuleList( $rootnode );

	writeModList( $rootnode );
	writeAnnotatedList( $rootnode );
	writeHeaderList();

	my $node;

	foreach $node ( @{$rootnode->{Kids}} ) {
		next if !defined $node->{Compound} 
			|| defined $node->{ExtSource};
		print "list: Queueing $node->{astNodeName} for doc\n"
			if $debug;
		push @docQueue, $node;
	}

	while( $#docQueue >= 0 ) {
		$node = pop @docQueue;
		print "Dequeueing $node->{astNodeName} for doc\n"
			if $debug;
		writeCompoundDoc( $node );
	}
}

=head2 writeAnnotatedList

	Parameters: rootnode

	Writes out a list of classes with short descriptions to
	index-long.html.

=cut

sub writeAnnotatedList
{
	my ( $root ) = @_;
	my $short;

	open(CLIST, ">$outputdir/index-long.html") 
		|| die "Couldn't create $outputdir/index-long.html: $?\n";

	print CLIST pageHeader( $lib." Annotated List" ),
		"<TABLE WIDTH=\"100%\" BORDER=\"0\">\n";

	foreach my $node ( @clist ) {
		confess "undef in clist\n" if !defined $node;

		$docnode = $node->{DocNode};
		$short = "";

		if( defined $docnode && exists $docnode->{ClassShort} ) {
			$short = deref($docnode->{ClassShort}, $rootnode );
			if( !defined $short ) {
				print $root->{astNodeName}, "has undef short\n";
				next;
			}
				
		}

		print CLIST "<TR><TD>", refName( $node ), 
			"</TD><TD>", $short, "</TD></TR>";
	}

	print CLIST "</TABLE>", $docBotty;
	close CLIST;
}

=head2 writeModList

	Parameters: rootnode

	Writes out a concise list of classes to index.html

=cut

sub writeModList
{
	my ( $root ) = @_;

	open(CLIST, ">$outputdir/index.html") 
		|| die "Couldn't create $outputdir/index.html: $?\n";

	print CLIST pageHeader( $lib." Module Index" ),
		"<TABLE WIDTH=\"100%\" BORDER=\"0\">\n";

	my $cols = exists $opt->{"html-cols"} ? $opt->{"html-cols"} : 3;
	my ( $ctr, $size ) = ( 0, int(($#clist+1)/$cols) );
	$size = 1 if $size < 1;

	print CLIST "<TR>";
	my $s;

	while ( $ctr <= $#clist ) {
		print CLIST "<TD>";
		$s = $ctr+$size-1;

		if ( $s > $#clist ) {
			$s = $#clist;
		}
		elsif ( ($#clist - $s) < $cols) {
			$s = $#clist;
		}

		print "Writing from $ctr to ",$ctr+$size-1,"\n" if $debug;
		writeListPart( \@clist, $ctr, $s );
		print CLIST "</TD>";
		$ctr = $s+1;
	}

	print CLIST<<EOF;
</TR>
</TABLE>
$docBotty
EOF
	close CLIST;
}

=head3

	Parameters: list, start index, end index

	Helper for writeModList.  Prints a table containing a hyperlinked
	list of all nodes in the list from start index to end index. A
	table header is also printed.

=cut

sub writeListPart
{
	my( $list, $start, $stop ) = @_;

	print CLIST "<TABLE BORDER=\"0\">";

	print CLIST "<TR><TH>", 
		esc( $list->[ $start ]->{astNodeName} ),
		" - ", esc( $list->[ $stop ]->{astNodeName} ), 
		"</TH></TR>";

	for $ctr ( $start..$stop ) {
		print CLIST "<TR><TD>", refName( $list->[ $ctr ] ),
			"</TD></TR>\n";
	}

	print CLIST "</TABLE>";
}


=head2 writeAllMembers

	Parameters: node

	Writes a list of all methods to "full-list-<class file>"

=cut

sub writeAllMembers
{
	my( $node ) = @_;
	my $file = "$outputdir/full-list-".$node->{Ref};
	my %allmem = ();

	kdocAstUtil::allMembers( \%allmem, $node );

	open( ALLMEM, ">$file" ) || die "Couldn't create $file: $?\n";

	print ALLMEM pageHeader( esc($node->{astNodeName})." - All Methods" ),
		"<UL>";

	my $mem;
	foreach $mem ( sort keys %allmem ) {
		print ALLMEM "<LI>", refName( $allmem{ $mem } ), "</LI>\n";
	}

	print ALLMEM "</UL>$docBotty";

	close ALLMEM;
}

=head2 writeHeaderList

	Generates the header-list.html file, which contains links
	to each processed header. The ARGV list is used.

=cut

sub writeHeaderList
{
	open(HDRIDX, ">$outputdir/header-list.html") 
		|| die "Couldn't create $outputdir/header-list.html: $?\n";

	print HDRIDX pageHeader( $lib." File Index" ), "<UL>\n";

	foreach $header ( sort @main::ARGV ) {
		$_ = $header;
		$header = basename ( $_ ) if $main::striphpath;
# convert dashes to double dash, convert path to dash
		s/-/--g/g;
		s/\/|\./-/g;

		print HDRIDX  "\t<LI>",hyper($_.".html",$header),"</LI>\n";
	}
	
print HDRIDX "</UL>\n",$docBotty;

}

=head2 pageHeader

	Parameters: heading, description

	Returns a string containing an HTML page heading.

=cut

sub pageHeader
{
	my( $heading, $desc ) = @_;

	$desc = "" if !defined $desc;

	my $libtext = "";
	if( $lib ne "" ) {
		$libtext = "<TR><TD><small>$lib documentation</small>".
			"</TD></TR>";
	}

	return <<EOF;
<HTML><HEAD>
<TITLE>$heading</TITLE>
<META NAME="Generator" CONTENT="KDOC $main::Version">
</HEAD>
<BODY bgcolor="#ffffff" text="#000000" link="#0000ff" vlink="#000099" alink= "#ffffff">
<TABLE WIDTH="100%" BORDER="0">
<TD VALIGN="top" align="left">
<TABLE BORDER="0"><TR>
$libtext
<TR><TD><small><A HREF="index.html">Index</small></TD></TR>
<TR><TD><small><A HREF="index-long.html">Annotated List</small></TD></TR>
<TR><TD><small><A HREF="header-list.html">Files</A></small></TD></TR>
</TABLE>
</TD>

<TD VALIGN="top" align="right"><H1>$heading</H1><p>$desc</p></TD>
</TR></TABLE>
<HR>
EOF
	
}

=head2 writeCompoundDoc

	Parameters: $node

	Write documentation for one compound node.

=cut

sub writeCompoundDoc
{
	my( $node ) = @_;
	if( exists $node->{ExtSource} ) {
		warn "Trying to write doc for ".$node->{AstNodeName}.
			" from ".$node->{ExtSource}."\n";
		return;
	}

	my $file = "$outputdir/".$node->{Ref};
	my $docnode = $node->{DocNode};
	my $hasdoc = exists $node->{DocNode} ? 1 : 0;
	my @list = ();
	my $version = undef;
	my $author = undef;

	open( CLASS, ">$file" ) || die "Couldn't create $file: $?\n";

	# Header
	 
	my $source = kdocAstUtil::nodeSource( $node );
	my $short = "";

	if( $hasdoc ) {
		if ( exists $docnode->{ClassShort} ) {
			$short .= deref($docnode->{ClassShort}, $rootnode );
		}

		if ( exists $docnode->{Deprecated} ) {
			$short .= "</p><p><b>Deprecated: use with care</b>";
		}

		if ( exists $docnode->{Internal} ) {
			$short .= "</p><p><b>Internal use only</b>";
		}
		$version = esc($docnode->{Version}) 
				if exists $docnode->{Version};
		$author = esc($docnode->{Author}) 
				if exists $docnode->{Author};
	}

	# check for pure abstract
	if ( exists $node->{Pure} ) {
		$short .= 
			"</p><p><b>Contains pure virtuals</b></p><p>";
	}

	# full name
	$short .= "</p><p><code>".refNameEvery( $node, $rootnode )."</code><p>"
			unless $node->{Parent} eq $rootnode;

	# include
	$short .= "</p><p><code>#include &lt;".$source
			."&gt;</code></p><p>";

	# inheritance
	if ( $node != $rootnode && exists $node->{InList} ) {
		my $comma = "Inherits: ";

		# FIXME: bad hack to get rid of duplicates.
		my $last = undef;
		my $out = "";

		foreach my $in ( sort{ $a->{astNodeName} cmp $b->{astNodeName}}
				@{$node->{InList}} ) {
			next if $in == $last;

			my $n = $in->{Node};
			if( defined $n ) {
				next if $n eq $rootnode;
				$out .= $comma.refNameFull( $n, $rootnode );
				if ( exists $n->{ExtSource} ) {
					$out .= " <small>(".$n->{ExtSource}
					.")</small>";
				}
			}
			else {
				$out .= esc( $in->{astNodeName} );
			}
			
			$comma = ", ";
			$last = $in;
		}

		$short .= "$out</p><p>" unless $out eq "";
	}

	if ( $node != $rootnode && exists $node->{InBy} ) {
		my $comma .= "Inherited by: ";

		@list = ();
		kdocAstUtil::inheritedBy( \@list, $node );

		# FIXME: bad hack to get rid of duplicates.
		my $last = 1;

		foreach $in ( sort { $a->{astNodeName} cmp $b->{astNodeName} }
				@list ) {
			$short .= $comma.refNameFull( $in, $rootnode );
			if ( exists $in->{ExtSource} ) {
				$short .= " <small>(".
				$in->{ExtSource}.")</small>";
			}
	
			$comma = ", ";
		}
		$short .= "</p><p>";
	}

	$short .= "<small>".hyper( "#longdesc", "More..." )."</small>";

	# print it

	print CLASS pageHeader( 
		$node->{NodeType}." ".esc($node->{astNodeName}), 
		$short );


	# member list
	
	print CLASS "<p>", hyper( encodeURL("full-list-".$node->{Ref}),
		"List of all Methods" ),"</p>\n";

	if ( exists $node->{Kids} ) {
		foreach my $type ( kdocAstUtil::allTypes( $node->{Kids} ) ) {
			@list = ();
			kdocAstUtil::findNodes( \@list, $node->{Kids}, 
					"NodeType", $type );

			listMembers( $node, exists $typedesc{$type}
				? $typedesc{$type} : $type, \@list );
		}

	}
	else {
		print CLASS "<center><H4>No members</H4></center>\n";
		return;
	}

	# long description
	if ( $hasdoc ) {
		print CLASS "<HR><A NAME=\"longdesc\">",
			"<H2>Detailed Description</H2>";
		printDoc( $docnode, *CLASS, $rootnode, 1 );
	}

	# member doc
	my $kid;
	my ($numref, $ref);

	foreach $kid ( @{$node->{Kids}} ) {
			next if defined $kid->{ExtSource} 
			|| (!$main::doPrivate && 
				$kid->{Access} =~ /private/);

		if( exists $kid->{Compound} ) {
			print "idl: Queueing $kid->{astNodeName} for doc\n"
				if $debug;
			push @docQueue, $kid;
		}

		# we check this after checking if the node is compound,
		# so it is queued for c.doc even if it doesn't have explicit
		# documentation.
		next if !defined $kid->{DocNode};

		if( !exists $kid->{NumRef} ) {
			warn $kid->{astNodeName}, " type ",
			$kid->{NodeType}, " doesn't have a numref\n";
		}

		( $numref = $kid->{NumRef} ) =~ s/^.*?#//g;
		( $ref = $kid->{Ref} ) =~ s/^.*?#//g;

		printMemberName( $kid, $ref, $numref );
		printDoc( $kid->{DocNode}, *CLASS, $rootnode );

		if ( $kid->{NodeType} eq "method" ) {
			$ref = kdocAstUtil::findOverride( $rootnode, $node, 
				$kid->{astNodeName} );
			if ( defined $ref ) {
				print CLASS "<p>Reimplemented from ",
					refNameFull( $ref, $rootnode ), "</p>\n";
			}
		}
	}

	# done

	if ( defined $version || defined $author ) {
		print CLASS "<HR><UL>",
			defined $version ? 
				"<LI><i>Version</i>: $version</LI>" : "",
			defined $author ? 
				"<LI><i>Author</i>: $author</LI>" : "",
			"<LI><i>Generated</i>: $gentext</LI></UL>",
			"</BODY></HTML>\n";
	}
	else {
		print CLASS $docBotty;
	}

	close CLASS;

	# full member list

	writeAllMembers( $node );
}




=head2 listMembers

	Parameters: compound node, description, node list ref?

=cut

sub listMembers
{
	my( $class, $desc, $nodelist ) = @_;
	my $name;
	my $type;
	my $flags;
	

	my $nodes = defined $nodelist ? $nodelist : $class->{Kids};

	if (  $#{$nodes} < 0 ) {
		print CLASS "<center><H4>No members</H4></center>\n";
		return;
	}

print CLASS<<EOF;
<H2>$desc</H2>
<UL>
EOF
	foreach $m ( @{$nodes} ) {
		next if exists $m->{ExtSource};
		if( exists $m->{Compound} ) {
			# compound docs not printed for rootnode
			next if $class eq $rootnode; 

			$name = refName( $m );
		}
		elsif( exists $m->{DocNode} ) {
			# compound nodes have their own page
			$name = refName( $m,  'NumRef' );
		} else {
			$name = esc( $m->{astNodeName} );
		}

		$type = $m->{NodeType};

		print CLASS "<LI>";

		if( $type eq "var" ) {
			print CLASS esc( $m->{Type}), 
				" <b>", $name,"</b>\n";
		}
		elsif( $type eq "method" ) {
			$flags = $m->{Flags};

			if ( !defined $flags ) {
				warn "Method ".$m->{astNodeName}.
					" has no flags\n";
			}

			$name = "<i>$name</i>" if $flags =~ /p/;
			my $extra = "";
			$extra .= "virtual " if $flags =~ "v";
			$extra .= "static " if $flags =~ "s";

			print CLASS $extra, esc($m->{ReturnType}),
				" <b>", $name, "</b> (", 
				esc($m->{Params}), ") ", 
				$flags =~ /c/ ? " const\n": "\n";
		}
		elsif( $type eq "enum" ) {
			print CLASS "enum <b>", $name, "</b> {",
				esc($m->{Params}),"}\n";
		}
		elsif( $type eq "typedef" ) {
			print CLASS "typedef ", 
				esc($m->{Type}), " <b>",
				$name,"</b>";
		}
		else { 
			# unknown type
			print CLASS esc($type), " <b>",
				$name,"</b>\n";
		}

		print CLASS "</LI>\n";
	}

print CLASS<<EOF;
</UL>
EOF

}

=head2 printMemberName

	Parameters: member node, names...

	Prints the name of one member, customized to type. If names are
	specified, a name anchor is written for each one.

=cut

sub printMemberName
{
	my $m = shift;

	my $name = esc( $m->{astNodeName} );
	my $type = $m->{NodeType};
	my $ref;
	my $flags = undef;

	foreach $ref ( @_ ) {
		print CLASS "<A NAME=", $ref, "></A>";
	}

	print CLASS "<HR><p><strong>";

	if( $type eq "var" ) {
		print CLASS textRef($m->{Type}, $rootnode ), 
		" <b>", $name,"</b>\n";
	}
	elsif( $type eq "method" ) {
		$flags = $m->{Flags};
		$name = "<i>$name</i>" if $flags =~ /p/;

		print CLASS textRef($m->{ReturnType}, $rootnode ),
		" <b>", $name, "</b> (", 
		textRef($m->{Params}, $rootnode ), ")\n";
	}
	elsif( $type eq "enum" ) {
		print CLASS "enum <b>", $name, "</b> {",
		esc($m->{Params}),"}\n";
	}
	elsif( $type eq "typedef" ) {
		print CLASS "typedef ", 
		textRef($m->{Type}, $rootnode ), " <b>",
		$name,"</b>";
	}
	else {
		print CLASS $name, " <small>(", 
			esc($type), ")</small>";
	}

	print CLASS "</strong>";

# extra attributes
	my @extra = ();

	if( !exists $m->{Access} ) {
		print "Member without access:\n";
		kdocAstUtil::dumpAst( $m );
	}

	($ref = $m->{Access}) =~ s/_slots//g;

	push @extra, $ref
		unless $ref =~ /public/
		|| $ref =~ /signal/;

	if ( defined $flags ) {
		my $f;
		my $n;
		foreach $f ( split( "", $flags ) ) {
			$n = $main::flagnames{ $f };
			warn "flag $f has no long name.\n" if !defined $n;
			push @extra, $n;
		}
	}

	if ( $#extra >= 0 ) {
		print CLASS " <small>[", join( " ", @extra ), "]</small>";
	}

	print CLASS "</p>";
	
# finis
}



=head2 makeModuleList

	Parameters: node

	fills global @clist with a list of all direct, non-external
	compound children of node.

=cut

sub makeModuleList
{
	my ( $rootnode ) = @_;

	@clist = ();

	foreach my $node ( @{$rootnode->{Kids}} ) {
		confess "undefined child in rootnode" unless defined $node;

		push @clist, $node unless (exists $node->{ExtSource}
				|| !exists $node->{Compound});

	}

	@clist = sort { $a->{astNodeName} cmp $b->{astNodeName}  }
			@clist;
}

1;
