###################################################
# EJS function wrapper generator
# Copyright jelmer@samba.org 2005
# Copyright Andrew Tridgell 2005
# released under the GNU GPL

package Parse::Pidl::Samba4::EJS;

use strict;
use Parse::Pidl::Typelist;
use Parse::Pidl::Util qw(has_property);

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

my $res;
my $res_hdr;

my %constants;

my $tabs = "";

sub pidl_hdr ($)
{
	$res_hdr .= shift;
}

sub pidl($)
{
	my $d = shift;
	if ($d) {
		$res .= $tabs;
		$res .= $d;
	}
	$res .= "\n";
}

sub indent()
{
	$tabs .= "\t";
}

sub deindent()
{
	$tabs = substr($tabs, 0, -1);
}

# this should probably be in ndr.pm
sub GenerateStructEnv($)
{
	my $x = shift;
	my %env;

	foreach my $e (@{$x->{ELEMENTS}}) {
		if ($e->{NAME}) {
			$env{$e->{NAME}} = "r->$e->{NAME}";
		}
	}

	$env{"this"} = "r";

	return \%env;
}

sub GenerateFunctionInEnv($)
{
	my $fn = shift;
	my %env;

	foreach my $e (@{$fn->{ELEMENTS}}) {
		if (grep (/in/, @{$e->{DIRECTION}})) {
			$env{$e->{NAME}} = "r->in.$e->{NAME}";
		}
	}

	return \%env;
}

sub GenerateFunctionOutEnv($)
{
	my $fn = shift;
	my %env;

	foreach my $e (@{$fn->{ELEMENTS}}) {
		if (grep (/out/, @{$e->{DIRECTION}})) {
			$env{$e->{NAME}} = "r->out.$e->{NAME}";
		} elsif (grep (/in/, @{$e->{DIRECTION}})) {
			$env{$e->{NAME}} = "r->in.$e->{NAME}";
		}
	}

	return \%env;
}

sub get_pointer_to($)
{
	my $var_name = shift;
	
	if ($var_name =~ /^\*(.*)$/) {
		return $1;
	} elsif ($var_name =~ /^\&(.*)$/) {
		return "&($var_name)";
	} else {
		return "&$var_name";
	}
}

sub get_value_of($)
{
	my $var_name = shift;

	if ($var_name =~ /^\&(.*)$/) {
		return $1;
	} else {
		return "*$var_name";
	}
}

#####################################################################
# work out is a parse function should be declared static or not
sub fn_declare($$)
{
	my ($fn,$decl) = @_;

	if (has_property($fn, "public")) {
		pidl_hdr "$decl;\n";
		pidl "$decl";
	} else {
		pidl "static $decl";
	}
}

###########################
# pull a scalar element
sub EjsPullScalar($$$$$)
{
	my ($e, $l, $var, $name, $env) = @_;

	return if (has_property($e, "value"));

        my $pl = Parse::Pidl::NDR::GetPrevLevel($e, $l);
        $var = get_pointer_to($var);
        # have to handle strings specially :(
        if ($e->{TYPE} eq "string" && $pl && $pl->{TYPE} eq "POINTER") {
                $var = get_pointer_to($var);
        }
	pidl "NDR_CHECK(ejs_pull_$e->{TYPE}(ejs, v, $name, $var));";
}

###########################
# pull a pointer element
sub EjsPullPointer($$$$$)
{
	my ($e, $l, $var, $name, $env) = @_;
	pidl "if (ejs_pull_null(ejs, v, $name)) {";
	indent;
	pidl "$var = NULL;";
	deindent;
	pidl "} else {";
	indent;
	pidl "EJS_ALLOC(ejs, $var);";
	$var = get_value_of($var);		
	EjsPullElement($e, Parse::Pidl::NDR::GetNextLevel($e, $l), $var, $name, $env);
	deindent;
	pidl "}";
}

###########################
# pull a string element
sub EjsPullString($$$$$)
{
	my ($e, $l, $var, $name, $env) = @_;
	$var = get_pointer_to($var);
	pidl "NDR_CHECK(ejs_pull_string(ejs, v, $name, $var));";
}


###########################
# pull an array element
sub EjsPullArray($$$$$)
{
	my ($e, $l, $var, $name, $env) = @_;
	my $nl = Parse::Pidl::NDR::GetNextLevel($e, $l);
	my $length = Parse::Pidl::Util::ParseExpr($l->{LENGTH_IS}, $env);
	my $size = Parse::Pidl::Util::ParseExpr($l->{SIZE_IS}, $env);
	my $pl = Parse::Pidl::NDR::GetPrevLevel($e, $l);
	if ($pl && $pl->{TYPE} eq "POINTER") {
		$var = get_pointer_to($var);
	}
	# uint8 arrays are treated as data blobs
	if ($nl->{TYPE} eq 'DATA' && $e->{TYPE} eq 'uint8') {
		if (!$l->{IS_FIXED}) {
			pidl "EJS_ALLOC_N(ejs, $var, $size);";
		}
		pidl "ejs_pull_array_uint8(ejs, v, $name, $var, $length);";
		return;
	}
	my $avar = $var . "[i]";
	pidl "{";
	indent;
	pidl "uint32_t i;";
	if (!$l->{IS_FIXED}) {
		pidl "EJS_ALLOC_N(ejs, $var, $size);";
	}
	pidl "for (i=0;i<$length;i++) {";
	indent;
	pidl "char *id = talloc_asprintf(ejs, \"%s.%u\", $name, i);";
	EjsPullElement($e, $nl, $avar, "id", $env);
	pidl "talloc_free(id);";
	deindent;
	pidl "}";
	pidl "ejs_push_uint32(ejs, v, $name \".length\", &i);";
	deindent;
	pidl "}";
}

###########################
# pull a switch element
sub EjsPullSwitch($$$$$)
{
	my ($e, $l, $var, $name, $env) = @_;
	my $switch_var = Parse::Pidl::Util::ParseExpr($l->{SWITCH_IS}, $env);
	pidl "ejs_set_switch(ejs, $switch_var);";
	EjsPullElement($e, Parse::Pidl::NDR::GetNextLevel($e, $l), $var, $name, $env);
}

###########################
# pull a structure element
sub EjsPullElement($$$$$)
{
	my ($e, $l, $var, $name, $env) = @_;
	if (has_property($e, "charset")) {
		EjsPullString($e, $l, $var, $name, $env);
	} elsif ($l->{TYPE} eq "ARRAY") {
		EjsPullArray($e, $l, $var, $name, $env);
	} elsif ($l->{TYPE} eq "DATA") {
		EjsPullScalar($e, $l, $var, $name, $env);
	} elsif (($l->{TYPE} eq "POINTER")) {
		EjsPullPointer($e, $l, $var, $name, $env);
	} elsif (($l->{TYPE} eq "SWITCH")) {
		EjsPullSwitch($e, $l, $var, $name, $env);
	} else {
		pidl "return ejs_panic(ejs, \"unhandled pull type $l->{TYPE}\");";
	}
}

#############################################
# pull a structure/union element at top level
sub EjsPullElementTop($$)
{
	my $e = shift;
	my $env = shift;
	my $l = $e->{LEVELS}[0];
	my $var = Parse::Pidl::Util::ParseExpr($e->{NAME}, $env);
	my $name = "\"$e->{NAME}\"";
	EjsPullElement($e, $l, $var, $name, $env);
}

###########################
# pull a struct
sub EjsStructPull($$)
{
	my $name = shift;
	my $d = shift;
	my $env = GenerateStructEnv($d);
	fn_declare($d, "NTSTATUS ejs_pull_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, struct $name *r)");
	pidl "{";
	indent;
	pidl "NDR_CHECK(ejs_pull_struct_start(ejs, &v, name));";
        foreach my $e (@{$d->{ELEMENTS}}) {
		EjsPullElementTop($e, $env);
	}
	pidl "return NT_STATUS_OK;";
	deindent;
	pidl "}\n";
}

###########################
# pull a union
sub EjsUnionPull($$)
{
	my $name = shift;
	my $d = shift;
	my $have_default = 0;
	my $env = GenerateStructEnv($d);
	fn_declare($d, "NTSTATUS ejs_pull_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, union $name *r)");
	pidl "{";
	indent;
	pidl "NDR_CHECK(ejs_pull_struct_start(ejs, &v, name));";
	pidl "switch (ejs->switch_var) {";
	indent;
	foreach my $e (@{$d->{ELEMENTS}}) {
		if ($e->{CASE} eq "default") {
			$have_default = 1;
		}
		pidl "$e->{CASE}:";
		indent;
		if ($e->{TYPE} ne "EMPTY") {
			EjsPullElementTop($e, $env);
		}
		pidl "break;";
		deindent;
	}
	if (! $have_default) {
		pidl "default:";
		indent;
		pidl "return ejs_panic(ejs, \"Bad switch value\");";
		deindent;
	}
	deindent;
	pidl "}";
	pidl "return NT_STATUS_OK;";
	deindent;
	pidl "}";
}

##############################################
# put the enum elements in the constants array
sub EjsEnumConstant($)
{
	my $d = shift;
	my $v = 0;
	foreach my $e (@{$d->{ELEMENTS}}) {
		my $el = $e;
		chomp $el;
		if ($el =~ /^(.*)=\s*(.*)\s*$/) {
			$el = $1;
			$v = $2;
		}
		$constants{$el} = $v;
		$v++;
	}
}

###########################
# pull a enum
sub EjsEnumPull($$)
{
	my $name = shift;
	my $d = shift;
	EjsEnumConstant($d);
	fn_declare($d, "NTSTATUS ejs_pull_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, enum $name *r)");
	pidl "{";
	indent;
	pidl "unsigned e;";
	pidl "NDR_CHECK(ejs_pull_enum(ejs, v, name, &e));";
	pidl "*r = e;";
	pidl "return NT_STATUS_OK;";
	deindent;
	pidl "}\n";
}

###########################
# pull a bitmap
sub EjsBitmapPull($$)
{
	my $name = shift;
	my $d = shift;
	my $type_fn = $d->{BASE_TYPE};
	my($type_decl) = Parse::Pidl::Typelist::mapType($d->{BASE_TYPE});
	fn_declare($d, "NTSTATUS ejs_pull_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, $type_decl *r)");
	pidl "{";
	indent;
	pidl "return ejs_pull_$type_fn(ejs, v, name, r);";
	deindent;
	pidl "}";
}


###########################
# generate a structure pull
sub EjsTypedefPull($)
{
	my $d = shift;
	return if (has_property($d, "noejs"));
	if ($d->{DATA}->{TYPE} eq 'STRUCT') {
		EjsStructPull($d->{NAME}, $d->{DATA});
	} elsif ($d->{DATA}->{TYPE} eq 'UNION') {
		EjsUnionPull($d->{NAME}, $d->{DATA});
	} elsif ($d->{DATA}->{TYPE} eq 'ENUM') {
		EjsEnumPull($d->{NAME}, $d->{DATA});
	} elsif ($d->{DATA}->{TYPE} eq 'BITMAP') {
		EjsBitmapPull($d->{NAME}, $d->{DATA});
	} else {
		warn "Unhandled pull typedef $d->{NAME} of type $d->{DATA}->{TYPE}";
	}
}

#####################
# generate a function
sub EjsPullFunction($)
{
	my $d = shift;
	my $env = GenerateFunctionInEnv($d);
	my $name = $d->{NAME};

	pidl "\nstatic NTSTATUS ejs_pull_$name(struct ejs_rpc *ejs, struct MprVar *v, struct $name *r)";
	pidl "{";
	indent;
	pidl "NDR_CHECK(ejs_pull_struct_start(ejs, &v, \"input\"));";

	# we pull non-array elements before array elements as arrays
	# may have length_is() or size_is() properties that depend
	# on the non-array elements
	foreach my $e (@{$d->{ELEMENTS}}) {
		next unless (grep(/in/, @{$e->{DIRECTION}}));
		next if (has_property($e, "length_is") || 
			 has_property($e, "size_is"));
		EjsPullElementTop($e, $env);
	}

	foreach my $e (@{$d->{ELEMENTS}}) {
		next unless (grep(/in/, @{$e->{DIRECTION}}));
		next unless (has_property($e, "length_is") || 
			     has_property($e, "size_is"));
		EjsPullElementTop($e, $env);
	}

	pidl "return NT_STATUS_OK;";
	deindent;
	pidl "}\n";
}


###########################
# push a scalar element
sub EjsPushScalar($$$$$)
{
	my ($e, $l, $var, $name, $env) = @_;
        # have to handle strings specially :(
        my $pl = Parse::Pidl::NDR::GetPrevLevel($e, $l);
        if ($e->{TYPE} ne "string" || ($pl && $pl->{TYPE} eq "POINTER")) {
                $var = get_pointer_to($var);
        }
	pidl "NDR_CHECK(ejs_push_$e->{TYPE}(ejs, v, $name, $var));";
}

###########################
# push a string element
sub EjsPushString($$$$$)
{
	my ($e, $l, $var, $name, $env) = @_;
	pidl "NDR_CHECK(ejs_push_string(ejs, v, $name, $var));";
}

###########################
# push a pointer element
sub EjsPushPointer($$$$$)
{
	my ($e, $l, $var, $name, $env) = @_;
	pidl "if (NULL == $var) {";
	indent;
	pidl "NDR_CHECK(ejs_push_null(ejs, v, $name));";
	deindent;
	pidl "} else {";
	indent;
	$var = get_value_of($var);		
	EjsPushElement($e, Parse::Pidl::NDR::GetNextLevel($e, $l), $var, $name, $env);
	deindent;
	pidl "}";
}

###########################
# push a switch element
sub EjsPushSwitch($$$$$)
{
	my ($e, $l, $var, $name, $env) = @_;
	my $switch_var = Parse::Pidl::Util::ParseExpr($l->{SWITCH_IS}, $env);
	pidl "ejs_set_switch(ejs, $switch_var);";
	EjsPushElement($e, Parse::Pidl::NDR::GetNextLevel($e, $l), $var, $name, $env);
}


###########################
# push an array element
sub EjsPushArray($$$$$)
{
	my ($e, $l, $var, $name, $env) = @_;
	my $nl = Parse::Pidl::NDR::GetNextLevel($e, $l);
	my $length = Parse::Pidl::Util::ParseExpr($l->{LENGTH_IS}, $env);
	my $pl = Parse::Pidl::NDR::GetPrevLevel($e, $l);
	if ($pl && $pl->{TYPE} eq "POINTER") {
		$var = get_pointer_to($var);
	}
	# uint8 arrays are treated as data blobs
	if ($nl->{TYPE} eq 'DATA' && $e->{TYPE} eq 'uint8') {
		pidl "ejs_push_array_uint8(ejs, v, $name, $var, $length);";
		return;
	}
	my $avar = $var . "[i]";
	pidl "{";
	indent;
	pidl "uint32_t i;";
	pidl "for (i=0;i<$length;i++) {";
	indent;
	pidl "const char *id = talloc_asprintf(ejs, \"%s.%u\", $name, i);";
	EjsPushElement($e, $nl, $avar, "id", $env);
	deindent;
	pidl "}";
	pidl "ejs_push_uint32(ejs, v, $name \".length\", &i);";
	deindent;
	pidl "}";
}

################################
# push a structure/union element
sub EjsPushElement($$$$$)
{
	my ($e, $l, $var, $name, $env) = @_;
	if (has_property($e, "charset")) {
		EjsPushString($e, $l, $var, $name, $env);
	} elsif ($l->{TYPE} eq "ARRAY") {
		EjsPushArray($e, $l, $var, $name, $env);
	} elsif ($l->{TYPE} eq "DATA") {
		EjsPushScalar($e, $l, $var, $name, $env);
	} elsif (($l->{TYPE} eq "POINTER")) {
		EjsPushPointer($e, $l, $var, $name, $env);
	} elsif (($l->{TYPE} eq "SWITCH")) {
		EjsPushSwitch($e, $l, $var, $name, $env);
	} else {
		pidl "return ejs_panic(ejs, \"unhandled push type $l->{TYPE}\");";
	}
}

#############################################
# push a structure/union element at top level
sub EjsPushElementTop($$)
{
	my $e = shift;
	my $env = shift;
	my $l = $e->{LEVELS}[0];
	my $var = Parse::Pidl::Util::ParseExpr($e->{NAME}, $env);
	my $name = "\"$e->{NAME}\"";
	EjsPushElement($e, $l, $var, $name, $env);
}

###########################
# push a struct
sub EjsStructPush($$)
{
	my $name = shift;
	my $d = shift;
	my $env = GenerateStructEnv($d);
	fn_declare($d, "NTSTATUS ejs_push_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, const struct $name *r)");
	pidl "{";
	indent;
	pidl "NDR_CHECK(ejs_push_struct_start(ejs, &v, name));";
        foreach my $e (@{$d->{ELEMENTS}}) {
		EjsPushElementTop($e, $env);
	}
	pidl "return NT_STATUS_OK;";
	deindent;
	pidl "}\n";
}

###########################
# push a union
sub EjsUnionPush($$)
{
	my $name = shift;
	my $d = shift;
	my $have_default = 0;
	my $env = GenerateStructEnv($d);
	fn_declare($d, "NTSTATUS ejs_push_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, const union $name *r)");
	pidl "{";
	indent;
	pidl "NDR_CHECK(ejs_push_struct_start(ejs, &v, name));";
	pidl "switch (ejs->switch_var) {";
	indent;
	foreach my $e (@{$d->{ELEMENTS}}) {
		if ($e->{CASE} eq "default") {
			$have_default = 1;
		}
		pidl "$e->{CASE}:";
		indent;
		if ($e->{TYPE} ne "EMPTY") {
			EjsPushElementTop($e, $env);
		}
		pidl "break;";
		deindent;
	}
	if (! $have_default) {
		pidl "default:";
		indent;
		pidl "return ejs_panic(ejs, \"Bad switch value\");";
		deindent;
	}
	deindent;
	pidl "}";
	pidl "return NT_STATUS_OK;";
	deindent;
	pidl "}";
}

###########################
# push a enum
sub EjsEnumPush($$)
{
	my $name = shift;
	my $d = shift;
	EjsEnumConstant($d);
	fn_declare($d, "NTSTATUS ejs_push_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, const enum $name *r)");
	pidl "{";
	indent;
	pidl "unsigned e = *r;";
	pidl "NDR_CHECK(ejs_push_enum(ejs, v, name, &e));";
	pidl "return NT_STATUS_OK;";
	deindent;
	pidl "}\n";
}

###########################
# push a bitmap
sub EjsBitmapPush($$)
{
	my $name = shift;
	my $d = shift;
	my $type_fn = $d->{BASE_TYPE};
	my($type_decl) = Parse::Pidl::Typelist::mapType($d->{BASE_TYPE});
	# put the bitmap elements in the constants array
	foreach my $e (@{$d->{ELEMENTS}}) {
		if ($e =~ /^(\w*)\s*(.*)\s*$/) {
			my $bname = $1;
			my $v = $2;
			$constants{$bname} = $v;
		}
	}
	fn_declare($d, "NTSTATUS ejs_push_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, const $type_decl *r)");
	pidl "{";
	indent;
	pidl "return ejs_push_$type_fn(ejs, v, name, r);";
	deindent;
	pidl "}";
}


###########################
# generate a structure push
sub EjsTypedefPush($)
{
	my $d = shift;
	return if (has_property($d, "noejs"));

	if ($d->{DATA}->{TYPE} eq 'STRUCT') {
		EjsStructPush($d->{NAME}, $d->{DATA});
	} elsif ($d->{DATA}->{TYPE} eq 'UNION') {
		EjsUnionPush($d->{NAME}, $d->{DATA});
	} elsif ($d->{DATA}->{TYPE} eq 'ENUM') {
		EjsEnumPush($d->{NAME}, $d->{DATA});
	} elsif ($d->{DATA}->{TYPE} eq 'BITMAP') {
		EjsBitmapPush($d->{NAME}, $d->{DATA});
	} else {
		warn "Unhandled push typedef $d->{NAME} of type $d->{DATA}->{TYPE}";
	}
}


#####################
# generate a function
sub EjsPushFunction($)
{
	my $d = shift;
	my $env = GenerateFunctionOutEnv($d);

	pidl "\nstatic NTSTATUS ejs_push_$d->{NAME}(struct ejs_rpc *ejs, struct MprVar *v, const struct $d->{NAME} *r)";
	pidl "{";
	indent;
	pidl "NDR_CHECK(ejs_push_struct_start(ejs, &v, \"output\"));";

	foreach my $e (@{$d->{ELEMENTS}}) {
		next unless (grep(/out/, @{$e->{DIRECTION}}));
		EjsPushElementTop($e, $env);
	}

	if ($d->{RETURN_TYPE}) {
		my $t = $d->{RETURN_TYPE};
		pidl "NDR_CHECK(ejs_push_$t(ejs, v, \"result\", &r->out.result));";
	}

	pidl "return NT_STATUS_OK;";
	deindent;
	pidl "}\n";
}


#################################
# generate a ejs mapping function
sub EjsFunction($$)
{
	my $d = shift;
	my $iface = shift;
	my $name = $d->{NAME};
	my $callnum = uc("DCERPC_$name");
	my $table = "&dcerpc_table_$iface";

	pidl "static int ejs_$name(int eid, int argc, struct MprVar **argv)";
	pidl "{";
	indent;
	pidl "return ejs_rpc_call(eid, argc, argv, $table, $callnum, (ejs_pull_function_t)ejs_pull_$name, (ejs_push_function_t)ejs_push_$name);";
	deindent;
	pidl "}\n";
}

###################
# handle a constant
sub EjsConst($)
{
    my $const = shift;
    $constants{$const->{NAME}} = $const->{VALUE};
}

#####################################################################
# parse the interface definitions
sub EjsInterface($$)
{
	my($interface,$needed) = @_;
	my @fns = ();
	my $name = $interface->{NAME};

	%constants = ();

	pidl_hdr "#ifndef _HEADER_EJS_$interface->{NAME}\n";
	pidl_hdr "#define _HEADER_EJS_$interface->{NAME}\n\n";

	if (has_property($interface, "depends")) {
		foreach (split / /, $interface->{PROPERTIES}->{depends}) {
			pidl_hdr "#include \"librpc/gen_ndr/ndr_$_\_ejs\.h\"\n";
		}
	}

	pidl_hdr "\n";

	foreach my $d (@{$interface->{TYPES}}) {
		($needed->{"push_$d->{NAME}"}) && EjsTypedefPush($d);
		($needed->{"pull_$d->{NAME}"}) && EjsTypedefPull($d);
	}

	foreach my $d (@{$interface->{FUNCTIONS}}) {
		next if not defined($d->{OPNUM});
		next if Parse::Pidl::Util::has_property($d, "noejs");

		EjsPullFunction($d);
		EjsPushFunction($d);
		EjsFunction($d, $name);

		push (@fns, $d->{NAME});
	}

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

	pidl "static int ejs_$name\_init(int eid, int argc, struct MprVar **argv)";
	pidl "{";
	indent;
	pidl "struct MprVar *obj = mprInitObject(eid, \"$name\", argc, argv);";
	foreach (@fns) {
		pidl "mprSetCFunction(obj, \"$_\", ejs_$_);";
	}
	foreach my $v (keys %constants) {
		my $value = $constants{$v};
		if (substr($value, 0, 1) eq "\"") {
			pidl "mprSetVar(obj, \"$v\", mprString($value));";
		} else {
			pidl "mprSetVar(obj, \"$v\", mprCreateNumberVar($value));";
		}
	}
	pidl "return ejs_rpc_init(obj, \"$name\");";
	deindent;
	pidl "}\n";

	pidl "NTSTATUS ejs_init_$name(void)";
	pidl "{";
	indent;
	pidl "ejsDefineCFunction(-1, \"$name\_init\", ejs_$name\_init, NULL, MPR_VAR_SCRIPT_HANDLE);";
	pidl "return NT_STATUS_OK;";
	deindent;
	pidl "}";

	pidl_hdr "\n";
	pidl_hdr "#endif /* _HEADER_EJS_$interface->{NAME} */\n";
}

#####################################################################
# parse a parsed IDL into a C header
sub Parse($$)
{
    my($ndr,$hdr) = @_;
    
    my $ejs_hdr = $hdr;
    $ejs_hdr =~ s/.h$/_ejs.h/;
    $res = "";
	$res_hdr = "";

    pidl_hdr "/* header auto-generated by pidl */\n\n";
	
    pidl "
/* EJS wrapper functions auto-generated by pidl */
#include \"includes.h\"
#include \"lib/appweb/ejs/ejs.h\"
#include \"scripting/ejs/ejsrpc.h\"
#include \"scripting/ejs/smbcalls.h\"
#include \"librpc/gen_ndr/ndr_misc_ejs.h\"
#include \"$hdr\"
#include \"$ejs_hdr\"

";

    my %needed = ();

    foreach my $x (@{$ndr}) {
	    ($x->{TYPE} eq "INTERFACE") && NeededInterface($x, \%needed);
    }

    foreach my $x (@{$ndr}) {
	    ($x->{TYPE} eq "INTERFACE") && EjsInterface($x, \%needed);
    }

    return ($res_hdr, $res);
}

sub NeededFunction($$)
{
	my ($fn,$needed) = @_;
	$needed->{"pull_$fn->{NAME}"} = 1;
	$needed->{"push_$fn->{NAME}"} = 1;
	foreach my $e (@{$fn->{ELEMENTS}}) {
		if (grep (/in/, @{$e->{DIRECTION}})) {
			$needed->{"pull_$e->{TYPE}"} = 1;
		}
		if (grep (/out/, @{$e->{DIRECTION}})) {
			$needed->{"push_$e->{TYPE}"} = 1;
		}
	}
}

sub NeededTypedef($$)
{
	my ($t,$needed) = @_;
	if (Parse::Pidl::Util::has_property($t, "public")) {
		$needed->{"pull_$t->{NAME}"} = not Parse::Pidl::Util::has_property($t, "noejs");
		$needed->{"push_$t->{NAME}"} = not Parse::Pidl::Util::has_property($t, "noejs");
	}
	if ($t->{DATA}->{TYPE} ne "STRUCT" && 
	    $t->{DATA}->{TYPE} ne "UNION") {
		return;
	}
	for my $e (@{$t->{DATA}->{ELEMENTS}}) {
		if ($needed->{"pull_$t->{NAME}"}) {
			$needed->{"pull_$e->{TYPE}"} = 1;
		}
		if ($needed->{"push_$t->{NAME}"}) {
			$needed->{"push_$e->{TYPE}"} = 1;
		}
	}
}

#####################################################################
# work out what parse functions are needed
sub NeededInterface($$)
{
	my ($interface,$needed) = @_;
	foreach my $d (@{$interface->{FUNCTIONS}}) {
	    NeededFunction($d, $needed);
	}
	foreach my $d (reverse @{$interface->{TYPES}}) {
	    NeededTypedef($d, $needed);
	}
}

1;