###################################################
# Samba4 parser generator for IDL structures
# Copyright jelmer@samba.org 2005
# released under the GNU GPL

package Parse::Pidl::Typelist;

require Exporter;
@ISA = qw(Exporter);
@EXPORT_OK = qw(hasType getType mapType);
use vars qw($VERSION);
$VERSION = '0.01';

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

my %typedefs = ();

# a list of known scalar types
my $scalars = {
	# 0 byte types
	"void"		=> {
				C_TYPE		=> "void",
				IS_REFERENCE	=> 0,
			},

	# 1 byte types
	"char"		=> {
				C_TYPE		=> "char",
				IS_REFERENCE	=> 0,
			},
	"int8"		=> {
				C_TYPE		=> "int8_t",
				IS_REFERENCE	=> 0,
			},
	"uint8"		=> {
				C_TYPE		=> "uint8_t",
				IS_REFERENCE	=> 0,
			},

	# 2 byte types
	"int16"		=> {
				C_TYPE		=> "int16_t",
				IS_REFERENCE	=> 0,
			},
	"uint16"	=> {	C_TYPE		=> "uint16_t",
				IS_REFERENCE	=> 0,
			},

	# 4 byte types
	"int32"		=> {
				C_TYPE		=> "int32_t",
				IS_REFERENCE	=> 0,
			},
	"uint32"	=> {	C_TYPE		=> "uint32_t",
				IS_REFERENCE	=> 0,
			},

	# 8 byte types
	"hyper"		=> {
				C_TYPE		=> "uint64_t",
				IS_REFERENCE	=> 0,
			},
	"dlong"		=> {
				C_TYPE		=> "int64_t",
				IS_REFERENCE	=> 0,
			},
	"udlong"	=> {
				C_TYPE		=> "uint64_t",
				IS_REFERENCE	=> 0,
			},
	"udlongr"	=> {
				C_TYPE		=> "uint64_t",
				IS_REFERENCE	=> 0,
			},

	# DATA_BLOB types
	"DATA_BLOB"	=> {
				C_TYPE		=> "DATA_BLOB",
				IS_REFERENCE	=> 0,
			},

	# string types
	"string"	=> {
				C_TYPE		=> "const char *",
				IS_REFERENCE	=> 1,
			},
	"string_array"	=> {
				C_TYPE		=> "const char **",
				IS_REFERENCE	=> 1,
			},

	# time types
	"time_t"	=> {
				C_TYPE		=> "time_t",
				IS_REFERENCE	=> 0,
			},
	"NTTIME"	=> {
				C_TYPE		=> "NTTIME",
				IS_REFERENCE	=> 0,
			},
	"NTTIME_1sec"	=> {
				C_TYPE		=> "NTTIME",
				IS_REFERENCE	=> 0,
			},
	"NTTIME_hyper"	=> {
				C_TYPE		=> "NTTIME",
				IS_REFERENCE	=> 0,
			},


	# error code types
	"WERROR"	=> {
				C_TYPE		=> "WERROR",
				IS_REFERENCE	=> 0,
			},
	"NTSTATUS"	=> {
				C_TYPE		=> "NTSTATUS",
				IS_REFERENCE	=> 0,
			},
	"COMRESULT" => { 
				C_TYPE		=> "COMRESULT",
				IS_REFERENCE	=> 0,
			},

	# special types
	"nbt_string"	=> {
				C_TYPE		=> "const char *",
				IS_REFERENCE	=> 1,
			},
	"wrepl_nbt_name"=> {
				C_TYPE		=> "struct nbt_name *",
				IS_REFERENCE	=> 1,
			},
	"ipv4address"	=> {
				C_TYPE		=> "const char *",
				IS_REFERENCE	=> 1,
			}
};

# map from a IDL type to a C header type
sub mapScalarType($)
{
	my $name = shift;

	# it's a bug when a type is not in the list
	# of known scalars or has no mapping
	return $typedefs{$name}->{DATA}->{C_TYPE} if defined($typedefs{$name}) and defined($typedefs{$name}->{DATA}->{C_TYPE});

	die("Unknown scalar type $name");
}

sub addType($)
{
	my $t = shift;
	$typedefs{$t->{NAME}} = $t;
}

sub getType($)
{
	my $t = shift;
	return undef if not hasType($t);
	return $typedefs{$t};
}

sub typeIs($$)
{
	my $t = shift;
	my $tt = shift;

	return 1 if (hasType($t) and getType($t)->{DATA}->{TYPE} eq $tt);
	return 0;
}

sub hasType($)
{
	my $t = shift;
	return 1 if defined($typedefs{$t});
	return 0;
}

sub is_scalar($)
{
	my $type = shift;

	return 0 unless(hasType($type));

	if (my $dt = getType($type)->{DATA}->{TYPE}) {
		return 1 if ($dt eq "SCALAR" or $dt eq "ENUM" or $dt eq "BITMAP");
	}

	return 0;
}

sub scalar_is_reference($)
{
	my $name = shift;

	return $scalars->{$name}{IS_REFERENCE} if defined($scalars->{$name}) and defined($scalars->{$name}{IS_REFERENCE});
	return 0;
}

sub RegisterScalars()
{
	foreach my $k (keys %{$scalars}) {
		$typedefs{$k} = {
			NAME => $k,
			TYPE => "TYPEDEF",
			DATA => $scalars->{$k}
		};
		$typedefs{$k}->{DATA}->{TYPE} = "SCALAR";
		$typedefs{$k}->{DATA}->{NAME} = $k;
	}
}

my $aliases = {
	"DWORD" => "uint32",
	"int" => "int32",
	"WORD" => "uint16",
	"char" => "uint8",
	"long" => "int32",
	"short" => "int16",
	"HYPER_T" => "hyper",
	"HRESULT" => "COMRESULT",
};

sub RegisterAliases()
{
	foreach my $k (keys %{$aliases}) {
		$typedefs{$k} = $typedefs{$aliases->{$k}};
	}
}

sub enum_type_fn($)
{
	my $enum = shift;
	if (has_property($enum->{PARENT}, "enum8bit")) {
		return "uint8";
	} elsif (has_property($enum->{PARENT}, "v1_enum")) {
		return "uint32";
	}
	return "uint16";
}

sub bitmap_type_fn($)
{
	my $bitmap = shift;

	if (has_property($bitmap, "bitmap8bit")) {
		return "uint8";
	} elsif (has_property($bitmap, "bitmap16bit")) {
		return "uint16";
	} elsif (has_property($bitmap, "bitmap64bit")) {
		return "hyper";
	}
	return "uint32";
}

sub mapType($)
{
	my $t = shift;
	die("Undef passed to mapType") unless defined($t);
	my $dt;

	unless ($dt or ($dt = getType($t))) {
		# Best guess
		return "struct $t";
	}
	return mapScalarType($t) if ($dt->{DATA}->{TYPE} eq "SCALAR");
	return "enum $dt->{NAME}" if ($dt->{DATA}->{TYPE} eq "ENUM");
	return "struct $dt->{NAME}" if ($dt->{DATA}->{TYPE} eq "STRUCT");
	return "struct $dt->{NAME}" if ($dt->{DATA}->{TYPE} eq "INTERFACE");
	return "union $dt->{NAME}" if ($dt->{DATA}->{TYPE} eq "UNION");

	if ($dt->{DATA}->{TYPE} eq "BITMAP") {
		return mapScalarType(bitmap_type_fn($dt->{DATA}));
	}

	die("Unknown type $dt->{DATA}->{TYPE}");
}

sub LoadIdl($)
{
	my $idl = shift;

	foreach my $x (@{$idl}) {
		next if $x->{TYPE} ne "INTERFACE";

		# DCOM interfaces can be types as well
		addType({
			NAME => $x->{NAME},
			TYPE => "TYPEDEF",
			DATA => $x
			}) if (has_property($x, "object"));

		foreach my $y (@{$x->{DATA}}) {
			addType($y) if (
				$y->{TYPE} eq "TYPEDEF" 
			     or $y->{TYPE} eq "DECLARE");
		}
	}
}

RegisterScalars();
RegisterAliases();

1;
