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

package NdrHeader;

use strict;
use pidl::typelist;

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

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

sub tabs()
{
	for (my($i)=0; $i < $tab_depth; $i++) {
		pidl "\t";
	}
}

#####################################################################
# 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();
	HeaderType($element, $element->{TYPE}, "");
	pidl " ";
	my $prefix = "";
	my $postfix = "";
	foreach my $l (@{$element->{LEVELS}}) 
	{
		if (($l->{TYPE} eq "POINTER")) {
			my $nl = Ndr::GetNextLevel($element, $l);
			$nl = Ndr::GetNextLevel($element, $nl) if ($nl->{TYPE} eq "SUBCONTEXT");
			next if ($nl->{TYPE} eq "DATA" and typelist::scalar_is_reference($nl->{DATA_TYPE}));
			$prefix .= "*";
		} elsif ($l->{TYPE} eq "ARRAY") {
			my $pl = Ndr::GetPrevLevel($element, $l);
			next if ($pl and $pl->{TYPE} eq "POINTER");

			if ($l->{IS_FIXED}) { 
				$postfix .= "[$l->{SIZE_IS}]"; 
			} else {
				$prefix .= "*";
			}
		} elsif ($l->{TYPE} eq "DATA") {
    			pidl "$prefix$element->{NAME}$postfix";
		}
	}

	if (defined $element->{ARRAY_LEN}[0] && util::is_constant($element->{ARRAY_LEN}[0])) {
		pidl "[$element->{ARRAY_LEN}[0]]";
	}
	pidl ";";
	if (defined $element->{PROPERTIES}) {
		HeaderProperties($element->{PROPERTIES}, ["in", "out"]);
	}
	pidl "\n";
}

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

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

    if (not util::useUintEnums()) {
    	pidl "\nenum $name {\n";
	$tab_depth++;
	foreach my $e (@{$enum->{ELEMENTS}}) {
 	    unless ($first) { pidl ",\n"; }
	    $first = 0;
	    tabs();
	    pidl $e;
	}
	pidl "\n";
	$tab_depth--;
	pidl "}";
    } else {
        my $count = 0;
	pidl "\nenum $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 "\n/* bitmap $name */\n";

    foreach my $e (@{$bitmap->{ELEMENTS}})
    {
	    pidl "#define $e\n";
    }

    pidl "\n";
}

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

	pidl "\nunion $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}, []);
	}
}

#####################################################################
# 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);
		return;
	}

	if (util::has_property($e, "charset")) {
		pidl "char";
	} else {
		pidl typelist::mapType($e->{TYPE});
	}
}

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

#####################################################################
# prototype a typedef
sub HeaderTypedefProto($)
{
    my($d) = shift;

	my $tf = NdrParser::get_typefamily($d->{DATA}{TYPE});

    if (util::has_property($d, "gensize")) {
		my $size_args = $tf->{SIZE_FN_ARGS}->($d);
		pidl "size_t ndr_size_$d->{NAME}($size_args);\n";
    }

    return unless util::has_property($d, "public");

	my $pull_args = $tf->{PULL_FN_ARGS}->($d);
	my $push_args = $tf->{PUSH_FN_ARGS}->($d);
	my $print_args = $tf->{PRINT_FN_ARGS}->($d);
	unless (util::has_property($d, "nopush")) {
		pidl "NTSTATUS ndr_push_$d->{NAME}($push_args);\n";
	}
	unless (util::has_property($d, "nopull")) {
	    pidl "NTSTATUS ndr_pull_$d->{NAME}($pull_args);\n";
	}
    unless (util::has_property($d, "noprint")) {
	    pidl "void ndr_print_$d->{NAME}($print_args);\n";
    }
}

#####################################################################
# 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 my $e (@{$fn->{ELEMENTS}}) {
	    if (util::has_property($e, $prop)) {
		    HeaderElement($e);
	    }
    }
}

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

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

    foreach my $e (@{$fn->{ELEMENTS}}) {
	    if (util::has_property($e, $prop)) {
		    return 1;
	    }
    }

    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")) {
	    tabs();
	    pidl "struct {\n";
	    $tab_depth++;
	    HeaderFunctionInOut($fn, "in");
	    $tab_depth--;
	    tabs();
	    pidl "} in;\n\n";
	    $needed++;
    }

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

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

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

#####################################################################
# output prototypes for a IDL function
sub HeaderFnProto($$)
{
	my ($interface,$fn) = @_;
	my $name = $fn->{NAME};

	pidl "void ndr_print_$name(struct ndr_print *ndr, const char *name, int flags, const struct $name *r);\n";

	if (defined($fn->{OPNUM})) {
		pidl "NTSTATUS dcerpc_$name(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx, struct $name *r);\n";
   		pidl "struct rpc_request *dcerpc_$name\_send(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx, struct $name *r);\n";
	}

	return unless util::has_property($fn, "public");

	pidl "NTSTATUS ndr_push_$name(struct ndr_push *ndr, int flags, const struct $name *r);\n";
	pidl "NTSTATUS ndr_pull_$name(struct ndr_pull *ndr, int flags, struct $name *r);\n";

	pidl "\n";
}

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

	my $count = 0;

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

	if (defined $interface->{PROPERTIES}->{depends}) {
		my @d = split / /, $interface->{PROPERTIES}->{depends};
		foreach my $i (@d) {
			pidl "#include \"librpc/gen_ndr/ndr_$i\.h\"\n";
		}
	}

	if (defined $interface->{PROPERTIES}->{uuid}) {
		my $name = uc $interface->{NAME};
		pidl "#define DCERPC_$name\_UUID " . 
		util::make_str($interface->{PROPERTIES}->{uuid}) . "\n";

		if(!defined $interface->{PROPERTIES}->{version}) { $interface->{PROPERTIES}->{version} = "0.0"; }
		pidl "#define DCERPC_$name\_VERSION $interface->{PROPERTIES}->{version}\n";

		pidl "#define DCERPC_$name\_NAME \"$interface->{NAME}\"\n";

		if(!defined $interface->{PROPERTIES}->{helpstring}) { $interface->{PROPERTIES}->{helpstring} = "NULL"; }
		pidl "#define DCERPC_$name\_HELPSTRING $interface->{PROPERTIES}->{helpstring}\n";

		pidl "\nextern const struct dcerpc_interface_table dcerpc_table_$interface->{NAME};\n";
		pidl "NTSTATUS dcerpc_server_$interface->{NAME}_init(void);\n\n";
	}

	foreach my $d (@{$interface->{FUNCTIONS}}) {
		next if not defined($d->{OPNUM});
		my $u_name = uc $d->{NAME};
		pidl "#define DCERPC_$u_name (";
	
		if (defined($interface->{BASE})) {
			pidl "DCERPC_" . uc $interface->{BASE} . "_CALL_COUNT + ";
		}

		if ($d->{OPNUM} != $count) {
			die ("Function ".$d->{NAME}." has: wrong opnum [".$d->{OPNUM}."] should be [".$count."]");
		}

		pidl sprintf("0x%02x", $count) . ")\n";
		$count++;
	}

	pidl "\n#define DCERPC_" . uc $interface->{NAME} . "_CALL_COUNT (";
	
	if (defined($interface->{BASE})) {
		pidl "DCERPC_" . uc $interface->{BASE} . "_CALL_COUNT + ";
	}
	
	pidl "$count)\n\n";

	foreach my $d (@{$interface->{CONSTS}}) {
		HeaderConst($d);
	}

	foreach my $d (@{$interface->{TYPEDEFS}}) {
		HeaderTypedef($d);
		HeaderTypedefProto($d);
	}

	foreach my $d (@{$interface->{FUNCTIONS}}) {
		HeaderFunction($d);
		HeaderFnProto($interface, $d);
	}

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

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

	$res = "";
    pidl "/* header auto-generated by pidl */\n\n";
    foreach my $x (@{$idl}) {
	    ($x->{TYPE} eq "INTERFACE") && HeaderInterface($x);
    }
    return $res;
}

1;
