# Make prototypes from .c files
# $Id$

use Getopt::Std;
use File::Compare;

use JSON;

my $comment = 0;
my $doxygen = 0;
my $funcdoc = 0;
my $if_0 = 0;
my $brace = 0;
my $line = "";
my $debug = 0;
my $oproto = 1;
my $private_func_re = "^_";
my %depfunction;
my %exported;
my %deprecated;
my $apple = 0;
my %documentation;

getopts('x:m:o:p:dqE:R:P:') || die "foo";
if($opt_a) {
    $apple = 1;
}

if($opt_a) {
    $apple = 1;
}

if($opt_d) {
    $debug = 1;
}

if($opt_q) {
    $oproto = 0;
}

if($opt_R) {
    $private_func_re = $opt_R;
}
my %flags = (
	  'multiline-proto' => 1,
	  'header' => 1,
	  'function-blocking' => 0,
	  'gnuc-attribute' => 1,
	  'cxx' => 1
	  );
if($opt_m) {
    foreach $i (split(/,/, $opt_m)) {
	if($i eq "roken") {
	    $flags{"multiline-proto"} = 0;
	    $flags{"header"} = 0;
	    $flags{"function-blocking"} = 0;
	    $flags{"gnuc-attribute"} = 0;
	    $flags{"cxx"} = 0;
	} else {
	    if(substr($i, 0, 3) eq "no-") {
		$flags{substr($i, 3)} = 0;
	    } else {
		$flags{$i} = 1;
	    }
	}
    }
}

if($opt_x) {
    my $EXP;
    local $/;
    open(EXP, '<', $opt_x) || die "open ${opt_x}";
    my $obj = JSON->new->utf8->decode(<EXP>);
    close $EXP;

    foreach my $x (keys %$obj) {
	if (defined $obj->{$x}->{"export"}) {
	    $exported{$x} = $obj->{$x};
	}
	if (defined $obj->{$x}->{"deprecated"}) {
	    $deprecated{$x} = $obj->{$x}->{"deprecated"};
	}
    }
}

while(<>) {
    print $brace, " ", $_ if($debug);
    
    # Handle C comments
    s@/\*.*\*/@@;
    s@//.*/@@;
    if ( s@/\*\*(.*)@@) { $comment = 1; $doxygen = 1; $funcdoc = $1;
    } elsif ( s@/\*.*@@) { $comment = 1;
    } elsif ($comment && s@.*\*/@@) { $comment = 0; $doxygen = 0;
    } elsif ($doxygen) { $funcdoc .= $_; next;
    } elsif ($comment) { next; }

    if(/^\#if 0/) {
	$if_0 = 1;
    }
    if($if_0 && /^\#endif/) {
	$if_0 = 0;
    }
    if($if_0) { next }
    if(/^\s*\#/) {
	next;
    }
    if(/^\s*$/) {
	$line = "";
	next;
    }
    if(/\{/){
	if (!/\}/) {
	    $brace++;
	}
	$_ = $line;
	while(s/\*\//\ca/){
	    s/\/\*(.|\n)*\ca//;
	}
	s/^\s*//;
	s/\s*$//;
	s/\s+/ /g;
	if($_ =~ /\)$/){
	    if(!/^static/ && !/^PRIVATE/){
		$attr = "";
		if(m/(.*)(__attribute__\s?\(.*\))/) {
		    $attr .= " $2";
		    $_ = $1;
		}
		if(m/(.*)\s(\w+DEPRECATED_FUNCTION)\s?(\(.*\))(.*)/) {
		    $depfunction{$2} = 1;
		    $attr .= " $2$3";
		    $_ = "$1 $4";
		}
		if(m/(.*)\s(\w+DEPRECATED)(.*)/) {
		    $attr .= " $2";
		    $_ = "$1 $3";
		}
		if(m/(.*)\s(HEIMDAL_\w+_ATTRIBUTE)\s?(\(.*\))?(.*)/) {
		    $attr .= " $2$3";
		    $_ = "$1 $4";
		}
		# remove outer ()
		s/\s*\(/</;
		s/\)\s?$/>/;
		# remove , within ()
		while(s/\(([^()]*),(.*)\)/($1\$$2)/g){}
		s/\<\s*void\s*\>/<>/;
		# remove parameter names 
		if($opt_P eq "remove") {
		    s/(\s*)([a-zA-Z0-9_]+)([,>])/$3/g;
		    s/\s+\*/*/g;
		    s/\(\*(\s*)([a-zA-Z0-9_]+)\)/(*)/g;
		} elsif($opt_P eq "comment") {
		    s/([a-zA-Z0-9_]+)([,>])/\/\*$1\*\/$2/g;
		    s/\(\*([a-zA-Z0-9_]+)\)/(*\/\*$1\*\/)/g;
		}
		s/\<\>/<void>/;
		# add newlines before parameters
		if($flags{"multiline-proto"}) {
		    s/,\s*/,\n\t/g;
		} else {
		    s/,\s*/, /g;
		}
		# fix removed ,
		s/\$/,/g;
		# match function name
		/([a-zA-Z0-9_]+)\s*\</;
		$f = $1;
		if($oproto) {
		    $LP = "__P((";
		    $RP = "))";
		} else {
		    $LP = "(";
		    $RP = ")";
		}
		# only add newline if more than one parameter
                if($flags{"multiline-proto"} && /,/){ 
		    s/\</ $LP\n\t/;
		}else{
		    s/\</ $LP/;
		}
		s/\>/$RP/;
		# insert newline before function name
		if($flags{"multiline-proto"}) {
		    s/(.*)\s([a-zA-Z0-9_]+ \Q$LP\E)/$1\n$2/;
		}
		if($attr ne "") {
		    $_ .= "\n    $attr";
		}
		if ($funcdoc) {
		    $documentation{$f} = $funcdoc;
		}
		$funcdoc = undef;
		if ($apple && exists $exported{$f}) {
		    $ios = $exported{$f}{ios};
		    $ios = "NA" if (!defined $ios);
		    $mac = $exported{$f}{macos};
		    $mac = "NA" if (!defined $mac);
		    die "$f neither" if ($mac eq "NA" and $ios eq "NA");
		    $_ = $_ . "  __OSX_AVAILABLE_STARTING(__MAC_${mac}, __IPHONE_${ios})";
		}
		if (exists $deprecated{$f}) {
		    $_ = $_ . "  GSSAPI_DEPRECATED_FUNCTION(\"$deprecated{$f}\")";
		    $depfunction{GSSAPI_DEPRECATED_FUNCTION} = 1;
		}
		$_ = $_ . ";";
		$funcs{$f} = $_;
	    }
	}
	$line = "";
    }
    if(/\}/){
	$brace--;
    }
    if(/^\}/){
	$brace = 0;
    }
    if($brace == 0) {
	$line = $line . " " . $_;
    }
}

die "reached end of code and still in doxygen comment" if ($doxygen);
die "reached end of code and still in comment" if ($comment);

sub foo {
    local ($arg) = @_;
    $_ = $arg;
    s/.*\/([^\/]*)/$1/;
    s/.*\\([^\\]*)/$1/;
    s/[^a-zA-Z0-9]/_/g;
    "__" . $_ . "__";
}

if($opt_o) {
    open(OUT, ">${opt_o}.new");
    $block = &foo($opt_o);
} else {
    $block = "__public_h__";
}

if($opt_p) {
    open(PRIV, ">${opt_p}.new");
    $private = &foo($opt_p);
} else {
    $private = "__private_h__";
}

$public_h = "";
$private_h = "";

$public_h_header .= "/* This is a generated file */
#ifndef $block
#define $block
#ifndef DOXY

";
if ($oproto) {
    $public_h_header .= "#ifdef __STDC__
#include <stdarg.h>
#ifndef __P
#define __P(x) x
#endif
#else
#ifndef __P
#define __P(x) ()
#endif
#endif

";
} else {
    $public_h_header .= "#include <stdarg.h>

";
}
$public_h_trailer = "";

$private_h_header = "/* This is a generated file */
#ifndef $private
#define $private

";
if($oproto) {
    $private_h_header .= "#ifdef __STDC__
#include <stdarg.h>
#ifndef __P
#define __P(x) x
#endif
#else
#ifndef __P
#define __P(x) ()
#endif
#endif

";
} else {
    $private_h_header .= "#include <stdarg.h>

";
}
$private_h_trailer = "";


foreach(sort keys %funcs){
    if(/^(DllMain|main)$/) { next }
    if ($funcs{$_} =~ /\^/) {
	$beginblock = "#ifdef __BLOCKS__\n";
	$endblock = "#endif /* __BLOCKS__ */\n";
    } else {
	$beginblock = $endblock = "";
    }
    # if we have an export table and doesn't have content, or matches private RE
    if((scalar(keys(%exported)) ne 0 && !exists $exported{$_} ) || /$private_func_re/) {
	$private_h .= $beginblock;
#	if ($apple and not /$private_func_re/) {
#	    $private_h .= "#define $_ __ApplePrivate_${_}\n";
#	}
	$private_h .= $funcs{$_} . "\n" ;
	$private_h .= $endblock . "\n";
	if($funcs{$_} =~ /__attribute__/) {
	    $private_attribute_seen = 1;
	}
    } else {
	if($documentation{$_}) {
	    $public_h .= "/**\n";
	    $public_h .= "$documentation{$_}";
	    $public_h .= " */\n\n";
	}
	if($flags{"function-blocking"}) {
	    $fupper = uc $_;
	    if($exported{$_} =~ /proto/) {
		$public_h .= "#if !defined(HAVE_$fupper) || defined(NEED_${fupper}_PROTO)\n";
	    } else {
		$public_h .= "#ifndef HAVE_$fupper\n";
	    }
	}
	$public_h .= $beginblock . $funcs{$_} . "\n" . $endblock;
	if($funcs{$_} =~ /__attribute__/) {
	    $public_attribute_seen = 1;
	}
	if($flags{"function-blocking"}) {
	    $public_h .= "#endif\n";
	}
	$public_h .= "\n";
    }
}

if($flags{"gnuc-attribute"}) {
    if ($public_attribute_seen) {
	$public_h_header .= "#if !defined(__GNUC__) && !defined(__attribute__)
#define __attribute__(x)
#endif

";
    }

    if ($private_attribute_seen) {
	$private_h_header .= "#if !defined(__GNUC__) && !defined(__attribute__)
#define __attribute__(x)
#endif

";
    }
}

my $depstr = "";
my $undepstr = "";
foreach (keys %depfunction) {
    $depstr .= "#ifndef $_
#ifndef __has_extension
#define __has_extension(x) 0
#define ${_}has_extension 1
#endif
#if __has_extension(attribute_deprecated_with_message)
#define $_(x) __attribute__((__deprecated__(x)))
#elif defined(__GNUC__) && ((__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1 )))
#define $_(X) __attribute__((__deprecated__))
#else
#define $_(X)
#endif
#ifdef ${_}has_extension
#undef __has_extension
#undef ${_}has_extension
#endif
#endif /* $_ */


";
    $public_h_trailer .= "#undef $_

";
    $private_h_trailer .= "#undef $_
#define $_(X)

";
}

$public_h_header .= $depstr;
$private_h_header .= $depstr;


if($flags{"cxx"}) {
    $public_h_header .= "#ifdef __cplusplus
extern \"C\" {
#endif

";
    $public_h_trailer = "#ifdef __cplusplus
}
#endif

" . $public_h_trailer;

}
if ($opt_E) {
    $public_h_header .= "#ifndef $opt_E
#ifndef ${opt_E}_FUNCTION
#if defined(_WIN32)
#define ${opt_E}_FUNCTION __declspec(dllimport)
#define ${opt_E}_CALL __stdcall
#define ${opt_E}_VARIABLE __declspec(dllimport)
#else
#define ${opt_E}_FUNCTION
#define ${opt_E}_CALL
#define ${opt_E}_VARIABLE
#endif
#endif
#endif
";
    
    $private_h_header .= "#ifndef $opt_E
#ifndef ${opt_E}_FUNCTION
#if defined(_WIN32)
#define ${opt_E}_FUNCTION __declspec(dllimport)
#define ${opt_E}_CALL __stdcall
#define ${opt_E}_VARIABLE __declspec(dllimport)
#else
#define ${opt_E}_FUNCTION
#define ${opt_E}_CALL
#define ${opt_E}_VARIABLE
#endif
#endif
#endif

";
}
    
$public_h_trailer .= $undepstr;
$private_h_trailer .= $undepstr;

if ($public_h ne "" && $flags{"header"}) {
    $public_h = $public_h_header . $public_h . 
	$public_h_trailer . "#endif /* DOXY */\n#endif /* $block */\n";
}
if ($private_h ne "" && $flags{"header"}) {
    $private_h = $private_h_header . $private_h .
	$private_h_trailer . "#endif /* $private */\n";
}

if($opt_o) {
    print OUT $public_h;
} 
if($opt_p) {
    print PRIV $private_h;
} 

close OUT;
close PRIV;

if ($opt_o) {

    if (compare("${opt_o}.new", ${opt_o}) != 0) {
	printf("updating ${opt_o}\n");
	rename("${opt_o}.new", ${opt_o});
    } else {
	unlink("${opt_o}.new");
    }
}
	
if ($opt_p) {
    if (compare("${opt_p}.new", ${opt_p}) != 0) {
	printf("updating ${opt_p}\n");
	rename("${opt_p}.new", ${opt_p});
    } else {
	unlink("${opt_p}.new");
    }
}
