###################################################
# create C header files for an IDL structure
# Copyright tridge@samba.org 2000
# Copyright jelmer@samba.org 2005
# released under the GNU GPL

package Parse::Pidl::Samba4::Header;

use strict;
use Parse::Pidl::Typelist qw(mapType);
use Parse::Pidl::Util qw(has_property is_constant);
use Parse::Pidl::NDR qw(GetNextLevel GetPrevLevel);
use Parse::Pidl::Samba4 qw(is_intree);

use vars qw($VERSION);
$VERSION = '0.01';

my($res);
my($tab_depth);

sub pidl($) { $res .= shift; }

sub tabs()
{
	my $res = "";
	$res .="\t" foreach (1..$tab_depth);
	return $res;
}

#####################################################################
# parse a properties list
sub HeaderProperties($$)
{
    my($props,$ignores) = @_;
	my $ret = "";

    foreach my $d (keys %{$props}) {
		next if (grep(/^$d$/, @$ignores));
		if($props->{$d} ne "1") {
			$ret.= "$d($props->{$d}),";
		} else {
			$ret.="$d,";
		}
	}

	if ($ret) {
		pidl "/* [" . substr($ret, 0, -1) . "] */";
	}
}

#####################################################################
# parse a structure element
sub HeaderElement($)
{
	my($element) = shift;

	pidl tabs();
	if (has_property($element, "represent_as")) {
		pidl mapType($element->{PROPERTIES}->{represent_as})." ";
	} else {
		HeaderType($element, $element->{TYPE}, "");
		pidl " ";
		my $numstar = $element->{POINTERS};
		if ($numstar >= 1) {
			$numstar-- if Parse::Pidl::Typelist::scalar_is_reference($element->{TYPE});
		}
		foreach (@{$element->{ARRAY_LEN}})
		{
			next if is_constant($_) and 
				not has_property($element, "charset");
			$numstar++;
		}
		pidl "*" foreach (1..$numstar);
	}
	pidl $element->{NAME};
	foreach (@{$element->{ARRAY_LEN}}) {
		next unless (is_constant($_) and 
			not has_property($element, "charset"));
		pidl "[$_]";
	}

	pidl ";";
	if (defined $element->{PROPERTIES}) {
		HeaderProperties($element->{PROPERTIES}, ["in", "out"]);
	}
	pidl "\n";
}

#####################################################################
# parse a struct
sub HeaderStruct($$)
{
    my($struct,$name) = @_;
	pidl "struct $name {\n";
    $tab_depth++;
    my $el_count=0;
    if (defined $struct->{ELEMENTS}) {
		foreach (@{$struct->{ELEMENTS}}) {
		    HeaderElement($_);
		    $el_count++;
		}
    }
    if ($el_count == 0) {
	    # some compilers can't handle empty structures
	    pidl tabs()."char _empty_;\n";
    }
    $tab_depth--;
    pidl tabs()."}";
	if (defined $struct->{PROPERTIES}) {
		HeaderProperties($struct->{PROPERTIES}, []);
	}
}

#####################################################################
# parse a enum
sub HeaderEnum($$)
{
    my($enum,$name) = @_;
    my $first = 1;

    if (not Parse::Pidl::Util::useUintEnums()) {
    	pidl "enum $name {\n";
	$tab_depth++;
	foreach my $e (@{$enum->{ELEMENTS}}) {
 	    unless ($first) { pidl ",\n"; }
	    $first = 0;
	    pidl tabs();
	    pidl $e;
	}
	pidl "\n";
	$tab_depth--;
	pidl "}";
    } else {
        my $count = 0;
	pidl "enum $name { __donnot_use_enum_$name=0x7FFFFFFF};\n";
	my $with_val = 0;
	my $without_val = 0;
	foreach my $e (@{$enum->{ELEMENTS}}) {
	    my $t = "$e";
	    my $name;
	    my $value;
	    if ($t =~ /(.*)=(.*)/) {
	    	$name = $1;
	    	$value = $2;
		$with_val = 1;
		die ("you can't mix enum member with values and without values when using --uint-enums!")
			unless ($without_val == 0);
	    } else {
	    	$name = $t;
	    	$value = $count++;
		$without_val = 1;
		die ("you can't mix enum member with values and without values when using --uint-enums!")
			unless ($with_val == 0);
	    }
	    pidl "#define $name ( $value )\n";
	}
	pidl "\n";
    }
}

#####################################################################
# parse a bitmap
sub HeaderBitmap($$)
{
    my($bitmap,$name) = @_;

    pidl "/* bitmap $name */\n";
    pidl "#define $_\n" foreach (@{$bitmap->{ELEMENTS}});
    pidl "\n";
}

#####################################################################
# parse a union
sub HeaderUnion($$)
{
	my($union,$name) = @_;
	my %done = ();

	pidl "union $name {\n";
	$tab_depth++;
	foreach my $e (@{$union->{ELEMENTS}}) {
		if ($e->{TYPE} ne "EMPTY") {
			if (! defined $done{$e->{NAME}}) {
				HeaderElement($e);
			}
			$done{$e->{NAME}} = 1;
		}
	}
	$tab_depth--;
	pidl "}";

	if (defined $union->{PROPERTIES}) {
		HeaderProperties($union->{PROPERTIES}, []);
	}
}

sub decorate($$)
{
        my($name,$levels) = @_;
	my $prev_ptr = 0;
	foreach my $i (0..$#{$levels}) {
	    if ($levels->[$i]{TYPE} eq "POINTER") {
		    $name = "*$name";
		    $prev_ptr = 1;
	    } elsif ($levels->[$i]{TYPE} eq "ARRAY") {
		    $name = ($prev_ptr ? "($name)" : $name) . "[$levels->[$i]{SIZE_IS}]";
	    }
	}
	$name
}

sub HeaderDecorated($$)
{
        my($e,$name) = @_;
	$name = $e->{NAME} unless defined($name);
	pidl "typedef ";
	if (has_property($e, "charset")) {
		pidl "const char";
	} else {
		pidl mapType($e->{DATA}{DATA_TYPE});
	}
	pidl " " . decorate($name, $e->{DATA}{LEVELS});
}

#####################################################################
# parse a type
sub HeaderType($$$)
{
	my($e,$data,$name) = @_;
	if (ref($data) eq "HASH") {
		($data->{TYPE} eq "ENUM") && HeaderEnum($data, $name);
		($data->{TYPE} eq "BITMAP") && HeaderBitmap($data, $name);
		($data->{TYPE} eq "STRUCT") && HeaderStruct($data, $name);
		($data->{TYPE} eq "UNION") && HeaderUnion($data, $name);
		($data->{TYPE} eq "DECORATED") && HeaderDecorated($e, $name);
		return;
	}

	if (has_property($e, "charset") and $e->{TYPEREF}{DATA}{TYPE} ne "DECORATED") {
		pidl "const char";
	} else {
		pidl mapType($e->{TYPE});
	}
}

#####################################################################
# parse a typedef
sub HeaderTypedef($)
{
    my($typedef) = shift;
    HeaderType($typedef, $typedef->{DATA}, $typedef->{NAME});
    pidl ";\n\n" unless ($typedef->{DATA}->{TYPE} eq "BITMAP");
}

#####################################################################
# parse a const
sub HeaderConst($)
{
    my($const) = shift;
    if (!defined($const->{ARRAY_LEN}[0])) {
    	pidl "#define $const->{NAME}\t( $const->{VALUE} )\n";
    } else {
    	pidl "#define $const->{NAME}\t $const->{VALUE}\n";
    }
}

#####################################################################
# parse a function
sub HeaderFunctionInOut($$)
{
    my($fn,$prop) = @_;

    foreach (@{$fn->{ELEMENTS}}) {
		HeaderElement($_) if (has_property($_, $prop));
    }
}

#####################################################################
# determine if we need an "in" or "out" section
sub HeaderFunctionInOut_needed($$)
{
    my($fn,$prop) = @_;

    return 1 if ($prop eq "out" && $fn->{RETURN_TYPE} ne "void");

    foreach (@{$fn->{ELEMENTS}}) {
	    return 1 if (has_property($_, $prop));
    }

    return undef;
}

my %headerstructs;

#####################################################################
# parse a function
sub HeaderFunction($)
{
    my($fn) = shift;

    return if ($headerstructs{$fn->{NAME}});

    $headerstructs{$fn->{NAME}} = 1;

    pidl "\nstruct $fn->{NAME} {\n";
    $tab_depth++;
    my $needed = 0;

    if (HeaderFunctionInOut_needed($fn, "in")) {
	    pidl tabs()."struct {\n";
	    $tab_depth++;
	    HeaderFunctionInOut($fn, "in");
	    $tab_depth--;
	    pidl tabs()."} in;\n\n";
	    $needed++;
    }

    if (HeaderFunctionInOut_needed($fn, "out")) {
	    pidl tabs()."struct {\n";
	    $tab_depth++;
	    HeaderFunctionInOut($fn, "out");
	    if ($fn->{RETURN_TYPE} ne "void") {
		    pidl tabs().mapType($fn->{RETURN_TYPE}) . " result;\n";
	    }
	    $tab_depth--;
	    pidl tabs()."} out;\n\n";
	    $needed++;
    }

    if (! $needed) {
	    # sigh - some compilers don't like empty structures
	    pidl tabs()."int _dummy_element;\n";
    }

    $tab_depth--;
    pidl "};\n\n";
}

sub HeaderImport
{
	my @imports = @_;
	foreach (@imports) {
		s/\.idl\"$//;
		s/^\"//;
		pidl "#include \"librpc/gen_ndr/$_\.h\"\n";
	}
}

sub HeaderInclude
{
	my @includes = @_;
	foreach (@includes) {
		pidl "#include $_\n";
	}
}

#####################################################################
# parse the interface definitions
sub HeaderInterface($)
{
	my($interface) = shift;

	pidl "#ifndef _HEADER_$interface->{NAME}\n";
	pidl "#define _HEADER_$interface->{NAME}\n\n";

	foreach my $d (@{$interface->{DATA}}) {
		next if ($d->{TYPE} ne "CONST");
		HeaderConst($d);
	}

	foreach my $d (@{$interface->{DATA}}) {
		next if ($d->{TYPE} ne "TYPEDEF");
		HeaderTypedef($d);
	}

	foreach my $d (@{$interface->{DATA}}) {
		next if ($d->{TYPE} ne "FUNCTION");

		HeaderFunction($d);
	}

	pidl "#endif /* _HEADER_$interface->{NAME} */\n";
}

#####################################################################
# parse a parsed IDL into a C header
sub Parse($)
{
    my($idl) = shift;
    $tab_depth = 0;

	$res = "";
	%headerstructs = ();
    pidl "/* header auto-generated by pidl */\n\n";
	if (!is_intree()) {
		pidl "#include <core.h>\n\n";
	}
	
    foreach (@{$idl}) {
	    ($_->{TYPE} eq "INTERFACE") && HeaderInterface($_);
	    ($_->{TYPE} eq "IMPORT") && HeaderImport(@{$_->{PATHS}});
	    ($_->{TYPE} eq "INCLUDE") && HeaderInclude(@{$_->{PATHS}});
    }
    return $res;
}

1;
